diff --git a/build/args/all.gn b/build/args/all.gn index 9a3fee65b82d2..f1de439d833fc 100644 --- a/build/args/all.gn +++ b/build/args/all.gn @@ -45,7 +45,3 @@ enable_cet_shadow_stack = false # V8 in the browser process. # Ref: https://source.chromium.org/chromium/chromium/src/+/45fba672185aae233e75d6ddc81ea1e0b30db050:v8/BUILD.gn;l=281 is_cfi = false - -# TODO(nornagon): this is disabled until node.js's internals can be made -# compatible with sandboxed pointers. -v8_enable_sandboxed_pointers = false diff --git a/patches/node/.patches b/patches/node/.patches index 873260a1dff32..2066849e25cbb 100644 --- a/patches/node/.patches +++ b/patches/node/.patches @@ -44,5 +44,4 @@ src_update_importmoduledynamically.patch fix_add_v8_enable_reverse_jsargs_defines_in_common_gypi.patch json_parse_errors_made_user-friendly.patch build_define_libcpp_abi_namespace_as_cr_to_align_with_chromium.patch -serdes_allocate_using_the_v8_arraybuffer_allocator.patch -nodearraybufferallocator_uses_v8_arraybuffer_allocator.patch +support_v8_sandboxed_pointers.patch diff --git a/patches/node/fix_crypto_tests_to_run_with_bssl.patch b/patches/node/fix_crypto_tests_to_run_with_bssl.patch index bcfd21a6fad4c..44a303b93a03b 100644 --- a/patches/node/fix_crypto_tests_to_run_with_bssl.patch +++ b/patches/node/fix_crypto_tests_to_run_with_bssl.patch @@ -251,34 +251,6 @@ index 3bbca5b0da395b94c04da7bb7c55b107e41367d8..af62558c4f23aa82804e0077da7b7f3a // -diff --git a/test/parallel/test-crypto-certificate.js b/test/parallel/test-crypto-certificate.js -index 4a5f1f149fe6c739f7f1d2ee17df6e61a942d621..ffd10deddbffceba1f28a37f54608798e87d5a36 100644 ---- a/test/parallel/test-crypto-certificate.js -+++ b/test/parallel/test-crypto-certificate.js -@@ -41,7 +41,10 @@ function copyArrayBuffer(buf) { - - function checkMethods(certificate) { - -+ /* spkacValid has a md5 based signature which is not allowed in boringssl -+ https://boringssl.googlesource.com/boringssl/+/33d7e32ce40c04e8f1b99c05964956fda187819f - assert.strictEqual(certificate.verifySpkac(spkacValid), true); -+ */ - assert.strictEqual(certificate.verifySpkac(spkacFail), false); - - assert.strictEqual( -@@ -56,10 +59,12 @@ function checkMethods(certificate) { - ); - assert.strictEqual(certificate.exportChallenge(spkacFail), ''); - -+ /* spkacValid has a md5 based signature which is not allowed in boringssl - const ab = copyArrayBuffer(spkacValid); - assert.strictEqual(certificate.verifySpkac(ab), true); - assert.strictEqual(certificate.verifySpkac(new Uint8Array(ab)), true); - assert.strictEqual(certificate.verifySpkac(new DataView(ab)), true); -+ */ - } - - { diff --git a/test/parallel/test-crypto-cipher-decipher.js b/test/parallel/test-crypto-cipher-decipher.js index 35514afbea92562a81c163b1e4d918b4ab609f71..13098e1acf12c309f2ed6f6143a2c2eeb8a2763d 100644 --- a/test/parallel/test-crypto-cipher-decipher.js diff --git a/patches/node/nodearraybufferallocator_uses_v8_arraybuffer_allocator.patch b/patches/node/nodearraybufferallocator_uses_v8_arraybuffer_allocator.patch deleted file mode 100644 index 8b7df125a910c..0000000000000 --- a/patches/node/nodearraybufferallocator_uses_v8_arraybuffer_allocator.patch +++ /dev/null @@ -1,190 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jeremy Rose -Date: Tue, 21 Jun 2022 17:01:46 -0700 -Subject: NodeArrayBufferAllocator uses v8::ArrayBuffer::Allocator - -This allows it to work when the v8 sandbox is enabled. - -diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js -index 4c459b58b5a048d9d8a4f15f4011e7cce68089f4..6fb4c8d4567aee5b313ad621ea42699a196f18c7 100644 ---- a/lib/internal/bootstrap/pre_execution.js -+++ b/lib/internal/bootstrap/pre_execution.js -@@ -14,7 +14,6 @@ const { - getOptionValue, - getEmbedderOptions, - } = require('internal/options'); --const { reconnectZeroFillToggle } = require('internal/buffer'); - const { - defineOperation, - emitExperimentalWarning, -@@ -26,10 +25,6 @@ const { ERR_MANIFEST_ASSERT_INTEGRITY } = require('internal/errors').codes; - const assert = require('internal/assert'); - - function prepareMainThreadExecution(expandArgv1 = false) { -- // TODO(joyeecheung): this is also necessary for workers when they deserialize -- // this toggle from the snapshot. -- reconnectZeroFillToggle(); -- - // Patch the process object with legacy properties and normalizations - patchProcessObject(expandArgv1); - setupTraceCategoryState(); -diff --git a/lib/internal/buffer.js b/lib/internal/buffer.js -index bd38cf48a7fc6e8d61d8f11fa15c34aee182cbe3..1aa071cdc071dcdaf5c3b4bed0d3d76e5871731d 100644 ---- a/lib/internal/buffer.js -+++ b/lib/internal/buffer.js -@@ -30,7 +30,7 @@ const { - hexWrite, - ucs2Write, - utf8Write, -- getZeroFillToggle -+ setZeroFillToggle - } = internalBinding('buffer'); - const { - untransferable_object_private_symbol, -@@ -1055,24 +1055,15 @@ function markAsUntransferable(obj) { - // 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. --let zeroFill = getZeroFillToggle(); - function createUnsafeBuffer(size) { -- zeroFill[0] = 0; -+ setZeroFillToggle(false); - try { - return new FastBuffer(size); - } finally { -- zeroFill[0] = 1; -+ setZeroFillToggle(true) - } - } - --// The connection between the JS land zero fill toggle and the --// C++ one in the NodeArrayBufferAllocator gets lost if the toggle --// is deserialized from the snapshot, because V8 owns the underlying --// memory of this toggle. This resets the connection. --function reconnectZeroFillToggle() { -- zeroFill = getZeroFillToggle(); --} -- - module.exports = { - FastBuffer, - addBufferPrototypeMethods, -@@ -1080,5 +1071,4 @@ module.exports = { - createUnsafeBuffer, - readUInt16BE, - readUInt32BE, -- reconnectZeroFillToggle - }; -diff --git a/src/api/environment.cc b/src/api/environment.cc -index 2abf5994405e8da2a04d1b23b75ccd3658398474..024d612a04d83583b397549589d994e32cf0107f 100644 ---- a/src/api/environment.cc -+++ b/src/api/environment.cc -@@ -83,16 +83,16 @@ MaybeLocal PrepareStackTraceCallback(Local context, - void* NodeArrayBufferAllocator::Allocate(size_t size) { - void* ret; - if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers) -- ret = UncheckedCalloc(size); -+ ret = allocator_->Allocate(size); - else -- ret = UncheckedMalloc(size); -+ ret = allocator_->AllocateUninitialized(size); - if (LIKELY(ret != nullptr)) - total_mem_usage_.fetch_add(size, std::memory_order_relaxed); - return ret; - } - - void* NodeArrayBufferAllocator::AllocateUninitialized(size_t size) { -- void* ret = node::UncheckedMalloc(size); -+ void* ret = allocator_->AllocateUninitialized(size); - if (LIKELY(ret != nullptr)) - total_mem_usage_.fetch_add(size, std::memory_order_relaxed); - return ret; -@@ -100,7 +100,7 @@ void* NodeArrayBufferAllocator::AllocateUninitialized(size_t size) { - - void* NodeArrayBufferAllocator::Reallocate( - void* data, size_t old_size, size_t size) { -- void* ret = UncheckedRealloc(static_cast(data), size); -+ void* ret = allocator_->Reallocate(data, old_size, size); - if (LIKELY(ret != nullptr) || UNLIKELY(size == 0)) - total_mem_usage_.fetch_add(size - old_size, std::memory_order_relaxed); - return ret; -@@ -108,7 +108,7 @@ void* NodeArrayBufferAllocator::Reallocate( - - void NodeArrayBufferAllocator::Free(void* data, size_t size) { - total_mem_usage_.fetch_sub(size, std::memory_order_relaxed); -- free(data); -+ allocator_->Free(data, size); - } - - DebuggingArrayBufferAllocator::~DebuggingArrayBufferAllocator() { -diff --git a/src/node_buffer.cc b/src/node_buffer.cc -index 215bd8003aabe17e43ac780c723cfe971b437eae..eb00eb6f592e20f3c17a529f30b09673774eb1c1 100644 ---- a/src/node_buffer.cc -+++ b/src/node_buffer.cc -@@ -1175,33 +1175,14 @@ void SetBufferPrototype(const FunctionCallbackInfo& args) { - env->set_buffer_prototype_object(proto); - } - --void GetZeroFillToggle(const FunctionCallbackInfo& args) { -+void SetZeroFillToggle(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - NodeArrayBufferAllocator* allocator = env->isolate_data()->node_allocator(); - Local ab; -- // It can be a nullptr when running inside an isolate where we -- // do not own the ArrayBuffer allocator. -- if (allocator == nullptr) { -- // Create a dummy Uint32Array - the JS land can only toggle the C++ land -- // setting when the allocator uses our toggle. With this the toggle in JS -- // land results in no-ops. -- ab = ArrayBuffer::New(env->isolate(), sizeof(uint32_t)); -- } else { -+ if (allocator != nullptr) { - uint32_t* zero_fill_field = allocator->zero_fill_field(); -- std::unique_ptr backing = -- ArrayBuffer::NewBackingStore(zero_fill_field, -- sizeof(*zero_fill_field), -- [](void*, size_t, void*) {}, -- nullptr); -- ab = ArrayBuffer::New(env->isolate(), std::move(backing)); -+ *zero_fill_field = args[0]->BooleanValue(env->isolate()); - } -- -- ab->SetPrivate( -- env->context(), -- env->untransferable_object_private_symbol(), -- True(env->isolate())).Check(); -- -- args.GetReturnValue().Set(Uint32Array::New(ab, 0, 1)); - } - - void DetachArrayBuffer(const FunctionCallbackInfo& args) { -@@ -1310,7 +1291,7 @@ void Initialize(Local target, - env->SetMethod(target, "ucs2Write", StringWrite); - env->SetMethod(target, "utf8Write", StringWrite); - -- env->SetMethod(target, "getZeroFillToggle", GetZeroFillToggle); -+ env->SetMethod(target, "setZeroFillToggle", SetZeroFillToggle); - } - - } // anonymous namespace -@@ -1350,7 +1331,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { - registry->Register(StringWrite); - registry->Register(StringWrite); - registry->Register(StringWrite); -- registry->Register(GetZeroFillToggle); -+ registry->Register(SetZeroFillToggle); - - registry->Register(DetachArrayBuffer); - registry->Register(CopyArrayBuffer); -diff --git a/src/node_internals.h b/src/node_internals.h -index d37be23cd63e82d4040777bd0e17ed449ec0b15b..0b66996f11c66800a7e21ee84fa101450b856227 100644 ---- a/src/node_internals.h -+++ b/src/node_internals.h -@@ -118,6 +118,8 @@ class NodeArrayBufferAllocator : public ArrayBufferAllocator { - private: - uint32_t zero_fill_field_ = 1; // Boolean but exposed as uint32 to JS land. - std::atomic total_mem_usage_ {0}; -+ -+ std::unique_ptr allocator_{v8::ArrayBuffer::Allocator::NewDefaultAllocator()}; - }; - - class DebuggingArrayBufferAllocator final : public NodeArrayBufferAllocator { diff --git a/patches/node/serdes_allocate_using_the_v8_arraybuffer_allocator.patch b/patches/node/serdes_allocate_using_the_v8_arraybuffer_allocator.patch deleted file mode 100644 index c0bd2fd4e1a4d..0000000000000 --- a/patches/node/serdes_allocate_using_the_v8_arraybuffer_allocator.patch +++ /dev/null @@ -1,84 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jeremy Rose -Date: Tue, 21 Jun 2022 10:04:21 -0700 -Subject: allocate using the v8 arraybuffer allocator - -This is required when the v8 sandbox is enabled, otherwise v8 will crash when -calling the `v8.serialize()` method from Node.js. - -diff --git a/src/node_serdes.cc b/src/node_serdes.cc -index f6f0034bc24d09e3ad65491c7d6be0b9c9db1581..31a3d1c1ea19d649dcb7019a521f8033fae76962 100644 ---- a/src/node_serdes.cc -+++ b/src/node_serdes.cc -@@ -29,6 +29,11 @@ using v8::ValueSerializer; - - namespace serdes { - -+v8::ArrayBuffer::Allocator* GetAllocator() { -+ static v8::ArrayBuffer::Allocator* allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); -+ return allocator; -+}; -+ - class SerializerContext : public BaseObject, - public ValueSerializer::Delegate { - public: -@@ -37,10 +42,15 @@ class SerializerContext : public BaseObject, - - ~SerializerContext() override = default; - -+ // v8::ValueSerializer::Delegate - void ThrowDataCloneError(Local message) override; - Maybe WriteHostObject(Isolate* isolate, Local object) override; - Maybe GetSharedArrayBufferId( - Isolate* isolate, Local 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& args); -@@ -61,6 +71,7 @@ class SerializerContext : public BaseObject, - - private: - ValueSerializer serializer_; -+ size_t last_length_; - }; - - class DeserializerContext : public BaseObject, -@@ -144,6 +155,22 @@ Maybe SerializerContext::GetSharedArrayBufferId( - return id.ToLocalChecked()->Uint32Value(env()->context()); - } - -+void* SerializerContext::ReallocateBufferMemory(void* old_buffer, -+ size_t old_length, -+ size_t* new_length) { -+ *new_length = std::max(static_cast(4096), *new_length); -+ last_length_ = *new_length; -+ if (old_buffer) { -+ return GetAllocator()->Reallocate(old_buffer, old_length, *new_length); -+ } else { -+ return GetAllocator()->Allocate(*new_length); -+ } -+} -+ -+void SerializerContext::FreeBufferMemory(void* buffer) { -+ GetAllocator()->Free(buffer, last_length_); -+} -+ - Maybe SerializerContext::WriteHostObject(Isolate* isolate, - Local input) { - MaybeLocal ret; -@@ -211,7 +238,11 @@ void SerializerContext::ReleaseBuffer(const FunctionCallbackInfo& args) { - std::pair ret = ctx->serializer_.Release(); - auto buf = Buffer::New(ctx->env(), - reinterpret_cast(ret.first), -- ret.second); -+ ret.second, -+ [](char* data, void* hint){ -+ GetAllocator()->Free(data, reinterpret_cast(hint)); -+ }, -+ reinterpret_cast(ctx->last_length_)); - - if (!buf.IsEmpty()) { - args.GetReturnValue().Set(buf.ToLocalChecked()); diff --git a/patches/node/support_v8_sandboxed_pointers.patch b/patches/node/support_v8_sandboxed_pointers.patch new file mode 100644 index 0000000000000..2e309e9efcaef --- /dev/null +++ b/patches/node/support_v8_sandboxed_pointers.patch @@ -0,0 +1,372 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jeremy Rose +Date: Tue, 21 Jun 2022 10:04:21 -0700 +Subject: support V8 sandboxed pointers + +This refactors several allocators to allocate within the V8 memory cage, +allowing them to be compatible with the V8_SANDBOXED_POINTERS feature. + +diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js +index 4c459b58b5a048d9d8a4f15f4011e7cce68089f4..6fb4c8d4567aee5b313ad621ea42699a196f18c7 100644 +--- a/lib/internal/bootstrap/pre_execution.js ++++ b/lib/internal/bootstrap/pre_execution.js +@@ -14,7 +14,6 @@ const { + getOptionValue, + getEmbedderOptions, + } = require('internal/options'); +-const { reconnectZeroFillToggle } = require('internal/buffer'); + const { + defineOperation, + emitExperimentalWarning, +@@ -26,10 +25,6 @@ const { ERR_MANIFEST_ASSERT_INTEGRITY } = require('internal/errors').codes; + const assert = require('internal/assert'); + + function prepareMainThreadExecution(expandArgv1 = false) { +- // TODO(joyeecheung): this is also necessary for workers when they deserialize +- // this toggle from the snapshot. +- reconnectZeroFillToggle(); +- + // Patch the process object with legacy properties and normalizations + patchProcessObject(expandArgv1); + setupTraceCategoryState(); +diff --git a/lib/internal/buffer.js b/lib/internal/buffer.js +index bd38cf48a7fc6e8d61d8f11fa15c34aee182cbe3..1aa071cdc071dcdaf5c3b4bed0d3d76e5871731d 100644 +--- a/lib/internal/buffer.js ++++ b/lib/internal/buffer.js +@@ -30,7 +30,7 @@ const { + hexWrite, + ucs2Write, + utf8Write, +- getZeroFillToggle ++ setZeroFillToggle + } = internalBinding('buffer'); + const { + untransferable_object_private_symbol, +@@ -1055,24 +1055,15 @@ function markAsUntransferable(obj) { + // 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. +-let zeroFill = getZeroFillToggle(); + function createUnsafeBuffer(size) { +- zeroFill[0] = 0; ++ setZeroFillToggle(false); + try { + return new FastBuffer(size); + } finally { +- zeroFill[0] = 1; ++ setZeroFillToggle(true) + } + } + +-// The connection between the JS land zero fill toggle and the +-// C++ one in the NodeArrayBufferAllocator gets lost if the toggle +-// is deserialized from the snapshot, because V8 owns the underlying +-// memory of this toggle. This resets the connection. +-function reconnectZeroFillToggle() { +- zeroFill = getZeroFillToggle(); +-} +- + module.exports = { + FastBuffer, + addBufferPrototypeMethods, +@@ -1080,5 +1071,4 @@ module.exports = { + createUnsafeBuffer, + readUInt16BE, + readUInt32BE, +- reconnectZeroFillToggle + }; +diff --git a/src/api/environment.cc b/src/api/environment.cc +index 2abf5994405e8da2a04d1b23b75ccd3658398474..024d612a04d83583b397549589d994e32cf0107f 100644 +--- a/src/api/environment.cc ++++ b/src/api/environment.cc +@@ -83,16 +83,16 @@ MaybeLocal PrepareStackTraceCallback(Local context, + void* NodeArrayBufferAllocator::Allocate(size_t size) { + void* ret; + if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers) +- ret = UncheckedCalloc(size); ++ ret = allocator_->Allocate(size); + else +- ret = UncheckedMalloc(size); ++ ret = allocator_->AllocateUninitialized(size); + if (LIKELY(ret != nullptr)) + total_mem_usage_.fetch_add(size, std::memory_order_relaxed); + return ret; + } + + void* NodeArrayBufferAllocator::AllocateUninitialized(size_t size) { +- void* ret = node::UncheckedMalloc(size); ++ void* ret = allocator_->AllocateUninitialized(size); + if (LIKELY(ret != nullptr)) + total_mem_usage_.fetch_add(size, std::memory_order_relaxed); + return ret; +@@ -100,7 +100,7 @@ void* NodeArrayBufferAllocator::AllocateUninitialized(size_t size) { + + void* NodeArrayBufferAllocator::Reallocate( + void* data, size_t old_size, size_t size) { +- void* ret = UncheckedRealloc(static_cast(data), size); ++ void* ret = allocator_->Reallocate(data, old_size, size); + if (LIKELY(ret != nullptr) || UNLIKELY(size == 0)) + total_mem_usage_.fetch_add(size - old_size, std::memory_order_relaxed); + return ret; +@@ -108,7 +108,7 @@ void* NodeArrayBufferAllocator::Reallocate( + + void NodeArrayBufferAllocator::Free(void* data, size_t size) { + total_mem_usage_.fetch_sub(size, std::memory_order_relaxed); +- free(data); ++ allocator_->Free(data, size); + } + + DebuggingArrayBufferAllocator::~DebuggingArrayBufferAllocator() { +diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc +index 8dffad89c80e0906780d1b26ba9a65ba1e76ce0a..45bc99ce75248794e95b2dcb0101c28152e2bfd0 100644 +--- a/src/crypto/crypto_util.cc ++++ b/src/crypto/crypto_util.cc +@@ -318,10 +318,35 @@ ByteSource& ByteSource::operator=(ByteSource&& other) noexcept { + return *this; + } + +-std::unique_ptr ByteSource::ReleaseToBackingStore() { ++std::unique_ptr ByteSource::ReleaseToBackingStore(Environment* env) { + // It's ok for allocated_data_ to be nullptr but + // only if size_ is zero. + CHECK_IMPLIES(size_ > 0, allocated_data_ != nullptr); ++#if defined(V8_SANDBOXED_POINTERS) ++ // When V8 sandboxed pointers are enabled, we have to copy into the memory ++ // cage. We still want to ensure we erase the data on free though, so ++ // provide a custom deleter that calls OPENSSL_cleanse. ++ if (!size()) ++ return ArrayBuffer::NewBackingStore(env->isolate(), 0); ++ std::unique_ptr allocator(ArrayBuffer::Allocator::NewDefaultAllocator()); ++ void* v8_data = allocator->Allocate(size()); ++ CHECK(v8_data); ++ memcpy(v8_data, allocated_data_, size()); ++ OPENSSL_clear_free(allocated_data_, size()); ++ std::unique_ptr ptr = ArrayBuffer::NewBackingStore( ++ v8_data, ++ size(), ++ [](void* data, size_t length, void*) { ++ OPENSSL_cleanse(data, length); ++ std::unique_ptr allocator(ArrayBuffer::Allocator::NewDefaultAllocator()); ++ allocator->Free(data, length); ++ }, nullptr); ++ CHECK(ptr); ++ allocated_data_ = nullptr; ++ data_ = nullptr; ++ size_ = 0; ++ return ptr; ++#else + std::unique_ptr ptr = ArrayBuffer::NewBackingStore( + allocated_data_, + size(), +@@ -333,10 +358,11 @@ std::unique_ptr ByteSource::ReleaseToBackingStore() { + data_ = nullptr; + size_ = 0; + return ptr; ++#endif // defined(V8_SANDBOXED_POINTERS) + } + + Local ByteSource::ToArrayBuffer(Environment* env) { +- std::unique_ptr store = ReleaseToBackingStore(); ++ std::unique_ptr store = ReleaseToBackingStore(env); + return ArrayBuffer::New(env->isolate(), std::move(store)); + } + +@@ -665,6 +691,16 @@ CryptoJobMode GetCryptoJobMode(v8::Local args) { + } + + namespace { ++#if defined(V8_SANDBOXED_POINTERS) ++// When V8 sandboxed pointers are enabled, the secure heap cannot be used as ++// all ArrayBuffers must be allocated inside the V8 memory cage. ++void SecureBuffer(const FunctionCallbackInfo& args) { ++ CHECK(args[0]->IsUint32()); ++ uint32_t len = args[0].As()->Value(); ++ Local buffer = ArrayBuffer::New(args.GetIsolate(), len); ++ args.GetReturnValue().Set(Uint8Array::New(buffer, 0, len)); ++} ++#else + // SecureBuffer uses openssl to allocate a Uint8Array using + // OPENSSL_secure_malloc. Because we do not yet actually + // make use of secure heap, this has the same semantics as +@@ -692,6 +728,7 @@ void SecureBuffer(const FunctionCallbackInfo& args) { + Local buffer = ArrayBuffer::New(env->isolate(), store); + args.GetReturnValue().Set(Uint8Array::New(buffer, 0, len)); + } ++#endif // defined(V8_SANDBOXED_POINTERS) + + void SecureHeapUsed(const FunctionCallbackInfo& args) { + #ifndef OPENSSL_IS_BORINGSSL +diff --git a/src/crypto/crypto_util.h b/src/crypto/crypto_util.h +index 0ce3a8f219a2952f660ff72a6ce36ee109add649..06e9eb72e4ea60db4c63d08b24b80a1e6c4f3eaf 100644 +--- a/src/crypto/crypto_util.h ++++ b/src/crypto/crypto_util.h +@@ -257,7 +257,7 @@ class ByteSource { + // Creates a v8::BackingStore that takes over responsibility for + // any allocated data. The ByteSource will be reset with size = 0 + // after being called. +- std::unique_ptr ReleaseToBackingStore(); ++ std::unique_ptr ReleaseToBackingStore(Environment* env); + + v8::Local ToArrayBuffer(Environment* env); + +diff --git a/src/node_buffer.cc b/src/node_buffer.cc +index 215bd8003aabe17e43ac780c723cfe971b437eae..eb00eb6f592e20f3c17a529f30b09673774eb1c1 100644 +--- a/src/node_buffer.cc ++++ b/src/node_buffer.cc +@@ -1175,33 +1175,14 @@ void SetBufferPrototype(const FunctionCallbackInfo& args) { + env->set_buffer_prototype_object(proto); + } + +-void GetZeroFillToggle(const FunctionCallbackInfo& args) { ++void SetZeroFillToggle(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + NodeArrayBufferAllocator* allocator = env->isolate_data()->node_allocator(); + Local ab; +- // It can be a nullptr when running inside an isolate where we +- // do not own the ArrayBuffer allocator. +- if (allocator == nullptr) { +- // Create a dummy Uint32Array - the JS land can only toggle the C++ land +- // setting when the allocator uses our toggle. With this the toggle in JS +- // land results in no-ops. +- ab = ArrayBuffer::New(env->isolate(), sizeof(uint32_t)); +- } else { ++ if (allocator != nullptr) { + uint32_t* zero_fill_field = allocator->zero_fill_field(); +- std::unique_ptr backing = +- ArrayBuffer::NewBackingStore(zero_fill_field, +- sizeof(*zero_fill_field), +- [](void*, size_t, void*) {}, +- nullptr); +- ab = ArrayBuffer::New(env->isolate(), std::move(backing)); ++ *zero_fill_field = args[0]->BooleanValue(env->isolate()); + } +- +- ab->SetPrivate( +- env->context(), +- env->untransferable_object_private_symbol(), +- True(env->isolate())).Check(); +- +- args.GetReturnValue().Set(Uint32Array::New(ab, 0, 1)); + } + + void DetachArrayBuffer(const FunctionCallbackInfo& args) { +@@ -1310,7 +1291,7 @@ void Initialize(Local target, + env->SetMethod(target, "ucs2Write", StringWrite); + env->SetMethod(target, "utf8Write", StringWrite); + +- env->SetMethod(target, "getZeroFillToggle", GetZeroFillToggle); ++ env->SetMethod(target, "setZeroFillToggle", SetZeroFillToggle); + } + + } // anonymous namespace +@@ -1350,7 +1331,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { + registry->Register(StringWrite); + registry->Register(StringWrite); + registry->Register(StringWrite); +- registry->Register(GetZeroFillToggle); ++ registry->Register(SetZeroFillToggle); + + registry->Register(DetachArrayBuffer); + registry->Register(CopyArrayBuffer); +diff --git a/src/node_i18n.cc b/src/node_i18n.cc +index c537a247f55ff070da1988fc8b7309b5692b5c18..59bfb597849cd5a94800d6c83b238ef77245243e 100644 +--- a/src/node_i18n.cc ++++ b/src/node_i18n.cc +@@ -104,7 +104,7 @@ namespace { + + template + MaybeLocal ToBufferEndian(Environment* env, MaybeStackBuffer* buf) { +- MaybeLocal ret = Buffer::New(env, buf); ++ MaybeLocal ret = Buffer::Copy(env, reinterpret_cast(buf->out()), buf->length() * sizeof(T)); + if (ret.IsEmpty()) + return ret; + +diff --git a/src/node_internals.h b/src/node_internals.h +index d37be23cd63e82d4040777bd0e17ed449ec0b15b..0b66996f11c66800a7e21ee84fa101450b856227 100644 +--- a/src/node_internals.h ++++ b/src/node_internals.h +@@ -118,6 +118,8 @@ class NodeArrayBufferAllocator : public ArrayBufferAllocator { + private: + uint32_t zero_fill_field_ = 1; // Boolean but exposed as uint32 to JS land. + std::atomic total_mem_usage_ {0}; ++ ++ std::unique_ptr allocator_{v8::ArrayBuffer::Allocator::NewDefaultAllocator()}; + }; + + class DebuggingArrayBufferAllocator final : public NodeArrayBufferAllocator { +diff --git a/src/node_serdes.cc b/src/node_serdes.cc +index f6f0034bc24d09e3ad65491c7d6be0b9c9db1581..989a5618992806af05b64611bf7103b313d8223e 100644 +--- a/src/node_serdes.cc ++++ b/src/node_serdes.cc +@@ -29,6 +29,11 @@ using v8::ValueSerializer; + + namespace serdes { + ++v8::ArrayBuffer::Allocator* GetAllocator() { ++ static v8::ArrayBuffer::Allocator* allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); ++ return allocator; ++}; ++ + class SerializerContext : public BaseObject, + public ValueSerializer::Delegate { + public: +@@ -37,10 +42,15 @@ class SerializerContext : public BaseObject, + + ~SerializerContext() override = default; + ++ // v8::ValueSerializer::Delegate + void ThrowDataCloneError(Local message) override; + Maybe WriteHostObject(Isolate* isolate, Local object) override; + Maybe GetSharedArrayBufferId( + Isolate* isolate, Local 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& args); +@@ -61,6 +71,7 @@ class SerializerContext : public BaseObject, + + private: + ValueSerializer serializer_; ++ size_t last_length_ = 0; + }; + + class DeserializerContext : public BaseObject, +@@ -144,6 +155,21 @@ Maybe SerializerContext::GetSharedArrayBufferId( + return id.ToLocalChecked()->Uint32Value(env()->context()); + } + ++void* SerializerContext::ReallocateBufferMemory(void* old_buffer, ++ size_t requested_size, ++ size_t* new_length) { ++ *new_length = std::max(static_cast(4096), requested_size); ++ last_length_ = *new_length; ++ if (old_buffer) ++ return GetAllocator()->Reallocate(old_buffer, last_length_, *new_length); ++ else ++ return GetAllocator()->Allocate(*new_length); ++} ++ ++void SerializerContext::FreeBufferMemory(void* buffer) { ++ GetAllocator()->Free(buffer, last_length_); ++} ++ + Maybe SerializerContext::WriteHostObject(Isolate* isolate, + Local input) { + MaybeLocal ret; +@@ -211,7 +237,12 @@ void SerializerContext::ReleaseBuffer(const FunctionCallbackInfo& args) { + std::pair ret = ctx->serializer_.Release(); + auto buf = Buffer::New(ctx->env(), + reinterpret_cast(ret.first), +- ret.second); ++ ret.second, ++ [](char* data, void* hint){ ++ if (data) ++ GetAllocator()->Free(data, reinterpret_cast(hint)); ++ }, ++ reinterpret_cast(ctx->last_length_)); + + if (!buf.IsEmpty()) { + args.GetReturnValue().Set(buf.ToLocalChecked());