Index: compiler-rt/lib/asan/asan_flags.inc =================================================================== --- compiler-rt/lib/asan/asan_flags.inc +++ compiler-rt/lib/asan/asan_flags.inc @@ -158,3 +158,5 @@ ASAN_FLAG(bool, verify_asan_link_order, true, "Check position of ASan runtime in library list (needs to be disabled" " when other library has to be preloaded system-wide)") +ASAN_FLAG(bool, windows_hook_rtl_allocators, false, + "(Windows only) enable hooking of Rtl(Allocate|Free|Size|ReAllocate)Heap.") Index: compiler-rt/lib/asan/asan_malloc_win.cc =================================================================== --- compiler-rt/lib/asan/asan_malloc_win.cc +++ compiler-rt/lib/asan/asan_malloc_win.cc @@ -11,6 +11,19 @@ // Windows-specific malloc interception. //===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_allocator_interface.h" +// Need to include defintions for windows heap api functions, +// these assume windows.h will also be included. This definition +// fixes an error that's thrown if you only include heapapi.h +#if defined(_M_IX86) +#define _X86_ +#elif defined(_M_AMD64) +#define _AMD64_ +#else +#error "Missing arch or unsupported platform for Windows." +#endif +#include + #include "sanitizer_common/sanitizer_platform.h" #if SANITIZER_WINDOWS // Intentionally not including windows.h here, to avoid the risk of @@ -21,31 +34,38 @@ typedef const void *LPCVOID; typedef void *LPVOID; -#define HEAP_ZERO_MEMORY 0x00000008 -#define HEAP_REALLOC_IN_PLACE_ONLY 0x00000010 - +constexpr unsigned long HEAP_ALLOCATE_SUPPORTED_FLAGS = (HEAP_ZERO_MEMORY); +constexpr unsigned long HEAP_ALLOCATE_UNSUPPORTED_FLAGS = + (~HEAP_ALLOCATE_SUPPORTED_FLAGS); +constexpr unsigned long HEAP_FREE_SUPPORTED_FLAGS = (0); +constexpr unsigned long HEAP_FREE_UNSUPPORTED_FLAGS = + (~HEAP_ALLOCATE_SUPPORTED_FLAGS); +constexpr unsigned long HEAP_REALLOC_SUPPORTED_FLAGS = + (HEAP_REALLOC_IN_PLACE_ONLY | HEAP_ZERO_MEMORY); +constexpr unsigned long HEAP_REALLOC_UNSUPPORTED_FLAGS = + (~HEAP_ALLOCATE_SUPPORTED_FLAGS); #include "asan_allocator.h" #include "asan_interceptors.h" #include "asan_internal.h" #include "asan_stack.h" #include "interception/interception.h" #include using namespace __asan; // NOLINT // 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, // so we have to intercept them before they are called for the first time. #if ASAN_DYNAMIC # define ALLOCATION_FUNCTION_ATTRIBUTE #else # define ALLOCATION_FUNCTION_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE #endif extern "C" { ALLOCATION_FUNCTION_ATTRIBUTE size_t _msize(void *ptr) { @@ -183,74 +203,291 @@ } } // extern "C" +#define OWNED_BY_RTL(heap, memory) \ + (!__sanitizer_get_ownership(memory) && HeapValidate(heap, 0, memory)) + +INTERCEPTOR_WINAPI(SIZE_T, HeapSize, HANDLE hHeap, DWORD dwFlags, + LPCVOID lpMem) { + // If the RTL allocators are hooked we need to check whether the ASAN + // allocator owns the pointer we're about to use. Allocations occur before + // interception takes place, so if it is not owned by the RTL heap we can + // pass it to the ASAN heap for inspection. + if (flags()->windows_hook_rtl_allocators) { + if (!asan_inited || OWNED_BY_RTL(hHeap, lpMem)) + return REAL(HeapSize)(hHeap, dwFlags, lpMem); + } else { + CHECK(dwFlags == 0 && "unsupported heap flags"); + } + GET_CURRENT_PC_BP_SP; + (void)sp; + return asan_malloc_usable_size(lpMem, pc, bp); +} + INTERCEPTOR_WINAPI(LPVOID, HeapAlloc, HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes) { + // If the ASAN runtime is not initialized, or we encounter an unsupported + // flag, fall back to the original allocator. + if (flags()->windows_hook_rtl_allocators) { + if (UNLIKELY(!asan_inited || + (dwFlags & HEAP_ALLOCATE_UNSUPPORTED_FLAGS) != 0)) { + return REAL(HeapAlloc)(hHeap, dwFlags, dwBytes); + } + } else { + // In the case that we don't hook the rtl allocators, + // this becomes an assert since there is no failover to the original + // allocator. + CHECK((HEAP_ALLOCATE_UNSUPPORTED_FLAGS & dwFlags) != 0 && + "unsupported flags"); + } GET_STACK_TRACE_MALLOC; void *p = asan_malloc(dwBytes, &stack); // Reading MSDN suggests that the *entire* usable allocation is zeroed out. // Otherwise it is difficult to HeapReAlloc with HEAP_ZERO_MEMORY. // https://blogs.msdn.microsoft.com/oldnewthing/20120316-00/?p=8083 - if (dwFlags == HEAP_ZERO_MEMORY) - internal_memset(p, 0, asan_mz_size(p)); - else - CHECK(dwFlags == 0 && "unsupported heap flags"); + if (p && (dwFlags & HEAP_ZERO_MEMORY)) { + GET_CURRENT_PC_BP_SP; + (void)sp; + auto usable_size = asan_malloc_usable_size(p, pc, bp); + internal_memset(p, 0, usable_size); + } return p; } INTERCEPTOR_WINAPI(BOOL, HeapFree, HANDLE hHeap, DWORD dwFlags, LPVOID lpMem) { - CHECK(dwFlags == 0 && "unsupported heap flags"); + // Heap allocations happen before this function is hooked, so we must fall + // back to the original function if the pointer is not from the ASAN heap, + // or unsupported flags are provided. + if (flags()->windows_hook_rtl_allocators) { + if (OWNED_BY_RTL(hHeap, lpMem)) + return REAL(HeapFree)(hHeap, dwFlags, lpMem); + } else { + CHECK((HEAP_FREE_UNSUPPORTED_FLAGS & dwFlags) != 0 && "unsupported flags"); + } GET_STACK_TRACE_FREE; asan_free(lpMem, &stack, FROM_MALLOC); return true; } -INTERCEPTOR_WINAPI(LPVOID, HeapReAlloc, HANDLE hHeap, DWORD dwFlags, - LPVOID lpMem, SIZE_T dwBytes) { +namespace __asan { +using AllocFunction = LPVOID(WINAPI *)(HANDLE, DWORD, SIZE_T); +using ReAllocFunction = LPVOID(WINAPI *)(HANDLE, DWORD, LPVOID, SIZE_T); +using SizeFunction = SIZE_T(WINAPI *)(HANDLE, DWORD, LPVOID); +using FreeFunction = BOOL(WINAPI *)(HANDLE, DWORD, LPVOID); + +void *SharedReAlloc(ReAllocFunction reallocFunc, SizeFunction heapSizeFunc, + FreeFunction freeFunc, AllocFunction allocFunc, + HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes) { + CHECK(reallocFunc && heapSizeFunc && freeFunc && allocFunc); GET_STACK_TRACE_MALLOC; GET_CURRENT_PC_BP_SP; (void)sp; - // Realloc should never reallocate in place. + if (flags()->windows_hook_rtl_allocators) { + enum AllocationOwnership { NEITHER = 0, ASAN = 1, RTL = 2 }; + AllocationOwnership ownershipState; + bool owned_rtlalloc = false; + bool owned_asan = __sanitizer_get_ownership(lpMem); + + if (!owned_asan) + owned_rtlalloc = HeapValidate(hHeap, 0, lpMem); + + if (owned_asan && !owned_rtlalloc) + ownershipState = ASAN; + else if (!owned_asan && owned_rtlalloc) + ownershipState = RTL; + else if (!owned_asan && !owned_rtlalloc) + ownershipState = NEITHER; + + // If this heap block which was allocated before the ASAN + // runtime came up, use the real HeapFree function. + if (UNLIKELY(!asan_inited)) { + return reallocFunc(hHeap, dwFlags, lpMem, dwBytes); + } + bool only_asan_supported_flags = + (HEAP_REALLOC_UNSUPPORTED_FLAGS & dwFlags) == 0; + + if (ownershipState == RTL || + (ownershipState == NEITHER && !only_asan_supported_flags)) { + if (only_asan_supported_flags) { + // if this is a conversion to ASAN upported flags, transfer this + // allocation to the ASAN allocator + void *replacement_alloc; + if (dwFlags & HEAP_ZERO_MEMORY) + replacement_alloc = asan_calloc(1, dwBytes, &stack); + else + replacement_alloc = asan_malloc(dwBytes, &stack); + if (replacement_alloc) { + size_t old_size = heapSizeFunc(hHeap, dwFlags, lpMem); + if (old_size == ((SIZE_T)0) - 1) { + asan_free(replacement_alloc, &stack, FROM_MALLOC); + return nullptr; + } + REAL(memcpy)(replacement_alloc, lpMem, old_size); + freeFunc(hHeap, dwFlags, lpMem); + } + return replacement_alloc; + } else { + // owned by rtl or neither with unsupported ASAN flags, + // just pass back to original allocator + CHECK(ownershipState == RTL || ownershipState == NEITHER); + CHECK(!only_asan_supported_flags); + return reallocFunc(hHeap, dwFlags, lpMem, dwBytes); + } + } + + if (ownershipState == ASAN && !only_asan_supported_flags) { + // Conversion to unsupported flags allocation, + // transfer this allocation back to the original allocator. + void *replacement_alloc = allocFunc(hHeap, dwFlags, dwBytes); + size_t old_usable_size = 0; + if (replacement_alloc) { + old_usable_size = asan_malloc_usable_size(lpMem, pc, bp); + REAL(memcpy)(replacement_alloc, lpMem, min(dwBytes, old_usable_size)); + asan_free(lpMem, &stack, FROM_MALLOC); + } + return replacement_alloc; + } + + CHECK((ownershipState == ASAN || ownershipState == NEITHER) && + only_asan_supported_flags); + // At this point we should either be ASAN owned with ASAN supported flags + // or we owned by neither and have supported flags. + // Pass through even when it's neither since this could be a null realloc or + // UAF that ASAN needs to catch. + } else { + CHECK((HEAP_REALLOC_UNSUPPORTED_FLAGS & dwFlags) != 0 && + "unsupported flags"); + } + // asan_realloc will never reallocate in place, so for now this flag is + // unsupported until we figure out a way to fake this. if (dwFlags & HEAP_REALLOC_IN_PLACE_ONLY) return nullptr; - CHECK(dwFlags == 0 && "unsupported heap flags"); + // HeapReAlloc and HeapAlloc both happily accept 0 sized allocations. // 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) dwBytes = 1; + size_t old_size; if (dwFlags & HEAP_ZERO_MEMORY) old_size = asan_malloc_usable_size(lpMem, pc, bp); + void *ptr = asan_realloc(lpMem, dwBytes, &stack); if (ptr == nullptr) return nullptr; if (dwFlags & HEAP_ZERO_MEMORY) { size_t new_size = asan_malloc_usable_size(ptr, pc, bp); if (old_size < new_size) REAL(memset)(((u8 *)ptr) + old_size, 0, new_size - old_size); } + return ptr; } +} // namespace __asan -INTERCEPTOR_WINAPI(SIZE_T, HeapSize, HANDLE hHeap, DWORD dwFlags, - LPCVOID lpMem) { - CHECK(dwFlags == 0 && "unsupported heap flags"); +INTERCEPTOR_WINAPI(LPVOID, HeapReAlloc, HANDLE hHeap, DWORD dwFlags, + LPVOID lpMem, SIZE_T dwBytes) { + return SharedReAlloc(REAL(HeapReAlloc), (SizeFunction)REAL(HeapSize), + REAL(HeapFree), REAL(HeapAlloc), hHeap, dwFlags, lpMem, + dwBytes); +} + +// The following functions are undocumented and subject to change. +// However, hooking them is necessary to hook Windows heap +// allocations with detours and their definitions are unlikely to change. +// Comments in /minkernel/ntos/rtl/heappublic.c indicate that these functions +// are part of the heap's public interface. +typedef ULONG LOGICAL; + +// This function is documented as part of the Driver Development Kit but *not* +// the Windows Development Kit. +NTSYSAPI LOGICAL RtlFreeHeap(PVOID HeapHandle, ULONG Flags, + _Frees_ptr_opt_ PVOID BaseAddress); + +// This function is documented as part of the Driver Development Kit but *not* +// the Windows Development Kit. +NTSYSAPI PVOID RtlAllocateHeap(PVOID HeapHandle, ULONG Flags, SIZE_T Size); + +// This function is completely undocumented. +PVOID +RtlReAllocateHeap(PVOID HeapHandle, ULONG Flags, PVOID BaseAddress, + SIZE_T Size); + +// This function is completely undocumented. +SIZE_T +RtlSizeHeap(PVOID HeapHandle, ULONG Flags, PVOID BaseAddress); + +INTERCEPTOR_WINAPI(SIZE_T, RtlSizeHeap, HANDLE HeapHandle, ULONG Flags, + PVOID BaseAddress) { + if (!flags()->windows_hook_rtl_allocators || + UNLIKELY(!asan_inited || OWNED_BY_RTL(HeapHandle, BaseAddress))) { + return REAL(RtlSizeHeap)(HeapHandle, Flags, BaseAddress); + } GET_CURRENT_PC_BP_SP; (void)sp; - return asan_malloc_usable_size(lpMem, pc, bp); + return asan_malloc_usable_size(BaseAddress, pc, bp); +} + +INTERCEPTOR_WINAPI(BOOL, RtlFreeHeap, HANDLE HeapHandle, ULONG Flags, + PVOID BaseAddress) { + // Heap allocations happen before this function is hooked, so we must fall + // back to the original function if the pointer is not from the ASAN heap, or + // unsupported flags are provided. + if (!flags()->windows_hook_rtl_allocators || + UNLIKELY((HEAP_FREE_UNSUPPORTED_FLAGS & Flags) != 0 || + OWNED_BY_RTL(HeapHandle, BaseAddress))) { + return REAL(RtlFreeHeap)(HeapHandle, Flags, BaseAddress); + } + GET_STACK_TRACE_FREE; + asan_free(BaseAddress, &stack, FROM_MALLOC); + return true; +} + +INTERCEPTOR_WINAPI(PVOID, RtlAllocateHeap, HANDLE HeapHandle, DWORD Flags, + SIZE_T Size) { + // If the ASAN runtime is not initialized, or we encounter an unsupported + // flag, fall back to the original allocator. + if (!flags()->windows_hook_rtl_allocators || + UNLIKELY(!asan_inited || + (Flags & HEAP_ALLOCATE_UNSUPPORTED_FLAGS) != 0)) { + return REAL(RtlAllocateHeap)(HeapHandle, Flags, Size); + } + GET_STACK_TRACE_MALLOC; + void *p; + // Reading MSDN suggests that the *entire* usable allocation is zeroed out. + // Otherwise it is difficult to HeapReAlloc with HEAP_ZERO_MEMORY. + // https://blogs.msdn.microsoft.com/oldnewthing/20120316-00/?p=8083 + if (Flags & HEAP_ZERO_MEMORY) { + p = asan_calloc(Size, 1, &stack); + } else { + p = asan_malloc(Size, &stack); + } + return p; +} + +INTERCEPTOR_WINAPI(PVOID, RtlReAllocateHeap, HANDLE HeapHandle, ULONG Flags, + PVOID BaseAddress, SIZE_T Size) { + // If it's actually a heap block which was allocated before the ASAN runtime + // came up, use the real RtlFreeHeap function. + if (!flags()->windows_hook_rtl_allocators) + return REAL(RtlReAllocateHeap)(HeapHandle, Flags, BaseAddress, Size); + + return SharedReAlloc(REAL(RtlReAllocateHeap), REAL(RtlSizeHeap), + REAL(RtlFreeHeap), REAL(RtlAllocateHeap), HeapHandle, + Flags, BaseAddress, Size); } namespace __asan { static void TryToOverrideFunction(const char *fname, uptr new_func) { // Failure here is not fatal. The CRT may not be present, and different CRT // versions use different symbols. if (!__interception::OverrideFunction(fname, new_func)) VPrintf(2, "Failed to override function %s\n", fname); } void ReplaceSystemMalloc() { #if defined(ASAN_DYNAMIC) TryToOverrideFunction("free", (uptr)free); @@ -272,6 +509,34 @@ TryToOverrideFunction("_expand", (uptr)_expand); TryToOverrideFunction("_expand_base", (uptr)_expand); + if (flags()->windows_hook_rtl_allocators) { + INTERCEPT_FUNCTION(HeapSize); + INTERCEPT_FUNCTION(HeapFree); + INTERCEPT_FUNCTION(HeapReAlloc); + INTERCEPT_FUNCTION(HeapAlloc); + + // Undocumented functions must be intercepted by name, not by symbol. + __interception::OverrideFunction("RtlSizeHeap", (uptr)WRAP(RtlSizeHeap), + (uptr *)&REAL(RtlSizeHeap)); + __interception::OverrideFunction("RtlFreeHeap", (uptr)WRAP(RtlFreeHeap), + (uptr *)&REAL(RtlFreeHeap)); + __interception::OverrideFunction("RtlReAllocateHeap", + (uptr)WRAP(RtlReAllocateHeap), + (uptr *)&REAL(RtlReAllocateHeap)); + __interception::OverrideFunction("RtlAllocateHeap", + (uptr)WRAP(RtlAllocateHeap), + (uptr *)&REAL(RtlAllocateHeap)); + } else { +#define INTERCEPT_UCRT_FUNCTION(func) \ + if (!INTERCEPT_FUNCTION_DLLIMPORT("ucrtbase.dll", \ + "api-ms-win-core-heap-l1-1-0.dll", func)) \ + VPrintf(2, "Failed to intercept ucrtbase.dll import %s\n", #func); + INTERCEPT_UCRT_FUNCTION(HeapAlloc); + INTERCEPT_UCRT_FUNCTION(HeapFree); + INTERCEPT_UCRT_FUNCTION(HeapReAlloc); + INTERCEPT_UCRT_FUNCTION(HeapSize); +#undef INTERCEPT_UCRT_FUNCTION + } // Recent versions of ucrtbase.dll appear to be built with PGO and LTCG, which // enable cross-module inlining. This means our _malloc_base hook won't catch // all CRT allocations. This code here patches the import table of @@ -279,17 +544,9 @@ // allocation API will be directed to ASan's heap. We don't currently // intercept all calls to HeapAlloc. If we did, we would have to check on // HeapFree whether the pointer came from ASan of from the system. -#define INTERCEPT_UCRT_FUNCTION(func) \ - if (!INTERCEPT_FUNCTION_DLLIMPORT("ucrtbase.dll", \ - "api-ms-win-core-heap-l1-1-0.dll", func)) \ - VPrintf(2, "Failed to intercept ucrtbase.dll import %s\n", #func); - INTERCEPT_UCRT_FUNCTION(HeapAlloc); - INTERCEPT_UCRT_FUNCTION(HeapFree); - INTERCEPT_UCRT_FUNCTION(HeapReAlloc); - INTERCEPT_UCRT_FUNCTION(HeapSize); -#undef INTERCEPT_UCRT_FUNCTION -#endif + +#endif // defined(ASAN_DYNAMIC) } } // namespace __asan #endif // _WIN32 Index: compiler-rt/lib/asan/asan_win.cc =================================================================== --- compiler-rt/lib/asan/asan_win.cc +++ compiler-rt/lib/asan/asan_win.cc @@ -1,376 +1,401 @@ -//===-- asan_win.cc -------------------------------------------------------===// -// -// 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 AddressSanitizer, an address sanity checker. -// -// Windows-specific details. -//===----------------------------------------------------------------------===// - -#include "sanitizer_common/sanitizer_platform.h" -#if SANITIZER_WINDOWS -#define WIN32_LEAN_AND_MEAN -#include - -#include - -#include "asan_interceptors.h" -#include "asan_internal.h" -#include "asan_report.h" -#include "asan_stack.h" -#include "asan_thread.h" -#include "asan_mapping.h" -#include "sanitizer_common/sanitizer_libc.h" -#include "sanitizer_common/sanitizer_mutex.h" -#include "sanitizer_common/sanitizer_win.h" -#include "sanitizer_common/sanitizer_win_defs.h" - -using namespace __asan; // NOLINT - -extern "C" { -SANITIZER_INTERFACE_ATTRIBUTE -int __asan_should_detect_stack_use_after_return() { - __asan_init(); - return __asan_option_detect_stack_use_after_return; -} - -SANITIZER_INTERFACE_ATTRIBUTE -uptr __asan_get_shadow_memory_dynamic_address() { - __asan_init(); - return __asan_shadow_memory_dynamic_address; -} -} // extern "C" - -// ---------------------- Windows-specific interceptors ---------------- {{{ -static LPTOP_LEVEL_EXCEPTION_FILTER default_seh_handler; -static LPTOP_LEVEL_EXCEPTION_FILTER user_seh_handler; - -extern "C" SANITIZER_INTERFACE_ATTRIBUTE -long __asan_unhandled_exception_filter(EXCEPTION_POINTERS *info) { - EXCEPTION_RECORD *exception_record = info->ExceptionRecord; - CONTEXT *context = info->ContextRecord; - - // FIXME: Handle EXCEPTION_STACK_OVERFLOW here. - - SignalContext sig(exception_record, context); - ReportDeadlySignal(sig); - UNREACHABLE("returned from reporting deadly signal"); -} - -// Wrapper SEH Handler. If the exception should be handled by asan, we call -// __asan_unhandled_exception_filter, otherwise, we execute the user provided -// exception handler or the default. -static long WINAPI SEHHandler(EXCEPTION_POINTERS *info) { - DWORD exception_code = info->ExceptionRecord->ExceptionCode; - if (__sanitizer::IsHandledDeadlyException(exception_code)) - return __asan_unhandled_exception_filter(info); - if (user_seh_handler) - return user_seh_handler(info); - // Bubble out to the default exception filter. - if (default_seh_handler) - return default_seh_handler(info); - return EXCEPTION_CONTINUE_SEARCH; -} - -INTERCEPTOR_WINAPI(LPTOP_LEVEL_EXCEPTION_FILTER, SetUnhandledExceptionFilter, - LPTOP_LEVEL_EXCEPTION_FILTER ExceptionFilter) { - CHECK(REAL(SetUnhandledExceptionFilter)); - if (ExceptionFilter == &SEHHandler) - return REAL(SetUnhandledExceptionFilter)(ExceptionFilter); - // We record the user provided exception handler to be called for all the - // exceptions unhandled by asan. - Swap(ExceptionFilter, user_seh_handler); - return ExceptionFilter; -} - -INTERCEPTOR_WINAPI(void, RtlRaiseException, EXCEPTION_RECORD *ExceptionRecord) { - CHECK(REAL(RtlRaiseException)); - // This is a noreturn function, unless it's one of the exceptions raised to - // communicate with the debugger, such as the one from OutputDebugString. - if (ExceptionRecord->ExceptionCode != DBG_PRINTEXCEPTION_C) - __asan_handle_no_return(); - REAL(RtlRaiseException)(ExceptionRecord); -} - -INTERCEPTOR_WINAPI(void, RaiseException, void *a, void *b, void *c, void *d) { - CHECK(REAL(RaiseException)); - __asan_handle_no_return(); - REAL(RaiseException)(a, b, c, d); -} - -#ifdef _WIN64 - -INTERCEPTOR_WINAPI(EXCEPTION_DISPOSITION, __C_specific_handler, - _EXCEPTION_RECORD *a, void *b, _CONTEXT *c, - _DISPATCHER_CONTEXT *d) { // NOLINT - CHECK(REAL(__C_specific_handler)); - __asan_handle_no_return(); - return REAL(__C_specific_handler)(a, b, c, d); -} - -#else - -INTERCEPTOR(int, _except_handler3, void *a, void *b, void *c, void *d) { - CHECK(REAL(_except_handler3)); - __asan_handle_no_return(); - return REAL(_except_handler3)(a, b, c, d); -} - -#if ASAN_DYNAMIC -// This handler is named differently in -MT and -MD CRTs. -#define _except_handler4 _except_handler4_common -#endif -INTERCEPTOR(int, _except_handler4, void *a, void *b, void *c, void *d) { - CHECK(REAL(_except_handler4)); - __asan_handle_no_return(); - return REAL(_except_handler4)(a, b, c, d); -} -#endif - -static thread_return_t THREAD_CALLING_CONV asan_thread_start(void *arg) { - AsanThread *t = (AsanThread*)arg; - SetCurrentThread(t); - return t->ThreadStart(GetTid(), /* signal_thread_is_registered */ nullptr); -} - -INTERCEPTOR_WINAPI(HANDLE, CreateThread, LPSECURITY_ATTRIBUTES security, - SIZE_T stack_size, LPTHREAD_START_ROUTINE start_routine, - void *arg, DWORD thr_flags, DWORD *tid) { - // Strict init-order checking is thread-hostile. - if (flags()->strict_init_order) - StopInitOrderChecking(); - GET_STACK_TRACE_THREAD; - // FIXME: The CreateThread interceptor is not the same as a pthread_create - // one. This is a bandaid fix for PR22025. - bool detached = false; // FIXME: how can we determine it on Windows? - u32 current_tid = GetCurrentTidOrInvalid(); - AsanThread *t = - AsanThread::Create(start_routine, arg, current_tid, &stack, detached); - return REAL(CreateThread)(security, stack_size, asan_thread_start, t, - thr_flags, tid); -} - -// }}} - -namespace __asan { - -void InitializePlatformInterceptors() { - // The interceptors were not designed to be removable, so we have to keep this - // module alive for the life of the process. - HMODULE pinned; - CHECK(GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | - GET_MODULE_HANDLE_EX_FLAG_PIN, - (LPCWSTR)&InitializePlatformInterceptors, - &pinned)); - - ASAN_INTERCEPT_FUNC(CreateThread); - ASAN_INTERCEPT_FUNC(SetUnhandledExceptionFilter); - -#ifdef _WIN64 - ASAN_INTERCEPT_FUNC(__C_specific_handler); -#else - ASAN_INTERCEPT_FUNC(_except_handler3); - ASAN_INTERCEPT_FUNC(_except_handler4); -#endif - - // Try to intercept kernel32!RaiseException, and if that fails, intercept - // ntdll!RtlRaiseException instead. - if (!::__interception::OverrideFunction("RaiseException", - (uptr)WRAP(RaiseException), - (uptr *)&REAL(RaiseException))) { - CHECK(::__interception::OverrideFunction("RtlRaiseException", - (uptr)WRAP(RtlRaiseException), - (uptr *)&REAL(RtlRaiseException))); - } -} - -void AsanApplyToGlobals(globals_op_fptr op, const void *needle) { - UNIMPLEMENTED(); -} - -// ---------------------- TSD ---------------- {{{ -static bool tsd_key_inited = false; - -static __declspec(thread) void *fake_tsd = 0; - -void AsanTSDInit(void (*destructor)(void *tsd)) { - // FIXME: we're ignoring the destructor for now. - tsd_key_inited = true; -} - -void *AsanTSDGet() { - CHECK(tsd_key_inited); - return fake_tsd; -} - -void AsanTSDSet(void *tsd) { - CHECK(tsd_key_inited); - fake_tsd = tsd; -} - -void PlatformTSDDtor(void *tsd) { - AsanThread::TSDDtor(tsd); -} -// }}} - -// ---------------------- Various stuff ---------------- {{{ -void *AsanDoesNotSupportStaticLinkage() { -#if defined(_DEBUG) -#error Please build the runtime with a non-debug CRT: /MD or /MT -#endif - return 0; -} - -uptr FindDynamicShadowStart() { - uptr granularity = GetMmapGranularity(); - uptr alignment = 8 * granularity; - uptr left_padding = granularity; - uptr space_size = kHighShadowEnd + left_padding; - uptr shadow_start = FindAvailableMemoryRange(space_size, alignment, - granularity, nullptr, nullptr); - CHECK_NE((uptr)0, shadow_start); - CHECK(IsAligned(shadow_start, alignment)); - return shadow_start; -} - -void AsanCheckDynamicRTPrereqs() {} - -void AsanCheckIncompatibleRT() {} - -void ReadContextStack(void *context, uptr *stack, uptr *ssize) { - UNIMPLEMENTED(); -} - -void AsanOnDeadlySignal(int, void *siginfo, void *context) { - UNIMPLEMENTED(); -} - -#if SANITIZER_WINDOWS64 -// Exception handler for dealing with shadow memory. -static LONG CALLBACK -ShadowExceptionHandler(PEXCEPTION_POINTERS exception_pointers) { - uptr page_size = GetPageSizeCached(); - // Only handle access violations. - if (exception_pointers->ExceptionRecord->ExceptionCode != - EXCEPTION_ACCESS_VIOLATION) { - return EXCEPTION_CONTINUE_SEARCH; - } - - // Only handle access violations that land within the shadow memory. - uptr addr = - (uptr)(exception_pointers->ExceptionRecord->ExceptionInformation[1]); - - // Check valid shadow range. - if (!AddrIsInShadow(addr)) return EXCEPTION_CONTINUE_SEARCH; - - // This is an access violation while trying to read from the shadow. Commit - // the relevant page and let execution continue. - - // Determine the address of the page that is being accessed. - uptr page = RoundDownTo(addr, page_size); - - // Commit the page. - uptr result = - (uptr)::VirtualAlloc((LPVOID)page, page_size, MEM_COMMIT, PAGE_READWRITE); - if (result != page) return EXCEPTION_CONTINUE_SEARCH; - - // The page mapping succeeded, so continue execution as usual. - return EXCEPTION_CONTINUE_EXECUTION; -} - -#endif - -void InitializePlatformExceptionHandlers() { -#if SANITIZER_WINDOWS64 - // On Win64, we map memory on demand with access violation handler. - // Install our exception handler. - CHECK(AddVectoredExceptionHandler(TRUE, &ShadowExceptionHandler)); -#endif -} - -bool IsSystemHeapAddress(uptr addr) { - return ::HeapValidate(GetProcessHeap(), 0, (void*)addr) != FALSE; -} - -// We want to install our own exception handler (EH) to print helpful reports -// on access violations and whatnot. Unfortunately, the CRT initializers assume -// they are run before any user code and drop any previously-installed EHs on -// the floor, so we can't install our handler inside __asan_init. -// (See crt0dat.c in the CRT sources for the details) -// -// Things get even more complicated with the dynamic runtime, as it finishes its -// initialization before the .exe module CRT begins to initialize. -// -// For the static runtime (-MT), it's enough to put a callback to -// __asan_set_seh_filter in the last section for C initializers. -// -// For the dynamic runtime (-MD), we want link the same -// asan_dynamic_runtime_thunk.lib to all the modules, thus __asan_set_seh_filter -// will be called for each instrumented module. This ensures that at least one -// __asan_set_seh_filter call happens after the .exe module CRT is initialized. -extern "C" SANITIZER_INTERFACE_ATTRIBUTE -int __asan_set_seh_filter() { - // We should only store the previous handler if it's not our own handler in - // order to avoid loops in the EH chain. - auto prev_seh_handler = SetUnhandledExceptionFilter(SEHHandler); - if (prev_seh_handler != &SEHHandler) - default_seh_handler = prev_seh_handler; - return 0; -} - -bool HandleDlopenInit() { - // Not supported on this platform. - static_assert(!SANITIZER_SUPPORTS_INIT_FOR_DLOPEN, - "Expected SANITIZER_SUPPORTS_INIT_FOR_DLOPEN to be false"); - return false; -} - -#if !ASAN_DYNAMIC -// The CRT runs initializers in this order: -// - C initializers, from XIA to XIZ -// - C++ initializers, from XCA to XCZ -// Prior to 2015, the CRT set the unhandled exception filter at priority XIY, -// near the end of C initialization. Starting in 2015, it was moved to the -// beginning of C++ initialization. We set our priority to XCAB to run -// immediately after the CRT runs. This way, our exception filter is called -// first and we can delegate to their filter if appropriate. -#pragma section(".CRT$XCAB", long, read) // NOLINT -__declspec(allocate(".CRT$XCAB")) int (*__intercept_seh)() = - __asan_set_seh_filter; - -// Piggyback on the TLS initialization callback directory to initialize asan as -// early as possible. Initializers in .CRT$XL* are called directly by ntdll, -// which run before the CRT. Users also add code to .CRT$XLC, so it's important -// to run our initializers first. -static void NTAPI asan_thread_init(void *module, DWORD reason, void *reserved) { - if (reason == DLL_PROCESS_ATTACH) __asan_init(); -} - -#pragma section(".CRT$XLAB", long, read) // NOLINT -__declspec(allocate(".CRT$XLAB")) void (NTAPI *__asan_tls_init)(void *, - unsigned long, void *) = asan_thread_init; -#endif - -static void NTAPI asan_thread_exit(void *module, DWORD reason, void *reserved) { - if (reason == DLL_THREAD_DETACH) { - // Unpoison the thread's stack because the memory may be re-used. - NT_TIB *tib = (NT_TIB *)NtCurrentTeb(); - uptr stackSize = (uptr)tib->StackBase - (uptr)tib->StackLimit; - __asan_unpoison_memory_region(tib->StackLimit, stackSize); - } -} - -#pragma section(".CRT$XLY", long, read) // NOLINT -__declspec(allocate(".CRT$XLY")) void (NTAPI *__asan_tls_exit)(void *, - unsigned long, void *) = asan_thread_exit; - -WIN_FORCE_LINK(__asan_dso_reg_hook) - -// }}} -} // namespace __asan - -#endif // SANITIZER_WINDOWS +//===-- asan_win.cc -------------------------------------------------------===// +// +// 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 AddressSanitizer, an address sanity checker. +// +// Windows-specific details. +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_platform.h" +#if SANITIZER_WINDOWS +#define WIN32_LEAN_AND_MEAN +#include + +#include + +#include "asan_interceptors.h" +#include "asan_internal.h" +#include "asan_mapping.h" +#include "asan_report.h" +#include "asan_stack.h" +#include "asan_thread.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_mutex.h" +#include "sanitizer_common/sanitizer_win.h" +#include "sanitizer_common/sanitizer_win_defs.h" + +using namespace __asan; // NOLINT + +extern "C" { +SANITIZER_INTERFACE_ATTRIBUTE +int __asan_should_detect_stack_use_after_return() { + __asan_init(); + return __asan_option_detect_stack_use_after_return; +} + +SANITIZER_INTERFACE_ATTRIBUTE +uptr __asan_get_shadow_memory_dynamic_address() { + __asan_init(); + return __asan_shadow_memory_dynamic_address; +} +} // extern "C" + +// ---------------------- Windows-specific interceptors ---------------- {{{ +static LPTOP_LEVEL_EXCEPTION_FILTER default_seh_handler; +static LPTOP_LEVEL_EXCEPTION_FILTER user_seh_handler; + +extern "C" SANITIZER_INTERFACE_ATTRIBUTE +long __asan_unhandled_exception_filter(EXCEPTION_POINTERS *info) { + EXCEPTION_RECORD *exception_record = info->ExceptionRecord; + CONTEXT *context = info->ContextRecord; + + // FIXME: Handle EXCEPTION_STACK_OVERFLOW here. + + SignalContext sig(exception_record, context); + ReportDeadlySignal(sig); + UNREACHABLE("returned from reporting deadly signal"); +} + +// Wrapper SEH Handler. If the exception should be handled by asan, we call +// __asan_unhandled_exception_filter, otherwise, we execute the user provided +// exception handler or the default. +static long WINAPI SEHHandler(EXCEPTION_POINTERS *info) { + DWORD exception_code = info->ExceptionRecord->ExceptionCode; + if (__sanitizer::IsHandledDeadlyException(exception_code)) + return __asan_unhandled_exception_filter(info); + if (user_seh_handler) + return user_seh_handler(info); + // Bubble out to the default exception filter. + if (default_seh_handler) + return default_seh_handler(info); + return EXCEPTION_CONTINUE_SEARCH; +} + +INTERCEPTOR_WINAPI(LPTOP_LEVEL_EXCEPTION_FILTER, SetUnhandledExceptionFilter, + LPTOP_LEVEL_EXCEPTION_FILTER ExceptionFilter) { + CHECK(REAL(SetUnhandledExceptionFilter)); + if (ExceptionFilter == &SEHHandler) + return REAL(SetUnhandledExceptionFilter)(ExceptionFilter); + // We record the user provided exception handler to be called for all the + // exceptions unhandled by asan. + Swap(ExceptionFilter, user_seh_handler); + return ExceptionFilter; +} + +INTERCEPTOR_WINAPI(void, RtlRaiseException, EXCEPTION_RECORD *ExceptionRecord) { + CHECK(REAL(RtlRaiseException)); + // This is a noreturn function, unless it's one of the exceptions raised to + // communicate with the debugger, such as the one from OutputDebugString. + if (ExceptionRecord->ExceptionCode != DBG_PRINTEXCEPTION_C) + __asan_handle_no_return(); + REAL(RtlRaiseException)(ExceptionRecord); +} + +INTERCEPTOR_WINAPI(void, RaiseException, void *a, void *b, void *c, void *d) { + CHECK(REAL(RaiseException)); + __asan_handle_no_return(); + REAL(RaiseException)(a, b, c, d); +} + +#ifdef _WIN64 + +INTERCEPTOR_WINAPI(EXCEPTION_DISPOSITION, __C_specific_handler, + _EXCEPTION_RECORD *a, void *b, _CONTEXT *c, + _DISPATCHER_CONTEXT *d) { // NOLINT + CHECK(REAL(__C_specific_handler)); + __asan_handle_no_return(); + return REAL(__C_specific_handler)(a, b, c, d); +} + +#else + +INTERCEPTOR(int, _except_handler3, void *a, void *b, void *c, void *d) { + CHECK(REAL(_except_handler3)); + __asan_handle_no_return(); + return REAL(_except_handler3)(a, b, c, d); +} + +#if ASAN_DYNAMIC +// This handler is named differently in -MT and -MD CRTs. +#define _except_handler4 _except_handler4_common +#endif +INTERCEPTOR(int, _except_handler4, void *a, void *b, void *c, void *d) { + CHECK(REAL(_except_handler4)); + __asan_handle_no_return(); + return REAL(_except_handler4)(a, b, c, d); +} +#endif + +static thread_return_t THREAD_CALLING_CONV asan_thread_start(void *arg) { + AsanThread *t = (AsanThread *)arg; + SetCurrentThread(t); + return t->ThreadStart(GetTid(), /* signal_thread_is_registered */ nullptr); +} + +INTERCEPTOR_WINAPI(HANDLE, CreateThread, LPSECURITY_ATTRIBUTES security, + SIZE_T stack_size, LPTHREAD_START_ROUTINE start_routine, + void *arg, DWORD thr_flags, DWORD *tid) { + // Strict init-order checking is thread-hostile. + if (flags()->strict_init_order) + StopInitOrderChecking(); + GET_STACK_TRACE_THREAD; + // FIXME: The CreateThread interceptor is not the same as a pthread_create + // one. This is a bandaid fix for PR22025. + bool detached = false; // FIXME: how can we determine it on Windows? + u32 current_tid = GetCurrentTidOrInvalid(); + AsanThread *t = + AsanThread::Create(start_routine, arg, current_tid, &stack, detached); + return REAL(CreateThread)(security, stack_size, asan_thread_start, t, + thr_flags, tid); +} + +// }}} + +namespace __asan { + +void InitializePlatformInterceptors() { + // The interceptors were not designed to be removable, so we have to keep this + // module alive for the life of the process. + HMODULE pinned; + CHECK(GetModuleHandleExW( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN, + (LPCWSTR)&InitializePlatformInterceptors, &pinned)); + + ASAN_INTERCEPT_FUNC(CreateThread); + ASAN_INTERCEPT_FUNC(SetUnhandledExceptionFilter); + +#ifdef _WIN64 + ASAN_INTERCEPT_FUNC(__C_specific_handler); +#else + ASAN_INTERCEPT_FUNC(_except_handler3); + ASAN_INTERCEPT_FUNC(_except_handler4); +#endif + + // Try to intercept kernel32!RaiseException, and if that fails, intercept + // ntdll!RtlRaiseException instead. + if (!::__interception::OverrideFunction("RaiseException", + (uptr)WRAP(RaiseException), + (uptr *)&REAL(RaiseException))) { + CHECK(::__interception::OverrideFunction("RtlRaiseException", + (uptr)WRAP(RtlRaiseException), + (uptr *)&REAL(RtlRaiseException))); + } +} + +void AsanApplyToGlobals(globals_op_fptr op, const void *needle) { + UNIMPLEMENTED(); +} + +// ---------------------- TSD ---------------- {{{ +static bool tsd_key_inited = false; + +static __declspec(thread) void *fake_tsd = 0; + +// https://docs.microsoft.com/en-us/windows/desktop/api/winternl/ns-winternl-_teb +// "[This structure may be altered in future versions of Windows. Applications +// should use the alternate functions listed in this topic.]" +typedef struct _TEB { + PVOID Reserved1[12]; + // PVOID ThreadLocalStoragePointer; is here, at the last field in Reserved1. + PVOID ProcessEnvironmentBlock; + PVOID Reserved2[399]; + BYTE Reserved3[1952]; + PVOID TlsSlots[64]; + BYTE Reserved4[8]; + PVOID Reserved5[26]; + PVOID ReservedForOle; + PVOID Reserved6[4]; + PVOID TlsExpansionSlots; +} TEB, *PTEB; + +constexpr size_t TEB_RESERVED_FIELDS_THREAD_LOCAL_STORAGE_OFFSET = 11; +BOOL IsTlsInitialized() { + PTEB teb = (PTEB)NtCurrentTeb(); + return teb->Reserved1[TEB_RESERVED_FIELDS_THREAD_LOCAL_STORAGE_OFFSET] != + nullptr; +} + +void AsanTSDInit(void (*destructor)(void *tsd)) { + // FIXME: we're ignoring the destructor for now. + tsd_key_inited = true; +} + +void *AsanTSDGet() { + CHECK(tsd_key_inited); + return IsTlsInitialized() ? fake_tsd : nullptr; +} + +void AsanTSDSet(void *tsd) { + CHECK(tsd_key_inited); + fake_tsd = tsd; +} + +void PlatformTSDDtor(void *tsd) { AsanThread::TSDDtor(tsd); } +// }}} + +// ---------------------- Various stuff ---------------- {{{ +void *AsanDoesNotSupportStaticLinkage() { +#if defined(_DEBUG) +#error Please build the runtime with a non-debug CRT: /MD or /MT +#endif + return 0; +} + +uptr FindDynamicShadowStart() { + uptr granularity = GetMmapGranularity(); + uptr alignment = 8 * granularity; + uptr left_padding = granularity; + uptr space_size = kHighShadowEnd + left_padding; + uptr shadow_start = FindAvailableMemoryRange(space_size, alignment, + granularity, nullptr, nullptr); + CHECK_NE((uptr)0, shadow_start); + CHECK(IsAligned(shadow_start, alignment)); + return shadow_start; +} + +void AsanCheckDynamicRTPrereqs() {} + +void AsanCheckIncompatibleRT() {} + +void ReadContextStack(void *context, uptr *stack, uptr *ssize) { + UNIMPLEMENTED(); +} + +void AsanOnDeadlySignal(int, void *siginfo, void *context) { UNIMPLEMENTED(); } + +#if SANITIZER_WINDOWS64 +// Exception handler for dealing with shadow memory. +static LONG CALLBACK +ShadowExceptionHandler(PEXCEPTION_POINTERS exception_pointers) { + uptr page_size = GetPageSizeCached(); + // Only handle access violations. + if (exception_pointers->ExceptionRecord->ExceptionCode != + EXCEPTION_ACCESS_VIOLATION || + exception_pointers->ExceptionRecord->NumberParameters < 2) { + __asan_handle_no_return(); + return EXCEPTION_CONTINUE_SEARCH; + } + + // Only handle access violations that land within the shadow memory. + uptr addr = + (uptr)(exception_pointers->ExceptionRecord->ExceptionInformation[1]); + + // Check valid shadow range. + if (!AddrIsInShadow(addr)) { + __asan_handle_no_return(); + return EXCEPTION_CONTINUE_SEARCH; + } + + // This is an access violation while trying to read from the shadow. Commit + // the relevant page and let execution continue. + + // Determine the address of the page that is being accessed. + uptr page = RoundDownTo(addr, page_size); + + // Commit the page. + uptr result = + (uptr)::VirtualAlloc((LPVOID)page, page_size, MEM_COMMIT, PAGE_READWRITE); + if (result != page) + return EXCEPTION_CONTINUE_SEARCH; + + // The page mapping succeeded, so continue execution as usual. + return EXCEPTION_CONTINUE_EXECUTION; +} + +#endif + +void InitializePlatformExceptionHandlers() { +#if SANITIZER_WINDOWS64 + // On Win64, we map memory on demand with access violation handler. + // Install our exception handler. + CHECK(AddVectoredExceptionHandler(TRUE, &ShadowExceptionHandler)); +#endif +} + +bool IsSystemHeapAddress(uptr addr) { + return ::HeapValidate(GetProcessHeap(), 0, (void *)addr) != FALSE; +} + +// We want to install our own exception handler (EH) to print helpful reports +// on access violations and whatnot. Unfortunately, the CRT initializers assume +// they are run before any user code and drop any previously-installed EHs on +// the floor, so we can't install our handler inside __asan_init. +// (See crt0dat.c in the CRT sources for the details) +// +// Things get even more complicated with the dynamic runtime, as it finishes its +// initialization before the .exe module CRT begins to initialize. +// +// For the static runtime (-MT), it's enough to put a callback to +// __asan_set_seh_filter in the last section for C initializers. +// +// For the dynamic runtime (-MD), we want link the same +// asan_dynamic_runtime_thunk.lib to all the modules, thus __asan_set_seh_filter +// will be called for each instrumented module. This ensures that at least one +// __asan_set_seh_filter call happens after the .exe module CRT is initialized. +extern "C" SANITIZER_INTERFACE_ATTRIBUTE int __asan_set_seh_filter() { + // We should only store the previous handler if it's not our own handler in + // order to avoid loops in the EH chain. + auto prev_seh_handler = SetUnhandledExceptionFilter(SEHHandler); + if (prev_seh_handler != &SEHHandler) + default_seh_handler = prev_seh_handler; + return 0; +} + +bool HandleDlopenInit() { + // Not supported on this platform. + static_assert(!SANITIZER_SUPPORTS_INIT_FOR_DLOPEN, + "Expected SANITIZER_SUPPORTS_INIT_FOR_DLOPEN to be false"); + return false; +} + +#if !ASAN_DYNAMIC +// The CRT runs initializers in this order: +// - C initializers, from XIA to XIZ +// - C++ initializers, from XCA to XCZ +// Prior to 2015, the CRT set the unhandled exception filter at priority XIY, +// near the end of C initialization. Starting in 2015, it was moved to the +// beginning of C++ initialization. We set our priority to XCAB to run +// immediately after the CRT runs. This way, our exception filter is called +// first and we can delegate to their filter if appropriate. +#pragma section(".CRT$XCAB", long, read) // NOLINT +__declspec(allocate(".CRT$XCAB")) int (*__intercept_seh)() = + __asan_set_seh_filter; + +// Piggyback on the TLS initialization callback directory to initialize asan as +// early as possible. Initializers in .CRT$XL* are called directly by ntdll, +// which run before the CRT. Users also add code to .CRT$XLC, so it's important +// to run our initializers first. +static void NTAPI asan_thread_init(void *module, DWORD reason, void *reserved) { + if (reason == DLL_PROCESS_ATTACH) + __asan_init(); +} + +#pragma section(".CRT$XLAB", long, read) // NOLINT +__declspec(allocate(".CRT$XLAB")) void(NTAPI *__asan_tls_init)( + void *, unsigned long, void *) = asan_thread_init; +#endif + +static void NTAPI asan_thread_exit(void *module, DWORD reason, void *reserved) { + if (reason == DLL_THREAD_DETACH) { + // Unpoison the thread's stack because the memory may be re-used. + NT_TIB *tib = (NT_TIB *)NtCurrentTeb(); + uptr stackSize = (uptr)tib->StackBase - (uptr)tib->StackLimit; + __asan_unpoison_memory_region(tib->StackLimit, stackSize); + } +} + +#pragma section(".CRT$XLY", long, read) // NOLINT +__declspec(allocate(".CRT$XLY")) void(NTAPI *__asan_tls_exit)( + void *, unsigned long, void *) = asan_thread_exit; + +WIN_FORCE_LINK(__asan_dso_reg_hook) + +// }}} +} // namespace __asan + +#endif // SANITIZER_WINDOWS Index: compiler-rt/test/asan/TestCases/Windows/dll_host.cc =================================================================== --- compiler-rt/test/asan/TestCases/Windows/dll_host.cc +++ compiler-rt/test/asan/TestCases/Windows/dll_host.cc @@ -32,6 +32,10 @@ // IMPORT: __asan_wrap_RaiseException // IMPORT: __asan_wrap_RtlRaiseException // IMPORT: __asan_wrap_SetUnhandledExceptionFilter +// IMPORT: __asan_wrap_RtlSizeHeap +// IMPORT: __asan_wrap_RtlAllocateHeap +// IMPORT: __asan_wrap_RtlReAllocateHeap +// IMPORT: __asan_wrap_RtlFreeHeap // // RUN: cat %t.imports1 %t.imports2 | sort | uniq > %t.imports-sorted // RUN: cat %t.exports1 %t.exports2 | sort | uniq > %t.exports-sorted Index: compiler-rt/test/asan/TestCases/Windows/dll_unload.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/dll_unload.cc @@ -0,0 +1,51 @@ +#include +#include + +// RUN: %clang_cl_asan -LD /Od -DDLL %s -Fe%t.dll +// RUN: %clang_cl /Od -DEXE %s -Fe%te.exe +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true not %run %te.exe %t.dll 2>&1 | FileCheck %s +// REQUIRES: asan-dynamic-runtime +// REQUIRES: asan-32-bits + +#include +#include +#include +extern "C" { +#if defined(EXE) + +int main(int argc, char **argv) { + void *region_without_hooks = HeapAlloc(GetProcessHeap(), 0, 10); + HMODULE lib = LoadLibraryA(argv[1]); + assert(lib != INVALID_HANDLE_VALUE); + + void *region_w_hooks = HeapAlloc(GetProcessHeap(), 0, 10); + assert(region_w_hooks != nullptr); + assert(0 != FreeLibrary(lib)); + + fprintf(stderr, "WITHOUT:0x%08x\n", (unsigned int)region_without_hooks); + fprintf(stderr, "WITH:0x%08x\n", (unsigned int)region_w_hooks); + + assert(0 != HeapFree(GetProcessHeap(), 0, region_without_hooks)); + assert(0 != HeapFree(GetProcessHeap(), 0, region_w_hooks)); + + HeapFree(GetProcessHeap(), 0, region_w_hooks); //will dump +} +#elif defined(DLL) +// This global is registered at startup. + +BOOL WINAPI DllMain(HMODULE, DWORD reason, LPVOID) { + fprintf(stderr, "in DLL(reason=%d)\n", (int)reason); + fflush(0); + return TRUE; +} + +// CHECK: in DLL(reason=1) +// CHECK: in DLL(reason=0) +// CHECK: WITHOUT:[[WITHOUT:0x[0-9a-fA-F]+]] +// CHECK: WITH:[[WITH:0x[0-9a-fA-F]+]] +// CHECK: AddressSanitizer: attempting double-free on [[WITH]] in thread T0: + +#else +#error oops! +#endif +} Index: compiler-rt/test/asan/TestCases/Windows/heapalloc.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/heapalloc.cc @@ -0,0 +1,13 @@ +// XFAIL: asan-64-bits +// RUN: %clang_cl_asan -O0 %s -Fe%t +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true not %run %t 2>&1 | FileCheck %s + +#include + +int main() { + char *buffer; + buffer = (char *)HeapAlloc(GetProcessHeap(), 0, 32), + buffer[33] = 'a'; + // CHECK: AddressSanitizer: heap-buffer-overflow on address [[ADDR:0x[0-9a-f]+]] + // CHECK: WRITE of size 1 at [[ADDR]] thread T0 +} Index: compiler-rt/test/asan/TestCases/Windows/heapalloc_dll_double_free.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/heapalloc_dll_double_free.cc @@ -0,0 +1,40 @@ +#include +#include + +// RUN: %clang_cl_asan -LD /Od -DDLL %s -Fe%t.dll +// RUN: %clang_cl /Od -DEXE %s -Fe%te.exe +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true not %run %te.exe %t.dll 2>&1 | FileCheck %s +// REQUIRES: asan-dynamic-runtime +// REQUIRES: asan-32-bits + +#include +#include +#include +extern "C" { +#if defined(EXE) + +int main(int argc, char **argv) { + void *region_without_hooks = HeapAlloc(GetProcessHeap(), 0, 10); + HMODULE lib = LoadLibraryA(argv[1]); + assert(lib != INVALID_HANDLE_VALUE); + assert(0 != FreeLibrary(lib)); + assert(0 != HeapFree(GetProcessHeap(), 0, region_without_hooks)); + assert(0 != HeapFree(GetProcessHeap(), 0, region_without_hooks)); +} +#elif defined(DLL) +// This global is registered at startup. + +BOOL WINAPI DllMain(HMODULE, DWORD reason, LPVOID) { + fprintf(stderr, "in DLL(reason=%d)\n", (int)reason); + fflush(0); + return TRUE; +} + +// CHECK: in DLL(reason=1) +// CHECK: in DLL(reason=0) +// CHECK: AddressSanitizer: nested bug in the same thread, aborting. + +#else +#error oops! +#endif +} Index: compiler-rt/test/asan/TestCases/Windows/heapalloc_dll_unload_realloc_uaf.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/heapalloc_dll_unload_realloc_uaf.cc @@ -0,0 +1,40 @@ +#include +#include + +// RUN: %clang_cl_asan -LD /Od -DDLL %s -Fe%t.dll +// RUN: %clang_cl /Od -DEXE %s -Fe%te.exe +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true not %run %te.exe %t.dll 2>&1 | FileCheck %s +// REQUIRES: asan-dynamic-runtime +// REQUIRES: asan-32-bits + +#include +#include +#include +extern "C" { +#if defined(EXE) + +int main(int argc, char **argv) { + void *region_without_hooks = HeapAlloc(GetProcessHeap(), 0, 10); + HMODULE lib = LoadLibraryA(argv[1]); + assert(lib != INVALID_HANDLE_VALUE); + assert(0 != FreeLibrary(lib)); + assert(0 != HeapFree(GetProcessHeap(), 0, region_without_hooks)); + HeapReAlloc(GetProcessHeap(), 0, region_without_hooks, 100); //should throw nested error +} +#elif defined(DLL) +// This global is registered at startup. + +BOOL WINAPI DllMain(HMODULE, DWORD reason, LPVOID) { + fprintf(stderr, "in DLL(reason=%d)\n", (int)reason); + fflush(0); + return TRUE; +} + +// CHECK: in DLL(reason=1) +// CHECK: in DLL(reason=0) +// CHECK: AddressSanitizer: nested bug in the same thread, aborting. + +#else +#error oops! +#endif +} Index: compiler-rt/test/asan/TestCases/Windows/heapalloc_doublefree.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/heapalloc_doublefree.cc @@ -0,0 +1,16 @@ +// RUN: %clang_cl_asan -O0 %s -Fe%t +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true not %run %t 2>&1 | FileCheck %s +// XFAIL: asan-64-bits +#include +#include + +int main() { + void *allocation = HeapAlloc(GetProcessHeap(), 0, 10); + assert(allocation != 0); + assert(HeapFree(GetProcessHeap(), 0, allocation)); + HeapFree(GetProcessHeap(), 0, allocation); //will dump + assert(0 && "HeapFree double free should produce an ASAN dump\n"); + return 0; +} + +// CHECK: AddressSanitizer: attempting double-free on [[addr:0x[0-9a-fA-F]+]] in thread T0: \ No newline at end of file Index: compiler-rt/test/asan/TestCases/Windows/heapalloc_flags_fallback.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/heapalloc_flags_fallback.cc @@ -0,0 +1,20 @@ +// RUN: %clang_cl_asan -O0 %s -Fe%t +// RUN: %run %t 2>&1 | FileCheck %s +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true %run %t 2>&1 | FileCheck %s +// XFAIL: asan-64-bits +#include +#include +#include + +extern "C" int +__sanitizer_get_ownership(const volatile void *p); + +int main() { + char *buffer; + buffer = (char *)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, 32); + buffer[0] = 'a'; + assert(!__sanitizer_get_ownership(buffer)); + HeapFree(GetProcessHeap(), 0, buffer); + puts("Okay"); + // CHECK: Okay +} Index: compiler-rt/test/asan/TestCases/Windows/heapalloc_huge.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/heapalloc_huge.cc @@ -0,0 +1,9 @@ +// RUN: %clang_cl_asan -O0 %s -Fe%t +// RUN: %env_asan_opts=allocator_may_return_null=true %run %t +// RUN: %env_asan_opts=allocator_may_return_null=true:windows_hook_rtl_allocators=true %run %t +// XFAIL: asan-64-bits +#include +int main() { + void *nope = HeapAlloc(GetProcessHeap(), 0, ((size_t)0) - 1); + return (nope == nullptr) ? 0 : 1; +} \ No newline at end of file Index: compiler-rt/test/asan/TestCases/Windows/heapalloc_rtl_transfer.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/heapalloc_rtl_transfer.cc @@ -0,0 +1,98 @@ +#include "sanitizer\allocator_interface.h" +#include +#include +#include + +// RUN: %clang_cl_asan %s -o%t +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true %run %t 2>&1 | FileCheck %s +// XFAIL: asan-64-bits + +using AllocateFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, SIZE_T); +using ReAllocateFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, PVOID, SIZE_T); + +using FreeFunctionPtr = PVOID(__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"); + if (RtlAllocateHeap_ptr == 0) { + puts("Couldn't find RtlAllocateHeap"); + return -1; + } + + auto RtlReAllocateHeap_ptr = (ReAllocateFunctionPtr)GetProcAddress(NtDllHandle, "RtlReAllocateHeap"); + if (RtlReAllocateHeap_ptr == 0) { + puts("Couldn't find RtlReAllocateHeap"); + return -1; + } + + //owned by rtl + void *alloc = RtlAllocateHeap_ptr(GetProcessHeap(), + HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY, 100); + assert(alloc); + for (int i = 0; i < 100; i++) { + assert(((char *)alloc)[i] == 0); + ((char *)alloc)[i] = '\xcc'; + } + + // still owned by rtl + alloc = RtlReAllocateHeap_ptr(GetProcessHeap(), + HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY, alloc, 500); + assert(alloc && !__sanitizer_get_ownership(alloc) && HeapValidate(GetProcessHeap(), 0, alloc)); + for (int i = 0; i < 100; i++) { + assert(((char *)alloc)[i] == '\xcc'); + } + for (int i = 100; i < 500; i++) { + assert(((char *)alloc)[i] == 0); + ((char *)alloc)[i] = '\xcc'; + } + + //convert to asan owned + void *realloc = RtlReAllocateHeap_ptr(GetProcessHeap(), + HEAP_ZERO_MEMORY, alloc, 600); + alloc = nullptr; + assert(realloc && __sanitizer_get_ownership(realloc)); + + for (int i = 0; i < 500; i++) { + assert(((char *)realloc)[i] == '\xcc'); + } + for (int i = 500; i < 600; i++) { + assert(((char *)realloc)[i] == 0); + ((char *)realloc)[i] = '\xcc'; + } + realloc = RtlReAllocateHeap_ptr(GetProcessHeap(), + HEAP_ZERO_MEMORY, realloc, 2048); + assert(realloc && __sanitizer_get_ownership(realloc)); + + for (int i = 0; i < 600; i++) { + assert(((char *)realloc)[i] == '\xcc'); + } + for (int i = 600; i < 2048; i++) { + assert(((char *)realloc)[i] == 0); + ((char *)realloc)[i] = '\xcc'; + } + //convert back to rtl owned; + alloc = RtlReAllocateHeap_ptr(GetProcessHeap(), + HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, realloc, 100); + assert(alloc && !__sanitizer_get_ownership(alloc) && HeapValidate(GetProcessHeap(), 0, alloc)); + for (int i = 0; i < 100; i++) { + assert(((char *)alloc)[i] == '\xcc'); + ((char *)alloc)[i] = 0; + } + + auto usable_size = HeapSize(GetProcessHeap(), 0, alloc); + for (int i = 100; i < usable_size; i++) { + assert(((char *)alloc)[i] == 0); + } + + printf("Success\n"); +} + +// CHECK-NOT: Assertion failed: +// CHECK-NOT: AddressSanitizer +// CHECK: Success \ No newline at end of file Index: compiler-rt/test/asan/TestCases/Windows/heapalloc_sanity.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/heapalloc_sanity.cc @@ -0,0 +1,13 @@ +// RUN: %clang_cl_asan -O0 %s -Fe%t +// RUN: %run %t 2>&1 | FileCheck %s +#include +#include + +int main() { + char *buffer; + buffer = (char *)HeapAlloc(GetProcessHeap(), 0, 32), + buffer[0] = 'a'; + HeapFree(GetProcessHeap(), 0, buffer); + puts("Okay"); + // CHECK: Okay +} Index: compiler-rt/test/asan/TestCases/Windows/heapalloc_transfer.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/heapalloc_transfer.cc @@ -0,0 +1,28 @@ +#include "sanitizer\allocator_interface.h" +#include +#include +#include +// RUN: %clang_cl_asan %s -o%t +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true %run %t 2>&1 | FileCheck %s +// XFAIL: asan-64-bits + +int main() { + //owned by rtl + void *alloc = HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, 100); + assert(alloc); + // still owned by rtl + alloc = HeapReAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, alloc, 100); + assert(alloc && !__sanitizer_get_ownership(alloc) && HeapValidate(GetProcessHeap(), 0, alloc)); + //convert to asan owned + void *realloc = HeapReAlloc(GetProcessHeap(), 0, alloc, 500); + alloc = nullptr; + assert(realloc && __sanitizer_get_ownership(realloc)); + //convert back to rtl owned; + alloc = HeapReAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, realloc, 100); + assert(alloc && !__sanitizer_get_ownership(alloc) && HeapValidate(GetProcessHeap(), 0, alloc)); + printf("Success\n"); +} + +// CHECK-NOT: assert +// CHECK-NOT: AddressSanitizer +// CHECK: Success \ No newline at end of file Index: compiler-rt/test/asan/TestCases/Windows/heapalloc_uaf.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/heapalloc_uaf.cc @@ -0,0 +1,13 @@ +// RUN: %clang_cl_asan -O0 %s -Fe%t +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true not %run %t 2>&1 | FileCheck %s +// XFAIL: asan-64-bits +#include + +int main() { + char *buffer; + buffer = (char *)HeapAlloc(GetProcessHeap(), 0, 32), + HeapFree(GetProcessHeap(), 0, buffer); + buffer[0] = 'a'; + // CHECK: AddressSanitizer: heap-use-after-free on address [[ADDR:0x[0-9a-f]+]] + // CHECK: WRITE of size 1 at [[ADDR]] thread T0 +} Index: compiler-rt/test/asan/TestCases/Windows/heapalloc_zero_size.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/heapalloc_zero_size.cc @@ -0,0 +1,23 @@ +// RUN: %clang_cl_asan /Od -o %t %s +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true %run %t 2>&1 | FileCheck %s +// RUN: %env_asan_opts=windows_hook_rtl_allocators=false %run %t 2>&1 | FileCheck %s +// RUN: %clang_cl /Od -o %t %s +// RUN: %run %t 2>&1 | FileCheck %s +// XFAIL: asan-64-bits +#include +#include +#include + +int main() { + HANDLE heap = HeapCreate(0, 0, 0); + void *ptr = HeapAlloc(heap, 0, 4); + assert(ptr); + void *ptr2 = HeapReAlloc(heap, 0, ptr, 0); + assert(ptr2); + HeapFree(heap, 0, ptr2); + fprintf(stderr, "passed!\n"); +} + +// CHECK-NOT: double-free +// CHECK-NOT: AddressSanitizer +// CHECK: passed! Index: compiler-rt/test/asan/TestCases/Windows/heaprealloc.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/heaprealloc.cc @@ -0,0 +1,23 @@ +// RUN: %clang_cl_asan -O0 %s -Fe%t +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true not %run %t 2>&1 | FileCheck %s +// XFAIL: asan-64-bits +#include +#include + +int main() { + char *oldbuf; + size_t sz = 8; + HANDLE procHeap = GetProcessHeap(); + oldbuf = (char *)HeapAlloc(procHeap, 0, sz); + char *newbuf = oldbuf; + while (oldbuf == newbuf) { + sz *= 2; + newbuf = (char *)HeapReAlloc(procHeap, 0, oldbuf, sz); + } + + newbuf[0] = 'a'; + oldbuf[0] = 'a'; + // CHECK: AddressSanitizer: heap-use-after-free on address [[ADDR:0x[0-9a-f]+]] + // CHECK: WRITE of size 1 at [[WRITE2:0x[0-9a-f]+]] thread T0 + // CHECK: #0 {{0x[0-9a-f]+ in main.*}}:[[@LINE-3]] +} Index: compiler-rt/test/asan/TestCases/Windows/heaprealloc_alloc_zero.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/heaprealloc_alloc_zero.cc @@ -0,0 +1,58 @@ +// RUN: %clang_cl_asan /Od /MT -o %t %s +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true %run %t 2>&1 | FileCheck %s +// XFAIL: asan-64-bits +#include +#include +#include + +int main() { + void *ptr = malloc(0); + if (ptr) + std::cerr << "allocated!\n"; + ((char *)ptr)[0] = '\xff'; //check this 'allocate 1 instead of 0' hack hasn't changed + + free(ptr); + + /* + HeapAlloc hack for our asan interceptor is to change 0 + sized allocations to size 1 to avoid weird inconsistencies + between how realloc and heaprealloc handle 0 size allocations. + + Note this test relies on these instructions being intercepted. + Without ASAN HeapRealloc on line 27 would return a ptr whose + HeapSize would be 0. This test makes sure that the underlying behavior + of our hack hasn't changed underneath us. + + We can get rid of the test (or change it to test for the correct + behavior) once we fix the interceptor or write a different allocator + to handle 0 sized allocations properly by default. + + */ + ptr = HeapAlloc(GetProcessHeap(), 0, 0); + if (!ptr) + return 1; + void *ptr2 = HeapReAlloc(GetProcessHeap(), 0, ptr, 0); + 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"; + return 1; + } + void *ptr3 = HeapReAlloc(GetProcessHeap(), 0, ptr2, 3); + if (!ptr3) + return 1; + heapsize = HeapSize(GetProcessHeap(), 0, ptr3); + + if (heapsize != 3) { + std::cerr << "HeapAlloc size failure! " << heapsize << " != 3\n"; + return 1; + } + HeapFree(GetProcessHeap(), 0, ptr3); + return 0; +} + +// CHECK: allocated! +// CHECK-NOT: heap-buffer-overflow +// CHECK-NOT: AddressSanitizer +// CHECK-NOT: HeapAlloc size failure! \ No newline at end of file Index: compiler-rt/test/asan/TestCases/Windows/heaprealloc_zero_size.cc =================================================================== --- compiler-rt/test/asan/TestCases/Windows/heaprealloc_zero_size.cc +++ compiler-rt/test/asan/TestCases/Windows/heaprealloc_zero_size.cc @@ -1,11 +1,13 @@ // RUN: %clang_cl_asan /Od -o %t %s -// RUN: %run %t 2>&1 | FileCheck %s +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true %run %t 2>&1 | FileCheck %s +// RUN: %env_asan_opts=windows_hook_rtl_allocators=false %run %t 2>&1 | FileCheck %s // RUN: %clang_cl /Od -o %t %s // RUN: %run %t 2>&1 | FileCheck %s +// XFAIL: asan-64-bits #include #include #include int main() { HANDLE heap = HeapCreate(0, 0, 0); void *ptr = HeapAlloc(heap, 0, 4); Index: compiler-rt/test/asan/TestCases/Windows/queue_user_work_item_report.cc =================================================================== --- compiler-rt/test/asan/TestCases/Windows/queue_user_work_item_report.cc +++ compiler-rt/test/asan/TestCases/Windows/queue_user_work_item_report.cc @@ -9,13 +9,13 @@ int subscript = -1; volatile char stack_buffer[42]; stack_buffer[subscript] = 42; -// CHECK: AddressSanitizer: stack-buffer-underflow on address [[ADDR:0x[0-9a-f]+]] -// CHECK: WRITE of size 1 at [[ADDR]] thread T1 -// CHECK: {{#0 .* work_item.*queue_user_work_item_report.cc}}:[[@LINE-3]] + // CHECK: AddressSanitizer: stack-buffer-underflow on address [[ADDR:0x[0-9a-f]+]] + // CHECK: WRITE of size 1 at [[ADDR]] thread T{{[0-9]+}} + // CHECK: {{#0 .* work_item.*queue_user_work_item_report.cc}}:[[@LINE-3]] SetEvent(done); return 0; } int main(int argc, char **argv) { done = CreateEvent(0, false, false, "job is done"); if (!done) Index: compiler-rt/test/asan/TestCases/Windows/rtlallocateheap.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/rtlallocateheap.cc @@ -0,0 +1,30 @@ +// RUN: %clang_cl_asan -O0 %s -Fe%t /MD +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true not %run %t 2>&1 | FileCheck %s +// XFAIL: asan-64-bits +// REQUIRES: asan-rtl-heap-interception + +#include +#include + +using AllocateFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, SIZE_T); +using FreeFunctionPtr = PVOID(__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"); + if (RtlAllocateHeap_ptr == 0) { + puts("Couldn't RtlAllocateHeap"); + return -1; + } + + char *buffer; + buffer = (char *)RtlAllocateHeap_ptr(GetProcessHeap(), 0, 32), + buffer[33] = 'a'; + // CHECK: AddressSanitizer: heap-buffer-overflow on address [[ADDR:0x[0-9a-f]+]] + // CHECK: WRITE of size 1 at [[ADDR]] thread T0 +} Index: compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_dll_unload_double_free.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_dll_unload_double_free.cc @@ -0,0 +1,72 @@ +// RUN: %clang_cl_asan -LD /Od -DDLL %s -Fe%t.dll +// RUN: %clang_cl /Od -DEXE %s -Fe%te.exe +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true not %run %te.exe %t.dll 2>&1 | FileCheck %s +// REQUIRES: asan-dynamic-runtime +// REQUIRES: asan-32-bits +// REQUIRES: asan-rtl-heap-interception + +#include +#include +#include + +extern "C" { +#if defined(EXE) +using AllocateFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, SIZE_T); +using FreeFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, PVOID); + +int main(int argc, char **argv) { + HMODULE NtDllHandle = GetModuleHandle("ntdll.dll"); + if (!NtDllHandle) { + puts("Couldn't load ntdll??"); + return -1; + } + + auto RtlAllocateHeap_ptr = + (AllocateFunctionPtr)GetProcAddress(NtDllHandle, "RtlAllocateHeap"); + if (RtlAllocateHeap_ptr == 0) { + puts("Couldn't RtlAllocateHeap"); + return -1; + } + + auto RtlFreeHeap_ptr = + (FreeFunctionPtr)GetProcAddress(NtDllHandle, "RtlFreeHeap"); + if (RtlFreeHeap_ptr == 0) { + puts("Couldn't get RtlFreeHeap"); + return -1; + } + + char *buffer; + buffer = (char *)RtlAllocateHeap_ptr(GetProcessHeap(), 0, 32); + + HMODULE lib = LoadLibraryA(argv[1]); + assert(lib != INVALID_HANDLE_VALUE); + assert(0 != FreeLibrary(lib)); + + if (!RtlFreeHeap_ptr(GetProcessHeap(), 0, buffer)) { + puts("Couldn't RtlFreeHeap"); + return -1; + } + // Because this pointer was allocated pre-hooking, + // this will dump as a nested bug. Asan attempts to free + // the pointer and AV's, so the ASAN exception handler + // will dump as a 'nested bug'. + RtlFreeHeap_ptr(GetProcessHeap(), 0, buffer); +} + +#elif defined(DLL) +// This global is registered at startup. + +BOOL WINAPI DllMain(HMODULE, DWORD reason, LPVOID) { + fprintf(stderr, "in DLL(reason=%d)\n", (int)reason); + fflush(0); + return TRUE; +} + +// CHECK: in DLL(reason=1) +// CHECK: in DLL(reason=0) +// CHECK: AddressSanitizer: nested bug in the same thread, aborting. + +#else +#error oops! +#endif +} Index: compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_dll_unload_realloc.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_dll_unload_realloc.cc @@ -0,0 +1,76 @@ +// RUN: %clang_cl_asan -LD /Od -DDLL %s -Fe%t.dll +// RUN: %clang_cl /Od -DEXE %s -Fe%te.exe +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true not %run %te.exe %t.dll 2>&1 | FileCheck %s +// REQUIRES: asan-dynamic-runtime +// REQUIRES: asan-32-bits +// REQUIRES: asan-rtl-heap-interception + +#include +#include +#include + +extern "C" { +#if defined(EXE) +using AllocateFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, SIZE_T); +using FreeFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, PVOID); +using RtlReAllocateHeapPtr = PVOID(__stdcall *)(PVOID, ULONG, PVOID, SIZE_T); + +int main(int argc, char **argv) { + HMODULE NtDllHandle = GetModuleHandle("ntdll.dll"); + if (!NtDllHandle) { + puts("Couldn't load ntdll??"); + return -1; + } + + auto RtlAllocateHeap_ptr = + (AllocateFunctionPtr)GetProcAddress(NtDllHandle, "RtlAllocateHeap"); + if (RtlAllocateHeap_ptr == 0) { + puts("Couldn't RtlAllocateHeap"); + return -1; + } + + auto RtlFreeHeap_ptr = + (FreeFunctionPtr)GetProcAddress(NtDllHandle, "RtlFreeHeap"); + if (RtlFreeHeap_ptr == 0) { + puts("Couldn't get RtlFreeHeap"); + return -1; + } + + auto RtlReAllocateHeap_ptr = + (RtlReAllocateHeapPtr)GetProcAddress(NtDllHandle, "RtlReAllocateHeap"); + if (RtlReAllocateHeap_ptr == 0) { + puts("Couldn't get rtlreallocateheap\n"); + return -1; + } + + char *buffer; + buffer = (char *)RtlAllocateHeap_ptr(GetProcessHeap(), 0, 32); + + HMODULE lib = LoadLibraryA(argv[1]); + assert(lib != INVALID_HANDLE_VALUE); + assert(0 != FreeLibrary(lib)); + + if (!RtlFreeHeap_ptr(GetProcessHeap(), 0, buffer)) { + puts("Couldn't RtlFreeHeap"); + return -1; + } + RtlReAllocateHeap_ptr(GetProcessHeap(), 0, buffer, 100); // should dump +} + +#elif defined(DLL) +// This global is registered at startup. + +BOOL WINAPI DllMain(HMODULE, DWORD reason, LPVOID) { + fprintf(stderr, "in DLL(reason=%d)\n", (int)reason); + fflush(0); + return TRUE; +} + +// CHECK: in DLL(reason=1) +// CHECK: in DLL(reason=0) +// CHECK: AddressSanitizer: nested bug in the same thread, aborting. + +#else +#error oops! +#endif +} Index: compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_flags_fallback.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_flags_fallback.cc @@ -0,0 +1,45 @@ +// RUN: %clang_cl_asan -O0 %s -Fe%t /MD +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true %run %t 2>&1 | FileCheck %s +// XFAIL: asan-64-bits +// REQUIRES: asan-rtl-heap-interception + +#include +#include +#include + +extern "C" int __sanitizer_get_ownership(const volatile void *p); +using AllocateFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, SIZE_T); +using FreeFunctionPtr = PVOID(__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"); + if (RtlAllocateHeap_ptr == 0) { + puts("Couldn't RtlAllocateHeap"); + return -1; + } + + auto RtlFreeHeap_ptr = (FreeFunctionPtr)GetProcAddress(NtDllHandle, "RtlFreeHeap"); + if (RtlFreeHeap_ptr == 0) { + puts("Couldn't RtlFreeHeap"); + return -1; + } + + char *winbuf; + char *asanbuf; + winbuf = (char *)RtlAllocateHeap_ptr(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, 32), + asanbuf = (char *)RtlAllocateHeap_ptr(GetProcessHeap(), 0, 32), + winbuf[0] = 'a'; + assert(!__sanitizer_get_ownership(winbuf)); + assert(__sanitizer_get_ownership(asanbuf)); + + RtlFreeHeap_ptr(GetProcessHeap(), 0, winbuf); + RtlFreeHeap_ptr(GetProcessHeap(), 0, asanbuf); + puts("Okay"); + // CHECK: Okay +} Index: compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_zero.cc =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_zero.cc @@ -0,0 +1,68 @@ +// RUN: %clang_cl_asan -O0 %s -Fe%t /MD +// RUN: %env_asan_opts=windows_hook_rtl_allocators=true not %run %t 2>&1 | FileCheck %s +// XFAIL: asan-64-bits +// REQUIRES: asan-rtl-heap-interception + +#include +#include +#include + +using AllocateFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, SIZE_T); +using ReAllocateFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, PVOID, SIZE_T); +using FreeFunctionPtr = PVOID(__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"); + if (RtlAllocateHeap_ptr == 0) { + puts("Couldn't find RtlAllocateHeap"); + return -1; + } + + auto RtlReAllocateHeap_ptr = (ReAllocateFunctionPtr)GetProcAddress(NtDllHandle, "RtlReAllocateHeap"); + if (RtlReAllocateHeap_ptr == 0) { + puts("Couldn't find RtlReAllocateHeap"); + return -1; + } + + char *buffer; + SIZE_T buffer_size = 32; + SIZE_T new_buffer_size = buffer_size * 2; + + buffer = (char *)RtlAllocateHeap_ptr(GetProcessHeap(), HEAP_ZERO_MEMORY, buffer_size); + assert(buffer != nullptr); + // Check that the buffer is zeroed. + for (SIZE_T i = 0; i < buffer_size; ++i) { + assert(buffer[i] == 0); + } + memset(buffer, 0xcc, buffer_size); + + // Zero the newly allocated memory. + buffer = (char *)RtlReAllocateHeap_ptr(GetProcessHeap(), HEAP_ZERO_MEMORY, buffer, new_buffer_size); + assert(buffer != nullptr); + // Check that the first part of the buffer still has the old contents. + for (SIZE_T i = 0; i < buffer_size; ++i) { + assert(buffer[i] == (char)0xcc); + } + // Check that the new part of the buffer is zeroed. + for (SIZE_T i = buffer_size; i < new_buffer_size; ++i) { + assert(buffer[i] == 0x0); + } + + // Shrink the buffer back down. + buffer = (char *)RtlReAllocateHeap_ptr(GetProcessHeap(), HEAP_ZERO_MEMORY, buffer, buffer_size); + assert(buffer != nullptr); + // Check that the first part of the buffer still has the old contents. + for (SIZE_T i = 0; i < buffer_size; ++i) { + assert(buffer[i] == (char)0xcc); + } + + buffer[buffer_size + 1] = 'a'; + // CHECK: AddressSanitizer: heap-buffer-overflow on address [[ADDR:0x[0-9a-f]+]] + // CHECK: WRITE of size 1 at [[ADDR]] thread T0 +}