Skip to content

Commit 04bfcd6

Browse files
committed
src: support V8 sandbox memory cage in allocators
When V8_ENABLE_SANDBOX is enabled, all ArrayBuffer backing stores must be allocated within the V8 memory cage — external pointers cannot be directly wrapped and must be copied into V8-managed memory instead. This commit refactors allocators in node_buffer.cc, node_serdes.cc, and node_trace_events.cc to route allocations through ArrayBuffer::Allocator::NewDefaultAllocator() when the sandbox is enabled, ensuring memory lands inside the cage. In node_serdes.cc, ValueSerializer::Delegate is also extended with ReallocateBufferMemory/FreeBufferMemory overrides so the serializer's internal buffer is cage-allocated from the start.
1 parent 488a854 commit 04bfcd6

File tree

4 files changed

+103
-9
lines changed

4 files changed

+103
-9
lines changed

src/api/environment.cc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,12 +305,16 @@ void SetIsolateUpForNode(v8::Isolate* isolate) {
305305

306306
//
307307
IsolateGroup GetOrCreateIsolateGroup() {
308-
// When pointer compression is disabled, we cannot create new groups,
309-
// in which case we'll always return the default.
308+
#ifndef V8_ENABLE_SANDBOX
309+
// When the V8 sandbox is enabled, all isolates must share the same sandbox
310+
// so that ArrayBuffer backing stores allocated via NewDefaultAllocator()
311+
// (which uses the default IsolateGroup's sandbox) are valid for all
312+
// isolates. Creating new groups would give each group its own sandbox,
313+
// causing a mismatch with the allocator.
310314
if (IsolateGroup::CanCreateNewGroups()) {
311315
return IsolateGroup::Create();
312316
}
313-
317+
#endif
314318
return IsolateGroup::GetDefault();
315319
}
316320

src/node_buffer.cc

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ MaybeLocal<Object> New(Environment* env,
529529
}
530530
}
531531

