1 //===-- hwasan_report.cpp -------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file is a part of HWAddressSanitizer.
10 //
11 // Error reporting.
12 //===----------------------------------------------------------------------===//
13
14 #include "hwasan_report.h"
15
16 #include <dlfcn.h>
17
18 #include "hwasan.h"
19 #include "hwasan_allocator.h"
20 #include "hwasan_globals.h"
21 #include "hwasan_mapping.h"
22 #include "hwasan_thread.h"
23 #include "hwasan_thread_list.h"
24 #include "sanitizer_common/sanitizer_allocator_internal.h"
25 #include "sanitizer_common/sanitizer_array_ref.h"
26 #include "sanitizer_common/sanitizer_common.h"
27 #include "sanitizer_common/sanitizer_flags.h"
28 #include "sanitizer_common/sanitizer_internal_defs.h"
29 #include "sanitizer_common/sanitizer_mutex.h"
30 #include "sanitizer_common/sanitizer_placement_new.h"
31 #include "sanitizer_common/sanitizer_report_decorator.h"
32 #include "sanitizer_common/sanitizer_stackdepot.h"
33 #include "sanitizer_common/sanitizer_stacktrace_printer.h"
34 #include "sanitizer_common/sanitizer_symbolizer.h"
35
36 using namespace __sanitizer;
37
38 namespace __hwasan {
39
40 class ScopedReport {
41 public:
ScopedReport(bool fatal)42 explicit ScopedReport(bool fatal) : fatal(fatal) {
43 Lock lock(&error_message_lock_);
44 error_message_ptr_ = &error_message_;
45 ++hwasan_report_count;
46 }
47
~ScopedReport()48 ~ScopedReport() {
49 void (*report_cb)(const char *);
50 {
51 Lock lock(&error_message_lock_);
52 report_cb = error_report_callback_;
53 error_message_ptr_ = nullptr;
54 }
55 if (report_cb)
56 report_cb(error_message_.data());
57 if (fatal)
58 SetAbortMessage(error_message_.data());
59 if (common_flags()->print_module_map >= 2 ||
60 (fatal && common_flags()->print_module_map))
61 DumpProcessMap();
62 if (fatal)
63 Die();
64 }
65
MaybeAppendToErrorMessage(const char * msg)66 static void MaybeAppendToErrorMessage(const char *msg) {
67 Lock lock(&error_message_lock_);
68 if (!error_message_ptr_)
69 return;
70 error_message_ptr_->Append(msg);
71 }
72
SetErrorReportCallback(void (* callback)(const char *))73 static void SetErrorReportCallback(void (*callback)(const char *)) {
74 Lock lock(&error_message_lock_);
75 error_report_callback_ = callback;
76 }
77
78 private:
79 InternalScopedString error_message_;
80 bool fatal;
81
82 static Mutex error_message_lock_;
83 static InternalScopedString *error_message_ptr_
84 SANITIZER_GUARDED_BY(error_message_lock_);
85 static void (*error_report_callback_)(const char *);
86 };
87
88 Mutex ScopedReport::error_message_lock_;
89 InternalScopedString *ScopedReport::error_message_ptr_;
90 void (*ScopedReport::error_report_callback_)(const char *);
91
92 // If there is an active ScopedReport, append to its error message.
AppendToErrorMessageBuffer(const char * buffer)93 void AppendToErrorMessageBuffer(const char *buffer) {
94 ScopedReport::MaybeAppendToErrorMessage(buffer);
95 }
96
GetStackTraceFromId(u32 id)97 static StackTrace GetStackTraceFromId(u32 id) {
98 CHECK(id);
99 StackTrace res = StackDepotGet(id);
100 CHECK(res.trace);
101 return res;
102 }
103
MaybePrintAndroidHelpUrl()104 static void MaybePrintAndroidHelpUrl() {
105 #if SANITIZER_ANDROID
106 Printf(
107 "Learn more about HWASan reports: "
108 "https://source.android.com/docs/security/test/memory-safety/"
109 "hwasan-reports\n");
110 #endif
111 }
112
113 namespace {
114 // A RAII object that holds a copy of the current thread stack ring buffer.
115 // The actual stack buffer may change while we are iterating over it (for
116 // example, Printf may call syslog() which can itself be built with hwasan).
117 class SavedStackAllocations {
118 public:
119 SavedStackAllocations() = default;
120
SavedStackAllocations(Thread * t)121 explicit SavedStackAllocations(Thread *t) { CopyFrom(t); }
122
CopyFrom(Thread * t)123 void CopyFrom(Thread *t) {
124 StackAllocationsRingBuffer *rb = t->stack_allocations();
125 uptr size = rb->size() * sizeof(uptr);
126 void *storage =
127 MmapAlignedOrDieOnFatalError(size, size * 2, "saved stack allocations");
128 new (&rb_) StackAllocationsRingBuffer(*rb, storage);
129 thread_id_ = t->unique_id();
130 }
131
~SavedStackAllocations()132 ~SavedStackAllocations() {
133 if (rb_) {
134 StackAllocationsRingBuffer *rb = get();
135 UnmapOrDie(rb->StartOfStorage(), rb->size() * sizeof(uptr));
136 }
137 }
138
get() const139 const StackAllocationsRingBuffer *get() const {
140 return (const StackAllocationsRingBuffer *)&rb_;
141 }
142
get()143 StackAllocationsRingBuffer *get() {
144 return (StackAllocationsRingBuffer *)&rb_;
145 }
146
thread_id() const147 u32 thread_id() const { return thread_id_; }
148
149 private:
150 uptr rb_ = 0;
151 u32 thread_id_;
152 };
153
154 class Decorator: public __sanitizer::SanitizerCommonDecorator {
155 public:
Decorator()156 Decorator() : SanitizerCommonDecorator() { }
Access()157 const char *Access() { return Blue(); }
Allocation() const158 const char *Allocation() const { return Magenta(); }
Origin() const159 const char *Origin() const { return Magenta(); }
Name() const160 const char *Name() const { return Green(); }
Location()161 const char *Location() { return Green(); }
Thread()162 const char *Thread() { return Green(); }
163 };
164 } // namespace
165
FindHeapAllocation(HeapAllocationsRingBuffer * rb,uptr tagged_addr,HeapAllocationRecord * har,uptr * ring_index,uptr * num_matching_addrs,uptr * num_matching_addrs_4b)166 static bool FindHeapAllocation(HeapAllocationsRingBuffer *rb, uptr tagged_addr,
167 HeapAllocationRecord *har, uptr *ring_index,
168 uptr *num_matching_addrs,
169 uptr *num_matching_addrs_4b) {
170 if (!rb) return false;
171
172 *num_matching_addrs = 0;
173 *num_matching_addrs_4b = 0;
174 for (uptr i = 0, size = rb->size(); i < size; i++) {
175 auto h = (*rb)[i];
176 if (h.tagged_addr <= tagged_addr &&
177 h.tagged_addr + h.requested_size > tagged_addr) {
178 *har = h;
179 *ring_index = i;
180 return true;
181 }
182
183 // Measure the number of heap ring buffer entries that would have matched
184 // if we had only one entry per address (e.g. if the ring buffer data was
185 // stored at the address itself). This will help us tune the allocator
186 // implementation for MTE.
187 if (UntagAddr(h.tagged_addr) <= UntagAddr(tagged_addr) &&
188 UntagAddr(h.tagged_addr) + h.requested_size > UntagAddr(tagged_addr)) {
189 ++*num_matching_addrs;
190 }
191
192 // Measure the number of heap ring buffer entries that would have matched
193 // if we only had 4 tag bits, which is the case for MTE.
194 auto untag_4b = [](uptr p) {
195 return p & ((1ULL << 60) - 1);
196 };
197 if (untag_4b(h.tagged_addr) <= untag_4b(tagged_addr) &&
198 untag_4b(h.tagged_addr) + h.requested_size > untag_4b(tagged_addr)) {
199 ++*num_matching_addrs_4b;
200 }
201 }
202 return false;
203 }
204
PrintStackAllocations(const StackAllocationsRingBuffer * sa,tag_t addr_tag,uptr untagged_addr)205 static void PrintStackAllocations(const StackAllocationsRingBuffer *sa,
206 tag_t addr_tag, uptr untagged_addr) {
207 uptr frames = Min((uptr)flags()->stack_history_size, sa->size());
208 bool found_local = false;
209 InternalScopedString location;
210 for (uptr i = 0; i < frames; i++) {
211 const uptr *record_addr = &(*sa)[i];
212 uptr record = *record_addr;
213 if (!record)
214 break;
215 tag_t base_tag =
216 reinterpret_cast<uptr>(record_addr) >> kRecordAddrBaseTagShift;
217 const uptr fp = (record >> kRecordFPShift) << kRecordFPLShift;
218 CHECK_LT(fp, kRecordFPModulus);
219 uptr pc_mask = (1ULL << kRecordFPShift) - 1;
220 uptr pc = record & pc_mask;
221 FrameInfo frame;
222 if (!Symbolizer::GetOrInit()->SymbolizeFrame(pc, &frame))
223 continue;
224 for (LocalInfo &local : frame.locals) {
225 if (!local.has_frame_offset || !local.has_size || !local.has_tag_offset)
226 continue;
227 if (!(local.name && internal_strlen(local.name)) &&
228 !(local.function_name && internal_strlen(local.function_name)) &&
229 !(local.decl_file && internal_strlen(local.decl_file)))
230 continue;
231 tag_t obj_tag = base_tag ^ local.tag_offset;
232 if (obj_tag != addr_tag)
233 continue;
234
235 // We only store bits 4-19 of FP (bits 0-3 are guaranteed to be zero).
236 // So we know only `FP % kRecordFPModulus`, and we can only calculate
237 // `local_beg % kRecordFPModulus`.
238 // Out of all possible `local_beg` we will only consider 2 candidates
239 // nearest to the `untagged_addr`.
240 uptr local_beg_mod = (fp + local.frame_offset) % kRecordFPModulus;
241 // Pick `local_beg` in the same 1 MiB block as `untagged_addr`.
242 uptr local_beg =
243 RoundDownTo(untagged_addr, kRecordFPModulus) + local_beg_mod;
244 // Pick the largest `local_beg <= untagged_addr`. It's either the current
245 // one or the one before.
246 if (local_beg > untagged_addr)
247 local_beg -= kRecordFPModulus;
248
249 uptr offset = -1ull;
250 const char *whence;
251 const char *cause = nullptr;
252 uptr best_beg;
253
254 // Try two 1 MiB blocks options and pick nearest one.
255 for (uptr i = 0; i < 2; ++i, local_beg += kRecordFPModulus) {
256 uptr local_end = local_beg + local.size;
257 if (local_beg > local_end)
258 continue; // This is a wraparound.
259 if (local_beg <= untagged_addr && untagged_addr < local_end) {
260 offset = untagged_addr - local_beg;
261 whence = "inside";
262 cause = "use-after-scope";
263 best_beg = local_beg;
264 break; // This is as close at it can be.
265 }
266
267 if (untagged_addr >= local_end) {
268 uptr new_offset = untagged_addr - local_end;
269 if (new_offset < offset) {
270 offset = new_offset;
271 whence = "after";
272 cause = "stack-buffer-overflow";
273 best_beg = local_beg;
274 }
275 } else {
276 uptr new_offset = local_beg - untagged_addr;
277 if (new_offset < offset) {
278 offset = new_offset;
279 whence = "before";
280 cause = "stack-buffer-overflow";
281 best_beg = local_beg;
282 }
283 }
284 }
285
286 // To fail the `untagged_addr` must be near nullptr, which is impossible
287 // with Linux user space memory layout.
288 if (!cause)
289 continue;
290
291 if (!found_local) {
292 Printf("\nPotentially referenced stack objects:\n");
293 found_local = true;
294 }
295
296 Decorator d;
297 Printf("%s", d.Error());
298 Printf("Cause: %s\n", cause);
299 Printf("%s", d.Default());
300 Printf("%s", d.Location());
301 StackTracePrinter::GetOrInit()->RenderSourceLocation(
302 &location, local.decl_file, local.decl_line, /* column= */ 0,
303 common_flags()->symbolize_vs_style,
304 common_flags()->strip_path_prefix);
305 Printf(
306 "%p is located %zd bytes %s a %zd-byte local variable %s "
307 "[%p,%p) "
308 "in %s %s\n",
309 untagged_addr, offset, whence, local.size, local.name, best_beg,
310 best_beg + local.size, local.function_name, location.data());
311 location.clear();
312 Printf("%s\n", d.Default());
313 }
314 frame.Clear();
315 }
316
317 if (found_local)
318 return;
319
320 // We didn't find any locals. Most likely we don't have symbols, so dump
321 // the information that we have for offline analysis.
322 InternalScopedString frame_desc;
323 Printf("Previously allocated frames:\n");
324 for (uptr i = 0; i < frames; i++) {
325 const uptr *record_addr = &(*sa)[i];
326 uptr record = *record_addr;
327 if (!record)
328 break;
329 uptr pc_mask = (1ULL << 48) - 1;
330 uptr pc = record & pc_mask;
331 frame_desc.AppendF(" record_addr:%p record:0x%zx",
332 reinterpret_cast<const void *>(record_addr), record);
333 SymbolizedStackHolder symbolized_stack(
334 Symbolizer::GetOrInit()->SymbolizePC(pc));
335 const SymbolizedStack *frame = symbolized_stack.get();
336 if (frame) {
337 StackTracePrinter::GetOrInit()->RenderFrame(
338 &frame_desc, " %F %L", 0, frame->info.address, &frame->info,
339 common_flags()->symbolize_vs_style,
340 common_flags()->strip_path_prefix);
341 }
342 Printf("%s\n", frame_desc.data());
343 frame_desc.clear();
344 }
345 }
346
347 // Returns true if tag == *tag_ptr, reading tags from short granules if
348 // necessary. This may return a false positive if tags 1-15 are used as a
349 // regular tag rather than a short granule marker.
TagsEqual(tag_t tag,tag_t * tag_ptr)350 static bool TagsEqual(tag_t tag, tag_t *tag_ptr) {
351 if (tag == *tag_ptr)
352 return true;
353 if (*tag_ptr == 0 || *tag_ptr > kShadowAlignment - 1)
354 return false;
355 uptr mem = ShadowToMem(reinterpret_cast<uptr>(tag_ptr));
356 tag_t inline_tag = *reinterpret_cast<tag_t *>(mem + kShadowAlignment - 1);
357 return tag == inline_tag;
358 }
359
360 // HWASan globals store the size of the global in the descriptor. In cases where
361 // we don't have a binary with symbols, we can't grab the size of the global
362 // from the debug info - but we might be able to retrieve it from the
363 // descriptor. Returns zero if the lookup failed.
GetGlobalSizeFromDescriptor(uptr ptr)364 static uptr GetGlobalSizeFromDescriptor(uptr ptr) {
365 // Find the ELF object that this global resides in.
366 Dl_info info;
367 if (dladdr(reinterpret_cast<void *>(ptr), &info) == 0)
368 return 0;
369 auto *ehdr = reinterpret_cast<const ElfW(Ehdr) *>(info.dli_fbase);
370 auto *phdr_begin = reinterpret_cast<const ElfW(Phdr) *>(
371 reinterpret_cast<const u8 *>(ehdr) + ehdr->e_phoff);
372
373 // Get the load bias. This is normally the same as the dli_fbase address on
374 // position-independent code, but can be different on non-PIE executables,
375 // binaries using LLD's partitioning feature, or binaries compiled with a
376 // linker script.
377 ElfW(Addr) load_bias = 0;
378 for (const auto &phdr :
379 ArrayRef<const ElfW(Phdr)>(phdr_begin, phdr_begin + ehdr->e_phnum)) {
380 if (phdr.p_type != PT_LOAD || phdr.p_offset != 0)
381 continue;
382 load_bias = reinterpret_cast<ElfW(Addr)>(ehdr) - phdr.p_vaddr;
383 break;
384 }
385
386 // Walk all globals in this ELF object, looking for the one we're interested
387 // in. Once we find it, we can stop iterating and return the size of the
388 // global we're interested in.
389 for (const hwasan_global &global :
390 HwasanGlobalsFor(load_bias, phdr_begin, ehdr->e_phnum))
391 if (global.addr() <= ptr && ptr < global.addr() + global.size())
392 return global.size();
393
394 return 0;
395 }
396
ReportStats()397 void ReportStats() {}
398
399 constexpr uptr kDumpWidth = 16;
400 constexpr uptr kShadowLines = 17;
401 constexpr uptr kShadowDumpSize = kShadowLines * kDumpWidth;
402
403 constexpr uptr kShortLines = 3;
404 constexpr uptr kShortDumpSize = kShortLines * kDumpWidth;
405 constexpr uptr kShortDumpOffset = (kShadowLines - kShortLines) / 2 * kDumpWidth;
406
GetPrintTagStart(uptr addr)407 static uptr GetPrintTagStart(uptr addr) {
408 addr = MemToShadow(addr);
409 addr = RoundDownTo(addr, kDumpWidth);
410 addr -= kDumpWidth * (kShadowLines / 2);
411 return addr;
412 }
413
414 template <typename PrintTag>
PrintTagInfoAroundAddr(uptr addr,uptr num_rows,InternalScopedString & s,PrintTag print_tag)415 static void PrintTagInfoAroundAddr(uptr addr, uptr num_rows,
416 InternalScopedString &s,
417 PrintTag print_tag) {
418 uptr center_row_beg = RoundDownTo(addr, kDumpWidth);
419 uptr beg_row = center_row_beg - kDumpWidth * (num_rows / 2);
420 uptr end_row = center_row_beg + kDumpWidth * ((num_rows + 1) / 2);
421 for (uptr row = beg_row; row < end_row; row += kDumpWidth) {
422 s.Append(row == center_row_beg ? "=>" : " ");
423 s.AppendF("%p:", (void *)ShadowToMem(row));
424 for (uptr i = 0; i < kDumpWidth; i++) {
425 s.Append(row + i == addr ? "[" : " ");
426 print_tag(s, row + i);
427 s.Append(row + i == addr ? "]" : " ");
428 }
429 s.Append("\n");
430 }
431 }
432
433 template <typename GetTag, typename GetShortTag>
PrintTagsAroundAddr(uptr addr,GetTag get_tag,GetShortTag get_short_tag)434 static void PrintTagsAroundAddr(uptr addr, GetTag get_tag,
435 GetShortTag get_short_tag) {
436 InternalScopedString s;
437 addr = MemToShadow(addr);
438 s.AppendF(
439 "\nMemory tags around the buggy address (one tag corresponds to %zd "
440 "bytes):\n",
441 kShadowAlignment);
442 PrintTagInfoAroundAddr(addr, kShadowLines, s,
443 [&](InternalScopedString &s, uptr tag_addr) {
444 tag_t tag = get_tag(tag_addr);
445 s.AppendF("%02x", tag);
446 });
447
448 s.AppendF(
449 "Tags for short granules around the buggy address (one tag corresponds "
450 "to %zd bytes):\n",
451 kShadowAlignment);
452 PrintTagInfoAroundAddr(addr, kShortLines, s,
453 [&](InternalScopedString &s, uptr tag_addr) {
454 tag_t tag = get_tag(tag_addr);
455 if (tag >= 1 && tag <= kShadowAlignment) {
456 tag_t short_tag = get_short_tag(tag_addr);
457 s.AppendF("%02x", short_tag);
458 } else {
459 s.Append("..");
460 }
461 });
462 s.Append(
463 "See "
464 "https://clang.llvm.org/docs/"
465 "HardwareAssistedAddressSanitizerDesign.html#short-granules for a "
466 "description of short granule tags\n");
467 Printf("%s", s.data());
468 }
469
GetTopPc(const StackTrace * stack)470 static uptr GetTopPc(const StackTrace *stack) {
471 return stack->size ? StackTrace::GetPreviousInstructionPc(stack->trace[0])
472 : 0;
473 }
474
475 namespace {
476 class BaseReport {
477 public:
BaseReport(StackTrace * stack,bool fatal,uptr tagged_addr,uptr access_size)478 BaseReport(StackTrace *stack, bool fatal, uptr tagged_addr, uptr access_size)
479 : scoped_report(fatal),
480 stack(stack),
481 tagged_addr(tagged_addr),
482 access_size(access_size),
483 untagged_addr(UntagAddr(tagged_addr)),
484 ptr_tag(GetTagFromPointer(tagged_addr)),
485 mismatch_offset(FindMismatchOffset()),
486 heap(CopyHeapChunk()),
487 allocations(CopyAllocations()),
488 candidate(FindBufferOverflowCandidate()),
489 shadow(CopyShadow()) {}
490
491 protected:
492 struct OverflowCandidate {
493 uptr untagged_addr = 0;
494 bool after = false;
495 bool is_close = false;
496
497 struct {
498 uptr begin = 0;
499 uptr end = 0;
500 u32 thread_id = 0;
501 u32 stack_id = 0;
502 bool is_allocated = false;
503 } heap;
504 };
505
506 struct HeapAllocation {
507 HeapAllocationRecord har = {};
508 uptr ring_index = 0;
509 uptr num_matching_addrs = 0;
510 uptr num_matching_addrs_4b = 0;
511 u32 free_thread_id = 0;
512 };
513
514 struct Allocations {
515 ArrayRef<SavedStackAllocations> stack;
516 ArrayRef<HeapAllocation> heap;
517 };
518
519 struct HeapChunk {
520 uptr begin = 0;
521 uptr size = 0;
522 u32 stack_id = 0;
523 bool from_small_heap = false;
524 bool is_allocated = false;
525 };
526
527 struct Shadow {
528 uptr addr = 0;
529 tag_t tags[kShadowDumpSize] = {};
530 tag_t short_tags[kShortDumpSize] = {};
531 };
532
533 sptr FindMismatchOffset() const;
534 Shadow CopyShadow() const;
535 tag_t GetTagCopy(uptr addr) const;
536 tag_t GetShortTagCopy(uptr addr) const;
537 HeapChunk CopyHeapChunk() const;
538 Allocations CopyAllocations();
539 OverflowCandidate FindBufferOverflowCandidate() const;
540 void PrintAddressDescription() const;
541 void PrintHeapOrGlobalCandidate() const;
542 void PrintTags(uptr addr) const;
543
544 SavedStackAllocations stack_allocations_storage[16];
545 HeapAllocation heap_allocations_storage[256];
546
547 const ScopedReport scoped_report;
548 const StackTrace *stack = nullptr;
549 const uptr tagged_addr = 0;
550 const uptr access_size = 0;
551 const uptr untagged_addr = 0;
552 const tag_t ptr_tag = 0;
553 const sptr mismatch_offset = 0;
554
555 const HeapChunk heap;
556 const Allocations allocations;
557 const OverflowCandidate candidate;
558
559 const Shadow shadow;
560 };
561
FindMismatchOffset() const562 sptr BaseReport::FindMismatchOffset() const {
563 if (!access_size)
564 return 0;
565 sptr offset =
566 __hwasan_test_shadow(reinterpret_cast<void *>(tagged_addr), access_size);
567 CHECK_GE(offset, 0);
568 CHECK_LT(offset, static_cast<sptr>(access_size));
569 tag_t *tag_ptr =
570 reinterpret_cast<tag_t *>(MemToShadow(untagged_addr + offset));
571 tag_t mem_tag = *tag_ptr;
572
573 if (mem_tag && mem_tag < kShadowAlignment) {
574 tag_t *granule_ptr = reinterpret_cast<tag_t *>((untagged_addr + offset) &
575 ~(kShadowAlignment - 1));
576 // If offset is 0, (untagged_addr + offset) is not aligned to granules.
577 // This is the offset of the leftmost accessed byte within the bad granule.
578 u8 in_granule_offset = (untagged_addr + offset) & (kShadowAlignment - 1);
579 tag_t short_tag = granule_ptr[kShadowAlignment - 1];
580 // The first mismatch was a short granule that matched the ptr_tag.
581 if (short_tag == ptr_tag) {
582 // If the access starts after the end of the short granule, then the first
583 // bad byte is the first byte of the access; otherwise it is the first
584 // byte past the end of the short granule
585 if (mem_tag > in_granule_offset) {
586 offset += mem_tag - in_granule_offset;
587 }
588 }
589 }
590 return offset;
591 }
592
CopyShadow() const593 BaseReport::Shadow BaseReport::CopyShadow() const {
594 Shadow result;
595 if (!MemIsApp(untagged_addr))
596 return result;
597
598 result.addr = GetPrintTagStart(untagged_addr + mismatch_offset);
599 uptr tag_addr = result.addr;
600 uptr short_end = kShortDumpOffset + ARRAY_SIZE(shadow.short_tags);
601 for (uptr i = 0; i < ARRAY_SIZE(result.tags); ++i, ++tag_addr) {
602 if (!MemIsShadow(tag_addr))
603 continue;
604 result.tags[i] = *reinterpret_cast<tag_t *>(tag_addr);
605 if (i < kShortDumpOffset || i >= short_end)
606 continue;
607 uptr granule_addr = ShadowToMem(tag_addr);
608 if (1 <= result.tags[i] && result.tags[i] <= kShadowAlignment &&
609 IsAccessibleMemoryRange(granule_addr, kShadowAlignment)) {
610 result.short_tags[i - kShortDumpOffset] =
611 *reinterpret_cast<tag_t *>(granule_addr + kShadowAlignment - 1);
612 }
613 }
614 return result;
615 }
616
GetTagCopy(uptr addr) const617 tag_t BaseReport::GetTagCopy(uptr addr) const {
618 CHECK_GE(addr, shadow.addr);
619 uptr idx = addr - shadow.addr;
620 CHECK_LT(idx, ARRAY_SIZE(shadow.tags));
621 return shadow.tags[idx];
622 }
623
GetShortTagCopy(uptr addr) const624 tag_t BaseReport::GetShortTagCopy(uptr addr) const {
625 CHECK_GE(addr, shadow.addr + kShortDumpOffset);
626 uptr idx = addr - shadow.addr - kShortDumpOffset;
627 CHECK_LT(idx, ARRAY_SIZE(shadow.short_tags));
628 return shadow.short_tags[idx];
629 }
630
CopyHeapChunk() const631 BaseReport::HeapChunk BaseReport::CopyHeapChunk() const {
632 HeapChunk result = {};
633 if (MemIsShadow(untagged_addr))
634 return result;
635 HwasanChunkView chunk = FindHeapChunkByAddress(untagged_addr);
636 result.begin = chunk.Beg();
637 if (result.begin) {
638 result.size = chunk.ActualSize();
639 result.from_small_heap = chunk.FromSmallHeap();
640 result.is_allocated = chunk.IsAllocated();
641 result.stack_id = chunk.GetAllocStackId();
642 }
643 return result;
644 }
645
CopyAllocations()646 BaseReport::Allocations BaseReport::CopyAllocations() {
647 if (MemIsShadow(untagged_addr))
648 return {};
649 uptr stack_allocations_count = 0;
650 uptr heap_allocations_count = 0;
651 hwasanThreadList().VisitAllLiveThreads([&](Thread *t) {
652 if (stack_allocations_count < ARRAY_SIZE(stack_allocations_storage) &&
653 t->AddrIsInStack(untagged_addr)) {
654 stack_allocations_storage[stack_allocations_count++].CopyFrom(t);
655 }
656
657 if (heap_allocations_count < ARRAY_SIZE(heap_allocations_storage)) {
658 // Scan all threads' ring buffers to find if it's a heap-use-after-free.
659 HeapAllocationRecord har;
660 uptr ring_index, num_matching_addrs, num_matching_addrs_4b;
661 if (FindHeapAllocation(t->heap_allocations(), tagged_addr, &har,
662 &ring_index, &num_matching_addrs,
663 &num_matching_addrs_4b)) {
664 auto &ha = heap_allocations_storage[heap_allocations_count++];
665 ha.har = har;
666 ha.ring_index = ring_index;
667 ha.num_matching_addrs = num_matching_addrs;
668 ha.num_matching_addrs_4b = num_matching_addrs_4b;
669 ha.free_thread_id = t->unique_id();
670 }
671 }
672 });
673
674 return {{stack_allocations_storage, stack_allocations_count},
675 {heap_allocations_storage, heap_allocations_count}};
676 }
677
FindBufferOverflowCandidate() const678 BaseReport::OverflowCandidate BaseReport::FindBufferOverflowCandidate() const {
679 OverflowCandidate result = {};
680 if (MemIsShadow(untagged_addr))
681 return result;
682 // Check if this looks like a heap buffer overflow by scanning
683 // the shadow left and right and looking for the first adjacent
684 // object with a different memory tag. If that tag matches ptr_tag,
685 // check the allocator if it has a live chunk there.
686 tag_t *tag_ptr = reinterpret_cast<tag_t *>(MemToShadow(untagged_addr));
687 tag_t *candidate_tag_ptr = nullptr, *left = tag_ptr, *right = tag_ptr;
688 uptr candidate_distance = 0;
689 for (; candidate_distance < 1000; candidate_distance++) {
690 if (MemIsShadow(reinterpret_cast<uptr>(left)) && TagsEqual(ptr_tag, left)) {
691 candidate_tag_ptr = left;
692 break;
693 }
694 --left;
695 if (MemIsShadow(reinterpret_cast<uptr>(right)) &&
696 TagsEqual(ptr_tag, right)) {
697 candidate_tag_ptr = right;
698 break;
699 }
700 ++right;
701 }
702
703 constexpr auto kCloseCandidateDistance = 1;
704 result.is_close = candidate_distance <= kCloseCandidateDistance;
705
706 result.after = candidate_tag_ptr == left;
707 result.untagged_addr = ShadowToMem(reinterpret_cast<uptr>(candidate_tag_ptr));
708 HwasanChunkView chunk = FindHeapChunkByAddress(result.untagged_addr);
709 if (chunk.IsAllocated()) {
710 result.heap.is_allocated = true;
711 result.heap.begin = chunk.Beg();
712 result.heap.end = chunk.End();
713 result.heap.thread_id = chunk.GetAllocThreadId();
714 result.heap.stack_id = chunk.GetAllocStackId();
715 }
716 return result;
717 }
718
PrintHeapOrGlobalCandidate() const719 void BaseReport::PrintHeapOrGlobalCandidate() const {
720 Decorator d;
721 if (candidate.heap.is_allocated) {
722 uptr offset;
723 const char *whence;
724 if (candidate.heap.begin <= untagged_addr &&
725 untagged_addr < candidate.heap.end) {
726 offset = untagged_addr - candidate.heap.begin;
727 whence = "inside";
728 } else if (candidate.after) {
729 offset = untagged_addr - candidate.heap.end;
730 whence = "after";
731 } else {
732 offset = candidate.heap.begin - untagged_addr;
733 whence = "before";
734 }
735 Printf("%s", d.Error());
736 Printf("\nCause: heap-buffer-overflow\n");
737 Printf("%s", d.Default());
738 Printf("%s", d.Location());
739 Printf("%p is located %zd bytes %s a %zd-byte region [%p,%p)\n",
740 untagged_addr, offset, whence,
741 candidate.heap.end - candidate.heap.begin, candidate.heap.begin,
742 candidate.heap.end);
743 Printf("%s", d.Allocation());
744 Printf("allocated by thread T%u here:\n", candidate.heap.thread_id);
745 Printf("%s", d.Default());
746 GetStackTraceFromId(candidate.heap.stack_id).Print();
747 return;
748 }
749 // Check whether the address points into a loaded library. If so, this is
750 // most likely a global variable.
751 const char *module_name;
752 uptr module_address;
753 Symbolizer *sym = Symbolizer::GetOrInit();
754 if (sym->GetModuleNameAndOffsetForPC(candidate.untagged_addr, &module_name,
755 &module_address)) {
756 Printf("%s", d.Error());
757 Printf("\nCause: global-overflow\n");
758 Printf("%s", d.Default());
759 DataInfo info;
760 Printf("%s", d.Location());
761 if (sym->SymbolizeData(candidate.untagged_addr, &info) && info.start) {
762 Printf(
763 "%p is located %zd bytes %s a %zd-byte global variable "
764 "%s [%p,%p) in %s\n",
765 untagged_addr,
766 candidate.after ? untagged_addr - (info.start + info.size)
767 : info.start - untagged_addr,
768 candidate.after ? "after" : "before", info.size, info.name,
769 info.start, info.start + info.size, module_name);
770 } else {
771 uptr size = GetGlobalSizeFromDescriptor(candidate.untagged_addr);
772 if (size == 0)
773 // We couldn't find the size of the global from the descriptors.
774 Printf(
775 "%p is located %s a global variable in "
776 "\n #0 0x%x (%s+0x%x)\n",
777 untagged_addr, candidate.after ? "after" : "before",
778 candidate.untagged_addr, module_name, module_address);
779 else
780 Printf(
781 "%p is located %s a %zd-byte global variable in "
782 "\n #0 0x%x (%s+0x%x)\n",
783 untagged_addr, candidate.after ? "after" : "before", size,
784 candidate.untagged_addr, module_name, module_address);
785 }
786 Printf("%s", d.Default());
787 }
788 }
789
PrintAddressDescription() const790 void BaseReport::PrintAddressDescription() const {
791 Decorator d;
792 int num_descriptions_printed = 0;
793
794 if (MemIsShadow(untagged_addr)) {
795 Printf("%s%p is HWAsan shadow memory.\n%s", d.Location(), untagged_addr,
796 d.Default());
797 return;
798 }
799
800 // Print some very basic information about the address, if it's a heap.
801 if (heap.begin) {
802 Printf(
803 "%s[%p,%p) is a %s %s heap chunk; "
804 "size: %zd offset: %zd\n%s",
805 d.Location(), heap.begin, heap.begin + heap.size,
806 heap.from_small_heap ? "small" : "large",
807 heap.is_allocated ? "allocated" : "unallocated", heap.size,
808 untagged_addr - heap.begin, d.Default());
809 }
810
811 auto announce_by_id = [](u32 thread_id) {
812 hwasanThreadList().VisitAllLiveThreads([&](Thread *t) {
813 if (thread_id == t->unique_id())
814 t->Announce();
815 });
816 };
817
818 // Check stack first. If the address is on the stack of a live thread, we
819 // know it cannot be a heap / global overflow.
820 for (const auto &sa : allocations.stack) {
821 Printf("%s", d.Error());
822 Printf("\nCause: stack tag-mismatch\n");
823 Printf("%s", d.Location());
824 Printf("Address %p is located in stack of thread T%zd\n", untagged_addr,
825 sa.thread_id());
826 Printf("%s", d.Default());
827 announce_by_id(sa.thread_id());
828 PrintStackAllocations(sa.get(), ptr_tag, untagged_addr);
829 num_descriptions_printed++;
830 }
831
832 if (allocations.stack.empty() && candidate.untagged_addr &&
833 candidate.is_close) {
834 PrintHeapOrGlobalCandidate();
835 num_descriptions_printed++;
836 }
837
838 for (const auto &ha : allocations.heap) {
839 const HeapAllocationRecord har = ha.har;
840
841 Printf("%s", d.Error());
842 Printf("\nCause: use-after-free\n");
843 Printf("%s", d.Location());
844 Printf("%p is located %zd bytes inside a %zd-byte region [%p,%p)\n",
845 untagged_addr, untagged_addr - UntagAddr(har.tagged_addr),
846 har.requested_size, UntagAddr(har.tagged_addr),
847 UntagAddr(har.tagged_addr) + har.requested_size);
848 Printf("%s", d.Allocation());
849 Printf("freed by thread T%u here:\n", ha.free_thread_id);
850 Printf("%s", d.Default());
851 GetStackTraceFromId(har.free_context_id).Print();
852
853 Printf("%s", d.Allocation());
854 Printf("previously allocated by thread T%u here:\n", har.alloc_thread_id);
855 Printf("%s", d.Default());
856 GetStackTraceFromId(har.alloc_context_id).Print();
857
858 // Print a developer note: the index of this heap object
859 // in the thread's deallocation ring buffer.
860 Printf("hwasan_dev_note_heap_rb_distance: %zd %zd\n", ha.ring_index + 1,
861 flags()->heap_history_size);
862 Printf("hwasan_dev_note_num_matching_addrs: %zd\n", ha.num_matching_addrs);
863 Printf("hwasan_dev_note_num_matching_addrs_4b: %zd\n",
864 ha.num_matching_addrs_4b);
865
866 announce_by_id(ha.free_thread_id);
867 // TODO: announce_by_id(har.alloc_thread_id);
868 num_descriptions_printed++;
869 }
870
871 if (candidate.untagged_addr && num_descriptions_printed == 0) {
872 PrintHeapOrGlobalCandidate();
873 num_descriptions_printed++;
874 }
875
876 // Print the remaining threads, as an extra information, 1 line per thread.
877 if (flags()->print_live_threads_info) {
878 Printf("\n");
879 hwasanThreadList().VisitAllLiveThreads([&](Thread *t) { t->Announce(); });
880 }
881
882 if (!num_descriptions_printed)
883 // We exhausted our possibilities. Bail out.
884 Printf("HWAddressSanitizer can not describe address in more detail.\n");
885 if (num_descriptions_printed > 1) {
886 Printf(
887 "There are %d potential causes, printed above in order "
888 "of likeliness.\n",
889 num_descriptions_printed);
890 }
891 }
892
PrintTags(uptr addr) const893 void BaseReport::PrintTags(uptr addr) const {
894 if (shadow.addr) {
895 PrintTagsAroundAddr(
896 addr, [&](uptr addr) { return GetTagCopy(addr); },
897 [&](uptr addr) { return GetShortTagCopy(addr); });
898 }
899 }
900
901 class InvalidFreeReport : public BaseReport {
902 public:
InvalidFreeReport(StackTrace * stack,uptr tagged_addr)903 InvalidFreeReport(StackTrace *stack, uptr tagged_addr)
904 : BaseReport(stack, flags()->halt_on_error, tagged_addr, 0) {}
905 ~InvalidFreeReport();
906
907 private:
908 };
909
~InvalidFreeReport()910 InvalidFreeReport::~InvalidFreeReport() {
911 Decorator d;
912 Printf("%s", d.Error());
913 uptr pc = GetTopPc(stack);
914 const char *bug_type = "invalid-free";
915 const Thread *thread = GetCurrentThread();
916 if (thread) {
917 Report("ERROR: %s: %s on address %p at pc %p on thread T%zd\n",
918 SanitizerToolName, bug_type, untagged_addr, pc, thread->unique_id());
919 } else {
920 Report("ERROR: %s: %s on address %p at pc %p on unknown thread\n",
921 SanitizerToolName, bug_type, untagged_addr, pc);
922 }
923 Printf("%s", d.Access());
924 if (shadow.addr) {
925 Printf("tags: %02x/%02x (ptr/mem)\n", ptr_tag,
926 GetTagCopy(MemToShadow(untagged_addr)));
927 }
928 Printf("%s", d.Default());
929
930 stack->Print();
931
932 PrintAddressDescription();
933 PrintTags(untagged_addr);
934 MaybePrintAndroidHelpUrl();
935 ReportErrorSummary(bug_type, stack);
936 }
937
938 class TailOverwrittenReport : public BaseReport {
939 public:
TailOverwrittenReport(StackTrace * stack,uptr tagged_addr,uptr orig_size,const u8 * expected)940 explicit TailOverwrittenReport(StackTrace *stack, uptr tagged_addr,
941 uptr orig_size, const u8 *expected)
942 : BaseReport(stack, flags()->halt_on_error, tagged_addr, 0),
943 orig_size(orig_size),
944 tail_size(kShadowAlignment - (orig_size % kShadowAlignment)) {
945 CHECK_GT(tail_size, 0U);
946 CHECK_LT(tail_size, kShadowAlignment);
947 internal_memcpy(tail_copy,
948 reinterpret_cast<u8 *>(untagged_addr + orig_size),
949 tail_size);
950 internal_memcpy(actual_expected, expected, tail_size);
951 // Short granule is stashed in the last byte of the magic string. To avoid
952 // confusion, make the expected magic string contain the short granule tag.
953 if (orig_size % kShadowAlignment != 0)
954 actual_expected[tail_size - 1] = ptr_tag;
955 }
956 ~TailOverwrittenReport();
957
958 private:
959 const uptr orig_size = 0;
960 const uptr tail_size = 0;
961 u8 actual_expected[kShadowAlignment] = {};
962 u8 tail_copy[kShadowAlignment] = {};
963 };
964
~TailOverwrittenReport()965 TailOverwrittenReport::~TailOverwrittenReport() {
966 Decorator d;
967 Printf("%s", d.Error());
968 const char *bug_type = "allocation-tail-overwritten";
969 Report("ERROR: %s: %s; heap object [%p,%p) of size %zd\n", SanitizerToolName,
970 bug_type, untagged_addr, untagged_addr + orig_size, orig_size);
971 Printf("\n%s", d.Default());
972 Printf(
973 "Stack of invalid access unknown. Issue detected at deallocation "
974 "time.\n");
975 Printf("%s", d.Allocation());
976 Printf("deallocated here:\n");
977 Printf("%s", d.Default());
978 stack->Print();
979 if (heap.begin) {
980 Printf("%s", d.Allocation());
981 Printf("allocated here:\n");
982 Printf("%s", d.Default());
983 GetStackTraceFromId(heap.stack_id).Print();
984 }
985
986 InternalScopedString s;
987 u8 *tail = tail_copy;
988 s.Append("Tail contains: ");
989 for (uptr i = 0; i < kShadowAlignment - tail_size; i++) s.Append(".. ");
990 for (uptr i = 0; i < tail_size; i++) s.AppendF("%02x ", tail[i]);
991 s.Append("\n");
992 s.Append("Expected: ");
993 for (uptr i = 0; i < kShadowAlignment - tail_size; i++) s.Append(".. ");
994 for (uptr i = 0; i < tail_size; i++) s.AppendF("%02x ", actual_expected[i]);
995 s.Append("\n");
996 s.Append(" ");
997 for (uptr i = 0; i < kShadowAlignment - tail_size; i++) s.Append(" ");
998 for (uptr i = 0; i < tail_size; i++)
999 s.AppendF("%s ", actual_expected[i] != tail[i] ? "^^" : " ");
1000
1001 s.AppendF(
1002 "\nThis error occurs when a buffer overflow overwrites memory\n"
1003 "after a heap object, but within the %zd-byte granule, e.g.\n"
1004 " char *x = new char[20];\n"
1005 " x[25] = 42;\n"
1006 "%s does not detect such bugs in uninstrumented code at the time of "
1007 "write,"
1008 "\nbut can detect them at the time of free/delete.\n"
1009 "To disable this feature set HWASAN_OPTIONS=free_checks_tail_magic=0\n",
1010 kShadowAlignment, SanitizerToolName);
1011 Printf("%s", s.data());
1012 GetCurrentThread()->Announce();
1013 PrintTags(untagged_addr);
1014 MaybePrintAndroidHelpUrl();
1015 ReportErrorSummary(bug_type, stack);
1016 }
1017
1018 class TagMismatchReport : public BaseReport {
1019 public:
TagMismatchReport(StackTrace * stack,uptr tagged_addr,uptr access_size,bool is_store,bool fatal,uptr * registers_frame)1020 explicit TagMismatchReport(StackTrace *stack, uptr tagged_addr,
1021 uptr access_size, bool is_store, bool fatal,
1022 uptr *registers_frame)
1023 : BaseReport(stack, fatal, tagged_addr, access_size),
1024 is_store(is_store),
1025 registers_frame(registers_frame) {}
1026 ~TagMismatchReport();
1027
1028 private:
1029 const bool is_store;
1030 const uptr *registers_frame;
1031 };
1032
~TagMismatchReport()1033 TagMismatchReport::~TagMismatchReport() {
1034 Decorator d;
1035 // TODO: when possible, try to print heap-use-after-free, etc.
1036 const char *bug_type = "tag-mismatch";
1037 uptr pc = GetTopPc(stack);
1038 Printf("%s", d.Error());
1039 Report("ERROR: %s: %s on address %p at pc %p\n", SanitizerToolName, bug_type,
1040 untagged_addr, pc);
1041
1042 Thread *t = GetCurrentThread();
1043
1044 tag_t mem_tag = GetTagCopy(MemToShadow(untagged_addr + mismatch_offset));
1045
1046 Printf("%s", d.Access());
1047 if (mem_tag && mem_tag < kShadowAlignment) {
1048 tag_t short_tag =
1049 GetShortTagCopy(MemToShadow(untagged_addr + mismatch_offset));
1050 Printf(
1051 "%s of size %zu at %p tags: %02x/%02x(%02x) (ptr/mem) in thread T%zd\n",
1052 is_store ? "WRITE" : "READ", access_size, untagged_addr, ptr_tag,
1053 mem_tag, short_tag, t->unique_id());
1054 } else {
1055 Printf("%s of size %zu at %p tags: %02x/%02x (ptr/mem) in thread T%zd\n",
1056 is_store ? "WRITE" : "READ", access_size, untagged_addr, ptr_tag,
1057 mem_tag, t->unique_id());
1058 }
1059 if (mismatch_offset)
1060 Printf("Invalid access starting at offset %zu\n", mismatch_offset);
1061 Printf("%s", d.Default());
1062
1063 stack->Print();
1064
1065 PrintAddressDescription();
1066 t->Announce();
1067
1068 PrintTags(untagged_addr + mismatch_offset);
1069
1070 if (registers_frame)
1071 ReportRegisters(registers_frame, pc);
1072
1073 MaybePrintAndroidHelpUrl();
1074 ReportErrorSummary(bug_type, stack);
1075 }
1076 } // namespace
1077
ReportInvalidFree(StackTrace * stack,uptr tagged_addr)1078 void ReportInvalidFree(StackTrace *stack, uptr tagged_addr) {
1079 InvalidFreeReport R(stack, tagged_addr);
1080 }
1081
ReportTailOverwritten(StackTrace * stack,uptr tagged_addr,uptr orig_size,const u8 * expected)1082 void ReportTailOverwritten(StackTrace *stack, uptr tagged_addr, uptr orig_size,
1083 const u8 *expected) {
1084 TailOverwrittenReport R(stack, tagged_addr, orig_size, expected);
1085 }
1086
ReportTagMismatch(StackTrace * stack,uptr tagged_addr,uptr access_size,bool is_store,bool fatal,uptr * registers_frame)1087 void ReportTagMismatch(StackTrace *stack, uptr tagged_addr, uptr access_size,
1088 bool is_store, bool fatal, uptr *registers_frame) {
1089 TagMismatchReport R(stack, tagged_addr, access_size, is_store, fatal,
1090 registers_frame);
1091 }
1092
1093 // See the frame breakdown defined in __hwasan_tag_mismatch (from
1094 // hwasan_tag_mismatch_{aarch64,riscv64}.S).
ReportRegisters(const uptr * frame,uptr pc)1095 void ReportRegisters(const uptr *frame, uptr pc) {
1096 Printf("\nRegisters where the failure occurred (pc %p):\n", pc);
1097
1098 // We explicitly print a single line (4 registers/line) each iteration to
1099 // reduce the amount of logcat error messages printed. Each Printf() will
1100 // result in a new logcat line, irrespective of whether a newline is present,
1101 // and so we wish to reduce the number of Printf() calls we have to make.
1102 #if defined(__aarch64__)
1103 Printf(" x0 %016llx x1 %016llx x2 %016llx x3 %016llx\n",
1104 frame[0], frame[1], frame[2], frame[3]);
1105 #elif SANITIZER_RISCV64
1106 Printf(" sp %016llx x1 %016llx x2 %016llx x3 %016llx\n",
1107 reinterpret_cast<const u8 *>(frame) + 256, frame[1], frame[2],
1108 frame[3]);
1109 #endif
1110 Printf(" x4 %016llx x5 %016llx x6 %016llx x7 %016llx\n",
1111 frame[4], frame[5], frame[6], frame[7]);
1112 Printf(" x8 %016llx x9 %016llx x10 %016llx x11 %016llx\n",
1113 frame[8], frame[9], frame[10], frame[11]);
1114 Printf(" x12 %016llx x13 %016llx x14 %016llx x15 %016llx\n",
1115 frame[12], frame[13], frame[14], frame[15]);
1116 Printf(" x16 %016llx x17 %016llx x18 %016llx x19 %016llx\n",
1117 frame[16], frame[17], frame[18], frame[19]);
1118 Printf(" x20 %016llx x21 %016llx x22 %016llx x23 %016llx\n",
1119 frame[20], frame[21], frame[22], frame[23]);
1120 Printf(" x24 %016llx x25 %016llx x26 %016llx x27 %016llx\n",
1121 frame[24], frame[25], frame[26], frame[27]);
1122 // hwasan_check* reduces the stack pointer by 256, then __hwasan_tag_mismatch
1123 // passes it to this function.
1124 #if defined(__aarch64__)
1125 Printf(" x28 %016llx x29 %016llx x30 %016llx sp %016llx\n", frame[28],
1126 frame[29], frame[30], reinterpret_cast<const u8 *>(frame) + 256);
1127 #elif SANITIZER_RISCV64
1128 Printf(" x28 %016llx x29 %016llx x30 %016llx x31 %016llx\n", frame[28],
1129 frame[29], frame[30], frame[31]);
1130 #else
1131 #endif
1132 }
1133
1134 } // namespace __hwasan
1135
__hwasan_set_error_report_callback(void (* callback)(const char *))1136 void __hwasan_set_error_report_callback(void (*callback)(const char *)) {
1137 __hwasan::ScopedReport::SetErrorReportCallback(callback);
1138 }
1139