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.
________________________________
From: Trivikram Kamat ***@***.***>
Sent: Monday, 19 October 2026 09:35:28
To: nodejs/node ***@***.***>
Cc: Subscribed ***@***.***>
Subject: [nodejs/node] ffi: GC of DynamicLibrary frees registered callbacks still held by native code (Issue #66841)
[https://avatars.githubusercontent.com/u/16024985?s=20&v=4]trivikr created an issue (nodejs/node#66841)<#66841>
Version
main
Platform
macOS
Subsystem
ffi
What steps will reproduce the bug?
import { DynamicLibrary } from 'node:ffi';
const libraryPath = "../node/test/ffi/fixture_library/build/Release/ffi_test_library.dylib";
// A separate handle provides the native function that invokes the callback.
const keeper = new DynamicLibrary(libraryPath);
const call_int_callback = keeper.getFunction('call_int_callback',
{ arguments: ['pointer', 'i32'], return: 'i32' });
let lib = new DynamicLibrary(libraryPath);
const cbPtr = lib.registerCallback(
{ arguments: ['i32'], return: 'i32' }, (x) => x * 2);
console.log(call_int_callback(cbPtr, 21)); // 42
lib = null; // drop the handle without calling close()
globalThis.gc(); // ~DynamicLibrary -> Close() -> ffi_closure_free
console.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.
Additional information
No response
—
Reply to this email directly, view it on GitHub<#66841?email_source=notifications&email_token=BZVB6CBCNA52DRE3QMSMWYL5UWDYBA5CNFSL4Z3JMQ5C6L3HNF2C22DVMIXUS43TOVSS6NJZGE3TGMJUGM3TDJTSMVQXG33OVJZXKYTTMNZGSYTFMSSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/BZVB6CGXB6C6NYWH3ZIICST5UWDYBAVCNFSNUABEKJSXA33TNF2G64TZHMZDOMJZGM3TOOJ3JFZXG5LFHM2TSMJXGMYTIMZXGGQXMAQ>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
________________________________
From: saQ ***@***.***>
Sent: Monday, 19 October 2026 09:36:00
To: nodejs/node ***@***.***>; nodejs/node ***@***.***>
Cc: Subscribed ***@***.***>
Subject: Re: [nodejs/node] ffi: GC of DynamicLibrary frees registered callbacks still held by native code (Issue #66841)
Closed
Sent from Outlook for Android<https://aka.ms/AAb9ysg>
________________________________
From: Trivikram Kamat ***@***.***>
Sent: Monday, 19 October 2026 09:35:28
To: nodejs/node ***@***.***>
Cc: Subscribed ***@***.***>
Subject: [nodejs/node] ffi: GC of DynamicLibrary frees registered callbacks still held by native code (Issue #66841)
[https://avatars.githubusercontent.com/u/16024985?s=20&v=4]trivikr created an issue (nodejs/node#66841)<#66841>
Version
main
Platform
macOS
Subsystem
ffi
What steps will reproduce the bug?
import { DynamicLibrary } from 'node:ffi';
const libraryPath = "../node/test/ffi/fixture_library/build/Release/ffi_test_library.dylib";
// A separate handle provides the native function that invokes the callback.
const keeper = new DynamicLibrary(libraryPath);
const call_int_callback = keeper.getFunction('call_int_callback',
{ arguments: ['pointer', 'i32'], return: 'i32' });
let lib = new DynamicLibrary(libraryPath);
const cbPtr = lib.registerCallback(
{ arguments: ['i32'], return: 'i32' }, (x) => x * 2);
console.log(call_int_callback(cbPtr, 21)); // 42
lib = null; // drop the handle without calling close()
globalThis.gc(); // ~DynamicLibrary -> Close() -> ffi_closure_free
console.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.
Additional information
No response
—
Reply to this email directly, view it on GitHub<#66841?email_source=notifications&email_token=BZVB6CBCNA52DRE3QMSMWYL5UWDYBA5CNFSL4Z3JMQ5C6L3HNF2C22DVMIXUS43TOVSS6NJZGE3TGMJUGM3TDJTSMVQXG33OVJZXKYTTMNZGSYTFMSSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/BZVB6CGXB6C6NYWH3ZIICST5UWDYBAVCNFSNUABEKJSXA33TNF2G64TZHMZDOMJZGM3TOOJ3JFZXG5LFHM2TSMJXGMYTIMZXGGQXMAQ>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
Activity
saqibsafwan4-code commentedon Oct 19, 2026
saqibsafwan4-code commentedon Oct 19, 2026