[sandbox] Introduce BoundedSize

A BoundedSize is just a regular size_t when the sandbox is disabled.
However, when the sandbox is enabled, a BoundedLength is guaranteed to
be in the range [0, kMaxSafeBufferSizeForSandbox]. This is (currently)
achieved by storing the length shifted to the left, then right-shifting
it when loading it. This guarantees that the top bits are zero.

BoundedSizes are used to ensure safe access to variable-sized buffers,
in particular ArrayBuffers and their views, located inside the sandbox.
If a full size_t is used to represent their size, it may allow an
attacker to "reach out of" the sandbox address space by setting the
length to a very large value. A BoundedSize prevents this.

Bug: chromium:1360375
Change-Id: I0579693db528af96c41eeaa64bd3ed71266aacd9
Cq-Include-Trybots: luci.v8.try.triggered:v8_linux64_no_sandbox_dbg_ng_triggered
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3876823
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Commit-Queue: Samuel Groß <saelo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#83631}
This commit is contained in:
Samuel Groß 2022-10-11 10:53:04 +00:00 committed by V8 LUCI CQ
parent 325853cf50
commit e9775165b6
23 changed files with 329 additions and 62 deletions

View File

@ -2071,6 +2071,8 @@ filegroup(
"src/sandbox/sandbox.h",
"src/sandbox/sandboxed-pointer-inl.h",
"src/sandbox/sandboxed-pointer.h",
"src/sandbox/bounded-size-inl.h",
"src/sandbox/bounded-size.h",
"src/base/sanitizer/asan.h",
"src/base/sanitizer/lsan-page-allocator.cc",
"src/base/sanitizer/lsan-page-allocator.h",

View File

@ -3456,6 +3456,8 @@ v8_header_set("v8_internal_headers") {
"src/roots/roots.h",
"src/runtime/runtime-utils.h",
"src/runtime/runtime.h",
"src/sandbox/bounded-size-inl.h",
"src/sandbox/bounded-size.h",
"src/sandbox/external-pointer-inl.h",
"src/sandbox/external-pointer-table-inl.h",
"src/sandbox/external-pointer-table.h",

View File

@ -182,7 +182,7 @@ constexpr size_t kSandboxSizeLog2 = 37; // 128 GB
#else
// Everywhere else use a 1TB sandbox.
constexpr size_t kSandboxSizeLog2 = 40; // 1 TB
#endif // V8_OS_ANDROID
#endif // V8_TARGET_OS_ANDROID
constexpr size_t kSandboxSize = 1ULL << kSandboxSizeLog2;
// Required alignment of the sandbox. For simplicity, we require the
@ -223,6 +223,21 @@ static_assert(kSandboxMinimumReservationSize > kPtrComprCageReservationSize,
"The minimum reservation size for a sandbox must be larger than "
"the pointer compression cage contained within it.");
// The maximum buffer size allowed inside the sandbox. This is mostly dependent
// on the size of the guard regions around the sandbox: an attacker must not be
// able to construct a buffer that appears larger than the guard regions and
// thereby "reach out of" the sandbox.
constexpr size_t kMaxSafeBufferSizeForSandbox = 32ULL * GB - 1;
static_assert(kMaxSafeBufferSizeForSandbox <= kSandboxGuardRegionSize,
"The maximum allowed buffer size must not be larger than the "
"sandbox's guard regions");
constexpr size_t kBoundedSizeShift = 29;
static_assert(1ULL << (64 - kBoundedSizeShift) ==
kMaxSafeBufferSizeForSandbox + 1,
"The maximum size of a BoundedSize must be synchronized with the "
"kMaxSafeBufferSizeForSandbox");
#endif // V8_ENABLE_SANDBOX
#ifdef V8_COMPRESS_POINTERS

View File

@ -64,8 +64,8 @@ TNode<JSArrayBuffer> TypedArrayBuiltinsAssembler::AllocateEmptyOnHeapBuffer(
StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kBitFieldOffset,
Int32Constant(bitfield_value));
StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kByteLengthOffset,
UintPtrConstant(0));
StoreBoundedSizeToObject(buffer, JSArrayBuffer::kRawByteLengthOffset,
UintPtrConstant(0));
StoreSandboxedPointerToObject(buffer, JSArrayBuffer::kBackingStoreOffset,
EmptyBackingStoreBufferConstant());
StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kExtensionOffset,
@ -141,7 +141,7 @@ TF_BUILTIN(TypedArrayPrototypeByteLength, TypedArrayBuiltinsAssembler) {
// Default to zero if the {receiver}s buffer was detached.
TNode<UintPtrT> byte_length = Select<UintPtrT>(
IsDetachedBuffer(receiver_buffer), [=] { return UintPtrConstant(0); },
[=] { return LoadJSArrayBufferViewRawByteLength(receiver_array); });
[=] { return LoadJSArrayBufferViewByteLength(receiver_array); });
Return(ChangeUintPtrToTagged(byte_length));
}
}

