// Copyright 2020 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_EXTERNAL_POINTER_TABLE_H_ #define V8_SANDBOX_EXTERNAL_POINTER_TABLE_H_ #include "include/v8config.h" #include "src/base/atomicops.h" #include "src/base/memory.h" #include "src/base/platform/mutex.h" #include "src/common/globals.h" #ifdef V8_SANDBOX_IS_AVAILABLE namespace v8 { namespace internal { class Isolate; /** * A table storing pointers to objects outside the sandbox. * * An external pointer table provides the basic mechanisms to ensure * memory-safe access to objects located outside the sandbox, but referenced * from within it. When an external pointer table is used, objects located * inside the sandbox reference outside objects through indices into the table. * * Type safety can be ensured by using type-specific tags for the external * pointers. These tags will be ORed into the unused top bits of the pointer * when storing them and will be ANDed away when loading the pointer later * again. If a pointer of the wrong type is accessed, some of the top bits will * remain in place, rendering the pointer inaccessible. * * Temporal memory safety is achieved through garbage collection of the table, * which ensures that every entry is either an invalid pointer or a valid * pointer pointing to a live object. * * Spatial memory safety can, if necessary, be ensured by storing the size of a * referenced object together with the object itself outside the sandbox, and * referencing both through a single entry in the table. * * The garbage collection algorithm for the table works as follows: * - The top bit of every entry is reserved for the marking bit. * - Every store to an entry automatically sets the marking bit when ORing * with the tag. This avoids the need for write barriers. * - Every load of an entry automatically removes the marking bit when ANDing * with the inverted tag. * - When the GC marking visitor finds a live object with an external pointer, * it marks the corresponding entry as alive through Mark(), which sets the * marking bit using an atomic CAS operation. * - When marking is finished, Sweep() iterates of the table once while the * mutator is stopped and builds a freelist from all dead entries while also * removing the marking bit from any live entry. * * The freelist is a singly-linked list, using the lower 32 bits of each entry * to store the index of the next free entry. When the freelist is empty and a * new entry is allocated, the table grows in place and the freelist is * re-populated from the newly added entries. */ class V8_EXPORT_PRIVATE ExternalPointerTable { public: // Size of an ExternalPointerTable, for layout computation in IsolateData. // Asserted to be equal to the actual size in external-pointer-table.cc. static int constexpr kSize = 3 * kSystemPointerSize; ExternalPointerTable() = default; // Initializes this external pointer table by reserving the backing memory // and initializing the freelist. inline void Init(Isolate* isolate); // Resets this external pointer table and deletes all associated memory. inline void TearDown(); // Retrieves the entry at the given index. // // This method is atomic and can be called from background threads. inline Address Get(uint32_t index, ExternalPointerTag tag) const; // Sets the entry at the given index to the given value. // // This method is atomic and can be called from background threads. inline void Set(uint32_t index, Address value, ExternalPointerTag tag); // Allocates a new entry in the external pointer table. The caller must // initialize the entry afterwards through set(). In particular, the caller is // responsible for setting the mark bit of the new entry. // TODO(saelo) this can fail, in which case we should probably do GC + retry. // // This method is atomic and can be called from background threads. inline uint32_t Allocate(); // Runtime function called from CSA. Internally just calls Allocate(). static uint32_t AllocateEntry(ExternalPointerTable* table); // Marks the specified entry as alive. // // This method is atomic and can be called from background threads. inline void Mark(uint32_t index); // Frees unmarked entries. // // This method must be called on the mutator thread or while that thread is // stopped. // // Returns the number of live entries after sweeping. uint32_t Sweep(Isolate* isolate); private: // Required for Isolate::CheckIsolateLayout(). friend class Isolate; // An external pointer table grows in blocks of this size. This is also the // initial size of the table. static const size_t kBlockSize = 64 * KB; static const size_t kEntriesPerBlock = kBlockSize / kSystemPointerSize; static const Address kExternalPointerMarkBit = 1ULL << 63; // Returns true if this external pointer table has been initialized. bool is_initialized() { return buffer_ != kNullAddress; } // Extends the table and adds newly created entries to the freelist. Returns // the new freelist head. When calling this method, mutex_ must be locked. // // TODO(saelo) this can fail, deal with that appropriately. uint32_t Grow(); // Computes the address of the specified entry. inline Address entry_address(uint32_t index) const { return buffer_ + index * sizeof(Address); } // Loads the value at the given index. This method is non-atomic, only use it // when no other threads can currently access the table. inline Address load(uint32_t index) const { return base::Memory
(entry_address(index)); } // Stores the provided value at the given index. This method is non-atomic, // only use it when no other threads can currently access the table. inline void store(uint32_t index, Address value) { base::Memory(entry_address(index)) = value; } // Atomically loads the value at the given index. inline Address load_atomic(uint32_t index) const { auto addr = reinterpret_cast