You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
rive_native: support environments that prohibit runtime executable memory for FFI callbacks #656
This is present in the latest rive (0.14.11) and rive_native (0.1.11) packages.
I searched the documentation, forums, and existing issues.
I found no existing issue covering this.
Description
Rive Flutter requires the Dart VM to allocate executable memory for FFI callback trampolines. On GrapheneOS, an app using Rive aborts when the per-app Dynamic code loading from memory restriction is enabled:
Normal Rive usage reaches Dart FFI callbacks, not only optional scripting APIs:
FFIRiveFile in rive_native/lib/src/ffi/rive_ffi.dart registers the view-model callbacks with Pointer.fromFunction when a .riv file is decoded.
FFIFlutterFactory in rive_native/lib/src/ffi/flutter_renderer_ffi.dart registers its renderer callbacks with Pointer.fromFunction.
Other APIs use NativeCallable, including state-machine, asset-loader, scripting, and Luau callbacks.
Despite accepting only static functions, Pointer.fromFunction uses the same Dart VM FFI callback trampoline mechanism as NativeCallable, as confirmed in sqlite3.dart#396. The VM allocates a trampoline page and changes it to executable (Dart VM source). When that operation is prohibited, the failure is fatal and cannot be caught by the Flutter application.
Reproduction
A Linux machine with systemd can reproduce the restriction without GrapheneOS. In a minimal Flutter app, initialize Rive and decode a bundled .riv file:
await rive.RiveNative.init();
final data =await rootBundle.load('assets/example.riv');
await rive.File.decode(
data.buffer.asUint8List(),
riveFactory: rive.Factory.flutter,
);
Build the Linux application, then run it with executable-memory creation disabled:
flutter build linux --release
systemd-run --user --scope --property=MemoryDenyWriteExecute=yes \
./build/linux/x64/release/bundle/<app-name>
Loading the Rive file triggers the mprotect failure. The same restriction is exposed on GrapheneOS as Dynamic code loading from memory.
Expected behavior
Rive Flutter should provide a path that does not require Dart FFI callback trampolines or newly executable anonymous mappings, so ordinary .riv loading and rendering work under MemoryDenyWriteExecute=yes and the equivalent GrapheneOS restriction.
Replacing NativeCallable with Pointer.fromFunction is not sufficient; the callback boundary needs a statically compiled native bridge or another design that avoids Dart FFI callbacks requiring runtime executable memory.
Environment
rive: 0.14.11
rive_native: 0.1.11
Flutter: 3.38.10
Dart: 3.10.9
Affected: Android with GrapheneOS memory DCL disabled; reproducible on Linux with MemoryDenyWriteExecute=yes
Activity