// Copyright 2021 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_SANDBOX_H_ #define V8_SANDBOX_SANDBOX_H_ #include "include/v8-internal.h" #include "include/v8-platform.h" #include "include/v8config.h" #include "src/common/globals.h" #include "testing/gtest/include/gtest/gtest_prod.h" // nogncheck namespace v8 { namespace internal { #ifdef V8_SANDBOX_IS_AVAILABLE /** * The V8 Sandbox. * * When enabled, V8 reserves a large region of virtual address space - the * sandbox - and places most of its objects inside of it. It is then assumed * that an attacker can, by exploiting a vulnerability in V8, corrupt memory * inside the sandbox arbitrarily and from different threads. The sandbox * attempts to stop an attacker from corrupting other memory in the process. * * The sandbox relies on a number of different mechanisms to achieve its goal. * For example, objects inside the sandbox can reference each other through * offsets from the start of the sandbox ("sandboxed pointers") instead of raw * pointers, and external objects can be referenced through indices into a * per-Isolate table of external pointers ("sandboxed external pointers"). * * The pointer compression region, which contains most V8 objects, and inside * of which compressed (32-bit) pointers are used, is located at the start of * the sandbox. The remainder of the sandbox is mostly used for memory * buffers, in particular ArrayBuffer backing stores and WASM memory cages. * * As the embedder is responsible for providing ArrayBuffer allocators, V8 * exposes the virtual address space backing the sandbox to the embedder. */ class V8_EXPORT_PRIVATE Sandbox { public: // +- ~~~ -+---------------------------------------- ~~~ -+- ~~~ -+ // | 32 GB | (Ideally) 1 TB | 32 GB | // | | | | // | Guard | 4 GB : ArrayBuffer backing stores, | Guard | // | Region | V8 Heap : WASM memory buffers, and | Region | // | (front) | Region : any other sandboxed objects. | (back) | // +- ~~~ -+----------------+----------------------- ~~~ -+- ~~~ -+ // ^ ^ // base base + size Sandbox() = default; Sandbox(const Sandbox&) = delete; Sandbox& operator=(Sandbox&) = delete; bool Initialize(v8::VirtualAddressSpace* vas); void Disable() { CHECK(!initialized_); disabled_ = true; } void TearDown(); bool is_initialized() const { return initialized_; } bool is_disabled() const { return disabled_; } bool is_enabled() const { return !disabled_; } bool is_partially_reserved() const { return is_partially_reserved_; } Address base() const { return base_; } Address end() const { return end_; } size_t size() const { return size_; } Address base_address() const { return reinterpret_cast
(&base_); } Address end_address() const { return reinterpret_cast(&end_); } Address size_address() const { return reinterpret_cast(&size_); } v8::PageAllocator* page_allocator() const { return sandbox_page_allocator_.get(); } v8::VirtualAddressSpace* address_space() const { return address_space_.get(); } bool Contains(Address addr) const { return addr >= base_ && addr < base_ + size_; } bool Contains(void* ptr) const { return Contains(reinterpret_cast(ptr)); } #ifdef V8_SANDBOXED_POINTERS class SandboxedPointerConstants final { public: Address empty_backing_store_buffer() const { return empty_backing_store_buffer_; } Address empty_backing_store_buffer_address() const { return reinterpret_cast(&empty_backing_store_buffer_); } void set_empty_backing_store_buffer(Address value) { empty_backing_store_buffer_ = value; } void Reset() { empty_backing_store_buffer_ = 0; } private: Address empty_backing_store_buffer_ = 0; }; const SandboxedPointerConstants& constants() const { return constants_; } #endif private: // The SequentialUnmapperTest calls the private Initialize method to create a // sandbox without guard regions, which would consume too much memory. friend class SequentialUnmapperTest; // These tests call the private Initialize methods below. FRIEND_TEST(SandboxTest, InitializationWithSize); FRIEND_TEST(SandboxTest, PartiallyReservedSandboxInitialization); FRIEND_TEST(SandboxTest, PartiallyReservedSandboxPageAllocation); // We allow tests to disable the guard regions around the sandbox. This is // useful for example for tests like the SequentialUnmapperTest which track // page allocations and so would incur a large overhead from the guard // regions. The provided virtual address space must be able to allocate // subspaces. The size must be a multiple of the allocation granularity of the // virtual memory space. bool Initialize(v8::VirtualAddressSpace* vas, size_t size, bool use_guard_regions); // Used when reserving virtual memory is too expensive. A partially reserved // sandbox does not reserve all of its virtual memory and so doesn't have the // desired security properties as unrelated mappings could end up inside of // it and be corrupted. The size and size_to_reserve parameters must be // multiples of the allocation granularity of the virtual address space. bool InitializeAsPartiallyReservedSandbox(v8::VirtualAddressSpace* vas, size_t size, size_t size_to_reserve); // Initialize the constant objects for this sandbox. Called by the Initialize // methods above. void InitializeConstants(); Address base_ = kNullAddress; Address end_ = kNullAddress; size_t size_ = 0; // Base and size of the virtual memory reservation backing this sandbox. // These can be different from the sandbox base and size due to guard regions // or when a fake sandbox is used. Address reservation_base_ = kNullAddress; size_t reservation_size_ = 0; bool initialized_ = false; bool disabled_ = false; bool is_partially_reserved_ = false; // The virtual address subspace backing the sandbox. std::unique_ptr