Commit f71d6441ced for nodejs
commit f71d6441ced93dd6f5e4386c5fb4de0fe146768a
Author: Trivikram Kamat <trivikr.dev@gmail.com>
Date: Tue Sep 29 20:33:37 2026 -0700
ffi: fix use-after-free in PrepareFunction
PrepareFunction() looked up the function cache before parsing the
signature. Parsing can run user getters, and a getter that calls
lib.close() clears the cache, which invalidates the iterator. For a
function that was already cached, reading it afterwards used freed
memory and crashed the process.
Look up the cache after the signature is parsed. If the library was
closed during parsing, ResolveSymbol() now throws
ERR_FFI_LIBRARY_CLOSED.
Signed-off-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com>
Assisted-by: claude:opus-5.5
PR-URL: https://github.com/nodejs/node/pull/66368
Fixes: https://github.com/nodejs/node/issues/66367
Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
diff --git a/src/node_ffi.cc b/src/node_ffi.cc
index 732657a368e..7e8ad60aa4d 100644
--- a/src/node_ffi.cc
+++ b/src/node_ffi.cc
@@ -144,12 +144,14 @@ Maybe<void*> DynamicLibrary::ResolveSymbol(Environment* env,
Maybe<DynamicLibrary::PreparedFunction> DynamicLibrary::PrepareFunction(
Environment* env, const std::string& name, Local<Object> signature) {
std::shared_ptr<FFIFunction> fn;
- auto existing = functions_.find(name);
FunctionSignature parsed;
if (!ParseFunctionSignature(env, name, signature).To(&parsed)) {
return {};
}
+ // Look up the cache only after parsing: the signature's getters run user
+ // code that may close the library, which clears `functions_`.
+ auto existing = functions_.find(name);
auto [return_type, args, return_type_name, arg_type_names] =
std::move(parsed);
diff --git a/test/ffi/test-ffi-dynamic-library.js b/test/ffi/test-ffi-dynamic-library.js
index 3240f194972..62cc7e0b4d4 100644
--- a/test/ffi/test-ffi-dynamic-library.js
+++ b/test/ffi/test-ffi-dynamic-library.js
@@ -290,6 +290,21 @@ test('closed libraries reject subsequent operations', () => {
assert.throws(() => lib.getSymbols(), /Library is closed/);
});
+test('closing the library from a signature getter of a cached function', () => {
+ const lib = new ffi.DynamicLibrary(libraryPath);
+ lib.getFunction('add_i32', fixtureSymbols.add_i32);
+
+ assert.throws(() => {
+ lib.getFunction('add_i32', {
+ arguments: ['i32', 'i32'],
+ get return() {
+ lib.close();
+ return 'i32';
+ },
+ });
+ }, { code: 'ERR_FFI_LIBRARY_CLOSED' });
+});
+
test('optimized fast calls reject calls after the library is closed', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
multiply_f64: fixtureSymbols.multiply_f64,