#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"
#ifdef V8_OS_LINUX
#include <signal.h>
#include <sys/mman.h>
#include <unistd.h>
#endif
#ifdef V8_USE_ADDRESS_SANITIZER
#include <sanitizer/asan_interface.h>
#endif
namespace v8 {
namespace internal {
#ifdef V8_ENABLE_SANDBOX
SandboxTesting::Mode SandboxTesting::mode_ = …;
Address SandboxTesting::target_page_base_ = …;
Address SandboxTesting::target_page_size_ = …;
#ifdef V8_ENABLE_MEMORY_CORRUPTION_API
namespace {
void SandboxGetBase(const v8::FunctionCallbackInfo<v8::Value>& info) {
DCHECK(ValidateCallbackInfo(info));
v8::Isolate* isolate = info.GetIsolate();
double sandbox_base = GetProcessWideSandbox()->base();
info.GetReturnValue().Set(v8::Number::New(isolate, sandbox_base));
}
void SandboxGetByteLength(const v8::FunctionCallbackInfo<v8::Value>& info) {
DCHECK(ValidateCallbackInfo(info));
v8::Isolate* isolate = info.GetIsolate();
double sandbox_size = GetProcessWideSandbox()->size();
info.GetReturnValue().Set(v8::Number::New(isolate, sandbox_size));
}
void SandboxMemoryView(const v8::FunctionCallbackInfo<v8::Value>& info) {
DCHECK(ValidateCallbackInfo(info));
v8::Isolate* isolate = info.GetIsolate();
Local<v8::Context> context = isolate->GetCurrentContext();
if (!info.IsConstructCall()) {
isolate->ThrowError("Sandbox.MemoryView must be invoked with 'new'");
return;
}
Local<v8::Integer> arg1, arg2;
if (!info[0]->ToInteger(context).ToLocal(&arg1) ||
!info[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));
info.GetReturnValue().Set(Utils::ToLocal(buffer));
}
using ArgumentObjectExtractorFunction = std::function<bool(
const v8::FunctionCallbackInfo<v8::Value>&, Tagged<HeapObject>* out)>;
static bool GetArgumentObjectPassedAsReference(
const v8::FunctionCallbackInfo<v8::Value>& info, Tagged<HeapObject>* out) {
v8::Isolate* isolate = info.GetIsolate();
if (info.Length() == 0) {
isolate->ThrowError("First argument must be provided");
return false;
}
Handle<Object> arg = Utils::OpenHandle(*info[0]);
if (!IsHeapObject(*arg)) {
isolate->ThrowError("First argument must be a HeapObject");
return false;
}
*out = Cast<HeapObject>(*arg);
return true;
}
static bool GetArgumentObjectPassedAsAddress(
const v8::FunctionCallbackInfo<v8::Value>& info, Tagged<HeapObject>* out) {
Sandbox* sandbox = GetProcessWideSandbox();
v8::Isolate* isolate = info.GetIsolate();
Local<v8::Context> context = isolate->GetCurrentContext();
if (info.Length() == 0) {
isolate->ThrowError("First argument must be provided");
return false;
}
Local<v8::Uint32> arg1;
if (!info[0]->ToUint32(context).ToLocal(&arg1)) {
isolate->ThrowError("First argument must be the address of a HeapObject");
return false;
}
uint32_t address = arg1->Value();
address &= ~kHeapObjectTagMask;
*out = HeapObject::FromAddress(sandbox->base() + address);
return true;
}
void SandboxGetAddressOf(const v8::FunctionCallbackInfo<v8::Value>& info) {
DCHECK(ValidateCallbackInfo(info));
v8::Isolate* isolate = info.GetIsolate();
Tagged<HeapObject> obj;
if (!GetArgumentObjectPassedAsReference(info, &obj)) {
return;
}
uint32_t address = static_cast<uint32_t>(obj->address());
info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, address));
}
void SandboxGetObjectAt(const v8::FunctionCallbackInfo<v8::Value>& info) {
DCHECK(ValidateCallbackInfo(info));
v8::Isolate* isolate = info.GetIsolate();
Tagged<HeapObject> obj;
if (!GetArgumentObjectPassedAsAddress(info, &obj)) {
return;
}
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
Handle<Object> handle(obj, i_isolate);
info.GetReturnValue().Set(ToApiHandle<v8::Value>(handle));
}
void SandboxIsValidObjectAt(const v8::FunctionCallbackInfo<v8::Value>& info) {
DCHECK(ValidateCallbackInfo(info));
v8::Isolate* isolate = info.GetIsolate();
Tagged<HeapObject> obj;
if (!GetArgumentObjectPassedAsAddress(info, &obj)) {
return;
}
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
auto IsLocatedInMappedMemory = [&](Tagged<HeapObject> obj) {
return !heap->memory_allocator()->IsOutsideAllocatedSpace(obj.address());
};
bool is_valid = false;
if (IsLocatedInMappedMemory(obj)) {
Tagged<Map> map = obj->map();
if (IsLocatedInMappedMemory(map)) {
is_valid = IsMap(map);
}
}
info.GetReturnValue().Set(is_valid);
}
static void SandboxIsWritableImpl(
const v8::FunctionCallbackInfo<v8::Value>& info,
ArgumentObjectExtractorFunction getArgumentObject) {
DCHECK(ValidateCallbackInfo(info));
Tagged<HeapObject> obj;
if (!getArgumentObject(info, &obj)) {
return;
}
MemoryChunkMetadata* chunk = MemoryChunkMetadata::FromHeapObject(obj);
bool is_writable = chunk->IsWritable();
info.GetReturnValue().Set(is_writable);
}
void SandboxIsWritable(const v8::FunctionCallbackInfo<v8::Value>& info) {
SandboxIsWritableImpl(info, &GetArgumentObjectPassedAsReference);
}
void SandboxIsWritableObjectAt(
const v8::FunctionCallbackInfo<v8::Value>& info) {
SandboxIsWritableImpl(info, &GetArgumentObjectPassedAsAddress);
}
static void SandboxGetSizeOfImpl(
const v8::FunctionCallbackInfo<v8::Value>& info,
ArgumentObjectExtractorFunction getArgumentObject) {
DCHECK(ValidateCallbackInfo(info));
Tagged<HeapObject> obj;
if (!getArgumentObject(info, &obj)) {
return;
}
int size = obj->Size();
info.GetReturnValue().Set(size);
}
void SandboxGetSizeOf(const v8::FunctionCallbackInfo<v8::Value>& info) {
SandboxGetSizeOfImpl(info, &GetArgumentObjectPassedAsReference);
}
void SandboxGetSizeOfObjectAt(const v8::FunctionCallbackInfo<v8::Value>& info) {
SandboxGetSizeOfImpl(info, &GetArgumentObjectPassedAsAddress);
}
static void SandboxGetInstanceTypeOfImpl(
const v8::FunctionCallbackInfo<v8::Value>& info,
ArgumentObjectExtractorFunction getArgumentObject) {
DCHECK(ValidateCallbackInfo(info));
v8::Isolate* isolate = info.GetIsolate();
Tagged<HeapObject> obj;
if (!getArgumentObject(info, &obj)) {
return;
}
InstanceType type = obj->map()->instance_type();
std::stringstream out;
out << type;
MaybeLocal<v8::String> result =
v8::String::NewFromUtf8(isolate, out.str().c_str());
info.GetReturnValue().Set(result.ToLocalChecked());
}
void SandboxGetInstanceTypeOf(const v8::FunctionCallbackInfo<v8::Value>& info) {
SandboxGetInstanceTypeOfImpl(info, &GetArgumentObjectPassedAsReference);
}
void SandboxGetInstanceTypeOfObjectAt(
const v8::FunctionCallbackInfo<v8::Value>& info) {
SandboxGetInstanceTypeOfImpl(info, &GetArgumentObjectPassedAsAddress);
}
static void SandboxGetInstanceTypeIdOfImpl(
const v8::FunctionCallbackInfo<v8::Value>& info,
ArgumentObjectExtractorFunction getArgumentObject) {
DCHECK(ValidateCallbackInfo(info));
Tagged<HeapObject> obj;
if (!getArgumentObject(info, &obj)) {
return;
}
InstanceType type = obj->map()->instance_type();
static_assert(std::is_same_v<std::underlying_type_t<InstanceType>, uint16_t>);
if (type > LAST_TYPE) {
const uint16_t kUnknownInstanceType = std::numeric_limits<uint16_t>::max();
type = static_cast<InstanceType>(kUnknownInstanceType);
}
info.GetReturnValue().Set(type);
}
void SandboxGetInstanceTypeIdOf(
const v8::FunctionCallbackInfo<v8::Value>& info) {
SandboxGetInstanceTypeIdOfImpl(info, &GetArgumentObjectPassedAsReference);
}
void SandboxGetInstanceTypeIdOfObjectAt(
const v8::FunctionCallbackInfo<v8::Value>& info) {
SandboxGetInstanceTypeIdOfImpl(info, &GetArgumentObjectPassedAsAddress);
}
void SandboxGetFieldOffsetImpl(
const v8::FunctionCallbackInfo<v8::Value>& info,
ArgumentObjectExtractorFunction getArgumentObject) {
DCHECK(ValidateCallbackInfo(info));
v8::Isolate* isolate = info.GetIsolate();
Tagged<HeapObject> obj;
if (!getArgumentObject(info, &obj)) {
return;
}
InstanceType instance_type = Cast<HeapObject>(*obj)->map()->instance_type();
v8::String::Utf8Value field_name(isolate, info[1]);
if (!*field_name) {
isolate->ThrowError("Second argument must be a string");
return;
}
auto& all_fields = SandboxTesting::GetFieldOffsetMap();
if (all_fields.find(instance_type) == all_fields.end()) {
isolate->ThrowError(
"Unknown object type. If needed, add it in "
"SandboxTesting::GetFieldOffsetMap");
return;
}
auto& obj_fields = all_fields[instance_type];
if (obj_fields.find(*field_name) == obj_fields.end()) {
isolate->ThrowError(
"Unknown field. If needed, add it in "
"SandboxTesting::GetFieldOffsetMap");
return;
}
int offset = obj_fields[*field_name];
info.GetReturnValue().Set(offset);
}
void SandboxGetFieldOffsetOf(const v8::FunctionCallbackInfo<v8::Value>& info) {
SandboxGetFieldOffsetImpl(info, &GetArgumentObjectPassedAsReference);
}
void SandboxGetFieldOffsetOfObjectAt(
const v8::FunctionCallbackInfo<v8::Value>& info) {
SandboxGetFieldOffsetImpl(info, &GetArgumentObjectPassedAsAddress);
}
void SandboxGetTargetPage(const v8::FunctionCallbackInfo<v8::Value>& info) {
DCHECK(ValidateCallbackInfo(info));
v8::Isolate* isolate = info.GetIsolate();
Address page = SandboxTesting::target_page_base();
CHECK_NE(page, kNullAddress);
info.GetReturnValue().Set(v8::Number::New(isolate, page));
}
Handle<FunctionTemplateInfo> NewFunctionTemplate(
Isolate* isolate, FunctionCallback func,
ConstructorBehavior constructor_behavior) {
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(isolate, 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::DefineOwnAccessorIgnoreAttributes(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);
}
}
void SandboxTesting::InstallMemoryCorruptionApiIfEnabled(Isolate* isolate) {
#ifndef V8_ENABLE_MEMORY_CORRUPTION_API
#error "This function should not be available in any shipping build " \
"where it could potentially be abused to facilitate exploitation."
#endif
if (!IsEnabled()) return;
CHECK(GetProcessWideSandbox()->is_initialized());
Handle<JSObject> sandbox = isolate->factory()->NewJSObject(
isolate->object_function(), AllocationType::kOld);
InstallGetter(isolate, sandbox, SandboxGetBase, "base");
InstallGetter(isolate, sandbox, SandboxGetByteLength, "byteLength");
InstallConstructor(isolate, sandbox, SandboxMemoryView, "MemoryView", 2);
InstallFunction(isolate, sandbox, SandboxGetAddressOf, "getAddressOf", 1);
InstallFunction(isolate, sandbox, SandboxGetObjectAt, "getObjectAt", 1);
InstallFunction(isolate, sandbox, SandboxIsValidObjectAt, "isValidObjectAt",
1);
InstallFunction(isolate, sandbox, SandboxIsWritable, "isWritable", 1);
InstallFunction(isolate, sandbox, SandboxIsWritableObjectAt,
"isWritableObjectAt", 1);
InstallFunction(isolate, sandbox, SandboxGetSizeOf, "getSizeOf", 1);
InstallFunction(isolate, sandbox, SandboxGetSizeOfObjectAt,
"getSizeOfObjectAt", 1);
InstallFunction(isolate, sandbox, SandboxGetInstanceTypeOf,
"getInstanceTypeOf", 1);
InstallFunction(isolate, sandbox, SandboxGetInstanceTypeOfObjectAt,
"getInstanceTypeOfObjectAt", 1);
InstallFunction(isolate, sandbox, SandboxGetInstanceTypeIdOf,
"getInstanceTypeIdOf", 1);
InstallFunction(isolate, sandbox, SandboxGetInstanceTypeIdOfObjectAt,
"getInstanceTypeIdOfObjectAt", 1);
InstallFunction(isolate, sandbox, SandboxGetFieldOffsetOf, "getFieldOffsetOf",
2);
InstallFunction(isolate, sandbox, SandboxGetFieldOffsetOfObjectAt,
"getFieldOffsetOfObjectAt", 2);
if (mode() == Mode::kForTesting) {
InstallGetter(isolate, sandbox, SandboxGetTargetPage, "targetPage");
}
Handle<JSGlobalObject> global = isolate->global_object();
Handle<String> name =
isolate->factory()->NewStringFromAsciiChecked("Sandbox");
JSObject::AddProperty(isolate, global, name, sandbox, DONT_ENUM);
}
#endif
namespace {
#ifdef V8_OS_LINUX
void PrintToStderr(const char* output) { … }
[[noreturn]] void FilterCrash(const char* reason) { … }
struct sigaction g_old_sigabrt_handler, g_old_sigtrap_handler,
g_old_sigbus_handler, g_old_sigsegv_handler;
void UninstallCrashFilter() { … }
void CrashFilter(int signal, siginfo_t* info, void* void_context) { … }
#ifdef V8_USE_ADDRESS_SANITIZER
void AsanFaultHandler() {
Address faultaddr = reinterpret_cast<Address>(__asan_get_report_address());
if (faultaddr == kNullAddress) {
FilterCrash(
"Caught ASan fault without a fault address. Ignoring it as we cannot "
"check if it is a sandbox violation. Exiting process...\n");
}
if (GetProcessWideSandbox()->Contains(faultaddr)) {
FilterCrash(
"Caught harmless ASan fault (inside sandbox address space). Exiting "
"process...\n");
}
UninstallCrashFilter();
PrintToStderr("\n## V8 sandbox violation detected!\n\n");
}
#endif
void InstallCrashFilter() { … }
#endif
}
void SandboxTesting::Enable(Mode mode) { … }
bool SandboxTesting::IsInsideTargetPage(Address faultaddr) { … }
SandboxTesting::FieldOffsetMap& SandboxTesting::GetFieldOffsetMap() { … }
#endif
}
}