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;