1 // Copyright 2023 The Chromium Authors
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 PARTITION_ALLOC_POINTERS_RAW_PTR_HOOKABLE_IMPL_H_
6 #define PARTITION_ALLOC_POINTERS_RAW_PTR_HOOKABLE_IMPL_H_
7 
8 #include <cstddef>
9 #include <type_traits>
10 
11 #include "partition_alloc/partition_alloc_base/compiler_specific.h"
12 #include "partition_alloc/partition_alloc_base/component_export.h"
13 #include "partition_alloc/partition_alloc_base/cxx20_is_constant_evaluated.h"
14 #include "partition_alloc/partition_alloc_buildflags.h"
15 #include "partition_alloc/partition_alloc_forward.h"
16 
17 #if !BUILDFLAG(USE_HOOKABLE_RAW_PTR)
18 #error "Included under wrong build option"
19 #endif
20 
21 namespace base::internal {
22 
23 struct RawPtrHooks {
24   using WrapPtr = void(uintptr_t address);
25   using ReleaseWrappedPtr = void(uintptr_t address);
26   using SafelyUnwrapForDereference = void(uintptr_t address);
27   using SafelyUnwrapForExtraction = void(uintptr_t address);
28   using UnsafelyUnwrapForComparison = void(uintptr_t address);
29   using Advance = void(uintptr_t old_address, uintptr_t new_address);
30   using Duplicate = void(uintptr_t address);
31   using WrapPtrForDuplication = void(uintptr_t address);
32   using UnsafelyUnwrapForDuplication = void(uintptr_t address);
33 
34   WrapPtr* wrap_ptr;
35   ReleaseWrappedPtr* release_wrapped_ptr;
36   SafelyUnwrapForDereference* safely_unwrap_for_dereference;
37   SafelyUnwrapForExtraction* safely_unwrap_for_extraction;
38   UnsafelyUnwrapForComparison* unsafely_unwrap_for_comparison;
39   Advance* advance;
40   Duplicate* duplicate;
41   WrapPtrForDuplication* wrap_ptr_for_duplication;
42   UnsafelyUnwrapForDuplication* unsafely_unwrap_for_duplication;
43 };
44 
45 PA_COMPONENT_EXPORT(RAW_PTR) const RawPtrHooks* GetRawPtrHooks();
46 PA_COMPONENT_EXPORT(RAW_PTR) void InstallRawPtrHooks(const RawPtrHooks*);
47 PA_COMPONENT_EXPORT(RAW_PTR) void ResetRawPtrHooks();
48 
49 template <bool EnableHooks>
50 struct RawPtrHookableImpl {
51   // Since this Impl is used for BRP-ASan, match BRP as closely as possible.
52   static constexpr bool kMustZeroOnConstruct = true;
53   static constexpr bool kMustZeroOnMove = true;
54   static constexpr bool kMustZeroOnDestruct = true;
55 
56   // Wraps a pointer.
57   template <typename T>
WrapRawPtrRawPtrHookableImpl58   PA_ALWAYS_INLINE static constexpr T* WrapRawPtr(T* ptr) {
59     if (!partition_alloc::internal::base::is_constant_evaluated()) {
60       if (EnableHooks) {
61         GetRawPtrHooks()->wrap_ptr(reinterpret_cast<uintptr_t>(ptr));
62       }
63     }
64     return ptr;
65   }
66 
67   // Notifies the allocator when a wrapped pointer is being removed or replaced.
68   template <typename T>
ReleaseWrappedPtrRawPtrHookableImpl69   PA_ALWAYS_INLINE static constexpr void ReleaseWrappedPtr(T* ptr) {
70     if (!partition_alloc::internal::base::is_constant_evaluated()) {
71       if (EnableHooks) {
72         GetRawPtrHooks()->release_wrapped_ptr(reinterpret_cast<uintptr_t>(ptr));
73       }
74     }
75   }
76 
77   // Unwraps the pointer, while asserting that memory hasn't been freed. The
78   // function is allowed to crash on nullptr.
79   template <typename T>
SafelyUnwrapPtrForDereferenceRawPtrHookableImpl80   PA_ALWAYS_INLINE static constexpr T* SafelyUnwrapPtrForDereference(
81       T* wrapped_ptr) {
82     if (!partition_alloc::internal::base::is_constant_evaluated()) {
83       if (EnableHooks) {
84         GetRawPtrHooks()->safely_unwrap_for_dereference(
85             reinterpret_cast<uintptr_t>(wrapped_ptr));
86       }
87     }
88     return wrapped_ptr;
89   }
90 
91   // Unwraps the pointer, while asserting that memory hasn't been freed. The
92   // function must handle nullptr gracefully.
93   template <typename T>
SafelyUnwrapPtrForExtractionRawPtrHookableImpl94   PA_ALWAYS_INLINE static constexpr T* SafelyUnwrapPtrForExtraction(
95       T* wrapped_ptr) {
96     if (!partition_alloc::internal::base::is_constant_evaluated()) {
97       if (EnableHooks) {
98         GetRawPtrHooks()->safely_unwrap_for_extraction(
99             reinterpret_cast<uintptr_t>(wrapped_ptr));
100       }
101     }
102     return wrapped_ptr;
103   }
104 
105   // Unwraps the pointer, without making an assertion on whether memory was
106   // freed or not.
107   template <typename T>
UnsafelyUnwrapPtrForComparisonRawPtrHookableImpl108   PA_ALWAYS_INLINE static constexpr T* UnsafelyUnwrapPtrForComparison(
109       T* wrapped_ptr) {
110     if (!partition_alloc::internal::base::is_constant_evaluated()) {
111       if (EnableHooks) {
112         GetRawPtrHooks()->unsafely_unwrap_for_comparison(
113             reinterpret_cast<uintptr_t>(wrapped_ptr));
114       }
115     }
116     return wrapped_ptr;
117   }
118 
119   // Upcasts the wrapped pointer.
120   template <typename To, typename From>
UpcastRawPtrHookableImpl121   PA_ALWAYS_INLINE static constexpr To* Upcast(From* wrapped_ptr) {
122     static_assert(std::is_convertible_v<From*, To*>,
123                   "From must be convertible to To.");
124     // Note, this cast may change the address if upcasting to base that lies in
125     // the middle of the derived object.
126     return wrapped_ptr;
127   }
128 
129   // Advance the wrapped pointer by `delta_elems`.
130   template <
131       typename T,
132       typename Z,
133       typename =
134           std::enable_if_t<partition_alloc::internal::is_offset_type<Z>, void>>
135   PA_ALWAYS_INLINE static constexpr T*
AdvanceRawPtrHookableImpl136   Advance(T* wrapped_ptr, Z delta_elems, bool /*is_in_pointer_modification*/) {
137     if (!partition_alloc::internal::base::is_constant_evaluated()) {
138       if (EnableHooks) {
139         GetRawPtrHooks()->advance(
140             reinterpret_cast<uintptr_t>(wrapped_ptr),
141             reinterpret_cast<uintptr_t>(wrapped_ptr + delta_elems));
142       }
143     }
144     return wrapped_ptr + delta_elems;
145   }
146 
147   // Retreat the wrapped pointer by `delta_elems`.
148   template <
149       typename T,
150       typename Z,
151       typename =
152           std::enable_if_t<partition_alloc::internal::is_offset_type<Z>, void>>
153   PA_ALWAYS_INLINE static constexpr T*
RetreatRawPtrHookableImpl154   Retreat(T* wrapped_ptr, Z delta_elems, bool /*is_in_pointer_modification*/) {
155     if (!partition_alloc::internal::base::is_constant_evaluated()) {
156       if (EnableHooks) {
157         GetRawPtrHooks()->advance(
158             reinterpret_cast<uintptr_t>(wrapped_ptr),
159             reinterpret_cast<uintptr_t>(wrapped_ptr - delta_elems));
160       }
161     }
162     return wrapped_ptr - delta_elems;
163   }
164 
165   template <typename T>
GetDeltaElemsRawPtrHookableImpl166   PA_ALWAYS_INLINE static constexpr ptrdiff_t GetDeltaElems(T* wrapped_ptr1,
167                                                             T* wrapped_ptr2) {
168     return wrapped_ptr1 - wrapped_ptr2;
169   }
170 
171   // Returns a copy of a wrapped pointer, without making an assertion on whether
172   // memory was freed or not.
173   template <typename T>
DuplicateRawPtrHookableImpl174   PA_ALWAYS_INLINE static constexpr T* Duplicate(T* wrapped_ptr) {
175     if (!partition_alloc::internal::base::is_constant_evaluated()) {
176       if (EnableHooks) {
177         GetRawPtrHooks()->duplicate(reinterpret_cast<uintptr_t>(wrapped_ptr));
178       }
179     }
180     return wrapped_ptr;
181   }
182 
183   // `WrapRawPtrForDuplication` and `UnsafelyUnwrapPtrForDuplication` are used
184   // to create a new raw_ptr<T> from another raw_ptr<T> of a different flavor.
185   template <typename T>
WrapRawPtrForDuplicationRawPtrHookableImpl186   PA_ALWAYS_INLINE static constexpr T* WrapRawPtrForDuplication(T* ptr) {
187     if (!partition_alloc::internal::base::is_constant_evaluated()) {
188       if (EnableHooks) {
189         GetRawPtrHooks()->wrap_ptr_for_duplication(
190             reinterpret_cast<uintptr_t>(ptr));
191       }
192     }
193     return ptr;
194   }
195 
196   template <typename T>
UnsafelyUnwrapPtrForDuplicationRawPtrHookableImpl197   PA_ALWAYS_INLINE static constexpr T* UnsafelyUnwrapPtrForDuplication(
198       T* wrapped_ptr) {
199     if (!partition_alloc::internal::base::is_constant_evaluated()) {
200       if (EnableHooks) {
201         GetRawPtrHooks()->unsafely_unwrap_for_duplication(
202             reinterpret_cast<uintptr_t>(wrapped_ptr));
203       }
204     }
205     return wrapped_ptr;
206   }
207 
208   template <typename T>
TraceRawPtrHookableImpl209   static constexpr void Trace(uint64_t owner_id, T* wrapped_ptr) {}
UntraceRawPtrHookableImpl210   static constexpr void Untrace(uint64_t owner_id) {}
211 
212   // This is for accounting only, used by unit tests.
IncrementSwapCountForTestRawPtrHookableImpl213   PA_ALWAYS_INLINE static constexpr void IncrementSwapCountForTest() {}
IncrementLessCountForTestRawPtrHookableImpl214   PA_ALWAYS_INLINE static constexpr void IncrementLessCountForTest() {}
215 };
216 
217 }  // namespace base::internal
218 
219 #endif  // PARTITION_ALLOC_POINTERS_RAW_PTR_HOOKABLE_IMPL_H_
220