From abc3d45771782c6c53ace02ccf2d5bdda89e94eb Mon Sep 17 00:00:00 2001 From: Christian Aurich Zanettini Martins Date: Tue, 22 Sep 2026 20:50:10 -0300 Subject: [PATCH] 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 --- src/ffi/data.cc | 17 ++++++++++++----- test/ffi/test-ffi-memory.js | 12 ++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/ffi/data.cc b/src/ffi/data.cc index dbeba94cd1b9..ae2481f5c53a 100644 --- a/src/ffi/data.cc +++ b/src/ffi/data.cc @@ -546,7 +546,6 @@ static bool ZeroCopyUnavailable(Environment* env) { void ToBuffer(const FunctionCallbackInfo& 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& 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 buf; @@ -654,8 +657,12 @@ void ToArrayBuffer(const FunctionCallbackInfo& 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 ab; diff --git a/test/ffi/test-ffi-memory.js b/test/ffi/test-ffi-memory.js index 53fe3928029b..d1d48988f46d 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;