diff --git a/compiler-rt/lib/hwasan/hwasan_report.cpp b/compiler-rt/lib/hwasan/hwasan_report.cpp --- a/compiler-rt/lib/hwasan/hwasan_report.cpp +++ b/compiler-rt/lib/hwasan/hwasan_report.cpp @@ -296,6 +296,75 @@ return 0; } +static void ShowCandidate(uptr untagged_addr, tag_t *candidate, tag_t *left, + tag_t *right) { + Decorator d; + uptr mem = ShadowToMem(reinterpret_cast(candidate)); + HwasanChunkView chunk = FindHeapChunkByAddress(mem); + if (chunk.IsAllocated()) { + uptr offset; + const char *whence; + if (untagged_addr < chunk.End() && untagged_addr >= chunk.Beg()) { + offset = untagged_addr - chunk.Beg(); + whence = "inside"; + } else if (candidate == left) { + offset = untagged_addr - chunk.End(); + whence = "to the right of"; + } else { + offset = chunk.Beg() - untagged_addr; + whence = "to the left of"; + } + Printf("%s", d.Error()); + Printf("\nCause: heap-buffer-overflow\n"); + Printf("%s", d.Default()); + Printf("%s", d.Location()); + Printf("%p is located %zd bytes %s %zd-byte region [%p,%p)\n", + untagged_addr, offset, whence, chunk.UsedSize(), chunk.Beg(), + chunk.End()); + Printf("%s", d.Allocation()); + Printf("allocated here:\n"); + Printf("%s", d.Default()); + GetStackTraceFromId(chunk.GetAllocStackId()).Print(); + return; + } + // 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)) { + Printf("%s", d.Error()); + Printf("\nCause: global-overflow\n"); + Printf("%s", d.Default()); + DataInfo info; + Printf("%s", d.Location()); + 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 { + uptr size = GetGlobalSizeFromDescriptor(mem); + if (size == 0) + // We couldn't find the size of the global from the descriptors. + 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); + else + Printf( + "%p is located to the %s of a %zd-byte global variable in " + "(%s+0x%x)\n", + untagged_addr, candidate == left ? "right" : "left", size, + module_name, module_address); + } + Printf("%s", d.Default()); + } +} + void PrintAddressDescription( uptr tagged_addr, uptr access_size, StackAllocationsRingBuffer *current_stack_allocations) { @@ -324,7 +393,8 @@ 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++) { + uptr candidate_distance = 0; + for (; candidate_distance < 1000; candidate_distance++) { if (TagsEqual(addr_tag, left)) { candidate = left; break; @@ -337,68 +407,32 @@ ++right; } - if (candidate) { - uptr mem = ShadowToMem(reinterpret_cast(candidate)); - HwasanChunkView chunk = FindHeapChunkByAddress(mem); - if (chunk.IsAllocated()) { - uptr offset; - const char *whence; - if (untagged_addr < chunk.End() && untagged_addr >= chunk.Beg()) { - offset = untagged_addr - chunk.Beg(); - whence = "inside"; - } else if (candidate == left) { - offset = untagged_addr - chunk.End(); - whence = "to the right of"; - } else { - offset = chunk.Beg() - untagged_addr; - whence = "to the left of"; - } + constexpr auto kCloseCandidateDistance = 1; + + if (candidate && candidate_distance <= kCloseCandidateDistance) { + ShowCandidate(untagged_addr, candidate, left, right); + num_descriptions_printed++; + } + + hwasanThreadList().VisitAllLiveThreads([&](Thread *t) { + if (t->AddrIsInStack(untagged_addr)) { + // TODO(fmayer): figure out how to distinguish use-after-return and + // stack-buffer-overflow. + Printf("%s", d.Error()); + Printf("\nCause: stack tag-mismatch\n"); Printf("%s", d.Location()); - Printf("%p is located %zd bytes %s %zd-byte region [%p,%p)\n", - untagged_addr, offset, whence, chunk.UsedSize(), chunk.Beg(), - chunk.End()); - Printf("%s", d.Allocation()); - Printf("allocated here:\n"); + Printf("Address %p is located in stack of thread T%zd\n", untagged_addr, + t->unique_id()); Printf("%s", d.Default()); - GetStackTraceFromId(chunk.GetAllocStackId()).Print(); + t->Announce(); + + auto *sa = (t == GetCurrentThread() && current_stack_allocations) + ? current_stack_allocations + : t->stack_allocations(); + PrintStackAllocations(sa, addr_tag, untagged_addr); 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 { - uptr size = GetGlobalSizeFromDescriptor(mem); - if (size == 0) - // We couldn't find the size of the global from the descriptors. - 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); - else - Printf( - "%p is located to the %s of a %zd-byte global variable in " - "(%s+0x%x)\n", - untagged_addr, candidate == left ? "right" : "left", size, - 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. @@ -407,6 +441,8 @@ if (FindHeapAllocation(t->heap_allocations(), tagged_addr, &har, &ring_index, &num_matching_addrs, &num_matching_addrs_4b)) { + Printf("%s", d.Error()); + Printf("\nCause: use-after-free\n"); 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), @@ -433,29 +469,25 @@ 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++; - } }); + if (candidate && num_descriptions_printed == 0) { + ShowCandidate(untagged_addr, candidate, left, right); + 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"); + if (num_descriptions_printed > 1) { + Printf( + "There are %d potential causes, printed above in order " + "of likeliness.", + num_descriptions_printed); + } } void ReportStats() {} diff --git a/compiler-rt/test/hwasan/TestCases/global.c b/compiler-rt/test/hwasan/TestCases/global.c --- a/compiler-rt/test/hwasan/TestCases/global.c +++ b/compiler-rt/test/hwasan/TestCases/global.c @@ -10,6 +10,7 @@ int x = 1; int main(int argc, char **argv) { + // CHECK: Cause: global-overflow // RSYM: is located 0 bytes to the right of 4-byte global variable x {{.*}} in {{.*}}global.c.tmp // RNOSYM: is located to the right of a 4-byte global variable in ({{.*}}global.c.tmp+{{.*}}) // LSYM: is located 4 bytes to the left of 4-byte global variable x {{.*}} in {{.*}}global.c.tmp diff --git a/compiler-rt/test/hwasan/TestCases/heap-buffer-overflow.c b/compiler-rt/test/hwasan/TestCases/heap-buffer-overflow.c --- a/compiler-rt/test/hwasan/TestCases/heap-buffer-overflow.c +++ b/compiler-rt/test/hwasan/TestCases/heap-buffer-overflow.c @@ -31,6 +31,7 @@ if (size == 1000000) { fprintf(stderr, "is a large allocated heap chunk; size: 1003520 offset: %d\n", offset); + fprintf(stderr, "Cause: heap-buffer-overflow\n"); fprintf(stderr, "is located %s of 1000000-byte region\n", offset == -30 ? "30 bytes to the left" : "0 bytes to the right"); return -1; @@ -38,26 +39,33 @@ #endif // CHECK40: allocated heap chunk; size: 32 offset: 8 +// CHECK40: Cause: heap-buffer-overflow // CHECK40: is located 10 bytes to the right of 30-byte region // // CHECK80: allocated heap chunk; size: 32 offset: 16 +// CHECK80: Cause: heap-buffer-overflow // CHECK80: is located 50 bytes to the right of 30-byte region // +// CHECKm30: Cause: heap-buffer-overflow // CHECKm30: is located 30 bytes to the left of 30-byte region // // CHECKMm30: is a large allocated heap chunk; size: 1003520 offset: -30 +// CHECKMm30: Cause: heap-buffer-overflow // CHECKMm30: is located 30 bytes to the left of 1000000-byte region // // CHECKM: is a large allocated heap chunk; size: 1003520 offset: 1000000 +// CHECKM: Cause: heap-buffer-overflow // CHECKM: is located 0 bytes to the right of 1000000-byte region // // CHECK31: tags: [[TAG:..]]/0e (ptr/mem) +// CHECK31: Cause: heap-buffer-overflow // CHECK31: is located 1 bytes to the right of 30-byte region // CHECK31: Memory tags around the buggy address // CHECK31: [0e] // CHECK31: Tags for short granules around the buggy address // CHECK31: {{\[}}[[TAG]]] // +// CHECK20: Cause: heap-buffer-overflow // CHECK20: is located 10 bytes to the right of 20-byte region [0x{{.*}}0,0x{{.*}}4) free(x); } diff --git a/compiler-rt/test/hwasan/TestCases/stack-oob.c b/compiler-rt/test/hwasan/TestCases/stack-oob.c --- a/compiler-rt/test/hwasan/TestCases/stack-oob.c +++ b/compiler-rt/test/hwasan/TestCases/stack-oob.c @@ -27,6 +27,7 @@ // CHECK: READ of size 1 at // CHECK: #0 {{.*}} in f{{.*}}stack-oob.c:[[@LINE-6]] + // CHECK: Cause: stack tag-mismatch // CHECK: is located in stack of threa // CHECK: SUMMARY: HWAddressSanitizer: tag-mismatch {{.*}} in f diff --git a/compiler-rt/test/hwasan/TestCases/stack-uar.c b/compiler-rt/test/hwasan/TestCases/stack-uar.c --- a/compiler-rt/test/hwasan/TestCases/stack-uar.c +++ b/compiler-rt/test/hwasan/TestCases/stack-uar.c @@ -30,9 +30,10 @@ return *p; // CHECK: READ of size 1 at // CHECK: #0 {{.*}} in main{{.*}}stack-uar.c:[[@LINE-2]] + // CHECK: Cause: stack tag-mismatch // CHECK: is located in stack of thread // CHECK: Potentially referenced stack objects: - // CHECK-NEXT: zzz in buggy {{.*}}stack-uar.c:[[@LINE-19]] + // CHECK-NEXT: zzz in buggy {{.*}}stack-uar.c:[[@LINE-20]] // CHECK-NEXT: Memory tags around the buggy address // NOSYM: Previously allocated frames: diff --git a/compiler-rt/test/hwasan/TestCases/thread-uaf.c b/compiler-rt/test/hwasan/TestCases/thread-uaf.c --- a/compiler-rt/test/hwasan/TestCases/thread-uaf.c +++ b/compiler-rt/test/hwasan/TestCases/thread-uaf.c @@ -30,6 +30,7 @@ // CHECK: ERROR: HWAddressSanitizer: tag-mismatch on address // CHECK: WRITE of size 1 {{.*}} in thread T3 // CHECK: thread-uaf.c:[[@LINE-3]] + // CHECK: Cause: use-after-free // CHECK: freed by thread T2 here // CHECK: in Deallocate // CHECK: previously allocated here: diff --git a/compiler-rt/test/hwasan/TestCases/use-after-free-and-overflow.c b/compiler-rt/test/hwasan/TestCases/use-after-free-and-overflow.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-free-and-overflow.c @@ -0,0 +1,64 @@ +// Checks that we do not print a faraway buffer overrun if we find a +// use-after-free. +// RUN: %clang_hwasan -O0 %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK +// REQUIRES: stable-runtime + +#include +#include +#include + +#define ALLOC_ATTEMPTS 256 + +char *Untag(void *x) { + return (char *)__hwasan_tag_pointer(x, 0); +} + +void *FindMatch(void *ptrs[ALLOC_ATTEMPTS], void *value) { + for (int i = 0; i < ALLOC_ATTEMPTS; ++i) { + if (!ptrs[i]) { + return NULL; + } + int distance = Untag(value) - Untag(ptrs[i]); + // Leave at least one granule of gap to the allocation. + if (abs(distance) < 1000 && abs(distance) > 32) { + return ptrs[i]; + } + } + return NULL; +} + +int main(int argc, char **argv) { + __hwasan_enable_allocator_tagging(); + void *ptrs[ALLOC_ATTEMPTS] = {}; + // Find two allocations that are close enough so that they would be + // candidates as buffer overflows for each other. + void *one; + void *other; + for (int i = 0; i < ALLOC_ATTEMPTS; ++i) { + one = malloc(16); + other = FindMatch(ptrs, one); + ptrs[i] = one; + if (other) { + break; + } + } + if (!other) { + fprintf(stderr, "Could not find closeby allocations.\n"); + abort(); + } + __hwasan_tag_memory(Untag(one), 3, 16); + __hwasan_tag_memory(Untag(other), 3, 16); + // Tag potential adjaceant allocations with a mismatching tag, otherwise this + // test would flake. + __hwasan_tag_memory(Untag(one) + 16, 4, 16); + __hwasan_tag_memory(Untag(one) - 16, 4, 16); + void *retagged_one = __hwasan_tag_pointer(one, 3); + free(retagged_one); + volatile char *ptr = (char *)retagged_one; + *ptr = 1; +} + +// CHECK-NOT: Cause: heap-buffer-overflow +// CHECK: Cause: use-after-free +// CHECK-NOT: Cause: heap-buffer-overflow diff --git a/compiler-rt/test/hwasan/TestCases/use-after-free.c b/compiler-rt/test/hwasan/TestCases/use-after-free.c --- a/compiler-rt/test/hwasan/TestCases/use-after-free.c +++ b/compiler-rt/test/hwasan/TestCases/use-after-free.c @@ -24,15 +24,16 @@ // CHECK: #{{[0-9]}} {{.*}} in main {{.*}}use-after-free.c:[[@LINE-2]] // Offset is 5 or 11 depending on left/right alignment. // CHECK: is a small unallocated heap chunk; size: 32 offset: {{5|11}} + // CHECK: Cause: use-after-free // CHECK: is located 5 bytes inside of 10-byte region // // CHECK: freed by thread {{.*}} here: // CHECK: #0 {{.*}} in {{.*}}free{{.*}} {{.*}}hwasan_allocation_functions.cpp - // CHECK: #1 {{.*}} in main {{.*}}use-after-free.c:[[@LINE-14]] + // CHECK: #1 {{.*}} in main {{.*}}use-after-free.c:[[@LINE-15]] // CHECK: previously allocated here: // CHECK: #0 {{.*}} in {{.*}}malloc{{.*}} {{.*}}hwasan_allocation_functions.cpp - // CHECK: #1 {{.*}} in main {{.*}}use-after-free.c:[[@LINE-19]] + // CHECK: #1 {{.*}} in main {{.*}}use-after-free.c:[[@LINE-20]] // CHECK: Memory tags around the buggy address (one tag corresponds to 16 bytes): // CHECK: =>{{.*}}[[MEM_TAG]] // CHECK: SUMMARY: HWAddressSanitizer: tag-mismatch