diff options
Diffstat (limited to 'lib/hwasan/hwasan_report.cpp')
| -rw-r--r-- | lib/hwasan/hwasan_report.cpp | 547 | 
1 files changed, 547 insertions, 0 deletions
diff --git a/lib/hwasan/hwasan_report.cpp b/lib/hwasan/hwasan_report.cpp new file mode 100644 index 000000000000..346889797888 --- /dev/null +++ b/lib/hwasan/hwasan_report.cpp @@ -0,0 +1,547 @@ +//===-- 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<char> error_message_; +  bool fatal; + +  static InternalMmapVector<char> *error_message_ptr_; +  static BlockingMutex error_message_lock_; +}; + +InternalMmapVector<char> *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<uptr>(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<uptr>(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<uptr>(tag_ptr)); +  tag_t inline_tag = *reinterpret_cast<tag_t *>(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<tag_t*>(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<uptr>(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++; +    } +  } + +  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<tag_t *>( +      RoundDownTo(reinterpret_cast<uptr>(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 ? "=>" : "  "); +    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("%s\n", row == center_row_beg ? "<=" : "  "); +  } +  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<uptr>(tag)); +      s.append("%02x", +               *reinterpret_cast<u8 *>(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<tag_t*>(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 = "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<u8*>(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<tag_t*>(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<void *>(tagged_addr), access_size); +  CHECK(offset >= 0 && offset < static_cast<sptr>(access_size)); +  tag_t ptr_tag = GetTagFromPointer(tagged_addr); +  tag_t *tag_ptr = +      reinterpret_cast<tag_t *>(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<uptr>(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  | 
