Back to home page

EIC code displayed by LXR

 
 

    


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_