diff --git a/compiler-rt/lib/asan/asan_allocator.cpp b/compiler-rt/lib/asan/asan_allocator.cpp index 05ae3a430cabd..59aeec6a6ae66 100644 --- a/compiler-rt/lib/asan/asan_allocator.cpp +++ b/compiler-rt/lib/asan/asan_allocator.cpp @@ -93,6 +93,11 @@ class ChunkHeader { atomic_uint8_t chunk_state; u8 alloc_type : 2; u8 lsan_tag : 2; +#if SANITIZER_WINDOWS + // True if this was a zero-size allocation upgraded to size 1. + // Used to report the original size (0) to the user via HeapSize/RtlSizeHeap. + u8 from_zero_alloc : 1; +#endif // align < 8 -> 0 // else -> log2(min(align, 512)) - 2 @@ -610,6 +615,9 @@ struct Allocator { uptr chunk_beg = user_beg - kChunkHeaderSize; AsanChunk *m = reinterpret_cast(chunk_beg); m->alloc_type = alloc_type; +#if SANITIZER_WINDOWS + m->from_zero_alloc = upgraded_from_zero; +#endif CHECK(size); m->SetUsedSize(size); m->user_requested_alignment_log = user_requested_alignment_log; @@ -863,6 +871,23 @@ struct Allocator { return m->UsedSize(); } +#if SANITIZER_WINDOWS + // Returns true if the allocation at p was a zero-size request that was + // internally upgraded to size 1. + bool FromZeroAllocation(uptr p) { + return reinterpret_cast(p - kChunkHeaderSize)->from_zero_alloc; + } + + // Marks an existing size 1 allocation as having originally been zero-size. + // Used by SharedReAlloc which augments size 0 to 1 before calling + // asan_realloc, bypassing Allocate's own zero-size tracking. + void MarkAsZeroAllocation(uptr p) { + AsanChunk* m = reinterpret_cast(p - kChunkHeaderSize); + m->from_zero_alloc = 1; + PoisonShadow(p, ASAN_SHADOW_GRANULARITY, kAsanHeapLeftRedzoneMagic); + } +#endif + uptr AllocationSizeFast(uptr p) { return reinterpret_cast(p - kChunkHeaderSize)->UsedSize(); } @@ -1125,6 +1150,17 @@ uptr asan_malloc_usable_size(const void *ptr, uptr pc, uptr bp) { GET_STACK_TRACE_FATAL(pc, bp); ReportMallocUsableSizeNotOwned((uptr)ptr, &stack); } +#if SANITIZER_WINDOWS + // Zero-size allocations are internally upgraded to size 1 so that + // malloc(0)/new(0) return unique non-NULL pointers as required by the + // standard. Windows heap APIs (HeapSize, RtlSizeHeap, _msize) should still + // report the originally requested size (0). + if (usable_size > 0 && + instance.FromZeroAllocation(reinterpret_cast(ptr))) { + DCHECK(usable_size == 1); + return 0; + } +#endif return usable_size; } @@ -1221,9 +1257,24 @@ void asan_delete_array_sized_aligned(void *ptr, uptr size, uptr alignment, asan_delete_sized_aligned(ptr, size, alignment, stack, /*array=*/true); } -uptr asan_mz_size(const void *ptr) { - return instance.AllocationSize(reinterpret_cast(ptr)); +uptr asan_mz_size(const void* ptr) { + uptr size = instance.AllocationSize(reinterpret_cast(ptr)); + +#if SANITIZER_WINDOWS + if (size > 0 && instance.FromZeroAllocation(reinterpret_cast(ptr))) { + DCHECK(size == 1); + return 0; + } +#endif + + return size; +} + +#if SANITIZER_WINDOWS +void asan_mark_zero_allocation(void* ptr) { + instance.MarkAsZeroAllocation(reinterpret_cast(ptr)); } +#endif void asan_mz_force_lock() SANITIZER_NO_THREAD_SAFETY_ANALYSIS { instance.ForceLock(); diff --git a/compiler-rt/lib/asan/asan_malloc_win.cpp b/compiler-rt/lib/asan/asan_malloc_win.cpp index ea6f7dfaa08cf..e728591cc145e 100644 --- a/compiler-rt/lib/asan/asan_malloc_win.cpp +++ b/compiler-rt/lib/asan/asan_malloc_win.cpp @@ -53,6 +53,13 @@ BOOL WINAPI HeapValidate(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem); using namespace __asan; +// Marks an allocation as originally zero-size. Must be called on allocations +// that were changed from size 0 to 1 outside of Allocate() (e.g. +// SharedReAlloc). +namespace __asan { +void asan_mark_zero_allocation(void* ptr); +} + // MT: Simply defining functions with the same signature in *.obj // files overrides the standard functions in the CRT. // MD: Memory allocation functions are defined in the CRT .dll, @@ -371,7 +378,8 @@ void *SharedReAlloc(ReAllocFunction reallocFunc, SizeFunction heapSizeFunc, // passing a 0 size into asan_realloc will free the allocation. // To avoid this and keep behavior consistent, fudge the size if 0. // (asan_malloc already does this) - if (dwBytes == 0) + bool was_zero_size = (dwBytes == 0); + if (was_zero_size) dwBytes = 1; size_t old_size; @@ -382,6 +390,9 @@ void *SharedReAlloc(ReAllocFunction reallocFunc, SizeFunction heapSizeFunc, if (ptr == nullptr) return nullptr; + if (was_zero_size) + asan_mark_zero_allocation(ptr); + if (dwFlags & HEAP_ZERO_MEMORY) { size_t new_size = asan_malloc_usable_size(ptr, pc, bp); if (old_size < new_size) diff --git a/compiler-rt/test/asan/TestCases/Windows/heaprealloc_alloc_zero.cpp b/compiler-rt/test/asan/TestCases/Windows/heaprealloc_alloc_zero.cpp index 6a5f8a1e7ea09..9c9f7ee57a8da 100644 --- a/compiler-rt/test/asan/TestCases/Windows/heaprealloc_alloc_zero.cpp +++ b/compiler-rt/test/asan/TestCases/Windows/heaprealloc_alloc_zero.cpp @@ -40,8 +40,9 @@ int main() { if (!ptr2) return 1; size_t heapsize = HeapSize(GetProcessHeap(), 0, ptr2); - if (heapsize != 1) { // will be 0 without ASAN turned on - std::cerr << "HeapAlloc size failure! " << heapsize << " != 1\n"; + // HeapSize will retrieve the user-defined size, not the ASan-allocated size of 1 + if (heapsize != 0) { + std::cerr << "HeapAlloc size failure! " << heapsize << " != 0\n"; return 1; } void *ptr3 = HeapReAlloc(GetProcessHeap(), 0, ptr2, 3); diff --git a/compiler-rt/test/asan/TestCases/Windows/rtlsizeheap_zero.cpp b/compiler-rt/test/asan/TestCases/Windows/rtlsizeheap_zero.cpp new file mode 100644 index 0000000000000..4a98a99d2acb1 --- /dev/null +++ b/compiler-rt/test/asan/TestCases/Windows/rtlsizeheap_zero.cpp @@ -0,0 +1,107 @@ +// RUN: %clang_cl_asan %s %Fe%t +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true %run %t 2>&1 | FileCheck %s +// RUN: %clang_cl_asan %s %Fe%t /DFAIL_CHECK +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL +// +// Verify that zero-size heap allocations report size 0 through Windows heap +// size APIs, preventing false positives when the result is used with memset. + +#include +#include +#include + +using AllocateFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, SIZE_T); +using FreeFunctionPtr = BOOL(__stdcall *)(PVOID, ULONG, PVOID); +using SizeFunctionPtr = SIZE_T(__stdcall *)(PVOID, ULONG, PVOID); + +int main() { + HMODULE NtDllHandle = GetModuleHandle("ntdll.dll"); + if (!NtDllHandle) { + puts("Couldn't load ntdll"); + return -1; + } + + auto RtlAllocateHeap_ptr = + (AllocateFunctionPtr)GetProcAddress(NtDllHandle, "RtlAllocateHeap"); + auto RtlFreeHeap_ptr = + (FreeFunctionPtr)GetProcAddress(NtDllHandle, "RtlFreeHeap"); + auto RtlSizeHeap_ptr = + (SizeFunctionPtr)GetProcAddress(NtDllHandle, "RtlSizeHeap"); + + if (!RtlAllocateHeap_ptr || !RtlFreeHeap_ptr || !RtlSizeHeap_ptr) { + puts("Couldn't find Rtl heap functions"); + return -1; + } + + // Test RtlAllocateHeap with zero size + { + char *buffer = + (char *)RtlAllocateHeap_ptr(GetProcessHeap(), HEAP_ZERO_MEMORY, 0); + if (buffer) { + auto size = RtlSizeHeap_ptr(GetProcessHeap(), 0, buffer); + memset(buffer, 0, size); +#ifdef FAIL_CHECK + // heap-buffer-overflow since actual size is 0 + memset(buffer, 0, 1); +#endif + RtlFreeHeap_ptr(GetProcessHeap(), 0, buffer); + } + } + + // Test malloc with zero size + { + char *buffer = (char *)malloc(0); + if (buffer) { + auto size = _msize(buffer); + auto rtl_size = RtlSizeHeap_ptr(GetProcessHeap(), 0, buffer); + memset(buffer, 0, size); + memset(buffer, 0, rtl_size); + free(buffer); + } + } + + // Test operator new with zero size + { + char *buffer = new char[0]; + auto size = _msize(buffer); + auto rtl_size = RtlSizeHeap_ptr(GetProcessHeap(), 0, buffer); + memset(buffer, 0, size); + memset(buffer, 0, rtl_size); + delete[] buffer; + } + + // Test GlobalAlloc with zero size. + // GlobalAlloc calls RtlAllocateHeap internally. + { + HGLOBAL hMem = GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, 0); + if (hMem) { + char *buffer = (char *)hMem; + auto size = GlobalSize(hMem); + auto rtl_size = RtlSizeHeap_ptr(GetProcessHeap(), 0, buffer); + memset(buffer, 0, size); + memset(buffer, 0, rtl_size); + GlobalFree(hMem); + } + } + + // Test LocalAlloc with zero size. + // LocalAlloc calls RtlAllocateHeap internally. + { + HLOCAL hMem = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, 0); + if (hMem) { + char *buffer = (char *)hMem; + auto size = LocalSize(hMem); + auto rtl_size = RtlSizeHeap_ptr(GetProcessHeap(), 0, buffer); + memset(buffer, 0, size); + memset(buffer, 0, rtl_size); + LocalFree(hMem); + } + } + + puts("Success"); + return 0; +} + +// CHECK: Success +// CHECK-NOT: AddressSanitizer: heap-buffer-overflow +// CHECK-FAIL: AddressSanitizer: heap-buffer-overflow