v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
external-pointer-table.h
Go to the documentation of this file.
1// Copyright 2020 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef V8_SANDBOX_EXTERNAL_POINTER_TABLE_H_
6#define V8_SANDBOX_EXTERNAL_POINTER_TABLE_H_
7
8#include "include/v8config.h"
10#include "src/base/memory.h"
11#include "src/common/globals.h"
12#include "src/sandbox/check.h"
16
17#ifdef V8_COMPRESS_POINTERS
18
19namespace v8 {
20namespace internal {
21
22class Isolate;
23class Counters;
24class ReadOnlyArtifacts;
25
40struct ExternalPointerTableEntry {
41 enum class EvacuateMarkMode { kTransferMark, kLeaveUnmarked, kClearMark };
42
43 // Make this entry an external pointer entry containing the given pointer
44 // tagged with the given tag.
45 inline void MakeExternalPointerEntry(Address value, ExternalPointerTag tag,
46 bool mark_as_alive);
47
48 // Load and untag the external pointer stored in this entry.
49 // This entry must be an external pointer entry.
50 // If the specified tag doesn't match the actual tag of this entry, the
51 // resulting pointer will be invalid and cannot be dereferenced.
52 inline Address GetExternalPointer(ExternalPointerTagRange tag_range) const;
53
54 // Tag and store the given external pointer in this entry.
55 // This entry must be an external pointer entry.
56 inline void SetExternalPointer(Address value, ExternalPointerTag tag);
57
58 // Returns true if this entry contains an external pointer with the given tag.
59 inline bool HasExternalPointer(ExternalPointerTagRange tag_range) const;
60
61 // Exchanges the external pointer stored in this entry with the provided one.
62 // Returns the old external pointer. This entry must be an external pointer
63 // entry. If the provided tag doesn't match the tag of the old entry, the
64 // returned pointer will be invalid.
65 inline Address ExchangeExternalPointer(Address value, ExternalPointerTag tag);
66
67 // Load the tag of the external pointer stored in this entry.
68 // This entry must be an external pointer entry.
69 inline ExternalPointerTag GetExternalPointerTag() const;
70
71 // Returns the address of the managed resource contained in this entry or
72 // nullptr if this entry does not reference a managed resource.
73 inline Address ExtractManagedResourceOrNull() const;
74
75 // Invalidate the entry. Any access to a zapped entry will result in an
76 // invalid pointer that will crash upon dereference.
77 inline void MakeZappedEntry();
78
79 // Make this entry a freelist entry, containing the index of the next entry
80 // on the freelist.
81 inline void MakeFreelistEntry(uint32_t next_entry_index);
82
83 // Get the index of the next entry on the freelist. This method may be
84 // called even when the entry is not a freelist entry. However, the result
85 // is only valid if this is a freelist entry. This behaviour is required
86 // for efficient entry allocation, see TryAllocateEntryFromFreelist.
87 inline uint32_t GetNextFreelistEntryIndex() const;
88
89 // Make this entry an evacuation entry containing the address of the handle to
90 // the entry being evacuated.
91 inline void MakeEvacuationEntry(Address handle_location);
92
93 // Returns true if this entry contains an evacuation entry.
94 inline bool HasEvacuationEntry() const;
95
96 // Move the content of this entry into the provided entry, possibly clearing
97 // the marking bit. Used during table compaction and during promotion.
98 // Invalidates the source entry.
99 inline void Evacuate(ExternalPointerTableEntry& dest, EvacuateMarkMode mode);
100
101 // Mark this entry as alive during table garbage collection.
102 inline void Mark();
103
104 static constexpr bool IsWriteProtected = false;
105
106 private:
107 friend class ExternalPointerTable;
108
109 // TODO(saelo): generalize this payload struct and reuse it for all other
110 // pointer table that use type tags. For that, we probably will have to make
111 // this more flexible, allowing shifts and masks to be applied to both the
112 // tag- and payload bits since the tables store the tag bits differently.
113 struct Payload {
114 Payload(Address pointer, ExternalPointerTag tag)
115 : encoded_word_(Tag(pointer, tag)) {}
116
117 static Address Tag(Address pointer, ExternalPointerTag tag) {
118 DCHECK_LE(tag, kLastExternalPointerTag);
119 return pointer | (static_cast<Address>(tag) << kExternalPointerTagShift);
120 }
121
122 static bool CheckTag(Address content, ExternalPointerTagRange tag_range) {
123 // TODO(saelo): use well-known null entries per type tag instead of a
124 // generic null entry. Then this check can be removed.
125 if (ExternalPointerCanBeEmpty(tag_range) && !content) {
126 return true;
127 }
128
129 ExternalPointerTag tag = static_cast<ExternalPointerTag>(
131 return tag_range.Contains(tag);
132 }
133
134 Address Untag(ExternalPointerTagRange tag_range) const {
135 Address content = encoded_word_;
136 SBXCHECK(CheckTag(content, tag_range));
137 return content & kExternalPointerPayloadMask;
138 }
139
140 Address Untag(ExternalPointerTag tag) const {
141 return Untag(ExternalPointerTagRange(tag, tag));
142 }
143
144 bool IsTaggedWithTagIn(ExternalPointerTagRange tag_range) const {
145 return CheckTag(encoded_word_, tag_range);
146 }
147
148 bool IsTaggedWith(ExternalPointerTag tag) const {
149 return IsTaggedWithTagIn(ExternalPointerTagRange(tag));
150 }
151
152 void SetMarkBit() { encoded_word_ |= kExternalPointerMarkBit; }
153
154 void ClearMarkBit() { encoded_word_ &= ~kExternalPointerMarkBit; }
155
156 bool HasMarkBitSet() const {
157 return encoded_word_ & kExternalPointerMarkBit;
158 }
159
160 uint32_t ExtractFreelistLink() const {
161 return static_cast<uint32_t>(encoded_word_);
162 }
163
164 ExternalPointerTag ExtractTag() const {
165 return static_cast<ExternalPointerTag>(
166 (encoded_word_ & kExternalPointerTagMask) >>
168 }
169
170 bool ContainsFreelistLink() const {
171 return IsTaggedWith(kExternalPointerFreeEntryTag);
172 }
173
174 bool ContainsEvacuationEntry() const {
175 return IsTaggedWith(kExternalPointerEvacuationEntryTag);
176 }
177
178 Address ExtractEvacuationEntryHandleLocation() const {
179 return Untag(kExternalPointerEvacuationEntryTag);
180 }
181
182 bool ContainsPointer() const {
183 return !ContainsFreelistLink() && !ContainsEvacuationEntry();
184 }
185
186 bool operator==(Payload other) const {
187 return encoded_word_ == other.encoded_word_;
188 }
189
190 bool operator!=(Payload other) const {
191 return encoded_word_ != other.encoded_word_;
192 }
193
194 private:
195 Address encoded_word_;
196 };
197
198 inline Payload GetRawPayload() {
199 return payload_.load(std::memory_order_relaxed);
200 }
201 inline void SetRawPayload(Payload new_payload) {
202 return payload_.store(new_payload, std::memory_order_relaxed);
203 }
204
205 inline void MaybeUpdateRawPointerForLSan(Address value) {
206#if defined(LEAK_SANITIZER)
207 raw_pointer_for_lsan_ = value;
208#endif // LEAK_SANITIZER
209 }
210
211 // ExternalPointerTable entries consist of a single pointer-sized word
212 // containing a tag and marking bit together with the actual content (e.g. an
213 // external pointer).
214 std::atomic<Payload> payload_;
215
216#if defined(LEAK_SANITIZER)
217 // When LSan is active, it must be able to detect live references to heap
218 // allocations from an external pointer table. It will, however, not be able
219 // to recognize the encoded pointers as they will have their top bits set. So
220 // instead, when LSan is active we use "fat" entries where the 2nd atomic
221 // words contains the unencoded raw pointer which LSan will be able to
222 // recognize as such.
223 // NOTE: THIS MODE IS NOT SECURE! Attackers are able to modify an
224 // ExternalPointerHandle to point to the raw pointer part, not the encoded
225 // part of an entry, thereby bypassing the type checks. If this mode is ever
226 // needed outside of testing environments, then the external pointer
227 // accessors (e.g. in the JIT) need to be made aware that entries are now 16
228 // bytes large so that all entry accesses are again guaranteed to access an
229 // encoded pointer.
230 Address raw_pointer_for_lsan_;
231#endif // LEAK_SANITIZER
232};
233
234#if defined(LEAK_SANITIZER)
235// When LSan is active, we need "fat" entries, see above.
236static_assert(sizeof(ExternalPointerTableEntry) == 16);
237#else
238// We expect ExternalPointerTable entries to consist of a single 64-bit word.
239static_assert(sizeof(ExternalPointerTableEntry) == 8);
240#endif
241
308class V8_EXPORT_PRIVATE ExternalPointerTable
309 : public CompactibleExternalEntityTable<
310 ExternalPointerTableEntry, kExternalPointerTableReservationSize> {
311 using Base =
312 CompactibleExternalEntityTable<ExternalPointerTableEntry,
313 kExternalPointerTableReservationSize>;
314
315#if defined(LEAK_SANITIZER)
316 // When LSan is active, we use "fat" entries, see above.
317 static_assert(kMaxExternalPointers == kMaxCapacity * 2);
318#else
319 static_assert(kMaxExternalPointers == kMaxCapacity);
320#endif
321 static_assert(kSupportsCompaction);
322
323 public:
324 using EvacuateMarkMode = ExternalPointerTableEntry::EvacuateMarkMode;
325
326 ExternalPointerTable() = default;
327 ExternalPointerTable(const ExternalPointerTable&) = delete;
328 ExternalPointerTable& operator=(const ExternalPointerTable&) = delete;
329
330 // The Spaces used by an ExternalPointerTable.
331 struct Space : public Base::Space {
332 public:
333 // During table compaction, we may record the addresses of fields
334 // containing external pointer handles (if they are evacuation candidates).
335 // As such, if such a field is invalidated (for example because the host
336 // object is converted to another object type), we need to be notified of
337 // that. Note that we do not need to care about "re-validated" fields here:
338 // if an external pointer field is first converted to different kind of
339 // field, then again converted to a external pointer field, then it will be
340 // re-initialized, at which point it will obtain a new entry in the
341 // external pointer table which cannot be a candidate for evacuation.
342 inline void NotifyExternalPointerFieldInvalidated(
343 Address field_address, ExternalPointerTagRange tag_range);
344
345 // Not atomic. Mutators and concurrent marking must be paused.
346 void AssertEmpty() { CHECK(segments_.empty()); }
347
348 bool allocate_black() { return allocate_black_; }
349 void set_allocate_black(bool allocate_black) {
350 allocate_black_ = allocate_black;
351 }
352
353 private:
354 bool allocate_black_ = false;
355 };
356
357 // Initializes all slots in the RO space from pre-existing artifacts.
358 void SetUpFromReadOnlyArtifacts(Space* read_only_space,
359 const ReadOnlyArtifacts* artifacts);
360
361 // Retrieves the entry referenced by the given handle.
362 //
363 // This method is atomic and can be called from background threads.
364 inline Address Get(ExternalPointerHandle handle,
365 ExternalPointerTagRange tag_range) const;
366
367 // Sets the entry referenced by the given handle.
368 //
369 // This method is atomic and can be called from background threads.
370 inline void Set(ExternalPointerHandle handle, Address value,
371 ExternalPointerTag tag);
372
373 // Exchanges the entry referenced by the given handle with the given value,
374 // returning the previous value. The same tag is applied both to decode the
375 // previous value and encode the given value.
376 //
377 // This method is atomic and can be called from background threads.
378 inline Address Exchange(ExternalPointerHandle handle, Address value,
379 ExternalPointerTag tag);
380
381 // Retrieves the tag used for the entry referenced by the given handle.
382 //
383 // This method is atomic and can be called from background threads.
384 inline ExternalPointerTag GetTag(ExternalPointerHandle handle) const;
385
386 // Invalidates the entry referenced by the given handle.
387 inline void Zap(ExternalPointerHandle handle);
388
389 // Allocates a new entry in the given space. The caller must provide the
390 // initial value and tag for the entry.
391 //
392 // This method is atomic and can be called from background threads.
393 inline ExternalPointerHandle AllocateAndInitializeEntry(
394 Space* space, Address initial_value, ExternalPointerTag tag);
395
396 // Marks the specified entry as alive.
397 //
398 // If the space to which the entry belongs is currently being compacted, this
399 // may also mark the entry for evacuation for which the location of the
400 // handle is required. See the comments about the compaction algorithm for
401 // more details.
402 //
403 // This method is atomic and can be called from background threads.
404 inline void Mark(Space* space, ExternalPointerHandle handle,
405 Address handle_location);
406
407 // Evacuate the specified entry from one space to another, updating the handle
408 // location in place.
409 //
410 // This method is not atomic and can be called only when the mutator is
411 // paused.
412 inline void Evacuate(Space* from_space, Space* to_space,
413 ExternalPointerHandle handle, Address handle_location,
414 EvacuateMarkMode mode);
415
416 // Evacuate all segments from from_space to to_space, leaving from_space empty
417 // with an empty free list. Then free unmarked entries, finishing compaction
418 // if it was running, and collecting freed entries onto to_space's free list.
419 //
420 // The from_space will be left empty with an empty free list.
421 //
422 // This method must only be called while mutator threads are stopped as it is
423 // not safe to allocate table entries while the table is being swept.
424 //
425 // SweepAndCompact is the same as EvacuateAndSweepAndCompact, except without
426 // the evacuation phase.
427 //
428 // Sweep is the same as SweepAndCompact, but assumes that compaction was not
429 // running.
430 //
431 // Returns the number of live entries after sweeping.
432 uint32_t EvacuateAndSweepAndCompact(Space* to_space, Space* from_space,
433 Counters* counters);
434 uint32_t SweepAndCompact(Space* space, Counters* counters);
435 uint32_t Sweep(Space* space, Counters* counters);
436
437 inline bool Contains(Space* space, ExternalPointerHandle handle) const;
438
439 // A resource outside of the V8 heap whose lifetime is tied to something
440 // inside the V8 heap. This class makes that relationship explicit.
441 //
442 // Knowing about such objects is important for the sandbox to guarantee
443 // memory safety. In particular, it is necessary to prevent issues where the
444 // external resource is destroyed before the entry in the
445 // ExternalPointerTable (EPT) that references it is freed. In that case, the
446 // EPT entry would then contain a dangling pointer which could be abused by
447 // an attacker to cause a use-after-free outside of the sandbox.
448 //
449 // Currently, this is solved by remembering the EPT entry in the external
450 // object and zapping/invalidating it when the resource is destroyed. An
451 // alternative approach that might be preferable in the future would be to
452 // destroy the external resource only when the EPT entry is freed. This would
453 // avoid the need to manually keep track of the entry, for example.
454 class ManagedResource : public Malloced {
455 public:
456 // This method must be called before destroying the external resource.
457 // When the sandbox is enabled, it will take care of zapping its EPT entry.
458 inline void ZapExternalPointerTableEntry();
459
460 private:
461 friend class ExternalPointerTable;
462 // Currently required for snapshot stress mode, see deserializer.cc.
463 template <typename IsolateT>
464 friend class Deserializer;
465
466 ExternalPointerTable* owning_table_ = nullptr;
468 };
469
470 private:
471 static inline bool IsValidHandle(ExternalPointerHandle handle);
472 static inline uint32_t HandleToIndex(ExternalPointerHandle handle);
473 static inline ExternalPointerHandle IndexToHandle(uint32_t index);
474
475 inline void TakeOwnershipOfManagedResourceIfNecessary(
476 Address value, ExternalPointerHandle handle, ExternalPointerTag tag);
477 inline void FreeManagedResourceIfPresent(uint32_t entry_index);
478
479 void ResolveEvacuationEntryDuringSweeping(
480 uint32_t index, ExternalPointerHandle* handle_location,
481 uint32_t start_of_evacuation_area);
482};
483
484} // namespace internal
485} // namespace v8
486
487#endif // V8_COMPRESS_POINTERS
488
489#endif // V8_SANDBOX_EXTERNAL_POINTER_TABLE_H_
#define SBXCHECK(condition)
Definition check.h:61
uintptr_t Address
Definition memory.h:13
bool operator==(PointerWithPayload< PointerType, PayloadType, NumPayloadBits > lhs, PointerWithPayload< PointerType, PayloadType, NumPayloadBits > rhs)
V8_INLINE const Operation & Get(const Graph &graph, OpIndex index)
Definition graph.h:1231
void Exchange(LiftoffAssembler *lasm, Register dst, Register lhs, Register rhs)
constexpr uint64_t kExternalPointerTagShift
bool operator!=(ExternalReference lhs, ExternalReference rhs)
constexpr uint64_t kExternalPointerPayloadMask
constexpr uint64_t kExternalPointerTagMask
TagRange< ExternalPointerTag > ExternalPointerTagRange
constexpr size_t kMaxExternalPointers
constexpr ExternalPointerHandle kNullExternalPointerHandle
uint32_t ExternalPointerHandle
constexpr uint64_t kExternalPointerMarkBit
#define DCHECK_LE(v1, v2)
Definition logging.h:490
#define CHECK(condition)
Definition logging.h:124
#define V8_EXPORT_PRIVATE
Definition macros.h:460
std::unique_ptr< ValueMirror > value