#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/common/globals.h"
#include "src/sandbox/check.h"
#include "src/sandbox/compactible-external-entity-table.h"
#include "src/sandbox/tagged-payload.h"
#include "src/utils/allocation.h"
#ifdef V8_COMPRESS_POINTERS
namespace v8 {
namespace internal {
class Isolate;
class Counters;
class ReadOnlyArtifacts;
* The entries of an ExternalPointerTable.
*
* Each entry consists of a single pointer-sized word containing the external
* pointer, the marking bit, and a type tag. An entry can either be:
* - A "regular" entry, containing the external pointer together with a type
* tag and the marking bit in the unused upper bits, or
* - A freelist entry, tagged with the kExternalPointerFreeEntryTag and
* containing the index of the next free entry in the lower 32 bits, or
* - An evacuation entry, tagged with the kExternalPointerEvacuationEntryTag
* and containing the address of the ExternalPointerSlot referencing the
* entry that will be evacuated into this entry. See the compaction
* algorithm overview for more details about these entries.
*/
struct ExternalPointerTableEntry {
enum class EvacuateMarkMode { kTransferMark, kLeaveUnmarked, kClearMark };
inline void MakeExternalPointerEntry(Address value, ExternalPointerTag tag,
bool mark_as_alive);
inline Address GetExternalPointer(ExternalPointerTagRange tag_range) const;
inline void SetExternalPointer(Address value, ExternalPointerTag tag);
inline bool HasExternalPointer(ExternalPointerTagRange tag_range) const;
inline Address ExchangeExternalPointer(Address value, ExternalPointerTag tag);
inline ExternalPointerTag GetExternalPointerTag() const;
inline Address ExtractManagedResourceOrNull() const;
inline void MakeZappedEntry();
inline void MakeFreelistEntry(uint32_t next_entry_index);
inline uint32_t GetNextFreelistEntryIndex() const;
inline void MakeEvacuationEntry(Address handle_location);
inline bool HasEvacuationEntry() const;
inline void Evacuate(ExternalPointerTableEntry& dest, EvacuateMarkMode mode);
inline void CopyFrom(const ExternalPointerTableEntry& src);
inline void Mark();
static constexpr bool IsWriteProtected = false;
private:
friend class ExternalPointerTable;
friend class ExternalPointerTableEntryPrinter;
struct Payload {
Payload(Address pointer, ExternalPointerTag tag)
: encoded_word_(Tag(pointer, tag)) {}
static Address Tag(Address pointer, ExternalPointerTag tag) {
DCHECK_LE(tag, kLastExternalPointerTag);
return pointer | (static_cast<Address>(tag) << kExternalPointerTagShift);
}
static bool CheckTag(Address content, ExternalPointerTagRange tag_range) {
if (ExternalPointerCanBeEmpty(tag_range) && !content) {
return true;
}
ExternalPointerTag tag = static_cast<ExternalPointerTag>(
(content & kExternalPointerTagMask) >> kExternalPointerTagShift);
return tag_range.Contains(tag);
}
Address Untag(ExternalPointerTagRange tag_range) const {
Address content = encoded_word_;
SBXCHECK(CheckTag(content, tag_range));
return content & kExternalPointerPayloadMask;
}
Address Untag(ExternalPointerTag tag) const {
return Untag(ExternalPointerTagRange(tag, tag));
}
bool IsTaggedWithTagIn(ExternalPointerTagRange tag_range) const {
return CheckTag(encoded_word_, tag_range);
}
bool IsTaggedWith(ExternalPointerTag tag) const {
return IsTaggedWithTagIn(ExternalPointerTagRange(tag));
}
void SetMarkBit() { encoded_word_ |= kExternalPointerMarkBit; }
void ClearMarkBit() { encoded_word_ &= ~kExternalPointerMarkBit; }
bool HasMarkBitSet() const {
return encoded_word_ & kExternalPointerMarkBit;
}
uint32_t ExtractFreelistLink() const {
return static_cast<uint32_t>(encoded_word_);
}
ExternalPointerTag ExtractTag() const {
return static_cast<ExternalPointerTag>(
(encoded_word_ & kExternalPointerTagMask) >>
kExternalPointerTagShift);
}
bool ContainsFreelistLink() const {
return IsTaggedWith(kExternalPointerFreeEntryTag);
}
bool ContainsEvacuationEntry() const {
return IsTaggedWith(kExternalPointerEvacuationEntryTag);
}
Address ExtractEvacuationEntryHandleLocation() const {
return Untag(kExternalPointerEvacuationEntryTag);
}
bool ContainsPointer() const {
return !ContainsFreelistLink() && !ContainsEvacuationEntry();
}
bool operator==(Payload other) const {
return encoded_word_ == other.encoded_word_;
}
bool operator!=(Payload other) const {
return encoded_word_ != other.encoded_word_;
}
private:
Address encoded_word_;
};
inline Payload GetRawPayload() const {
return payload_.load(std::memory_order_relaxed);
}
inline void SetRawPayload(Payload new_payload) {
return payload_.store(new_payload, std::memory_order_relaxed);
}
inline void MaybeUpdateRawPointerForLSan(Address value) {
#if defined(LEAK_SANITIZER)
raw_pointer_for_lsan_ = value;
#endif
}
std::atomic<Payload> payload_;
#if defined(LEAK_SANITIZER)
Address raw_pointer_for_lsan_;
#endif
};
#if defined(LEAK_SANITIZER)
static_assert(sizeof(ExternalPointerTableEntry) == 16);
#else
static_assert(sizeof(ExternalPointerTableEntry) == 8);
#endif
* A table storing pointers to objects outside the V8 heap.
*
* When V8_ENABLE_SANDBOX, its primary use is for pointing to objects outside
* the sandbox, as described below.
* When V8_COMPRESS_POINTERS, external pointer tables are also used to ease
* alignment requirements in heap object fields via indirection.
*
* A table's role for the V8 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 either by storing the
* size of the referenced object together with the object itself outside the
* sandbox, or by storing both the pointer and the size in one (double-width)
* table entry.
*
* Table memory management:
* ------------------------
* The garbage collection algorithm works as follows:
* - One 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, SweepAndCompact() iterates over a Space once
* while the mutator is stopped and builds a freelist from all dead entries
* while also possibly clearing the marking bit from any live entry.
*
* Generational collection for tables:
* -----------------------------------
* Young-generation objects with external pointer slots allocate their
* ExternalPointerTable entries in a spatially partitioned young external
* pointer space. There are two different mechanisms:
* - When using the semi-space nursery, promoting an object evacuates its EPT
* entries to the old external pointer space.
* - For the in-place MinorMS nursery, possibly-concurrent marking populates
* the SURVIVOR_TO_EXTERNAL_POINTER remembered sets. In the pause, promoted
* objects use this remembered set to evacuate their EPT entries to the old
* external pointer space. Survivors have their EPT entries are left in
* place.
* In a full collection, segments from the young EPT space are eagerly promoted
* during the pause, leaving the young generation empty.
*
* Table compaction:
* -----------------
* Additionally, the external pointer table supports compaction.
* For details about the compaction algorithm see the
* CompactibleExternalEntityTable class.
*/
class V8_EXPORT_PRIVATE ExternalPointerTable
: public CompactibleExternalEntityTable<
ExternalPointerTableEntry, kExternalPointerTableReservationSize> {
using Base =
CompactibleExternalEntityTable<ExternalPointerTableEntry,
kExternalPointerTableReservationSize>;
#if defined(LEAK_SANITIZER)
static_assert(kMaxExternalPointers == kMaxCapacity * 2);
#else
static_assert(kMaxExternalPointers == kMaxCapacity);
#endif
static_assert(kSupportsCompaction);
public:
using EvacuateMarkMode = ExternalPointerTableEntry::EvacuateMarkMode;
ExternalPointerTable() = default;
ExternalPointerTable(const ExternalPointerTable&) = delete;
ExternalPointerTable& operator=(const ExternalPointerTable&) = delete;
struct Space : public Base::Space {
public:
inline void NotifyExternalPointerFieldInvalidated(
Address field_address, ExternalPointerTagRange tag_range);
void AssertEmpty() { CHECK(segments_.empty()); }
};
void SetUpFromReadOnlyArtifacts(Space* read_only_space,
const ReadOnlyArtifacts* artifacts);
inline Address Get(ExternalPointerHandle handle,
ExternalPointerTagRange tag_range) const;
inline void Set(ExternalPointerHandle handle, Address value,
ExternalPointerTag tag);
inline Address Exchange(ExternalPointerHandle handle, Address value,
ExternalPointerTag tag);
inline ExternalPointerTag GetTag(ExternalPointerHandle handle) const;
inline void Zap(ExternalPointerHandle handle);
inline ExternalPointerHandle AllocateAndInitializeEntry(
Space* space, Address initial_value, ExternalPointerTag tag);
inline ExternalPointerHandle DuplicateEntry(Space* space,
ExternalPointerHandle handle);
inline void Mark(Space* space, ExternalPointerHandle handle,
Address handle_location);
inline void Evacuate(Space* from_space, Space* to_space,
ExternalPointerHandle handle, Address handle_location,
EvacuateMarkMode mode);
uint32_t EvacuateAndSweepAndCompact(Space* to_space, Space* from_space,
Counters* counters);
uint32_t SweepAndCompact(Space* space, Counters* counters);
uint32_t Sweep(Space* space, Counters* counters);
inline bool Contains(Space* space, ExternalPointerHandle handle) const;
class ManagedResource : public Malloced {
public:
inline void ZapExternalPointerTableEntry();
private:
friend class ExternalPointerTable;
template <typename IsolateT>
friend class Deserializer;
ExternalPointerTable* owning_table_ = nullptr;
ExternalPointerHandle ept_entry_ = kNullExternalPointerHandle;
};
private:
static inline bool IsValidHandle(ExternalPointerHandle handle);
static inline uint32_t HandleToIndex(ExternalPointerHandle handle);
static inline ExternalPointerHandle IndexToHandle(uint32_t index);
inline void TakeOwnershipOfManagedResourceIfNecessary(
Address value, ExternalPointerHandle handle, ExternalPointerTag tag);
inline void FreeManagedResourceIfPresent(uint32_t entry_index);
void ResolveEvacuationEntryDuringSweeping(
uint32_t index, ExternalPointerHandle* handle_location,
uint32_t start_of_evacuation_area);
};
#ifdef OBJECT_PRINT
class ExternalPointerTableEntryPrinter {
public:
static void PrintHeader(const char* space_name);
static void PrintIfInUse(
ExternalPointerHandle handle, const ExternalPointerTableEntry& entry,
std::function<bool(ExternalPointerTag)> entry_callback);
static void PrintFooter();
};
template <>
class TableEntryPrinter<ExternalPointerTableEntry>
: public ExternalPointerTableEntryPrinter {};
#endif
}
}
#endif
#endif