Skip to content

Commit 452ab00

Browse files
committed
src: refactor sandbox code into helper
1 parent 78e9555 commit 452ab00

File tree

12 files changed

+240
-126
lines changed

12 files changed

+240
-126
lines changed

src/api/environment.cc

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -305,11 +305,15 @@ void SetIsolateUpForNode(v8::Isolate* isolate) {
305305

306306
IsolateGroup GetOrCreateIsolateGroup() {
307307
#ifndef V8_ENABLE_SANDBOX
308-
// When the V8 sandbox is enabled, all isolates must share the same sandbox
309-
// so that ArrayBuffer backing stores allocated via NewDefaultAllocator()
310-
// (which uses the default IsolateGroup's sandbox) are valid for all
311-
// isolates. Creating new groups would give each group its own sandbox,
312-
// causing a mismatch with the allocator.
308+
// IsolateGroup::CanCreateNewGroups() only reflects whether multiple pointer
309+
// compression cages are supported at build time; it has no awareness of the
310+
// V8 sandbox (they are orthogonal concepts). However, when the sandbox is
311+
// enabled the cage-allocation helpers (node_v8_sandbox.h) route through the
312+
// default allocator, which allocates inside the *default* group's sandbox.
313+
// Creating a new group would give it its own sandbox, so ArrayBuffers
314+
// allocated by the default allocator would be invalid for isolates in that
315+
// group. We therefore always fall through to GetDefault() when the sandbox
316+
// is enabled.
313317
if (IsolateGroup::CanCreateNewGroups()) {
314318
return IsolateGroup::Create();
315319
}

src/crypto/crypto_dh.cc

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "memory_tracker-inl.h"
88
#include "ncrypto.h"
99
#include "node_errors.h"
10+
#include "node_v8_sandbox.h"
1011
#ifndef OPENSSL_IS_BORINGSSL
1112
#include "openssl/bnerr.h"
1213
#endif
@@ -61,18 +62,11 @@ MaybeLocal<Value> DataPointerToBuffer(Environment* env, DataPointer&& data) {
6162
bool secure;
6263
};
6364
#ifdef V8_ENABLE_SANDBOX
64-
auto backing = ArrayBuffer::NewBackingStore(
65-
env->isolate(),
66-
data.size(),
67-
BackingStoreInitializationMode::kUninitialized,
68-
BackingStoreOnFailureMode::kReturnNull);
65+
auto backing = CopyCageBackingStore(data.get(), data.size());
6966
if (!backing) {
7067
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
7168
return MaybeLocal<Value>();
7269
}
73-
if (data.size() > 0) {
74-
memcpy(backing->Data(), data.get(), data.size());
75-
}
7670
#else
7771
auto backing = ArrayBuffer::NewBackingStore(
7872
data.get(),

src/crypto/crypto_util.cc

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "ncrypto.h"
88
#include "node_buffer.h"
99
#include "node_options-inl.h"
10+
#include "node_v8_sandbox.h"
1011
#include "string_bytes.h"
1112
#include "threadpoolwork-inl.h"
1213
#include "util-inl.h"
@@ -364,22 +365,12 @@ std::unique_ptr<BackingStore> ByteSource::ReleaseToBackingStore(
364365
// only if size_ is zero.
365366
CHECK_IMPLIES(size_ > 0, allocated_data_ != nullptr);
366367
#ifdef V8_ENABLE_SANDBOX
367-
// If the v8 sandbox is enabled, then all array buffers must be allocated
368-
// via the isolate. External buffers are not allowed. So, instead of wrapping
369-
// the allocated data we'll copy it instead.
370-
371-
// TODO(@jasnell): It would be nice to use an abstracted utility to do this
372-
// branch instead of duplicating the V8_ENABLE_SANDBOX check each time.
373-
std::unique_ptr<BackingStore> ptr = ArrayBuffer::NewBackingStore(
374-
env->isolate(),
375-
size(),
376-
BackingStoreInitializationMode::kUninitialized,
377-
BackingStoreOnFailureMode::kReturnNull);
368+
std::unique_ptr<BackingStore> ptr = CopyCageBackingStore(
369+
allocated_data_, size());
378370
if (!ptr) {
379371
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
380372
return nullptr;
381373
}
382-
memcpy(ptr->Data(), allocated_data_, size());
383374
OPENSSL_clear_free(allocated_data_, size_);
384375
#else
385376
std::unique_ptr<BackingStore> ptr = ArrayBuffer::NewBackingStore(
@@ -690,20 +681,18 @@ namespace {
690681
// using OPENSSL_malloc. However, if the secure heap is
691682
// initialized, SecureBuffer will automatically use it.
692683
void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
693-
Environment* env = Environment::GetCurrent(args);
694-
#ifdef V8_ENABLE_SANDBOX
695-
// The v8 sandbox is enabled, so we cannot use the secure heap because
696-
// the sandbox requires that all array buffers be allocated via the isolate.
697-
// That is fundamentally incompatible with the secure heap which allocates
698-
// in openssl's secure heap area. Instead we'll just throw an error here.
699-
//
700-
// That said, we really shouldn't get here in the first place since the
701-
// option to enable the secure heap is only available when the sandbox
702-
// is disabled.
703-
UNREACHABLE();
704-
#else
705684
CHECK(args[0]->IsUint32());
706685
uint32_t len = args[0].As<Uint32>()->Value();
686+
#ifdef V8_ENABLE_SANDBOX
687+
// The V8 sandbox requires all array buffers to be allocated inside the cage,
688+
// which is incompatible with OpenSSL's secure heap. Fall back to a regular
689+
// V8-managed allocation. The --secure-heap option is disabled at the CLI
690+
// level when the sandbox is enabled, but this function can still be reached
691+
// via internal callers like randomUUID.
692+
Local<ArrayBuffer> buffer = ArrayBuffer::New(args.GetIsolate(), len);
693+
args.GetReturnValue().Set(Uint8Array::New(buffer, 0, len));
694+
#else
695+
Environment* env = Environment::GetCurrent(args);
707696

708697
auto data = DataPointer::SecureAlloc(len);
709698
CHECK(data.isSecure());

src/crypto/crypto_x509.cc

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "memory_tracker-inl.h"
88
#include "ncrypto.h"
99
#include "node_errors.h"
10+
#include "node_v8_sandbox.h"
1011
#include "util-inl.h"
1112
#include "v8.h"
1213

@@ -141,19 +142,11 @@ MaybeLocal<Value> ToBuffer(Environment* env, BIOPointer* bio) {
141142
if (!mem) [[unlikely]]
142143
return {};
143144
#ifdef V8_ENABLE_SANDBOX
144-
// If the v8 sandbox is enabled, then all array buffers must be allocated
145-
// via the isolate. External buffers are not allowed. So, instead of wrapping
146-
// the BIOPointer we'll copy it instead.
147-
auto backing = ArrayBuffer::NewBackingStore(
148-
env->isolate(),
149-
mem->length,
150-
BackingStoreInitializationMode::kUninitialized,
151-
BackingStoreOnFailureMode::kReturnNull);
145+
auto backing = CopyCageBackingStore(mem->data, mem->length);
152146
if (!backing) {
153147
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
154148
return MaybeLocal<Value>();
155149
}
156-
memcpy(backing->Data(), mem->data, mem->length);
157150
#else
158151
auto backing = ArrayBuffer::NewBackingStore(
159152
mem->data,

src/env.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,23 @@ void IsolateData::MemoryInfo(MemoryTracker* tracker) const {
642642
// TODO(joyeecheung): implement MemoryRetainer in the option classes.
643643
}
644644

645+
void Environment::AddTraceCategoryMapping(const uint8_t* source,
646+
uint8_t* dest) {
647+
trace_category_mappings_.push_back({source, dest});
648+
}
649+
650+
void Environment::SyncTraceCategoryBuffers() {
651+
for (const auto& mapping : trace_category_mappings_) {
652+
*mapping.dest = *mapping.source;
653+
}
654+
}
655+
645656
void TrackingTraceStateObserver::UpdateTraceCategoryState() {
657+
// Sync sandbox-copied trace category buffers before anything else.
658+
// This must happen regardless of thread/JS-callability checks below,
659+
// because the underlying enabled_pointer values have already changed.
660+
env_->SyncTraceCategoryBuffers();
661+
646662
if (!env_->owns_process_state() || !env_->can_call_into_js()) {
647663
// Ideally, we’d have a consistent story that treats all threads/Environment
648664
// instances equally here. However, tracing is essentially global, and this

src/env.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,15 @@ class TickInfo : public MemoryRetainer {
472472
AliasedUint8Array fields_;
473473
};
474474

475+
// Mapping between a trace category enabled pointer (owned by the trace
476+
// infrastructure, outside the V8 sandbox) and a cage-allocated copy that
477+
// backs a JS Uint8Array. Used to keep the two in sync when V8_ENABLE_SANDBOX
478+
// prevents wrapping the external pointer directly.
479+
struct TraceCategoryMapping {
480+
const uint8_t* source; // Original enabled_pointer (external, read-only).
481+
uint8_t* dest; // Cage-allocated copy backing the JS buffer.
482+
};
483+
475484
class TrackingTraceStateObserver :
476485
public v8::TracingController::TraceStateObserver {
477486
public:
@@ -731,6 +740,9 @@ class Environment final : public MemoryRetainer {
731740
void PrintSyncTrace() const;
732741
inline void set_trace_sync_io(bool value);
733742

743+
void AddTraceCategoryMapping(const uint8_t* source, uint8_t* dest);
744+
void SyncTraceCategoryBuffers();
745+
734746
inline void set_force_context_aware(bool value);
735747
inline bool force_context_aware() const;
736748

@@ -1162,6 +1174,7 @@ class Environment final : public MemoryRetainer {
11621174
int should_not_abort_scope_counter_ = 0;
11631175

11641176
std::unique_ptr<TrackingTraceStateObserver> trace_state_observer_;
1177+
std::vector<TraceCategoryMapping> trace_category_mappings_;
11651178

11661179
AliasedInt32Array stream_base_state_;
11671180

src/node_buffer.cc

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "simdutf.h"
3333
#include "string_bytes.h"
3434

35+
#include "node_v8_sandbox.h"
3536
#include "util-inl.h"
3637
#include "v8-fast-api-calls.h"
3738
#include "v8.h"
@@ -123,6 +124,18 @@ Local<ArrayBuffer> CallbackInfo::CreateTrackedArrayBuffer(
123124
CHECK_NOT_NULL(callback);
124125
CHECK_IMPLIES(data == nullptr, length == 0);
125126

127+
#ifdef V8_ENABLE_SANDBOX
128+
// When the V8 sandbox is enabled, external backing stores are not supported.
129+
// Copy the data into the cage and immediately free the original via callback.
130+
void* cage_data = SandboxAllocate(length, /* zero_fill */ false);
131+
CHECK_NOT_NULL(cage_data);
132+
if (length > 0) memcpy(cage_data, data, length);
133+
callback(data, hint);
134+
std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
135+
cage_data, length, SandboxBackingStoreDeleter, nullptr);
136+
Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
137+
// No CallbackInfo needed — the original data has already been freed.
138+
#else
126139
CallbackInfo* self = new CallbackInfo(env, callback, data, hint);
127140
std::unique_ptr<BackingStore> bs =
128141
ArrayBuffer::NewBackingStore(data, length, [](void*, size_t, void* arg) {
@@ -140,6 +153,7 @@ Local<ArrayBuffer> CallbackInfo::CreateTrackedArrayBuffer(
140153
self->persistent_.Reset(env->isolate(), ab);
141154
self->persistent_.SetWeak();
142155
}
156+
#endif // V8_ENABLE_SANDBOX
143157

144158
return ab;
145159
}
@@ -1452,9 +1466,7 @@ inline size_t CheckNumberToSize(Local<Value> number) {
14521466
double maxSize = static_cast<double>(std::numeric_limits<size_t>::max());
14531467
CHECK(value >= 0 && value < maxSize);
14541468
size_t size = static_cast<size_t>(value);
1455-
#ifdef V8_ENABLE_SANDBOX
1456-
CHECK_LE(size, v8::internal::kMaxSafeBufferSizeForSandbox);
1457-
#endif
1469+
CHECK_LE(size, v8::ArrayBuffer::kMaxByteLength);
14581470
return size;
14591471
}
14601472

@@ -1476,35 +1488,13 @@ void CreateUnsafeArrayBuffer(const FunctionCallbackInfo<Value>& args) {
14761488
env->isolate_data()->is_building_snapshot()) {
14771489
buf = ArrayBuffer::New(isolate, size);
14781490
} else {
1479-
#ifdef V8_ENABLE_SANDBOX
1480-
std::unique_ptr<ArrayBuffer::Allocator> allocator(
1481-
ArrayBuffer::Allocator::NewDefaultAllocator());
1482-
void* data = allocator->AllocateUninitialized(size);
1491+
void* data = SandboxAllocate(size, /* zero_fill */ false);
14831492
if (!data) [[unlikely]] {
14841493
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
14851494
return;
14861495
}
14871496
std::unique_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(
1488-
data,
1489-
size,
1490-
[](void* data, size_t length, void*) {
1491-
std::unique_ptr<ArrayBuffer::Allocator> allocator(
1492-
ArrayBuffer::Allocator::NewDefaultAllocator());
1493-
allocator->Free(data, length);
1494-
},
1495-
nullptr);
1496-
#else
1497-
std::unique_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(
1498-
isolate,
1499-
size,
1500-
BackingStoreInitializationMode::kUninitialized,
1501-
v8::BackingStoreOnFailureMode::kReturnNull);
1502-
1503-
if (!store) [[unlikely]] {
1504-
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
1505-
return;
1506-
}
1507-
#endif
1497+
data, size, SandboxBackingStoreDeleter, nullptr);
15081498

15091499
buf = ArrayBuffer::New(isolate, std::move(store));
15101500
}

src/node_i18n.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,12 @@ namespace {
105105
template <typename T>
106106
MaybeLocal<Object> ToBufferEndian(Environment* env, MaybeStackBuffer<T>* buf) {
107107
Local<Object> ret;
108+
#ifdef V8_ENABLE_SANDBOX
109+
if (!Buffer::Copy(env, reinterpret_cast<char*>(buf->out()),
110+
buf->length() * sizeof(T)).ToLocal(&ret)) {
111+
#else
108112
if (!Buffer::New(env, buf).ToLocal(&ret)) {
113+
#endif
109114
return {};
110115
}
111116

@@ -182,7 +187,12 @@ MaybeLocal<Object> TranscodeLatin1ToUcs2(Environment* env,
182187
return {};
183188
}
184189

190+
#ifdef V8_ENABLE_SANDBOX
191+
return Buffer::Copy(env, reinterpret_cast<char*>(destbuf.out()),
192+
destbuf.length() * sizeof(char16_t));
193+
#else
185194
return Buffer::New(env, &destbuf);
195+
#endif
186196
}
187197

188198
MaybeLocal<Object> TranscodeFromUcs2(Environment* env,
@@ -227,7 +237,12 @@ MaybeLocal<Object> TranscodeUcs2FromUtf8(Environment* env,
227237
return {};
228238
}
229239

240+
#ifdef V8_ENABLE_SANDBOX
241+
return Buffer::Copy(env, reinterpret_cast<char*>(destbuf.out()),
242+
destbuf.length() * sizeof(char16_t));
243+
#else
230244
return Buffer::New(env, &destbuf);
245+
#endif
231246
}
232247

233248
MaybeLocal<Object> TranscodeUtf8FromUcs2(Environment* env,
@@ -251,7 +266,12 @@ MaybeLocal<Object> TranscodeUtf8FromUcs2(Environment* env,
251266
return {};
252267
}
253268

269+
#ifdef V8_ENABLE_SANDBOX
270+
return Buffer::Copy(env, reinterpret_cast<char*>(destbuf.out()),
271+
destbuf.length() * sizeof(char));
272+
#else
254273
return Buffer::New(env, &destbuf);
274+
#endif
255275
}
256276

257277
constexpr const char* EncodingName(const enum encoding encoding) {

src/node_sea.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "node_snapshot_builder.h"
1212
#include "node_union_bytes.h"
1313
#include "node_v8_platform-inl.h"
14+
#include "node_v8_sandbox.h"
1415
#include "simdjson.h"
1516
#include "util-inl.h"
1617

@@ -833,13 +834,19 @@ void GetAsset(const FunctionCallbackInfo<Value>& args) {
833834
if (it == sea_resource.assets.end()) {
834835
return;
835836
}
837+
#ifdef V8_ENABLE_SANDBOX
838+
std::unique_ptr<v8::BackingStore> store = CopyCageBackingStore(
839+
it->second.data(), it->second.size());
840+
CHECK(store);
841+
#else
836842
// We cast away the constness here, the JS land should ensure that
837843
// the data is not mutated.
838844
std::unique_ptr<v8::BackingStore> store = ArrayBuffer::NewBackingStore(
839845
const_cast<char*>(it->second.data()),
840846
it->second.size(),
841847
[](void*, size_t, void*) {},
842848
nullptr);
849+
#endif
843850
Local<ArrayBuffer> ab = ArrayBuffer::New(args.GetIsolate(), std::move(store));
844851
args.GetReturnValue().Set(ab);
845852
}

0 commit comments

Comments
 (0)