//===-- hwasan_report.cpp -------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file is a part of HWAddressSanitizer. // // Error reporting. //===----------------------------------------------------------------------===// #include "hwasan.h" #include "hwasan_allocator.h" #include "hwasan_mapping.h" #include "hwasan_report.h" #include "hwasan_thread.h" #include "hwasan_thread_list.h" #include "sanitizer_common/sanitizer_allocator_internal.h" #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_flags.h" #include "sanitizer_common/sanitizer_mutex.h" #include "sanitizer_common/sanitizer_report_decorator.h" #include "sanitizer_common/sanitizer_stackdepot.h" #include "sanitizer_common/sanitizer_stacktrace_printer.h" #include "sanitizer_common/sanitizer_symbolizer.h" using namespace __sanitizer; namespace __hwasan { class ScopedReport { public: ScopedReport(bool fatal = false) : error_message_(1), fatal(fatal) { BlockingMutexLock lock(&error_message_lock_); error_message_ptr_ = fatal ? &error_message_ : nullptr; ++hwasan_report_count; } ~ScopedReport() { { BlockingMutexLock lock(&error_message_lock_); if (fatal) SetAbortMessage(error_message_.data()); error_message_ptr_ = nullptr; } if (common_flags()->print_module_map >= 2 || (fatal && common_flags()->print_module_map)) DumpProcessMap(); if (fatal) Die(); } static void MaybeAppendToErrorMessage(const char *msg) { BlockingMutexLock lock(&error_message_lock_); if (!error_message_ptr_) return; uptr len = internal_strlen(msg); uptr old_size = error_message_ptr_->size(); error_message_ptr_->resize(old_size + len); // overwrite old trailing '\0', keep new trailing '\0' untouched. internal_memcpy(&(*error_message_ptr_)[old_size - 1], msg, len); } private: ScopedErrorReportLock error_report_lock_; InternalMmapVector error_message_; bool fatal; static InternalMmapVector *error_message_ptr_; static BlockingMutex error_message_lock_; }; InternalMmapVector *ScopedReport::error_message_ptr_; BlockingMutex ScopedReport::error_message_lock_; // If there is an active ScopedReport, append to its error message. void AppendToErrorMessageBuffer(const char *buffer) { ScopedReport::MaybeAppendToErrorMessage(buffer); } static StackTrace GetStackTraceFromId(u32 id) { CHECK(id); StackTrace res = StackDepotGet(id); CHECK(res.trace); return res; } // A RAII object that holds a copy of the current thread stack ring buffer. // The actual stack buffer may change while we are iterating over it (for // example, Printf may call syslog() which can itself be built with hwasan). class SavedStackAllocations { public: SavedStackAllocations(StackAllocationsRingBuffer *rb) { uptr size = rb->size() * sizeof(uptr); void *storage = MmapAlignedOrDieOnFatalError(size, size * 2, "saved stack allocations"); new (&rb_) StackAllocationsRingBuffer(*rb, storage); } ~SavedStackAllocations() { StackAllocationsRingBuffer *rb = get(); UnmapOrDie(rb->StartOfStorage(), rb->size() * sizeof(uptr)); } StackAllocationsRingBuffer *get() { return (StackAllocationsRingBuffer *)&rb_; } private: uptr rb_; }; class Decorator: public __sanitizer::SanitizerCommonDecorator { public: Decorator() : SanitizerCommonDecorator() { } const char *Access() { return Blue(); } const char *Allocation() const { return Magenta(); } const char *Origin() const { return Magenta(); } const char *Name() const { return Green(); } const char *Location() { return Green(); } const char *Thread() { return Green(); } }; // Returns the index of the rb element that matches tagged_addr (plus one), // or zero if found nothing. uptr FindHeapAllocation(HeapAllocationsRingBuffer *rb, uptr tagged_addr, HeapAllocationRecord *har) { if (!rb) return 0; for (uptr i = 0, size = rb->size(); i < size; i++) { auto h = (*rb)[i]; if (h.tagged_addr <= tagged_addr && h.tagged_addr + h.requested_size > tagged_addr) { *har = h; return i + 1; } } return 0; } static void PrintStackAllocations(StackAllocationsRingBuffer *sa, tag_t addr_tag, uptr untagged_addr) { uptr frames = Min((uptr)flags()->stack_history_size, sa->size()); bool found_local = false; for (uptr i = 0; i < frames; i++) { const uptr *record_addr = &(*sa)[i]; uptr record = *record_addr; if (!record) break; tag_t base_tag = reinterpret_cast(record_addr) >> kRecordAddrBaseTagShift; uptr fp = (record >> kRecordFPShift) << kRecordFPLShift; uptr pc_mask = (1ULL << kRecordFPShift) - 1; uptr pc = record & pc_mask; FrameInfo frame; if (Symbolizer::GetOrInit()->SymbolizeFrame(pc, &frame)) { for (LocalInfo &local : frame.locals) { if (!local.has_frame_offset || !local.has_size || !local.has_tag_offset) continue; tag_t obj_tag = base_tag ^ local.tag_offset; if (obj_tag != addr_tag) continue; // Calculate the offset from the object address to the faulting // address. Because we only store bits 4-19 of FP (bits 0-3 are // guaranteed to be zero), the calculation is performed mod 2^20 and may // harmlessly underflow if the address mod 2^20 is below the object // address. uptr obj_offset = (untagged_addr - fp - local.frame_offset) & (kRecordFPModulus - 1); if (obj_offset >= local.size) continue; if (!found_local) { Printf("Potentially referenced stack objects:\n"); found_local = true; } Printf(" %s in %s %s:%d\n", local.name, local.function_name, local.decl_file, local.decl_line); } frame.Clear(); } } if (found_local) return; // We didn't find any locals. Most likely we don't have symbols, so dump // the information that we have for offline analysis. InternalScopedString frame_desc(GetPageSizeCached() * 2); Printf("Previously allocated frames:\n"); for (uptr i = 0; i < frames; i++) { const uptr *record_addr = &(*sa)[i]; uptr record = *record_addr; if (!record) break; uptr pc_mask = (1ULL << 48) - 1; uptr pc = record & pc_mask; frame_desc.append(" record_addr:0x%zx record:0x%zx", reinterpret_cast(record_addr), record); if (SymbolizedStack *frame = Symbolizer::GetOrInit()->SymbolizePC(pc)) { RenderFrame(&frame_desc, " %F %L\n", 0, frame->info, common_flags()->symbolize_vs_style, common_flags()->strip_path_prefix); frame->ClearAll(); } Printf("%s", frame_desc.data()); frame_desc.clear(); } } // Returns true if tag == *tag_ptr, reading tags from short granules if // necessary. This may return a false positive if tags 1-15 are used as a // regular tag rather than a short granule marker. static bool TagsEqual(tag_t tag, tag_t *tag_ptr) { if (tag == *tag_ptr) return true; if (*tag_ptr == 0 || *tag_ptr > kShadowAlignment - 1) return false; uptr mem = ShadowToMem(reinterpret_cast(tag_ptr)); tag_t inline_tag = *reinterpret_cast(mem + kShadowAlignment - 1); return tag == inline_tag; } void PrintAddressDescription( uptr tagged_addr, uptr access_size, StackAllocationsRingBuffer *current_stack_allocations) { Decorator d; int num_descriptions_printed = 0; uptr untagged_addr = UntagAddr(tagged_addr); // Print some very basic information about the address, if it's a heap. HwasanChunkView chunk = FindHeapChunkByAddress(untagged_addr); if (uptr beg = chunk.Beg()) { uptr size = chunk.ActualSize(); Printf("%s[%p,%p) is a %s %s heap chunk; " "size: %zd offset: %zd\n%s", d.Location(), beg, beg + size, chunk.FromSmallHeap() ? "small" : "large", chunk.IsAllocated() ? "allocated" : "unallocated", size, untagged_addr - beg, d.Default()); } // Check if this looks like a heap buffer overflow by scanning // the shadow left and right and looking for the first adjacent // object with a different memory tag. If that tag matches addr_tag, // check the allocator if it has a live chunk there. tag_t addr_tag = GetTagFromPointer(tagged_addr); tag_t *tag_ptr = reinterpret_cast(MemToShadow(untagged_addr)); tag_t *candidate = nullptr, *left = tag_ptr, *right = tag_ptr; for (int i = 0; i < 1000; i++) { if (TagsEqual(addr_tag, left)) { candidate = left; break; } --left; if (TagsEqual(addr_tag, right)) { candidate = right; break; } ++right; } if (candidate) { uptr mem = ShadowToMem(reinterpret_cast(candidate)); HwasanChunkView chunk = FindHeapChunkByAddress(mem); if (chunk.IsAllocated()) { Printf("%s", d.Location()); Printf("%p is located %zd bytes to the %s of %zd-byte region [%p,%p)\n", untagged_addr, candidate == left ? untagged_addr - chunk.End() : chunk.Beg() - untagged_addr, candidate == left ? "right" : "left", chunk.UsedSize(), chunk.Beg(), chunk.End()); Printf("%s", d.Allocation()); Printf("allocated here:\n"); Printf("%s", d.Default()); GetStackTraceFromId(chunk.GetAllocStackId()).Print(); num_descriptions_printed++; } else { // Check whether the address points into a loaded library. If so, this is // most likely a global variable. const char *module_name; uptr module_address; Symbolizer *sym = Symbolizer::GetOrInit(); if (sym->GetModuleNameAndOffsetForPC(mem, &module_name, &module_address)) { DataInfo info; if (sym->SymbolizeData(mem, &info) && info.start) { Printf( "%p is located %zd bytes to the %s of %zd-byte global variable " "%s [%p,%p) in %s\n", untagged_addr, candidate == left ? untagged_addr - (info.start + info.size) : info.start - untagged_addr, candidate == left ? "right" : "left", info.size, info.name, info.start, info.start + info.size, module_name); } else { Printf("%p is located to the %s of a global variable in (%s+0x%x)\n", untagged_addr, candidate == left ? "right" : "left", module_name, module_address); } num_descriptions_printed++; } } } hwasanThreadList().VisitAllLiveThreads([&](Thread *t) { // Scan all threads' ring buffers to find if it's a heap-use-after-free. HeapAllocationRecord har; if (uptr D = FindHeapAllocation(t->heap_allocations(), tagged_addr, &har)) { Printf("%s", d.Location()); Printf("%p is located %zd bytes inside of %zd-byte region [%p,%p)\n", untagged_addr, untagged_addr - UntagAddr(har.tagged_addr), har.requested_size, UntagAddr(har.tagged_addr), UntagAddr(har.tagged_addr) + har.requested_size); Printf("%s", d.Allocation()); Printf("freed by thread T%zd here:\n", t->unique_id()); Printf("%s", d.Default()); GetStackTraceFromId(har.free_context_id).Print(); Printf("%s", d.Allocation()); Printf("previously allocated here:\n", t); Printf("%s", d.Default()); GetStackTraceFromId(har.alloc_context_id).Print(); // Print a developer note: the index of this heap object // in the thread's deallocation ring buffer. Printf("hwasan_dev_note_heap_rb_distance: %zd %zd\n", D, flags()->heap_history_size); t->Announce(); num_descriptions_printed++; } // Very basic check for stack memory. if (t->AddrIsInStack(untagged_addr)) { Printf("%s", d.Location()); Printf("Address %p is located in stack of thread T%zd\n", untagged_addr, t->unique_id()); Printf("%s", d.Default()); t->Announce(); auto *sa = (t == GetCurrentThread() && current_stack_allocations) ? current_stack_allocations : t->stack_allocations(); PrintStackAllocations(sa, addr_tag, untagged_addr); num_descriptions_printed++; } }); // Print the remaining threads, as an extra information, 1 line per thread. hwasanThreadList().VisitAllLiveThreads([&](Thread *t) { t->Announce(); }); if (!num_descriptions_printed) // We exhausted our possibilities. Bail out. Printf("HWAddressSanitizer can not describe address in more detail.\n"); } void ReportStats() {} static void PrintTagInfoAroundAddr(tag_t *tag_ptr, uptr num_rows, void (*print_tag)(InternalScopedString &s, tag_t *tag)) { const uptr row_len = 16; // better be power of two. tag_t *center_row_beg = reinterpret_cast( RoundDownTo(reinterpret_cast(tag_ptr), row_len)); tag_t *beg_row = center_row_beg - row_len * (num_rows / 2); tag_t *end_row = center_row_beg + row_len * ((num_rows + 1) / 2); InternalScopedString s(GetPageSizeCached() * 8); for (tag_t *row = beg_row; row < end_row; row += row_len) { s.append("%s", row == center_row_beg ? "=>" : " "); s.append("%p:", row); for (uptr i = 0; i < row_len; i++) { s.append("%s", row + i == tag_ptr ? "[" : " "); print_tag(s, &row[i]); s.append("%s", row + i == tag_ptr ? "]" : " "); } s.append("\n"); } Printf("%s", s.data()); } static void PrintTagsAroundAddr(tag_t *tag_ptr) { Printf( "Memory tags around the buggy address (one tag corresponds to %zd " "bytes):\n", kShadowAlignment); PrintTagInfoAroundAddr(tag_ptr, 17, [](InternalScopedString &s, tag_t *tag) { s.append("%02x", *tag); }); Printf( "Tags for short granules around the buggy address (one tag corresponds " "to %zd bytes):\n", kShadowAlignment); PrintTagInfoAroundAddr(tag_ptr, 3, [](InternalScopedString &s, tag_t *tag) { if (*tag >= 1 && *tag <= kShadowAlignment) { uptr granule_addr = ShadowToMem(reinterpret_cast(tag)); s.append("%02x", *reinterpret_cast(granule_addr + kShadowAlignment - 1)); } else { s.append(".."); } }); Printf( "See " "https://clang.llvm.org/docs/" "HardwareAssistedAddressSanitizerDesign.html#short-granules for a " "description of short granule tags\n"); } void ReportInvalidFree(StackTrace *stack, uptr tagged_addr) { ScopedReport R(flags()->halt_on_error); uptr untagged_addr = UntagAddr(tagged_addr); tag_t ptr_tag = GetTagFromPointer(tagged_addr); tag_t *tag_ptr = reinterpret_cast(MemToShadow(untagged_addr)); tag_t mem_tag = *tag_ptr; Decorator d; Printf("%s", d.Error()); uptr pc = stack->size ? stack->trace[0] : 0; const char *bug_type = "invalid-free"; Report("ERROR: %s: %s on address %p at pc %p\n", SanitizerToolName, bug_type, untagged_addr, pc); Printf("%s", d.Access()); Printf("tags: %02x/%02x (ptr/mem)\n", ptr_tag, mem_tag); Printf("%s", d.Default()); stack->Print(); PrintAddressDescription(tagged_addr, 0, nullptr); PrintTagsAroundAddr(tag_ptr); ReportErrorSummary(bug_type, stack); } void ReportTailOverwritten(StackTrace *stack, uptr tagged_addr, uptr orig_size, const u8 *expected) { uptr tail_size = kShadowAlignment - (orig_size % kShadowAlignment); ScopedReport R(flags()->halt_on_error); Decorator d; uptr untagged_addr = UntagAddr(tagged_addr); Printf("%s", d.Error()); const char *bug_type = "allocation-tail-overwritten"; Report("ERROR: %s: %s; heap object [%p,%p) of size %zd\n", SanitizerToolName, bug_type, untagged_addr, untagged_addr + orig_size, orig_size); Printf("\n%s", d.Default()); stack->Print(); HwasanChunkView chunk = FindHeapChunkByAddress(untagged_addr); if (chunk.Beg()) { Printf("%s", d.Allocation()); Printf("allocated here:\n"); Printf("%s", d.Default()); GetStackTraceFromId(chunk.GetAllocStackId()).Print(); } InternalScopedString s(GetPageSizeCached() * 8); CHECK_GT(tail_size, 0U); CHECK_LT(tail_size, kShadowAlignment); u8 *tail = reinterpret_cast(untagged_addr + orig_size); s.append("Tail contains: "); for (uptr i = 0; i < kShadowAlignment - tail_size; i++) s.append(".. "); for (uptr i = 0; i < tail_size; i++) s.append("%02x ", tail[i]); s.append("\n"); s.append("Expected: "); for (uptr i = 0; i < kShadowAlignment - tail_size; i++) s.append(".. "); for (uptr i = 0; i < tail_size; i++) s.append("%02x ", expected[i]); s.append("\n"); s.append(" "); for (uptr i = 0; i < kShadowAlignment - tail_size; i++) s.append(" "); for (uptr i = 0; i < tail_size; i++) s.append("%s ", expected[i] != tail[i] ? "^^" : " "); s.append("\nThis error occurs when a buffer overflow overwrites memory\n" "to the right of a heap object, but within the %zd-byte granule, e.g.\n" " char *x = new char[20];\n" " x[25] = 42;\n" "%s does not detect such bugs in uninstrumented code at the time of write," "\nbut can detect them at the time of free/delete.\n" "To disable this feature set HWASAN_OPTIONS=free_checks_tail_magic=0\n", kShadowAlignment, SanitizerToolName); Printf("%s", s.data()); GetCurrentThread()->Announce(); tag_t *tag_ptr = reinterpret_cast(MemToShadow(untagged_addr)); PrintTagsAroundAddr(tag_ptr); ReportErrorSummary(bug_type, stack); } void ReportTagMismatch(StackTrace *stack, uptr tagged_addr, uptr access_size, bool is_store, bool fatal, uptr *registers_frame) { ScopedReport R(fatal); SavedStackAllocations current_stack_allocations( GetCurrentThread()->stack_allocations()); Decorator d; Printf("%s", d.Error()); uptr untagged_addr = UntagAddr(tagged_addr); // TODO: when possible, try to print heap-use-after-free, etc. const char *bug_type = "tag-mismatch"; uptr pc = stack->size ? stack->trace[0] : 0; Report("ERROR: %s: %s on address %p at pc %p\n", SanitizerToolName, bug_type, untagged_addr, pc); Thread *t = GetCurrentThread(); sptr offset = __hwasan_test_shadow(reinterpret_cast(tagged_addr), access_size); CHECK(offset >= 0 && offset < static_cast(access_size)); tag_t ptr_tag = GetTagFromPointer(tagged_addr); tag_t *tag_ptr = reinterpret_cast(MemToShadow(untagged_addr + offset)); tag_t mem_tag = *tag_ptr; Printf("%s", d.Access()); Printf("%s of size %zu at %p tags: %02x/%02x (ptr/mem) in thread T%zd\n", is_store ? "WRITE" : "READ", access_size, untagged_addr, ptr_tag, mem_tag, t->unique_id()); if (offset != 0) Printf("Invalid access starting at offset [%zu, %zu)\n", offset, Min(access_size, static_cast(offset) + (1 << kShadowScale))); Printf("%s", d.Default()); stack->Print(); PrintAddressDescription(tagged_addr, access_size, current_stack_allocations.get()); t->Announce(); PrintTagsAroundAddr(tag_ptr); if (registers_frame) ReportRegisters(registers_frame, pc); ReportErrorSummary(bug_type, stack); } // See the frame breakdown defined in __hwasan_tag_mismatch (from // hwasan_tag_mismatch_aarch64.S). void ReportRegisters(uptr *frame, uptr pc) { Printf("Registers where the failure occurred (pc %p):\n", pc); // We explicitly print a single line (4 registers/line) each iteration to // reduce the amount of logcat error messages printed. Each Printf() will // result in a new logcat line, irrespective of whether a newline is present, // and so we wish to reduce the number of Printf() calls we have to make. Printf(" x0 %016llx x1 %016llx x2 %016llx x3 %016llx\n", frame[0], frame[1], frame[2], frame[3]); Printf(" x4 %016llx x5 %016llx x6 %016llx x7 %016llx\n", frame[4], frame[5], frame[6], frame[7]); Printf(" x8 %016llx x9 %016llx x10 %016llx x11 %016llx\n", frame[8], frame[9], frame[10], frame[11]); Printf(" x12 %016llx x13 %016llx x14 %016llx x15 %016llx\n", frame[12], frame[13], frame[14], frame[15]); Printf(" x16 %016llx x17 %016llx x18 %016llx x19 %016llx\n", frame[16], frame[17], frame[18], frame[19]); Printf(" x20 %016llx x21 %016llx x22 %016llx x23 %016llx\n", frame[20], frame[21], frame[22], frame[23]); Printf(" x24 %016llx x25 %016llx x26 %016llx x27 %016llx\n", frame[24], frame[25], frame[26], frame[27]); Printf(" x28 %016llx x29 %016llx x30 %016llx\n", frame[28], frame[29], frame[30]); } } // namespace __hwasan