[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:
parent
58d19ed76a
commit
4a12cb1022
@ -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",
|
||||
|
13
BUILD.gn
13
BUILD.gn
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
194
src/sandbox/testing.cc
Normal 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
28
src/sandbox/testing.h
Normal 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_
|
Loading…
Reference in New Issue
Block a user