| // Copyright 2023 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 V8_SANDBOX_INDIRECT_POINTER_TAG_H_ |
| #define V8_SANDBOX_INDIRECT_POINTER_TAG_H_ |
| |
| #include "src/common/globals.h" |
| #include "src/objects/instance-type.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| // Defines the list of valid indirect pointer tags. |
| // |
| // When accessing a trusted/indirect pointer, an IndirectPointerTag must be |
| // provided which indicates the expected instance type of the pointed-to |
| // object. When the sandbox is enabled, this tag is used to ensure type-safe |
| // access to objects referenced via trusted pointers: if the provided tag |
| // doesn't match the tag of the object in the trusted pointer table, an |
| // inaccessible pointer will be returned. |
| // |
| // We use the shifted instance type as tag and an AND-based type-checking |
| // mechanism in the TrustedPointerTable, similar to the one used by the |
| // ExternalPointerTable: the entry in the table is ORed with the tag and then |
| // ANDed with the inverse of the tag upon access. This has the benefit that the |
| // type check and the removal of the marking bit can be folded into a single |
| // bitwise operations. However, it is not technically guaranteed that simply |
| // using the instance type as tag works for this scheme as the bits of one |
| // instance type may happen to be a superset of those of another instance type, |
| // thereby causing the type check to incorrectly pass. As such, the chance of |
| // getting "incompabitle" tags increases when adding more tags here so we may |
| // at some point want to consider manually assigning tag values that are |
| // guaranteed to work (similar for how we do it for ExternalPointerTags). |
| |
| constexpr int kIndirectPointerTagShift = 48; |
| constexpr uint64_t kIndirectPointerTagMask = 0x7fff000000000000; |
| constexpr uint64_t kTrustedPointerTableMarkBit = 0x8000000000000000; |
| // We use a reserved bit for the free entry tag so that the |
| // kUnknownIndirectPointerTag cannot untag free entries. Due to that, not all |
| // tags in the kAllTagsForAndBasedTypeChecking are usable here (which is |
| // ensured by static asserts below, see VALIDATE_INDIRECT_POINTER_TAG). |
| // However, in practice this would probably be fine since the payload is a |
| // table index, and so would likely always crash when treated as a pointer. As |
| // such, if there is ever need for more tags, this can be reconsidered. |
| // Note that we use a bit in the 2nd most significant byte here due to top byte |
| // ignore (TBI), which allows dereferencing pointers even if bits in the most |
| // significant byte are set. |
| constexpr uint64_t kTrustedPointerTableFreeEntryBit = 0x0080000000000000; |
| constexpr uint64_t kIndirectPointerTagMaskWithoutFreeEntryBit = |
| 0x7f7f000000000000; |
| |
| // Format is (name, instance type, tag id) |
| #define INDIRECT_POINTER_TAG_LIST(V) \ |
| V(kCodeIndirectPointerTag, CODE_TYPE, 1) \ |
| V(kBytecodeArrayIndirectPointerTag, BYTECODE_ARRAY_TYPE, 2) \ |
| V(kInterpreterDataIndirectPointerTag, INTERPRETER_DATA_TYPE, 3) \ |
| V(kRegExpDataIndirectPointerTag, REG_EXP_DATA_TYPE, 4) \ |
| IF_WASM(V, kWasmTrustedInstanceDataIndirectPointerTag, \ |
| WASM_TRUSTED_INSTANCE_DATA_TYPE, 5) \ |
| IF_WASM(V, kWasmInternalFunctionIndirectPointerTag, \ |
| WASM_INTERNAL_FUNCTION_TYPE, 6) \ |
| IF_WASM(V, kWasmFunctionDataIndirectPointerTag, WASM_FUNCTION_DATA_TYPE, 7) |
| |
| #define MAKE_TAG(i) \ |
| (kAllTagsForAndBasedTypeChecking[i] << kIndirectPointerTagShift) |
| |
| // TODO(saelo): consider renaming this to something like TypeTag or |
| // InstanceTypeTag since that better captures what this represents. |
| enum IndirectPointerTag : uint64_t { |
| // The null tag. Usually used to express the lack of a valid tag, for example |
| // in non-sandbox builds. |
| kIndirectPointerNullTag = 0, |
| |
| // This tag can be used when an indirect pointer field can legitimately refer |
| // to objects of different types. |
| // NOTE: this tag effectively disables the built-in type-checking mechanism. |
| // As such, in virtually all cases the caller needs to perform runtime-type |
| // checks (i.e. IsXyzObject(obj))` afterwards which need to be able to |
| // correctly handle unexpected types. The last point is worth stressing |
| // further. As an example, the following code is NOT correct: |
| // |
| // auto obj = LoadTrustedPointerField<kUnknownIndirectPointerTag>(...); |
| // if (IsFoo(obj)) { |
| // Cast<Foo>(obj)->foo(); |
| // } else if (IsBar(obj)) { |
| // Cast<Bar>(obj)->bar(); |
| // } else { |
| // // Potential type confusion here! |
| // Cast<Baz>(obj)->baz(); |
| // } |
| // |
| // This is because an attacker can swap trusted pointers and thereby cause an |
| // object of a different/unexpected type to be returned. Instead, in this |
| // case a CHECK can for example be used to make the code correct: |
| // |
| // // ... |
| // } else { |
| // // Must be a Baz object |
| // CHECK(IsBaz(obj)); |
| // Cast<Baz>(obj)->baz(); |
| // } |
| // |
| kUnknownIndirectPointerTag = kIndirectPointerTagMaskWithoutFreeEntryBit, |
| |
| // Tag used internally by the trusted pointer table to mark free entries. |
| // See also the comment above kTrustedPointerTableFreeEntryBit for why this |
| // uses a dedicated bit. |
| kFreeTrustedPointerTableEntryTag = kTrustedPointerTableFreeEntryBit, |
| |
| // "Regular" tags. One per supported instance type. |
| #define INDIRECT_POINTER_TAG_ENUM_DECL(name, instance_type, tag_id) \ |
| name = MAKE_TAG(tag_id), |
| INDIRECT_POINTER_TAG_LIST(INDIRECT_POINTER_TAG_ENUM_DECL) |
| #undef INDIRECT_POINTER_TAG_ENUM_DECL |
| }; |
| |
| #define VALIDATE_INDIRECT_POINTER_TAG(name, instance_type, tag_id) \ |
| static_assert((name & kIndirectPointerTagMask) == name); \ |
| static_assert((name & kIndirectPointerTagMaskWithoutFreeEntryBit) == name); |
| INDIRECT_POINTER_TAG_LIST(VALIDATE_INDIRECT_POINTER_TAG) |
| #undef VALIDATE_INDIRECT_POINTER_TAG |
| static_assert((kFreeTrustedPointerTableEntryTag & kIndirectPointerTagMask) == |
| kFreeTrustedPointerTableEntryTag); |
| static_assert((kFreeTrustedPointerTableEntryTag & |
| kIndirectPointerTagMaskWithoutFreeEntryBit) == 0); |
| |
| V8_INLINE constexpr bool IsValidIndirectPointerTag(IndirectPointerTag tag) { |
| #define VALID_INDIRECT_POINTER_TAG_CASE(tag, instance_type, tag_id) case tag: |
| switch (tag) { |
| INDIRECT_POINTER_TAG_LIST(VALID_INDIRECT_POINTER_TAG_CASE) |
| return true; |
| default: |
| return false; |
| } |
| #undef VALID_INDIRECT_POINTER_TAG_CASE |
| } |
| |
| // Migrating objects into trusted space is typically performed in multiple |
| // steps, where all references to the object from inside the sandbox are first |
| // changed to indirect pointers before actually moving the object out of the |
| // sandbox. As we have CHECKs that trusted pointer table entries point outside |
| // of the sandbox, we need this helper function to disable that CHECK for |
| // objects that are in the process of being migrated into trusted space. |
| V8_INLINE constexpr bool IsTrustedSpaceMigrationInProgressForObjectsWithTag( |
| IndirectPointerTag tag) { |
| return false; |
| } |
| |
| // The null tag is also considered an invalid tag since no indirect pointer |
| // field should be using this tag. |
| static_assert(!IsValidIndirectPointerTag(kIndirectPointerNullTag)); |
| |
| V8_INLINE IndirectPointerTag |
| IndirectPointerTagFromInstanceType(InstanceType instance_type) { |
| uint64_t raw = 0; |
| switch (instance_type) { |
| #define CASE(name, instance_type, tag_id) \ |
| case instance_type: \ |
| raw = MAKE_TAG(tag_id); \ |
| break; |
| INDIRECT_POINTER_TAG_LIST(CASE) |
| #undef CASE |
| case ATOM_REG_EXP_DATA_TYPE: |
| case IR_REG_EXP_DATA_TYPE: |
| // TODO(saelo): Consider adding support for inheritance hierarchies in |
| // our tag checking mechanism. |
| raw = kRegExpDataIndirectPointerTag; |
| break; |
| #if V8_ENABLE_WEBASSEMBLY |
| case WASM_EXPORTED_FUNCTION_DATA_TYPE: |
| case WASM_JS_FUNCTION_DATA_TYPE: |
| case WASM_CAPI_FUNCTION_DATA_TYPE: |
| // TODO(saelo): Consider adding support for inheritance hierarchies in |
| // our tag checking mechanism. |
| raw = kWasmFunctionDataIndirectPointerTag; |
| break; |
| #endif // V8_ENABLE_WEBASSEMBLY |
| default: |
| UNREACHABLE(); |
| } |
| return static_cast<IndirectPointerTag>(raw); |
| } |
| |
| V8_INLINE InstanceType |
| InstanceTypeFromIndirectPointerTag(IndirectPointerTag tag) { |
| DCHECK(IsValidIndirectPointerTag(tag)); |
| switch (tag) { |
| #define CASE(name, instance_type, tag_id) \ |
| case MAKE_TAG(tag_id): \ |
| return instance_type; \ |
| break; |
| #undef CASE |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| #undef MAKE_TAG |
| #undef INDIRECT_POINTER_TAG_LIST |
| |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif // V8_SANDBOX_INDIRECT_POINTER_TAG_H_ |