Repository navigation
Expand file tree
/
Copy pathv8-sandbox.h
More file actions
229 lines (198 loc) · 9.7 KB
/
Copy pathv8-sandbox.h
File metadata and controls
229 lines (198 loc) · 9.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// Copyright 2024 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 INCLUDE_V8_SANDBOX_H_
#define INCLUDE_V8_SANDBOX_H_
#include <cstdint>
#include "v8-internal.h" // NOLINT(build/include_directory)
#include "v8config.h" // NOLINT(build/include_directory)
namespace v8 {
/**
* A pointer tag used for wrapping and unwrapping `CppHeap` pointers as used
* with JS API wrapper objects that rely on `v8::Object::Wrap()` and
* `v8::Object::Unwrap()`.
*
* The CppHeapPointers use a range-based type checking scheme, where on access
* to a pointer, the actual type of the pointer is checked to be within a
* specified range of types. This allows supporting type hierarchies, where a
* type check for a supertype must succeed for any subtype.
*
* The tag is currently in practice limited to 15 bits since it needs to fit
* together with a marking bit into the unused parts of a pointer.
*/
enum class CppHeapPointerTag : uint16_t {
kFirstTag = 0,
kNullTag = 0,
// Subtypes of v8::Object::Wrappable [0x0001 .. 0x6fff]
kFirstObjectWrappableTag = 1,
kFirstEmbedderWrappableTag = kFirstObjectWrappableTag,
/**
* The lower type ids are reserved for the embedder to assign. For that, the
* main requirement is that all (transitive) child classes of a given parent
* class have type ids in the same range, and that there are no unrelated
* types in that range. For example, given the following type hierarchy:
*
* A F
* / \
* B E
* / \
* C D
*
* a potential type id assignment that satistifes these requirements is
* {C: 0, D: 1, B: 2, A: 3, E: 4, F: 5}. With that, the type check for type A
* would check for the range [0, 4], while the check for B would check range
* [0, 2], and for F it would simply check [5, 5].
*
* In addition, there is an option for performance tweaks: if the size of the
* type range corresponding to a supertype is a power of two and starts at a
* power of two (e.g. [0x100, 0x13f]), then the compiler can often optimize
* the type check to use even fewer instructions (essentially replace a AND +
* SUB with a single AND).
*/
kLastEmbedderWrappableTag = 0x6eff,
// V8-internal Oilpan objects that inherit from v8::Object::Wrappable.
kFirstV8InternalWrappableTag = 0x6f00,
// Kept temporarily for backwards compatibility with Chromium's
// wrapper_type_info.h across the V8 roll.
kFirstV8InternalTag = kFirstV8InternalWrappableTag,
kLastV8InternalWrappableTag = 0x6fff,
kLastObjectWrappableTag = kLastV8InternalWrappableTag,
// Non-v8::Object::Wrappable CppHeap objects [0x7000 .. 0x7ffc]
kFirstNonWrappableTag = 0x7000,
kFirstV8InternalNonWrappableTag = kFirstNonWrappableTag,
kLastV8InternalNonWrappableTag = 0x70ff,
kFirstEmbedderNonWrappableTag = 0x7100,
kLastEmbedderNonWrappableTag = 0x7ffc,
kLastNonWrappableTag = kLastEmbedderNonWrappableTag,
#if !V8_ENABLE_SANDBOX
// Embedders that use the sandbox should use specific tags for each type.
kDefaultTag = kFirstEmbedderWrappableTag,
#endif // !V8_ENABLE_SANDBOX
kZappedEntryTag = 0x7ffd,
kEvacuationEntryTag = 0x7ffe,
kFreeEntryTag = 0x7fff,
// The tags are limited to 15 bits, so the last tag is 0x7fff.
kLastTag = 0x7fff,
};
using CppHeapPointerTagRange = internal::TagRange<CppHeapPointerTag>;
constexpr CppHeapPointerTagRange kAnyCppHeapPointer(
CppHeapPointerTag::kFirstTag, CppHeapPointerTag::kZappedEntryTag);
// All tags that are used with v8::Object::Wrappable have to be within this
// tag range. The reason is that in some cases, an APIWrapper object has to be
// unwrapped to access the v8::Object::Wrappable base class, e.g. to get type
// information.
constexpr CppHeapPointerTagRange kObjectWrappableTagRange(
CppHeapPointerTag::kFirstObjectWrappableTag,
CppHeapPointerTag::kLastObjectWrappableTag);
// The tag range that embedders can use for their own types that inherit from
// v8::Object::Wrappable.
constexpr CppHeapPointerTagRange kEmbedderWrappableTagRange(
CppHeapPointerTag::kFirstEmbedderWrappableTag,
CppHeapPointerTag::kLastEmbedderWrappableTag);
// The tag range for all non-v8::Object::Wrappable CppHeap objects, both
// V8-internal and embedder-owned.
constexpr CppHeapPointerTagRange kNonWrappableTagRange(
CppHeapPointerTag::kFirstNonWrappableTag,
CppHeapPointerTag::kLastNonWrappableTag);
// The tag range that embedders can use for their own types that do not inherit
// from v8::Object::Wrappable.
constexpr CppHeapPointerTagRange kEmbedderNonWrappableTagRange(
CppHeapPointerTag::kFirstEmbedderNonWrappableTag,
CppHeapPointerTag::kLastEmbedderNonWrappableTag);
static_assert(kObjectWrappableTagRange.Contains(kEmbedderWrappableTagRange));
static_assert(kNonWrappableTagRange.Contains(kEmbedderNonWrappableTagRange));
/**
* Hardware support for the V8 Sandbox.
*
* This is an experimental feature that may change or be removed without
* further notice. Use at your own risk.
*/
class SandboxHardwareSupport {
public:
/**
* Initialize sandbox hardware support. This needs to be called before
* creating any thread that might access sandbox memory since it sets up
* hardware permissions to the memory that will be inherited on clone.
*/
V8_EXPORT static void InitializeBeforeThreadCreation();
};
namespace internal {
#ifdef V8_COMPRESS_POINTERS
V8_INLINE static Address* GetCppHeapPointerTableBase(v8::Isolate* isolate) {
Address addr = reinterpret_cast<Address>(isolate) +
Internals::kIsolateCppHeapPointerTableOffset +
Internals::kExternalEntityTableBasePointerOffset;
return *reinterpret_cast<Address**>(addr);
}
#endif // V8_COMPRESS_POINTERS
template <typename T>
V8_INLINE static T* ReadCppHeapPointerField(v8::Isolate* isolate,
Address heap_object_ptr, int offset,
CppHeapPointerTagRange tag_range) {
// This is a specialized version of the CppHeapPointerTable accessors
// which (1) allows the code to be inlined into the callers for performance
// and (2) is optimized for code size as there are a huge number of callers
// from auto-generated bindings code.
#ifdef V8_COMPRESS_POINTERS
const CppHeapPointerHandle handle =
Internals::ReadRawField<CppHeapPointerHandle>(heap_object_ptr, offset);
const uint32_t index = handle >> kExternalPointerIndexShift;
const Address* table = GetCppHeapPointerTableBase(isolate);
const std::atomic<Address>* ptr =
reinterpret_cast<const std::atomic<Address>*>(&table[index]);
Address entry = std::atomic_load_explicit(ptr, std::memory_order_relaxed);
// Note: the cast to uint32_t is important here. Otherwise, the uint16_t's
// would be promoted to int in the range check below, which would result in
// undefined behavior (signed integer underflow) if the actual value is less
// than the lower bound. Then, the compiler would take advantage of the
// undefined behavior and turn the range check into a simple
// `actual_tag <= last_tag` comparison, which is incorrect.
uint32_t actual_tag = static_cast<uint16_t>(entry);
// The actual_tag is shifted to the left by one and contains the marking
// bit in the LSB. To ignore that during the type check, simply add one to
// the (shifted) range.
constexpr int kTagShift = internal::kCppHeapPointerTagShift;
uint32_t first_tag = static_cast<uint32_t>(tag_range.first) << kTagShift;
uint32_t last_tag = (static_cast<uint32_t>(tag_range.last) << kTagShift) + 1;
// Avoid DCE of the entry logic using volatile.
volatile Address safe_entry;
if (actual_tag >= first_tag && actual_tag <= last_tag) [[likely]] {
safe_entry = entry >> kCppHeapPointerPayloadShift;
} else {
// If the type check failed, we simply return nullptr here. That way:
// 1. The null handle always results in nullptr being returned here, which
// is a desired property. Otherwise, we would need an explicit check for
// the null handle above, and therefore an additional branch. This
// works because the 0th entry of the table always contains nullptr
// tagged with the null tag (i.e. an all-zeros entry). As such,
// regardless of whether the type check succeeds, the result will
// always be nullptr.
// 2. The returned pointer is guaranteed to crash even on platforms with
// top byte ignore (TBI), such as Arm64. The alternative would be to
// simply return the original entry with the left-shifted payload.
// However, due to TBI, an access to that may not always result in a
// crash (specifically, if the second most significant byte happens to
// be zero). In addition, there shouldn't be a difference on Arm64
// between returning nullptr or the original entry, since it will
// simply compile to a `csel x0, x8, xzr, lo` instead of a
// `csel x0, x10, x8, lo` instruction.
// 3. The machine code sequence ends up being pretty short, which is
// important here as this code will be inlined into a lot of functions.
safe_entry = 0;
}
return reinterpret_cast<T*>(safe_entry);
#else // !V8_COMPRESS_POINTERS
return reinterpret_cast<T*>(
Internals::ReadRawField<Address>(heap_object_ptr, offset));
#endif // !V8_COMPRESS_POINTERS
}
// TODO(saelo): temporary workaround needed to introduce range-based type
// checks for the external pointer table. See comment above
// ExternalPointerCanBeEmpty(ExternalPointerTagRange) function for details.
V8_INLINE static constexpr bool ExternalPointerCanBeEmpty(
CppHeapPointerTagRange tag_range) {
return true;
}
} // namespace internal
} // namespace v8
#endif // INCLUDE_V8_SANDBOX_H_