[sandbox] Add new Memory Corruption API

When enabled, this API exposes a new global 'Sandbox' object which
contains a number of functions and objects that in effect emulate
typical memory corruption primitives constructed by exploits. In
particular, the 'MemoryView' constructor can construct ArrayBuffers
instances that can corrupt arbitrary memory inside the sandbox. Further,
the getAddressOf(obj) and getSizeInBytesOf(obj) functions can be used
respectively to obtain the address (relative to the base of the sandbox)
and size of any HeapObject that can be accessed from JavaScript.

This API is useful for testing the sandbox, for example to
facilitate developing PoC sandbox escapes or writing regression tests.
In the future, it may also be used by custom V8 sandbox fuzzers.

Bug: v8:12878
Change-Id: I4e420b2ff28bd834b0693f1546942e51c71bfdda
Cq-Include-Trybots: luci.v8.try:v8_linux64_heap_sandbox_dbg_ng,v8_linux_arm64_sim_heap_sandbox_dbg_ng
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3650718
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Commit-Queue: Samuel Groß <saelo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80659}
This commit is contained in:
Samuel Groß 2022-05-20 11:53:14 +02:00 committed by V8 LUCI CQ
parent 58d19ed76a
commit 4a12cb1022
6 changed files with 244 additions and 1 deletions

View File

@ -2021,6 +2021,8 @@ filegroup(
"src/sandbox/external-pointer-table.cc",
"src/sandbox/external-pointer-table-inl.h",
"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",

View File

@ -315,6 +315,11 @@ declare_args() {
# Enable all available sandbox features. Implies v8_enable_sandbox.
v8_enable_sandbox_future = false
# Expose the memory corruption API to JavaScript. Useful for testing the sandbox.
# WARNING This will expose builtins that (by design) cause memory corruption.
# Sets -DV8_EXPOSE_MEMORY_CORRUPTION_API
v8_expose_memory_corruption_api = false
# Experimental feature for collecting per-class zone memory stats.
# Requires use_rtti = true
v8_enable_precise_zone_stats = false
@ -549,6 +554,9 @@ assert(!v8_enable_sandboxed_pointers || v8_enable_sandbox,
assert(!v8_enable_sandboxed_external_pointers || v8_enable_sandbox,
"Sandboxed external pointers require the sandbox")
assert(!v8_expose_memory_corruption_api || v8_enable_sandbox,
"The Memory Corruption API requires the sandbox")
assert(
!v8_enable_pointer_compression_shared_cage || v8_enable_pointer_compression,
"Can't share a pointer compression cage if pointers aren't compressed")
@ -1018,6 +1026,9 @@ config("features") {
if (v8_fuchsia_use_vmex_resource) {
defines += [ "V8_USE_VMEX_RESOURCE" ]
}
if (v8_expose_memory_corruption_api) {
defines += [ "V8_EXPOSE_MEMORY_CORRUPTION_API" ]
}
}
config("toolchain") {
@ -3418,6 +3429,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",
@ -4534,6 +4546,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",

View File

@ -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
@ -5891,6 +5892,12 @@ bool Genesis::InstallSpecialObjects(Isolate* isolate,
}
#endif // V8_ENABLE_WEBASSEMBLY
#ifdef V8_EXPOSE_MEMORY_CORRUPTION_API
if (GetProcessWideSandbox()->is_initialized()) {
MemoryCorruptionApi::Install(isolate);
}
#endif // V8_EXPOSE_MEMORY_CORRUPTION_API
return true;
}

View File

@ -12,7 +12,6 @@
#include "testing/gtest/include/gtest/gtest_prod.h" // nogncheck
namespace v8 {
namespace internal {
#ifdef V8_ENABLE_SANDBOX

194
src/sandbox/testing.cc Normal file
View File

@ -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<v8::Value>& 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<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
Local<v8::Context> context = isolate->GetCurrentContext();
if (!args.IsConstructCall()) {
isolate->ThrowError("Sandbox.MemoryView must be invoked with 'new'");
return;
}
Local<v8::Integer> 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*>(isolate)->factory();
std::unique_ptr<BackingStore> memory = BackingStore::WrapAllocation(
reinterpret_cast<void*>(sandbox->base() + offset), size,
v8::BackingStore::EmptyDeleter, nullptr, SharedFlag::kNotShared);
if (!memory) {
isolate->ThrowError("Out of memory: MemoryView backing store");
return;
}
Handle<JSArrayBuffer> buffer = factory->NewJSArrayBuffer(std::move(memory));
args.GetReturnValue().Set(Utils::ToLocal(buffer));
}
// Sandbox.getAddressOf(object) -> Number
void SandboxGetAddressOf(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
if (args.Length() == 0) {
isolate->ThrowError("First argument must be provided");
return;
}
Handle<Object> 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<uint32_t>(HeapObject::cast(*arg).address());
args.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, address));
}
// Sandbox.getSizeOf(object) -> Number
void SandboxGetSizeOf(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
if (args.Length() == 0) {
isolate->ThrowError("First argument must be provided");
return;
}
Handle<Object> 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<FunctionTemplateInfo> 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<v8::Isolate*>(isolate);
Local<FunctionTemplate> function_template =
FunctionTemplate::New(api_isolate, func, {}, {}, 0, constructor_behavior,
SideEffectType::kHasSideEffect);
return v8::Utils::OpenHandle(*function_template);
}
Handle<JSFunction> CreateFunc(Isolate* isolate, FunctionCallback func,
Handle<String> name, bool is_constructor) {
ConstructorBehavior constructor_behavior = is_constructor
? ConstructorBehavior::kAllow
: ConstructorBehavior::kThrow;
Handle<FunctionTemplateInfo> function_template =
NewFunctionTemplate(isolate, func, constructor_behavior);
return ApiNatives::InstantiateFunction(function_template, name)
.ToHandleChecked();
}
void InstallFunc(Isolate* isolate, Handle<JSObject> holder,
FunctionCallback func, const char* name, int num_parameters,
bool is_constructor) {
Factory* factory = isolate->factory();
Handle<String> function_name = factory->NewStringFromAsciiChecked(name);
Handle<JSFunction> 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<JSObject> object,
FunctionCallback func, const char* name) {
Factory* factory = isolate->factory();
Handle<String> property_name = factory->NewStringFromAsciiChecked(name);
Handle<JSFunction> getter = CreateFunc(isolate, func, property_name, false);
Handle<Object> setter = factory->null_value();
JSObject::DefineAccessor(object, property_name, getter, setter, FROZEN);
}
void InstallFunction(Isolate* isolate, Handle<JSObject> holder,
FunctionCallback func, const char* name,
int num_parameters) {
InstallFunc(isolate, holder, func, name, num_parameters, false);
}
void InstallConstructor(Isolate* isolate, Handle<JSObject> 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<JSObject> 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<JSGlobalObject> global = isolate->global_object();
Handle<String> name = factory->NewStringFromAsciiChecked("Sandbox");
JSObject::AddProperty(isolate, global, name, sandbox, DONT_ENUM);
}
#endif // V8_EXPOSE_MEMORY_CORRUPTION_API
} // namespace internal
} // namespace v8

28
src/sandbox/testing.h Normal file
View File

@ -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_