Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions common.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,12 @@
'OPENSSL_NO_ASM',
],
}],
# Disable C++ features in BoringSSL headers
['1==1', {
'defines': [
'BORINGSSL_NO_CXX',
],
}],
],
}
}
7 changes: 7 additions & 0 deletions deps/v8/include/v8.h
Original file line number Diff line number Diff line change
Expand Up @@ -5203,6 +5203,13 @@ class V8_EXPORT ArrayBuffer : public Object {
*/
static std::unique_ptr<BackingStore> NewBackingStore(Isolate* isolate,
size_t byte_length);
/**
* Returns a new standalone BackingStore with uninitialized memory and
* return nullptr on failure.
* This variant is for not breaking ABI on Node.js LTS. DO NOT USE.
*/
static std::unique_ptr<BackingStore> NewBackingStoreForNodeLTS(
Isolate* isolate, size_t byte_length);
/**
* Returns a new standalone BackingStore that takes over the ownership of
* the given buffer. The destructor of the BackingStore invokes the given
Expand Down
17 changes: 17 additions & 0 deletions deps/v8/src/api/api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7535,6 +7535,23 @@ std::unique_ptr<v8::BackingStore> v8::ArrayBuffer::NewBackingStore(
static_cast<v8::BackingStore*>(backing_store.release()));
}

std::unique_ptr<v8::BackingStore> v8::ArrayBuffer::NewBackingStoreForNodeLTS(
Isolate* isolate, size_t byte_length) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
LOG_API(i_isolate, ArrayBuffer, NewBackingStore);
CHECK_LE(byte_length, i::JSArrayBuffer::kMaxByteLength);
ENTER_V8_NO_SCRIPT_NO_EXCEPTION(i_isolate);
std::unique_ptr<i::BackingStoreBase> backing_store =
i::BackingStore::Allocate(i_isolate, byte_length,
i::SharedFlag::kNotShared,
i::InitializedFlag::kUninitialized);
if (!backing_store) {
return nullptr;
}
return std::unique_ptr<v8::BackingStore>(
static_cast<v8::BackingStore*>(backing_store.release()));
}

std::unique_ptr<v8::BackingStore> v8::ArrayBuffer::NewBackingStore(
void* data, size_t byte_length, v8::BackingStore::DeleterCallback deleter,
void* deleter_data) {
Expand Down
2 changes: 1 addition & 1 deletion deps/v8/third_party/zlib/zutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */
# endif
#endif

#if defined(MACOS) || defined(TARGET_OS_MAC)
#if (defined(MACOS) || defined(TARGET_OS_MAC)) && !defined(__APPLE__)
# define OS_CODE 7
# ifndef Z_SOLO
# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os
Expand Down
2 changes: 1 addition & 1 deletion deps/zlib/zutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */
# endif
#endif

#if defined(MACOS) || defined(TARGET_OS_MAC)
#if (defined(MACOS) || defined(TARGET_OS_MAC)) && !defined(__APPLE__)
# define OS_CODE 7
# ifndef Z_SOLO
# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os
Expand Down
103 changes: 58 additions & 45 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,64 +320,76 @@ function onnewsession(sessionId, session) {

function onPskServerCallback(identity, maxPskLen) {
const owner = this[owner_symbol];
const ret = owner[kPskCallback](owner, identity);
if (ret == null)
return undefined;

let psk;
if (isArrayBufferView(ret)) {
psk = ret;
} else {
if (typeof ret !== 'object') {
throw new ERR_INVALID_ARG_TYPE(
'ret',
['Object', 'Buffer', 'TypedArray', 'DataView'],
ret
try {
const ret = owner[kPskCallback](owner, identity);
if (ret == null)
return undefined;

let psk;
if (isArrayBufferView(ret)) {
psk = ret;
} else {
if (typeof ret !== 'object') {
throw new ERR_INVALID_ARG_TYPE(
'ret',
['Object', 'Buffer', 'TypedArray', 'DataView'],
ret
);
}
psk = ret.psk;
validateBuffer(psk, 'psk');
}

if (psk.length > maxPskLen) {
throw new ERR_INVALID_ARG_VALUE(
'psk',
psk,
`Pre-shared key exceeds ${maxPskLen} bytes`
);
}
psk = ret.psk;
validateBuffer(psk, 'psk');
}

if (psk.length > maxPskLen) {
throw new ERR_INVALID_ARG_VALUE(
'psk',
psk,
`Pre-shared key exceeds ${maxPskLen} bytes`
);
return psk;
} catch (err) {
owner.destroy(err);
return undefined;
}

return psk;
}

function onPskClientCallback(hint, maxPskLen, maxIdentityLen) {
const owner = this[owner_symbol];
const ret = owner[kPskCallback](hint);
if (ret == null)
return undefined;

if (typeof ret !== 'object')
throw new ERR_INVALID_ARG_TYPE('ret', 'Object', ret);
try {
const ret = owner[kPskCallback](hint);
if (ret == null)
return undefined;

if (typeof ret !== 'object')
throw new ERR_INVALID_ARG_TYPE('ret', 'Object', ret);

validateBuffer(ret.psk, 'psk');
if (ret.psk.length > maxPskLen) {
throw new ERR_INVALID_ARG_VALUE(
'psk',
ret.psk,
`Pre-shared key exceeds ${maxPskLen} bytes`
);
}

validateBuffer(ret.psk, 'psk');
if (ret.psk.length > maxPskLen) {
throw new ERR_INVALID_ARG_VALUE(
'psk',
ret.psk,
`Pre-shared key exceeds ${maxPskLen} bytes`
);
}
validateString(ret.identity, 'identity');
if (Buffer.byteLength(ret.identity) > maxIdentityLen) {
throw new ERR_INVALID_ARG_VALUE(
'identity',
ret.identity,
`PSK identity exceeds ${maxIdentityLen} bytes`
);
}

validateString(ret.identity, 'identity');
if (Buffer.byteLength(ret.identity) > maxIdentityLen) {
throw new ERR_INVALID_ARG_VALUE(
'identity',
ret.identity,
`PSK identity exceeds ${maxIdentityLen} bytes`
);
return { psk: ret.psk, identity: ret.identity };
} catch (err) {
owner.destroy(err);
return undefined;
}

return { psk: ret.psk, identity: ret.identity };
}

function onkeylog(line) {
Expand Down Expand Up @@ -1090,6 +1102,7 @@ function tlsConnectionListener(rawSocket) {
socket[kErrorEmitted] = false;
socket.on('close', onSocketClose);
socket.on('_tlsError', onSocketTLSError);
socket.on('error', onSocketTLSError);
}

// AUTHENTICATION MODES
Expand Down
19 changes: 8 additions & 11 deletions lib/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const {
compare: _compare,
compareOffset,
createFromString,
createUnsafeArrayBuffer,
fill: bindingFill,
indexOfBuffer,
indexOfNumber,
Expand All @@ -64,7 +65,6 @@ const {
swap64: _swap64,
kMaxLength,
kStringMaxLength,
zeroFill: bindingZeroFill
} = internalBinding('buffer');
const {
getOwnNonIndexProperties,
Expand Down Expand Up @@ -140,23 +140,20 @@ const constants = ObjectDefineProperties({}, {
Buffer.poolSize = 8 * 1024;
let poolSize, poolOffset, allocPool;

// A toggle used to access the zero fill setting of the array buffer allocator
// in C++.
// |zeroFill| can be undefined when running inside an isolate where we
// do not own the ArrayBuffer allocator. Zero fill is always on in that case.
const zeroFill = bindingZeroFill || [0];

const encodingsMap = ObjectCreate(null);
for (let i = 0; i < encodings.length; ++i)
encodingsMap[encodings[i]] = i;

// CVE-2025-55131: The previous implementation used a zeroFill toggle that was
// vulnerable to race conditions. Now we use a dedicated C++ function to create
// ArrayBuffers with uninitialized memory, bypassing the toggle mechanism entirely.
function createUnsafeBuffer(size) {
zeroFill[0] = 0;
try {
// Small allocations (<=64 bytes) are heap-allocated and don't benefit from
// skipping zero-fill, so just use the normal path for simplicity.
if (size <= 64) {
return new FastBuffer(size);
} finally {
zeroFill[0] = 1;
}
return new FastBuffer(createUnsafeArrayBuffer(size));
}

function createPool() {
Expand Down
9 changes: 6 additions & 3 deletions src/async_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ struct AsyncWrapObject : public AsyncWrap {
void AsyncWrap::DestroyAsyncIdsCallback(Environment* env) {
Local<Function> fn = env->async_hooks_destroy_function();

TryCatchScope try_catch(env, TryCatchScope::CatchMode::kFatal);
TryCatchScope try_catch(env,
TryCatchScope::CatchMode::kFatalRethrowStackOverflow);

do {
std::vector<double> destroy_async_id_list;
Expand Down Expand Up @@ -134,7 +135,8 @@ void Emit(Environment* env, double async_id, AsyncHooks::Fields type,

HandleScope handle_scope(env->isolate());
Local<Value> async_id_value = Number::New(env->isolate(), async_id);
TryCatchScope try_catch(env, TryCatchScope::CatchMode::kFatal);
TryCatchScope try_catch(env,
TryCatchScope::CatchMode::kFatalRethrowStackOverflow);
USE(fn->Call(env->context(), Undefined(env->isolate()), 1, &async_id_value));
}

Expand Down Expand Up @@ -671,7 +673,8 @@ void AsyncWrap::EmitAsyncInit(Environment* env,
object,
};

TryCatchScope try_catch(env, TryCatchScope::CatchMode::kFatal);
TryCatchScope try_catch(env,
TryCatchScope::CatchMode::kFatalRethrowStackOverflow);
USE(init_fn->Call(env->context(), object, arraysize(argv), argv));
}

Expand Down
72 changes: 48 additions & 24 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@
#include "util-inl.h"
#include "v8.h"

#include <cstring>
#include <climits>
#include <cstring>
#include <limits>

#define THROW_AND_RETURN_UNLESS_BUFFER(env, obj) \
THROW_AND_RETURN_IF_NOT_BUFFER(env, obj, "argument") \
Expand Down Expand Up @@ -1129,13 +1130,59 @@ void SetBufferPrototype(const FunctionCallbackInfo<Value>& args) {
}


// Converts a number parameter to size_t suitable for ArrayBuffer sizes
// Could be larger than uint32_t
// See v8::internal::TryNumberToSize and v8::internal::NumberToSize
inline size_t CheckNumberToSize(Local<Value> number) {
CHECK(number->IsNumber());
double value = number.As<Number>()->Value();
// See v8::internal::TryNumberToSize on this (and on < comparison)
double maxSize = static_cast<double>(std::numeric_limits<size_t>::max());
CHECK(value >= 0 && value < maxSize);
return static_cast<size_t>(value);
}

// Creates an ArrayBuffer with uninitialized memory, bypassing the zero-fill
// toggle mechanism. This is used by Buffer.allocUnsafe and Buffer.allocUnsafeSlow.
// CVE-2025-55131: The zero-fill toggle mechanism was vulnerable to race conditions.
void CreateUnsafeArrayBuffer(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
if (args.Length() != 1) {
return env->ThrowRangeError("Invalid array buffer length");
}

size_t size = CheckNumberToSize(args[0]);

Isolate* isolate = env->isolate();

Local<ArrayBuffer> buf;

NodeArrayBufferAllocator* allocator = env->isolate_data()->node_allocator();
// 0-length, or zero-fill flag is set, or no allocator available
if (size == 0 || per_process::cli_options->zero_fill_all_buffers ||
allocator == nullptr) {
buf = ArrayBuffer::New(isolate, size);
} else {
std::unique_ptr<BackingStore> store =
ArrayBuffer::NewBackingStoreForNodeLTS(isolate, size);
if (!store) {
return env->ThrowRangeError("Array buffer allocation failed");
}
buf = ArrayBuffer::New(isolate, std::move(store));
}

args.GetReturnValue().Set(buf);
}

void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
Environment* env = Environment::GetCurrent(context);

env->SetMethod(target, "setBufferPrototype", SetBufferPrototype);
env->SetMethodNoSideEffect(target, "createUnsafeArrayBuffer",
CreateUnsafeArrayBuffer);
env->SetMethodNoSideEffect(target, "createFromString", CreateFromString);

env->SetMethodNoSideEffect(target, "byteLengthUtf8", ByteLengthUtf8);
Expand Down Expand Up @@ -1179,29 +1226,6 @@ void Initialize(Local<Object> target,
env->SetMethod(target, "utf8Write", StringWrite<UTF8>);

Blob::Initialize(env, target);

// It can be a nullptr when running inside an isolate where we
// do not own the ArrayBuffer allocator.
if (NodeArrayBufferAllocator* allocator =
env->isolate_data()->node_allocator()) {
uint32_t* zero_fill_field = allocator->zero_fill_field();
std::unique_ptr<BackingStore> backing =
ArrayBuffer::NewBackingStore(zero_fill_field,
sizeof(*zero_fill_field),
[](void*, size_t, void*){},
nullptr);
Local<ArrayBuffer> array_buffer =
ArrayBuffer::New(env->isolate(), std::move(backing));
array_buffer->SetPrivate(
env->context(),
env->untransferable_object_private_symbol(),
True(env->isolate())).Check();
CHECK(target
->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "zeroFill"),
Uint32Array::New(array_buffer, 0, 1))
.FromJust());
}
}

} // anonymous namespace
Expand Down
Loading