diff --git a/BUILD.bazel b/BUILD.bazel index 3d37f45cede..584701ef478 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1921,6 +1921,8 @@ filegroup( "src/sandbox/external-pointer.h", "src/sandbox/external-pointer-table.cc", "src/sandbox/external-pointer-table.h", + "src/sandbox/testing.cc", + "src/sandbox/testing.h", "src/sandbox/sandbox.cc", "src/sandbox/sandbox.h", "src/sandbox/sandboxed-pointer-inl.h", diff --git a/BUILD.gn b/BUILD.gn index 7ef8c1f2e06..d0538db38c3 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -304,18 +304,18 @@ declare_args() { # Enable the experimental V8 sandbox. # Sets -DV8_SANDBOX. - v8_enable_sandbox = false + v8_enable_sandbox = true # Enable external pointer sandboxing. Requires v8_enable_sandbox. # Sets -DV8_SANDBOXED_EXTERNAL_POINRTERS. - v8_enable_sandboxed_external_pointers = false + v8_enable_sandboxed_external_pointers = true # Enable sandboxed pointers. Requires v8_enable_sandbox. # Sets -DV8_SANDBOXED_POINTERS. - v8_enable_sandboxed_pointers = false + v8_enable_sandboxed_pointers = true # Enable all available sandbox features. Implies v8_enable_sandbox. - v8_enable_sandbox_future = false + v8_enable_sandbox_future = true # Experimental feature for collecting per-class zone memory stats. # Requires use_rtti = true @@ -3332,6 +3332,7 @@ v8_header_set("v8_internal_headers") { "src/sandbox/sandbox.h", "src/sandbox/sandboxed-pointer-inl.h", "src/sandbox/sandboxed-pointer.h", + "src/sandbox/testing.h", "src/snapshot/code-serializer.h", "src/snapshot/context-deserializer.h", "src/snapshot/context-serializer.h", @@ -4353,6 +4354,7 @@ v8_source_set("v8_base_without_compiler") { "src/runtime/runtime.cc", "src/sandbox/external-pointer-table.cc", "src/sandbox/sandbox.cc", + "src/sandbox/testing.cc", "src/snapshot/code-serializer.cc", "src/snapshot/context-deserializer.cc", "src/snapshot/context-serializer.cc", diff --git a/src/d8/d8.cc b/src/d8/d8.cc index 050cbdc78df..061379666a8 100644 --- a/src/d8/d8.cc +++ b/src/d8/d8.cc @@ -2860,7 +2860,7 @@ Local Shell::CreateNodeTemplates(Isolate* isolate) { Local Shell::CreateGlobalTemplate(Isolate* isolate) { Local global_template = ObjectTemplate::New(isolate); - global_template->Set(Symbol::GetToStringTag(isolate), +/* global_template->Set(Symbol::GetToStringTag(isolate), String::NewFromUtf8Literal(isolate, "global")); global_template->Set(isolate, "version", FunctionTemplate::New(isolate, Version)); @@ -2877,13 +2877,13 @@ Local Shell::CreateGlobalTemplate(Isolate* isolate) { global_template->Set(isolate, "readline", FunctionTemplate::New(isolate, ReadLine)); global_template->Set(isolate, "load", - FunctionTemplate::New(isolate, ExecuteFile)); + FunctionTemplate::New(isolate, ExecuteFile));*/ global_template->Set(isolate, "setTimeout", FunctionTemplate::New(isolate, SetTimeout)); // Some Emscripten-generated code tries to call 'quit', which in turn would // call C's exit(). This would lead to memory leaks, because there is no way // we can terminate cleanly then, so we need a way to hide 'quit'. - if (!options.omit_quit) { +/* if (!options.omit_quit) { global_template->Set(isolate, "quit", FunctionTemplate::New(isolate, Quit)); } global_template->Set(isolate, "testRunner", @@ -2909,7 +2909,7 @@ Local Shell::CreateGlobalTemplate(Isolate* isolate) { if (i::FLAG_expose_async_hooks) { global_template->Set(isolate, "async_hooks", Shell::CreateAsyncHookTemplate(isolate)); - } + }*/ return global_template; } diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc index 16015435073..ecd1fbb4116 100644 --- a/src/init/bootstrapper.cc +++ b/src/init/bootstrapper.cc @@ -24,6 +24,7 @@ #include "src/logging/runtime-call-stats-scope.h" #include "src/objects/instance-type.h" #include "src/objects/objects.h" +#include "src/sandbox/testing.h" #ifdef ENABLE_VTUNE_TRACEMARK #include "src/extensions/vtunedomain-support-extension.h" #endif // ENABLE_VTUNE_TRACEMARK @@ -5694,6 +5695,10 @@ bool Genesis::InstallSpecialObjects(Isolate* isolate, } #endif // V8_ENABLE_WEBASSEMBLY + if (GetProcessWideSandbox()->is_initialized()) { + MemoryCorruptionApi::Install(isolate); + } + return true; } diff --git a/src/sandbox/testing.cc b/src/sandbox/testing.cc new file mode 100644 index 00000000000..327fd33588d --- /dev/null +++ b/src/sandbox/testing.cc @@ -0,0 +1,194 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/sandbox/testing.h" + +#include "src/api/api-inl.h" +#include "src/api/api-natives.h" +#include "src/common/globals.h" +#include "src/execution/isolate-inl.h" +#include "src/heap/factory.h" +#include "src/objects/backing-store.h" +#include "src/objects/js-objects.h" +#include "src/objects/templates.h" +#include "src/sandbox/sandbox.h" + +namespace v8 { +namespace internal { + +//#ifdef V8_EXPOSE_MEMORY_CORRUPTION_API + +namespace { + +// Sandbox.byteLength +void SandboxGetByteLength(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + double sandbox_size = GetProcessWideSandbox()->size(); + args.GetReturnValue().Set(v8::Number::New(isolate, sandbox_size)); +} + +// new Sandbox.MemoryView(args) -> Sandbox.MemoryView +void SandboxMemoryView(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + if (!args.IsConstructCall()) { + isolate->ThrowError("Sandbox.MemoryView must be invoked with 'new'"); + return; + } + + Local arg1, arg2; + if (!args[0]->ToInteger(context).ToLocal(&arg1) || + !args[1]->ToInteger(context).ToLocal(&arg2)) { + isolate->ThrowError("Expects two number arguments (start offset and size)"); + return; + } + + Sandbox* sandbox = GetProcessWideSandbox(); + CHECK_LE(sandbox->size(), kMaxSafeIntegerUint64); + + uint64_t offset = arg1->Value(); + uint64_t size = arg2->Value(); + if (offset > sandbox->size() || size > sandbox->size() || + (offset + size) > sandbox->size()) { + isolate->ThrowError( + "The MemoryView must be entirely contained within the sandbox"); + return; + } + + Factory* factory = reinterpret_cast(isolate)->factory(); + std::unique_ptr memory = BackingStore::WrapAllocation( + reinterpret_cast(sandbox->base() + offset), size, + v8::BackingStore::EmptyDeleter, nullptr, SharedFlag::kNotShared); + if (!memory) { + isolate->ThrowError("Out of memory: MemoryView backing store"); + return; + } + Handle buffer = factory->NewJSArrayBuffer(std::move(memory)); + args.GetReturnValue().Set(Utils::ToLocal(buffer)); +} + +// Sandbox.getAddressOf(object) -> Number +void SandboxGetAddressOf(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + + if (args.Length() == 0) { + isolate->ThrowError("First argument must be provided"); + return; + } + + Handle arg = Utils::OpenHandle(*args[0]); + if (!arg->IsHeapObject()) { + isolate->ThrowError("First argument must be a HeapObject"); + return; + } + + // HeapObjects must be allocated inside the pointer compression cage so their + // address relative to the start of the sandbox can be obtained simply by + // taking the lowest 32 bits of the absolute address. + uint32_t address = static_cast(HeapObject::cast(*arg).address()); + args.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, address)); +} + +// Sandbox.getSizeOf(object) -> Number +void SandboxGetSizeOf(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + + if (args.Length() == 0) { + isolate->ThrowError("First argument must be provided"); + return; + } + + Handle arg = Utils::OpenHandle(*args[0]); + if (!arg->IsHeapObject()) { + isolate->ThrowError("First argument must be a HeapObject"); + return; + } + + int size = HeapObject::cast(*arg).Size(); + args.GetReturnValue().Set(v8::Integer::New(isolate, size)); +} + +Handle NewFunctionTemplate( + Isolate* isolate, FunctionCallback func, + ConstructorBehavior constructor_behavior) { + // Use the API functions here as they are more convenient to use. + v8::Isolate* api_isolate = reinterpret_cast(isolate); + Local function_template = + FunctionTemplate::New(api_isolate, func, {}, {}, 0, constructor_behavior, + SideEffectType::kHasSideEffect); + return v8::Utils::OpenHandle(*function_template); +} + +Handle CreateFunc(Isolate* isolate, FunctionCallback func, + Handle name, bool is_constructor) { + ConstructorBehavior constructor_behavior = is_constructor + ? ConstructorBehavior::kAllow + : ConstructorBehavior::kThrow; + Handle function_template = + NewFunctionTemplate(isolate, func, constructor_behavior); + return ApiNatives::InstantiateFunction(function_template, name) + .ToHandleChecked(); +} + +void InstallFunc(Isolate* isolate, Handle holder, + FunctionCallback func, const char* name, int num_parameters, + bool is_constructor) { + Factory* factory = isolate->factory(); + Handle function_name = factory->NewStringFromAsciiChecked(name); + Handle function = + CreateFunc(isolate, func, function_name, is_constructor); + function->shared().set_length(num_parameters); + JSObject::AddProperty(isolate, holder, function_name, function, NONE); +} + +void InstallGetter(Isolate* isolate, Handle object, + FunctionCallback func, const char* name) { + Factory* factory = isolate->factory(); + Handle property_name = factory->NewStringFromAsciiChecked(name); + Handle getter = CreateFunc(isolate, func, property_name, false); + Handle setter = factory->null_value(); + JSObject::DefineAccessor(object, property_name, getter, setter, FROZEN); +} + +void InstallFunction(Isolate* isolate, Handle holder, + FunctionCallback func, const char* name, + int num_parameters) { + InstallFunc(isolate, holder, func, name, num_parameters, false); +} + +void InstallConstructor(Isolate* isolate, Handle holder, + FunctionCallback func, const char* name, + int num_parameters) { + InstallFunc(isolate, holder, func, name, num_parameters, true); +} + +} // namespace + +// static +void MemoryCorruptionApi::Install(Isolate* isolate) { + CHECK(GetProcessWideSandbox()->is_initialized()); + + Factory* factory = isolate->factory(); + + // Create the special Sandbox object that provides read/write access to the + // sandbox address space alongside other miscellaneous functionality. + Handle sandbox = + factory->NewJSObject(isolate->object_function(), AllocationType::kOld); + + InstallGetter(isolate, sandbox, SandboxGetByteLength, "byteLength"); + InstallConstructor(isolate, sandbox, SandboxMemoryView, "MemoryView", 2); + InstallFunction(isolate, sandbox, SandboxGetAddressOf, "getAddressOf", 1); + InstallFunction(isolate, sandbox, SandboxGetSizeOf, "getSizeOf", 1); + + // Install the Sandbox object as property on the global object. + Handle global = isolate->global_object(); + Handle name = factory->NewStringFromAsciiChecked("Sandbox"); + JSObject::AddProperty(isolate, global, name, sandbox, DONT_ENUM); +} + +//#endif // V8_EXPOSE_MEMORY_CORRUPTION_API + +} // namespace internal +} // namespace v8 diff --git a/src/sandbox/testing.h b/src/sandbox/testing.h new file mode 100644 index 00000000000..0c30397c3c5 --- /dev/null +++ b/src/sandbox/testing.h @@ -0,0 +1,28 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_SANDBOX_TESTING_H_ +#define V8_SANDBOX_TESTING_H_ + +#include "src/common/globals.h" + +namespace v8 { +namespace internal { + +//#ifdef V8_EXPOSE_MEMORY_CORRUPTION_API +// A JavaScript API that emulates typical exploit primitives. +// +// This can be used for testing the sandbox, for example to write regression +// tests for bugs in the sandbox or to develop fuzzers. +class MemoryCorruptionApi { + public: + V8_EXPORT_PRIVATE static void Install(Isolate* isolate); +}; + +//#endif // V8_EXPOSE_MEMORY_CORRUPTION_API + +} // namespace internal +} // namespace v8 + +#endif // V8_SANDBOX_TESTING_H_