Commit be2ab89

Browse files
trivikraduh95
authored andcommitted
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 PR-URL: #65083Fixes: #65082 Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent 87ee7bf commit be2ab89

6 files changed

Lines changed: 147 additions & 9 deletions

File tree

β€Žlib/internal/ffi/fast-api.jsβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
'use strict';
22

33
const{
4+
ArrayBufferPrototypeGetDetached,
45
ArrayPrototypeIncludes,
6+
DataViewPrototypeGetBuffer,
57
NumberIsInteger,
68
ObjectDefineProperty,
79
ReflectApply,
810
SafeWeakMap,
911
StringPrototypeIncludes,
1012
TypeError,
13+
TypedArrayPrototypeGetBuffer,
1114
}=primordials;
1215

1316
const{
@@ -16,7 +19,9 @@ const {
1619

1720
const{
1821
isAnyArrayBuffer,
22+
isArrayBuffer,
1923
isArrayBufferView,
24+
isDataView,
2025
}=require('internal/util/types');
2126

2227
const{
@@ -167,6 +172,28 @@ function getStringConversionPointer(state, value, index) {
167172
returnentry.pointer;
168173
}
169174

175+
functiongetRawPointerArg(value,index){
176+
letbuffer;
177+
letisView=false;
178+
if(isArrayBuffer(value)){
179+
buffer=value;
180+
}elseif(isArrayBufferView(value)){
181+
isView=true;
182+
buffer=isDataView(value) ?
183+
DataViewPrototypeGetBuffer(value) :
184+
TypedArrayPrototypeGetBuffer(value);
185+
}
186+
187+
if(buffer!==undefined&&isArrayBuffer(buffer)&&
188+
ArrayBufferPrototypeGetDetached(buffer)){
189+
throwFFIArgError(isView ?
190+
`Argument ${index} is an ArrayBufferView backed by a detached ArrayBuffer` :
191+
`Argument ${index} is a detached ArrayBuffer`);
192+
}
193+
194+
returngetRawPointer(value);
195+
}
196+
170197
functionconvertPointerArg(type,value,stringState,index){
171198
validateFastPointerArg(type,value,index);
172199
if(needsNullPointerConversion(type)&&
@@ -177,7 +204,7 @@ function convertPointerArg(type, value, stringState, index) {
177204
returngetStringConversionPointer(stringState,value,index);
178205
}
179206
if(hasPointerMemoryArg(type,value)){
180-
returngetRawPointer(value);
207+
returngetRawPointerArg(value,index);
181208
}
182209
// Pointer-like values (e.g. BigInt addresses) are passed through, matching
183210
// ToFFIArgument in src/ffi/types.cc and the single-argument fast path.
@@ -287,7 +314,7 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
287314
if(fastBufferInvoke!==undefined){
288315
returnfastBufferInvoke(arg);
289316
}
290-
arg=getRawPointer(arg);
317+
arg=getRawPointerArg(arg,0);
291318
}
292319
returnrawFn(arg);
293320
};

β€Žsrc/ffi/data.ccβ€Ž

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ void ExportBytes(const FunctionCallbackInfo<Value>& args) {
685685
args[0]->IsArrayBufferView()) {
686686
view.ReadValue(args[0]);
687687
if (view.WasDetached()) {
688-
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBufferView backing store");
688+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
689689
return;
690690
}
691691
} else {
@@ -749,15 +749,26 @@ void GetRawPointer(const FunctionCallbackInfo<Value>& args) {
749749
std::shared_ptr<BackingStore> store;
750750

751751
if (args[0]->IsArrayBuffer()) {
752-
store = args[0].As<ArrayBuffer>()->GetBackingStore();
752+
Local<ArrayBuffer> buffer = args[0].As<ArrayBuffer>();
753+
if (buffer->WasDetached()) {
754+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
755+
return;
756+
}
757+
store = buffer->GetBackingStore();
753758
} elseif (args[0]->IsSharedArrayBuffer()) {
754759
store = args[0].As<SharedArrayBuffer>()->GetBackingStore();
755760
} elseif (args[0]->IsArrayBufferView()) {
761+
Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
762+
if (view->Buffer()->WasDetached()) {
763+
THROW_ERR_INVALID_ARG_VALUE(
764+
env, "ArrayBufferView is backed by a detached ArrayBuffer");
765+
return;
766+
}
756767
// Access the store here to ensure that it exists. Small typed arrays
757768
// may not have a store until this point and can instead be stored
758769
// entirely in-heap.
759-
store = args[0].As<ArrayBufferView>()->Buffer()->GetBackingStore();
760-
offset = args[0].As<ArrayBufferView>()->ByteOffset();
770+
store = view->Buffer()->GetBackingStore();
771+
offset = view->ByteOffset();
761772
} else {
762773
THROW_ERR_INVALID_ARG_TYPE(
763774
env,

β€Žsrc/ffi/fast.ccβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,18 +246,45 @@ extern "C" uintptr_t node_ffi_fast_buffer_data(v8::Local<v8::Value> value,
246246
// returns zero after throwing, preventing the native target from seeing an
247247
// invalid pointer value.
248248
constexpruintptr_tkInvalidBuffer = std::numeric_limits<uintptr_t>::max();
249+
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
249250

250251
// Accept only memory-backed JS values in the native helper. Other pointer
251252
// conversions, including strings, stay in the JS wrapper so their temporary
252253
// lifetime is explicit.
253254
if (value->IsArrayBufferView()) {
255+
v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
256+
if (view->Buffer()->WasDetached()) {
257+
if (isolate != nullptr) {
258+
// No HandleScope is active during a Fast API call, so open one before
259+
// creating the error object.
260+
v8::HandleScope scope(isolate);
261+
THROW_ERR_INVALID_ARG_VALUE(
262+
isolate,
263+
"Argument %u is an ArrayBufferView backed by a detached "
264+
"ArrayBuffer",
265+
index);
266+
}
267+
returnkInvalidBuffer;
268+
}
254269
returnPointerFromValue(value);
255270
}
256-
if (value->IsArrayBuffer() || value->IsSharedArrayBuffer()) {
271+
if (value->IsArrayBuffer()) {
272+
if (value.As<v8::ArrayBuffer>()->WasDetached()) {
273+
if (isolate != nullptr) {
274+
// No HandleScope is active during a Fast API call, so open one before
275+
// creating the error object.
276+
v8::HandleScope scope(isolate);
277+
THROW_ERR_INVALID_ARG_VALUE(
278+
isolate, "Argument %u is a detached ArrayBuffer", index);
279+
}
280+
returnkInvalidBuffer;
281+
}
282+
returnPointerFromValue(value);
283+
}
284+
if (value->IsSharedArrayBuffer()) {
257285
returnPointerFromValue(value);
258286
}
259287

260-
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
261288
if (isolate != nullptr) {
262289
// No HandleScope is active during a Fast API call, so open one before
263290
// creating the error object.

β€Žsrc/ffi/types.ccβ€Ž

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,14 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
700700
// invalidating that backing store during the active FFI call is
701701
// unsupported and dangerous.
702702
Local<ArrayBufferView> view = arg.As<ArrayBufferView>();
703+
if (view->Buffer()->WasDetached()) {
704+
THROW_ERR_INVALID_ARG_VALUE(
705+
env,
706+
"Argument %u is an ArrayBufferView backed by a detached "
707+
"ArrayBuffer",
708+
index);
709+
return {};
710+
}
703711
std::shared_ptr<BackingStore> store = view->Buffer()->GetBackingStore();
704712

705713
if (!store) {
@@ -721,6 +729,11 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
721729
// that backing store during the active FFI call is unsupported and
722730
// dangerous.
723731
Local<ArrayBuffer> buffer = arg.As<ArrayBuffer>();
732+
if (buffer->WasDetached()) {
733+
THROW_ERR_INVALID_ARG_VALUE(
734+
env, "Argument %u is a detached ArrayBuffer", index);
735+
return {};
736+
}
724737
std::shared_ptr<BackingStore> store = buffer->GetBackingStore();
725738

726739
if (!store) {

β€Žtest/ffi/test-ffi-fast-buffer.jsβ€Ž

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ test('fast FFI string buffers survive reentrant callbacks', {
9898

9999
test('optimized buffer signatures preserve pointer-like conversions',()=>{
100100
constlib=newffi.DynamicLibrary(libraryPath);
101+
constasPointer=lib.getFunction('pointer_to_usize',{
102+
arguments: ['pointer'],
103+
return: 'u64',
104+
});
101105
constasBuffer=lib.getFunction('pointer_to_usize',{
102106
arguments: ['buffer'],
103107
return: 'u64',
@@ -107,6 +111,10 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
107111
return: 'u64',
108112
});
109113

114+
functioncallPointer(value){
115+
returnasPointer(value);
116+
}
117+
110118
functioncallBuffer(value){
111119
returnasBuffer(value);
112120
}
@@ -117,17 +125,34 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
117125

118126
try{
119127
for(leti=0;i<100_000;i++){
128+
assert.strictEqual(callPointer(0n),0n);
120129
assert.strictEqual(callBuffer(0n),0n);
121130
assert.strictEqual(callArrayBuffer(0n),0n);
122131
}
123132

124-
for(constcallof[callBuffer,callArrayBuffer]){
133+
for(constcallof[callPointer,callBuffer,callArrayBuffer]){
125134
assert.strictEqual(call(null),0n);
126135
assert.strictEqual(call(undefined),0n);
127136
assert.notStrictEqual(call('ffi'),0n);
128137

129138
constbytes=Buffer.alloc(1);
130139
assert.strictEqual(call(bytes),ffi.getRawPointer(bytes));
140+
141+
constarrayBuffer=newArrayBuffer(8);
142+
consttypedArray=newUint8Array(arrayBuffer);
143+
constdataView=newDataView(arrayBuffer);
144+
arrayBuffer.transfer();
145+
146+
assert.throws(()=>call(arrayBuffer),{
147+
code: 'ERR_INVALID_ARG_VALUE',
148+
message: 'Argument 0 is a detached ArrayBuffer',
149+
});
150+
for(constviewof[typedArray,dataView]){
151+
assert.throws(()=>call(view),{
152+
code: 'ERR_INVALID_ARG_VALUE',
153+
message: 'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
154+
});
155+
}
131156
}
132157
}finally{
133158
lib.close();

β€Žtest/ffi/test-ffi-memory.jsβ€Ž

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,41 @@ test('ffi getRawPointer returns raw addresses for byte sources', () => {
146146
assert.strictEqual(sharedViewPointer,sharedArrayBufferPointer+2n);
147147
});
148148

149+
test('ffi rejects detached array buffers and views as pointers',()=>{
150+
constarrayBuffer=newArrayBuffer(8);
151+
consttypedArray=newUint8Array(arrayBuffer);
152+
constdataView=newDataView(arrayBuffer);
153+
154+
arrayBuffer.transfer();
155+
156+
assert.throws(()=>ffi.exportArrayBuffer(arrayBuffer,0n,0),{
157+
code: 'ERR_INVALID_ARG_VALUE',
158+
message: 'ArrayBuffer is detached',
159+
});
160+
161+
for(const[value,rawPointerMessage,argumentMessage]of[
162+
[
163+
arrayBuffer,
164+
'ArrayBuffer is detached',
165+
'Argument 0 is a detached ArrayBuffer',
166+
],
167+
...[typedArray,dataView].map((view)=>[
168+
view,
169+
'ArrayBufferView is backed by a detached ArrayBuffer',
170+
'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
171+
]),
172+
]){
173+
assert.throws(()=>ffi.getRawPointer(value),{
174+
code: 'ERR_INVALID_ARG_VALUE',
175+
message: rawPointerMessage,
176+
});
177+
assert.throws(()=>symbols.pointer_to_usize(value),{
178+
code: 'ERR_INVALID_ARG_VALUE',
179+
message: argumentMessage,
180+
});
181+
}
182+
});
183+
149184
test('ffi exportString and exportBuffer copy data into native memory',()=>{
150185
withAllocations(common.mustCall((alloc)=>{
151186
conststringPtr=alloc(16);

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit be2ab89

Browse files
trivikraduh95
authored andcommitted
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 PR-URL: #65083Fixes: #65082 Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent 87ee7bf commit be2ab89

6 files changed

Lines changed: 147 additions & 9 deletions

File tree

β€Žlib/internal/ffi/fast-api.jsβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
'use strict';
22

33
const{
4+
ArrayBufferPrototypeGetDetached,
45
ArrayPrototypeIncludes,
6+
DataViewPrototypeGetBuffer,
57
NumberIsInteger,
68
ObjectDefineProperty,
79
ReflectApply,
810
SafeWeakMap,
911
StringPrototypeIncludes,
1012
TypeError,
13+
TypedArrayPrototypeGetBuffer,
1114
}=primordials;
1215

1316
const{
@@ -16,7 +19,9 @@ const {
1619

1720
const{
1821
isAnyArrayBuffer,
22+
isArrayBuffer,
1923
isArrayBufferView,
24+
isDataView,
2025
}=require('internal/util/types');
2126

2227
const{
@@ -167,6 +172,28 @@ function getStringConversionPointer(state, value, index) {
167172
returnentry.pointer;
168173
}
169174

175+
functiongetRawPointerArg(value,index){
176+
letbuffer;
177+
letisView=false;
178+
if(isArrayBuffer(value)){
179+
buffer=value;
180+
}elseif(isArrayBufferView(value)){
181+
isView=true;
182+
buffer=isDataView(value) ?
183+
DataViewPrototypeGetBuffer(value) :
184+
TypedArrayPrototypeGetBuffer(value);
185+
}
186+
187+
if(buffer!==undefined&&isArrayBuffer(buffer)&&
188+
ArrayBufferPrototypeGetDetached(buffer)){
189+
throwFFIArgError(isView ?
190+
`Argument ${index} is an ArrayBufferView backed by a detached ArrayBuffer` :
191+
`Argument ${index} is a detached ArrayBuffer`);
192+
}
193+
194+
returngetRawPointer(value);
195+
}
196+
170197
functionconvertPointerArg(type,value,stringState,index){
171198
validateFastPointerArg(type,value,index);
172199
if(needsNullPointerConversion(type)&&
@@ -177,7 +204,7 @@ function convertPointerArg(type, value, stringState, index) {
177204
returngetStringConversionPointer(stringState,value,index);
178205
}
179206
if(hasPointerMemoryArg(type,value)){
180-
returngetRawPointer(value);
207+
returngetRawPointerArg(value,index);
181208
}
182209
// Pointer-like values (e.g. BigInt addresses) are passed through, matching
183210
// ToFFIArgument in src/ffi/types.cc and the single-argument fast path.
@@ -287,7 +314,7 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
287314
if(fastBufferInvoke!==undefined){
288315
returnfastBufferInvoke(arg);
289316
}
290-
arg=getRawPointer(arg);
317+
arg=getRawPointerArg(arg,0);
291318
}
292319
returnrawFn(arg);
293320
};

β€Žsrc/ffi/data.ccβ€Ž

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ void ExportBytes(const FunctionCallbackInfo<Value>& args) {
685685
args[0]->IsArrayBufferView()) {
686686
view.ReadValue(args[0]);
687687
if (view.WasDetached()) {
688-
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBufferView backing store");
688+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
689689
return;
690690
}
691691
} else {
@@ -749,15 +749,26 @@ void GetRawPointer(const FunctionCallbackInfo<Value>& args) {
749749
std::shared_ptr<BackingStore> store;
750750

751751
if (args[0]->IsArrayBuffer()) {
752-
store = args[0].As<ArrayBuffer>()->GetBackingStore();
752+
Local<ArrayBuffer> buffer = args[0].As<ArrayBuffer>();
753+
if (buffer->WasDetached()) {
754+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
755+
return;
756+
}
757+
store = buffer->GetBackingStore();
753758
} elseif (args[0]->IsSharedArrayBuffer()) {
754759
store = args[0].As<SharedArrayBuffer>()->GetBackingStore();
755760
} elseif (args[0]->IsArrayBufferView()) {
761+
Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
762+
if (view->Buffer()->WasDetached()) {
763+
THROW_ERR_INVALID_ARG_VALUE(
764+
env, "ArrayBufferView is backed by a detached ArrayBuffer");
765+
return;
766+
}
756767
// Access the store here to ensure that it exists. Small typed arrays
757768
// may not have a store until this point and can instead be stored
758769
// entirely in-heap.
759-
store = args[0].As<ArrayBufferView>()->Buffer()->GetBackingStore();
760-
offset = args[0].As<ArrayBufferView>()->ByteOffset();
770+
store = view->Buffer()->GetBackingStore();
771+
offset = view->ByteOffset();
761772
} else {
762773
THROW_ERR_INVALID_ARG_TYPE(
763774
env,

β€Žsrc/ffi/fast.ccβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,18 +246,45 @@ extern "C" uintptr_t node_ffi_fast_buffer_data(v8::Local<v8::Value> value,
246246
// returns zero after throwing, preventing the native target from seeing an
247247
// invalid pointer value.
248248
constexpruintptr_tkInvalidBuffer = std::numeric_limits<uintptr_t>::max();
249+
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
249250

250251
// Accept only memory-backed JS values in the native helper. Other pointer
251252
// conversions, including strings, stay in the JS wrapper so their temporary
252253
// lifetime is explicit.
253254
if (value->IsArrayBufferView()) {
255+
v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
256+
if (view->Buffer()->WasDetached()) {
257+
if (isolate != nullptr) {
258+
// No HandleScope is active during a Fast API call, so open one before
259+
// creating the error object.
260+
v8::HandleScope scope(isolate);
261+
THROW_ERR_INVALID_ARG_VALUE(
262+
isolate,
263+
"Argument %u is an ArrayBufferView backed by a detached "
264+
"ArrayBuffer",
265+
index);
266+
}
267+
returnkInvalidBuffer;
268+
}
254269
returnPointerFromValue(value);
255270
}
256-
if (value->IsArrayBuffer() || value->IsSharedArrayBuffer()) {
271+
if (value->IsArrayBuffer()) {
272+
if (value.As<v8::ArrayBuffer>()->WasDetached()) {
273+
if (isolate != nullptr) {
274+
// No HandleScope is active during a Fast API call, so open one before
275+
// creating the error object.
276+
v8::HandleScope scope(isolate);
277+
THROW_ERR_INVALID_ARG_VALUE(
278+
isolate, "Argument %u is a detached ArrayBuffer", index);
279+
}
280+
returnkInvalidBuffer;
281+
}
282+
returnPointerFromValue(value);
283+
}
284+
if (value->IsSharedArrayBuffer()) {
257285
returnPointerFromValue(value);
258286
}
259287

260-
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
261288
if (isolate != nullptr) {
262289
// No HandleScope is active during a Fast API call, so open one before
263290
// creating the error object.

β€Žsrc/ffi/types.ccβ€Ž

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,14 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
700700
// invalidating that backing store during the active FFI call is
701701
// unsupported and dangerous.
702702
Local<ArrayBufferView> view = arg.As<ArrayBufferView>();
703+
if (view->Buffer()->WasDetached()) {
704+
THROW_ERR_INVALID_ARG_VALUE(
705+
env,
706+
"Argument %u is an ArrayBufferView backed by a detached "
707+
"ArrayBuffer",
708+
index);
709+
return {};
710+
}
703711
std::shared_ptr<BackingStore> store = view->Buffer()->GetBackingStore();
704712

705713
if (!store) {
@@ -721,6 +729,11 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
721729
// that backing store during the active FFI call is unsupported and
722730
// dangerous.
723731
Local<ArrayBuffer> buffer = arg.As<ArrayBuffer>();
732+
if (buffer->WasDetached()) {
733+
THROW_ERR_INVALID_ARG_VALUE(
734+
env, "Argument %u is a detached ArrayBuffer", index);
735+
return {};
736+
}
724737
std::shared_ptr<BackingStore> store = buffer->GetBackingStore();
725738

726739
if (!store) {

β€Žtest/ffi/test-ffi-fast-buffer.jsβ€Ž

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ test('fast FFI string buffers survive reentrant callbacks', {
9898

9999
test('optimized buffer signatures preserve pointer-like conversions',()=>{
100100
constlib=newffi.DynamicLibrary(libraryPath);
101+
constasPointer=lib.getFunction('pointer_to_usize',{
102+
arguments: ['pointer'],
103+
return: 'u64',
104+
});
101105
constasBuffer=lib.getFunction('pointer_to_usize',{
102106
arguments: ['buffer'],
103107
return: 'u64',
@@ -107,6 +111,10 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
107111
return: 'u64',
108112
});
109113

114+
functioncallPointer(value){
115+
returnasPointer(value);
116+
}
117+
110118
functioncallBuffer(value){
111119
returnasBuffer(value);
112120
}
@@ -117,17 +125,34 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
117125

118126
try{
119127
for(leti=0;i<100_000;i++){
128+
assert.strictEqual(callPointer(0n),0n);
120129
assert.strictEqual(callBuffer(0n),0n);
121130
assert.strictEqual(callArrayBuffer(0n),0n);
122131
}
123132

124-
for(constcallof[callBuffer,callArrayBuffer]){
133+
for(constcallof[callPointer,callBuffer,callArrayBuffer]){
125134
assert.strictEqual(call(null),0n);
126135
assert.strictEqual(call(undefined),0n);
127136
assert.notStrictEqual(call('ffi'),0n);
128137

129138
constbytes=Buffer.alloc(1);
130139
assert.strictEqual(call(bytes),ffi.getRawPointer(bytes));
140+
141+
constarrayBuffer=newArrayBuffer(8);
142+
consttypedArray=newUint8Array(arrayBuffer);
143+
constdataView=newDataView(arrayBuffer);
144+
arrayBuffer.transfer();
145+
146+
assert.throws(()=>call(arrayBuffer),{
147+
code: 'ERR_INVALID_ARG_VALUE',
148+
message: 'Argument 0 is a detached ArrayBuffer',
149+
});
150+
for(constviewof[typedArray,dataView]){
151+
assert.throws(()=>call(view),{
152+
code: 'ERR_INVALID_ARG_VALUE',
153+
message: 'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
154+
});
155+
}
131156
}
132157
}finally{
133158
lib.close();

β€Žtest/ffi/test-ffi-memory.jsβ€Ž

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,41 @@ test('ffi getRawPointer returns raw addresses for byte sources', () => {
146146
assert.strictEqual(sharedViewPointer,sharedArrayBufferPointer+2n);
147147
});
148148

149+
test('ffi rejects detached array buffers and views as pointers',()=>{
150+
constarrayBuffer=newArrayBuffer(8);
151+
consttypedArray=newUint8Array(arrayBuffer);
152+
constdataView=newDataView(arrayBuffer);
153+
154+
arrayBuffer.transfer();
155+
156+
assert.throws(()=>ffi.exportArrayBuffer(arrayBuffer,0n,0),{
157+
code: 'ERR_INVALID_ARG_VALUE',
158+
message: 'ArrayBuffer is detached',
159+
});
160+
161+
for(const[value,rawPointerMessage,argumentMessage]of[
162+
[
163+
arrayBuffer,
164+
'ArrayBuffer is detached',
165+
'Argument 0 is a detached ArrayBuffer',
166+
],
167+
...[typedArray,dataView].map((view)=>[
168+
view,
169+
'ArrayBufferView is backed by a detached ArrayBuffer',
170+
'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
171+
]),
172+
]){
173+
assert.throws(()=>ffi.getRawPointer(value),{
174+
code: 'ERR_INVALID_ARG_VALUE',
175+
message: rawPointerMessage,
176+
});
177+
assert.throws(()=>symbols.pointer_to_usize(value),{
178+
code: 'ERR_INVALID_ARG_VALUE',
179+
message: argumentMessage,
180+
});
181+
}
182+
});
183+
149184
test('ffi exportString and exportBuffer copy data into native memory',()=>{
150185
withAllocations(common.mustCall((alloc)=>{
151186
conststringPtr=alloc(16);

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit be2ab89

Browse files
trivikraduh95
authored andcommitted
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 PR-URL: #65083Fixes: #65082 Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent 87ee7bf commit be2ab89

6 files changed

Lines changed: 147 additions & 9 deletions

File tree

β€Žlib/internal/ffi/fast-api.jsβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
'use strict';
22

33
const{
4+
ArrayBufferPrototypeGetDetached,
45
ArrayPrototypeIncludes,
6+
DataViewPrototypeGetBuffer,
57
NumberIsInteger,
68
ObjectDefineProperty,
79
ReflectApply,
810
SafeWeakMap,
911
StringPrototypeIncludes,
1012
TypeError,
13+
TypedArrayPrototypeGetBuffer,
1114
}=primordials;
1215

1316
const{
@@ -16,7 +19,9 @@ const {
1619

1720
const{
1821
isAnyArrayBuffer,
22+
isArrayBuffer,
1923
isArrayBufferView,
24+
isDataView,
2025
}=require('internal/util/types');
2126

2227
const{
@@ -167,6 +172,28 @@ function getStringConversionPointer(state, value, index) {
167172
returnentry.pointer;
168173
}
169174

175+
functiongetRawPointerArg(value,index){
176+
letbuffer;
177+
letisView=false;
178+
if(isArrayBuffer(value)){
179+
buffer=value;
180+
}elseif(isArrayBufferView(value)){
181+
isView=true;
182+
buffer=isDataView(value) ?
183+
DataViewPrototypeGetBuffer(value) :
184+
TypedArrayPrototypeGetBuffer(value);
185+
}
186+
187+
if(buffer!==undefined&&isArrayBuffer(buffer)&&
188+
ArrayBufferPrototypeGetDetached(buffer)){
189+
throwFFIArgError(isView ?
190+
`Argument ${index} is an ArrayBufferView backed by a detached ArrayBuffer` :
191+
`Argument ${index} is a detached ArrayBuffer`);
192+
}
193+
194+
returngetRawPointer(value);
195+
}
196+
170197
functionconvertPointerArg(type,value,stringState,index){
171198
validateFastPointerArg(type,value,index);
172199
if(needsNullPointerConversion(type)&&
@@ -177,7 +204,7 @@ function convertPointerArg(type, value, stringState, index) {
177204
returngetStringConversionPointer(stringState,value,index);
178205
}
179206
if(hasPointerMemoryArg(type,value)){
180-
returngetRawPointer(value);
207+
returngetRawPointerArg(value,index);
181208
}
182209
// Pointer-like values (e.g. BigInt addresses) are passed through, matching
183210
// ToFFIArgument in src/ffi/types.cc and the single-argument fast path.
@@ -287,7 +314,7 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
287314
if(fastBufferInvoke!==undefined){
288315
returnfastBufferInvoke(arg);
289316
}
290-
arg=getRawPointer(arg);
317+
arg=getRawPointerArg(arg,0);
291318
}
292319
returnrawFn(arg);
293320
};

β€Žsrc/ffi/data.ccβ€Ž

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ void ExportBytes(const FunctionCallbackInfo<Value>& args) {
685685
args[0]->IsArrayBufferView()) {
686686
view.ReadValue(args[0]);
687687
if (view.WasDetached()) {
688-
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBufferView backing store");
688+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
689689
return;
690690
}
691691
} else {
@@ -749,15 +749,26 @@ void GetRawPointer(const FunctionCallbackInfo<Value>& args) {
749749
std::shared_ptr<BackingStore> store;
750750

751751
if (args[0]->IsArrayBuffer()) {
752-
store = args[0].As<ArrayBuffer>()->GetBackingStore();
752+
Local<ArrayBuffer> buffer = args[0].As<ArrayBuffer>();
753+
if (buffer->WasDetached()) {
754+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
755+
return;
756+
}
757+
store = buffer->GetBackingStore();
753758
} elseif (args[0]->IsSharedArrayBuffer()) {
754759
store = args[0].As<SharedArrayBuffer>()->GetBackingStore();
755760
} elseif (args[0]->IsArrayBufferView()) {
761+
Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
762+
if (view->Buffer()->WasDetached()) {
763+
THROW_ERR_INVALID_ARG_VALUE(
764+
env, "ArrayBufferView is backed by a detached ArrayBuffer");
765+
return;
766+
}
756767
// Access the store here to ensure that it exists. Small typed arrays
757768
// may not have a store until this point and can instead be stored
758769
// entirely in-heap.
759-
store = args[0].As<ArrayBufferView>()->Buffer()->GetBackingStore();
760-
offset = args[0].As<ArrayBufferView>()->ByteOffset();
770+
store = view->Buffer()->GetBackingStore();
771+
offset = view->ByteOffset();
761772
} else {
762773
THROW_ERR_INVALID_ARG_TYPE(
763774
env,

β€Žsrc/ffi/fast.ccβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,18 +246,45 @@ extern "C" uintptr_t node_ffi_fast_buffer_data(v8::Local<v8::Value> value,
246246
// returns zero after throwing, preventing the native target from seeing an
247247
// invalid pointer value.
248248
constexpruintptr_tkInvalidBuffer = std::numeric_limits<uintptr_t>::max();
249+
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
249250

250251
// Accept only memory-backed JS values in the native helper. Other pointer
251252
// conversions, including strings, stay in the JS wrapper so their temporary
252253
// lifetime is explicit.
253254
if (value->IsArrayBufferView()) {
255+
v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
256+
if (view->Buffer()->WasDetached()) {
257+
if (isolate != nullptr) {
258+
// No HandleScope is active during a Fast API call, so open one before
259+
// creating the error object.
260+
v8::HandleScope scope(isolate);
261+
THROW_ERR_INVALID_ARG_VALUE(
262+
isolate,
263+
"Argument %u is an ArrayBufferView backed by a detached "
264+
"ArrayBuffer",
265+
index);
266+
}
267+
returnkInvalidBuffer;
268+
}
254269
returnPointerFromValue(value);
255270
}
256-
if (value->IsArrayBuffer() || value->IsSharedArrayBuffer()) {
271+
if (value->IsArrayBuffer()) {
272+
if (value.As<v8::ArrayBuffer>()->WasDetached()) {
273+
if (isolate != nullptr) {
274+
// No HandleScope is active during a Fast API call, so open one before
275+
// creating the error object.
276+
v8::HandleScope scope(isolate);
277+
THROW_ERR_INVALID_ARG_VALUE(
278+
isolate, "Argument %u is a detached ArrayBuffer", index);
279+
}
280+
returnkInvalidBuffer;
281+
}
282+
returnPointerFromValue(value);
283+
}
284+
if (value->IsSharedArrayBuffer()) {
257285
returnPointerFromValue(value);
258286
}
259287

260-
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
261288
if (isolate != nullptr) {
262289
// No HandleScope is active during a Fast API call, so open one before
263290
// creating the error object.

β€Žsrc/ffi/types.ccβ€Ž

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,14 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
700700
// invalidating that backing store during the active FFI call is
701701
// unsupported and dangerous.
702702
Local<ArrayBufferView> view = arg.As<ArrayBufferView>();
703+
if (view->Buffer()->WasDetached()) {
704+
THROW_ERR_INVALID_ARG_VALUE(
705+
env,
706+
"Argument %u is an ArrayBufferView backed by a detached "
707+
"ArrayBuffer",
708+
index);
709+
return {};
710+
}
703711
std::shared_ptr<BackingStore> store = view->Buffer()->GetBackingStore();
704712

705713
if (!store) {
@@ -721,6 +729,11 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
721729
// that backing store during the active FFI call is unsupported and
722730
// dangerous.
723731
Local<ArrayBuffer> buffer = arg.As<ArrayBuffer>();
732+
if (buffer->WasDetached()) {
733+
THROW_ERR_INVALID_ARG_VALUE(
734+
env, "Argument %u is a detached ArrayBuffer", index);
735+
return {};
736+
}
724737
std::shared_ptr<BackingStore> store = buffer->GetBackingStore();
725738

726739
if (!store) {

β€Žtest/ffi/test-ffi-fast-buffer.jsβ€Ž

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ test('fast FFI string buffers survive reentrant callbacks', {
9898

9999
test('optimized buffer signatures preserve pointer-like conversions',()=>{
100100
constlib=newffi.DynamicLibrary(libraryPath);
101+
constasPointer=lib.getFunction('pointer_to_usize',{
102+
arguments: ['pointer'],
103+
return: 'u64',
104+
});
101105
constasBuffer=lib.getFunction('pointer_to_usize',{
102106
arguments: ['buffer'],
103107
return: 'u64',
@@ -107,6 +111,10 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
107111
return: 'u64',
108112
});
109113

114+
functioncallPointer(value){
115+
returnasPointer(value);
116+
}
117+
110118
functioncallBuffer(value){
111119
returnasBuffer(value);
112120
}
@@ -117,17 +125,34 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
117125

118126
try{
119127
for(leti=0;i<100_000;i++){
128+
assert.strictEqual(callPointer(0n),0n);
120129
assert.strictEqual(callBuffer(0n),0n);
121130
assert.strictEqual(callArrayBuffer(0n),0n);
122131
}
123132

124-
for(constcallof[callBuffer,callArrayBuffer]){
133+
for(constcallof[callPointer,callBuffer,callArrayBuffer]){
125134
assert.strictEqual(call(null),0n);
126135
assert.strictEqual(call(undefined),0n);
127136
assert.notStrictEqual(call('ffi'),0n);
128137

129138
constbytes=Buffer.alloc(1);
130139
assert.strictEqual(call(bytes),ffi.getRawPointer(bytes));
140+
141+
constarrayBuffer=newArrayBuffer(8);
142+
consttypedArray=newUint8Array(arrayBuffer);
143+
constdataView=newDataView(arrayBuffer);
144+
arrayBuffer.transfer();
145+
146+
assert.throws(()=>call(arrayBuffer),{
147+
code: 'ERR_INVALID_ARG_VALUE',
148+
message: 'Argument 0 is a detached ArrayBuffer',
149+
});
150+
for(constviewof[typedArray,dataView]){
151+
assert.throws(()=>call(view),{
152+
code: 'ERR_INVALID_ARG_VALUE',
153+
message: 'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
154+
});
155+
}
131156
}
132157
}finally{
133158
lib.close();

β€Žtest/ffi/test-ffi-memory.jsβ€Ž

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,41 @@ test('ffi getRawPointer returns raw addresses for byte sources', () => {
146146
assert.strictEqual(sharedViewPointer,sharedArrayBufferPointer+2n);
147147
});
148148

149+
test('ffi rejects detached array buffers and views as pointers',()=>{
150+
constarrayBuffer=newArrayBuffer(8);
151+
consttypedArray=newUint8Array(arrayBuffer);
152+
constdataView=newDataView(arrayBuffer);
153+
154+
arrayBuffer.transfer();
155+
156+
assert.throws(()=>ffi.exportArrayBuffer(arrayBuffer,0n,0),{
157+
code: 'ERR_INVALID_ARG_VALUE',
158+
message: 'ArrayBuffer is detached',
159+
});
160+
161+
for(const[value,rawPointerMessage,argumentMessage]of[
162+
[
163+
arrayBuffer,
164+
'ArrayBuffer is detached',
165+
'Argument 0 is a detached ArrayBuffer',
166+
],
167+
...[typedArray,dataView].map((view)=>[
168+
view,
169+
'ArrayBufferView is backed by a detached ArrayBuffer',
170+
'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
171+
]),
172+
]){
173+
assert.throws(()=>ffi.getRawPointer(value),{
174+
code: 'ERR_INVALID_ARG_VALUE',
175+
message: rawPointerMessage,
176+
});
177+
assert.throws(()=>symbols.pointer_to_usize(value),{
178+
code: 'ERR_INVALID_ARG_VALUE',
179+
message: argumentMessage,
180+
});
181+
}
182+
});
183+
149184
test('ffi exportString and exportBuffer copy data into native memory',()=>{
150185
withAllocations(common.mustCall((alloc)=>{
151186
conststringPtr=alloc(16);

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit be2ab89

Browse files
trivikraduh95
authored andcommitted
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 PR-URL: #65083Fixes: #65082 Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent 87ee7bf commit be2ab89

6 files changed

Lines changed: 147 additions & 9 deletions

File tree

β€Žlib/internal/ffi/fast-api.jsβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
'use strict';
22

33
const{
4+
ArrayBufferPrototypeGetDetached,
45
ArrayPrototypeIncludes,
6+
DataViewPrototypeGetBuffer,
57
NumberIsInteger,
68
ObjectDefineProperty,
79
ReflectApply,
810
SafeWeakMap,
911
StringPrototypeIncludes,
1012
TypeError,
13+
TypedArrayPrototypeGetBuffer,
1114
}=primordials;
1215

1316
const{
@@ -16,7 +19,9 @@ const {
1619

1720
const{
1821
isAnyArrayBuffer,
22+
isArrayBuffer,
1923
isArrayBufferView,
24+
isDataView,
2025
}=require('internal/util/types');
2126

2227
const{
@@ -167,6 +172,28 @@ function getStringConversionPointer(state, value, index) {
167172
returnentry.pointer;
168173
}
169174

175+
functiongetRawPointerArg(value,index){
176+
letbuffer;
177+
letisView=false;
178+
if(isArrayBuffer(value)){
179+
buffer=value;
180+
}elseif(isArrayBufferView(value)){
181+
isView=true;
182+
buffer=isDataView(value) ?
183+
DataViewPrototypeGetBuffer(value) :
184+
TypedArrayPrototypeGetBuffer(value);
185+
}
186+
187+
if(buffer!==undefined&&isArrayBuffer(buffer)&&
188+
ArrayBufferPrototypeGetDetached(buffer)){
189+
throwFFIArgError(isView ?
190+
`Argument ${index} is an ArrayBufferView backed by a detached ArrayBuffer` :
191+
`Argument ${index} is a detached ArrayBuffer`);
192+
}
193+
194+
returngetRawPointer(value);
195+
}
196+
170197
functionconvertPointerArg(type,value,stringState,index){
171198
validateFastPointerArg(type,value,index);
172199
if(needsNullPointerConversion(type)&&
@@ -177,7 +204,7 @@ function convertPointerArg(type, value, stringState, index) {
177204
returngetStringConversionPointer(stringState,value,index);
178205
}
179206
if(hasPointerMemoryArg(type,value)){
180-
returngetRawPointer(value);
207+
returngetRawPointerArg(value,index);
181208
}
182209
// Pointer-like values (e.g. BigInt addresses) are passed through, matching
183210
// ToFFIArgument in src/ffi/types.cc and the single-argument fast path.
@@ -287,7 +314,7 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
287314
if(fastBufferInvoke!==undefined){
288315
returnfastBufferInvoke(arg);
289316
}
290-
arg=getRawPointer(arg);
317+
arg=getRawPointerArg(arg,0);
291318
}
292319
returnrawFn(arg);
293320
};

β€Žsrc/ffi/data.ccβ€Ž

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ void ExportBytes(const FunctionCallbackInfo<Value>& args) {
685685
args[0]->IsArrayBufferView()) {
686686
view.ReadValue(args[0]);
687687
if (view.WasDetached()) {
688-
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBufferView backing store");
688+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
689689
return;
690690
}
691691
} else {
@@ -749,15 +749,26 @@ void GetRawPointer(const FunctionCallbackInfo<Value>& args) {
749749
std::shared_ptr<BackingStore> store;
750750

751751
if (args[0]->IsArrayBuffer()) {
752-
store = args[0].As<ArrayBuffer>()->GetBackingStore();
752+
Local<ArrayBuffer> buffer = args[0].As<ArrayBuffer>();
753+
if (buffer->WasDetached()) {
754+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
755+
return;
756+
}
757+
store = buffer->GetBackingStore();
753758
} elseif (args[0]->IsSharedArrayBuffer()) {
754759
store = args[0].As<SharedArrayBuffer>()->GetBackingStore();
755760
} elseif (args[0]->IsArrayBufferView()) {
761+
Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
762+
if (view->Buffer()->WasDetached()) {
763+
THROW_ERR_INVALID_ARG_VALUE(
764+
env, "ArrayBufferView is backed by a detached ArrayBuffer");
765+
return;
766+
}
756767
// Access the store here to ensure that it exists. Small typed arrays
757768
// may not have a store until this point and can instead be stored
758769
// entirely in-heap.
759-
store = args[0].As<ArrayBufferView>()->Buffer()->GetBackingStore();
760-
offset = args[0].As<ArrayBufferView>()->ByteOffset();
770+
store = view->Buffer()->GetBackingStore();
771+
offset = view->ByteOffset();
761772
} else {
762773
THROW_ERR_INVALID_ARG_TYPE(
763774
env,

β€Žsrc/ffi/fast.ccβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,18 +246,45 @@ extern "C" uintptr_t node_ffi_fast_buffer_data(v8::Local<v8::Value> value,
246246
// returns zero after throwing, preventing the native target from seeing an
247247
// invalid pointer value.
248248
constexpruintptr_tkInvalidBuffer = std::numeric_limits<uintptr_t>::max();
249+
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
249250

250251
// Accept only memory-backed JS values in the native helper. Other pointer
251252
// conversions, including strings, stay in the JS wrapper so their temporary
252253
// lifetime is explicit.
253254
if (value->IsArrayBufferView()) {
255+
v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
256+
if (view->Buffer()->WasDetached()) {
257+
if (isolate != nullptr) {
258+
// No HandleScope is active during a Fast API call, so open one before
259+
// creating the error object.
260+
v8::HandleScope scope(isolate);
261+
THROW_ERR_INVALID_ARG_VALUE(
262+
isolate,
263+
"Argument %u is an ArrayBufferView backed by a detached "
264+
"ArrayBuffer",
265+
index);
266+
}
267+
returnkInvalidBuffer;
268+
}
254269
returnPointerFromValue(value);
255270
}
256-
if (value->IsArrayBuffer() || value->IsSharedArrayBuffer()) {
271+
if (value->IsArrayBuffer()) {
272+
if (value.As<v8::ArrayBuffer>()->WasDetached()) {
273+
if (isolate != nullptr) {
274+
// No HandleScope is active during a Fast API call, so open one before
275+
// creating the error object.
276+
v8::HandleScope scope(isolate);
277+
THROW_ERR_INVALID_ARG_VALUE(
278+
isolate, "Argument %u is a detached ArrayBuffer", index);
279+
}
280+
returnkInvalidBuffer;
281+
}
282+
returnPointerFromValue(value);
283+
}
284+
if (value->IsSharedArrayBuffer()) {
257285
returnPointerFromValue(value);
258286
}
259287

260-
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
261288
if (isolate != nullptr) {
262289
// No HandleScope is active during a Fast API call, so open one before
263290
// creating the error object.

β€Žsrc/ffi/types.ccβ€Ž

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,14 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
700700
// invalidating that backing store during the active FFI call is
701701
// unsupported and dangerous.
702702
Local<ArrayBufferView> view = arg.As<ArrayBufferView>();
703+
if (view->Buffer()->WasDetached()) {
704+
THROW_ERR_INVALID_ARG_VALUE(
705+
env,
706+
"Argument %u is an ArrayBufferView backed by a detached "
707+
"ArrayBuffer",
708+
index);
709+
return {};
710+
}
703711
std::shared_ptr<BackingStore> store = view->Buffer()->GetBackingStore();
704712

705713
if (!store) {
@@ -721,6 +729,11 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
721729
// that backing store during the active FFI call is unsupported and
722730
// dangerous.
723731
Local<ArrayBuffer> buffer = arg.As<ArrayBuffer>();
732+
if (buffer->WasDetached()) {
733+
THROW_ERR_INVALID_ARG_VALUE(
734+
env, "Argument %u is a detached ArrayBuffer", index);
735+
return {};
736+
}
724737
std::shared_ptr<BackingStore> store = buffer->GetBackingStore();
725738

726739
if (!store) {

β€Žtest/ffi/test-ffi-fast-buffer.jsβ€Ž

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ test('fast FFI string buffers survive reentrant callbacks', {
9898

9999
test('optimized buffer signatures preserve pointer-like conversions',()=>{
100100
constlib=newffi.DynamicLibrary(libraryPath);
101+
constasPointer=lib.getFunction('pointer_to_usize',{
102+
arguments: ['pointer'],
103+
return: 'u64',
104+
});
101105
constasBuffer=lib.getFunction('pointer_to_usize',{
102106
arguments: ['buffer'],
103107
return: 'u64',
@@ -107,6 +111,10 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
107111
return: 'u64',
108112
});
109113

114+
functioncallPointer(value){
115+
returnasPointer(value);
116+
}
117+
110118
functioncallBuffer(value){
111119
returnasBuffer(value);
112120
}
@@ -117,17 +125,34 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
117125

118126
try{
119127
for(leti=0;i<100_000;i++){
128+
assert.strictEqual(callPointer(0n),0n);
120129
assert.strictEqual(callBuffer(0n),0n);
121130
assert.strictEqual(callArrayBuffer(0n),0n);
122131
}
123132

124-
for(constcallof[callBuffer,callArrayBuffer]){
133+
for(constcallof[callPointer,callBuffer,callArrayBuffer]){
125134
assert.strictEqual(call(null),0n);
126135
assert.strictEqual(call(undefined),0n);
127136
assert.notStrictEqual(call('ffi'),0n);
128137

129138
constbytes=Buffer.alloc(1);
130139
assert.strictEqual(call(bytes),ffi.getRawPointer(bytes));
140+
141+
constarrayBuffer=newArrayBuffer(8);
142+
consttypedArray=newUint8Array(arrayBuffer);
143+
constdataView=newDataView(arrayBuffer);
144+
arrayBuffer.transfer();
145+
146+
assert.throws(()=>call(arrayBuffer),{
147+
code: 'ERR_INVALID_ARG_VALUE',
148+
message: 'Argument 0 is a detached ArrayBuffer',
149+
});
150+
for(constviewof[typedArray,dataView]){
151+
assert.throws(()=>call(view),{
152+
code: 'ERR_INVALID_ARG_VALUE',
153+
message: 'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
154+
});
155+
}
131156
}
132157
}finally{
133158
lib.close();

β€Žtest/ffi/test-ffi-memory.jsβ€Ž

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,41 @@ test('ffi getRawPointer returns raw addresses for byte sources', () => {
146146
assert.strictEqual(sharedViewPointer,sharedArrayBufferPointer+2n);
147147
});
148148

149+
test('ffi rejects detached array buffers and views as pointers',()=>{
150+
constarrayBuffer=newArrayBuffer(8);
151+
consttypedArray=newUint8Array(arrayBuffer);
152+
constdataView=newDataView(arrayBuffer);
153+
154+
arrayBuffer.transfer();
155+
156+
assert.throws(()=>ffi.exportArrayBuffer(arrayBuffer,0n,0),{
157+
code: 'ERR_INVALID_ARG_VALUE',
158+
message: 'ArrayBuffer is detached',
159+
});
160+
161+
for(const[value,rawPointerMessage,argumentMessage]of[
162+
[
163+
arrayBuffer,
164+
'ArrayBuffer is detached',
165+
'Argument 0 is a detached ArrayBuffer',
166+
],
167+
...[typedArray,dataView].map((view)=>[
168+
view,
169+
'ArrayBufferView is backed by a detached ArrayBuffer',
170+
'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
171+
]),
172+
]){
173+
assert.throws(()=>ffi.getRawPointer(value),{
174+
code: 'ERR_INVALID_ARG_VALUE',
175+
message: rawPointerMessage,
176+
});
177+
assert.throws(()=>symbols.pointer_to_usize(value),{
178+
code: 'ERR_INVALID_ARG_VALUE',
179+
message: argumentMessage,
180+
});
181+
}
182+
});
183+
149184
test('ffi exportString and exportBuffer copy data into native memory',()=>{
150185
withAllocations(common.mustCall((alloc)=>{
151186
conststringPtr=alloc(16);

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit be2ab89

Browse files
trivikraduh95
authored andcommitted
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 PR-URL: #65083Fixes: #65082 Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent 87ee7bf commit be2ab89

6 files changed

Lines changed: 147 additions & 9 deletions

File tree

β€Žlib/internal/ffi/fast-api.jsβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
'use strict';
22

33
const{
4+
ArrayBufferPrototypeGetDetached,
45
ArrayPrototypeIncludes,
6+
DataViewPrototypeGetBuffer,
57
NumberIsInteger,
68
ObjectDefineProperty,
79
ReflectApply,
810
SafeWeakMap,
911
StringPrototypeIncludes,
1012
TypeError,
13+
TypedArrayPrototypeGetBuffer,
1114
}=primordials;
1215

1316
const{
@@ -16,7 +19,9 @@ const {
1619

1720
const{
1821
isAnyArrayBuffer,
22+
isArrayBuffer,
1923
isArrayBufferView,
24+
isDataView,
2025
}=require('internal/util/types');
2126

2227
const{
@@ -167,6 +172,28 @@ function getStringConversionPointer(state, value, index) {
167172
returnentry.pointer;
168173
}
169174

175+
functiongetRawPointerArg(value,index){
176+
letbuffer;
177+
letisView=false;
178+
if(isArrayBuffer(value)){
179+
buffer=value;
180+
}elseif(isArrayBufferView(value)){
181+
isView=true;
182+
buffer=isDataView(value) ?
183+
DataViewPrototypeGetBuffer(value) :
184+
TypedArrayPrototypeGetBuffer(value);
185+
}
186+
187+
if(buffer!==undefined&&isArrayBuffer(buffer)&&
188+
ArrayBufferPrototypeGetDetached(buffer)){
189+
throwFFIArgError(isView ?
190+
`Argument ${index} is an ArrayBufferView backed by a detached ArrayBuffer` :
191+
`Argument ${index} is a detached ArrayBuffer`);
192+
}
193+
194+
returngetRawPointer(value);
195+
}
196+
170197
functionconvertPointerArg(type,value,stringState,index){
171198
validateFastPointerArg(type,value,index);
172199
if(needsNullPointerConversion(type)&&
@@ -177,7 +204,7 @@ function convertPointerArg(type, value, stringState, index) {
177204
returngetStringConversionPointer(stringState,value,index);
178205
}
179206
if(hasPointerMemoryArg(type,value)){
180-
returngetRawPointer(value);
207+
returngetRawPointerArg(value,index);
181208
}
182209
// Pointer-like values (e.g. BigInt addresses) are passed through, matching
183210
// ToFFIArgument in src/ffi/types.cc and the single-argument fast path.
@@ -287,7 +314,7 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
287314
if(fastBufferInvoke!==undefined){
288315
returnfastBufferInvoke(arg);
289316
}
290-
arg=getRawPointer(arg);
317+
arg=getRawPointerArg(arg,0);
291318
}
292319
returnrawFn(arg);
293320
};

β€Žsrc/ffi/data.ccβ€Ž

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ void ExportBytes(const FunctionCallbackInfo<Value>& args) {
685685
args[0]->IsArrayBufferView()) {
686686
view.ReadValue(args[0]);
687687
if (view.WasDetached()) {
688-
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBufferView backing store");
688+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
689689
return;
690690
}
691691
} else {
@@ -749,15 +749,26 @@ void GetRawPointer(const FunctionCallbackInfo<Value>& args) {
749749
std::shared_ptr<BackingStore> store;
750750

751751
if (args[0]->IsArrayBuffer()) {
752-
store = args[0].As<ArrayBuffer>()->GetBackingStore();
752+
Local<ArrayBuffer> buffer = args[0].As<ArrayBuffer>();
753+
if (buffer->WasDetached()) {
754+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
755+
return;
756+
}
757+
store = buffer->GetBackingStore();
753758
} elseif (args[0]->IsSharedArrayBuffer()) {
754759
store = args[0].As<SharedArrayBuffer>()->GetBackingStore();
755760
} elseif (args[0]->IsArrayBufferView()) {
761+
Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
762+
if (view->Buffer()->WasDetached()) {
763+
THROW_ERR_INVALID_ARG_VALUE(
764+
env, "ArrayBufferView is backed by a detached ArrayBuffer");
765+
return;
766+
}
756767
// Access the store here to ensure that it exists. Small typed arrays
757768
// may not have a store until this point and can instead be stored
758769
// entirely in-heap.
759-
store = args[0].As<ArrayBufferView>()->Buffer()->GetBackingStore();
760-
offset = args[0].As<ArrayBufferView>()->ByteOffset();
770+
store = view->Buffer()->GetBackingStore();
771+
offset = view->ByteOffset();
761772
} else {
762773
THROW_ERR_INVALID_ARG_TYPE(
763774
env,

β€Žsrc/ffi/fast.ccβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,18 +246,45 @@ extern "C" uintptr_t node_ffi_fast_buffer_data(v8::Local<v8::Value> value,
246246
// returns zero after throwing, preventing the native target from seeing an
247247
// invalid pointer value.
248248
constexpruintptr_tkInvalidBuffer = std::numeric_limits<uintptr_t>::max();
249+
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
249250

250251
// Accept only memory-backed JS values in the native helper. Other pointer
251252
// conversions, including strings, stay in the JS wrapper so their temporary
252253
// lifetime is explicit.
253254
if (value->IsArrayBufferView()) {
255+
v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
256+
if (view->Buffer()->WasDetached()) {
257+
if (isolate != nullptr) {
258+
// No HandleScope is active during a Fast API call, so open one before
259+
// creating the error object.
260+
v8::HandleScope scope(isolate);
261+
THROW_ERR_INVALID_ARG_VALUE(
262+
isolate,
263+
"Argument %u is an ArrayBufferView backed by a detached "
264+
"ArrayBuffer",
265+
index);
266+
}
267+
returnkInvalidBuffer;
268+
}
254269
returnPointerFromValue(value);
255270
}
256-
if (value->IsArrayBuffer() || value->IsSharedArrayBuffer()) {
271+
if (value->IsArrayBuffer()) {
272+
if (value.As<v8::ArrayBuffer>()->WasDetached()) {
273+
if (isolate != nullptr) {
274+
// No HandleScope is active during a Fast API call, so open one before
275+
// creating the error object.
276+
v8::HandleScope scope(isolate);
277+
THROW_ERR_INVALID_ARG_VALUE(
278+
isolate, "Argument %u is a detached ArrayBuffer", index);
279+
}
280+
returnkInvalidBuffer;
281+
}
282+
returnPointerFromValue(value);
283+
}
284+
if (value->IsSharedArrayBuffer()) {
257285
returnPointerFromValue(value);
258286
}
259287

260-
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
261288
if (isolate != nullptr) {
262289
// No HandleScope is active during a Fast API call, so open one before
263290
// creating the error object.

β€Žsrc/ffi/types.ccβ€Ž

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,14 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
700700
// invalidating that backing store during the active FFI call is
701701
// unsupported and dangerous.
702702
Local<ArrayBufferView> view = arg.As<ArrayBufferView>();
703+
if (view->Buffer()->WasDetached()) {
704+
THROW_ERR_INVALID_ARG_VALUE(
705+
env,
706+
"Argument %u is an ArrayBufferView backed by a detached "
707+
"ArrayBuffer",
708+
index);
709+
return {};
710+
}
703711
std::shared_ptr<BackingStore> store = view->Buffer()->GetBackingStore();
704712

705713
if (!store) {
@@ -721,6 +729,11 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
721729
// that backing store during the active FFI call is unsupported and
722730
// dangerous.
723731
Local<ArrayBuffer> buffer = arg.As<ArrayBuffer>();
732+
if (buffer->WasDetached()) {
733+
THROW_ERR_INVALID_ARG_VALUE(
734+
env, "Argument %u is a detached ArrayBuffer", index);
735+
return {};
736+
}
724737
std::shared_ptr<BackingStore> store = buffer->GetBackingStore();
725738

726739
if (!store) {

β€Žtest/ffi/test-ffi-fast-buffer.jsβ€Ž

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ test('fast FFI string buffers survive reentrant callbacks', {
9898

9999
test('optimized buffer signatures preserve pointer-like conversions',()=>{
100100
constlib=newffi.DynamicLibrary(libraryPath);
101+
constasPointer=lib.getFunction('pointer_to_usize',{
102+
arguments: ['pointer'],
103+
return: 'u64',
104+
});
101105
constasBuffer=lib.getFunction('pointer_to_usize',{
102106
arguments: ['buffer'],
103107
return: 'u64',
@@ -107,6 +111,10 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
107111
return: 'u64',
108112
});
109113

114+
functioncallPointer(value){
115+
returnasPointer(value);
116+
}
117+
110118
functioncallBuffer(value){
111119
returnasBuffer(value);
112120
}
@@ -117,17 +125,34 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
117125

118126
try{
119127
for(leti=0;i<100_000;i++){
128+
assert.strictEqual(callPointer(0n),0n);
120129
assert.strictEqual(callBuffer(0n),0n);
121130
assert.strictEqual(callArrayBuffer(0n),0n);
122131
}
123132

124-
for(constcallof[callBuffer,callArrayBuffer]){
133+
for(constcallof[callPointer,callBuffer,callArrayBuffer]){
125134
assert.strictEqual(call(null),0n);
126135
assert.strictEqual(call(undefined),0n);
127136
assert.notStrictEqual(call('ffi'),0n);
128137

129138
constbytes=Buffer.alloc(1);
130139
assert.strictEqual(call(bytes),ffi.getRawPointer(bytes));
140+
141+
constarrayBuffer=newArrayBuffer(8);
142+
consttypedArray=newUint8Array(arrayBuffer);
143+
constdataView=newDataView(arrayBuffer);
144+
arrayBuffer.transfer();
145+
146+
assert.throws(()=>call(arrayBuffer),{
147+
code: 'ERR_INVALID_ARG_VALUE',
148+
message: 'Argument 0 is a detached ArrayBuffer',
149+
});
150+
for(constviewof[typedArray,dataView]){
151+
assert.throws(()=>call(view),{
152+
code: 'ERR_INVALID_ARG_VALUE',
153+
message: 'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
154+
});
155+
}
131156
}
132157
}finally{
133158
lib.close();

β€Žtest/ffi/test-ffi-memory.jsβ€Ž

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,41 @@ test('ffi getRawPointer returns raw addresses for byte sources', () => {
146146
assert.strictEqual(sharedViewPointer,sharedArrayBufferPointer+2n);
147147
});
148148

149+
test('ffi rejects detached array buffers and views as pointers',()=>{
150+
constarrayBuffer=newArrayBuffer(8);
151+
consttypedArray=newUint8Array(arrayBuffer);
152+
constdataView=newDataView(arrayBuffer);
153+
154+
arrayBuffer.transfer();
155+
156+
assert.throws(()=>ffi.exportArrayBuffer(arrayBuffer,0n,0),{
157+
code: 'ERR_INVALID_ARG_VALUE',
158+
message: 'ArrayBuffer is detached',
159+
});
160+
161+
for(const[value,rawPointerMessage,argumentMessage]of[
162+
[
163+
arrayBuffer,
164+
'ArrayBuffer is detached',
165+
'Argument 0 is a detached ArrayBuffer',
166+
],
167+
...[typedArray,dataView].map((view)=>[
168+
view,
169+
'ArrayBufferView is backed by a detached ArrayBuffer',
170+
'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
171+
]),
172+
]){
173+
assert.throws(()=>ffi.getRawPointer(value),{
174+
code: 'ERR_INVALID_ARG_VALUE',
175+
message: rawPointerMessage,
176+
});
177+
assert.throws(()=>symbols.pointer_to_usize(value),{
178+
code: 'ERR_INVALID_ARG_VALUE',
179+
message: argumentMessage,
180+
});
181+
}
182+
});
183+
149184
test('ffi exportString and exportBuffer copy data into native memory',()=>{
150185
withAllocations(common.mustCall((alloc)=>{
151186
conststringPtr=alloc(16);

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit be2ab89

Browse files
trivikraduh95
authored andcommitted
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 PR-URL: #65083Fixes: #65082 Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent 87ee7bf commit be2ab89

6 files changed

Lines changed: 147 additions & 9 deletions

File tree

β€Žlib/internal/ffi/fast-api.jsβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
'use strict';
22

33
const{
4+
ArrayBufferPrototypeGetDetached,
45
ArrayPrototypeIncludes,
6+
DataViewPrototypeGetBuffer,
57
NumberIsInteger,
68
ObjectDefineProperty,
79
ReflectApply,
810
SafeWeakMap,
911
StringPrototypeIncludes,
1012
TypeError,
13+
TypedArrayPrototypeGetBuffer,
1114
}=primordials;
1215

1316
const{
@@ -16,7 +19,9 @@ const {
1619

1720
const{
1821
isAnyArrayBuffer,
22+
isArrayBuffer,
1923
isArrayBufferView,
24+
isDataView,
2025
}=require('internal/util/types');
2126

2227
const{
@@ -167,6 +172,28 @@ function getStringConversionPointer(state, value, index) {
167172
returnentry.pointer;
168173
}
169174

175+
functiongetRawPointerArg(value,index){
176+
letbuffer;
177+
letisView=false;
178+
if(isArrayBuffer(value)){
179+
buffer=value;
180+
}elseif(isArrayBufferView(value)){
181+
isView=true;
182+
buffer=isDataView(value) ?
183+
DataViewPrototypeGetBuffer(value) :
184+
TypedArrayPrototypeGetBuffer(value);
185+
}
186+
187+
if(buffer!==undefined&&isArrayBuffer(buffer)&&
188+
ArrayBufferPrototypeGetDetached(buffer)){
189+
throwFFIArgError(isView ?
190+
`Argument ${index} is an ArrayBufferView backed by a detached ArrayBuffer` :
191+
`Argument ${index} is a detached ArrayBuffer`);
192+
}
193+
194+
returngetRawPointer(value);
195+
}
196+
170197
functionconvertPointerArg(type,value,stringState,index){
171198
validateFastPointerArg(type,value,index);
172199
if(needsNullPointerConversion(type)&&
@@ -177,7 +204,7 @@ function convertPointerArg(type, value, stringState, index) {
177204
returngetStringConversionPointer(stringState,value,index);
178205
}
179206
if(hasPointerMemoryArg(type,value)){
180-
returngetRawPointer(value);
207+
returngetRawPointerArg(value,index);
181208
}
182209
// Pointer-like values (e.g. BigInt addresses) are passed through, matching
183210
// ToFFIArgument in src/ffi/types.cc and the single-argument fast path.
@@ -287,7 +314,7 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
287314
if(fastBufferInvoke!==undefined){
288315
returnfastBufferInvoke(arg);
289316
}
290-
arg=getRawPointer(arg);
317+
arg=getRawPointerArg(arg,0);
291318
}
292319
returnrawFn(arg);
293320
};

β€Žsrc/ffi/data.ccβ€Ž

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ void ExportBytes(const FunctionCallbackInfo<Value>& args) {
685685
args[0]->IsArrayBufferView()) {
686686
view.ReadValue(args[0]);
687687
if (view.WasDetached()) {
688-
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBufferView backing store");
688+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
689689
return;
690690
}
691691
} else {
@@ -749,15 +749,26 @@ void GetRawPointer(const FunctionCallbackInfo<Value>& args) {
749749
std::shared_ptr<BackingStore> store;
750750

751751
if (args[0]->IsArrayBuffer()) {
752-
store = args[0].As<ArrayBuffer>()->GetBackingStore();
752+
Local<ArrayBuffer> buffer = args[0].As<ArrayBuffer>();
753+
if (buffer->WasDetached()) {
754+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
755+
return;
756+
}
757+
store = buffer->GetBackingStore();
753758
} elseif (args[0]->IsSharedArrayBuffer()) {
754759
store = args[0].As<SharedArrayBuffer>()->GetBackingStore();
755760
} elseif (args[0]->IsArrayBufferView()) {
761+
Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
762+
if (view->Buffer()->WasDetached()) {
763+
THROW_ERR_INVALID_ARG_VALUE(
764+
env, "ArrayBufferView is backed by a detached ArrayBuffer");
765+
return;
766+
}
756767
// Access the store here to ensure that it exists. Small typed arrays
757768
// may not have a store until this point and can instead be stored
758769
// entirely in-heap.
759-
store = args[0].As<ArrayBufferView>()->Buffer()->GetBackingStore();
760-
offset = args[0].As<ArrayBufferView>()->ByteOffset();
770+
store = view->Buffer()->GetBackingStore();
771+
offset = view->ByteOffset();
761772
} else {
762773
THROW_ERR_INVALID_ARG_TYPE(
763774
env,

β€Žsrc/ffi/fast.ccβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,18 +246,45 @@ extern "C" uintptr_t node_ffi_fast_buffer_data(v8::Local<v8::Value> value,
246246
// returns zero after throwing, preventing the native target from seeing an
247247
// invalid pointer value.
248248
constexpruintptr_tkInvalidBuffer = std::numeric_limits<uintptr_t>::max();
249+
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
249250

250251
// Accept only memory-backed JS values in the native helper. Other pointer
251252
// conversions, including strings, stay in the JS wrapper so their temporary
252253
// lifetime is explicit.
253254
if (value->IsArrayBufferView()) {
255+
v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
256+
if (view->Buffer()->WasDetached()) {
257+
if (isolate != nullptr) {
258+
// No HandleScope is active during a Fast API call, so open one before
259+
// creating the error object.
260+
v8::HandleScope scope(isolate);
261+
THROW_ERR_INVALID_ARG_VALUE(
262+
isolate,
263+
"Argument %u is an ArrayBufferView backed by a detached "
264+
"ArrayBuffer",
265+
index);
266+
}
267+
returnkInvalidBuffer;
268+
}
254269
returnPointerFromValue(value);
255270
}
256-
if (value->IsArrayBuffer() || value->IsSharedArrayBuffer()) {
271+
if (value->IsArrayBuffer()) {
272+
if (value.As<v8::ArrayBuffer>()->WasDetached()) {
273+
if (isolate != nullptr) {
274+
// No HandleScope is active during a Fast API call, so open one before
275+
// creating the error object.
276+
v8::HandleScope scope(isolate);
277+
THROW_ERR_INVALID_ARG_VALUE(
278+
isolate, "Argument %u is a detached ArrayBuffer", index);
279+
}
280+
returnkInvalidBuffer;
281+
}
282+
returnPointerFromValue(value);
283+
}
284+
if (value->IsSharedArrayBuffer()) {
257285
returnPointerFromValue(value);
258286
}
259287

260-
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
261288
if (isolate != nullptr) {
262289
// No HandleScope is active during a Fast API call, so open one before
263290
// creating the error object.

β€Žsrc/ffi/types.ccβ€Ž

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,14 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
700700
// invalidating that backing store during the active FFI call is
701701
// unsupported and dangerous.
702702
Local<ArrayBufferView> view = arg.As<ArrayBufferView>();
703+
if (view->Buffer()->WasDetached()) {
704+
THROW_ERR_INVALID_ARG_VALUE(
705+
env,
706+
"Argument %u is an ArrayBufferView backed by a detached "
707+
"ArrayBuffer",
708+
index);
709+
return {};
710+
}
703711
std::shared_ptr<BackingStore> store = view->Buffer()->GetBackingStore();
704712

705713
if (!store) {
@@ -721,6 +729,11 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
721729
// that backing store during the active FFI call is unsupported and
722730
// dangerous.
723731
Local<ArrayBuffer> buffer = arg.As<ArrayBuffer>();
732+
if (buffer->WasDetached()) {
733+
THROW_ERR_INVALID_ARG_VALUE(
734+
env, "Argument %u is a detached ArrayBuffer", index);
735+
return {};
736+
}
724737
std::shared_ptr<BackingStore> store = buffer->GetBackingStore();
725738

726739
if (!store) {

β€Žtest/ffi/test-ffi-fast-buffer.jsβ€Ž

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ test('fast FFI string buffers survive reentrant callbacks', {
9898

9999
test('optimized buffer signatures preserve pointer-like conversions',()=>{
100100
constlib=newffi.DynamicLibrary(libraryPath);
101+
constasPointer=lib.getFunction('pointer_to_usize',{
102+
arguments: ['pointer'],
103+
return: 'u64',
104+
});
101105
constasBuffer=lib.getFunction('pointer_to_usize',{
102106
arguments: ['buffer'],
103107
return: 'u64',
@@ -107,6 +111,10 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
107111
return: 'u64',
108112
});
109113

114+
functioncallPointer(value){
115+
returnasPointer(value);
116+
}
117+
110118
functioncallBuffer(value){
111119
returnasBuffer(value);
112120
}
@@ -117,17 +125,34 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
117125

118126
try{
119127
for(leti=0;i<100_000;i++){
128+
assert.strictEqual(callPointer(0n),0n);
120129
assert.strictEqual(callBuffer(0n),0n);
121130
assert.strictEqual(callArrayBuffer(0n),0n);
122131
}
123132

124-
for(constcallof[callBuffer,callArrayBuffer]){
133+
for(constcallof[callPointer,callBuffer,callArrayBuffer]){
125134
assert.strictEqual(call(null),0n);
126135
assert.strictEqual(call(undefined),0n);
127136
assert.notStrictEqual(call('ffi'),0n);
128137

129138
constbytes=Buffer.alloc(1);
130139
assert.strictEqual(call(bytes),ffi.getRawPointer(bytes));
140+
141+
constarrayBuffer=newArrayBuffer(8);
142+
consttypedArray=newUint8Array(arrayBuffer);
143+
constdataView=newDataView(arrayBuffer);
144+
arrayBuffer.transfer();
145+
146+
assert.throws(()=>call(arrayBuffer),{
147+
code: 'ERR_INVALID_ARG_VALUE',
148+
message: 'Argument 0 is a detached ArrayBuffer',
149+
});
150+
for(constviewof[typedArray,dataView]){
151+
assert.throws(()=>call(view),{
152+
code: 'ERR_INVALID_ARG_VALUE',
153+
message: 'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
154+
});
155+
}
131156
}
132157
}finally{
133158
lib.close();

β€Žtest/ffi/test-ffi-memory.jsβ€Ž

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,41 @@ test('ffi getRawPointer returns raw addresses for byte sources', () => {
146146
assert.strictEqual(sharedViewPointer,sharedArrayBufferPointer+2n);
147147
});
148148

149+
test('ffi rejects detached array buffers and views as pointers',()=>{
150+
constarrayBuffer=newArrayBuffer(8);
151+
consttypedArray=newUint8Array(arrayBuffer);
152+
constdataView=newDataView(arrayBuffer);
153+
154+
arrayBuffer.transfer();
155+
156+
assert.throws(()=>ffi.exportArrayBuffer(arrayBuffer,0n,0),{
157+
code: 'ERR_INVALID_ARG_VALUE',
158+
message: 'ArrayBuffer is detached',
159+
});
160+
161+
for(const[value,rawPointerMessage,argumentMessage]of[
162+
[
163+
arrayBuffer,
164+
'ArrayBuffer is detached',
165+
'Argument 0 is a detached ArrayBuffer',
166+
],
167+
...[typedArray,dataView].map((view)=>[
168+
view,
169+
'ArrayBufferView is backed by a detached ArrayBuffer',
170+
'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
171+
]),
172+
]){
173+
assert.throws(()=>ffi.getRawPointer(value),{
174+
code: 'ERR_INVALID_ARG_VALUE',
175+
message: rawPointerMessage,
176+
});
177+
assert.throws(()=>symbols.pointer_to_usize(value),{
178+
code: 'ERR_INVALID_ARG_VALUE',
179+
message: argumentMessage,
180+
});
181+
}
182+
});
183+
149184
test('ffi exportString and exportBuffer copy data into native memory',()=>{
150185
withAllocations(common.mustCall((alloc)=>{
151186
conststringPtr=alloc(16);

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit be2ab89

Browse files
trivikraduh95
authored andcommitted
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 PR-URL: #65083Fixes: #65082 Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent 87ee7bf commit be2ab89

6 files changed

Lines changed: 147 additions & 9 deletions

File tree

β€Žlib/internal/ffi/fast-api.jsβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
'use strict';
22

33
const{
4+
ArrayBufferPrototypeGetDetached,
45
ArrayPrototypeIncludes,
6+
DataViewPrototypeGetBuffer,
57
NumberIsInteger,
68
ObjectDefineProperty,
79
ReflectApply,
810
SafeWeakMap,
911
StringPrototypeIncludes,
1012
TypeError,
13+
TypedArrayPrototypeGetBuffer,
1114
}=primordials;
1215

1316
const{
@@ -16,7 +19,9 @@ const {
1619

1720
const{
1821
isAnyArrayBuffer,
22+
isArrayBuffer,
1923
isArrayBufferView,
24+
isDataView,
2025
}=require('internal/util/types');
2126

2227
const{
@@ -167,6 +172,28 @@ function getStringConversionPointer(state, value, index) {
167172
returnentry.pointer;
168173
}
169174

175+
functiongetRawPointerArg(value,index){
176+
letbuffer;
177+
letisView=false;
178+
if(isArrayBuffer(value)){
179+
buffer=value;
180+
}elseif(isArrayBufferView(value)){
181+
isView=true;
182+
buffer=isDataView(value) ?
183+
DataViewPrototypeGetBuffer(value) :
184+
TypedArrayPrototypeGetBuffer(value);
185+
}
186+
187+
if(buffer!==undefined&&isArrayBuffer(buffer)&&
188+
ArrayBufferPrototypeGetDetached(buffer)){
189+
throwFFIArgError(isView ?
190+
`Argument ${index} is an ArrayBufferView backed by a detached ArrayBuffer` :
191+
`Argument ${index} is a detached ArrayBuffer`);
192+
}
193+
194+
returngetRawPointer(value);
195+
}
196+
170197
functionconvertPointerArg(type,value,stringState,index){
171198
validateFastPointerArg(type,value,index);
172199
if(needsNullPointerConversion(type)&&
@@ -177,7 +204,7 @@ function convertPointerArg(type, value, stringState, index) {
177204
returngetStringConversionPointer(stringState,value,index);
178205
}
179206
if(hasPointerMemoryArg(type,value)){
180-
returngetRawPointer(value);
207+
returngetRawPointerArg(value,index);
181208
}
182209
// Pointer-like values (e.g. BigInt addresses) are passed through, matching
183210
// ToFFIArgument in src/ffi/types.cc and the single-argument fast path.
@@ -287,7 +314,7 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
287314
if(fastBufferInvoke!==undefined){
288315
returnfastBufferInvoke(arg);
289316
}
290-
arg=getRawPointer(arg);
317+
arg=getRawPointerArg(arg,0);
291318
}
292319
returnrawFn(arg);
293320
};

β€Žsrc/ffi/data.ccβ€Ž

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ void ExportBytes(const FunctionCallbackInfo<Value>& args) {
685685
args[0]->IsArrayBufferView()) {
686686
view.ReadValue(args[0]);
687687
if (view.WasDetached()) {
688-
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBufferView backing store");
688+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
689689
return;
690690
}
691691
} else {
@@ -749,15 +749,26 @@ void GetRawPointer(const FunctionCallbackInfo<Value>& args) {
749749
std::shared_ptr<BackingStore> store;
750750

751751
if (args[0]->IsArrayBuffer()) {
752-
store = args[0].As<ArrayBuffer>()->GetBackingStore();
752+
Local<ArrayBuffer> buffer = args[0].As<ArrayBuffer>();
753+
if (buffer->WasDetached()) {
754+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
755+
return;
756+
}
757+
store = buffer->GetBackingStore();
753758
} elseif (args[0]->IsSharedArrayBuffer()) {
754759
store = args[0].As<SharedArrayBuffer>()->GetBackingStore();
755760
} elseif (args[0]->IsArrayBufferView()) {
761+
Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
762+
if (view->Buffer()->WasDetached()) {
763+
THROW_ERR_INVALID_ARG_VALUE(
764+
env, "ArrayBufferView is backed by a detached ArrayBuffer");
765+
return;
766+
}
756767
// Access the store here to ensure that it exists. Small typed arrays
757768
// may not have a store until this point and can instead be stored
758769
// entirely in-heap.
759-
store = args[0].As<ArrayBufferView>()->Buffer()->GetBackingStore();
760-
offset = args[0].As<ArrayBufferView>()->ByteOffset();
770+
store = view->Buffer()->GetBackingStore();
771+
offset = view->ByteOffset();
761772
} else {
762773
THROW_ERR_INVALID_ARG_TYPE(
763774
env,

β€Žsrc/ffi/fast.ccβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,18 +246,45 @@ extern "C" uintptr_t node_ffi_fast_buffer_data(v8::Local<v8::Value> value,
246246
// returns zero after throwing, preventing the native target from seeing an
247247
// invalid pointer value.
248248
constexpruintptr_tkInvalidBuffer = std::numeric_limits<uintptr_t>::max();
249+
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
249250

250251
// Accept only memory-backed JS values in the native helper. Other pointer
251252
// conversions, including strings, stay in the JS wrapper so their temporary
252253
// lifetime is explicit.
253254
if (value->IsArrayBufferView()) {
255+
v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
256+
if (view->Buffer()->WasDetached()) {
257+
if (isolate != nullptr) {
258+
// No HandleScope is active during a Fast API call, so open one before
259+
// creating the error object.
260+
v8::HandleScope scope(isolate);
261+
THROW_ERR_INVALID_ARG_VALUE(
262+
isolate,
263+
"Argument %u is an ArrayBufferView backed by a detached "
264+
"ArrayBuffer",
265+
index);
266+
}
267+
returnkInvalidBuffer;
268+
}
254269
returnPointerFromValue(value);
255270
}
256-
if (value->IsArrayBuffer() || value->IsSharedArrayBuffer()) {
271+
if (value->IsArrayBuffer()) {
272+
if (value.As<v8::ArrayBuffer>()->WasDetached()) {
273+
if (isolate != nullptr) {
274+
// No HandleScope is active during a Fast API call, so open one before
275+
// creating the error object.
276+
v8::HandleScope scope(isolate);
277+
THROW_ERR_INVALID_ARG_VALUE(
278+
isolate, "Argument %u is a detached ArrayBuffer", index);
279+
}
280+
returnkInvalidBuffer;
281+
}
282+
returnPointerFromValue(value);
283+
}
284+
if (value->IsSharedArrayBuffer()) {
257285
returnPointerFromValue(value);
258286
}
259287

260-
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
261288
if (isolate != nullptr) {
262289
// No HandleScope is active during a Fast API call, so open one before
263290
// creating the error object.

β€Žsrc/ffi/types.ccβ€Ž

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,14 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
700700
// invalidating that backing store during the active FFI call is
701701
// unsupported and dangerous.
702702
Local<ArrayBufferView> view = arg.As<ArrayBufferView>();
703+
if (view->Buffer()->WasDetached()) {
704+
THROW_ERR_INVALID_ARG_VALUE(
705+
env,
706+
"Argument %u is an ArrayBufferView backed by a detached "
707+
"ArrayBuffer",
708+
index);
709+
return {};
710+
}
703711
std::shared_ptr<BackingStore> store = view->Buffer()->GetBackingStore();
704712

705713
if (!store) {
@@ -721,6 +729,11 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
721729
// that backing store during the active FFI call is unsupported and
722730
// dangerous.
723731
Local<ArrayBuffer> buffer = arg.As<ArrayBuffer>();
732+
if (buffer->WasDetached()) {
733+
THROW_ERR_INVALID_ARG_VALUE(
734+
env, "Argument %u is a detached ArrayBuffer", index);
735+
return {};
736+
}
724737
std::shared_ptr<BackingStore> store = buffer->GetBackingStore();
725738

726739
if (!store) {

β€Žtest/ffi/test-ffi-fast-buffer.jsβ€Ž

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ test('fast FFI string buffers survive reentrant callbacks', {
9898

9999
test('optimized buffer signatures preserve pointer-like conversions',()=>{
100100
constlib=newffi.DynamicLibrary(libraryPath);
101+
constasPointer=lib.getFunction('pointer_to_usize',{
102+
arguments: ['pointer'],
103+
return: 'u64',
104+
});
101105
constasBuffer=lib.getFunction('pointer_to_usize',{
102106
arguments: ['buffer'],
103107
return: 'u64',
@@ -107,6 +111,10 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
107111
return: 'u64',
108112
});
109113

114+
functioncallPointer(value){
115+
returnasPointer(value);
116+
}
117+
110118
functioncallBuffer(value){
111119
returnasBuffer(value);
112120
}
@@ -117,17 +125,34 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
117125

118126
try{
119127
for(leti=0;i<100_000;i++){
128+
assert.strictEqual(callPointer(0n),0n);
120129
assert.strictEqual(callBuffer(0n),0n);
121130
assert.strictEqual(callArrayBuffer(0n),0n);
122131
}
123132

124-
for(constcallof[callBuffer,callArrayBuffer]){
133+
for(constcallof[callPointer,callBuffer,callArrayBuffer]){
125134
assert.strictEqual(call(null),0n);
126135
assert.strictEqual(call(undefined),0n);
127136
assert.notStrictEqual(call('ffi'),0n);
128137

129138
constbytes=Buffer.alloc(1);
130139
assert.strictEqual(call(bytes),ffi.getRawPointer(bytes));
140+
141+
constarrayBuffer=newArrayBuffer(8);
142+
consttypedArray=newUint8Array(arrayBuffer);
143+
constdataView=newDataView(arrayBuffer);
144+
arrayBuffer.transfer();
145+
146+
assert.throws(()=>call(arrayBuffer),{
147+
code: 'ERR_INVALID_ARG_VALUE',
148+
message: 'Argument 0 is a detached ArrayBuffer',
149+
});
150+
for(constviewof[typedArray,dataView]){
151+
assert.throws(()=>call(view),{
152+
code: 'ERR_INVALID_ARG_VALUE',
153+
message: 'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
154+
});
155+
}
131156
}
132157
}finally{
133158
lib.close();

β€Žtest/ffi/test-ffi-memory.jsβ€Ž

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,41 @@ test('ffi getRawPointer returns raw addresses for byte sources', () => {
146146
assert.strictEqual(sharedViewPointer,sharedArrayBufferPointer+2n);
147147
});
148148

149+
test('ffi rejects detached array buffers and views as pointers',()=>{
150+
constarrayBuffer=newArrayBuffer(8);
151+
consttypedArray=newUint8Array(arrayBuffer);
152+
constdataView=newDataView(arrayBuffer);
153+
154+
arrayBuffer.transfer();
155+
156+
assert.throws(()=>ffi.exportArrayBuffer(arrayBuffer,0n,0),{
157+
code: 'ERR_INVALID_ARG_VALUE',
158+
message: 'ArrayBuffer is detached',
159+
});
160+
161+
for(const[value,rawPointerMessage,argumentMessage]of[
162+
[
163+
arrayBuffer,
164+
'ArrayBuffer is detached',
165+
'Argument 0 is a detached ArrayBuffer',
166+
],
167+
...[typedArray,dataView].map((view)=>[
168+
view,
169+
'ArrayBufferView is backed by a detached ArrayBuffer',
170+
'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
171+
]),
172+
]){
173+
assert.throws(()=>ffi.getRawPointer(value),{
174+
code: 'ERR_INVALID_ARG_VALUE',
175+
message: rawPointerMessage,
176+
});
177+
assert.throws(()=>symbols.pointer_to_usize(value),{
178+
code: 'ERR_INVALID_ARG_VALUE',
179+
message: argumentMessage,
180+
});
181+
}
182+
});
183+
149184
test('ffi exportString and exportBuffer copy data into native memory',()=>{
150185
withAllocations(common.mustCall((alloc)=>{
151186
conststringPtr=alloc(16);

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit be2ab89

Browse files
trivikraduh95
authored andcommitted
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 PR-URL: #65083Fixes: #65082 Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent 87ee7bf commit be2ab89

6 files changed

Lines changed: 147 additions & 9 deletions

File tree

β€Žlib/internal/ffi/fast-api.jsβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
'use strict';
22

33
const{
4+
ArrayBufferPrototypeGetDetached,
45
ArrayPrototypeIncludes,
6+
DataViewPrototypeGetBuffer,
57
NumberIsInteger,
68
ObjectDefineProperty,
79
ReflectApply,
810
SafeWeakMap,
911
StringPrototypeIncludes,
1012
TypeError,
13+
TypedArrayPrototypeGetBuffer,
1114
}=primordials;
1215

1316
const{
@@ -16,7 +19,9 @@ const {
1619

1720
const{
1821
isAnyArrayBuffer,
22+
isArrayBuffer,
1923
isArrayBufferView,
24+
isDataView,
2025
}=require('internal/util/types');
2126

2227
const{
@@ -167,6 +172,28 @@ function getStringConversionPointer(state, value, index) {
167172
returnentry.pointer;
168173
}
169174

175+
functiongetRawPointerArg(value,index){
176+
letbuffer;
177+
letisView=false;
178+
if(isArrayBuffer(value)){
179+
buffer=value;
180+
}elseif(isArrayBufferView(value)){
181+
isView=true;
182+
buffer=isDataView(value) ?
183+
DataViewPrototypeGetBuffer(value) :
184+
TypedArrayPrototypeGetBuffer(value);
185+
}
186+
187+
if(buffer!==undefined&&isArrayBuffer(buffer)&&
188+
ArrayBufferPrototypeGetDetached(buffer)){
189+
throwFFIArgError(isView ?
190+
`Argument ${index} is an ArrayBufferView backed by a detached ArrayBuffer` :
191+
`Argument ${index} is a detached ArrayBuffer`);
192+
}
193+
194+
returngetRawPointer(value);
195+
}
196+
170197
functionconvertPointerArg(type,value,stringState,index){
171198
validateFastPointerArg(type,value,index);
172199
if(needsNullPointerConversion(type)&&
@@ -177,7 +204,7 @@ function convertPointerArg(type, value, stringState, index) {
177204
returngetStringConversionPointer(stringState,value,index);
178205
}
179206
if(hasPointerMemoryArg(type,value)){
180-
returngetRawPointer(value);
207+
returngetRawPointerArg(value,index);
181208
}
182209
// Pointer-like values (e.g. BigInt addresses) are passed through, matching
183210
// ToFFIArgument in src/ffi/types.cc and the single-argument fast path.
@@ -287,7 +314,7 @@ function wrapWithRawPointerConversions(rawFn, argumentTypes, owner) {
287314
if(fastBufferInvoke!==undefined){
288315
returnfastBufferInvoke(arg);
289316
}
290-
arg=getRawPointer(arg);
317+
arg=getRawPointerArg(arg,0);
291318
}
292319
returnrawFn(arg);
293320
};

β€Žsrc/ffi/data.ccβ€Ž

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ void ExportBytes(const FunctionCallbackInfo<Value>& args) {
685685
args[0]->IsArrayBufferView()) {
686686
view.ReadValue(args[0]);
687687
if (view.WasDetached()) {
688-
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBufferView backing store");
688+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
689689
return;
690690
}
691691
} else {
@@ -749,15 +749,26 @@ void GetRawPointer(const FunctionCallbackInfo<Value>& args) {
749749
std::shared_ptr<BackingStore> store;
750750

751751
if (args[0]->IsArrayBuffer()) {
752-
store = args[0].As<ArrayBuffer>()->GetBackingStore();
752+
Local<ArrayBuffer> buffer = args[0].As<ArrayBuffer>();
753+
if (buffer->WasDetached()) {
754+
THROW_ERR_INVALID_ARG_VALUE(env, "ArrayBuffer is detached");
755+
return;
756+
}
757+
store = buffer->GetBackingStore();
753758
} elseif (args[0]->IsSharedArrayBuffer()) {
754759
store = args[0].As<SharedArrayBuffer>()->GetBackingStore();
755760
} elseif (args[0]->IsArrayBufferView()) {
761+
Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
762+
if (view->Buffer()->WasDetached()) {
763+
THROW_ERR_INVALID_ARG_VALUE(
764+
env, "ArrayBufferView is backed by a detached ArrayBuffer");
765+
return;
766+
}
756767
// Access the store here to ensure that it exists. Small typed arrays
757768
// may not have a store until this point and can instead be stored
758769
// entirely in-heap.
759-
store = args[0].As<ArrayBufferView>()->Buffer()->GetBackingStore();
760-
offset = args[0].As<ArrayBufferView>()->ByteOffset();
770+
store = view->Buffer()->GetBackingStore();
771+
offset = view->ByteOffset();
761772
} else {
762773
THROW_ERR_INVALID_ARG_TYPE(
763774
env,

β€Žsrc/ffi/fast.ccβ€Ž

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,18 +246,45 @@ extern "C" uintptr_t node_ffi_fast_buffer_data(v8::Local<v8::Value> value,
246246
// returns zero after throwing, preventing the native target from seeing an
247247
// invalid pointer value.
248248
constexpruintptr_tkInvalidBuffer = std::numeric_limits<uintptr_t>::max();
249+
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
249250

250251
// Accept only memory-backed JS values in the native helper. Other pointer
251252
// conversions, including strings, stay in the JS wrapper so their temporary
252253
// lifetime is explicit.
253254
if (value->IsArrayBufferView()) {
255+
v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
256+
if (view->Buffer()->WasDetached()) {
257+
if (isolate != nullptr) {
258+
// No HandleScope is active during a Fast API call, so open one before
259+
// creating the error object.
260+
v8::HandleScope scope(isolate);
261+
THROW_ERR_INVALID_ARG_VALUE(
262+
isolate,
263+
"Argument %u is an ArrayBufferView backed by a detached "
264+
"ArrayBuffer",
265+
index);
266+
}
267+
returnkInvalidBuffer;
268+
}
254269
returnPointerFromValue(value);
255270
}
256-
if (value->IsArrayBuffer() || value->IsSharedArrayBuffer()) {
271+
if (value->IsArrayBuffer()) {
272+
if (value.As<v8::ArrayBuffer>()->WasDetached()) {
273+
if (isolate != nullptr) {
274+
// No HandleScope is active during a Fast API call, so open one before
275+
// creating the error object.
276+
v8::HandleScope scope(isolate);
277+
THROW_ERR_INVALID_ARG_VALUE(
278+
isolate, "Argument %u is a detached ArrayBuffer", index);
279+
}
280+
returnkInvalidBuffer;
281+
}
282+
returnPointerFromValue(value);
283+
}
284+
if (value->IsSharedArrayBuffer()) {
257285
returnPointerFromValue(value);
258286
}
259287

260-
v8::Isolate* isolate = options != nullptr ? options->isolate : nullptr;
261288
if (isolate != nullptr) {
262289
// No HandleScope is active during a Fast API call, so open one before
263290
// creating the error object.

β€Žsrc/ffi/types.ccβ€Ž

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,14 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
700700
// invalidating that backing store during the active FFI call is
701701
// unsupported and dangerous.
702702
Local<ArrayBufferView> view = arg.As<ArrayBufferView>();
703+
if (view->Buffer()->WasDetached()) {
704+
THROW_ERR_INVALID_ARG_VALUE(
705+
env,
706+
"Argument %u is an ArrayBufferView backed by a detached "
707+
"ArrayBuffer",
708+
index);
709+
return {};
710+
}
703711
std::shared_ptr<BackingStore> store = view->Buffer()->GetBackingStore();
704712

705713
if (!store) {
@@ -721,6 +729,11 @@ Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
721729
// that backing store during the active FFI call is unsupported and
722730
// dangerous.
723731
Local<ArrayBuffer> buffer = arg.As<ArrayBuffer>();
732+
if (buffer->WasDetached()) {
733+
THROW_ERR_INVALID_ARG_VALUE(
734+
env, "Argument %u is a detached ArrayBuffer", index);
735+
return {};
736+
}
724737
std::shared_ptr<BackingStore> store = buffer->GetBackingStore();
725738

726739
if (!store) {

β€Žtest/ffi/test-ffi-fast-buffer.jsβ€Ž

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ test('fast FFI string buffers survive reentrant callbacks', {
9898

9999
test('optimized buffer signatures preserve pointer-like conversions',()=>{
100100
constlib=newffi.DynamicLibrary(libraryPath);
101+
constasPointer=lib.getFunction('pointer_to_usize',{
102+
arguments: ['pointer'],
103+
return: 'u64',
104+
});
101105
constasBuffer=lib.getFunction('pointer_to_usize',{
102106
arguments: ['buffer'],
103107
return: 'u64',
@@ -107,6 +111,10 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
107111
return: 'u64',
108112
});
109113

114+
functioncallPointer(value){
115+
returnasPointer(value);
116+
}
117+
110118
functioncallBuffer(value){
111119
returnasBuffer(value);
112120
}
@@ -117,17 +125,34 @@ test('optimized buffer signatures preserve pointer-like conversions', () => {
117125

118126
try{
119127
for(leti=0;i<100_000;i++){
128+
assert.strictEqual(callPointer(0n),0n);
120129
assert.strictEqual(callBuffer(0n),0n);
121130
assert.strictEqual(callArrayBuffer(0n),0n);
122131
}
123132

124-
for(constcallof[callBuffer,callArrayBuffer]){
133+
for(constcallof[callPointer,callBuffer,callArrayBuffer]){
125134
assert.strictEqual(call(null),0n);
126135
assert.strictEqual(call(undefined),0n);
127136
assert.notStrictEqual(call('ffi'),0n);
128137

129138
constbytes=Buffer.alloc(1);
130139
assert.strictEqual(call(bytes),ffi.getRawPointer(bytes));
140+
141+
constarrayBuffer=newArrayBuffer(8);
142+
consttypedArray=newUint8Array(arrayBuffer);
143+
constdataView=newDataView(arrayBuffer);
144+
arrayBuffer.transfer();
145+
146+
assert.throws(()=>call(arrayBuffer),{
147+
code: 'ERR_INVALID_ARG_VALUE',
148+
message: 'Argument 0 is a detached ArrayBuffer',
149+
});
150+
for(constviewof[typedArray,dataView]){
151+
assert.throws(()=>call(view),{
152+
code: 'ERR_INVALID_ARG_VALUE',
153+
message: 'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
154+
});
155+
}
131156
}
132157
}finally{
133158
lib.close();

β€Žtest/ffi/test-ffi-memory.jsβ€Ž

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,41 @@ test('ffi getRawPointer returns raw addresses for byte sources', () => {
146146
assert.strictEqual(sharedViewPointer,sharedArrayBufferPointer+2n);
147147
});
148148

149+
test('ffi rejects detached array buffers and views as pointers',()=>{
150+
constarrayBuffer=newArrayBuffer(8);
151+
consttypedArray=newUint8Array(arrayBuffer);
152+
constdataView=newDataView(arrayBuffer);
153+
154+
arrayBuffer.transfer();
155+
156+
assert.throws(()=>ffi.exportArrayBuffer(arrayBuffer,0n,0),{
157+
code: 'ERR_INVALID_ARG_VALUE',
158+
message: 'ArrayBuffer is detached',
159+
});
160+
161+
for(const[value,rawPointerMessage,argumentMessage]of[
162+
[
163+
arrayBuffer,
164+
'ArrayBuffer is detached',
165+
'Argument 0 is a detached ArrayBuffer',
166+
],
167+
...[typedArray,dataView].map((view)=>[
168+
view,
169+
'ArrayBufferView is backed by a detached ArrayBuffer',
170+
'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
171+
]),
172+
]){
173+
assert.throws(()=>ffi.getRawPointer(value),{
174+
code: 'ERR_INVALID_ARG_VALUE',
175+
message: rawPointerMessage,
176+
});
177+
assert.throws(()=>symbols.pointer_to_usize(value),{
178+
code: 'ERR_INVALID_ARG_VALUE',
179+
message: argumentMessage,
180+
});
181+
}
182+
});
183+
149184
test('ffi exportString and exportBuffer copy data into native memory',()=>{
150185
withAllocations(common.mustCall((alloc)=>{
151186
conststringPtr=alloc(16);

0 commit comments

Comments
Β (0)