Skip to content

src: prepare for v8 sandboxing #58376

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

Closed
Closed
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
17 changes: 17 additions & 0 deletions src/crypto/crypto_dh.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ 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 @@ -58,6 +60,20 @@ MaybeLocal<Value> DataPointerToBuffer(Environment* env, DataPointer&& data) {
struct Flag {
bool secure;
};
#ifdef V8_ENABLE_SANDBOX
auto backing = ArrayBuffer::NewBackingStore(
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(),
Expand All @@ -67,6 +83,7 @@ MaybeLocal<Value> DataPointerToBuffer(Environment* env, DataPointer&& data) {
},
new Flag{data.isSecure()});
data.release();
#endif // V8_ENABLE_SANDBOX

auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>());
Expand Down
41 changes: 38 additions & 3 deletions src/crypto/crypto_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ using ncrypto::EnginePointer;
using ncrypto::SSLPointer;
using v8::ArrayBuffer;
using v8::BackingStore;
using v8::BackingStoreInitializationMode;
using v8::BackingStoreOnFailureMode;
using v8::BigInt;
using v8::Context;
using v8::EscapableHandleScope;
Expand Down Expand Up @@ -339,16 +341,37 @@ ByteSource& ByteSource::operator=(ByteSource&& other) noexcept {
return *this;
}

std::unique_ptr<BackingStore> ByteSource::ReleaseToBackingStore() {
std::unique_ptr<BackingStore> 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);
#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(
env->isolate(),
size(),
BackingStoreInitializationMode::kUninitialized,
BackingStoreOnFailureMode::kReturnNull);
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;
Expand All @@ -357,7 +380,7 @@ std::unique_ptr<BackingStore> ByteSource::ReleaseToBackingStore() {
}

Local<ArrayBuffer> ByteSource::ToArrayBuffer(Environment* env) {
std::unique_ptr<BackingStore> store = ReleaseToBackingStore();
std::unique_ptr<BackingStore> store = ReleaseToBackingStore(env);
return ArrayBuffer::New(env->isolate(), std::move(store));
}

Expand Down Expand Up @@ -648,8 +671,19 @@ namespace {
// using OPENSSL_malloc. However, if the secure heap is
// initialized, SecureBuffer will automatically use it.
void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsUint32());
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();

auto data = DataPointer::SecureAlloc(len);
Expand All @@ -676,6 +710,7 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {

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
2 changes: 1 addition & 1 deletion src/crypto/crypto_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ class ByteSource final {
// 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<v8::BackingStore> ReleaseToBackingStore();
std::unique_ptr<v8::BackingStore> ReleaseToBackingStore(Environment* env);

v8::Local<v8::ArrayBuffer> ToArrayBuffer(Environment* env);

Expand Down
17 changes: 17 additions & 0 deletions src/crypto/crypto_x509.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ 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 @@ -131,13 +132,29 @@ MaybeLocal<Value> ToBuffer(Environment* env, BIOPointer* bio) {
if (bio == nullptr || !*bio) [[unlikely]]
return {};
BUF_MEM* mem = *bio;
#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(
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
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
2 changes: 1 addition & 1 deletion src/js_native_api_v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ napi_status NewExternalString(napi_env env,
CHECK_NEW_STRING_ARGS(env, str, length, result);

napi_status status;
#if defined(V8_ENABLE_SANDBOX)
#ifdef V8_ENABLE_SANDBOX
status = create_api(env, str, length, result);
if (status == napi_ok) {
if (copied != nullptr) {
Expand Down
4 changes: 2 additions & 2 deletions src/node_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1056,9 +1056,9 @@ napi_create_external_buffer(napi_env env,
NAPI_PREAMBLE(env);
CHECK_ARG(env, result);

#if defined(V8_ENABLE_SANDBOX)
#ifdef V8_ENABLE_SANDBOX
return napi_set_last_error(env, napi_no_external_buffers_allowed);
#endif
#endif // V8_ENABLE_SANDBOX

v8::Isolate* isolate = env->isolate;

Expand Down
5 changes: 5 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors,
}

// Any value less than 2 disables use of the secure heap.
#ifndef V8_ENABLE_SANDBOX
// The secure heap is not supported when V8_ENABLE_SANDBOX is enabled.
if (secure_heap >= 2) {
if ((secure_heap & (secure_heap - 1)) != 0)
errors->push_back("--secure-heap must be a power of 2");
Expand All @@ -93,6 +95,7 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors,
if ((secure_heap_min & (secure_heap_min - 1)) != 0)
errors->push_back("--secure-heap-min must be a power of 2");
}
#endif // V8_ENABLE_SANDBOX
#endif // HAVE_OPENSSL

if (use_largepages != "off" &&
Expand Down Expand Up @@ -1172,6 +1175,7 @@ PerProcessOptionsParser::PerProcessOptionsParser(
"force FIPS crypto (cannot be disabled)",
&PerProcessOptions::force_fips_crypto,
kAllowedInEnvvar);
#ifndef V8_ENABLE_SANDBOX
AddOption("--secure-heap",
"total size of the OpenSSL secure heap",
&PerProcessOptions::secure_heap,
Expand All @@ -1180,6 +1184,7 @@ PerProcessOptionsParser::PerProcessOptionsParser(
"minimum allocation size from the OpenSSL secure heap",
&PerProcessOptions::secure_heap_min,
kAllowedInEnvvar);
#endif // V8_ENABLE_SANDBOX
#endif // HAVE_OPENSSL
#if OPENSSL_VERSION_MAJOR >= 3
AddOption("--openssl-legacy-provider",
Expand Down
Loading