Avoid Native Calls

ID

swift.avoid_native_calls

Severity

info

Resource

Api

Language

Swift

Tags

CWE:242, NIST.SP.800-53, PCI-DSS:6.5.6

Description

This rule highlights the risks of using native C / Objective-C runtime functions in Swift applications, including security vulnerabilities, decreased type safety, and reduced portability. Native calls bypass Swift’s type and memory safety features, introducing potential vulnerabilities.

Rationale

Calling native functions can introduce security vulnerabilities and bypass Swift’s safety features:

  • Memory Safety: Native C functions can cause buffer overflows, use-after-free, and memory corruption

  • Type Safety: Direct runtime manipulation bypasses Swift’s strong type system

  • Portability: Platform-specific APIs (Darwin, Glibc) reduce cross-platform compatibility

  • Maintainability: Low-level APIs are harder to understand and maintain

  • ARC Bypass: Manual memory management with Objective-C runtime can cause leaks or crashes

This detector does NOT flag usages of Objective-C bridging types from Foundation (NS* types). They are safe and normal in Swift development.

Unsafe Patterns

The following native API categories are flagged by this rule:


  • Swift Runtime (undocumented functions)

import Swift

// FLAGGED: Undocumented Swift runtime function
if let type = _typeByName("MyApp.SomeClass") {
    // Direct access to Swift runtime internals
    let instance = _swift_allocObject(type, 32, 7)
}

// FLAGGED: Manual type metadata manipulation
let metadata = swift_getTypeByMangledNameInContext(
    "$s4MyApp9SomeClassCMn",
    256,
    context
)

These undocumented APIs can break between Swift versions and bypass type safety.


  • Objective-C Runtime

import ObjectiveC

// FLAGGED: Direct Objective-C runtime manipulation
let classObject = objc_getClass("UIViewController")
let selector = sel_registerName("viewDidLoad")
let method = class_getInstanceMethod(classObject, selector)

// FLAGGED: Direct ivar manipulation (bypasses property observers)
let object = NSObject()
object_setIvar(object, ivar, newValue)

// FLAGGED: Method swizzling
method_exchangeImplementations(originalMethod, swizzledMethod)

Objective-C runtime APIs bypass Swift’s access control, type checking, and memory safety.


  • C Standard Library (stdio, etc.)

import Darwin  // or Glibc on Linux

// FLAGGED: Direct C stdio usage (format string vulnerabilities)
printf(userInput)  // Format string injection risk
sprintf(buffer, userInput)  // Buffer overflow risk

// FLAGGED: Native, and also insecure PRNG
let random = arc4random()

// FLAGGED: Unsafe C string operations
let cString = UnsafeMutablePointer<CChar>.allocate(capacity: 100)
strcpy(cString, userInput)  // Buffer overflow risk

C standard library functions lack bounds checking and can cause memory corruption.


  • Darwin/Glibc Platform APIs

#if os(macOS) || os(iOS)
import Darwin

// FLAGGED: Direct syscall
syscall(SYS_open, path, flags)

// FLAGGED: Low-level memory operations
mmap(nil, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)
#else
import Glibc

// FLAGGED: Linux-specific syscalls
syscall(__NR_open, path, flags)
#endif

Direct system calls bypass platform abstractions and security checks.

Remediation

To address these risks, prefer Swift APIs and avoid direct native calls:


  • Option 1: Use Swift Standard Library

// Instead of: printf(userInput)
print(userInput)  // Safe, no format string vulnerabilities

// Instead of: sprintf(buffer, format, args)
let formatted = String(format: "%d items", count)  // Safe, bounds-checked

// Instead of: arc4random()
import CryptoKit
let randomBytes = SystemRandomNumberGenerator().next()  // Cryptographically secure

  • Option 2: Use Foundation APIs

// Instead of: objc_getClass() runtime manipulation
// Use proper Swift types and protocols
protocol ConfigurableViewController {
    func configure()
}

class MyViewController: UIViewController, ConfigurableViewController {
    func configure() {
        // Type-safe configuration
    }
}

// Instead of: object_setIvar()
// Use proper properties with observers
class MyClass {
    var value: String {
        didSet {
            print("Value changed to \(value)")
        }
    }
}

  • Option 3: Safe File I/O

// Instead of: fopen/fread/fclose
import Foundation

// SAFE: Using Foundation's file handling
do {
    let contents = try String(contentsOf: fileURL, encoding: .utf8)
    // Process contents safely
} catch {
    // Proper error handling
}

// SAFE: Writing with FileHandle
let data = content.data(using: .utf8)!
try data.write(to: fileURL, options: .atomic)

  • Option 4: Type-Safe Reflection

// Instead of: _typeByName() or NSClassFromString()
// Use generic types and protocols
protocol Instantiable {
    init()
}

func createInstance<T: Instantiable>(_ type: T.Type) -> T {
    return type.init()
}

// Or use Swift's Mirror for read-only reflection
let mirror = Mirror(reflecting: instance)
for child in mirror.children {
    print("\(child.label ?? ""): \(child.value)")
}

  • Option 5: Secure Random Numbers

// Instead of: arc4random() or drand48()
import CryptoKit

// SAFE: Cryptographically secure random
var randomBytes = [UInt8](repeating: 0, count: 32)
let status = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes)

// Or use SystemRandomNumberGenerator
var rng = SystemRandomNumberGenerator()
let randomInt = rng.next()

When Native Calls Are Necessary…​

If native calls are unavoidable:

  1. Isolate Native Code: Wrap native calls in a dedicated module with well-defined interfaces

  2. Input Validation: Strictly validate all inputs before passing to native code

  3. Memory Safety: Use UnsafePointer family with extreme care and proper deallocation

  4. Error Handling: Implement comprehensive error handling for all native calls

  5. Security Review: Conduct thorough security reviews of native integration points

  6. Documentation: Clearly document why native calls are necessary and their risks

  7. Testing: Write extensive unit and integration tests for native code boundaries

// Example: Safe wrapper for native functionality
struct SafeNativeWrapper {
    /// Validates input before calling native function
    static func performNativeOperation(input: String) throws -> Result {
        // 1. Input validation
        guard input.utf8.count < 1024 else {
            throw ValidationError.inputTooLarge
        }

        guard !input.contains("\0") else {
            throw ValidationError.nullByteDetected
        }

        // 2. Safe conversion to C string
        return input.withCString { cString in
            // 3. Isolated native call
            let result = native_function(cString)

            // 4. Validate result
            guard result >= 0 else {
                throw NativeError.operationFailed
            }

            return Result(value: result)
        }
    }
}

When writing platform-specific code, you may use conditional compilation checking if there is available a given framework or under a given platform:

// Prefer feature checks over direct native calls
#if canImport(Security)
import Security
// Use Security framework (safe APIs)
SecRandomCopyBytes(kSecRandomDefault, count, &bytes)
#endif

// Avoid direct Darwin/Glibc calls
#if os(macOS)
// Prefer Foundation/AppKit/Security frameworks
// over direct Darwin syscalls
#endif

Configuration

This detector does not need any specific configuration.

References