blob: a36b76f7f6e126ce5f9ba544775f8b0cfb736620 [file] [edit]
// Copyright 2023 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_TRUSTED_POINTER_TABLE_INL_H_
#define V8_SANDBOX_TRUSTED_POINTER_TABLE_INL_H_
#include "src/sandbox/external-entity-table-inl.h"
#include "src/sandbox/sandbox.h"
#include "src/sandbox/trusted-pointer-table.h"
#ifdef V8_ENABLE_SANDBOX
namespace v8 {
namespace internal {
void TrustedPointerTableEntry::MakeTrustedPointerEntry(Address content,
bool mark_as_alive) {
DCHECK_EQ(content & kMarkingBit, 0);
if (mark_as_alive) content |= kMarkingBit;
content_.store(content, std::memory_order_relaxed);
}
void TrustedPointerTableEntry::MakeFreelistEntry(uint32_t next_entry_index) {
Address content = kFreeEntryTag | next_entry_index;
content_.store(content, std::memory_order_relaxed);
}
Address TrustedPointerTableEntry::GetContent() const {
DCHECK(!IsFreelistEntry());
// We reuse the heap object tag bit as marking bit, so we need to explicitly
// set it here when accessing the pointer.
return content_.load(std::memory_order_relaxed) | kMarkingBit;
}
void TrustedPointerTableEntry::SetContent(Address new_content) {
DCHECK(!IsFreelistEntry());
// SetContent shouldn't change the marking state of the entry. Currently this
// is always automatically the case, but if this ever fails, we might need to
// manually copy the marking bit.
DCHECK_EQ(content_ & kMarkingBit, new_content & kMarkingBit);
content_.store(new_content, std::memory_order_relaxed);
}
bool TrustedPointerTableEntry::IsFreelistEntry() const {
auto content = content_.load(std::memory_order_relaxed);
return (content & kFreeEntryTag) == kFreeEntryTag;
}
uint32_t TrustedPointerTableEntry::GetNextFreelistEntryIndex() const {
return static_cast<uint32_t>(content_.load(std::memory_order_relaxed));
}
void TrustedPointerTableEntry::Mark() {
Address old_value = content_.load(std::memory_order_relaxed);
Address new_value = old_value | kMarkingBit;
// We don't need to perform the CAS in a loop since it can only fail if a new
// value has been written into the entry. This, however, will also have set
// the marking bit.
bool success = content_.compare_exchange_strong(old_value, new_value,
std::memory_order_relaxed);
DCHECK(success || (old_value & kMarkingBit) == kMarkingBit);
USE(success);
}
void TrustedPointerTableEntry::Unmark() {
Address content = content_.load(std::memory_order_relaxed);
content &= ~kMarkingBit;
content_.store(content, std::memory_order_relaxed);
}
bool TrustedPointerTableEntry::IsMarked() const {
Address value = content_.load(std::memory_order_relaxed);
return value & kMarkingBit;
}
Address TrustedPointerTable::Get(TrustedPointerHandle handle) const {
uint32_t index = HandleToIndex(handle);
return at(index).GetContent();
}
void TrustedPointerTable::Set(TrustedPointerHandle handle, Address pointer,
IndirectPointerTag tag) {
DCHECK_NE(kNullTrustedPointerHandle, handle);
Validate(pointer, tag);
uint32_t index = HandleToIndex(handle);
at(index).SetContent(pointer);
}
TrustedPointerHandle TrustedPointerTable::AllocateAndInitializeEntry(
Space* space, Address pointer, IndirectPointerTag tag) {
DCHECK(space->BelongsTo(this));
Validate(pointer, tag);
uint32_t index = AllocateEntry(space);
at(index).MakeTrustedPointerEntry(pointer, space->allocate_black());
return IndexToHandle(index);
}
void TrustedPointerTable::Mark(Space* space, TrustedPointerHandle handle) {
DCHECK(space->BelongsTo(this));
// The null entry is immortal and immutable, so no need to mark it as alive.
if (handle == kNullTrustedPointerHandle) return;
uint32_t index = HandleToIndex(handle);
DCHECK(space->Contains(index));
at(index).Mark();
}
template <typename Callback>
void TrustedPointerTable::IterateActiveEntriesIn(Space* space,
Callback callback) {
IterateEntriesIn(space, [&](uint32_t index) {
if (!at(index).IsFreelistEntry()) {
callback(IndexToHandle(index), at(index).GetContent());
}
});
}
uint32_t TrustedPointerTable::HandleToIndex(TrustedPointerHandle handle) const {
uint32_t index = handle >> kTrustedPointerHandleShift;
DCHECK_EQ(handle, index << kTrustedPointerHandleShift);
return index;
}
TrustedPointerHandle TrustedPointerTable::IndexToHandle(uint32_t index) const {
TrustedPointerHandle handle = index << kTrustedPointerHandleShift;
DCHECK_EQ(index, handle >> kTrustedPointerHandleShift);
return handle;
}
void TrustedPointerTable::Validate(Address pointer, IndirectPointerTag tag) {
if (IsTrustedSpaceMigrationInProgressForObjectsWithTag(tag)) {
// This CHECK is mostly just here to force tags to be taken out of the
// IsTrustedSpaceMigrationInProgressForObjectsWithTag function once the
// objects are fully migrated into trusted space.
DCHECK(GetProcessWideSandbox()->Contains(pointer));
return;
}
// Entries must never point into the sandbox, as they couldn't be trusted in
// that case. This CHECK is a defense-in-depth mechanism to guarantee this.
// However, on some platforms we cannot (always) reserve the full address
// space for the sandbox. In that case, the trusted space may legitimately
// end up inside the sandbox address space. This is ok since these
// configurations are anyway considered unsafe.
Sandbox* sandbox = GetProcessWideSandbox();
CHECK(!sandbox->Contains(pointer) || sandbox->is_partially_reserved());
}
} // namespace internal
} // namespace v8
#endif // V8_ENABLE_SANDBOX
#endif // V8_SANDBOX_TRUSTED_POINTER_TABLE_INL_H_