Memory safety / allocation failure handling (Out-of-memory)
Description
The commit implements memory-allocation failure handling for ArrayBuffer BackingStore usage across several Node.js internals (encoding_binding.cc, node_blob.cc, node_buffer.cc). Previously, allocation failures could result in unchecked pointers or crashes due to relying on an assumption that a backing store (bs/store) would always be non-null (e.g., CHECK(bs);). The patch adds OnFailureMode for backing stores, checks for null returns, and throws a memory-allocation error (ERR_MEMORY_ALLOCATION_FAILED) which is now a RangeError to align with V8's OutOfMemory error type. It also updates ERR_MEMORY_ALLOCATION_FAILED in node_errors.h from a generic Error to a RangeError. This reduces the risk of memory-safety issues (null dereferences or use-after-free) when memory allocation fails during operations like Encoding to UTF-8, Buffer.concat, or ArrayBuffer creation. While not a classic security flaw like XSS/SQLi, it mitigates memory-safety vulnerabilities that could be exploited via resource exhaustion or crashes under memory pressure, which can have security implications in production environments.
Proof of Concept
// Proof-of-concept: attempt to trigger an allocation failure path during common memory-heavy operations
// This simulates memory pressure that may cause ArrayBuffer backing store allocation to fail,
// resulting in a RangeError: ERR_MEMORY_ALLOCATION_FAILED
const huge = 'a'.repeat(2 ** 28); // ~256MB of data in a single string (adjust size for environment)
try {
// Force UTF-8 encoding path that allocates a backing store (e.g., Buffer.from with a large string)
const buf = Buffer.from(huge, 'utf8');
console.log('Allocated buffer length:', buf.length);
} catch (e) {
console.log('Caught:', e.name, e.message);
}
Commit Details
Author: Chengzhong Wu
Date: 2026-01-25 15:18 UTC
Message:
src: throw RangeError on failed ArrayBuffer BackingStore allocation
This also updates `ERR_MEMORY_ALLOCATION_FAILED` to be a RangeError,
aligning with V8's OutOfMemory error type.
PR-URL: https://github.com/nodejs/node/pull/61480
Refs: https://github.com/nodejs/node/blob/c755b0113ce0cb6d83baf2cf070ba381a5673db2/deps/v8/src/builtins/builtins-typed-array.cc#L584
Refs: https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype.grow
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Triage Assessment
Vulnerability Type: Memory safety (allocation failure handling)
Confidence: MEDIUM
Reasoning:
The commit changes allocation failure handling for ArrayBuffer BackingStore usage and updates the error type to RangeError. It adds guards on allocation results and returns/throws appropriately, reducing risk of null dereferences or use-after-free when memory allocation fails. While not a classic vulnerability like XSS or SQLi, it mitigates memory-safety related failures that could be exploitable under certain conditions.
Verification Assessment
Vulnerability Type: Memory safety / allocation failure handling (Out-of-memory)
Confidence: MEDIUM
Affected Versions: < 25.9.0
Code Diff
diff --git a/src/encoding_binding.cc b/src/encoding_binding.cc
index 7683d205aa6a3e..aa901350180b3c 100644
--- a/src/encoding_binding.cc
+++ b/src/encoding_binding.cc
@@ -17,6 +17,7 @@ namespace encoding_binding {
using v8::ArrayBuffer;
using v8::BackingStore;
using v8::BackingStoreInitializationMode;
+using v8::BackingStoreOnFailureMode;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
@@ -317,9 +318,15 @@ void BindingData::EncodeUtf8String(const FunctionCallbackInfo<Value>& args) {
Local<ArrayBuffer> ab;
{
std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
- isolate, length, BackingStoreInitializationMode::kUninitialized);
+ isolate,
+ length,
+ BackingStoreInitializationMode::kUninitialized,
+ BackingStoreOnFailureMode::kReturnNull);
- CHECK(bs);
+ if (!bs) [[unlikely]] {
+ THROW_ERR_MEMORY_ALLOCATION_FAILED(isolate);
+ return;
+ }
// We are certain that `data` is sufficiently large
str->WriteUtf8V2(isolate,
diff --git a/src/node_blob.cc b/src/node_blob.cc
index ab862bf93a411e..417cd8cbd307b9 100644
--- a/src/node_blob.cc
+++ b/src/node_blob.cc
@@ -22,6 +22,7 @@ using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStore;
using v8::BackingStoreInitializationMode;
+using v8::BackingStoreOnFailureMode;
using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
@@ -83,7 +84,14 @@ void Concat(const FunctionCallbackInfo<Value>& args) {
}
std::shared_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(
- isolate, total, BackingStoreInitializationMode::kUninitialized);
+ isolate,
+ total,
+ BackingStoreInitializationMode::kUninitialized,
+ BackingStoreOnFailureMode::kReturnNull);
+ if (!store) [[unlikely]] {
+ THROW_ERR_MEMORY_ALLOCATION_FAILED(isolate);
+ return;
+ }
uint8_t* ptr = static_cast<uint8_t*>(store->Data());
for (size_t n = 0; n < views.size(); n++) {
uint8_t* from =
diff --git a/src/node_buffer.cc b/src/node_buffer.cc
index ddee7b7e40c3ee..e1bee00825d140 100644
--- a/src/node_buffer.cc
+++ b/src/node_buffer.cc
@@ -59,6 +59,7 @@ using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStore;
using v8::BackingStoreInitializationMode;
+using v8::BackingStoreOnFailureMode;
using v8::CFunction;
using v8::Context;
using v8::EscapableHandleScope;
@@ -304,17 +305,20 @@ MaybeLocal<Object> New(Isolate* isolate,
EscapableHandleScope scope(isolate);
size_t length;
- if (!StringBytes::Size(isolate, string, enc).To(&length))
- return Local<Object>();
+ if (!StringBytes::Size(isolate, string, enc).To(&length)) return {};
size_t actual = 0;
std::unique_ptr<BackingStore> store;
if (length > 0) {
- store = ArrayBuffer::NewBackingStore(isolate, length);
+ store = ArrayBuffer::NewBackingStore(
+ isolate,
+ length,
+ BackingStoreInitializationMode::kZeroInitialized,
+ BackingStoreOnFailureMode::kReturnNull);
if (!store) [[unlikely]] {
THROW_ERR_MEMORY_ALLOCATION_FAILED(isolate);
- return Local<Object>();
+ return {};
}
actual = StringBytes::Write(
@@ -329,7 +333,14 @@ MaybeLocal<Object> New(Isolate* isolate,
if (actual < length) {
std::unique_ptr<BackingStore> old_store = std::move(store);
store = ArrayBuffer::NewBackingStore(
- isolate, actual, BackingStoreInitializationMode::kUninitialized);
+ isolate,
+ actual,
+ BackingStoreInitializationMode::kUninitialized,
+ BackingStoreOnFailureMode::kReturnNull);
+ if (!store) [[unlikely]] {
+ THROW_ERR_MEMORY_ALLOCATION_FAILED(isolate);
+ return {};
+ }
memcpy(store->Data(), old_store->Data(), actual);
}
Local<ArrayBuffer> buf = ArrayBuffer::New(isolate, std::move(store));
@@ -372,7 +383,14 @@ MaybeLocal<Object> New(Environment* env, size_t length) {
Local<ArrayBuffer> ab;
{
std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
- isolate, length, BackingStoreInitializationMode::kUninitialized);
+ isolate,
+ length,
+ BackingStoreInitializationMode::kUninitialized,
+ BackingStoreOnFailureMode::kReturnNull);
+ if (!bs) [[unlikely]] {
+ THROW_ERR_MEMORY_ALLOCATION_FAILED(isolate);
+ return {};
+ }
CHECK(bs);
@@ -412,9 +430,14 @@ MaybeLocal<Object> Copy(Environment* env, const char* data, size_t length) {
}
std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
- isolate, length, BackingStoreInitializationMode::kUninitialized);
-
- CHECK(bs);
+ isolate,
+ length,
+ BackingStoreInitializationMode::kUninitialized,
+ BackingStoreOnFailureMode::kReturnNull);
+ if (!bs) [[unlikely]] {
+ THROW_ERR_MEMORY_ALLOCATION_FAILED(isolate);
+ return {};
+ }
if (length > 0) memcpy(bs->Data(), data, length);
@@ -1449,8 +1472,8 @@ void CreateUnsafeArrayBuffer(const FunctionCallbackInfo<Value>& args) {
BackingStoreInitializationMode::kUninitialized,
v8::BackingStoreOnFailureMode::kReturnNull);
- if (!store) {
- env->ThrowRangeError("Array buffer allocation failed");
+ if (!store) [[unlikely]] {
+ THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
return;
}
diff --git a/src/node_errors.h b/src/node_errors.h
index 191374210aae2b..406ad251bcf4bb 100644
--- a/src/node_errors.h
+++ b/src/node_errors.h
@@ -106,7 +106,7 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details);
V(ERR_INVALID_URL_PATTERN, TypeError) \
V(ERR_INVALID_URL_SCHEME, TypeError) \
V(ERR_LOAD_SQLITE_EXTENSION, Error) \
- V(ERR_MEMORY_ALLOCATION_FAILED, Error) \
+ V(ERR_MEMORY_ALLOCATION_FAILED, RangeError) \
V(ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE, Error) \
V(ERR_MISSING_ARGS, TypeError) \
V(ERR_MISSING_PASSPHRASE, TypeError) \