From ebb66f2f9e2afcfd356ee4a461ceb32924a1f196 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Thu, 6 Aug 2026 08:14:53 -0700 Subject: [PATCH] ffi: reject detached ArrayBufferViews Detached ArrayBuffers were rejected with ERR_INVALID_ARG_VALUE, but detached views were not: typed arrays exported zero bytes and DataViews threw a bare TypeError from byteLength. Track detachment in ArrayBufferViewContents::Read() and skip the JS length check for detached buffers, so every detached input is rejected. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: codex:gpt-5.6-sol --- lib/ffi.js | 13 ++++++++++++- src/util-inl.h | 1 + test/ffi/test-ffi-memory.js | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/ffi.js b/lib/ffi.js index cde6cca7a86e..99a583363b90 100644 --- a/lib/ffi.js +++ b/lib/ffi.js @@ -1,6 +1,8 @@ 'use strict'; const { + ArrayBufferPrototypeGetDetached, + DataViewPrototypeGetBuffer, FunctionPrototypeCall, ObjectDefineProperty, ObjectFreeze, @@ -8,11 +10,14 @@ const { ObjectKeys, ObjectPrototypeToString, SymbolDispose, + TypedArrayPrototypeGetBuffer, } = primordials; const { Buffer } = require('buffer'); const { emitExperimentalWarning } = require('internal/util'); const { + isDataView, isArrayBufferView, + isSharedArrayBuffer, } = require('internal/util/types'); const { codes: { @@ -284,7 +289,13 @@ function exportArrayBufferView(source, data, len) { validateInteger(len, 'len', 0); - if (len < source.byteLength) { + // Reading byteLength throws for a detached DataView. Let the native binding + // reject detached views consistently with detached ArrayBuffers. + const buffer = isDataView(source) ? + DataViewPrototypeGetBuffer(source) : TypedArrayPrototypeGetBuffer(source); + if ((isSharedArrayBuffer(buffer) || + !ArrayBufferPrototypeGetDetached(buffer)) && + len < source.byteLength) { throw new ERR_OUT_OF_RANGE('len', `>= ${source.byteLength}`, len); } diff --git a/src/util-inl.h b/src/util-inl.h index e357d15a1449..19a497c03bd1 100644 --- a/src/util-inl.h +++ b/src/util-inl.h @@ -573,6 +573,7 @@ ArrayBufferViewContents::ArrayBufferViewContents( template void ArrayBufferViewContents::Read(v8::Local abv) { static_assert(sizeof(T) == 1, "Only supports one-byte data at the moment"); + was_detached_ = abv->Buffer()->WasDetached(); length_ = abv->ByteLength(); if (length_ > sizeof(stack_storage_) || abv->HasBuffer()) { auto buf_data = abv->Buffer()->Data(); diff --git a/test/ffi/test-ffi-memory.js b/test/ffi/test-ffi-memory.js index f17f56c410f8..ae95851f8f76 100644 --- a/test/ffi/test-ffi-memory.js +++ b/test/ffi/test-ffi-memory.js @@ -251,6 +251,23 @@ test('ffi validates memory access arguments', () => { assert.throws(() => ffi.exportArrayBufferView('bad', ptr, 4), { code: 'ERR_INVALID_ARG_TYPE' }); assert.throws(() => ffi.exportArrayBufferView(new Uint8Array([1]), ptr, -1), { code: 'ERR_OUT_OF_RANGE' }); assert.throws(() => ffi.exportArrayBufferView(new Uint8Array([1, 2]), ptr, 1), { code: 'ERR_OUT_OF_RANGE' }); + ffi.exportArrayBufferView(new Uint8Array(new SharedArrayBuffer(1)), ptr, 1); + + const detachedArrayBuffer = new ArrayBuffer(1); + detachedArrayBuffer.transfer(); + assert.throws(() => ffi.exportArrayBuffer(detachedArrayBuffer, ptr, 1), { + code: 'ERR_INVALID_ARG_VALUE', + }); + + for (const View of [Uint8Array, DataView]) { + const arrayBuffer = new ArrayBuffer(1); + const view = new View(arrayBuffer); + arrayBuffer.transfer(); + assert.throws(() => ffi.exportArrayBufferView(view, ptr, 1), { + code: 'ERR_INVALID_ARG_VALUE', + }); + } + assert.throws(() => ffi.toBuffer(maxPointer, 8), /pointer and length exceed the platform address range/); assert.throws(() => ffi.toArrayBuffer(maxPointer, 8), /pointer and length exceed the platform address range/); assert.throws(() => ffi.toBuffer(1n, bufferConstants.MAX_LENGTH + 1), { code: 'ERR_BUFFER_TOO_LARGE' });