Index: lib/hwasan/hwasan_allocator.cc =================================================================== --- lib/hwasan/hwasan_allocator.cc +++ lib/hwasan/hwasan_allocator.cc @@ -42,6 +42,8 @@ static RightAlignMode right_align_mode = kRightAlignNever; static bool right_align_8 = false; +// Initialized in HwasanAllocatorInit, an never changed. +static ALIGNED(16) u8 tail_magic[kShadowAlignment]; bool HwasanChunkView::IsAllocated() const { return metadata_ && metadata_->alloc_context_id && metadata_->requested_size; @@ -111,7 +113,8 @@ flags()->malloc_align_right); Die(); } - + for (uptr i = 0; i < kShadowAlignment; i++) + tail_magic[i] = GetCurrentThread()->GenerateRandomTag(); } void AllocatorSwallowThreadLocalCache(AllocatorCache *cache) { @@ -164,6 +167,9 @@ uptr fill_size = Min(size, (uptr)flags()->max_malloc_fill_size); internal_memset(allocated, flags()->malloc_fill_byte, fill_size); } + if (!right_align_mode) + internal_memcpy(reinterpret_cast(allocated) + orig_size, tail_magic, + size - orig_size); void *user_ptr = allocated; if (flags()->tag_in_malloc && @@ -208,6 +214,19 @@ uptr orig_size = meta->requested_size; u32 free_context_id = StackDepotPut(*stack); u32 alloc_context_id = meta->alloc_context_id; + + // Check tail magic. + uptr tagged_size = TaggedSize(orig_size); + if (flags()->free_checks_tail_magic && !meta->right_aligned && orig_size) { + uptr tail_size = tagged_size - orig_size; + CHECK_LT(tail_size, kShadowAlignment); + void *tail_beg = reinterpret_cast( + reinterpret_cast(aligned_ptr) + orig_size); + if (tail_size && internal_memcmp(tail_beg, tail_magic, tail_size)) + ReportTailOverwritten(stack, reinterpret_cast(tagged_ptr), + orig_size, tail_size, tail_magic); + } + meta->requested_size = 0; meta->alloc_context_id = 0; // This memory will not be reused by anyone else, so we are free to keep it Index: lib/hwasan/hwasan_flags.inc =================================================================== --- lib/hwasan/hwasan_flags.inc +++ lib/hwasan/hwasan_flags.inc @@ -64,6 +64,10 @@ "8: allocations are sometimes aligned right to 8-byte boundary; " "9: allocations are always aligned right to 8-byte boundary." ) +HWASAN_FLAG(bool, free_checks_tail_magic, 1, + "If set, free() will check the magic values " + "to the right of the allocated object " + "if the allocation size is not a divident of the granule size") HWASAN_FLAG( int, max_free_fill_size, 0, "HWASan allocator flag. max_free_fill_size is the maximal amount of " Index: lib/hwasan/hwasan_report.h =================================================================== --- lib/hwasan/hwasan_report.h +++ lib/hwasan/hwasan_report.h @@ -25,6 +25,8 @@ void ReportTagMismatch(StackTrace *stack, uptr addr, uptr access_size, bool is_store, bool fatal); void ReportInvalidFree(StackTrace *stack, uptr addr); +void ReportTailOverwritten(StackTrace *stack, uptr addr, uptr orig_size, + uptr tail_size, const u8 *expected); void ReportAtExitStatistics(); Index: lib/hwasan/hwasan_report.cc =================================================================== --- lib/hwasan/hwasan_report.cc +++ lib/hwasan/hwasan_report.cc @@ -323,6 +323,66 @@ 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); Index: test/hwasan/TestCases/tail-magic.c =================================================================== --- /dev/null +++ test/hwasan/TestCases/tail-magic.c @@ -0,0 +1,28 @@ +// Tests free_checks_tail_magic=1. +// RUN: %clang_hwasan %s -o %t +// RUN: %env_hwasan_opts=free_checks_tail_magic=0 %run %t +// RUN: %env_hwasan_opts=free_checks_tail_magic=1 not %run %t 2>&1 | FileCheck %s +// RUN: not %run %t 2>&1 | FileCheck %s + +// REQUIRES: stable-runtime + +#include +#include +#include + +static volatile void *sink; + +int main(int argc, char **argv) { + __hwasan_enable_allocator_tagging(); + + char *p = (char*)malloc(20); + sink = p; + p[20] = 0x42; + p[24] = 0x66; + free(p); +// CHECK: ERROR: HWAddressSanitizer: alocation-tail-overwritten; heap object [{{.*}}) of size 20 +// CHECK: in main {{.*}}tail-magic.c:[[@LINE-2]] +// CHECK: allocated here: +// CHECK: in main {{.*}}tail-magic.c:[[@LINE-8]] +// CHECK: Tail contains: .. .. .. .. 42 {{.. .. ..}} 66 +}