[sandbox] Store external pointer table indices shifted to the left

This guarantees that they are smaller than the maximum external pointer
table index when shifted to the right on load.

Bug: v8:10391
Change-Id: I601f37fbb9640ee4b5215958afcc474c5e0eb9af
Cq-Include-Trybots: luci.v8.try:v8_linux64_heap_sandbox_dbg_ng,v8_linux_arm64_sim_heap_sandbox_dbg_ng
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3359631
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Reviewed-by: Maya Lekova <mslekova@chromium.org>
Commit-Queue: Samuel Groß <saelo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78873}
This commit is contained in:
Samuel Groß 2022-01-28 11:21:09 +01:00 committed by V8 LUCI CQ
parent f572d69c7d
commit e7f7c4bbda
12 changed files with 93 additions and 57 deletions

View File

@ -368,17 +368,17 @@ Local<Value> Context::GetEmbedderData(int index) {
}
void* Context::GetAlignedPointerFromEmbedderData(int index) {
#ifndef V8_ENABLE_CHECKS
using A = internal::Address;
using I = internal::Internals;
static_assert(I::kEmbedderDataSlotSize == internal::kApiSystemPointerSize,
"Enable fast path with sandboxed external pointers enabled "
"once embedder data slots are 32 bits large");
#if !defined(V8_ENABLE_CHECKS) && !defined(V8_SANDBOXED_EXTERNAL_POINTERS)
using A = internal::Address;
A ctx = *reinterpret_cast<const A*>(this);
A embedder_data =
I::ReadTaggedPointerField(ctx, I::kNativeContextEmbedderDataOffset);
int value_offset =
I::kEmbedderDataArrayHeaderSize + (I::kEmbedderDataSlotSize * index);
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
value_offset += I::kEmbedderDataSlotRawPayloadOffset;
#endif
internal::Isolate* isolate = I::GetIsolateForSandbox(ctx);
return reinterpret_cast<void*>(
I::ReadExternalPointerField(isolate, embedder_data, value_offset,

View File

@ -256,6 +256,26 @@ constexpr bool kAllowBackingStoresOutsideSandbox = false;
constexpr bool kAllowBackingStoresOutsideSandbox = true;
#endif // V8_SANDBOXED_POINTERS
// The size of the virtual memory reservation for an external pointer table.
// This determines the maximum number of entries in a table. Using a maximum
// size allows omitting bounds checks on table accesses if the indices are
// guaranteed (e.g. through shifting) to be below the maximum index. This
// value must be a power of two.
static const size_t kExternalPointerTableReservationSize = 128 * MB;
// The maximum number of entries in an external pointer table.
static const size_t kMaxSandboxedExternalPointers =
kExternalPointerTableReservationSize / kApiSystemPointerSize;
// The external pointer table indices stored in HeapObjects as external
// pointers are shifted to the left by this amount to guarantee that they are
// smaller than the maximum table size.
static const uint32_t kExternalPointerIndexShift = 8;
static_assert((1 << (32 - kExternalPointerIndexShift)) ==
kMaxSandboxedExternalPointers,
"kExternalPointerTableReservationSize and "
"kExternalPointerIndexShift don't match");
#endif // V8_SANDBOX_IS_AVAILABLE
// If sandboxed external pointers are enabled, these tag values will be ORed
@ -288,17 +308,6 @@ enum ExternalPointerTag : uint64_t {
constexpr uint64_t kExternalPointerTagMask = 0xffff000000000000;
// The size of the virtual memory reservation for an external pointer table.
// This determines the maximum number of entries in a table. Using a maximum
// size allows omitting bounds checks on table accesses if the indices are
// guaranteed (e.g. through shifting) to be below the maximum index. This
// value must be a power of two.
static const size_t kExternalPointerTableReservationSize = 128 * MB;
// The maximum number of entries in an external pointer table.
static const size_t kMaxSandboxedExternalPointers =
kExternalPointerTableReservationSize / kApiSystemPointerSize;
// Converts encoded external pointer to address.
V8_EXPORT Address DecodeExternalPointerImpl(const Isolate* isolate,
ExternalPointer_t pointer,

View File

@ -735,18 +735,18 @@ Local<Value> Object::GetInternalField(int index) {
}
void* Object::GetAlignedPointerFromInternalField(int index) {
#ifndef V8_ENABLE_CHECKS
using A = internal::Address;
using I = internal::Internals;
static_assert(I::kEmbedderDataSlotSize == internal::kApiSystemPointerSize,
"Enable fast path with sandboxed external pointers enabled "
"once embedder data slots are 32 bits large");
#if !defined(V8_ENABLE_CHECKS) && !defined(V8_SANDBOXED_EXTERNAL_POINTERS)
using A = internal::Address;
A obj = *reinterpret_cast<A*>(this);
// Fast path: If the object is a plain JSObject, which is the common case, we
// know where to find the internal fields and can return the value directly.
auto instance_type = I::GetInstanceType(obj);
if (v8::internal::CanHaveInternalField(instance_type)) {
int offset = I::kJSObjectHeaderSize + (I::kEmbedderDataSlotSize * index);
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
offset += I::kEmbedderDataSlotRawPayloadOffset;
#endif
internal::Isolate* isolate = I::GetIsolateForSandbox(obj);
A value = I::ReadExternalPointerField(
isolate, obj, offset, internal::kEmbedderDataSlotPayloadTag);

View File

@ -3109,6 +3109,7 @@ void TurboAssembler::LoadExternalPointerField(Register destination,
DCHECK(!AreAliased(destination, isolate_root));
ASM_CODE_COMMENT(this);
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
DCHECK_NE(kExternalPointerNullTag, tag);
UseScratchRegisterScope temps(this);
Register external_table = temps.AcquireX();
if (isolate_root == no_reg) {
@ -3120,11 +3121,13 @@ void TurboAssembler::LoadExternalPointerField(Register destination,
IsolateData::external_pointer_table_offset() +
Internals::kExternalPointerTableBufferOffset));
Ldr(destination.W(), field_operand);
Ldr(destination,
MemOperand(external_table, destination, LSL, kSystemPointerSizeLog2));
if (tag != 0) {
And(destination, destination, Immediate(~tag));
}
// MemOperand doesn't support LSR currently (only LSL), so here we do the
// offset computation separately first.
STATIC_ASSERT(kExternalPointerIndexShift > kSystemPointerSizeLog2);
int shift_amount = kExternalPointerIndexShift - kSystemPointerSizeLog2;
Mov(destination, Operand(destination, LSR, shift_amount));
Ldr(destination, MemOperand(external_table, destination));
And(destination, destination, Immediate(~tag));
#else
Ldr(destination, field_operand);
#endif // V8_SANDBOXED_EXTERNAL_POINTERS

View File

@ -1584,17 +1584,22 @@ TNode<RawPtrT> CodeStubAssembler::EmptyBackingStoreBufferConstant() {
#endif // V8_SANDBOXED_POINTERS
}
TNode<ExternalPointerT> CodeStubAssembler::ChangeUint32ToExternalPointer(
TNode<Uint32T> value) {
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
TNode<ExternalPointerT> CodeStubAssembler::ChangeIndexToExternalPointer(
TNode<Uint32T> index) {
DCHECK_EQ(kExternalPointerSize, kUInt32Size);
return ReinterpretCast<ExternalPointerT>(value);
TNode<Uint32T> shifted_index =
Word32Shl(index, Uint32Constant(kExternalPointerIndexShift));
return ReinterpretCast<ExternalPointerT>(shifted_index);
}
TNode<Uint32T> CodeStubAssembler::ChangeExternalPointerToUint32(
TNode<ExternalPointerT> value) {
TNode<Uint32T> CodeStubAssembler::ChangeExternalPointerToIndex(
TNode<ExternalPointerT> external_pointer) {
DCHECK_EQ(kExternalPointerSize, kUInt32Size);
return ReinterpretCast<Uint32T>(value);
TNode<Uint32T> shifted_index = ReinterpretCast<Uint32T>(external_pointer);
return Word32Shr(shifted_index, Uint32Constant(kExternalPointerIndexShift));
}
#endif // V8_SANDBOXED_EXTERNAL_POINTERS
void CodeStubAssembler::InitializeExternalPointerField(TNode<HeapObject> object,
TNode<IntPtrT> offset) {
@ -1656,9 +1661,8 @@ void CodeStubAssembler::InitializeExternalPointerField(TNode<HeapObject> object,
// real value). TODO(saelo) initialize the entry with zero here and switch
// callers to a version that initializes the entry with a given pointer.
TNode<ExternalPointerT> encoded =
ChangeUint32ToExternalPointer(index.value());
StoreObjectFieldNoWriteBarrier<ExternalPointerT>(object, offset, encoded);
TNode<ExternalPointerT> pointer = ChangeIndexToExternalPointer(index.value());
StoreObjectFieldNoWriteBarrier<ExternalPointerT>(object, offset, pointer);
#endif
}
@ -1674,7 +1678,9 @@ TNode<RawPtrT> CodeStubAssembler::LoadExternalPointerFromObject(
TNode<ExternalPointerT> encoded =
LoadObjectField<ExternalPointerT>(object, offset);
TNode<Word32T> index = ChangeExternalPointerToUint32(encoded);
TNode<Uint32T> index = ChangeExternalPointerToIndex(encoded);
// TODO(v8:10391): consider updating ElementOffsetFromIndex to generate code
// that does one shift right instead of two shifts (right and then left).
TNode<IntPtrT> table_offset = ElementOffsetFromIndex(
ChangeUint32ToWord(index), SYSTEM_POINTER_ELEMENTS, 0);
@ -1701,8 +1707,9 @@ void CodeStubAssembler::StoreExternalPointerToObject(
TNode<ExternalPointerT> encoded =
LoadObjectField<ExternalPointerT>(object, offset);
TNode<Word32T> index = ChangeExternalPointerToUint32(encoded);
// TODO(v8:10391, saelo): bounds check if table is not caged
TNode<Uint32T> index = ChangeExternalPointerToIndex(encoded);
// TODO(v8:10391): consider updating ElementOffsetFromIndex to generate code
// that does one shift right instead of two shifts (right and then left).
TNode<IntPtrT> table_offset = ElementOffsetFromIndex(
ChangeUint32ToWord(index), SYSTEM_POINTER_ELEMENTS, 0);

View File

@ -1093,8 +1093,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
// ExternalPointerT-related functionality.
//
TNode<ExternalPointerT> ChangeUint32ToExternalPointer(TNode<Uint32T> value);
TNode<Uint32T> ChangeExternalPointerToUint32(TNode<ExternalPointerT> value);
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
TNode<ExternalPointerT> ChangeIndexToExternalPointer(TNode<Uint32T> index);
TNode<Uint32T> ChangeExternalPointerToIndex(TNode<ExternalPointerT> pointer);
#endif // V8_SANDBOXED_EXTERNAL_POINTERS
// Initialize an external pointer field in an object.
void InitializeExternalPointerField(TNode<HeapObject> object, int offset) {

View File

@ -418,6 +418,7 @@ void TurboAssembler::LoadExternalPointerField(
Register scratch, IsolateRootLocation isolateRootLocation) {
DCHECK(!AreAliased(destination, scratch));
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
DCHECK_NE(kExternalPointerNullTag, tag);
DCHECK(!field_operand.AddressUsesRegister(scratch));
if (isolateRootLocation == IsolateRootLocation::kInRootRegister) {
DCHECK(root_array_available_);
@ -431,11 +432,10 @@ void TurboAssembler::LoadExternalPointerField(
Internals::kExternalPointerTableBufferOffset));
}
movl(destination, field_operand);
shrq(destination, Immediate(kExternalPointerIndexShift));
movq(destination, Operand(scratch, destination, times_8, 0));
if (tag != 0) {
movq(scratch, Immediate64(~tag));
andq(destination, scratch);
}
movq(scratch, Immediate64(~tag));
andq(destination, scratch);
#else
movq(destination, field_operand);
#endif // V8_SANDBOXED_EXTERNAL_POINTERS

View File

@ -412,6 +412,7 @@ Node* MemoryLowering::DecodeExternalPointer(
DCHECK(V8_SANDBOXED_EXTERNAL_POINTERS_BOOL);
DCHECK(node->opcode() == IrOpcode::kLoad);
DCHECK_EQ(kExternalPointerSize, kUInt32Size);
DCHECK_NE(kExternalPointerNullTag, external_pointer_tag);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
__ InitializeEffectControl(effect, control);
@ -419,7 +420,11 @@ Node* MemoryLowering::DecodeExternalPointer(
// Clone the load node and put it here.
// TODO(turbofan): consider adding GraphAssembler::Clone() suitable for
// cloning nodes from arbitrary locaions in effect/control chains.
Node* index = __ AddNode(graph()->CloneNode(node));
STATIC_ASSERT(kExternalPointerIndexShift > kSystemPointerSizeLog2);
Node* shifted_index = __ AddNode(graph()->CloneNode(node));
Node* shift_amount =
__ Int32Constant(kExternalPointerIndexShift - kSystemPointerSizeLog2);
Node* offset = __ Word32Shr(shifted_index, shift_amount);
// Uncomment this to generate a breakpoint for debugging purposes.
// __ DebugBreak();
@ -436,13 +441,10 @@ Node* MemoryLowering::DecodeExternalPointer(
ExternalReference::external_pointer_table_address(isolate()));
Node* table = __ Load(MachineType::Pointer(), table_address,
Internals::kExternalPointerTableBufferOffset);
Node* offset = __ Int32Mul(index, __ Int32Constant(sizeof(Address)));
Node* decoded_ptr =
__ Load(MachineType::Pointer(), table, __ ChangeUint32ToUint64(offset));
if (external_pointer_tag != 0) {
Node* tag = __ IntPtrConstant(~external_pointer_tag);
decoded_ptr = __ WordAnd(decoded_ptr, tag);
}
Node* tag = __ IntPtrConstant(~external_pointer_tag);
decoded_ptr = __ WordAnd(decoded_ptr, tag);
return decoded_ptr;
#else
return node;

View File

@ -3224,13 +3224,15 @@ Node* WasmGraphBuilder::BuildLoadExternalPointerFromObject(
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
Node* external_pointer = gasm_->LoadFromObject(
MachineType::Uint32(), object, wasm::ObjectAccess::ToTagged(offset));
STATIC_ASSERT(kExternalPointerIndexShift > kSystemPointerSizeLog2);
Node* shift_amount =
gasm_->Int32Constant(kExternalPointerIndexShift - kSystemPointerSizeLog2);
Node* scaled_index = gasm_->Word32Shr(external_pointer, shift_amount);
Node* isolate_root = BuildLoadIsolateRoot();
Node* table =
gasm_->LoadFromObject(MachineType::Pointer(), isolate_root,
IsolateData::external_pointer_table_offset() +
Internals::kExternalPointerTableBufferOffset);
Node* scaled_index = gasm_->Int32Mul(
external_pointer, gasm_->Int32Constant(kSystemPointerSize));
Node* decoded_ptr = gasm_->Load(MachineType::Pointer(), table, scaled_index);
return gasm_->WordAnd(decoded_ptr, gasm_->IntPtrConstant(~tag));
#else

View File

@ -198,7 +198,8 @@ class MarkingVisitorBase : public HeapVisitor<int, ConcreteVisitor> {
V8_INLINE void VisitExternalPointer(HeapObject host,
ExternalPointer_t ptr) final {
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
external_pointer_table_->Mark(static_cast<uint32_t>(ptr));
uint32_t index = ptr >> kExternalPointerIndexShift;
external_pointer_table_->Mark(index);
#endif // V8_SANDBOXED_EXTERNAL_POINTERS
}

View File

@ -111,10 +111,17 @@ namespace internal {
3) \
/* number of times a cache event is triggered for a wasm module */ \
HR(wasm_cache_count, V8.WasmCacheCount, 0, 100, 101) \
/* Number of in-use external pointers in the external pointer table */ \
/* Counted after sweeping the table at the end of mark-compact GC */ \
HR(sandboxed_external_pointers_count, V8.SandboxedExternalPointersCount, 0, \
SANDBOXED_HISTOGRAM_LIST(HR)
#ifdef V8_SANDBOX_IS_AVAILABLE
#define SANDBOXED_HISTOGRAM_LIST(HR) \
/* Number of in-use external pointers in the external pointer table */ \
/* Counted after sweeping the table at the end of mark-compact GC */ \
HR(sandboxed_external_pointers_count, V8.SandboxedExternalPointersCount, 0, \
kMaxSandboxedExternalPointers, 101)
#else
#define SANDBOXED_HISTOGRAM_LIST(HR)
#endif // V8_SANDBOX_IS_AVAILABLE
#define NESTED_TIMED_HISTOGRAM_LIST(HT) \
/* Timer histograms, not thread safe: HT(name, caption, max, unit) */ \

View File

@ -18,7 +18,8 @@ V8_INLINE Address DecodeExternalPointer(const Isolate* isolate,
ExternalPointerTag tag) {
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
STATIC_ASSERT(kExternalPointerSize == kInt32Size);
return isolate->external_pointer_table().Get(encoded_pointer, tag);
uint32_t index = encoded_pointer >> kExternalPointerIndexShift;
return isolate->external_pointer_table().Get(index, tag);
#else
STATIC_ASSERT(kExternalPointerSize == kSystemPointerSize);
return encoded_pointer;
@ -35,6 +36,7 @@ V8_INLINE void InitExternalPointerField(Address field_address, Isolate* isolate,
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
ExternalPointer_t index = isolate->external_pointer_table().Allocate();
isolate->external_pointer_table().Set(index, value, tag);
index <<= kExternalPointerIndexShift;
base::Memory<ExternalPointer_t>(field_address) = index;
#else
// Pointer compression causes types larger than kTaggedSize to be unaligned.
@ -72,6 +74,7 @@ V8_INLINE void WriteExternalPointerField(Address field_address,
ExternalPointerTag tag) {
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
ExternalPointer_t index = base::Memory<ExternalPointer_t>(field_address);
index >>= kExternalPointerIndexShift;
isolate->external_pointer_table().Set(index, value, tag);
#else
// Pointer compression causes types larger than kTaggedSize to be unaligned.