Use of dart:ffi (memory-unsafe native FFI surface)

ID

dart.platform.dart_ffi_usage

Severity

low

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Platform

Language

Dart

Description

The code uses dart:ffi to load and call native libraries (DynamicLibrary.open, DynamicLibrary.process, DynamicLibrary.executable, lookupFunction, lookup, asFunction). FFI bypasses Dart’s safety guarantees and exposes memory-unsafe surfaces — the rule is informational and flags FFI call sites for manual audit.

Rationale

Dart’s safety story relies on the VM enforcing memory safety, bounds checking, and type checking on every operation. dart:ffi is an opt-out from that guarantee: it exposes raw pointers, manual memory management (malloc / calloc / free), and unchecked function-pointer casts. Every FFI call site is a potential source of:

  • Memory-safety bugs: use-after-free, double-free, buffer overflow, type-confusion. These are unreachable from pure Dart; they re-enter the picture as soon as dart:ffi is used.

  • Library loading attacks: DynamicLibrary.open(userPath) loads any path the attacker controls (CWE-829, untrusted code inclusion).

  • ABI / calling-convention mismatches: a signature mismatch between lookupFunction<NativeT, DartT>(…​) and the real native symbol produces silent stack / register corruption.

Cossack Labs recommends preferring Platform Channels over FFI for Flutter ↔ native interop: the channel layer is type-checked, message-based, and easier to audit.

The following code illustrates a vulnerable pattern detected by this rule:

class FfiSample {
  void load() {
    // VULNERABLE: Use of dart:ffi (memory-unsafe native FFI surface)
    final lib = DynamicLibrary.open('libfoo.so');
    // VULNERABLE: Use of dart:ffi (memory-unsafe native FFI surface)
    final processLib = DynamicLibrary.process();
    // VULNERABLE: Use of dart:ffi (memory-unsafe native FFI surface)
    final execLib = DynamicLibrary.executable();
    // VULNERABLE: Use of dart:ffi (memory-unsafe native FFI surface)
    final add = lib.lookupFunction<AddNative, AddDart>('add');
    final addPtr = lib.lookup<NativeFunction<AddNative>>('add');
    // VULNERABLE: Use of dart:ffi (memory-unsafe native FFI surface)
    final addFn = addPtr.asFunction<AddDart>();
    print('$processLib $execLib ${add(1, 2)} ${addFn(3, 4)}');
  }
}

Remediation

Audit every FFI call site:

  • Load only hardcoded, bundled library paths — never an attacker- controlled string in DynamicLibrary.open.

  • Use @Native<…​>(symbol: 'foo') annotations (Dart 3.5+) instead of runtime lookupFunction — the symbol and signature are compile-time validated.

  • Validate pointer arguments (non-null, length bounds) before crossing the FFI boundary in either direction.

  • Prefer Platform Channels (MethodChannel) for any Flutter ↔ native communication that doesn’t require zero-copy performance.

// Risky: runtime lookup, untrusted path
final lib = DynamicLibrary.open(userPath);
final add = lib.lookupFunction<Int32 Function(Int32), int Function(int)>('add');

// Safer: bundled library + Dart 3.5+ @Native annotation
@Native<Int32 Function(Int32)>(symbol: 'add')
external int add(int x);

Configuration

This detector does not need any configuration.

References

© 2026 Xygeni. All rights reserved