rive-app / rive-app/rive-flutter
rive_native: support environments that prohibit runtime executable memory for FFI callbacks
Nobody has claimed this yet.
- Dominant language
- Dart
- Stars
- 1.5k
- Forks
- 240
- PR merge metrics
- No merged PRs in 30d
Description
Submission checklist
- This is present in the latest
rive(0.14.11) andrive_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:
virtual_memory_posix.cc: error: mprotect failed: 13 (Permission denied)
This came up while investigating ente/ente#3436. The equivalent blocker in sqlite3.dart was fixed in simolus3/sqlite3.dart#396.
Rive-specific cause
Normal Rive usage reaches Dart FFI callbacks, not only optional scripting APIs:
FFIRiveFileinrive_native/lib/src/ffi/rive_ffi.dartregisters the view-model callbacks withPointer.fromFunctionwhen a.rivfile is decoded.FFIFlutterFactoryinrive_native/lib/src/ffi/flutter_renderer_ffi.dartregisters its renderer callbacks withPointer.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.11rive_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
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with FFIRiveFile in rive_native/lib/src/ffi/rive_ffi.dart and FFIFlutterFactory in rive_native/lib/src/ffi/flutter_renderer_ffi.dart, then reproduce with the minimal Flutter app and systemd-run command shown. Trace the Pointer.fromFunction and NativeCallable callback paths. Done means ordinary .riv loading and rendering work under MemoryDenyWriteExecute=yes without runtime executable-memory allocation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, dart, flutter, linux
- Domain
- desktop-dev, mobile-dev, operating-systems, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100