Commit f29ae51c authored by Samuel Groß's avatar Samuel Groß Committed by V8 LUCI CQ

[sandbox] Initialize the raw part of EmbedderDataSlots to zero

This simplifies various bits of logic around EmbedderDataSlots as the
raw part will now always contain a valid index into an external pointer
table entry.

This CL also unifies the initialization of EmbedderDataSlots by
providing a EmbedderDataSlots::Initialize method and adds more
documentation about the layout of EmbedderDataSlots in the different
configurations.

Bug: v8:10391
Change-Id: Ie952598898a7a6c9d40b28d3a7370bfc1291bcf0
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/+/3472495Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Reviewed-by: 's avatarMichael Lippautz <mlippautz@chromium.org>
Reviewed-by: 's avatarIgor Sheludko <ishell@chromium.org>
Commit-Queue: Samuel Groß <saelo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79384}
parent 30f9c924
......@@ -30,9 +30,8 @@ bool LocalEmbedderHeapTracer::ExtractWrappableInfo(
Isolate* isolate, const WrapperDescriptor& wrapper_descriptor,
const EmbedderDataSlot& type_slot, const EmbedderDataSlot& instance_slot,
WrapperInfo* info) {
if (type_slot.ToAlignedPointerSafe(isolate, &info->first) && info->first &&
instance_slot.ToAlignedPointerSafe(isolate, &info->second) &&
info->second) {
if (type_slot.ToAlignedPointer(isolate, &info->first) && info->first &&
instance_slot.ToAlignedPointer(isolate, &info->second) && info->second) {
return (wrapper_descriptor.embedder_id_for_garbage_collected ==
WrapperDescriptor::kUnknownEmbedderId) ||
(*static_cast<uint16_t*>(info->first) ==
......
......@@ -526,10 +526,10 @@ Handle<EmbedderDataArray> Factory::NewEmbedderDataArray(int length) {
array.set_length(length);
if (length > 0) {
ObjectSlot start(array.slots_start());
ObjectSlot end(array.slots_end());
size_t slot_count = end - start;
MemsetTagged(start, *undefined_value(), slot_count);
for (int i = 0; i < length; i++) {
// TODO(v8): consider initializing embedded data array with Smi::zero().
EmbedderDataSlot(array, i).Initialize(*undefined_value());
}
}
return handle(array, isolate());
}
......@@ -2010,10 +2010,9 @@ void initialize_length<PropertyArray>(PropertyArray array, int length) {
array.initialize_length(length);
}
inline void ZeroEmbedderFields(i::JSObject obj) {
int count = obj.GetEmbedderFieldCount();
for (int i = 0; i < count; i++) {
obj.SetEmbedderField(i, Smi::zero());
inline void InitEmbedderFields(i::JSObject obj, i::Object initial_value) {
for (int i = 0; i < obj.GetEmbedderFieldCount(); i++) {
EmbedderDataSlot(obj, i).Initialize(initial_value);
}
}
......@@ -2960,7 +2959,7 @@ Handle<JSArrayBufferView> Factory::NewJSArrayBufferView(
raw.set_byte_offset(byte_offset);
raw.set_byte_length(byte_length);
raw.set_bit_field(0);
ZeroEmbedderFields(raw);
InitEmbedderFields(raw, Smi::zero());
DCHECK_EQ(raw.GetEmbedderFieldCount(),
v8::ArrayBufferView::kEmbedderFieldCount);
return array_buffer_view;
......@@ -3874,7 +3873,7 @@ Handle<JSPromise> Factory::NewJSPromiseWithoutHook() {
JSPromise raw = *promise;
raw.set_reactions_or_result(Smi::zero(), SKIP_WRITE_BARRIER);
raw.set_flags(0);
ZeroEmbedderFields(*promise);
InitEmbedderFields(*promise, Smi::zero());
DCHECK_EQ(raw.GetEmbedderFieldCount(), v8::Promise::kEmbedderFieldCount);
return promise;
}
......
......@@ -217,20 +217,6 @@ class MarkingVisitorBase : public HeapVisitor<int, ConcreteVisitor> {
#endif // V8_SANDBOXED_EXTERNAL_POINTERS
}
V8_INLINE void VisitEmbedderDataSlot(HeapObject host,
EmbedderDataSlot slot) final {
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
// When sandboxed external pointers are enabled, EmbedderDataSlots may
// contain an external pointer, which must be marked as alive.
base::Atomic32* ptr = reinterpret_cast<base::Atomic32*>(
slot.address() + EmbedderDataSlot::kRawPayloadOffset);
uint32_t index = base::Relaxed_Load(ptr) >> kExternalPointerIndexShift;
if (external_pointer_table_->IsValidIndex(index)) {
external_pointer_table_->Mark(index);
}
#endif // V8_SANDBOXED_EXTERNAL_POINTERS
}
protected:
ConcreteVisitor* concrete_visitor() {
return static_cast<ConcreteVisitor*>(this);
......
......@@ -31,6 +31,17 @@ EmbedderDataSlot::EmbedderDataSlot(JSObject object, int embedder_field_index)
EmbedderDataSlot::EmbedderDataSlot(const EmbedderDataSlotSnapshot& snapshot)
: SlotBase(reinterpret_cast<Address>(&snapshot)) {}
void EmbedderDataSlot::Initialize(Object initial_value) {
// TODO(v8) initialize the slot with Smi::zero() instead. This'll also
// guarantee that we don't need a write barrier.
DCHECK(initial_value.IsSmi() ||
ReadOnlyHeap::Contains(HeapObject::cast(initial_value)));
ObjectSlot(address() + kTaggedPayloadOffset).Relaxed_Store(initial_value);
#ifdef V8_COMPRESS_POINTERS
ObjectSlot(address() + kRawPayloadOffset).Relaxed_Store(Smi::zero());
#endif
}
Object EmbedderDataSlot::load_tagged() const {
return ObjectSlot(address() + kTaggedPayloadOffset).Relaxed_Load();
}
......@@ -78,10 +89,10 @@ bool EmbedderDataSlot::ToAlignedPointer(Isolate* isolate,
// phase which is propely synched with GC (concurrent marker may still look
// at the tagged part of the embedder slot but read-only access is ok).
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
// This might crash if the value is not a valid index.
*out_pointer = reinterpret_cast<void*>(ReadExternalPointerField(
address() + kRawPayloadOffset, isolate, kEmbedderDataSlotPayloadTag));
// We don't actually care if the pointer is aligned or not...
// The raw part must always contain a valid external pointer table index.
*out_pointer = reinterpret_cast<void*>(
ReadExternalPointerField(address() + kExternalPointerOffset, isolate,
kEmbedderDataSlotPayloadTag));
return true;
#else
Address raw_value;
......@@ -99,39 +110,13 @@ bool EmbedderDataSlot::ToAlignedPointer(Isolate* isolate,
#endif // V8_SANDBOXED_EXTERNAL_POINTERS
}
bool EmbedderDataSlot::ToAlignedPointerSafe(Isolate* isolate,
void** out_pointer) const {
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
uint32_t index = base::Memory<uint32_t>(address() + kRawPayloadOffset);
if (!HAS_SMI_TAG(index)) return false;
index >>= kExternalPointerIndexShift;
Address raw_value;
if (isolate->external_pointer_table().IsValidIndex(index)) {
raw_value = isolate->external_pointer_table().Get(
index, kEmbedderDataSlotPayloadTag);
*out_pointer = reinterpret_cast<void*>(raw_value);
// The index being valid does not guarantee that this slot contains an
// external pointer. After initialization, the raw part will contain the
// "undefined" value (see Factory::InitializeJSObjectBody) which could look
// like an external pointer table index as well. To deal with that, we also
// check that the returned value has the embedder data slot tag, since
// otherwise the pointer would be invalid.
// TODO(v8:10391) maybe initialize the slot to zero to avoid this issue.
return (raw_value & kExternalPointerTagMask) == 0;
}
return false;
#else
return ToAlignedPointer(isolate, out_pointer);
#endif // V8_SANDBOXED_EXTERNAL_POINTERS
}
bool EmbedderDataSlot::store_aligned_pointer(Isolate* isolate, void* ptr) {
Address value = reinterpret_cast<Address>(ptr);
if (!HAS_SMI_TAG(value)) return false;
#ifdef V8_SANDBOXED_EXTERNAL_POINTERS
DCHECK_EQ(0, value & kExternalPointerTagMask);
// This also mark the entry as alive until the next GC.
InitExternalPointerField(address() + kRawPayloadOffset, isolate, value,
InitExternalPointerField(address() + kExternalPointerOffset, isolate, value,
kEmbedderDataSlotPayloadTag);
ObjectSlot(address() + kTaggedPayloadOffset).Relaxed_Store(Smi::zero());
return true;
......
......@@ -32,6 +32,63 @@ class Object;
class EmbedderDataSlot
: public SlotBase<EmbedderDataSlot, Address, kTaggedSize> {
public:
#if defined(V8_SANDBOXED_EXTERNAL_POINTERS)
// When the sandbox is enabled, an EmbedderDataSlot always contains a valid
// external pointer table index (initially, zero) in it's "raw" part and a
// valid tagged value in its 32-bit "tagged" part.
//
// Layout (sandbox):
// +-----------------------------------+-----------------------------------+
// | Tagged (Smi/CompressedPointer) | External Pointer Table Index |
// +-----------------------------------+-----------------------------------+
// ^ ^
// kTaggedPayloadOffset kRawPayloadOffset
// kExternalPointerOffset
static constexpr int kTaggedPayloadOffset = 0;
static constexpr int kRawPayloadOffset = kTaggedSize;
static constexpr int kExternalPointerOffset = kRawPayloadOffset;
#elif defined(V8_COMPRESS_POINTERS) && defined(V8_TARGET_BIG_ENDIAN)
// The raw payload is located in the other "tagged" part of the full pointer
// and cotains the upper part of an aligned address. The raw part is not
// expected to look like a tagged value.
//
// Layout (big endian pointer compression):
// +-----------------------------------+-----------------------------------+
// | External Pointer (high word) | Tagged (Smi/CompressedPointer) |
// | | OR External Pointer (low word) |
// +-----------------------------------+-----------------------------------+
// ^ ^
// kRawPayloadOffset kTaggedayloadOffset
// kExternalPointerOffset
static constexpr int kExternalPointerOffset = 0;
static constexpr int kRawPayloadOffset = 0;
static constexpr int kTaggedPayloadOffset = kTaggedSize;
#elif defined(V8_COMPRESS_POINTERS) && defined(V8_TARGET_LITTLE_ENDIAN)
// Layout (little endian pointer compression):
// +-----------------------------------+-----------------------------------+
// | Tagged (Smi/CompressedPointer) | External Pointer (high word) |
// | OR External Pointer (low word) | |
// +-----------------------------------+-----------------------------------+
// ^ ^
// kTaggedPayloadOffset kRawPayloadOffset
// kExternalPointerOffset
static constexpr int kExternalPointerOffset = 0;
static constexpr int kTaggedPayloadOffset = 0;
static constexpr int kRawPayloadOffset = kTaggedSize;
#else
// Layout (no pointer compression):
// +-----------------------------------------------------------------------+
// | Tagged (Smi/Pointer) OR External Pointer |
// +-----------------------------------------------------------------------+
// ^
// kTaggedPayloadOffset
// kExternalPointerOffset
static constexpr int kTaggedPayloadOffset = 0;
static constexpr int kExternalPointerOffset = 0;
#endif // V8_SANDBOXED_EXTERNAL_POINTERS
static constexpr int kRequiredPtrAlignment = kSmiTagSize;
using EmbedderDataSlotSnapshot = Address;
V8_INLINE static void PopulateEmbedderDataSnapshot(Map map,
JSObject js_object,
......@@ -43,25 +100,11 @@ class EmbedderDataSlot
V8_INLINE EmbedderDataSlot(JSObject object, int embedder_field_index);
V8_INLINE explicit EmbedderDataSlot(const EmbedderDataSlotSnapshot& snapshot);
#if defined(V8_TARGET_BIG_ENDIAN) && defined(V8_COMPRESS_POINTERS)
static constexpr int kTaggedPayloadOffset = kTaggedSize;
#else
static constexpr int kTaggedPayloadOffset = 0;
#endif
#ifdef V8_COMPRESS_POINTERS
// The raw payload is located in the other "tagged" part of the full pointer
// and cotains the upper part of aligned address. The raw part is not expected
// to look like a tagged value.
// When V8_SANDBOXED_EXTERNAL_POINTERS is defined the raw payload contains an
// index into the external pointer table.
static constexpr int kRawPayloadOffset = kTaggedSize - kTaggedPayloadOffset;
#endif
static constexpr int kRequiredPtrAlignment = kSmiTagSize;
// Opaque type used for storing raw embedder data.
using RawData = Address;
V8_INLINE void Initialize(Object initial_value);
V8_INLINE Object load_tagged() const;
V8_INLINE void store_smi(Smi value);
......@@ -82,20 +125,6 @@ class EmbedderDataSlot
// is undefined behaviour and most likely result in crashes.
V8_INLINE bool ToAlignedPointer(Isolate* isolate, void** out_result) const;
// Same as ToAlignedPointer() but with a workaround for sandboxed external
// pointers. When sandboxed external pointers are enabled, this method
// doesn't crash when the raw part of the slot contains "undefined" instead
// of a valid external table entry index (see
// Factory::InitializeJSObjectBody() for details). Returns true if this slot
// contains a valid external pointer, false otherwise.
//
// TODO(v8:10391) we could instead initialize the raw part to zero.
//
// Call this function if you are not sure whether the slot contains valid
// external pointer or not.
V8_INLINE bool ToAlignedPointerSafe(Isolate* isolate,
void** out_result) const;
// Returns true if the pointer was successfully stored or false it the pointer
// was improperly aligned.
V8_INLINE V8_WARN_UNUSED_RESULT bool store_aligned_pointer(Isolate* isolate,
......
......@@ -441,11 +441,33 @@ void JSObject::InitializeBody(Map map, int start_offset,
MapWord filler_map, Object undefined_filler) {
int size = map.instance_size();
int offset = start_offset;
int embedder_field_start = GetEmbedderFieldsStartOffset(map);
int embedder_field_count = GetEmbedderFieldCount(map);
if (embedder_field_count) {
// fill start with references to the undefined value object
DCHECK_LE(offset, embedder_field_start);
while (offset < embedder_field_start) {
WRITE_FIELD(*this, offset, undefined_filler);
offset += kTaggedSize;
}
// initialize embedder data slots
DCHECK_EQ(offset, embedder_field_start);
for (int i = 0; i < embedder_field_count; i++) {
// TODO(v8): consider initializing embedded data slots with Smi::zero().
EmbedderDataSlot(*this, i).Initialize(undefined_filler);
offset += kEmbedderDataSlotSize;
}
}
DCHECK_LE(offset, size);
if (is_slack_tracking_in_progress) {
int end_of_pre_allocated_offset =
size - (map.UnusedPropertyFields() * kTaggedSize);
DCHECK_LE(kHeaderSize, end_of_pre_allocated_offset);
// fill start with references to the undefined value object
DCHECK_LE(offset, end_of_pre_allocated_offset);
// fill pre allocated slots with references to the undefined value object
while (offset < end_of_pre_allocated_offset) {
WRITE_FIELD(*this, offset, undefined_filler);
offset += kTaggedSize;
......@@ -458,7 +480,7 @@ void JSObject::InitializeBody(Map map, int start_offset,
}
} else {
while (offset < size) {
// fill with references to the undefined value object
// fill everything with references to the undefined value object
WRITE_FIELD(*this, offset, undefined_filler);
offset += kTaggedSize;
}
......
......@@ -84,22 +84,24 @@ void BodyDescriptorBase::IterateJSObjectBodyImpl(Map map, HeapObject obj,
ObjectVisitor* v) {
#ifdef V8_COMPRESS_POINTERS
STATIC_ASSERT(kEmbedderDataSlotSize == 2 * kTaggedSize);
int header_size = JSObject::GetHeaderSize(map);
int inobject_fields_offset = map.GetInObjectPropertyOffset(0);
int header_end_offset = JSObject::GetHeaderSize(map);
int inobject_fields_start_offset = map.GetInObjectPropertyOffset(0);
// We are always requested to process header and embedder fields.
DCHECK_LE(inobject_fields_offset, end_offset);
DCHECK_LE(inobject_fields_start_offset, end_offset);
// Embedder fields are located between header and inobject properties.
if (header_size < inobject_fields_offset) {
if (header_end_offset < inobject_fields_start_offset) {
// There are embedder fields.
IteratePointers(obj, start_offset, header_size, v);
// Iterate only tagged payload of the embedder slots and skip raw payload.
DCHECK_EQ(header_size, JSObject::GetEmbedderFieldsStartOffset(map));
for (int offset = header_size + EmbedderDataSlot::kTaggedPayloadOffset;
offset < inobject_fields_offset; offset += kEmbedderDataSlotSize) {
IteratePointer(obj, offset, v);
DCHECK_EQ(header_end_offset, JSObject::GetEmbedderFieldsStartOffset(map));
IteratePointers(obj, start_offset, header_end_offset, v);
for (int offset = header_end_offset; offset < inobject_fields_start_offset;
offset += kEmbedderDataSlotSize) {
IteratePointer(obj, offset + EmbedderDataSlot::kTaggedPayloadOffset, v);
v->VisitExternalPointer(
obj, obj.RawExternalPointerField(
offset + EmbedderDataSlot::kExternalPointerOffset));
}
// Proceed processing inobject properties.
start_offset = inobject_fields_offset;
start_offset = inobject_fields_start_offset;
}
#else
// We store raw aligned pointers as Smis, so it's safe to iterate the whole
......@@ -107,11 +109,6 @@ void BodyDescriptorBase::IterateJSObjectBodyImpl(Map map, HeapObject obj,
STATIC_ASSERT(kEmbedderDataSlotSize == kTaggedSize);
#endif
IteratePointers(obj, start_offset, end_offset, v);
JSObject js_obj = JSObject::cast(obj);
for (int i = 0; i < js_obj.GetEmbedderFieldCount(); i++) {
v->VisitEmbedderDataSlot(obj, EmbedderDataSlot(js_obj, i));
}
}
template <typename ObjectVisitor>
......@@ -998,25 +995,20 @@ class EmbedderDataArray::BodyDescriptor final : public BodyDescriptorBase {
ObjectVisitor* v) {
#ifdef V8_COMPRESS_POINTERS
STATIC_ASSERT(kEmbedderDataSlotSize == 2 * kTaggedSize);
// Iterate only tagged payload of the embedder slots and skip raw payload.
for (int offset = EmbedderDataArray::OffsetOfElementAt(0) +
EmbedderDataSlot::kTaggedPayloadOffset;
for (int offset = EmbedderDataArray::OffsetOfElementAt(0);
offset < object_size; offset += kEmbedderDataSlotSize) {
IteratePointer(obj, offset, v);
IteratePointer(obj, offset + EmbedderDataSlot::kTaggedPayloadOffset, v);
v->VisitExternalPointer(
obj, obj.RawExternalPointerField(
offset + EmbedderDataSlot::kExternalPointerOffset));
}
#else
// We store raw aligned pointers as Smis, so it's safe to iterate the whole
// array.
STATIC_ASSERT(kEmbedderDataSlotSize == kTaggedSize);
IteratePointers(obj, EmbedderDataArray::kHeaderSize, object_size, v);
#endif
EmbedderDataArray array = EmbedderDataArray::cast(obj);
EmbedderDataSlot start(array, 0);
EmbedderDataSlot end(array, array.length());
for (EmbedderDataSlot slot = start; slot < end; ++slot) {
v->VisitEmbedderDataSlot(obj, slot);
}
}
static inline int SizeOf(Map map, HeapObject object) {
......
......@@ -182,11 +182,9 @@ class ObjectVisitor {
// Visits the object's map pointer, decoding as necessary
virtual void VisitMapPointer(HeapObject host) { UNREACHABLE(); }
// Visits an external pointer.
// Visits an external pointer. This is currently only guaranteed to be called
// when the sandbox is enabled.
virtual void VisitExternalPointer(HeapObject host, ExternalPointer_t ptr) {}
// Visits an EmbedderDataslot.
virtual void VisitEmbedderDataSlot(HeapObject host, EmbedderDataSlot slot) {}
};
// Helper version of ObjectVisitor that also takes care of caching base values
......
......@@ -40,9 +40,8 @@ void ExternalPointerTable::Init(Isolate* isolate) {
base::MutexGuard guard(mutex_);
Grow();
// Set up the special null entry. This entry must currently contain nullptr
// so that uninitialized EmbedderDataSlots work correctly. TODO(saelo) maybe
// make entry non-null once EmbedderDataSlots are properly sandboxified.
// Set up the special null entry. This entry must contain nullptr so that
// empty EmbedderDataSlots represent nullptr.
STATIC_ASSERT(kNullExternalPointer == 0);
store(kNullExternalPointer, kNullAddress);
}
......@@ -81,10 +80,6 @@ void ExternalPointerTable::Set(uint32_t index, Address value,
store_atomic(index, value | tag);
}
bool ExternalPointerTable::IsValidIndex(uint32_t index) const {
return index < capacity_ && !is_free(load_atomic(index));
}
uint32_t ExternalPointerTable::Allocate() {
DCHECK(is_initialized());
......
......@@ -83,11 +83,6 @@ class V8_EXPORT_PRIVATE ExternalPointerTable {
// This method is atomic and can be called from background threads.
inline void Set(uint32_t index, Address value, ExternalPointerTag tag);
// Returns true if the entry exists and isn't free.
//
// This method is atomic and can be called from background threads.
inline bool IsValidIndex(uint32_t index) const;
// 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.
......
......@@ -678,8 +678,8 @@ Handle<HeapObject> Deserializer<IsolateT>::ReadObject(SnapshotSpace space) {
JSObject js_obj = JSObject::cast(raw_obj);
for (int i = 0; i < js_obj.GetEmbedderFieldCount(); ++i) {
void* pointer;
CHECK(EmbedderDataSlot(js_obj, i).ToAlignedPointerSafe(
main_thread_isolate(), &pointer));
CHECK(EmbedderDataSlot(js_obj, i).ToAlignedPointer(main_thread_isolate(),
&pointer));
CHECK_NULL(pointer);
}
} else if (raw_obj.IsEmbedderDataArray(cage_base)) {
......@@ -688,7 +688,7 @@ Handle<HeapObject> Deserializer<IsolateT>::ReadObject(SnapshotSpace space) {
EmbedderDataSlot end(array, array.length());
for (EmbedderDataSlot slot = start; slot < end; ++slot) {
void* pointer;
CHECK(slot.ToAlignedPointerSafe(main_thread_isolate(), &pointer));
CHECK(slot.ToAlignedPointer(main_thread_isolate(), &pointer));
CHECK_NULL(pointer);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment