From c9143b725f5e3a5725e0fcbe89af548e4ba93863 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Thu, 6 Aug 2026 07:43:31 -0700 Subject: [PATCH 1/3] ffi: reject detached ArrayBuffers as pointers Reject detached ArrayBuffers and ArrayBuffer views in getRawPointer() and FFI pointer argument conversion. This prevents detached backing stores from being silently passed to native functions as null pointers. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: codex:gpt-5.6-sol --- src/ffi/data.cc | 17 ++++++++++++++--- src/ffi/types.cc | 13 +++++++++++++ test/ffi/test-ffi-memory.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/ffi/data.cc b/src/ffi/data.cc index 73b575395c8c..daf2cceea194 100644 --- a/src/ffi/data.cc +++ b/src/ffi/data.cc @@ -749,15 +749,26 @@ void GetRawPointer(const FunctionCallbackInfo& args) { std::shared_ptr store; if (args[0]->IsArrayBuffer()) { - store = args[0].As()->GetBackingStore(); + Local buffer = args[0].As(); + if (buffer->WasDetached()) { + THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached"); + return; + } + store = buffer->GetBackingStore(); } else if (args[0]->IsSharedArrayBuffer()) { store = args[0].As()->GetBackingStore(); } else if (args[0]->IsArrayBufferView()) { + Local view = args[0].As(); + if (view->Buffer()->WasDetached()) { + THROW_ERR_INVALID_ARG_VALUE( + env, "ArrayBufferView is backed by a detached ArrayBuffer"); + return; + } // Access the store here to ensure that it exists. Small typed arrays // may not have a store until this point and can instead be stored // entirely in-heap. - store = args[0].As()->Buffer()->GetBackingStore(); - offset = args[0].As()->ByteOffset(); + store = view->Buffer()->GetBackingStore(); + offset = view->ByteOffset(); } else { THROW_ERR_INVALID_ARG_TYPE( env, diff --git a/src/ffi/types.cc b/src/ffi/types.cc index 9ba3cc4da448..db0c913c547d 100644 --- a/src/ffi/types.cc +++ b/src/ffi/types.cc @@ -700,6 +700,14 @@ Maybe ToFFIArgument(Environment* env, // invalidating that backing store during the active FFI call is // unsupported and dangerous. Local view = arg.As(); + if (view->Buffer()->WasDetached()) { + THROW_ERR_INVALID_ARG_VALUE( + env, + "Argument %u is an ArrayBufferView backed by a detached " + "ArrayBuffer", + index); + return {}; + } std::shared_ptr store = view->Buffer()->GetBackingStore(); if (!store) { @@ -721,6 +729,11 @@ Maybe ToFFIArgument(Environment* env, // that backing store during the active FFI call is unsupported and // dangerous. Local buffer = arg.As(); + if (buffer->WasDetached()) { + THROW_ERR_INVALID_ARG_VALUE( + env, "Argument %u is a detached ArrayBuffer", index); + return {}; + } std::shared_ptr store = buffer->GetBackingStore(); if (!store) { diff --git a/test/ffi/test-ffi-memory.js b/test/ffi/test-ffi-memory.js index f17f56c410f8..ba1e31f073d7 100644 --- a/test/ffi/test-ffi-memory.js +++ b/test/ffi/test-ffi-memory.js @@ -146,6 +146,36 @@ test('ffi getRawPointer returns raw addresses for byte sources', () => { assert.strictEqual(sharedViewPointer, sharedArrayBufferPointer + 2n); }); +test('ffi rejects detached array buffers and views as pointers', () => { + const arrayBuffer = new ArrayBuffer(8); + const typedArray = new Uint8Array(arrayBuffer); + const dataView = new DataView(arrayBuffer); + + structuredClone(arrayBuffer, { transfer: [arrayBuffer] }); + + for (const [value, rawPointerMessage, argumentMessage] of [ + [ + arrayBuffer, + 'ArrayBuffer is detached', + 'Argument 0 is a detached ArrayBuffer', + ], + ...[typedArray, dataView].map((view) => [ + view, + 'ArrayBufferView is backed by a detached ArrayBuffer', + 'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer', + ]), + ]) { + assert.throws(() => ffi.getRawPointer(value), { + code: 'ERR_INVALID_ARG_VALUE', + message: rawPointerMessage, + }); + assert.throws(() => symbols.pointer_to_usize(value), { + code: 'ERR_INVALID_ARG_VALUE', + message: argumentMessage, + }); + } +}); + test('ffi exportString and exportBuffer copy data into native memory', () => { withAllocations(common.mustCall((alloc) => { const stringPtr = alloc(16); From ff1132262af0c3474faaea68fb685581fcc53c23 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Thu, 6 Aug 2026 07:57:17 -0700 Subject: [PATCH 2/3] fixup! ffi: clarify detached ArrayBuffer error Report that the ArrayBuffer is detached instead of describing its backing store as invalid. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> --- src/ffi/data.cc | 2 +- test/ffi/test-ffi-memory.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ffi/data.cc b/src/ffi/data.cc index daf2cceea194..6a8d54ca0d39 100644 --- a/src/ffi/data.cc +++ b/src/ffi/data.cc @@ -685,7 +685,7 @@ void ExportBytes(const FunctionCallbackInfo& args) { args[0]->IsArrayBufferView()) { view.ReadValue(args[0]); if (view.WasDetached()) { - THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBufferView backing store"); + THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached"); return; } } else { diff --git a/test/ffi/test-ffi-memory.js b/test/ffi/test-ffi-memory.js index ba1e31f073d7..a052a8889b92 100644 --- a/test/ffi/test-ffi-memory.js +++ b/test/ffi/test-ffi-memory.js @@ -153,6 +153,11 @@ test('ffi rejects detached array buffers and views as pointers', () => { structuredClone(arrayBuffer, { transfer: [arrayBuffer] }); + assert.throws(() => ffi.exportArrayBuffer(arrayBuffer, 0n, 0), { + code: 'ERR_INVALID_ARG_VALUE', + message: 'ArrayBuffer is detached', + }); + for (const [value, rawPointerMessage, argumentMessage] of [ [ arrayBuffer, From 6aa48c515afd50bf72ed700b6de98f2aba1afde8 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Thu, 6 Aug 2026 08:21:41 -0700 Subject: [PATCH 3/3] fixup! test: use arrayBuffer.transfer() --- test/ffi/test-ffi-memory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ffi/test-ffi-memory.js b/test/ffi/test-ffi-memory.js index a052a8889b92..ee88e9ef4151 100644 --- a/test/ffi/test-ffi-memory.js +++ b/test/ffi/test-ffi-memory.js @@ -151,7 +151,7 @@ test('ffi rejects detached array buffers and views as pointers', () => { const typedArray = new Uint8Array(arrayBuffer); const dataView = new DataView(arrayBuffer); - structuredClone(arrayBuffer, { transfer: [arrayBuffer] }); + arrayBuffer.transfer(); assert.throws(() => ffi.exportArrayBuffer(arrayBuffer, 0n, 0), { code: 'ERR_INVALID_ARG_VALUE',