From 669fac12cea1703c000dad87fdd1410dda38de46 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Wed, 12 Aug 2026 00:02:39 -0700 Subject: [PATCH 1/2] ffi: reject direct SharedArrayBuffer pointers Reject direct SharedArrayBuffer pointer arguments in the JavaScript wrapper and native fast-buffer helper. This keeps validation behavior consistent before and after optimization. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: codex:gpt-5.6-sol --- lib/internal/ffi/fast-api.js | 5 ++--- src/ffi/fast.cc | 10 ++++------ test/ffi/test-ffi-fast-buffer.js | 27 ++++++++++++++++++++++++++- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/lib/internal/ffi/fast-api.js b/lib/internal/ffi/fast-api.js index 4d46b9c7a8bb..89b199360a74 100644 --- a/lib/internal/ffi/fast-api.js +++ b/lib/internal/ffi/fast-api.js @@ -18,7 +18,6 @@ const { } = require('buffer'); const { - isAnyArrayBuffer, isArrayBuffer, isArrayBufferView, isDataView, @@ -130,7 +129,7 @@ function hasStringPointerArg(type, value) { function hasPointerMemoryArg(type, value) { return (needsRawPointerConversion(type) || needsStringPointerConversion(type)) && - (isArrayBufferView(value) || isAnyArrayBuffer(value)); + (isArrayBufferView(value) || isArrayBuffer(value)); } function enterStringConversion(state) { @@ -310,7 +309,7 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) { } finally { exitStringConversion(stringState); } - } else if (memory0 && (isArrayBufferView(arg) || isAnyArrayBuffer(arg))) { + } else if (memory0 && (isArrayBufferView(arg) || isArrayBuffer(arg))) { if (fastBufferInvoke !== undefined) { return fastBufferInvoke(arg); } diff --git a/src/ffi/fast.cc b/src/ffi/fast.cc index 919bacf061a8..8e92c56aebb7 100644 --- a/src/ffi/fast.cc +++ b/src/ffi/fast.cc @@ -248,9 +248,10 @@ extern "C" uintptr_t node_ffi_fast_buffer_data(v8::Local value, constexpr uintptr_t kInvalidBuffer = std::numeric_limits::max(); v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr; - // Accept only memory-backed JS values in the native helper. Other pointer - // conversions, including strings, stay in the JS wrapper so their temporary - // lifetime is explicit. + // Accept only the memory-backed JS values supported by ToFFIArgument in the + // native helper. Other pointer conversions, including strings and direct + // SharedArrayBuffers, stay in the JS wrapper so validation and temporary + // lifetimes match the generic path. if (value->IsArrayBufferView()) { v8::Local view = value.As(); if (view->Buffer()->WasDetached()) { @@ -281,9 +282,6 @@ extern "C" uintptr_t node_ffi_fast_buffer_data(v8::Local value, } return PointerFromValue(value); } - if (value->IsSharedArrayBuffer()) { - return PointerFromValue(value); - } if (isolate != nullptr) { // No HandleScope is active during a Fast API call, so open one before diff --git a/test/ffi/test-ffi-fast-buffer.js b/test/ffi/test-ffi-fast-buffer.js index a6b196efc014..09924f1fe256 100644 --- a/test/ffi/test-ffi-fast-buffer.js +++ b/test/ffi/test-ffi-fast-buffer.js @@ -1,4 +1,4 @@ -// Flags: --experimental-ffi --expose-internals +// Flags: --experimental-ffi --expose-internals --allow-natives-syntax 'use strict'; const common = require('../common'); @@ -70,6 +70,31 @@ test('fast FFI buffer arguments reject invalid values', () => { } }); +test('optimized pointer arguments reject direct SharedArrayBuffers', () => { + const lib = new ffi.DynamicLibrary(libraryPath); + const firstByte = lib.getFunction('first_byte', { + arguments: ['pointer'], + return: 'u8', + }); + const regular = new ArrayBuffer(1); + const shared = new SharedArrayBuffer(1); + const expected = { code: 'ERR_INVALID_ARG_VALUE' }; + + try { + assert.throws(() => firstByte(shared), expected); + + eval('%PrepareFunctionForOptimization(firstByte)'); + firstByte(regular); + firstByte(regular); + eval('%OptimizeFunctionOnNextCall(firstByte)'); + firstByte(regular); + + assert.throws(() => firstByte(shared), expected); + } finally { + lib.close(); + } +}); + test('fast FFI string buffers survive reentrant callbacks', { // Bundled libffi callbacks crash on SmartOS. skip: common.isSunOS, From 2bc35e27675a31d628e6dd810e2dcfc70c7aa514 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:56:52 +0000 Subject: [PATCH 2/2] fixup! ffi: reject direct SharedArrayBuffer pointers --- test/ffi/test-ffi-fast-buffer.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/ffi/test-ffi-fast-buffer.js b/test/ffi/test-ffi-fast-buffer.js index 09924f1fe256..0e5cd844cfe2 100644 --- a/test/ffi/test-ffi-fast-buffer.js +++ b/test/ffi/test-ffi-fast-buffer.js @@ -80,16 +80,20 @@ test('optimized pointer arguments reject direct SharedArrayBuffers', () => { const shared = new SharedArrayBuffer(1); const expected = { code: 'ERR_INVALID_ARG_VALUE' }; + function callFirstByte(value) { + return firstByte(value); + } + try { - assert.throws(() => firstByte(shared), expected); + assert.throws(() => callFirstByte(shared), expected); - eval('%PrepareFunctionForOptimization(firstByte)'); - firstByte(regular); - firstByte(regular); - eval('%OptimizeFunctionOnNextCall(firstByte)'); - firstByte(regular); + eval('%PrepareFunctionForOptimization(callFirstByte)'); + callFirstByte(regular); + callFirstByte(regular); + eval('%OptimizeFunctionOnNextCall(callFirstByte)'); + callFirstByte(regular); - assert.throws(() => firstByte(shared), expected); + assert.throws(() => callFirstByte(shared), expected); } finally { lib.close(); }