View File

@ -3291,16 +3291,6 @@ void MacroAssembler::RecordWriteField(Register object, int offset,
Bind(&done);
}
void TurboAssembler::EncodeSandboxedPointer(const Register& value) {
ASM_CODE_COMMENT(this);
#ifdef V8_ENABLE_SANDBOX
Sub(value, value, kPtrComprCageBaseRegister);
Mov(value, Operand(value, LSL, kSandboxedPointerShift));
#else
UNREACHABLE();
#endif
}
void TurboAssembler::DecodeSandboxedPointer(const Register& value) {
ASM_CODE_COMMENT(this);
#ifdef V8_ENABLE_SANDBOX
@ -3313,19 +3303,27 @@ void TurboAssembler::DecodeSandboxedPointer(const Register& value) {
void TurboAssembler::LoadSandboxedPointerField(
const Register& destination, const MemOperand& field_operand) {
#ifdef V8_ENABLE_SANDBOX
ASM_CODE_COMMENT(this);
Ldr(destination, field_operand);
DecodeSandboxedPointer(destination);
#else
UNREACHABLE();
#endif
}
void TurboAssembler::StoreSandboxedPointerField(
const Register& value, const MemOperand& dst_field_operand) {
#ifdef V8_ENABLE_SANDBOX
ASM_CODE_COMMENT(this);
UseScratchRegisterScope temps(this);
Register scratch = temps.AcquireX();
Mov(scratch, value);
EncodeSandboxedPointer(scratch);
Sub(scratch, value, kPtrComprCageBaseRegister);
Mov(scratch, Operand(scratch, LSL, kSandboxedPointerShift));
Str(scratch, dst_field_operand);
#else
UNREACHABLE();
#endif
}
void TurboAssembler::LoadExternalPointerField(Register destination,

View File

@ -1454,13 +1454,9 @@ class V8_EXPORT_PRIVATE TurboAssembler : public TurboAssemblerBase {
// Transform a SandboxedPointer from/to its encoded form, which is used when
// the pointer is stored on the heap and ensures that the pointer will always
// point into the sandbox.
void EncodeSandboxedPointer(const Register& value);
void DecodeSandboxedPointer(const Register& value);
// Load and decode a SandboxedPointer from the heap.
void LoadSandboxedPointerField(const Register& destination,
const MemOperand& field_operand);
// Encode and store a SandboxedPointer to the heap.
void StoreSandboxedPointerField(const Register& value,
const MemOperand& dst_field_operand);

View File

@ -1552,6 +1552,33 @@ TNode<RawPtrT> CodeStubAssembler::EmptyBackingStoreBufferConstant() {
#endif // V8_ENABLE_SANDBOX
}
TNode<UintPtrT> CodeStubAssembler::LoadBoundedSizeFromObject(
TNode<HeapObject> object, TNode<IntPtrT> field_offset) {
#ifdef V8_ENABLE_SANDBOX
TNode<Uint64T> raw_value = LoadObjectField<Uint64T>(object, field_offset);
TNode<Uint64T> shift_amount = Uint64Constant(kBoundedSizeShift);
TNode<Uint64T> decoded_value = Word64Shr(raw_value, shift_amount);
return ReinterpretCast<UintPtrT>(decoded_value);
#else
return LoadObjectField<UintPtrT>(object, field_offset);
#endif // V8_ENABLE_SANDBOX
}
void CodeStubAssembler::StoreBoundedSizeToObject(TNode<HeapObject> object,
TNode<IntPtrT> offset,
TNode<UintPtrT> value) {
#ifdef V8_ENABLE_SANDBOX
CSA_DCHECK(this, UintPtrLessThan(
value, IntPtrConstant(kMaxSafeBufferSizeForSandbox)));
TNode<Uint64T> raw_value = ReinterpretCast<Uint64T>(value);
TNode<Uint64T> shift_amount = Uint64Constant(kBoundedSizeShift);
TNode<Uint64T> encoded_value = Word64Shl(raw_value, shift_amount);
StoreObjectFieldNoWriteBarrier<Uint64T>(object, offset, encoded_value);
#else
StoreObjectFieldNoWriteBarrier<UintPtrT>(object, offset, value);
#endif // V8_ENABLE_SANDBOX
}
#ifdef V8_ENABLE_SANDBOX
TNode<RawPtrT> CodeStubAssembler::ExternalPointerTableAddress(
ExternalPointerTag tag) {
@ -14299,6 +14326,18 @@ void CodeStubAssembler::ThrowIfArrayBufferViewBufferIsDetached(
ThrowIfArrayBufferIsDetached(context, buffer, method_name);
}
TNode<UintPtrT> CodeStubAssembler::LoadJSArrayBufferByteLength(
TNode<JSArrayBuffer> array_buffer) {
return LoadBoundedSizeFromObject(array_buffer,
JSArrayBuffer::kRawByteLengthOffset);
}
TNode<UintPtrT> CodeStubAssembler::LoadJSArrayBufferMaxByteLength(
TNode<JSArrayBuffer> array_buffer) {
return LoadBoundedSizeFromObject(array_buffer,
JSArrayBuffer::kRawMaxByteLengthOffset);
}
TNode<RawPtrT> CodeStubAssembler::LoadJSArrayBufferBackingStorePtr(
TNode<JSArrayBuffer> array_buffer) {
return LoadSandboxedPointerFromObject(array_buffer,
@ -14311,16 +14350,38 @@ TNode<JSArrayBuffer> CodeStubAssembler::LoadJSArrayBufferViewBuffer(
JSArrayBufferView::kBufferOffset);
}
TNode<UintPtrT> CodeStubAssembler::LoadJSArrayBufferViewRawByteLength(
TNode<UintPtrT> CodeStubAssembler::LoadJSArrayBufferViewByteLength(
TNode<JSArrayBufferView> array_buffer_view) {
return LoadObjectField<UintPtrT>(array_buffer_view,
JSArrayBufferView::kByteLengthOffset);
return LoadBoundedSizeFromObject(array_buffer_view,
JSArrayBufferView::kRawByteLengthOffset);
}
void CodeStubAssembler::StoreJSArrayBufferViewByteLength(
TNode<JSArrayBufferView> array_buffer_view, TNode<UintPtrT> value) {
StoreBoundedSizeToObject(array_buffer_view,
JSArrayBufferView::kRawByteLengthOffset, value);
}
TNode<UintPtrT> CodeStubAssembler::LoadJSArrayBufferViewByteOffset(
TNode<JSArrayBufferView> array_buffer_view) {
return LoadObjectField<UintPtrT>(array_buffer_view,
JSArrayBufferView::kByteOffsetOffset);
return LoadBoundedSizeFromObject(array_buffer_view,
JSArrayBufferView::kRawByteOffsetOffset);
}
void CodeStubAssembler::StoreJSArrayBufferViewByteOffset(
TNode<JSArrayBufferView> array_buffer_view, TNode<UintPtrT> value) {
StoreBoundedSizeToObject(array_buffer_view,
JSArrayBufferView::kRawByteOffsetOffset, value);
}
TNode<UintPtrT> CodeStubAssembler::LoadJSTypedArrayLength(
TNode<JSTypedArray> typed_array) {
return LoadBoundedSizeFromObject(typed_array, JSTypedArray::kRawLengthOffset);
}
void CodeStubAssembler::StoreJSTypedArrayLength(TNode<JSTypedArray> typed_array,
TNode<UintPtrT> value) {
StoreBoundedSizeToObject(typed_array, JSTypedArray::kRawLengthOffset, value);
}
TNode<UintPtrT> CodeStubAssembler::LoadJSTypedArrayLengthAndCheckDetached(
@ -14418,7 +14479,7 @@ CodeStubAssembler::LoadVariableLengthJSArrayBufferViewByteLength(
// Check if the backing RAB has shrunk so that the buffer is out of
// bounds.
TNode<UintPtrT> array_byte_length =
LoadJSArrayBufferViewRawByteLength(array);
LoadJSArrayBufferViewByteLength(array);
GotoIfNot(UintPtrGreaterThanOrEqual(
buffer_byte_length,
UintPtrAdd(array_byte_offset, array_byte_length)),
@ -14463,7 +14524,7 @@ void CodeStubAssembler::IsJSArrayBufferViewDetachedOrOutOfBounds(
// Check if the backing RAB has shrunk so that the buffer is out of
// bounds.
TNode<UintPtrT> array_byte_length =
LoadJSArrayBufferViewRawByteLength(array_buffer_view);
LoadJSArrayBufferViewByteLength(array_buffer_view);
Branch(UintPtrGreaterThanOrEqual(
buffer_byte_length,
UintPtrAdd(array_byte_offset, array_byte_length)),

View File

@ -1087,10 +1087,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
void GotoIfForceSlowPath(Label* if_true);
//
// Caged pointer related functionality.
// Sandboxed pointer related functionality.
//
// Load a caged pointer value from an object.
// Load a sandboxed pointer value from an object.
TNode<RawPtrT> LoadSandboxedPointerFromObject(TNode<HeapObject> object,
int offset) {
return LoadSandboxedPointerFromObject(object, IntPtrConstant(offset));
@ -1099,7 +1099,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<RawPtrT> LoadSandboxedPointerFromObject(TNode<HeapObject> object,
TNode<IntPtrT> offset);
// Stored a caged pointer value to an object.
// Stored a sandboxed pointer value to an object.
void StoreSandboxedPointerToObject(TNode<HeapObject> object, int offset,
TNode<RawPtrT> pointer) {
StoreSandboxedPointerToObject(object, IntPtrConstant(offset), pointer);
@ -1111,6 +1111,27 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<RawPtrT> EmptyBackingStoreBufferConstant();
//
// Bounded size related functionality.
//
// Load a bounded size value from an object.
TNode<UintPtrT> LoadBoundedSizeFromObject(TNode<HeapObject> object,
int offset) {
return LoadBoundedSizeFromObject(object, IntPtrConstant(offset));
}
TNode<UintPtrT> LoadBoundedSizeFromObject(TNode<HeapObject> object,
TNode<IntPtrT> offset);
// Stored a bounded size value to an object.
void StoreBoundedSizeToObject(TNode<HeapObject> object, int offset,
TNode<UintPtrT> value) {
StoreBoundedSizeToObject(object, IntPtrConstant(offset), value);
}
void StoreBoundedSizeToObject(TNode<HeapObject> object, TNode<IntPtrT> offset,
TNode<UintPtrT> value);
//
// ExternalPointerT-related functionality.
//
@ -3757,6 +3778,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<BoolT> IsSideEffectFreeDebuggingActive();
// JSArrayBuffer helpers
TNode<UintPtrT> LoadJSArrayBufferByteLength(
TNode<JSArrayBuffer> array_buffer);
TNode<UintPtrT> LoadJSArrayBufferMaxByteLength(
TNode<JSArrayBuffer> array_buffer);
TNode<RawPtrT> LoadJSArrayBufferBackingStorePtr(
TNode<JSArrayBuffer> array_buffer);
void ThrowIfArrayBufferIsDetached(TNode<Context> context,
@ -3766,16 +3791,22 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
// JSArrayBufferView helpers
TNode<JSArrayBuffer> LoadJSArrayBufferViewBuffer(
TNode<JSArrayBufferView> array_buffer_view);
TNode<UintPtrT> LoadJSArrayBufferViewRawByteLength(
TNode<UintPtrT> LoadJSArrayBufferViewByteLength(
TNode<JSArrayBufferView> array_buffer_view);
void StoreJSArrayBufferViewByteLength(
TNode<JSArrayBufferView> array_buffer_view, TNode<UintPtrT> value);
TNode<UintPtrT> LoadJSArrayBufferViewByteOffset(
TNode<JSArrayBufferView> array_buffer_view);
void StoreJSArrayBufferViewByteOffset(
TNode<JSArrayBufferView> array_buffer_view, TNode<UintPtrT> value);
void ThrowIfArrayBufferViewBufferIsDetached(
TNode<Context> context, TNode<JSArrayBufferView> array_buffer_view,
const char* method_name);
// JSTypedArray helpers
TNode<UintPtrT> LoadJSTypedArrayLength(TNode<JSTypedArray> typed_array);
void StoreJSTypedArrayLength(TNode<JSTypedArray> typed_array,
TNode<UintPtrT> value);
TNode<UintPtrT> LoadJSTypedArrayLengthAndCheckDetached(
TNode<JSTypedArray> typed_array, Label* detached);
// Helper for length tracking JSTypedArrays and JSTypedArrays backed by

View File

@ -240,7 +240,7 @@ class MachineType {
}
constexpr static MachineType SandboxedPointer() {
return MachineType(MachineRepresentation::kSandboxedPointer,
MachineSemantic::kNone);
MachineSemantic::kInt64);
}
constexpr static MachineType Bool() {
return MachineType(MachineRepresentation::kBit, MachineSemantic::kBool);

View File

@ -376,39 +376,48 @@ FieldAccess AccessBuilder::ForJSArrayBufferViewBuffer() {
// static
FieldAccess AccessBuilder::ForJSArrayBufferViewByteLength() {
FieldAccess access = {kTaggedBase,
JSArrayBufferView::kByteLengthOffset,
JSArrayBufferView::kRawByteLengthOffset,
MaybeHandle<Name>(),
MaybeHandle<Map>(),
TypeCache::Get()->kJSArrayBufferViewByteLengthType,
MachineType::UintPtr(),
kNoWriteBarrier,
"JSArrayBufferViewByteLength"};
#ifdef V8_ENABLE_SANDBOX
access.is_bounded_size_access = true;
#endif
return access;
}
// static
FieldAccess AccessBuilder::ForJSArrayBufferViewByteOffset() {
FieldAccess access = {kTaggedBase,
JSArrayBufferView::kByteOffsetOffset,
JSArrayBufferView::kRawByteOffsetOffset,
MaybeHandle<Name>(),
MaybeHandle<Map>(),
TypeCache::Get()->kJSArrayBufferViewByteOffsetType,
MachineType::UintPtr(),
kNoWriteBarrier,
"JSArrayBufferViewByteOffset"};
#ifdef V8_ENABLE_SANDBOX
access.is_bounded_size_access = true;
#endif
return access;
}
// static
FieldAccess AccessBuilder::ForJSTypedArrayLength() {
FieldAccess access = {kTaggedBase,
JSTypedArray::kLengthOffset,
JSTypedArray::kRawLengthOffset,
MaybeHandle<Name>(),
MaybeHandle<Map>(),
TypeCache::Get()->kJSTypedArrayLengthType,
MachineType::UintPtr(),
kNoWriteBarrier,
"JSTypedArrayLength"};
#ifdef V8_ENABLE_SANDBOX
access.is_bounded_size_access = true;
#endif
return access;
}

View File

@ -339,7 +339,7 @@ ArchOpcode GetLoadOpcode(LoadRepresentation load_rep) {
break;
case MachineRepresentation::kSimd256: // Fall through.
case MachineRepresentation::kNone: // Fall through.
case MachineRepresentation::kMapWord:
case MachineRepresentation::kMapWord: // Fall through.
UNREACHABLE();
}
return opcode;
@ -377,7 +377,7 @@ ArchOpcode GetStoreOpcode(StoreRepresentation store_rep) {
return kX64Movdqu;
case MachineRepresentation::kSimd256: // Fall through.
case MachineRepresentation::kNone: // Fall through.
case MachineRepresentation::kMapWord:
case MachineRepresentation::kMapWord: // Fall through.
UNREACHABLE();
}
UNREACHABLE();

View File

@ -616,8 +616,11 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current,
Node* value = current->ValueInput(1);
const VirtualObject* vobject = current->GetVirtualObject(object);
Variable var;
// BoundedSize fields cannot currently be materialized by the deoptimizer,
// so we must not dematerialze them.
if (vobject && !vobject->HasEscaped() &&
vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var)) {
vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var) &&
!FieldAccessOf(op).is_bounded_size_access) {
current->Set(var, value);
current->MarkForDeletion();
} else {

View File

@ -492,6 +492,27 @@ Reduction MemoryLowering::ReduceLoadExternalPointerField(Node* node) {
return Changed(node);
}
Reduction MemoryLowering::ReduceLoadBoundedSize(Node* node) {
#ifdef V8_ENABLE_SANDBOX
const Operator* load_op =
!machine()->UnalignedLoadSupported(MachineRepresentation::kWord64)
? machine()->UnalignedLoad(MachineType::Uint64())
: machine()->Load(MachineType::Uint64());
NodeProperties::ChangeOp(node, load_op);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
__ InitializeEffectControl(effect, control);
Node* raw_value = __ AddNode(graph()->CloneNode(node));
Node* shift_amount = __ IntPtrConstant(kBoundedSizeShift);
Node* decoded_size = __ Word64Shr(raw_value, shift_amount);
return Replace(decoded_size);
#else
UNREACHABLE();
#endif
}
Reduction MemoryLowering::ReduceLoadMap(Node* node) {
#ifdef V8_MAP_PACKING
NodeProperties::ChangeOp(node, machine()->Load(MachineType::AnyTagged()));
@ -524,6 +545,10 @@ Reduction MemoryLowering::ReduceLoadField(Node* node) {
return ReduceLoadExternalPointerField(node);
}
if (access.is_bounded_size_access) {
return ReduceLoadBoundedSize(node);
}
NodeProperties::ChangeOp(node, machine()->Load(type));
return Changed(node);
@ -574,6 +599,8 @@ Reduction MemoryLowering::ReduceStoreField(Node* node,
DCHECK(!access.type.Is(Type::ExternalPointer()));
// SandboxedPointers are not currently stored by optimized code.
DCHECK(!access.type.Is(Type::SandboxedPointer()));
// Bounded size fields are not currently stored by optimized code.
DCHECK(!access.is_bounded_size_access);
MachineType machine_type = access.machine_type;
Node* object = node->InputAt(0);
Node* value = node->InputAt(1);

View File

@ -109,6 +109,7 @@ class MemoryLowering final : public Reducer {
AllocationState const* state,
WriteBarrierKind);
Reduction ReduceLoadExternalPointerField(Node* node);
Reduction ReduceLoadBoundedSize(Node* node);
Reduction ReduceLoadMap(Node* node);
Node* ComputeIndex(ElementAccess const& access, Node* node);
void EnsureAllocateOperator();

View File

@ -87,6 +87,12 @@ struct FieldAccess {
// initializing a newly
// allocated object or part
// of a map transition.
bool is_bounded_size_access = false; // Whether this field is stored as a
// bounded size field. In that case,
// the size is shifted to the left to
// guarantee that the value is at most
// kMaxSafeBufferSizeForSandbox after
// decoding.
FieldAccess()
: base_is_tagged(kTaggedBase),

View File

@ -28,11 +28,19 @@ RELEASE_ACQUIRE_ACCESSORS(JSTypedArray, base_pointer, Object,
kBasePointerOffset)
size_t JSArrayBuffer::byte_length() const {
return ReadField<size_t>(kByteLengthOffset);
return ReadBoundedSizeField(kRawByteLengthOffset);
}
void JSArrayBuffer::set_byte_length(size_t value) {
WriteField<size_t>(kByteLengthOffset, value);
WriteBoundedSizeField(kRawByteLengthOffset, value);
}
size_t JSArrayBuffer::max_byte_length() const {
return ReadBoundedSizeField(kRawMaxByteLengthOffset);
}
void JSArrayBuffer::set_max_byte_length(size_t value) {
WriteBoundedSizeField(kRawMaxByteLengthOffset, value);
}
DEF_GETTER(JSArrayBuffer, backing_store, void*) {
@ -169,19 +177,19 @@ bool JSArrayBuffer::IsEmpty() const {
}
size_t JSArrayBufferView::byte_offset() const {
return ReadField<size_t>(kByteOffsetOffset);
return ReadBoundedSizeField(kRawByteOffsetOffset);
}
void JSArrayBufferView::set_byte_offset(size_t value) {
WriteField<size_t>(kByteOffsetOffset, value);
WriteBoundedSizeField(kRawByteOffsetOffset, value);
}
size_t JSArrayBufferView::byte_length() const {
return ReadField<size_t>(kByteLengthOffset);
return ReadBoundedSizeField(kRawByteLengthOffset);
}
void JSArrayBufferView::set_byte_length(size_t value) {
WriteField<size_t>(kByteLengthOffset, value);
WriteBoundedSizeField(kRawByteLengthOffset, value);
}
bool JSArrayBufferView::WasDetached() const {
@ -250,15 +258,15 @@ inline void JSTypedArray::ForFixedTypedArray(ExternalArrayType array_type,
size_t JSTypedArray::length() const {
DCHECK(!is_length_tracking());
DCHECK(!is_backed_by_rab());
return ReadField<size_t>(kLengthOffset);
return ReadBoundedSizeField(kRawLengthOffset);
}
size_t JSTypedArray::LengthUnchecked() const {
return ReadField<size_t>(kLengthOffset);
return ReadBoundedSizeField(kRawLengthOffset);
}
void JSTypedArray::set_length(size_t value) {
WriteField<size_t>(kLengthOffset, value);
WriteBoundedSizeField(kRawLengthOffset, value);
}
DEF_GETTER(JSTypedArray, external_pointer, Address) {

View File

@ -28,7 +28,9 @@ class JSArrayBuffer
// On 32-bit architectures we limit this to 2GiB, so that
// we can continue to use CheckBounds with the Unsigned31
// restriction for the length.
#if V8_HOST_ARCH_32_BIT
#if V8_ENABLE_SANDBOX
static constexpr size_t kMaxByteLength = kMaxSafeBufferSizeForSandbox;
#elif V8_HOST_ARCH_32_BIT
static constexpr size_t kMaxByteLength = kMaxInt;
#else
static constexpr size_t kMaxByteLength = kMaxSafeInteger;
@ -37,6 +39,9 @@ class JSArrayBuffer
// [byte_length]: length in bytes
DECL_PRIMITIVE_ACCESSORS(byte_length, size_t)
// [max_byte_length]: maximum length in bytes
DECL_PRIMITIVE_ACCESSORS(max_byte_length, size_t)
// [backing_store]: backing memory for this array
// It should not be assumed that this will be nullptr for empty ArrayBuffers.
DECL_GETTER(backing_store, void*)
@ -257,10 +262,10 @@ class JSArrayBufferView
DECL_BOOLEAN_ACCESSORS(is_backed_by_rab)
inline bool IsVariableLength() const;
static constexpr int kEndOfTaggedFieldsOffset = kByteOffsetOffset;
static constexpr int kEndOfTaggedFieldsOffset = kRawByteOffsetOffset;
static_assert(IsAligned(kByteOffsetOffset, kUIntptrSize));
static_assert(IsAligned(kByteLengthOffset, kUIntptrSize));
static_assert(IsAligned(kRawByteOffsetOffset, kUIntptrSize));
static_assert(IsAligned(kRawByteLengthOffset, kUIntptrSize));
TQ_OBJECT_CONSTRUCTORS(JSArrayBufferView)
};
@ -271,6 +276,7 @@ class JSTypedArray
// TODO(v8:4153): This should be equal to JSArrayBuffer::kMaxByteLength
// eventually.
static constexpr size_t kMaxLength = v8::TypedArray::kMaxLength;
static_assert(kMaxLength <= JSArrayBuffer::kMaxByteLength);
// [length]: length of typed array in elements.
DECL_PRIMITIVE_GETTER(length, size_t)

View File

@ -12,8 +12,10 @@ bitfield struct JSArrayBufferFlags extends uint32 {
}
extern class JSArrayBuffer extends JSObjectWithEmbedderSlots {
byte_length: uintptr;
max_byte_length: uintptr;
// A BoundedSize if the sandbox is enabled
raw_byte_length: uintptr;
// A BoundedSize if the sandbox is enabled
raw_max_byte_length: uintptr;
// A SandboxedPtr if the sandbox is enabled
backing_store: RawPtr;
extension: RawPtr;
@ -23,6 +25,11 @@ extern class JSArrayBuffer extends JSObjectWithEmbedderSlots {
@ifnot(TAGGED_SIZE_8_BYTES) optional_padding: void;
}
extern operator '.byte_length' macro LoadJSArrayBufferByteLength(JSArrayBuffer):
uintptr;
extern operator '.max_byte_length' macro LoadJSArrayBufferMaxByteLength(
JSArrayBuffer): uintptr;
extern operator '.backing_store_ptr' macro LoadJSArrayBufferBackingStorePtr(
JSArrayBuffer): RawPtr;
@ -55,14 +62,25 @@ bitfield struct JSArrayBufferViewFlags extends uint32 {
@abstract
extern class JSArrayBufferView extends JSObjectWithEmbedderSlots {
buffer: JSArrayBuffer;
byte_offset: uintptr;
byte_length: uintptr;
// A BoundedSize if the sandbox is enabled
raw_byte_offset: uintptr;
// A BoundedSize if the sandbox is enabled
raw_byte_length: uintptr;
bit_field: JSArrayBufferViewFlags;
// Pads header size to be a multiple of kTaggedSize.
@if(TAGGED_SIZE_8_BYTES) optional_padding: uint32;
@ifnot(TAGGED_SIZE_8_BYTES) optional_padding: void;
}
extern operator '.byte_offset' macro LoadJSArrayBufferViewByteOffset(
JSArrayBufferView): uintptr;
extern operator '.byte_offset=' macro StoreJSArrayBufferViewByteOffset(
JSArrayBufferView, uintptr): void;
extern operator '.byte_length' macro LoadJSArrayBufferViewByteLength(
JSArrayBufferView): uintptr;
extern operator '.byte_length=' macro StoreJSArrayBufferViewByteLength(
JSArrayBufferView, uintptr): void;
@export
macro IsVariableLengthJSArrayBufferView(array: JSArrayBufferView): bool {
return array.bit_field.is_length_tracking || array.bit_field.is_backed_by_rab;
@ -88,12 +106,17 @@ macro LoadJSArrayBufferViewByteLength(
}
extern class JSTypedArray extends JSArrayBufferView {
length: uintptr;
// A BoundedSize if the sandbox is enabled
raw_length: uintptr;
// A SandboxedPtr if the sandbox is enabled
external_pointer: RawPtr;
base_pointer: ByteArray|Smi;
}
extern operator '.length' macro LoadJSTypedArrayLength(JSTypedArray): uintptr;
extern operator '.length=' macro StoreJSTypedArrayLength(
JSTypedArray, uintptr): void;
@export
macro IsOnHeapTypedArray(array: JSTypedArray): bool {
// See JSTypedArray::is_on_heap()

View File

@ -43,6 +43,7 @@
#include "src/objects/tagged-impl-inl.h"
#include "src/objects/tagged-index.h"
#include "src/objects/templates.h"
#include "src/sandbox/bounded-size-inl.h"
#include "src/sandbox/external-pointer-inl.h"
#include "src/sandbox/sandboxed-pointer-inl.h"
@ -663,6 +664,14 @@ void Object::WriteSandboxedPointerField(size_t offset, Isolate* isolate,
PtrComprCageBase(isolate), value);
}
size_t Object::ReadBoundedSizeField(size_t offset) const {
return i::ReadBoundedSizeField(field_address(offset));
}
void Object::WriteBoundedSizeField(size_t offset, size_t value) {
i::WriteBoundedSizeField(field_address(offset), value);
}
template <ExternalPointerTag tag>
void Object::InitExternalPointerField(size_t offset, Isolate* isolate,
Address value) {

View File

@ -743,6 +743,12 @@ class Object : public TaggedImpl<HeapObjectReferenceType::STRONG, Address> {
inline void WriteSandboxedPointerField(size_t offset, Isolate* isolate,
Address value);
//
// BoundedSize field accessors.
//
inline size_t ReadBoundedSizeField(size_t offset) const;
inline void WriteBoundedSizeField(size_t offset, size_t value);
//
// ExternalPointer_t field accessors.
//

View File

@ -0,0 +1,36 @@
// Copyright 2022 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_BOUNDED_SIZE_INL_H_
#define V8_SANDBOX_BOUNDED_SIZE_INL_H_
#include "include/v8-internal.h"
#include "src/common/ptr-compr-inl.h"
#include "src/sandbox/sandbox.h"
#include "src/sandbox/sandboxed-pointer.h"
namespace v8::internal {
V8_INLINE size_t ReadBoundedSizeField(Address field_address) {
#ifdef V8_ENABLE_SANDBOX
size_t raw_value = base::ReadUnalignedValue<size_t>(field_address);
return raw_value >> kBoundedSizeShift;
#else
return ReadMaybeUnalignedValue<size_t>(field_address);
#endif
}
V8_INLINE void WriteBoundedSizeField(Address field_address, size_t value) {
#ifdef V8_ENABLE_SANDBOX
DCHECK_LE(value, kMaxSafeBufferSizeForSandbox);
size_t raw_value = value << kBoundedSizeShift;
base::WriteUnalignedValue<size_t>(field_address, raw_value);
#else
WriteMaybeUnalignedValue<size_t>(field_address, value);
#endif
}
} // namespace v8::internal
#endif // V8_SANDBOX_BOUNDED_SIZE_INL_H_

View File

@ -0,0 +1,28 @@
// Copyright 2022 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_BOUNDED_SIZE_H_
#define V8_SANDBOX_BOUNDED_SIZE_H_
#include "src/common/globals.h"
namespace v8::internal {
//
// BoundedSize accessors.
//
// A BoundedSize is just a regular size_t when the sandbox is disabled.
// However, when the sandbox is enabled, a BoundedLength is guaranteed to be in
// the range [0, kMaxSafeBufferSizeForSandbox]. This property is required to
// ensure safe access to variable-sized buffers, in particular ArrayBuffers and
// their views, located inside the sandbox.
//
V8_INLINE size_t ReadBoundedLengthField(Address field_address);
V8_INLINE void WriteBoundedLengthField(Address field_address, size_t value);
} // namespace v8::internal
#endif // V8_SANDBOX_BOUNDED_SIZE_H_

View File

@ -277,13 +277,13 @@ extras_accessors = [
'FixedArray, data, uintptr_t, kHeaderSize',
'BytecodeArray, data, uintptr_t, kHeaderSize',
'JSArrayBuffer, backing_store, uintptr_t, kBackingStoreOffset',
'JSArrayBuffer, byte_length, size_t, kByteLengthOffset',
'JSArrayBufferView, byte_length, size_t, kByteLengthOffset',
'JSArrayBufferView, byte_offset, size_t, kByteOffsetOffset',
'JSArrayBuffer, byte_length, size_t, kRawByteLengthOffset',
'JSArrayBufferView, byte_length, size_t, kRawByteLengthOffset',
'JSArrayBufferView, byte_offset, size_t, kRawByteOffsetOffset',
'JSDate, value, Object, kValueOffset',
'JSRegExp, source, Object, kSourceOffset',
'JSTypedArray, external_pointer, uintptr_t, kExternalPointerOffset',
'JSTypedArray, length, Object, kLengthOffset',
'JSTypedArray, length, Object, kRawLengthOffset',
'Map, instance_size_in_words, char, kInstanceSizeInWordsOffset',
'Map, inobject_properties_start_or_constructor_function_index, char, kInobjectPropertiesStartOrConstructorFunctionIndexOffset',
'Map, instance_type, uint16_t, kInstanceTypeOffset',
@ -319,7 +319,7 @@ extras_accessors = [
'ThinString, actual, String, kActualOffset',
'Symbol, name, Object, kDescriptionOffset',
'FixedArrayBase, length, SMI, kLengthOffset',
];
]
#
# The following is a whitelist of classes we expect to find when scanning the