// 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_SECURITY_VM_CAGE_H_ #define V8_SECURITY_VM_CAGE_H_ #include "include/v8-internal.h" #include "src/base/bounded-page-allocator.h" #include "src/common/globals.h" namespace v8 { class PageAllocator; namespace internal { #ifdef V8_VIRTUAL_MEMORY_CAGE_IS_AVAILABLE /** * V8 Virtual Memory Cage. * * When the virtual memory cage is enabled, V8 will reserve a large region of * virtual address space - the cage - and place most of its objects inside of * it. This allows these objects to reference each other through offsets rather * than raw pointers, which in turn makes it harder for an attacker to abuse * them in an exploit. * * 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 virtual memory cage. The remainder of the cage is mostly used for memory * buffers, in particular ArrayBuffer backing stores and WASM memory cages. * * It should be assumed that an attacker is able to corrupt data arbitrarily * and concurrently inside the virtual memory cage. The heap sandbox, of which * the virtual memory cage is one building block, attempts to then stop an * attacker from corrupting data outside of the cage. * * As the embedder is responsible for providing ArrayBuffer allocators, v8 * exposes a page allocator for the virtual memory cage to the embedder. * * TODO(chromium:1218005) come up with a coherent naming scheme for this class * and the other "cages" in v8. */ class V8_EXPORT_PRIVATE V8VirtualMemoryCage { 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 caged objects. | (back) | // +- ~~~ -+----------------+----------------------- ~~~ -+- ~~~ -+ // ^ ^ // base base + size V8VirtualMemoryCage() = default; V8VirtualMemoryCage(const V8VirtualMemoryCage&) = delete; V8VirtualMemoryCage& operator=(V8VirtualMemoryCage&) = delete; bool Initialize(v8::PageAllocator* page_allocator); 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_fake_cage() const { return is_fake_cage_; } Address base() const { return base_; } size_t size() const { return size_; } v8::PageAllocator* page_allocator() const { return cage_page_allocator_.get(); } bool Contains(Address addr) const { return addr >= base_ && addr < base_ + size_; } bool Contains(void* ptr) const { return Contains(reinterpret_cast<Address>(ptr)); } private: // The SequentialUnmapperTest calls the private Initialize method to create a // cage without guard regions, which would otherwise consume too much memory. friend class SequentialUnmapperTest; // These tests call the private Initialize methods below. FRIEND_TEST(VirtualMemoryCageTest, InitializationWithSize); FRIEND_TEST(VirtualMemoryCageTest, InitializationAsFakeCage); FRIEND_TEST(VirtualMemoryCageTest, FakeCagePageAllocation); // We allow tests to disable the guard regions around the cage. 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. bool Initialize(v8::PageAllocator* page_allocator, size_t size, bool use_guard_regions); // Used on OSes where reserving virtual memory is too expensive. A fake cage // does not reserve all of the virtual memory and so doesn't have the desired // security properties. bool InitializeAsFakeCage(v8::PageAllocator* page_allocator, size_t size, size_t size_to_reserve); Address base_ = kNullAddress; size_t size_ = 0; // Base and size of the virtual memory reservation backing this cage. These // can be different from the cage base and size due to guard regions or when a // fake cage is used. Address reservation_base_ = kNullAddress; size_t reservation_size_ = 0; bool initialized_ = false; bool disabled_ = false; bool is_fake_cage_ = false; // The allocator through which the virtual memory of the cage was allocated. v8::PageAllocator* page_allocator_ = nullptr; // The allocator to allocate pages inside the cage. std::unique_ptr<v8::PageAllocator> cage_page_allocator_; }; #endif // V8_VIRTUAL_MEMORY_CAGE_IS_AVAILABLE #ifdef V8_VIRTUAL_MEMORY_CAGE // This function is only available when the cage is actually used. V8_EXPORT_PRIVATE V8VirtualMemoryCage* GetProcessWideVirtualMemoryCage(); #endif V8_INLINE bool IsValidBackingStorePointer(void* ptr) { #ifdef V8_VIRTUAL_MEMORY_CAGE Address addr = reinterpret_cast<Address>(ptr); return kAllowBackingStoresOutsideCage || addr == kNullAddress || GetProcessWideVirtualMemoryCage()->Contains(addr); #else return true; #endif } } // namespace internal } // namespace v8 #endif // V8_SECURITY_VM_CAGE_H_