//===-- hwasan_report.cc --------------------------------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file is a part of HWAddressSanitizer. // // Error reporting. //===----------------------------------------------------------------------===// #include "hwasan.h" #include "hwasan_allocator.h" #include "hwasan_mapping.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; } ~ScopedReport() { BlockingMutexLock lock(&error_message_lock_); if (fatal) { SetAbortMessage(error_message_.data()); Die(); } error_message_ptr_ = nullptr; } 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; } 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)); if (*tag_ptr != addr_tag) { // should be true usually. tag_t *left = tag_ptr, *right = tag_ptr; // scan left. for (int i = 0; i < 1000 && *left == *tag_ptr; i++, left--){} // scan right. for (int i = 0; i < 1000 && *right == *tag_ptr; i++, right++){} // Chose the object that has addr_tag and that is closer to addr. tag_t *candidate = nullptr; if (*right == addr_tag && *left == addr_tag) candidate = right - tag_ptr < tag_ptr - left ? right : left; else if (*right == addr_tag) candidate = right; else if (*left == addr_tag) candidate = left; 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 == right ? "left" : "right", 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++; } } } 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(); // Temporary report section, needs to be improved. Printf("Previously allocated frames:\n"); auto *sa = (t == GetCurrentThread() && current_stack_allocations) ? current_stack_allocations : t->stack_allocations(); uptr frames = Min((uptr)flags()->stack_history_size, sa->size()); InternalScopedString frame_desc(GetPageSizeCached() * 2); for (uptr i = 0; i < frames; i++) { uptr record = (*sa)[i]; if (!record) break; uptr sp = (record >> 48) << 4; uptr pc_mask = (1ULL << 48) - 1; uptr pc = record & pc_mask; if (SymbolizedStack *frame = Symbolizer::GetOrInit()->SymbolizePC(pc)) { frame_desc.append(" sp: 0x%zx pc: %p ", sp, pc); RenderFrame(&frame_desc, "in %f %s:%l\n", 0, frame->info, common_flags()->symbolize_vs_style, common_flags()->strip_path_prefix); frame->ClearAll(); if (auto Descr = GetStackFrameDescr(pc)) frame_desc.append(" %s\n", Descr); } Printf("%s", frame_desc.data()); frame_desc.clear(); } 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 PrintTagsAroundAddr(tag_t *tag_ptr) { Printf( "Memory tags around the buggy address (one tag corresponds to %zd " "bytes):\n", kShadowAlignment); const uptr row_len = 16; // better be power of two. const uptr num_rows = 17; 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 / 2); InternalScopedString s(GetPageSizeCached() * 8); for (tag_t *row = beg_row; row < end_row; row += row_len) { s.append("%s", row == center_row_beg ? "=>" : " "); for (uptr i = 0; i < row_len; i++) { s.append("%s", row + i == tag_ptr ? "[" : " "); s.append("%02x", row[i]); s.append("%s", row + i == tag_ptr ? "]" : " "); } s.append("%s\n", row == center_row_beg ? "<=" : " "); } Printf("%s", s.data()); } 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, uptr tail_size, const u8 *expected) { ScopedReport R(flags()->halt_on_error); Decorator d; uptr untagged_addr = UntagAddr(tagged_addr); Printf("%s", d.Error()); const char *bug_type = "alocation-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" "By default %s does not detect such bugs at the time of write,\n" "but can detect them at the time of free/delete.\n" "To disable this feature set HWASAN_OPTIONS=free_checks_tail_magic=0;\n" "To enable checking at the time of access, set " "HWASAN_OPTIONS=malloc_align_right to non-zero\n\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) { 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(); tag_t ptr_tag = GetTagFromPointer(tagged_addr); tag_t *tag_ptr = reinterpret_cast(MemToShadow(untagged_addr)); 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()); Printf("%s", d.Default()); stack->Print(); PrintAddressDescription(tagged_addr, access_size, current_stack_allocations.get()); t->Announce(); PrintTagsAroundAddr(tag_ptr); ReportErrorSummary(bug_type, stack); } } // namespace __hwasan