Commit 63a6ea26bb5 for nodejs

commit 63a6ea26bb550dc683a89216fdb70d3a9045c260
Author: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
Date:   Tue Sep 22 20:50:10 2026 -0300

    ffi: reject non-boolean copy arguments

    ffi.toBuffer() and ffi.toArrayBuffer() document copy as a boolean, but
    read it with BooleanValue(), which applies JavaScript truthiness. A
    falsy non-boolean such as null, 0 or '' therefore selects the
    zero-copy mode, which returns a writable view over foreign memory
    instead of a copy, while values such as 'false' or 1 select a copy.

    Throw ERR_INVALID_ARG_TYPE when copy is neither undefined nor a
    boolean. Omitting copy or passing undefined still makes a copy, and
    true and false keep their current behavior.

    Signed-off-by: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
    PR-URL: https://github.com/nodejs/node/pull/66219
    Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
    Reviewed-By: Filip Skokan <panva.ip@gmail.com>

diff --git a/src/ffi/data.cc b/src/ffi/data.cc
index ad92caf716e..c42d51600c9 100644
--- a/src/ffi/data.cc
+++ b/src/ffi/data.cc
@@ -546,7 +546,6 @@ static bool ZeroCopyUnavailable(Environment* env) {

 void ToBuffer(const FunctionCallbackInfo<Value>& args) {
   Environment* env = Environment::GetCurrent(args);
-  Isolate* isolate = env->isolate();

   THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, "");

@@ -589,8 +588,12 @@ void ToBuffer(const FunctionCallbackInfo<Value>& args) {
     return;
   }

-  bool copy = args.Length() < 3 || args[2]->IsUndefined() ||
-              args[2]->BooleanValue(isolate);
+  if (!args[2]->IsUndefined() && !args[2]->IsBoolean()) {
+    THROW_ERR_INVALID_ARG_TYPE(env, "The copy argument must be a boolean");
+    return;
+  }
+
+  bool copy = !args[2]->IsFalse();
   if (!copy && ZeroCopyUnavailable(env)) return;

   Local<Object> buf;
@@ -654,8 +657,12 @@ void ToArrayBuffer(const FunctionCallbackInfo<Value>& args) {
     return;
   }

-  bool copy = args.Length() < 3 || args[2]->IsUndefined() ||
-              args[2]->BooleanValue(isolate);
+  if (!args[2]->IsUndefined() && !args[2]->IsBoolean()) {
+    THROW_ERR_INVALID_ARG_TYPE(env, "The copy argument must be a boolean");
+    return;
+  }
+
+  bool copy = !args[2]->IsFalse();
   if (!copy && ZeroCopyUnavailable(env)) return;

   Local<ArrayBuffer> ab;
diff --git a/test/ffi/test-ffi-memory.js b/test/ffi/test-ffi-memory.js
index 3ed1dbcb117..38ea177b6cb 100644
--- a/test/ffi/test-ffi-memory.js
+++ b/test/ffi/test-ffi-memory.js
@@ -132,6 +132,18 @@ test('ffi zero-copy views throw with the V8 sandbox', { skip: !common.hasV8Sandb
   }));
 });

+test('ffi toBuffer and toArrayBuffer require a boolean copy argument', () => {
+  withAllocations(common.mustCall((alloc) => {
+    const ptr = alloc(4);
+    const type = { code: 'ERR_INVALID_ARG_TYPE' };
+
+    for (const copy of [null, 0, '', 'false', 1, {}]) {
+      assert.throws(() => ffi.toBuffer(ptr, 4, copy), type);
+      assert.throws(() => ffi.toArrayBuffer(ptr, 4, copy), type);
+    }
+  }));
+});
+
 test('ffi getRawPointer returns raw addresses for byte sources', () => {
   const buffer = Buffer.from([1, 2, 3]);
   const arrayBuffer = new Uint8Array([4, 5, 6, 7]).buffer;