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
16 changes: 5 additions & 11 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@
action='store_true',
dest='pointer_compression_shared_cage',
default=None,
help='[Experimental] Use V8 pointer compression with shared cage (requires --experimental-enable-pointer-compression)')
help='[Experimental] Use V8 pointer compression with a shared cage and enable the V8 sandbox (requires --experimental-enable-pointer-compression)')

parser.add_argument('--v8-options',
action='store',
Expand Down Expand Up @@ -2201,16 +2201,10 @@ def configure_v8(o, configs):
flavor not in ('aix', 'os400', 'zos') and
o['variables']['target_arch'] in maglev_enabled_architectures)
o['variables']['v8_enable_pointer_compression'] = 1 if options.enable_pointer_compression else 0
# Using the sandbox requires always allocating array buffer backing stores in the sandbox.
# We currently have many backing stores tied to pointers from C++ land that are not
# even necessarily dynamic (e.g. in static storage) for fast communication between JS and C++.
# Until we manage to get rid of all those, v8_enable_sandbox cannot be used.
# Note that enabling pointer compression without enabling sandbox is unsupported by V8,
# so this can be broken at any time.
o['variables']['v8_enable_sandbox'] = 0
# We set v8_enable_pointer_compression_shared_cage to 0 always, even when
# pointer compression is enabled so that we don't accidentally enable shared
# cage mode when pointer compression is on.
# Like V8's own default, the sandbox goes with the shared pointer compression
# cage. Multi-cage builds give every IsolateGroup its own sandbox, which the
# array buffer allocator does not know about yet.
o['variables']['v8_enable_sandbox'] = 1 if options.pointer_compression_shared_cage else 0
o['variables']['v8_enable_pointer_compression_shared_cage'] = 1 if options.pointer_compression_shared_cage else 0
o['variables']['v8_enable_external_code_space'] = 1 if options.enable_pointer_compression else 0
o['variables']['v8_enable_31bit_smis_on_64bit_arch'] = 1 if options.enable_pointer_compression else 0
Expand Down
9 changes: 4 additions & 5 deletions lib/ffi.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,10 @@ function exportString(str, data, len, encoding = 'utf8') {
throw new ERR_OUT_OF_RANGE('len', `>= ${requiredLength}`, len);
}

const targetBuffer = toBuffer(data, len, false);
const dataLength = sourceBuffer.length;

sourceBuffer.copy(targetBuffer, 0, 0, dataLength);
targetBuffer.fill(0, dataLength, dataLength + terminatorSize);
const terminated = Buffer.allocUnsafe(requiredLength);
sourceBuffer.copy(terminated);
terminated.fill(0, sourceBuffer.length);
exportBytes(terminated, data, len);
}

