|
|
|||
File indexing completed on 2026-01-06 10:14:50
0001 // Copyright 2024 the V8 project authors. All rights reserved. 0002 // Use of this source code is governed by a BSD-style license that can be 0003 // found in the LICENSE file. 0004 0005 #ifndef INCLUDE_V8_SANDBOX_H_ 0006 #define INCLUDE_V8_SANDBOX_H_ 0007 0008 #include <cstdint> 0009 0010 #include "v8-internal.h" // NOLINT(build/include_directory) 0011 #include "v8config.h" // NOLINT(build/include_directory) 0012 0013 namespace v8 { 0014 0015 /** 0016 * A pointer tag used for wrapping and unwrapping `CppHeap` pointers as used 0017 * with JS API wrapper objects that rely on `v8::Object::Wrap()` and 0018 * `v8::Object::Unwrap()`. 0019 * 0020 * The CppHeapPointers use a range-based type checking scheme, where on access 0021 * to a pointer, the actual type of the pointer is checked to be within a 0022 * specified range of types. This allows supporting type hierarchies, where a 0023 * type check for a supertype must succeed for any subtype. 0024 * 0025 * The tag is currently in practice limited to 15 bits since it needs to fit 0026 * together with a marking bit into the unused parts of a pointer. 0027 */ 0028 enum class CppHeapPointerTag : uint16_t { 0029 kFirstTag = 0, 0030 kNullTag = 0, 0031 0032 /** 0033 * The lower type ids are reserved for the embedder to assign. For that, the 0034 * main requirement is that all (transitive) child classes of a given parent 0035 * class have type ids in the same range, and that there are no unrelated 0036 * types in that range. For example, given the following type hierarchy: 0037 * 0038 * A F 0039 * / \ 0040 * B E 0041 * / \ 0042 * C D 0043 * 0044 * a potential type id assignment that satistifes these requirements is 0045 * {C: 0, D: 1, B: 2, A: 3, E: 4, F: 5}. With that, the type check for type A 0046 * would check for the range [0, 4], while the check for B would check range 0047 * [0, 2], and for F it would simply check [5, 5]. 0048 * 0049 * In addition, there is an option for performance tweaks: if the size of the 0050 * type range corresponding to a supertype is a power of two and starts at a 0051 * power of two (e.g. [0x100, 0x13f]), then the compiler can often optimize 0052 * the type check to use even fewer instructions (essentially replace a AND + 0053 * SUB with a single AND). 0054 */ 0055 0056 kDefaultTag = 0x7000, 0057 0058 kZappedEntryTag = 0x7ffd, 0059 kEvacuationEntryTag = 0x7ffe, 0060 kFreeEntryTag = 0x7fff, 0061 // The tags are limited to 15 bits, so the last tag is 0x7fff. 0062 kLastTag = 0x7fff, 0063 }; 0064 0065 // Convenience struct to represent tag ranges. This is used for type checks 0066 // against supertypes, which cover a range of types (their subtypes). 0067 // Both the lower- and the upper bound are inclusive. In other words, this 0068 // struct represents the range [lower_bound, upper_bound]. 0069 // TODO(saelo): reuse internal::TagRange here. 0070 struct CppHeapPointerTagRange { 0071 constexpr CppHeapPointerTagRange(CppHeapPointerTag lower, 0072 CppHeapPointerTag upper) 0073 : lower_bound(lower), upper_bound(upper) {} 0074 CppHeapPointerTag lower_bound; 0075 CppHeapPointerTag upper_bound; 0076 0077 // Check whether the tag of the given CppHeapPointerTable entry is within 0078 // this range. This method encodes implementation details of the 0079 // CppHeapPointerTable, which is necessary as it is used by 0080 // ReadCppHeapPointerField below. 0081 // Returns true if the check is successful and the tag of the given entry is 0082 // within this range, false otherwise. 0083 bool CheckTagOf(uint64_t entry) { 0084 // Note: the cast to uint32_t is important here. Otherwise, the uint16_t's 0085 // would be promoted to int in the range check below, which would result in 0086 // undefined behavior (signed integer undeflow) if the actual value is less 0087 // than the lower bound. Then, the compiler would take advantage of the 0088 // undefined behavior and turn the range check into a simple 0089 // `actual_tag <= last_tag` comparison, which is incorrect. 0090 uint32_t actual_tag = static_cast<uint16_t>(entry); 0091 // The actual_tag is shifted to the left by one and contains the marking 0092 // bit in the LSB. To ignore that during the type check, simply add one to 0093 // the (shifted) range. 0094 constexpr int kTagShift = internal::kCppHeapPointerTagShift; 0095 uint32_t first_tag = static_cast<uint32_t>(lower_bound) << kTagShift; 0096 uint32_t last_tag = (static_cast<uint32_t>(upper_bound) << kTagShift) + 1; 0097 return actual_tag >= first_tag && actual_tag <= last_tag; 0098 } 0099 }; 0100 0101 constexpr CppHeapPointerTagRange kAnyCppHeapPointer( 0102 CppHeapPointerTag::kFirstTag, CppHeapPointerTag::kLastTag); 0103 0104 class SandboxHardwareSupport { 0105 public: 0106 /** 0107 * Initialize sandbox hardware support. This needs to be called before 0108 * creating any thread that might access sandbox memory since it sets up 0109 * hardware permissions to the memory that will be inherited on clone. 0110 */ 0111 V8_EXPORT static void InitializeBeforeThreadCreation(); 0112 }; 0113 0114 namespace internal { 0115 0116 #ifdef V8_COMPRESS_POINTERS 0117 V8_INLINE static Address* GetCppHeapPointerTableBase(v8::Isolate* isolate) { 0118 Address addr = reinterpret_cast<Address>(isolate) + 0119 Internals::kIsolateCppHeapPointerTableOffset + 0120 Internals::kExternalPointerTableBasePointerOffset; 0121 return *reinterpret_cast<Address**>(addr); 0122 } 0123 #endif // V8_COMPRESS_POINTERS 0124 0125 template <typename T> 0126 V8_INLINE static T* ReadCppHeapPointerField(v8::Isolate* isolate, 0127 Address heap_object_ptr, int offset, 0128 CppHeapPointerTagRange tag_range) { 0129 #ifdef V8_COMPRESS_POINTERS 0130 // See src/sandbox/cppheap-pointer-table-inl.h. Logic duplicated here so 0131 // it can be inlined and doesn't require an additional call. 0132 const CppHeapPointerHandle handle = 0133 Internals::ReadRawField<CppHeapPointerHandle>(heap_object_ptr, offset); 0134 const uint32_t index = handle >> kExternalPointerIndexShift; 0135 const Address* table = GetCppHeapPointerTableBase(isolate); 0136 const std::atomic<Address>* ptr = 0137 reinterpret_cast<const std::atomic<Address>*>(&table[index]); 0138 Address entry = std::atomic_load_explicit(ptr, std::memory_order_relaxed); 0139 0140 Address pointer = entry; 0141 if (V8_LIKELY(tag_range.CheckTagOf(entry))) { 0142 pointer = entry >> kCppHeapPointerPayloadShift; 0143 } else { 0144 // If the type check failed, we simply return nullptr here. That way: 0145 // 1. The null handle always results in nullptr being returned here, which 0146 // is a desired property. Otherwise, we would need an explicit check for 0147 // the null handle above, and therefore an additional branch. This 0148 // works because the 0th entry of the table always contains nullptr 0149 // tagged with the null tag (i.e. an all-zeros entry). As such, 0150 // regardless of whether the type check succeeds, the result will 0151 // always be nullptr. 0152 // 2. The returned pointer is guaranteed to crash even on platforms with 0153 // top byte ignore (TBI), such as Arm64. The alternative would be to 0154 // simply return the original entry with the left-shifted payload. 0155 // However, due to TBI, an access to that may not always result in a 0156 // crash (specifically, if the second most significant byte happens to 0157 // be zero). In addition, there shouldn't be a difference on Arm64 0158 // between returning nullptr or the original entry, since it will 0159 // simply compile to a `csel x0, x8, xzr, lo` instead of a 0160 // `csel x0, x10, x8, lo` instruction. 0161 pointer = 0; 0162 } 0163 return reinterpret_cast<T*>(pointer); 0164 #else // !V8_COMPRESS_POINTERS 0165 return reinterpret_cast<T*>( 0166 Internals::ReadRawField<Address>(heap_object_ptr, offset)); 0167 #endif // !V8_COMPRESS_POINTERS 0168 } 0169 0170 } // namespace internal 0171 } // namespace v8 0172 0173 #endif // INCLUDE_V8_SANDBOX_H_
| [ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
|
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |
|