/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/v8/src/sandbox/testing.cc
1 294 строки
50 KB
Michaël Zasso
deps: update V8 to 14.6.202.33
24 апр 2026, 19:01
Не верифицирован
24 апр 2026, 19:01
f1e0b83
Код
Авторство
О чём код?
// 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 <vector> #include "src/api/api-inl.h" #include "src/api/api-natives.h" #include "src/base/platform/mutex.h" #include "src/base/virtual-address-space.h" #include "src/builtins/builtins.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/instance-type.h" #include "src/objects/js-objects.h" #include "src/objects/templates.h" #include "src/sandbox/sandbox.h" #ifdef V8_INTL_SUPPORT #include "src/objects/js-segments.h" #endif // V8_INTL_SUPPORT #ifdef V8_OS_LINUX #include <signal.h> #include <sys/mman.h> #include <sys/ucontext.h> #include <unistd.h> #include "src/base/platform/platform-linux.h" #endif // V8_OS_LINUX #if defined(V8_USE_ADDRESS_SANITIZER) #include <sanitizer/asan_interface.h> #endif #if defined(V8_USE_ADDRESS_SANITIZER) || defined(V8_USE_MEMORY_SANITIZER) || \ defined(V8_USE_UNDEFINED_BEHAVIOR_SANITIZER) #define V8_USE_ANY_SANITIZER 1 #include <sanitizer/common_interface_defs.h> #endif #if defined(V8_ENABLE_SANDBOX) && defined(V8_ENABLE_MEMORY_CORRUPTION_API) #include "src/sandbox/external-strings-cage.h" #endif // V8_ENABLE_SANDBOX && V8_ENABLE_MEMORY_CORRUPTION_API namespace v8 { namespace internal { #ifdef V8_ENABLE_SANDBOX SandboxTesting::Mode SandboxTesting::mode_ = SandboxTesting::Mode::kDisabled; namespace { void ThrowTypeError(v8::Isolate* isolate, std::string_view message) { isolate->ThrowException(v8::Exception::TypeError( v8::String::NewFromUtf8(isolate, message.data(), NewStringType::kNormal, static_cast<int>(message.size())) .ToLocalChecked())); } } // namespace #ifdef V8_ENABLE_MEMORY_CORRUPTION_API namespace { // Sandbox.base void SandboxGetBase(const v8::FunctionCallbackInfo<v8::Value>& info) { DCHECK(ValidateCallbackInfo(info)); v8::Isolate* isolate = info.GetIsolate(); double sandbox_base = Sandbox::current()->base(); info.GetReturnValue().Set(v8::Number::New(isolate, sandbox_base)); } // Sandbox.byteLength void SandboxGetByteLength(const v8::FunctionCallbackInfo<v8::Value>& info) { DCHECK(ValidateCallbackInfo(info)); v8::Isolate* isolate = info.GetIsolate(); double sandbox_size = Sandbox::current()->size(); info.GetReturnValue().Set(v8::Number::New(isolate, sandbox_size)); } // new Sandbox.MemoryView(info) -> Sandbox.MemoryView 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 = Sandbox::current(); 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)); } // The methods below either take a HeapObject or the address of a HeapObject as // argument. These helper functions can be used to extract the argument object // in both cases. 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 = Sandbox::current(); 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(); // Allow tagged addresses by removing the kHeapObjectTag and // kWeakHeapObjectTag. This allows clients to just read tagged pointers from // the heap and use them for these APIs. address &= ~kHeapObjectTagMask; *out = HeapObject::FromAddress(sandbox->base() + address); return true; } // Sandbox.getAddressOf(Object) -> Number void SandboxGetAddressOf(const v8::FunctionCallbackInfo<v8::Value>& info) { DCHECK(ValidateCallbackInfo(info)); v8::Isolate* isolate = info.GetIsolate(); Tagged<HeapObject> obj; if (!GetArgumentObjectPassedAsReference(info, &obj)) { 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>(obj->address()); info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, address)); } // Sandbox.getObjectAt(Number) -> Object 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)); } // Sandbox.isValidObjectAt(Address) -> Bool void SandboxIsValidObjectAt(const v8::FunctionCallbackInfo<v8::Value>& info) { DCHECK(ValidateCallbackInfo(info)); v8::Isolate* isolate = info.GetIsolate(); Sandbox* sandbox = Sandbox::current(); Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap(); auto IsLocatedInMappedMemory = [&](Address address) { if (heap->memory_allocator()->LookupChunkContainingAddress(address) != nullptr) { return true; } return heap->read_only_space()->ContainsSlow(address); }; Tagged<HeapObject> obj; if (!GetArgumentObjectPassedAsAddress(info, &obj)) { return; } // Simple heuristic: follow the Map chain three times until we find a MetaMap // (where the map pointer points to itself), or give up. info.GetReturnValue().Set(false); Address current = obj.address(); for (int i = 0; i < 3; i++) { if (!IsLocatedInMappedMemory(current)) { return; } uint32_t map_word = *reinterpret_cast<uint32_t*>(current); if ((map_word & kHeapObjectTag) != kHeapObjectTag) { return; } Address map_address = sandbox->base() + map_word - kHeapObjectTag; if (map_address == current) { info.GetReturnValue().Set(true); return; } current = map_address; } } static void SandboxIsWritableImpl( const v8::FunctionCallbackInfo<v8::Value>& info, ArgumentObjectExtractorFunction getArgumentObject) { DCHECK(ValidateCallbackInfo(info)); Tagged<HeapObject> obj; if (!getArgumentObject(info, &obj)) { return; } auto* page = BasePage::FromHeapObject( reinterpret_cast<Isolate*>(info.GetIsolate()), obj); bool is_writable = page->IsWritable(); info.GetReturnValue().Set(is_writable); } // Sandbox.isWritable(Object) -> Bool void SandboxIsWritable(const v8::FunctionCallbackInfo<v8::Value>& info) { SandboxIsWritableImpl(info, &GetArgumentObjectPassedAsReference); } // Sandbox.isWritableObjectAt(Number) -> Bool 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); } // Sandbox.getSizeOf(Object) -> Number void SandboxGetSizeOf(const v8::FunctionCallbackInfo<v8::Value>& info) { SandboxGetSizeOfImpl(info, &GetArgumentObjectPassedAsReference); } // Sandbox.getSizeOfObjectAt(Number) -> Number 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()); } // Sandbox.getInstanceTypeOf(Object) -> String void SandboxGetInstanceTypeOf(const v8::FunctionCallbackInfo<v8::Value>& info) { SandboxGetInstanceTypeOfImpl(info, &GetArgumentObjectPassedAsReference); } // Sandbox.getInstanceTypeOfObjectAt(Number) -> String 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) { // This can happen with corrupted objects. Canonicalize to a special // "unknown" instance type to indicate that this is an unknown type. const uint16_t kUnknownInstanceType = std::numeric_limits<uint16_t>::max(); type = static_cast<InstanceType>(kUnknownInstanceType); } info.GetReturnValue().Set(type); } // Sandbox.getInstanceTypeIdOf(Object) -> Number void SandboxGetInstanceTypeIdOf( const v8::FunctionCallbackInfo<v8::Value>& info) { SandboxGetInstanceTypeIdOfImpl(info, &GetArgumentObjectPassedAsReference); } // Sandbox.getInstanceTypeIdOfObjectAt(Number) -> Number void SandboxGetInstanceTypeIdOfObjectAt( const v8::FunctionCallbackInfo<v8::Value>& info) { SandboxGetInstanceTypeIdOfImpl(info, &GetArgumentObjectPassedAsAddress); } // Sandbox.getInstanceTypeIdFor(String) -> Number void SandboxGetInstanceTypeIdFor( const v8::FunctionCallbackInfo<v8::Value>& info) { DCHECK(ValidateCallbackInfo(info)); v8::Isolate* isolate = info.GetIsolate(); v8::String::Utf8Value type_name(isolate, info[0]); if (!*type_name) { isolate->ThrowError("First argument must be a string"); return; } auto& all_types = SandboxTesting::GetInstanceTypeMap(); if (all_types.find(*type_name) == all_types.end()) { isolate->ThrowError( "Unknown type name. If needed, add it in " "SandboxTesting::GetInstanceTypeMap"); return; } InstanceType type_id = all_types[*type_name]; info.GetReturnValue().Set(type_id); } // Obtain the offset of a field in an object. // // This can be used to obtain the offsets of internal object fields in order to // avoid hardcoding offsets into testcases. It basically makes the various // Foo::kBarOffset constants accessible from JavaScript. The main benefit of // that is that testcases continue to work if the field offset changes. // Additionally, if a field is removed, testcases that use it will fail and can // then be deleted if they are no longer useful. // // TODO(saelo): instead of this, consider adding an API like // `Sandbox.getTypeDescriptor(Number|String) -> Object` which, given an // instance type id or name, returns an object containing the offset constants // as properties as well as potentially other information such as the types of // the object's fields. // // Sandbox.getFieldOffset(Number, String) -> Number void SandboxGetFieldOffset(const v8::FunctionCallbackInfo<v8::Value>& info) { DCHECK(ValidateCallbackInfo(info)); v8::Isolate* isolate = info.GetIsolate(); Local<v8::Context> context = isolate->GetCurrentContext(); if (!info[0]->IsInt32()) { isolate->ThrowError("First argument must be an integer"); return; } int raw_type = info[0]->Int32Value(context).FromMaybe(-1); if (raw_type < FIRST_TYPE || raw_type > LAST_TYPE) { isolate->ThrowError("Invalid instance type"); return; } InstanceType instance_type = static_cast<InstanceType>(raw_type); v8::String::Utf8Value field_name(isolate, info[1]); if (!*field_name) { isolate->ThrowError("Second argument must be a string"); return; } if (std::optional<int> offset = SandboxTesting::GetFieldOffset(isolate, instance_type, *field_name)) { info.GetReturnValue().Set(offset.value()); } else { DCHECK(isolate->HasPendingException()); } } // Returns an array of all builtin names, index of the name is the builtin id. // // This can be used to determine the id of a specific builtin for use with // Sandbox.setFunctionCodeToBuiltin(). // // Sandbox.getBuiltinNames() -> Array[String] void SandboxGetBuiltinNames(const v8::FunctionCallbackInfo<v8::Value>& info) { DCHECK(ValidateCallbackInfo(info)); v8::Isolate* isolate = info.GetIsolate(); Local<v8::Context> context = isolate->GetCurrentContext(); Local<v8::Array> result = v8::Array::New(isolate, Builtins::kBuiltinCount); // Find a builtin with matching name. for (Builtin i = Builtins::kFirst; i <= Builtins::kLast; ++i) { Local<v8::String> name = v8::String::NewFromUtf8(isolate, Builtins::name(i)).ToLocalChecked(); CHECK(result->Set(context, static_cast<uint32_t>(i), name).FromJust()); } info.GetReturnValue().Set(result); } // Sets given function's code value to a given builtin's code object. // // This can be used to shortcut overwriting JSFunction's code in testcases. // // Sandbox.setFunctionCodeToBuiltin(Function, Number) -> Bool void SandboxSetFunctionCodeToBuiltin( const v8::FunctionCallbackInfo<v8::Value>& info) { DCHECK(ValidateCallbackInfo(info)); v8::Isolate* isolate = info.GetIsolate(); Local<v8::Context> context = isolate->GetCurrentContext(); if (!IsJSFunction(*v8::Utils::OpenHandle(*info[0]))) { isolate->ThrowError("First argument must be an function"); return; } if (!info[1]->IsInt32()) { isolate->ThrowError("Second argument must be an integer"); return; } int raw_builtin_id = info[1]->Int32Value(context).FromMaybe(-1); if (!Builtins::IsBuiltinId(raw_builtin_id)) { isolate->ThrowError("Invalid builtin id"); return; } Builtin builtin = static_cast<Builtin>(raw_builtin_id); auto function = Cast<JSFunction>(v8::Utils::OpenDirectHandle(*info[0])); Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); function->UpdateCode(i_isolate, i_isolate->builtins()->code(builtin)); info.GetReturnValue().Set(true); } // Corrupt one field of an object without setting up a memory view first. // // Sandbox.corruptObjectField(obj, offset, value); // is identical to // (new DataView(new Sandbox.MemoryView(0, 0x100000000))).setUint32( // Sandbox.getAddressOf(obj) + offset, value << kSmiTagSize, true); // // (note the Smi tagging and little endianness) // // Alternatively, a field name can be passed as the second argument; the effect // is identical to calling `Sandbox.getFieldOffset(getInstanceTypeIdOf(obj, // offset))` on that argument first. // // Sandbox.corruptObjectField(Object, Number, Number) -> undefined // Sandbox.corruptObjectField(Object, String, Number) -> undefined void SandboxCorruptObjectField( const v8::FunctionCallbackInfo<v8::Value>& info) { DCHECK(ValidateCallbackInfo(info)); v8::Isolate* isolate = info.GetIsolate(); Local<v8::Context> context = isolate->GetCurrentContext(); Tagged<HeapObject> obj; if (!GetArgumentObjectPassedAsReference(info, &obj)) return; int offset; int value; if (!info[1]->IsInt32() || !info[1]->Int32Value(context).To(&offset)) { v8::String::Utf8Value field_name(isolate, info[1]); if (!*field_name) { isolate->ThrowError("Second argument must be an integer or a string"); return; } InstanceType instance_type = obj->map()->instance_type(); if (std::optional<int> offset_from_name = SandboxTesting::GetFieldOffset( isolate, instance_type, *field_name)) { offset = offset_from_name.value(); } else { DCHECK(isolate->HasPendingException()); return; } } int object_size = obj->Size(); DCHECK_EQ(0, object_size % kTaggedSize); if (offset < 0 || offset >= object_size) { std::ostringstream error; error << "Second argument (offset=" << offset << ") is " << "out of bounds of the given object of size " << object_size; ThrowTypeError(isolate, error.view()); return; } if ((offset % kTaggedSize) != 0) { std::ostringstream error; error << "Second argument (offset=" << offset << ") is " << "not tagged-size-aligned"; ThrowTypeError(isolate, error.view()); return; } if (!info[2]->IsInt32() || !info[2]->Int32Value(context).To(&value)) { isolate->ThrowError("Third argument must be an integer"); return; } obj->WriteField(offset, value); } 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(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); } } // namespace void SandboxTesting::InstallMemoryCorruptionApi(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 CHECK(Sandbox::current()->is_initialized()); // Create the special Sandbox object that provides read/write access to the // sandbox address space alongside other miscellaneous functionality. 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, SandboxGetInstanceTypeIdFor, "getInstanceTypeIdFor", 1); InstallFunction(isolate, sandbox, SandboxGetFieldOffset, "getFieldOffset", 2); InstallFunction(isolate, sandbox, SandboxGetBuiltinNames, "getBuiltinNames", 0); InstallFunction(isolate, sandbox, SandboxSetFunctionCodeToBuiltin, "setFunctionCodeToBuiltin", 2); InstallFunction(isolate, sandbox, SandboxCorruptObjectField, "corruptObjectField", 3); // Install the Sandbox object as property on the global object. Handle<JSGlobalObject> global = isolate->global_object(); Handle<String> name = isolate->factory()->NewStringFromAsciiChecked("Sandbox"); JSObject::AddProperty(isolate, global, name, sandbox, DONT_ENUM); } namespace { struct SafeRegion { base::AddressRegion region; SandboxTesting::MemoryAccessTypes safe_access_types; }; base::LazyMutex g_safe_region_mutex = LAZY_MUTEX_INITIALIZER; std::vector<SafeRegion>& GetSafeRegions() { g_safe_region_mutex.Pointer()->AssertHeld(); static base::LeakyObject<std::vector<SafeRegion>> g_safe_regions; return *g_safe_regions.get(); } } // namespace void SandboxTesting::RegisterSafeMemoryRegion( Address start, size_t size, MemoryAccessTypes safe_access_types) { base::MutexGuard guard(g_safe_region_mutex.Pointer()); GetSafeRegions().push_back({{start, size}, safe_access_types}); } void SandboxTesting::UnregisterSafeMemoryRegion(Address start) { base::MutexGuard guard(g_safe_region_mutex.Pointer()); size_t num_removed = std::erase_if( GetSafeRegions(), [start](const auto& entry) { return entry.region.begin() == start; }); CHECK_EQ(num_removed, 1); } #endif // V8_ENABLE_MEMORY_CORRUPTION_API namespace { #ifdef V8_OS_LINUX void PrintToStderr(const char* output) { // NOTE: This code MUST be async-signal safe. // NO malloc or stdio is allowed here. ssize_t return_val = write(STDERR_FILENO, output, strlen(output)); USE(return_val); } using MemoryAccessType = SandboxTesting::MemoryAccessType; MemoryAccessType GetAccessType(void* context) { #ifdef V8_HOST_ARCH_X64 ucontext_t* ctx = reinterpret_cast<ucontext_t*>(context); // See the X86-64 architecture manual for the error code bits. static constexpr greg_t kWriteAccessBit = 1; static constexpr greg_t kInstructionFetchBit = 4; const greg_t err = ctx->uc_mcontext.gregs[REG_ERR]; if (err & (1 << kWriteAccessBit)) return MemoryAccessType::kWrite; if (err & (1 << kInstructionFetchBit)) return MemoryAccessType::kExecute; return MemoryAccessType::kRead; #else // V8_HOST_ARCH_X64 // Conservatively assume it's a write. return MemoryAccessType::kWrite; #endif // V8_HOST_ARCH_X64 } bool IsCrashInSafeMemoryRegion(Address faultaddr, MemoryAccessType access_type) { #ifdef V8_ENABLE_MEMORY_CORRUPTION_API if (g_safe_region_mutex.Pointer()->TryLock()) { bool is_safe = false; for (const auto& entry : GetSafeRegions()) { if (entry.region.contains(faultaddr)) { is_safe = entry.safe_access_types.contains(access_type); break; } } g_safe_region_mutex.Pointer()->Unlock(); return is_safe; } #endif // If we don't have the known-safe memory regions (or cannot access them, // which is very unlikely), we need to rely on data from /proc/self/maps, // which is much less accurate. PrintToStderr( "Cannot check if faulting address lies inside a know-safe memory region. " "Falling back to generic checks. Results will be inaccurate\n"); // We never expect to fault on an instruction fetch. if (access_type == MemoryAccessType::kExecute) return false; base::SignalSafeMapsParser parser; if (!parser.IsValid()) { PrintToStderr( "Could not access /proc/self/maps so cannot determine if access " "violation is safe.\n"); return false; } while (auto entry = parser.Next()) { if (faultaddr >= entry->start && faultaddr < entry->end) { // With in-sandbox corruption it is possible to cause (safe) access // violations inside the pointer table memory mappings. Unfortunately, // these can be both PROT_NONE and PROT_READ mappings (as some table have // RO segments), so we need to treat both of these as safe. However, we // should never see access violations on non-anonymous mappings, so we // can check for that here. Anonymous mappings will either not have a // name/path at all, or it will be something like [anon:foo-bar]. if (entry->pathname[0] != '\0' && entry->pathname[0] != '[') return false; return entry->permissions == PagePermissions::kNoAccess || entry->permissions == PagePermissions::kRead; } } PrintToStderr( "Could not find faulting address in /proc/self/maps so cannot " "determine if access violation is safe.\n"); return false; } [[noreturn]] void FilterCrash(const char* reason) { // NOTE: This code MUST be async-signal safe. // NO malloc or stdio is allowed here. PrintToStderr(reason); PrintToStderr(" Exiting process...\n"); // In sandbox fuzzing mode, we want to exit with a non-zero status to // indicate to the fuzzer that the sample "failed" (ran into an unrecoverable // error) and should probably not be mutated further. Otherwise, we exit with // zero, which is for example needed for regression tests to make them "pass" // when no sandbox violation is detected. int status = SandboxTesting::mode() == SandboxTesting::Mode::kForFuzzing ? -1 : 0; _exit(status); } struct sigaction g_old_handlers[NSIG]; constexpr int kSignalsToHandle[] = {SIGABRT, SIGTRAP, SIGBUS, SIGILL, SIGSEGV}; void UninstallCrashFilter() { // NOTE: This code MUST be async-signal safe. // NO malloc or stdio is allowed here. // It's important that we always restore all signal handlers. For example, if // we forward a SIGSEGV to Asan's signal handler, that signal handler may // terminate the process with SIGABRT, which we must then *not* ignore. // // Should any of the sigaction calls below ever fail, the default signal // handler will be invoked (due to SA_RESETHAND) and will terminate the // process, so there's no need to attempt to handle that condition. for (int signal : kSignalsToHandle) { sigaction(signal, &g_old_handlers[signal], nullptr); } // We should also uninstall the sanitizer death callback as our crash filter // may hand a crash over to sanitizers, which should then not enter our crash // filtering logic a second time. #ifdef V8_USE_ANY_SANITIZER __sanitizer_set_death_callback(nullptr); #endif // V8_USE_ANY_SANITIZER } void ForwardToOldHandler(int signal, siginfo_t* info, void* context) { if (g_old_handlers[signal].sa_flags & SA_SIGINFO) { g_old_handlers[signal].sa_sigaction(signal, info, context); } else { auto handler = g_old_handlers[signal].sa_handler; if (handler != SIG_DFL && handler != SIG_IGN) { handler(signal); } else { // In this case we simply return, let the faulting instruction re-trigger // the crash, and then let the kernel handle the signal appropriately. return; } } } // Signal handler to check if a crash represents a sandbox violation or is a // safe crash (e.g. because an access violation happened inside the sandbox). void CrashFilter(int signal, siginfo_t* info, void* context) { // NOTE: This code MUST be async-signal safe. // NO malloc or stdio is allowed here. #if V8_HAS_PKU_SUPPORT base::MemoryProtectionKey::SetDefaultPermissionsForAllKeysInSignalHandler(); #endif // V8_HAS_PKU_SUPPORT Address faultaddr = reinterpret_cast<Address>(info->si_addr); // Conservatively assume we're dealing with a write access. We'll determine // the actual access type when we know the type of signal we caught. MemoryAccessType access_type = MemoryAccessType::kWrite; switch (signal) { case SIGABRT: // SIGABRT often indicates a failed CHECK or similar, which is harmless. FilterCrash("Caught harmless signal (SIGABRT)."); case SIGTRAP: // Similarly, SIGTRAP may for example indicate UNREACHABLE code. FilterCrash("Caught harmless signal (SIGTRAP)."); case SIGILL: { access_type = MemoryAccessType::kExecute; // In the case of SIGILL, faultaddr will point to the faulting // instruction. // // In general, SIGILL can be caused by either: // * A release-mode assertion fail (e.g. __builtin_unreachable()) for // which the compiler generates a `udX` instruction. This is harmless. // * A bug causing us to execute random invalid machine code. This is bad. // // Here we try to detect which of these cases happened by looking at the // faulting instruction. This is a little sketchy as the read could fail. // However, the CPU has just attempted to execute the instruction, so it // _should_ be readable. If we ever see segfaults here, we could change it // to a "safe" read by e.g. using pipes and the read/write syscalls. #ifdef V8_HOST_ARCH_X64 uint8_t* code = reinterpret_cast<uint8_t*>(faultaddr); // "ud1" is 0x0f 0xb9 [...]. if (code[0] == 0x0f && code[1] == 0xb9) { FilterCrash("Caught harmless signal (SIGILL caused by ud1)."); } // "ud1" with any prefix (e.g. 0x67) is [Prefix] 0x0f 0xb9 [...]. if (code[1] == 0x0f && code[2] == 0xb9) { FilterCrash("Caught harmless signal (SIGILL caused by ud1)."); } // "ud2" is 0x0f 0x0b. if (code[0] == 0x0f && code[1] == 0x0b) { FilterCrash("Caught harmless signal (SIGILL caused by ud2)."); } #else PrintToStderr( "Cannot check for harmless SIGILL on this architecture. Please " "implement support for it.\n"); #endif // V8_HOST_ARCH_X64 break; } case SIGBUS: case SIGSEGV: { access_type = GetAccessType(context); // If we want to, we could merge most of the checks below (apart from the // check for non-canonical addresses) into the safe-region check. // However, the way they currently are is a bit more explicit and makes // it very clear what we consider to be "safe" crashes. if (Sandbox::current()->Contains(faultaddr)) { FilterCrash( "Caught harmless memory access violation (inside sandbox)."); } if (info->si_code == SI_KERNEL && faultaddr == 0) { // This combination appears to indicate a crash at a non-canonical // address on Linux. Crashes at non-canonical addresses are for example // caused by failed external pointer type checks. Memory accesses that // _always_ land at a non-canonical address are not exploitable and so // these are filtered out here. However, testcases need to be written // with this in mind and must cause crashes at valid addresses. FilterCrash( "Caught harmless memory access violation (non-canonical address)."); } if (faultaddr >= 0x8000'0000'0000'0000ULL) { // On Linux, it appears that the kernel will still report valid (i.e. // canonical) kernel space addresses via the si_addr field, so we need // to handle these separately. We've already filtered out non-canonical // addresses above, so here we can just test if the most-significant bit // of the address is set, and if so assume that it's a kernel address. FilterCrash( "Caught harmless memory access violation (kernel space address)."); } if (faultaddr < 0x1000) { // Nullptr dereferences are harmless as nothing can be mapped there. We // use the typical page size (which is also the default value of // mmap_min_addr on Linux) to determine what counts as a nullptr // dereference here. FilterCrash( "Caught harmless memory access violation (nullptr dereference)."); } size_t padding = 1 * MB; if (faultaddr < 4ULL * GB + padding) { // Currently we also ignore access violations in the first 4GB of the // virtual address space. See crbug.com/1470641 for more details. // We need to add some "padding" to the 4GB since we might access a // pointer that's < 4GB at an offset that makes the final address go // slightly above 4GB. FilterCrash( "Caught harmless memory access violation (first 4GB of virtual " "address space)."); } if (IsCrashInSafeMemoryRegion(faultaddr, access_type)) { // There are a number of other memory regions where crashes are safe // (and can be caused with in-sandbox corruption). Examples include the // various pointer tables, where a (safe) OOB crash can be triggered by // corrupting an in-sandbox table handle. These regions are registered // with the crash filter and if we get here, we crashed in one of them. // TODO(42202821): consider printing more information here, for example // the address range and the name of the safe mapping. FilterCrash("Caught harmless memory access violation (safe region)."); } // Stack overflow detection. // // On Linux, we generally have two types of stacks: // 1. The main thread's stack, allocated by the kernel, and // 2. The stacks of any other thread, allocated by the application // // These stacks differ in some ways, and that affects the way stack // overflows (caused e.g. by unbounded recursion) materialize: for (1) the // kernel will use a "gap" region below the stack segment, i.e. an // unmapped area into which the kernel itself will not place any mappings // and into which the stack cannot grow. A stack overflow therefore // crashes with a SEGV_MAPERR. On the other hand, for (2) the application // is responsible for allocating the stack and therefore also for // allocating any guard regions around it. As these guard regions must be // regular mappings (with PROT_NONE), a stack overflow will crash with a // SEGV_ACCERR. // // It's relatively hard to reliably and accurately detect stack overflow, // so here we use a simple heuristic: did we crash on any kind of access // violation on an address just below the current thread's stack region. // This may cause both false positives (e.g. an access not through the // stack pointer register that happens to also land just below the stack) // and false negatives (e.g. a stack overflow on the main thread that // "jumps over" the first page of the gap region), but is probably good // enough in practice. pthread_attr_t attr; int pthread_error = pthread_getattr_np(pthread_self(), &attr); if (!pthread_error) { uintptr_t stack_base; size_t stack_size; pthread_error = pthread_attr_getstack( &attr, reinterpret_cast<void**>(&stack_base), &stack_size); // The main thread's stack on Linux typically has a fairly large gap // region (1MB by default), but other thread's stacks usually have // smaller guard regions so here we're conservative and assume that the // guard region consists only of a single page. const size_t kMinStackGuardRegionSize = sysconf(_SC_PAGESIZE); uintptr_t stack_guard_region_start = stack_base - kMinStackGuardRegionSize; uintptr_t stack_guard_region_end = stack_base; if (!pthread_error && stack_guard_region_start <= faultaddr && faultaddr < stack_guard_region_end) { FilterCrash("Caught harmless stack overflow."); } } break; } default: // This might happen if we add support for more signals, so report this // as a sandbox violation so it gets looked into. PrintToStderr("Unhandled signal"); break; } // If we get here, we've detected a sandbox violation. PrintToStderr("\n## V8 sandbox violation detected!\n\n"); if (access_type == MemoryAccessType::kRead) { PrintToStderr( "The sandbox violation was a *read* access which is technically not a " "sandbox violation. This requires manual investigation.\n"); } // Restore the original signal handlers now so we don't get invoked again. // For example, the next signal handler in the chain (e.g. the ASAN handler) // might decide to terminate the process with abort() and so we must not be // catching (and ignoring) SIGABRT from now on. UninstallCrashFilter(); // Forward to the previous handler. We could also just return now and let the // application crash again (assuming we return to the same, crashing // instruction). However, that doesn't work in combination with sandbox // hardware support: ASAN's crash handler will not know about the PKEY // permissions and that it must be doing something equivalent to // SetDefaultPermissionsForAllKeysInSignalHandler() so it would quickly crash // when e.g. trying to unwind the crashing thread's stack. ForwardToOldHandler(signal, info, context); // The old handler might itself decide to return to the faulting instruction // (after uninstalling itself), so we need to allow for that. } #ifdef V8_USE_ANY_SANITIZER void SanitizerFaultHandler() { #ifdef V8_USE_ADDRESS_SANITIZER if (__asan_report_present()) { 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."); } MemoryAccessType access_type = __asan_get_report_access_type() == 0 ? MemoryAccessType::kRead : MemoryAccessType::kWrite; if (IsCrashInSafeMemoryRegion(faultaddr, access_type)) { FilterCrash("Caught harmless ASan fault (inside safe region)."); } } #endif // V8_USE_ADDRESS_SANITIZER // Sanitizers may report the failure via abort(), so we should also restore // the original signal handlers here. UninstallCrashFilter(); // In case of a sanitizer issue we opt for conservatively reporting a sandbox // violation that needs to be investigated. PrintToStderr("\n## V8 sandbox violation detected!\n\n"); } #endif // V8_USE_ANY_SANITIZER void InstallCrashFilter() { // Register an alternate stack for signal delivery so that signal handlers // can run properly even if for example the stack pointer has been corrupted // or the stack has overflowed. // Note that the alternate stack is currently only registered for the main // thread. Stack pointer corruption or stack overflows on background threads // may therefore still cause the signal handler to crash. base::OS::EnsureAlternativeSignalStackIsAvailableForCurrentThread(); struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_flags = SA_SIGINFO | SA_ONSTACK; action.sa_sigaction = &CrashFilter; sigemptyset(&action.sa_mask); bool success = true; for (int signal : kSignalsToHandle) { success &= (sigaction(signal, &action, &g_old_handlers[signal]) == 0); } CHECK(success); #ifdef V8_USE_ANY_SANITIZER // We install sanitizer specific crash handlers. These can only check for // in-sandbox crashes on certain configurations. // // The crash handler also resets the signal handler as sanitizer may use // `abort()` via `abort_on_error=1` option to signal problems. __sanitizer_set_death_callback(&SanitizerFaultHandler); #endif // V8_USE_ANY_SANITIZER } #endif // V8_OS_LINUX } // namespace void SandboxTesting::Enable(Mode mode) { CHECK_EQ(mode_, Mode::kDisabled); CHECK_NE(mode, Mode::kDisabled); CHECK(Sandbox::current()->is_initialized()); mode_ = mode; fprintf(stderr, "Sandbox testing mode is enabled. Only sandbox violations will be " "reported, all other crashes will be ignored.\n"); fprintf(stderr, "Sandbox bounds: [%p,%p)\n", reinterpret_cast<void*>(Sandbox::current()->base()), reinterpret_cast<void*>(Sandbox::current()->end())); #ifdef V8_OS_LINUX InstallCrashFilter(); #else FATAL("The sandbox crash filter is currently only available on Linux"); #endif // V8_OS_LINUX } void SandboxTesting::Disable() { if (mode_ == Mode::kDisabled) return; mode_ = Mode::kDisabled; #ifdef V8_OS_LINUX UninstallCrashFilter(); #else FATAL("The sandbox crash filter is currently only available on Linux"); #endif // V8_OS_LINUX } SandboxTesting::InstanceTypeMap& SandboxTesting::GetInstanceTypeMap() { // This mechanism is currently very crude and needs to be manually maintained // and extended (e.g. when adding a js test for the sandbox). In the future, // it would be nice to somehow automatically generate this map from the // object definitions and also support the class inheritance hierarchy. static base::LeakyObject<InstanceTypeMap> g_known_instance_types; auto& types = *g_known_instance_types.get(); bool is_initialized = types.size() != 0; if (!is_initialized) { types["JS_OBJECT_TYPE"] = JS_OBJECT_TYPE; types["JS_FUNCTION_TYPE"] = JS_FUNCTION_TYPE; types["JS_ARRAY_TYPE"] = JS_ARRAY_TYPE; types["JS_ARRAY_BUFFER_TYPE"] = JS_ARRAY_BUFFER_TYPE; types["JS_TYPED_ARRAY_TYPE"] = JS_TYPED_ARRAY_TYPE; types["SEQ_ONE_BYTE_STRING_TYPE"] = SEQ_ONE_BYTE_STRING_TYPE; types["SEQ_TWO_BYTE_STRING_TYPE"] = SEQ_TWO_BYTE_STRING_TYPE; types["INTERNALIZED_ONE_BYTE_STRING_TYPE"] = INTERNALIZED_ONE_BYTE_STRING_TYPE; types["SLICED_ONE_BYTE_STRING_TYPE"] = SLICED_ONE_BYTE_STRING_TYPE; types["CONS_ONE_BYTE_STRING_TYPE"] = CONS_ONE_BYTE_STRING_TYPE; types["SHARED_FUNCTION_INFO_TYPE"] = SHARED_FUNCTION_INFO_TYPE; types["SCRIPT_TYPE"] = SCRIPT_TYPE; types["JS_PROMISE_TYPE"] = JS_PROMISE_TYPE; types["PROMISE_REACTION"] = PROMISE_REACTION_TYPE; types["JS_FUNCTION"] = JS_FUNCTION_TYPE; types["SHARED_FUNCTION_INFO"] = SHARED_FUNCTION_INFO_TYPE; #ifdef V8_ENABLE_WEBASSEMBLY types["WASM_MODULE_OBJECT_TYPE"] = WASM_MODULE_OBJECT_TYPE; types["WASM_INSTANCE_OBJECT_TYPE"] = WASM_INSTANCE_OBJECT_TYPE; types["WASM_FUNC_REF_TYPE"] = WASM_FUNC_REF_TYPE; types["WASM_TABLE_OBJECT_TYPE"] = WASM_TABLE_OBJECT_TYPE; types["WASM_RESUME_DATA"] = WASM_RESUME_DATA_TYPE; #endif // V8_ENABLE_WEBASSEMBLY } return types; } SandboxTesting::FieldOffsetMap& SandboxTesting::GetFieldOffsetMap() { // This mechanism is currently very crude and needs to be manually maintained // and extended (e.g. when adding a js test for the sandbox). In the future, // it would be nice to somehow automatically generate this map from the // object definitions and also support the class inheritance hierarchy. static base::LeakyObject<FieldOffsetMap> g_known_fields; auto& fields = *g_known_fields.get(); bool is_initialized = fields.size() != 0; if (!is_initialized) { fields[JS_FUNCTION_TYPE]["dispatch_handle"] = JSFunction::kDispatchHandleOffset; fields[JS_FUNCTION_TYPE]["shared_function_info"] = JSFunction::kSharedFunctionInfoOffset; fields[JS_ARRAY_TYPE]["elements"] = JSArray::kElementsOffset; fields[JS_ARRAY_TYPE]["length"] = JSArray::kLengthOffset; fields[JS_TYPED_ARRAY_TYPE]["byte_length"] = JSTypedArray::kRawByteLengthOffset; fields[JS_TYPED_ARRAY_TYPE]["byte_offset"] = JSTypedArray::kRawByteOffsetOffset; fields[JS_TYPED_ARRAY_TYPE]["external_pointer"] = JSTypedArray::kExternalPointerOffset; fields[JS_TYPED_ARRAY_TYPE]["base_pointer"] = JSTypedArray::kBasePointerOffset; for (std::underlying_type_t<InstanceType> string_type = FIRST_STRING_TYPE; string_type <= LAST_STRING_TYPE; ++string_type) { InstanceType instance_type = static_cast<InstanceType>(string_type); fields[instance_type]["length"] = offsetof(String, length_); fields[instance_type]["hash"] = offsetof(String, raw_hash_field_); } fields[SLICED_ONE_BYTE_STRING_TYPE]["parent"] = offsetof(SlicedString, parent_); fields[CONS_ONE_BYTE_STRING_TYPE]["first"] = offsetof(ConsString, first_); fields[CONS_ONE_BYTE_STRING_TYPE]["second"] = offsetof(ConsString, second_); fields[SHARED_FUNCTION_INFO_TYPE]["trusted_function_data"] = SharedFunctionInfo::kTrustedFunctionDataOffset; fields[SHARED_FUNCTION_INFO_TYPE]["length"] = SharedFunctionInfo::kLengthOffset; fields[SHARED_FUNCTION_INFO_TYPE]["formal_parameter_count"] = SharedFunctionInfo::kFormalParameterCountOffset; fields[SHARED_FUNCTION_INFO_TYPE]["function_data"] = SharedFunctionInfo::kUntrustedFunctionDataOffset; fields[SHARED_FUNCTION_INFO_TYPE]["script"] = SharedFunctionInfo::kScriptOffset; fields[SCRIPT_TYPE]["wasm_managed_native_module"] = Script::kEvalFromPositionOffset; fields[JS_PROMISE_TYPE]["reactions_or_result"] = JSPromise::kReactionsOrResultOffset; fields[PROMISE_REACTION_TYPE]["fulfill_handler"] = offsetof(PromiseReaction, fulfill_handler_); fields[JS_FUNCTION_TYPE]["shared_function_info"] = JSFunction::kSharedFunctionInfoOffset; #ifdef V8_INTL_SUPPORT fields[JS_SEGMENTS_TYPE]["unicode_string"] = JSSegments::kUnicodeStringOffset; #endif // V8_INTL_SUPPORT #ifdef V8_ENABLE_WEBASSEMBLY fields[WASM_MODULE_OBJECT_TYPE]["managed_native_module"] = WasmModuleObject::kManagedNativeModuleOffset; fields[WASM_MODULE_OBJECT_TYPE]["script"] = WasmModuleObject::kScriptOffset; fields[WASM_INSTANCE_OBJECT_TYPE]["module_object"] = WasmInstanceObject::kModuleObjectOffset; fields[WASM_FUNC_REF_TYPE]["trusted_internal"] = WasmFuncRef::kTrustedInternalOffset; fields[WASM_TABLE_OBJECT_TYPE]["entries"] = WasmTableObject::kEntriesOffset; fields[WASM_TABLE_OBJECT_TYPE]["current_length"] = WasmTableObject::kCurrentLengthOffset; fields[WASM_TABLE_OBJECT_TYPE]["maximum_length"] = WasmTableObject::kMaximumLengthOffset; fields[WASM_TABLE_OBJECT_TYPE]["raw_type"] = WasmTableObject::kRawTypeOffset; fields[WASM_RESUME_DATA_TYPE]["trusted_suspender"] = WasmResumeData::kTrustedSuspenderOffset; fields[WASM_GLOBAL_OBJECT_TYPE]["raw_type"] = WasmGlobalObject::kRawTypeOffset; #endif // V8_ENABLE_WEBASSEMBLY } return fields; } std::optional<int> SandboxTesting::GetFieldOffset( v8::Isolate* isolate_for_errors, InstanceType instance_type, const std::string& field_name) { SandboxTesting::FieldOffsetMap& all_fields = SandboxTesting::GetFieldOffsetMap(); auto fields_it = all_fields.find(instance_type); if (fields_it == all_fields.end()) { std::ostringstream error; error << "Unknown object type \"" << ToString(instance_type) << "\". If needed, add it in SandboxTesting::GetFieldOffsetMap"; ThrowTypeError(isolate_for_errors, error.view()); return std::nullopt; } SandboxTesting::FieldOffsets& obj_fields = fields_it->second; auto offset_it = obj_fields.find(field_name); if (offset_it == obj_fields.end()) { std::ostringstream error; error << "Unknown field \"" << field_name << "\" of instance type " << ToString(instance_type) << ". If needed, add it in SandboxTesting::GetFieldOffsetMap"; ThrowTypeError(isolate_for_errors, error.view()); return std::nullopt; } return offset_it->second; } #endif // V8_ENABLE_SANDBOX } // namespace internal } // namespace v8