532-
#if defined(V8_ENABLE_SANDBOX)
532+
#ifdef V8_ENABLE_SANDBOX
533533
// When v8 sandbox is enabled, external backing stores are not supported
534534
// since all arraybuffer allocations are expected to be done by the isolate.
535535
// Since this violates the contract of this function, let's free the data and
@@ -1452,7 +1452,7 @@ inline size_t CheckNumberToSize(Local<Value> number) {
14521452
CHECK(value >= 0 && value < maxSize);
14531453
size_t size = static_cast<size_t>(value);
14541454
#ifdef V8_ENABLE_SANDBOX
1455-
CHECK_LE(size, kMaxSafeBufferSizeForSandbox);
1455+
CHECK_LE(size, v8::internal::kMaxSafeBufferSizeForSandbox);
14561456
#endif
14571457
return size;
14581458
}
@@ -1475,6 +1475,24 @@ void CreateUnsafeArrayBuffer(const FunctionCallbackInfo<Value>& args) {
14751475
env->isolate_data()->is_building_snapshot()) {
14761476
buf = ArrayBuffer::New(isolate, size);
14771477
} else {
1478+
#ifdef V8_ENABLE_SANDBOX
1479+
std::unique_ptr<ArrayBuffer::Allocator> allocator(
1480+
ArrayBuffer::Allocator::NewDefaultAllocator());
1481+
void* data = allocator->AllocateUninitialized(size);
1482+
if (!data) [[unlikely]] {
1483+
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
1484+
return;
1485+
}
1486+
std::unique_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(
1487+
data,
1488+
size,
1489+
[](void* data, size_t length, void*) {
1490+
std::unique_ptr<ArrayBuffer::Allocator> allocator(
1491+
ArrayBuffer::Allocator::NewDefaultAllocator());
1492+
allocator->Free(data, length);
1493+
},
1494+
nullptr);
1495+
#else
14781496
std::unique_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(
14791497
isolate,
14801498
size,
@@ -1485,6 +1503,7 @@ void CreateUnsafeArrayBuffer(const FunctionCallbackInfo<Value>& args) {
14851503
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
14861504
return;
14871505
}
1506+
#endif
14881507

14891508
buf = ArrayBuffer::New(isolate, std::move(store));
14901509
}

src/node_serdes.cc

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,26 @@ using v8::ValueSerializer;
2929

3030
namespace serdes {
3131

32+
v8::ArrayBuffer::Allocator* GetAllocator() {
33+
static v8::ArrayBuffer::Allocator* allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
34+
return allocator;
35+
}
36+
37+
void* Reallocate(void* data, size_t old_length,
38+
size_t new_length) {
39+
if (old_length == new_length) return data;
40+
uint8_t* new_data =
41+
reinterpret_cast<uint8_t*>(GetAllocator()->AllocateUninitialized(new_length));
42+
if (new_data == nullptr) return nullptr;
43+
size_t bytes_to_copy = std::min(old_length, new_length);
44+
memcpy(new_data, data, bytes_to_copy);
45+
if (new_length > bytes_to_copy) {
46+
memset(new_data + bytes_to_copy, 0, new_length - bytes_to_copy);
47+
}
48+
GetAllocator()->Free(data, old_length);
49+
return new_data;
50+
}
51+
3252
class SerializerContext : public BaseObject,
3353
public ValueSerializer::Delegate {
3454
public:
@@ -37,10 +57,15 @@ class SerializerContext : public BaseObject,
3757

3858
~SerializerContext() override = default;
3959

60+
// v8::ValueSerializer::Delegate
4061
void ThrowDataCloneError(Local<String> message) override;
4162
Maybe<bool> WriteHostObject(Isolate* isolate, Local<Object> object) override;
4263
Maybe<uint32_t> GetSharedArrayBufferId(
4364
Isolate* isolate, Local<SharedArrayBuffer> shared_array_buffer) override;
65+
void* ReallocateBufferMemory(void* old_buffer,
66+
size_t old_length,
67+
size_t* new_length) override;
68+
void FreeBufferMemory(void* buffer) override;
4469

4570
static void SetTreatArrayBufferViewsAsHostObjects(
4671
const FunctionCallbackInfo<Value>& args);
@@ -61,6 +86,7 @@ class SerializerContext : public BaseObject,
6186

6287
private:
6388
ValueSerializer serializer_;
89+
size_t last_length_ = 0;
6490
};
6591

6692
class DeserializerContext : public BaseObject,
@@ -145,6 +171,24 @@ Maybe<uint32_t> SerializerContext::GetSharedArrayBufferId(
145171
return id->Uint32Value(env()->context());
146172
}
147173

174+
void* SerializerContext::ReallocateBufferMemory(void* old_buffer,
175+
size_t requested_size,
176+
size_t* new_length) {
177+
*new_length = std::max(static_cast<size_t>(4096), requested_size);
178+
if (old_buffer) {
179+
void* ret = Reallocate(old_buffer, last_length_, *new_length);
180+
last_length_ = *new_length;
181+
return ret;
182+
} else {
183+
last_length_ = *new_length;
184+
return GetAllocator()->Allocate(*new_length);
185+
}
186+
}
187+
188+
void SerializerContext::FreeBufferMemory(void* buffer) {
189+
GetAllocator()->Free(buffer, last_length_);
190+
}
191+
148192
Maybe<bool> SerializerContext::WriteHostObject(Isolate* isolate,
149193
Local<Object> input) {
150194
Local<Value> args[1] = { input };
@@ -208,14 +252,25 @@ void SerializerContext::ReleaseBuffer(const FunctionCallbackInfo<Value>& args) {
208252
SerializerContext* ctx;
209253
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
210254

211-
// Note: Both ValueSerializer and this Buffer::New() variant use malloc()
212-
// as the underlying allocator.
213255
std::pair<uint8_t*, size_t> ret = ctx->serializer_.Release();
256+
#ifdef V8_ENABLE_SANDBOX
214257
Local<Object> buf;
215258
if (Buffer::New(ctx->env(), reinterpret_cast<char*>(ret.first), ret.second)
216259
.ToLocal(&buf)) {
217260
args.GetReturnValue().Set(buf);
218261
}
262+
#else
263+
std::unique_ptr<v8::BackingStore> bs =
264+
v8::ArrayBuffer::NewBackingStore(reinterpret_cast<char*>(ret.first), ret.second,
265+
[](void* data, size_t length, void* deleter_data) {
266+
if (data) GetAllocator()->Free(reinterpret_cast<char*>(data), length);
267+
}, nullptr);
268+
Local<ArrayBuffer> ab = v8::ArrayBuffer::New(ctx->env()->isolate(), std::move(bs));
269+
270+
auto buf = Buffer::New(ctx->env(), ab, 0, ret.second);
271+
if (!buf.IsEmpty())
272+
args.GetReturnValue().Set(buf.ToLocalChecked());
273+
#endif
219274
}
220275

221276
void SerializerContext::TransferArrayBuffer(

src/node_trace_events.cc

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,28 @@ static void GetCategoryEnabledBuffer(const FunctionCallbackInfo<Value>& args) {
129129
const uint8_t* enabled_pointer =
130130
TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(category_name.out());
131131
uint8_t* enabled_pointer_cast = const_cast<uint8_t*>(enabled_pointer);
132-
132+
uint8_t size = sizeof(*enabled_pointer_cast);
133+
134+
#ifdef V8_ENABLE_SANDBOX
135+
std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
136+
void* v8_data = allocator->Allocate(size);
137+
CHECK(v8_data);
138+
memcpy(v8_data, enabled_pointer_cast, size);
139+
std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
140+
v8_data,
141+
size,
142+
[](void* data, size_t length, void*) {
143+
std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
144+
allocator->Free(data, length);
145+
}, nullptr);
146+
#else
133147
std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
134148
enabled_pointer_cast,
135-
sizeof(*enabled_pointer_cast),
149+
size,
136150
[](void*, size_t, void*) {},
137151
nullptr);
152+
#endif
153+
138154
auto ab = ArrayBuffer::New(isolate, std::move(bs));
139155
v8::Local<Uint8Array> u8 = v8::Uint8Array::New(ab, 0, 1);
140156

0 commit comments

Comments
 (0)