1 // Copyright 2022 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 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "base/memory/raw_ptr_asan_service.h"
11
12 #if PA_BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
13
14 #include <sanitizer/allocator_interface.h>
15 #include <sanitizer/asan_interface.h>
16 #include <stdarg.h>
17 #include <string.h>
18
19 #include "base/check_op.h"
20 #include "base/compiler_specific.h"
21 #include "base/debug/asan_service.h"
22 #include "base/immediate_crash.h"
23 #include "base/logging.h"
24 #include "base/memory/raw_ptr.h"
25 #include "base/memory/raw_ptr_asan_bound_arg_tracker.h"
26 #include "base/memory/raw_ptr_asan_hooks.h"
27 #include "base/process/process.h"
28 #include "base/strings/stringprintf.h"
29 #include "base/task/thread_pool/thread_group.h"
30
31 namespace base {
32
33 RawPtrAsanService RawPtrAsanService::instance_;
34
35 namespace {
36
37 // https://github.com/llvm/llvm-project/blob/b84673b3f424882c4c1961fb2c49b6302b68f344/compiler-rt/lib/asan/asan_mapping.h#L154
38 constexpr size_t kShadowScale = 3;
39 // https://github.com/llvm/llvm-project/blob/b84673b3f424882c4c1961fb2c49b6302b68f344/compiler-rt/lib/asan/asan_allocator.cpp#L143
40 constexpr size_t kChunkHeaderSize = 16;
41 // https://github.com/llvm/llvm-project/blob/b84673b3f424882c4c1961fb2c49b6302b68f344/compiler-rt/lib/asan/asan_internal.h#L138
42 constexpr uint8_t kAsanHeapLeftRedzoneMagic = 0xfa;
43 // https://github.com/llvm/llvm-project/blob/b84673b3f424882c4c1961fb2c49b6302b68f344/compiler-rt/lib/asan/asan_internal.h#L145
44 constexpr uint8_t kAsanUserPoisonedMemoryMagic = 0xf7;
45
46 // Intentionally use thread-local-storage here. Making this sequence-local
47 // doesn't prevent sharing of PendingReport contents between unrelated tasks, so
48 // we keep this at a lower-level and avoid introducing additional assumptions
49 // about Chrome's sequence model.
50 constinit thread_local RawPtrAsanService::PendingReport pending_report;
51
52 } // namespace
53
54 // Mark the first eight bytes of every allocation's header as "user poisoned".
55 // This allows us to filter out allocations made before BRP-ASan is activated.
56 // The change shouldn't reduce the regular ASan coverage.
57
58 // static
59 NO_SANITIZE("address")
MallocHook(const volatile void * ptr,size_t size)60 void RawPtrAsanService::MallocHook(const volatile void* ptr, size_t size) {
61 uint8_t* header =
62 static_cast<uint8_t*>(const_cast<void*>(ptr)) - kChunkHeaderSize;
63 *RawPtrAsanService::GetInstance().GetShadow(header) =
64 kAsanUserPoisonedMemoryMagic;
65 }
66
67 NO_SANITIZE("address")
IsSupportedAllocation(void * allocation_start) const68 bool RawPtrAsanService::IsSupportedAllocation(void* allocation_start) const {
69 uint8_t* header = static_cast<uint8_t*>(allocation_start) - kChunkHeaderSize;
70 return *GetShadow(header) == kAsanUserPoisonedMemoryMagic;
71 }
72
73 NO_SANITIZE("address")
Configure(EnableDereferenceCheck enable_dereference_check,EnableExtractionCheck enable_extraction_check,EnableInstantiationCheck enable_instantiation_check)74 void RawPtrAsanService::Configure(
75 EnableDereferenceCheck enable_dereference_check,
76 EnableExtractionCheck enable_extraction_check,
77 EnableInstantiationCheck enable_instantiation_check) {
78 CHECK_EQ(mode_, Mode::kUninitialized);
79
80 Mode new_mode = enable_dereference_check || enable_extraction_check ||
81 enable_instantiation_check
82 ? Mode::kEnabled
83 : Mode::kDisabled;
84 if (new_mode == Mode::kEnabled) {
85 // The constants we use aren't directly exposed by the API, so
86 // validate them at runtime as carefully as possible.
87 size_t shadow_scale;
88 __asan_get_shadow_mapping(&shadow_scale, &shadow_offset_);
89 CHECK_EQ(shadow_scale, kShadowScale);
90
91 uint8_t* dummy_alloc = new uint8_t;
92 CHECK_EQ(*GetShadow(dummy_alloc - kChunkHeaderSize),
93 kAsanHeapLeftRedzoneMagic);
94
95 __asan_poison_memory_region(dummy_alloc, 1);
96 CHECK_EQ(*GetShadow(dummy_alloc), kAsanUserPoisonedMemoryMagic);
97 delete dummy_alloc;
98
99 __sanitizer_install_malloc_and_free_hooks(MallocHook, FreeHook);
100 debug::AsanService::GetInstance()->AddErrorCallback(ErrorReportCallback);
101 internal::InstallRawPtrHooks(base::internal::GetRawPtrAsanHooks());
102
103 is_dereference_check_enabled_ = !!enable_dereference_check;
104 is_extraction_check_enabled_ = !!enable_extraction_check;
105 is_instantiation_check_enabled_ = !!enable_instantiation_check;
106 }
107
108 mode_ = new_mode;
109 }
110
GetShadow(void * ptr) const111 uint8_t* RawPtrAsanService::GetShadow(void* ptr) const {
112 return reinterpret_cast<uint8_t*>(
113 (reinterpret_cast<uintptr_t>(ptr) >> kShadowScale) + shadow_offset_);
114 }
115
116 // static
SetPendingReport(ReportType type,const volatile void * ptr)117 void RawPtrAsanService::SetPendingReport(ReportType type,
118 const volatile void* ptr) {
119 // The actual ASan crash may occur at an offset from the pointer passed
120 // here, so track the whole region.
121 void* region_base;
122 size_t region_size;
123 __asan_locate_address(const_cast<void*>(ptr), nullptr, 0, ®ion_base,
124 ®ion_size);
125
126 pending_report = {type, reinterpret_cast<uintptr_t>(region_base),
127 region_size};
128 }
129
130 namespace {
131 enum class ProtectionStatus {
132 kNotProtected,
133 kManualAnalysisRequired,
134 kProtected,
135 };
136
ProtectionStatusToString(ProtectionStatus status)137 const char* ProtectionStatusToString(ProtectionStatus status) {
138 switch (status) {
139 case ProtectionStatus::kNotProtected:
140 return "NOT PROTECTED";
141 case ProtectionStatus::kManualAnalysisRequired:
142 return "MANUAL ANALYSIS REQUIRED";
143 case ProtectionStatus::kProtected:
144 return "PROTECTED";
145 }
146 }
147
148 // ASan doesn't have an API to get the current thread's identifier.
149 // We have to create a dummy allocation to determine it.
GetCurrentThreadId()150 int GetCurrentThreadId() {
151 int* dummy = new int;
152 int id = -1;
153 __asan_get_alloc_stack(dummy, nullptr, 0, &id);
154 delete dummy;
155 return id;
156 }
157 } // namespace
158
159 // static
ErrorReportCallback(const char * report,bool *)160 void RawPtrAsanService::ErrorReportCallback(const char* report, bool*) {
161 if (strcmp(__asan_get_report_description(), "heap-use-after-free") != 0) {
162 return;
163 }
164
165 struct {
166 ProtectionStatus protection_status;
167 const char* crash_details;
168 const char* protection_details;
169 } crash_info;
170
171 uintptr_t ptr = reinterpret_cast<uintptr_t>(__asan_get_report_address());
172 uintptr_t bound_arg_ptr = RawPtrAsanBoundArgTracker::GetProtectedArgPtr(ptr);
173 if (pending_report.allocation_base <= ptr &&
174 ptr < pending_report.allocation_base + pending_report.allocation_size) {
175 bool is_supported_allocation =
176 RawPtrAsanService::GetInstance().IsSupportedAllocation(
177 reinterpret_cast<void*>(pending_report.allocation_base));
178 switch (pending_report.type) {
179 case ReportType::kDereference: {
180 if (is_supported_allocation) {
181 crash_info = {ProtectionStatus::kProtected,
182 "This crash occurred while a raw_ptr<T> object "
183 "containing a dangling pointer was being dereferenced.",
184 "MiraclePtr is expected to make this crash "
185 "non-exploitable once fully enabled."};
186 } else {
187 crash_info = {ProtectionStatus::kNotProtected,
188 "This crash occurred while accessing a region that was "
189 "allocated before MiraclePtr was activated.",
190 "This crash is still exploitable with MiraclePtr."};
191 }
192 break;
193 }
194 case ReportType::kExtraction: {
195 if (is_supported_allocation && bound_arg_ptr) {
196 crash_info = {
197 ProtectionStatus::kProtected,
198 "This crash occurred inside a callback where a raw_ptr<T> "
199 "pointing to the same region was bound to one of the arguments.",
200 "MiraclePtr is expected to make this crash non-exploitable once "
201 "fully enabled."};
202 } else if (is_supported_allocation) {
203 crash_info = {
204 ProtectionStatus::kManualAnalysisRequired,
205 "A pointer to the same region was extracted from a raw_ptr<T> "
206 "object prior to this crash.",
207 "To determine the protection status, enable extraction warnings "
208 "and check whether the raw_ptr<T> object can be destroyed or "
209 "overwritten between the extraction and use."};
210 } else {
211 crash_info = {ProtectionStatus::kNotProtected,
212 "This crash occurred while accessing a region that was "
213 "allocated before MiraclePtr was activated.",
214 "This crash is still exploitable with MiraclePtr."};
215 }
216 break;
217 }
218 case ReportType::kInstantiation: {
219 crash_info = {ProtectionStatus::kNotProtected,
220 "A pointer to an already freed region was assigned to a "
221 "raw_ptr<T> object, which may lead to memory corruption.",
222 "This crash is still exploitable with MiraclePtr."};
223 }
224 }
225 } else if (bound_arg_ptr) {
226 // Note - this branch comes second to avoid hiding invalid instantiations,
227 // as we still consider it to be an error to instantiate a raw_ptr<T> from
228 // an invalid T* even if that T* is guaranteed to be quarantined.
229 bool is_supported_allocation =
230 RawPtrAsanService::GetInstance().IsSupportedAllocation(
231 reinterpret_cast<void*>(bound_arg_ptr));
232 if (is_supported_allocation) {
233 crash_info = {
234 ProtectionStatus::kProtected,
235 "This crash occurred inside a callback where a raw_ptr<T> pointing "
236 "to the same region was bound to one of the arguments.",
237 "MiraclePtr is expected to make this crash non-exploitable once "
238 "fully enabled."};
239 } else {
240 crash_info = {ProtectionStatus::kNotProtected,
241 "This crash occurred while accessing a region that was "
242 "allocated before MiraclePtr was activated.",
243 "This crash is still exploitable with MiraclePtr."};
244 }
245 } else {
246 crash_info = {
247 ProtectionStatus::kNotProtected,
248 "No raw_ptr<T> access to this region was detected prior to this crash.",
249 "This crash is still exploitable with MiraclePtr."};
250 }
251
252 // The race condition check below may override the protection status.
253 if (crash_info.protection_status != ProtectionStatus::kNotProtected) {
254 int free_thread_id = -1;
255 __asan_get_free_stack(reinterpret_cast<void*>(ptr), nullptr, 0,
256 &free_thread_id);
257 if (free_thread_id != GetCurrentThreadId()) {
258 crash_info.protection_status = ProtectionStatus::kManualAnalysisRequired;
259 crash_info.protection_details =
260 "The \"use\" and \"free\" threads don't match. This crash is likely "
261 "to have been caused by a race condition that is mislabeled as a "
262 "use-after-free. Make sure that the \"free\" is sequenced after the "
263 "\"use\" (e.g. both are on the same sequence, or the \"free\" is in "
264 "a task posted after the \"use\"). Otherwise, the crash is still "
265 "exploitable with MiraclePtr.";
266 } else if (internal::ThreadGroup::CurrentThreadHasGroup()) {
267 // We need to be especially careful with ThreadPool threads. Otherwise,
268 // we might miss false-positives where the "use" and "free" happen on
269 // different sequences but the same thread by chance.
270 crash_info.protection_status = ProtectionStatus::kManualAnalysisRequired;
271 crash_info.protection_details =
272 "This crash occurred in the thread pool. The sequence which invoked "
273 "the \"free\" is unknown, so the crash may have been caused by a "
274 "race condition that is mislabeled as a use-after-free. Make sure "
275 "that the \"free\" is sequenced after the \"use\" (e.g. both are on "
276 "the same sequence, or the \"free\" is in a task posted after the "
277 "\"use\"). Otherwise, the crash is still exploitable with "
278 "MiraclePtr.";
279 }
280 }
281
282 debug::AsanService::GetInstance()->Log(
283 "\nMiraclePtr Status: %s\n%s\n%s\n"
284 "Refer to "
285 "https://chromium.googlesource.com/chromium/src/+/main/base/memory/"
286 "raw_ptr.md for details.",
287 ProtectionStatusToString(crash_info.protection_status),
288 crash_info.crash_details, crash_info.protection_details);
289 }
290
291 namespace {
292 enum class MessageLevel {
293 kWarning,
294 kError,
295 };
296
LevelToString(MessageLevel level)297 const char* LevelToString(MessageLevel level) {
298 switch (level) {
299 case MessageLevel::kWarning:
300 return "WARNING";
301 case MessageLevel::kError:
302 return "ERROR";
303 }
304 }
305
306 // Prints AddressSanitizer-like custom error messages.
Log(MessageLevel level,uintptr_t address,const char * type,const char * description)307 void Log(MessageLevel level,
308 uintptr_t address,
309 const char* type,
310 const char* description) {
311 #if __has_builtin(__builtin_extract_return_addr) && \
312 __has_builtin(__builtin_return_address)
313 void* pc = __builtin_extract_return_addr(__builtin_return_address(0));
314 #else
315 void* pc = nullptr;
316 #endif
317
318 #if __has_builtin(__builtin_frame_address)
319 void* bp = __builtin_frame_address(0);
320 #else
321 void* bp = nullptr;
322 #endif
323
324 void* local_stack;
325 void* sp = &local_stack;
326
327 debug::AsanService::GetInstance()->Log(
328 "=================================================================\n"
329 "==%d==%s: MiraclePtr: %s on address %p at pc %p bp %p sp %p",
330 Process::Current().Pid(), LevelToString(level), type, address, pc, bp,
331 sp);
332 __sanitizer_print_stack_trace();
333 __asan_describe_address(reinterpret_cast<void*>(address));
334 debug::AsanService::GetInstance()->Log(
335 "%s\n"
336 "=================================================================",
337 description);
338 }
339 } // namespace
340
WarnOnDanglingExtraction(const volatile void * ptr) const341 void RawPtrAsanService::WarnOnDanglingExtraction(
342 const volatile void* ptr) const {
343 Log(MessageLevel::kWarning, reinterpret_cast<uintptr_t>(ptr),
344 "dangling-pointer-extraction",
345 "A regular ASan report will follow if the extracted pointer is "
346 "dereferenced later.\n"
347 "Otherwise, it is still likely a bug to rely on the address of an "
348 "already freed allocation.\n"
349 "Refer to "
350 "https://chromium.googlesource.com/chromium/src/+/main/base/memory/"
351 "raw_ptr.md for details.");
352 }
353
CrashOnDanglingInstantiation(const volatile void * ptr) const354 void RawPtrAsanService::CrashOnDanglingInstantiation(
355 const volatile void* ptr) const {
356 Log(MessageLevel::kError, reinterpret_cast<uintptr_t>(ptr),
357 "dangling-pointer-instantiation",
358 "This crash occurred due to an attempt to assign a dangling pointer to a "
359 "raw_ptr<T> variable, which might lead to use-after-free.\n"
360 "Note that this report might be a false positive if at the moment of the "
361 "crash another raw_ptr<T> is guaranteed to keep the allocation alive.\n"
362 "Refer to "
363 "https://chromium.googlesource.com/chromium/src/+/main/base/memory/"
364 "raw_ptr.md for details.");
365 base::ImmediateCrash();
366 }
367
368 } // namespace base
369
370 #endif // PA_BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
371