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:ffiis 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 runtimelookupFunction— 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);
References
-
OWASP Top 10 2025 - A03 : Software Supply Chain Failures.
-
CWE-119 — Improper Restriction of Operations within the Bounds of a Memory Buffer
-
Cossack Labs — Flutter application security considerations — Notes that FFI is memory-unsafe and recommends Platform Channels where possible
-
dart:ffi library — Dart API docs — Reference for
DynamicLibrary,lookupFunction,asFunction,Native