Path: blob/main/contrib/llvm-project/compiler-rt/lib/asan/asan_malloc_win.cpp
35233 views
//===-- asan_malloc_win.cpp -----------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file is a part of AddressSanitizer, an address sanity checker.9//10// Windows-specific malloc interception.11//===----------------------------------------------------------------------===//1213#include "sanitizer_common/sanitizer_allocator_interface.h"14#include "sanitizer_common/sanitizer_platform.h"15#if SANITIZER_WINDOWS16#include "asan_allocator.h"17#include "asan_interceptors.h"18#include "asan_internal.h"19#include "asan_stack.h"20#include "interception/interception.h"21#include <stddef.h>2223// Intentionally not including windows.h here, to avoid the risk of24// pulling in conflicting declarations of these functions. (With mingw-w64,25// there's a risk of windows.h pulling in stdint.h.)26typedef int BOOL;27typedef void *HANDLE;28typedef const void *LPCVOID;29typedef void *LPVOID;3031typedef unsigned long DWORD;32constexpr unsigned long HEAP_ZERO_MEMORY = 0x00000008;33constexpr unsigned long HEAP_REALLOC_IN_PLACE_ONLY = 0x00000010;34constexpr unsigned long HEAP_ALLOCATE_SUPPORTED_FLAGS = (HEAP_ZERO_MEMORY);35constexpr unsigned long HEAP_ALLOCATE_UNSUPPORTED_FLAGS =36(~HEAP_ALLOCATE_SUPPORTED_FLAGS);37constexpr unsigned long HEAP_FREE_UNSUPPORTED_FLAGS =38(~HEAP_ALLOCATE_SUPPORTED_FLAGS);39constexpr unsigned long HEAP_REALLOC_UNSUPPORTED_FLAGS =40(~HEAP_ALLOCATE_SUPPORTED_FLAGS);414243extern "C" {44LPVOID WINAPI HeapAlloc(HANDLE hHeap, DWORD dwFlags, size_t dwBytes);45LPVOID WINAPI HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem,46size_t dwBytes);47BOOL WINAPI HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem);48size_t WINAPI HeapSize(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem);4950BOOL WINAPI HeapValidate(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem);51}5253using namespace __asan;5455// MT: Simply defining functions with the same signature in *.obj56// files overrides the standard functions in the CRT.57// MD: Memory allocation functions are defined in the CRT .dll,58// so we have to intercept them before they are called for the first time.5960#if ASAN_DYNAMIC61# define ALLOCATION_FUNCTION_ATTRIBUTE62#else63# define ALLOCATION_FUNCTION_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE64#endif6566extern "C" {67ALLOCATION_FUNCTION_ATTRIBUTE68size_t _msize(void *ptr) {69GET_CURRENT_PC_BP_SP;70(void)sp;71return asan_malloc_usable_size(ptr, pc, bp);72}7374ALLOCATION_FUNCTION_ATTRIBUTE75size_t _msize_base(void *ptr) {76return _msize(ptr);77}7879ALLOCATION_FUNCTION_ATTRIBUTE80void free(void *ptr) {81GET_STACK_TRACE_FREE;82return asan_free(ptr, &stack, FROM_MALLOC);83}8485ALLOCATION_FUNCTION_ATTRIBUTE86void _free_dbg(void *ptr, int) {87free(ptr);88}8990ALLOCATION_FUNCTION_ATTRIBUTE91void _free_base(void *ptr) {92free(ptr);93}9495ALLOCATION_FUNCTION_ATTRIBUTE96void *malloc(size_t size) {97GET_STACK_TRACE_MALLOC;98return asan_malloc(size, &stack);99}100101ALLOCATION_FUNCTION_ATTRIBUTE102void *_malloc_base(size_t size) {103return malloc(size);104}105106ALLOCATION_FUNCTION_ATTRIBUTE107void *_malloc_dbg(size_t size, int, const char *, int) {108return malloc(size);109}110111ALLOCATION_FUNCTION_ATTRIBUTE112void *calloc(size_t nmemb, size_t size) {113GET_STACK_TRACE_MALLOC;114return asan_calloc(nmemb, size, &stack);115}116117ALLOCATION_FUNCTION_ATTRIBUTE118void *_calloc_base(size_t nmemb, size_t size) {119return calloc(nmemb, size);120}121122ALLOCATION_FUNCTION_ATTRIBUTE123void *_calloc_dbg(size_t nmemb, size_t size, int, const char *, int) {124return calloc(nmemb, size);125}126127ALLOCATION_FUNCTION_ATTRIBUTE128void *_calloc_impl(size_t nmemb, size_t size, int *errno_tmp) {129return calloc(nmemb, size);130}131132ALLOCATION_FUNCTION_ATTRIBUTE133void *realloc(void *ptr, size_t size) {134GET_STACK_TRACE_MALLOC;135return asan_realloc(ptr, size, &stack);136}137138ALLOCATION_FUNCTION_ATTRIBUTE139void *_realloc_dbg(void *ptr, size_t size, int) {140UNREACHABLE("_realloc_dbg should not exist!");141return 0;142}143144ALLOCATION_FUNCTION_ATTRIBUTE145void *_realloc_base(void *ptr, size_t size) {146return realloc(ptr, size);147}148149ALLOCATION_FUNCTION_ATTRIBUTE150void *_recalloc(void *p, size_t n, size_t elem_size) {151if (!p)152return calloc(n, elem_size);153const size_t size = n * elem_size;154if (elem_size != 0 && size / elem_size != n)155return 0;156157size_t old_size = _msize(p);158void *new_alloc = malloc(size);159if (new_alloc) {160REAL(memcpy)(new_alloc, p, Min<size_t>(size, old_size));161if (old_size < size)162REAL(memset)(((u8 *)new_alloc) + old_size, 0, size - old_size);163free(p);164}165return new_alloc;166}167168ALLOCATION_FUNCTION_ATTRIBUTE169void *_recalloc_base(void *p, size_t n, size_t elem_size) {170return _recalloc(p, n, elem_size);171}172173ALLOCATION_FUNCTION_ATTRIBUTE174void *_expand(void *memblock, size_t size) {175// _expand is used in realloc-like functions to resize the buffer if possible.176// We don't want memory to stand still while resizing buffers, so return 0.177return 0;178}179180ALLOCATION_FUNCTION_ATTRIBUTE181void *_expand_dbg(void *memblock, size_t size) {182return _expand(memblock, size);183}184185// TODO(timurrrr): Might want to add support for _aligned_* allocation186// functions to detect a bit more bugs. Those functions seem to wrap malloc().187188int _CrtDbgReport(int, const char*, int,189const char*, const char*, ...) {190ShowStatsAndAbort();191}192193int _CrtDbgReportW(int reportType, const wchar_t*, int,194const wchar_t*, const wchar_t*, ...) {195ShowStatsAndAbort();196}197198int _CrtSetReportMode(int, int) {199return 0;200}201} // extern "C"202203#define OWNED_BY_RTL(heap, memory) \204(!__sanitizer_get_ownership(memory) && HeapValidate(heap, 0, memory))205206INTERCEPTOR_WINAPI(size_t, HeapSize, HANDLE hHeap, DWORD dwFlags,207LPCVOID lpMem) {208// If the RTL allocators are hooked we need to check whether the ASAN209// allocator owns the pointer we're about to use. Allocations occur before210// interception takes place, so if it is not owned by the RTL heap we can211// pass it to the ASAN heap for inspection.212if (flags()->windows_hook_rtl_allocators) {213if (!AsanInited() || OWNED_BY_RTL(hHeap, lpMem))214return REAL(HeapSize)(hHeap, dwFlags, lpMem);215} else {216CHECK(dwFlags == 0 && "unsupported heap flags");217}218GET_CURRENT_PC_BP_SP;219(void)sp;220return asan_malloc_usable_size(lpMem, pc, bp);221}222223INTERCEPTOR_WINAPI(LPVOID, HeapAlloc, HANDLE hHeap, DWORD dwFlags,224size_t dwBytes) {225// If the ASAN runtime is not initialized, or we encounter an unsupported226// flag, fall back to the original allocator.227if (flags()->windows_hook_rtl_allocators) {228if (UNLIKELY(!AsanInited() ||229(dwFlags & HEAP_ALLOCATE_UNSUPPORTED_FLAGS) != 0)) {230return REAL(HeapAlloc)(hHeap, dwFlags, dwBytes);231}232} else {233// In the case that we don't hook the rtl allocators,234// this becomes an assert since there is no failover to the original235// allocator.236CHECK((HEAP_ALLOCATE_UNSUPPORTED_FLAGS & dwFlags) != 0 &&237"unsupported flags");238}239GET_STACK_TRACE_MALLOC;240void *p = asan_malloc(dwBytes, &stack);241// Reading MSDN suggests that the *entire* usable allocation is zeroed out.242// Otherwise it is difficult to HeapReAlloc with HEAP_ZERO_MEMORY.243// https://blogs.msdn.microsoft.com/oldnewthing/20120316-00/?p=8083244if (p && (dwFlags & HEAP_ZERO_MEMORY)) {245GET_CURRENT_PC_BP_SP;246(void)sp;247auto usable_size = asan_malloc_usable_size(p, pc, bp);248internal_memset(p, 0, usable_size);249}250return p;251}252253INTERCEPTOR_WINAPI(BOOL, HeapFree, HANDLE hHeap, DWORD dwFlags, LPVOID lpMem) {254// Heap allocations happen before this function is hooked, so we must fall255// back to the original function if the pointer is not from the ASAN heap,256// or unsupported flags are provided.257if (flags()->windows_hook_rtl_allocators) {258if (OWNED_BY_RTL(hHeap, lpMem))259return REAL(HeapFree)(hHeap, dwFlags, lpMem);260} else {261CHECK((HEAP_FREE_UNSUPPORTED_FLAGS & dwFlags) != 0 && "unsupported flags");262}263GET_STACK_TRACE_FREE;264asan_free(lpMem, &stack, FROM_MALLOC);265return true;266}267268namespace __asan {269using AllocFunction = LPVOID(WINAPI *)(HANDLE, DWORD, size_t);270using ReAllocFunction = LPVOID(WINAPI *)(HANDLE, DWORD, LPVOID, size_t);271using SizeFunction = size_t(WINAPI *)(HANDLE, DWORD, LPVOID);272using FreeFunction = BOOL(WINAPI *)(HANDLE, DWORD, LPVOID);273274void *SharedReAlloc(ReAllocFunction reallocFunc, SizeFunction heapSizeFunc,275FreeFunction freeFunc, AllocFunction allocFunc,276HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, size_t dwBytes) {277CHECK(reallocFunc && heapSizeFunc && freeFunc && allocFunc);278GET_STACK_TRACE_MALLOC;279GET_CURRENT_PC_BP_SP;280(void)sp;281if (flags()->windows_hook_rtl_allocators) {282enum AllocationOwnership { NEITHER = 0, ASAN = 1, RTL = 2 };283AllocationOwnership ownershipState;284bool owned_rtlalloc = false;285bool owned_asan = __sanitizer_get_ownership(lpMem);286287if (!owned_asan)288owned_rtlalloc = HeapValidate(hHeap, 0, lpMem);289290if (owned_asan && !owned_rtlalloc)291ownershipState = ASAN;292else if (!owned_asan && owned_rtlalloc)293ownershipState = RTL;294else if (!owned_asan && !owned_rtlalloc)295ownershipState = NEITHER;296297// If this heap block which was allocated before the ASAN298// runtime came up, use the real HeapFree function.299if (UNLIKELY(!AsanInited())) {300return reallocFunc(hHeap, dwFlags, lpMem, dwBytes);301}302bool only_asan_supported_flags =303(HEAP_REALLOC_UNSUPPORTED_FLAGS & dwFlags) == 0;304305if (ownershipState == RTL ||306(ownershipState == NEITHER && !only_asan_supported_flags)) {307if (only_asan_supported_flags) {308// if this is a conversion to ASAN upported flags, transfer this309// allocation to the ASAN allocator310void *replacement_alloc;311if (dwFlags & HEAP_ZERO_MEMORY)312replacement_alloc = asan_calloc(1, dwBytes, &stack);313else314replacement_alloc = asan_malloc(dwBytes, &stack);315if (replacement_alloc) {316size_t old_size = heapSizeFunc(hHeap, dwFlags, lpMem);317if (old_size == ((size_t)0) - 1) {318asan_free(replacement_alloc, &stack, FROM_MALLOC);319return nullptr;320}321REAL(memcpy)(replacement_alloc, lpMem, old_size);322freeFunc(hHeap, dwFlags, lpMem);323}324return replacement_alloc;325} else {326// owned by rtl or neither with unsupported ASAN flags,327// just pass back to original allocator328CHECK(ownershipState == RTL || ownershipState == NEITHER);329CHECK(!only_asan_supported_flags);330return reallocFunc(hHeap, dwFlags, lpMem, dwBytes);331}332}333334if (ownershipState == ASAN && !only_asan_supported_flags) {335// Conversion to unsupported flags allocation,336// transfer this allocation back to the original allocator.337void *replacement_alloc = allocFunc(hHeap, dwFlags, dwBytes);338size_t old_usable_size = 0;339if (replacement_alloc) {340old_usable_size = asan_malloc_usable_size(lpMem, pc, bp);341REAL(memcpy)(replacement_alloc, lpMem,342Min<size_t>(dwBytes, old_usable_size));343asan_free(lpMem, &stack, FROM_MALLOC);344}345return replacement_alloc;346}347348CHECK((ownershipState == ASAN || ownershipState == NEITHER) &&349only_asan_supported_flags);350// At this point we should either be ASAN owned with ASAN supported flags351// or we owned by neither and have supported flags.352// Pass through even when it's neither since this could be a null realloc or353// UAF that ASAN needs to catch.354} else {355CHECK((HEAP_REALLOC_UNSUPPORTED_FLAGS & dwFlags) != 0 &&356"unsupported flags");357}358// asan_realloc will never reallocate in place, so for now this flag is359// unsupported until we figure out a way to fake this.360if (dwFlags & HEAP_REALLOC_IN_PLACE_ONLY)361return nullptr;362363// HeapReAlloc and HeapAlloc both happily accept 0 sized allocations.364// passing a 0 size into asan_realloc will free the allocation.365// To avoid this and keep behavior consistent, fudge the size if 0.366// (asan_malloc already does this)367if (dwBytes == 0)368dwBytes = 1;369370size_t old_size;371if (dwFlags & HEAP_ZERO_MEMORY)372old_size = asan_malloc_usable_size(lpMem, pc, bp);373374void *ptr = asan_realloc(lpMem, dwBytes, &stack);375if (ptr == nullptr)376return nullptr;377378if (dwFlags & HEAP_ZERO_MEMORY) {379size_t new_size = asan_malloc_usable_size(ptr, pc, bp);380if (old_size < new_size)381REAL(memset)(((u8 *)ptr) + old_size, 0, new_size - old_size);382}383384return ptr;385}386} // namespace __asan387388INTERCEPTOR_WINAPI(LPVOID, HeapReAlloc, HANDLE hHeap, DWORD dwFlags,389LPVOID lpMem, size_t dwBytes) {390return SharedReAlloc(REAL(HeapReAlloc), (SizeFunction)REAL(HeapSize),391REAL(HeapFree), REAL(HeapAlloc), hHeap, dwFlags, lpMem,392dwBytes);393}394395// The following functions are undocumented and subject to change.396// However, hooking them is necessary to hook Windows heap397// allocations with detours and their definitions are unlikely to change.398// Comments in /minkernel/ntos/rtl/heappublic.c indicate that these functions399// are part of the heap's public interface.400typedef unsigned long LOGICAL;401402// This function is documented as part of the Driver Development Kit but *not*403// the Windows Development Kit.404LOGICAL RtlFreeHeap(void* HeapHandle, DWORD Flags,405void* BaseAddress);406407// This function is documented as part of the Driver Development Kit but *not*408// the Windows Development Kit.409void* RtlAllocateHeap(void* HeapHandle, DWORD Flags, size_t Size);410411// This function is completely undocumented.412void*413RtlReAllocateHeap(void* HeapHandle, DWORD Flags, void* BaseAddress,414size_t Size);415416// This function is completely undocumented.417size_t RtlSizeHeap(void* HeapHandle, DWORD Flags, void* BaseAddress);418419INTERCEPTOR_WINAPI(size_t, RtlSizeHeap, HANDLE HeapHandle, DWORD Flags,420void* BaseAddress) {421if (!flags()->windows_hook_rtl_allocators ||422UNLIKELY(!AsanInited() || OWNED_BY_RTL(HeapHandle, BaseAddress))) {423return REAL(RtlSizeHeap)(HeapHandle, Flags, BaseAddress);424}425GET_CURRENT_PC_BP_SP;426(void)sp;427return asan_malloc_usable_size(BaseAddress, pc, bp);428}429430INTERCEPTOR_WINAPI(BOOL, RtlFreeHeap, HANDLE HeapHandle, DWORD Flags,431void* BaseAddress) {432// Heap allocations happen before this function is hooked, so we must fall433// back to the original function if the pointer is not from the ASAN heap, or434// unsupported flags are provided.435if (!flags()->windows_hook_rtl_allocators ||436UNLIKELY((HEAP_FREE_UNSUPPORTED_FLAGS & Flags) != 0 ||437OWNED_BY_RTL(HeapHandle, BaseAddress))) {438return REAL(RtlFreeHeap)(HeapHandle, Flags, BaseAddress);439}440GET_STACK_TRACE_FREE;441asan_free(BaseAddress, &stack, FROM_MALLOC);442return true;443}444445INTERCEPTOR_WINAPI(void*, RtlAllocateHeap, HANDLE HeapHandle, DWORD Flags,446size_t Size) {447// If the ASAN runtime is not initialized, or we encounter an unsupported448// flag, fall back to the original allocator.449if (!flags()->windows_hook_rtl_allocators ||450UNLIKELY(!AsanInited() ||451(Flags & HEAP_ALLOCATE_UNSUPPORTED_FLAGS) != 0)) {452return REAL(RtlAllocateHeap)(HeapHandle, Flags, Size);453}454GET_STACK_TRACE_MALLOC;455void *p;456// Reading MSDN suggests that the *entire* usable allocation is zeroed out.457// Otherwise it is difficult to HeapReAlloc with HEAP_ZERO_MEMORY.458// https://blogs.msdn.microsoft.com/oldnewthing/20120316-00/?p=8083459if (Flags & HEAP_ZERO_MEMORY) {460p = asan_calloc(Size, 1, &stack);461} else {462p = asan_malloc(Size, &stack);463}464return p;465}466467INTERCEPTOR_WINAPI(void*, RtlReAllocateHeap, HANDLE HeapHandle, DWORD Flags,468void* BaseAddress, size_t Size) {469// If it's actually a heap block which was allocated before the ASAN runtime470// came up, use the real RtlFreeHeap function.471if (!flags()->windows_hook_rtl_allocators)472return REAL(RtlReAllocateHeap)(HeapHandle, Flags, BaseAddress, Size);473474return SharedReAlloc(REAL(RtlReAllocateHeap), REAL(RtlSizeHeap),475REAL(RtlFreeHeap), REAL(RtlAllocateHeap), HeapHandle,476Flags, BaseAddress, Size);477}478479namespace __asan {480481static void TryToOverrideFunction(const char *fname, uptr new_func) {482// Failure here is not fatal. The CRT may not be present, and different CRT483// versions use different symbols.484if (!__interception::OverrideFunction(fname, new_func))485VPrintf(2, "Failed to override function %s\n", fname);486}487488void ReplaceSystemMalloc() {489#if defined(ASAN_DYNAMIC)490TryToOverrideFunction("free", (uptr)free);491TryToOverrideFunction("_free_base", (uptr)free);492TryToOverrideFunction("malloc", (uptr)malloc);493TryToOverrideFunction("_malloc_base", (uptr)malloc);494TryToOverrideFunction("_malloc_crt", (uptr)malloc);495TryToOverrideFunction("calloc", (uptr)calloc);496TryToOverrideFunction("_calloc_base", (uptr)calloc);497TryToOverrideFunction("_calloc_crt", (uptr)calloc);498TryToOverrideFunction("realloc", (uptr)realloc);499TryToOverrideFunction("_realloc_base", (uptr)realloc);500TryToOverrideFunction("_realloc_crt", (uptr)realloc);501TryToOverrideFunction("_recalloc", (uptr)_recalloc);502TryToOverrideFunction("_recalloc_base", (uptr)_recalloc);503TryToOverrideFunction("_recalloc_crt", (uptr)_recalloc);504TryToOverrideFunction("_msize", (uptr)_msize);505TryToOverrideFunction("_msize_base", (uptr)_msize);506TryToOverrideFunction("_expand", (uptr)_expand);507TryToOverrideFunction("_expand_base", (uptr)_expand);508509if (flags()->windows_hook_rtl_allocators) {510ASAN_INTERCEPT_FUNC(HeapSize);511ASAN_INTERCEPT_FUNC(HeapFree);512ASAN_INTERCEPT_FUNC(HeapReAlloc);513ASAN_INTERCEPT_FUNC(HeapAlloc);514515// Undocumented functions must be intercepted by name, not by symbol.516__interception::OverrideFunction("RtlSizeHeap", (uptr)WRAP(RtlSizeHeap),517(uptr *)&REAL(RtlSizeHeap));518__interception::OverrideFunction("RtlFreeHeap", (uptr)WRAP(RtlFreeHeap),519(uptr *)&REAL(RtlFreeHeap));520__interception::OverrideFunction("RtlReAllocateHeap",521(uptr)WRAP(RtlReAllocateHeap),522(uptr *)&REAL(RtlReAllocateHeap));523__interception::OverrideFunction("RtlAllocateHeap",524(uptr)WRAP(RtlAllocateHeap),525(uptr *)&REAL(RtlAllocateHeap));526} else {527#define INTERCEPT_UCRT_FUNCTION(func) \528if (!INTERCEPT_FUNCTION_DLLIMPORT( \529"ucrtbase.dll", "api-ms-win-core-heap-l1-1-0.dll", func)) { \530VPrintf(2, "Failed to intercept ucrtbase.dll import %s\n", #func); \531}532INTERCEPT_UCRT_FUNCTION(HeapAlloc);533INTERCEPT_UCRT_FUNCTION(HeapFree);534INTERCEPT_UCRT_FUNCTION(HeapReAlloc);535INTERCEPT_UCRT_FUNCTION(HeapSize);536#undef INTERCEPT_UCRT_FUNCTION537}538// Recent versions of ucrtbase.dll appear to be built with PGO and LTCG, which539// enable cross-module inlining. This means our _malloc_base hook won't catch540// all CRT allocations. This code here patches the import table of541// ucrtbase.dll so that all attempts to use the lower-level win32 heap542// allocation API will be directed to ASan's heap. We don't currently543// intercept all calls to HeapAlloc. If we did, we would have to check on544// HeapFree whether the pointer came from ASan of from the system.545546#endif // defined(ASAN_DYNAMIC)547}548} // namespace __asan549550#endif // _WIN32551552553