Commit 8e8b4f0a93e for nodejs

commit 8e8b4f0a93e460826899fb0d75adc000336ea561
Author: Trivikram Kamat <trivikr.dev@gmail.com>
Date:   Thu Nov 5 22:44:45 2026 -0800

    ffi: reject signature conflicts from getFunctions getters

    A signature getter could request an earlier symbol with a different
    signature, making getFunctions() return a wrapper with that signature.
    Recheck pending functions before caching them, and throw on a mismatch.

    Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
    Assisted-by: claude:opus-5.5
    PR-URL: https://github.com/nodejs/node/pull/67194
    Fixes: https://github.com/nodejs/node/issues/67192
    Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
    Reviewed-By: Paolo Insogna <paolo@cowtech.it>

diff --git a/src/node_ffi.cc b/src/node_ffi.cc
index 317af551294..b8127587d4e 100644
--- a/src/node_ffi.cc
+++ b/src/node_ffi.cc
@@ -331,9 +331,10 @@ MaybeLocal<Function> DynamicLibrary::CreateFunction(

   // Creating a callable emits a trampoline, allocates an FFIFunctionInfo, and
   // on the SharedBuffer path allocates an ArrayBuffer, so reuse the one already
-  // handed out for this symbol. `PrepareFunction()` rejects a request that uses
-  // a different signature, so a hit always describes the same signature. An
-  // empty handle means the wrapper was collected; fall through and rebuild.
+  // handed out for this symbol. `PrepareFunction()` and `GetFunctions()` reject
+  // a request that uses a different signature, so a hit always describes the
+  // same signature. An empty handle means the wrapper was collected; fall
+  // through and rebuild.
   auto cached = function_wrappers_.find(name);
   if (cached != function_wrappers_.end()) {
     if (!cached->second.IsEmpty()) {
@@ -1075,6 +1076,23 @@ void DynamicLibrary::GetFunctions(const FunctionCallbackInfo<Value>& args) {
       });
     }

+    // The signature getters above run user code that may have requested an
+    // earlier symbol with a different signature. Check before caching anything
+    // so that a rejected call leaves the library unchanged.
+    for (const auto& item : pending) {
+      auto existing = lib->functions_.find(item.name);
+      if (existing != lib->functions_.end() &&
+          !SignaturesMatch(
+              *existing->second, item.fn->return_type, item.fn->args)) {
+        THROW_ERR_INVALID_ARG_VALUE(
+            env,
+            "Function %s"
+            " was already requested with a different signature",
+            item.name);
+        return;
+      }
+    }
+
     for (const auto& item : pending) {
       if (item.should_cache_symbol) {
         lib->symbols_.emplace(item.name, item.fn->ptr);
diff --git a/test/ffi/test-ffi-dynamic-library.js b/test/ffi/test-ffi-dynamic-library.js
index 138e1ccb410..f4379909be0 100644
--- a/test/ffi/test-ffi-dynamic-library.js
+++ b/test/ffi/test-ffi-dynamic-library.js
@@ -220,6 +220,54 @@ test('getFunction caches signatures consistently', () => {
   }
 });

+test('getFunctions rejects a signature changed by a getter', () => {
+  const lib = new ffi.DynamicLibrary(libraryPath);
+  const conflicting = { arguments: ['f64', 'f64'], return: 'f64' };
+
+  try {
+    let fn;
+    assert.throws(() => {
+      lib.getFunctions({
+        add_i32: fixtureSymbols.add_i32,
+        get add_i64() {
+          fn = lib.getFunction('add_i32', conflicting);
+          return fixtureSymbols.add_i64;
+        },
+      });
+    }, {
+      code: 'ERR_INVALID_ARG_VALUE',
+      message: /Function add_i32 was already requested with a different signature/,
+    });
+
+    // The rejected call must not cache any of its own entries.
+    assert.deepStrictEqual(Object.keys(lib.functions), ['add_i32']);
+    assert.strictEqual(lib.getFunction('add_i32', conflicting), fn);
+  } finally {
+    lib.close();
+  }
+});
+
+test('getFunctions accepts a matching signature requested by a getter', () => {
+  const lib = new ffi.DynamicLibrary(libraryPath);
+
+  try {
+    let fn;
+    const functions = lib.getFunctions({
+      add_i32: fixtureSymbols.add_i32,
+      get add_i64() {
+        fn = lib.getFunction('add_i32', fixtureSymbols.add_i32);
+        return fixtureSymbols.add_i64;
+      },
+    });
+
+    assert.strictEqual(functions.add_i32, fn);
+    assert.strictEqual(functions.add_i32(20, 22), 42);
+    assert.strictEqual(functions.add_i64(20n, 22n), 42n);
+  } finally {
+    lib.close();
+  }
+});
+
 test('resolving the same symbol reuses one function', () => {
   const lib = new ffi.DynamicLibrary(libraryPath);
   const definitions = { add_i32: fixtureSymbols.add_i32 };