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