-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
src: support V8 sandbox memory cage in allocators #62237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -529,7 +529,7 @@ MaybeLocal<Object> New(Environment* env, | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| #if defined(V8_ENABLE_SANDBOX) | ||||||
| #ifdef V8_ENABLE_SANDBOX | ||||||
| // When v8 sandbox is enabled, external backing stores are not supported | ||||||
| // since all arraybuffer allocations are expected to be done by the isolate. | ||||||
| // Since this violates the contract of this function, let's free the data and | ||||||
|
|
@@ -1453,7 +1453,7 @@ inline size_t CheckNumberToSize(Local<Value> number) { | |||||
| CHECK(value >= 0 && value < maxSize); | ||||||
| size_t size = static_cast<size_t>(value); | ||||||
| #ifdef V8_ENABLE_SANDBOX | ||||||
| CHECK_LE(size, kMaxSafeBufferSizeForSandbox); | ||||||
| CHECK_LE(size, v8::internal::kMaxSafeBufferSizeForSandbox); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
We shouldn't access |
||||||
| #endif | ||||||
| return size; | ||||||
| } | ||||||
|
|
@@ -1476,6 +1476,24 @@ void CreateUnsafeArrayBuffer(const FunctionCallbackInfo<Value>& args) { | |||||
| env->isolate_data()->is_building_snapshot()) { | ||||||
| buf = ArrayBuffer::New(isolate, size); | ||||||
| } else { | ||||||
| #ifdef V8_ENABLE_SANDBOX | ||||||
| std::unique_ptr<ArrayBuffer::Allocator> allocator( | ||||||
| ArrayBuffer::Allocator::NewDefaultAllocator()); | ||||||
| void* data = allocator->AllocateUninitialized(size); | ||||||
| if (!data) [[unlikely]] { | ||||||
| THROW_ERR_MEMORY_ALLOCATION_FAILED(env); | ||||||
| return; | ||||||
| } | ||||||
| std::unique_ptr<BackingStore> store = ArrayBuffer::NewBackingStore( | ||||||
| data, | ||||||
| size, | ||||||
| [](void* data, size_t length, void*) { | ||||||
| std::unique_ptr<ArrayBuffer::Allocator> allocator( | ||||||
| ArrayBuffer::Allocator::NewDefaultAllocator()); | ||||||
| allocator->Free(data, length); | ||||||
| }, | ||||||
| nullptr); | ||||||
| #else | ||||||
| std::unique_ptr<BackingStore> store = ArrayBuffer::NewBackingStore( | ||||||
| isolate, | ||||||
| size, | ||||||
|
|
@@ -1486,6 +1504,7 @@ void CreateUnsafeArrayBuffer(const FunctionCallbackInfo<Value>& args) { | |||||
| THROW_ERR_MEMORY_ALLOCATION_FAILED(env); | ||||||
| return; | ||||||
| } | ||||||
| #endif | ||||||
|
|
||||||
| buf = ArrayBuffer::New(isolate, std::move(store)); | ||||||
| } | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,26 @@ using v8::ValueSerializer; | |
|
|
||
| namespace serdes { | ||
|
|
||
| v8::ArrayBuffer::Allocator* GetAllocator() { | ||
| static v8::ArrayBuffer::Allocator* allocator = | ||
| v8::ArrayBuffer::Allocator::NewDefaultAllocator(); | ||
| return allocator; | ||
| } | ||
|
|
||
| void* Reallocate(void* data, size_t old_length, size_t new_length) { | ||
| if (old_length == new_length) return data; | ||
| uint8_t* new_data = reinterpret_cast<uint8_t*>( | ||
| GetAllocator()->AllocateUninitialized(new_length)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This leaks the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, nvm, I saw that the allocator is defined as |
||
| if (new_data == nullptr) return nullptr; | ||
| size_t bytes_to_copy = std::min(old_length, new_length); | ||
| memcpy(new_data, data, bytes_to_copy); | ||
| if (new_length > bytes_to_copy) { | ||
| memset(new_data + bytes_to_copy, 0, new_length - bytes_to_copy); | ||
| } | ||
| GetAllocator()->Free(data, old_length); | ||
| return new_data; | ||
| } | ||
|
|
||
| class SerializerContext : public BaseObject, | ||
| public ValueSerializer::Delegate { | ||
| public: | ||
|
|
@@ -37,10 +57,15 @@ class SerializerContext : public BaseObject, | |
|
|
||
| ~SerializerContext() override = default; | ||
|
|
||
| // v8::ValueSerializer::Delegate | ||
| void ThrowDataCloneError(Local<String> message) override; | ||
| Maybe<bool> WriteHostObject(Isolate* isolate, Local<Object> object) override; | ||
| Maybe<uint32_t> GetSharedArrayBufferId( | ||
| Isolate* isolate, Local<SharedArrayBuffer> shared_array_buffer) override; | ||
| void* ReallocateBufferMemory(void* old_buffer, | ||
| size_t old_length, | ||
| size_t* new_length) override; | ||
| void FreeBufferMemory(void* buffer) override; | ||
|
|
||
| static void SetTreatArrayBufferViewsAsHostObjects( | ||
| const FunctionCallbackInfo<Value>& args); | ||
|
|
@@ -61,6 +86,7 @@ class SerializerContext : public BaseObject, | |
|
|
||
| private: | ||
| ValueSerializer serializer_; | ||
| size_t last_length_ = 0; | ||
| }; | ||
|
|
||
| class DeserializerContext : public BaseObject, | ||
|
|
@@ -145,6 +171,24 @@ Maybe<uint32_t> SerializerContext::GetSharedArrayBufferId( | |
| return id->Uint32Value(env()->context()); | ||
| } | ||
|
|
||
| void* SerializerContext::ReallocateBufferMemory(void* old_buffer, | ||
| size_t requested_size, | ||
| size_t* new_length) { | ||
| *new_length = std::max(static_cast<size_t>(4096), requested_size); | ||
| if (old_buffer) { | ||
| void* ret = Reallocate(old_buffer, last_length_, *new_length); | ||
| last_length_ = *new_length; | ||
| return ret; | ||
| } else { | ||
| last_length_ = *new_length; | ||
| return GetAllocator()->Allocate(*new_length); | ||
| } | ||
| } | ||
|
|
||
| void SerializerContext::FreeBufferMemory(void* buffer) { | ||
| GetAllocator()->Free(buffer, last_length_); | ||
| } | ||
|
|
||
| Maybe<bool> SerializerContext::WriteHostObject(Isolate* isolate, | ||
| Local<Object> input) { | ||
| Local<Value> args[1] = { input }; | ||
|
|
@@ -208,14 +252,27 @@ void SerializerContext::ReleaseBuffer(const FunctionCallbackInfo<Value>& args) { | |
| SerializerContext* ctx; | ||
| ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This()); | ||
|
|
||
| // Note: Both ValueSerializer and this Buffer::New() variant use malloc() | ||
| // as the underlying allocator. | ||
| std::pair<uint8_t*, size_t> ret = ctx->serializer_.Release(); | ||
| #ifdef V8_ENABLE_SANDBOX | ||
| Local<Object> buf; | ||
| if (Buffer::New(ctx->env(), reinterpret_cast<char*>(ret.first), ret.second) | ||
| .ToLocal(&buf)) { | ||
| args.GetReturnValue().Set(buf); | ||
| } | ||
| #else | ||
| std::unique_ptr<v8::BackingStore> bs = v8::ArrayBuffer::NewBackingStore( | ||
| reinterpret_cast<char*>(ret.first), | ||
| ret.second, | ||
| [](void* data, size_t length, void* deleter_data) { | ||
| if (data) GetAllocator()->Free(reinterpret_cast<char*>(data), length); | ||
| }, | ||
| nullptr); | ||
| Local<ArrayBuffer> ab = | ||
| v8::ArrayBuffer::New(ctx->env()->isolate(), std::move(bs)); | ||
|
|
||
| auto buf = Buffer::New(ctx->env(), ab, 0, ret.second); | ||
| if (!buf.IsEmpty()) args.GetReturnValue().Set(buf.ToLocalChecked()); | ||
| #endif | ||
| } | ||
|
|
||
| void SerializerContext::TransferArrayBuffer( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -129,12 +129,28 @@ static void GetCategoryEnabledBuffer(const FunctionCallbackInfo<Value>& args) { | |
| const uint8_t* enabled_pointer = | ||
| TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(category_name.out()); | ||
| uint8_t* enabled_pointer_cast = const_cast<uint8_t*>(enabled_pointer); | ||
|
|
||
| uint8_t size = sizeof(*enabled_pointer_cast); | ||
|
|
||
| #ifdef V8_ENABLE_SANDBOX | ||
| std::unique_ptr<ArrayBuffer::Allocator> allocator( | ||
| ArrayBuffer::Allocator::NewDefaultAllocator()); | ||
| void* v8_data = allocator->Allocate(size); | ||
| CHECK(v8_data); | ||
| memcpy(v8_data, enabled_pointer_cast, size); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This breaks the semantic of |
||
| std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore( | ||
| enabled_pointer_cast, | ||
| sizeof(*enabled_pointer_cast), | ||
| [](void*, size_t, void*) {}, | ||
| v8_data, | ||
| size, | ||
| [](void* data, size_t length, void*) { | ||
| std::unique_ptr<ArrayBuffer::Allocator> allocator( | ||
| ArrayBuffer::Allocator::NewDefaultAllocator()); | ||
| allocator->Free(data, length); | ||
| }, | ||
| nullptr); | ||
| #else | ||
| std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore( | ||
| enabled_pointer_cast, size, [](void*, size_t, void*) {}, nullptr); | ||
| #endif | ||
|
|
||
| auto ab = ArrayBuffer::New(isolate, std::move(bs)); | ||
| v8::Local<Uint8Array> u8 = v8::Uint8Array::New(ab, 0, 1); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this function even return
trueifV8_ENABLE_SANDBOXis set?