Commit de073c56faf for nodejs

commit de073c56faf955fc69dc2db60d1bf5e6d0af2e33
Author: Trivikram Kamat <trivikr.dev@gmail.com>
Date:   Fri Oct 2 00:34:55 2026 -0700

    ffi: throw on allocation failure in toArrayBuffer()

    The copy path of toArrayBuffer() allocated its backing store with
    V8's default failure mode, so a large length aborted the process
    with a fatal out-of-memory error. toBuffer() throws a catchable
    ERR_MEMORY_ALLOCATION_FAILED for the same input.

    Allocate with kReturnNull and throw ERR_MEMORY_ALLOCATION_FAILED
    when the allocation fails. The memory is left uninitialized because
    memcpy() fills it right after.

    Signed-off-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com>
    Assisted-by: claude:opus-5.5
    PR-URL: https://github.com/nodejs/node/pull/66406
    Fixes: https://github.com/nodejs/node/issues/66405
    Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
    Reviewed-By: Anna Henningsen <anna@addaleax.net>
    Reviewed-By: Paolo Insogna <paolo@cowtech.it>

diff --git a/src/ffi/data.cc b/src/ffi/data.cc
index cd2a2481ef6..ca3315d43a3 100644
--- a/src/ffi/data.cc
+++ b/src/ffi/data.cc
@@ -15,6 +15,8 @@
 using v8::ArrayBuffer;
 using v8::ArrayBufferView;
 using v8::BackingStore;
+using v8::BackingStoreInitializationMode;
+using v8::BackingStoreOnFailureMode;
 using v8::BigInt;
 using v8::FunctionCallbackInfo;
 using v8::Integer;
@@ -670,8 +672,15 @@ void ToArrayBuffer(const FunctionCallbackInfo<Value>& args) {

   Local<ArrayBuffer> ab;
   if (copy) {
-    std::unique_ptr<BackingStore> store =
-        ArrayBuffer::NewBackingStore(isolate, len);
+    std::unique_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(
+        isolate,
+        len,
+        BackingStoreInitializationMode::kUninitialized,
+        BackingStoreOnFailureMode::kReturnNull);
+    if (!store) [[unlikely]] {
+      THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
+      return;
+    }
     if (len > 0) memcpy(store->Data(), reinterpret_cast<void*>(ptr), len);
     ab = ArrayBuffer::New(isolate, std::move(store));
   } else {
diff --git a/test/ffi/test-ffi-memory.js b/test/ffi/test-ffi-memory.js
index be9160337dd..1ca54ce897c 100644
--- a/test/ffi/test-ffi-memory.js
+++ b/test/ffi/test-ffi-memory.js
@@ -371,6 +371,17 @@ test('ffi rejects unsafe integers as an offset or length', () => {
   }));
 });

+test('ffi toBuffer and toArrayBuffer throw when the copy cannot be allocated', {
+  skip: (bufferConstants.MAX_LENGTH < 2 ** 50 && 'requires a 64-bit buffer length limit') ||
+        (common.isASan && 'ASan aborts on huge allocations') ||
+        (common.isAIX && 'huge allocations may succeed on AIX and get the process killed'),
+}, () => {
+  // The allocation fails before the source pointer is read.
+  const error = { code: 'ERR_MEMORY_ALLOCATION_FAILED' };
+  assert.throws(() => ffi.toBuffer(1n, 2 ** 50), error);
+  assert.throws(() => ffi.toArrayBuffer(1n, 2 ** 50), error);
+});
+
 test('ffi memory helpers reject missing required arguments', () => {
   const widths = ['Int8', 'Uint8', 'Int16', 'Uint16', 'Int32', 'Uint32',
                   'Int64', 'Uint64', 'Float32', 'Float64'];