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
ffi: GC of DynamicLibrary frees registered callbacks still held by native code #66841
import{DynamicLibrary}from'node:ffi';constlibraryPath="../node/test/ffi/fixture_library/build/Release/ffi_test_library.dylib";// A separate handle provides the native function that invokes the callback.constkeeper=newDynamicLibrary(libraryPath);constcall_int_callback=keeper.getFunction('call_int_callback',{arguments: ['pointer','i32'],return: 'i32'});letlib=newDynamicLibrary(libraryPath);constcbPtr=lib.registerCallback({arguments: ['i32'],return: 'i32'},(x)=>x*2);console.log(call_int_callback(cbPtr,21));// 42lib=null;// drop the handle without calling close()globalThis.gc();// ~DynamicLibrary -> Close() -> ffi_closure_freeconsole.log(call_int_callback(cbPtr,21));// segmentation fault
Run with
node --expose-gc repro.mjs
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
A callback pointer from library.registerCallback() should stay valid until the user calls library.close() or library.unregisterCallback(pointer). The docs only call it undefined behavior to invoke callback pointers after one of those explicit calls. Here neither was called.
What do you see instead?
The process crashes with a segmentation fault. Native code jumps into a freed ffi_closure trampoline, which is a use-after-free.
Activity
saqibsafwan4-code commentedon Oct 19, 2026
saqibsafwan4-code commentedon Oct 19, 2026