function exportBuffer(source, data, len) {
Expand Down
8 changes: 2 additions & 6 deletions lib/internal/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const {

const { setUnrefTimeout } = require('internal/timers');
const {
getCategoryEnabledBuffer,
categoryEnabledChecker,
trace,
nodeTraceEventCategory,
kAsyncBegin,
Expand Down Expand Up @@ -44,11 +44,7 @@ function getNextTraceEventId() {
return ++traceEventId;
}

const httpEnabled = getCategoryEnabledBuffer('node.http');

function isTraceHTTPEnabled() {
return httpEnabled[0] > 0;
}
const isTraceHTTPEnabled = categoryEnabledChecker('node.http');

const traceEventCategory = nodeTraceEventCategory('node.http');

Expand Down
19 changes: 17 additions & 2 deletions lib/internal/trace_events.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
'use strict';

const { getCategoryEnabledBuffer, trace, usePerfetto } = internalBinding('trace_events');
const {
getCategoryEnabledBuffer,
isTraceCategoryEnabled,
trace,
usePerfetto,
} = internalBinding('trace_events');
const {
CHAR_UPPERCASE_B,
CHAR_LOWERCASE_B,
Expand All @@ -17,6 +22,16 @@ if (usePerfetto) {
nodeTraceEventCategory = (category) => `node,${category}`;
}

// The returned function reads the category's enabled flag directly when the
// binding can expose it to JS, and asks the tracing controller otherwise.
function categoryEnabledChecker(category) {
const buffer = getCategoryEnabledBuffer(category);
if (buffer === undefined) {
return () => isTraceCategoryEnabled(category);
}
return () => buffer[0] > 0;
}

// The async events describe the execution of a single asynchronous operation, and are
// used to measure the time spent in a single asynchronous operation.
// Async events may overlap with each other. Different events do not have
Expand Down Expand Up @@ -44,7 +59,7 @@ const kTraceInstant = CHAR_LOWERCASE_N;

module.exports = {
usePerfetto,
getCategoryEnabledBuffer,
categoryEnabledChecker,
trace,
nodeTraceEventCategory,
kAsyncBegin,
Expand Down
10 changes: 5 additions & 5 deletions lib/internal/util/debuglog.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const {
} = primordials;
const { inspect, format, formatWithOptions } = require('internal/util/inspect');
const {
getCategoryEnabledBuffer,
categoryEnabledChecker,
trace,
nodeTraceEventCategory,
kAsyncBegin,
Expand Down Expand Up @@ -388,14 +388,14 @@ function debugWithTimer(set, cb) {
}

const traceCategory = nodeTraceEventCategory(`node.${StringPrototypeToLowerCase(set)}`);
let traceCategoryBuffer;
let traceCategoryEnabled;
let debugLogCategoryEnabled = false;
let timerFlags = kNone;

function ensureTimerFlagsAreUpdated() {
timerFlags &= ~kSkipTrace;

if (traceCategoryBuffer[0] === 0) {
if (!traceCategoryEnabled()) {
timerFlags |= kSkipTrace;
}
}
Expand Down Expand Up @@ -469,15 +469,15 @@ function debugWithTimer(set, cb) {
}
emitWarningIfNeeded(set);
debugLogCategoryEnabled = testEnabled(set);
traceCategoryBuffer = getCategoryEnabledBuffer(traceCategory);
traceCategoryEnabled = categoryEnabledChecker(traceCategory);

timerFlags = kNone;

if (!debugLogCategoryEnabled) {
timerFlags |= kSkipLog;
}

if (traceCategoryBuffer[0] === 0) {
if (!traceCategoryEnabled()) {
timerFlags |= kSkipTrace;
}

Expand Down
22 changes: 5 additions & 17 deletions src/crypto/crypto_dh.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ using ncrypto::DHPointer;
using ncrypto::EVPKeyCtxPointer;
using ncrypto::EVPKeyPointer;
using v8::ArrayBuffer;
using v8::BackingStoreInitializationMode;
using v8::BackingStoreOnFailureMode;
using v8::ConstructorBehavior;
using v8::Context;
using v8::DontDelete;
Expand Down Expand Up @@ -60,21 +58,8 @@ MaybeLocal<Value> DataPointerToBuffer(Environment* env, DataPointer&& data) {
struct Flag {
bool secure;
};
#ifdef V8_ENABLE_SANDBOX
auto backing = ArrayBuffer::NewBackingStore(
auto backing = AdoptIntoBackingStore(
env->isolate(),
data.size(),
BackingStoreInitializationMode::kUninitialized,
BackingStoreOnFailureMode::kReturnNull);
if (!backing) {
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
return MaybeLocal<Value>();
}
if (data.size() > 0) {
memcpy(backing->Data(), data.get(), data.size());
}
#else
auto backing = ArrayBuffer::NewBackingStore(
data.get(),
data.size(),
[](void* data, size_t len, void* ptr) {
Expand All @@ -83,7 +68,10 @@ MaybeLocal<Value> DataPointerToBuffer(Environment* env, DataPointer&& data) {
},
new Flag{data.isSecure()});
data.release();
#endif // V8_ENABLE_SANDBOX
if (!backing) {
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
return MaybeLocal<Value>();
}

auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>());
Expand Down
49 changes: 13 additions & 36 deletions src/crypto/crypto_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStore;
using v8::BackingStoreInitializationMode;
using v8::BackingStoreOnFailureMode;
using v8::BigInt;
using v8::Context;
using v8::EscapableHandleScope;
Expand Down Expand Up @@ -446,33 +444,18 @@ std::unique_ptr<BackingStore> ByteSource::ReleaseToBackingStore(
// It's ok for allocated_data_ to be nullptr but
// only if size_ is zero.
CHECK_IMPLIES(size_ > 0, allocated_data_ != nullptr);
#ifdef V8_ENABLE_SANDBOX
// If the v8 sandbox is enabled, then all array buffers must be allocated
// via the isolate. External buffers are not allowed. So, instead of wrapping
// the allocated data we'll copy it instead.

// TODO(@jasnell): It would be nice to use an abstracted utility to do this
// branch instead of duplicating the V8_ENABLE_SANDBOX check each time.
std::unique_ptr<BackingStore> ptr = ArrayBuffer::NewBackingStore(
std::unique_ptr<BackingStore> ptr = AdoptIntoBackingStore(
env->isolate(),
allocated_data_,
size(),
BackingStoreInitializationMode::kUninitialized,
BackingStoreOnFailureMode::kReturnNull);
[](void* data, size_t length, void*) {
OPENSSL_clear_free(data, length);
},
nullptr);
if (!ptr) {
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
return nullptr;
}
memcpy(ptr->Data(), allocated_data_, size());
OPENSSL_clear_free(allocated_data_, size_);
#else
std::unique_ptr<BackingStore> ptr = ArrayBuffer::NewBackingStore(
allocated_data_,
size(),
[](void* data, size_t length, void* deleter_data) {
OPENSSL_clear_free(deleter_data, length);
}, allocated_data_);
#endif // V8_ENABLE_SANDBOX
CHECK(ptr);
allocated_data_ = nullptr;
data_ = nullptr;
size_ = 0;
Expand Down Expand Up @@ -837,17 +820,6 @@ namespace {
// initialized, SecureBuffer will automatically use it.
void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
#ifdef V8_ENABLE_SANDBOX
// The v8 sandbox is enabled, so we cannot use the secure heap because
// the sandbox requires that all array buffers be allocated via the isolate.
// That is fundamentally incompatible with the secure heap which allocates
// in openssl's secure heap area. Instead we'll just throw an error here.
//
// That said, we really shouldn't get here in the first place since the
// option to enable the secure heap is only available when the sandbox
// is disabled.
UNREACHABLE();
#else
CHECK(args[0]->IsUint32());
uint32_t len = args[0].As<Uint32>()->Value();

Expand All @@ -857,7 +829,10 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
}
auto released = data.release();

std::shared_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(
// Under V8_ENABLE_SANDBOX this ends up as a plain copy, which is fine:
// --secure-heap is unavailable there, so SecureAlloc() is OPENSSL_malloc().
std::shared_ptr<BackingStore> store = AdoptIntoBackingStore(
env->isolate(),
released.data,
released.len,
[](void* data, size_t len, void* deleter_data) {
Expand All @@ -871,10 +846,12 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
true);
},
nullptr);
if (!store) {
return THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
}

Local<ArrayBuffer> buffer = ArrayBuffer::New(env->isolate(), store);
args.GetReturnValue().Set(Uint8Array::New(buffer, 0, len));
#endif // V8_ENABLE_SANDBOX
}

void SecureHeapUsed(const FunctionCallbackInfo<Value>& args) {
Expand Down
23 changes: 5 additions & 18 deletions src/crypto/crypto_x509.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ using ncrypto::X509View;
using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStoreInitializationMode;
using v8::BackingStoreOnFailureMode;
using v8::Boolean;
using v8::Context;
using v8::Date;
Expand Down Expand Up @@ -140,29 +138,18 @@ MaybeLocal<Value> ToBuffer(Environment* env, BIOPointer* bio) {
BUF_MEM* mem = *bio;
if (!mem) [[unlikely]]
return {};
#ifdef V8_ENABLE_SANDBOX
// If the v8 sandbox is enabled, then all array buffers must be allocated
// via the isolate. External buffers are not allowed. So, instead of wrapping
// the BIOPointer we'll copy it instead.
auto backing = ArrayBuffer::NewBackingStore(
auto backing = AdoptIntoBackingStore(
env->isolate(),
mem->length,
BackingStoreInitializationMode::kUninitialized,
BackingStoreOnFailureMode::kReturnNull);
if (!backing) {
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
return MaybeLocal<Value>();
}
memcpy(backing->Data(), mem->data, mem->length);
#else
auto backing = ArrayBuffer::NewBackingStore(
mem->data,
mem->length,
[](void*, size_t, void* data) {
BIOPointer free_me(static_cast<BIO*>(data));
},
bio->release());
#endif // V8_ENABLE_SANDBOX
if (!backing) {
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
return MaybeLocal<Value>();
}
auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
Local<Value> ret;
if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&ret)) return {};
Expand Down
26 changes: 21 additions & 5 deletions src/ffi/data.cc
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,17 @@ void ToString(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(out);
}

// Foreign memory lies outside the V8 sandbox and cannot back an ArrayBuffer.
static bool ZeroCopyUnavailable(Environment* env) {
#ifdef V8_ENABLE_SANDBOX
THROW_ERR_OPERATION_FAILED(
env, "Zero-copy views are not available when the V8 sandbox is enabled");
return true;
#else
return false;
#endif
}

void ToBuffer(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
Expand Down Expand Up @@ -580,9 +591,12 @@ void ToBuffer(const FunctionCallbackInfo<Value>& args) {
return;
}

bool copy = args.Length() < 3 || args[2]->IsUndefined() ||
args[2]->BooleanValue(isolate);
if (!copy && ZeroCopyUnavailable(env)) return;

Local<Object> buf;
if (args.Length() < 3 || args[2]->IsUndefined() ||
args[2]->BooleanValue(isolate)) {
if (copy) {
if (!Buffer::Copy(env, reinterpret_cast<char*>(ptr), len).ToLocal(&buf)) {
return;
}
Expand Down Expand Up @@ -642,10 +656,12 @@ void ToArrayBuffer(const FunctionCallbackInfo<Value>& args) {
return;
}

Local<ArrayBuffer> ab;
bool copy = args.Length() < 3 || args[2]->IsUndefined() ||
args[2]->BooleanValue(isolate);
if (!copy && ZeroCopyUnavailable(env)) return;

if (args.Length() < 3 || args[2]->IsUndefined() ||
args[2]->BooleanValue(isolate)) {
Local<ArrayBuffer> ab;
if (copy) {
std::unique_ptr<BackingStore> store =
ArrayBuffer::NewBackingStore(isolate, len);
memcpy(store->Data(), reinterpret_cast<void*>(ptr), len);
Expand Down
Loading
Loading