Please use GitHub pull requests for new patches. Phabricator shutdown timeline
Changeset View
Standalone View
compiler-rt/lib/asan/asan_malloc_win.cpp
Show All 22 Lines | |||||
// Intentionally not including windows.h here, to avoid the risk of | // Intentionally not including windows.h here, to avoid the risk of | ||||
// pulling in conflicting declarations of these functions. (With mingw-w64, | // pulling in conflicting declarations of these functions. (With mingw-w64, | ||||
// there's a risk of windows.h pulling in stdint.h.) | // there's a risk of windows.h pulling in stdint.h.) | ||||
typedef int BOOL; | typedef int BOOL; | ||||
typedef void *HANDLE; | typedef void *HANDLE; | ||||
typedef const void *LPCVOID; | typedef const void *LPCVOID; | ||||
typedef void *LPVOID; | typedef void *LPVOID; | ||||
typedef HANDLE HWND; | |||||
typedef HANDLE HGLOBAL; | |||||
typedef HANDLE HLOCAL; | |||||
typedef unsigned int UINT; | |||||
typedef unsigned long DWORD; | typedef unsigned long DWORD; | ||||
constexpr unsigned long HEAP_ZERO_MEMORY = 0x00000008; | constexpr unsigned long HEAP_ZERO_MEMORY = 0x00000008; | ||||
constexpr unsigned long HEAP_REALLOC_IN_PLACE_ONLY = 0x00000010; | constexpr unsigned long HEAP_REALLOC_IN_PLACE_ONLY = 0x00000010; | ||||
constexpr unsigned long HEAP_ALLOCATE_SUPPORTED_FLAGS = (HEAP_ZERO_MEMORY); | constexpr unsigned long HEAP_ALLOCATE_SUPPORTED_FLAGS = (HEAP_ZERO_MEMORY); | ||||
constexpr unsigned long HEAP_ALLOCATE_UNSUPPORTED_FLAGS = | constexpr unsigned long HEAP_ALLOCATE_UNSUPPORTED_FLAGS = | ||||
(~HEAP_ALLOCATE_SUPPORTED_FLAGS); | (~HEAP_ALLOCATE_SUPPORTED_FLAGS); | ||||
constexpr unsigned long HEAP_FREE_UNSUPPORTED_FLAGS = | constexpr unsigned long HEAP_FREE_UNSUPPORTED_FLAGS = | ||||
(~HEAP_ALLOCATE_SUPPORTED_FLAGS); | (~HEAP_ALLOCATE_SUPPORTED_FLAGS); | ||||
constexpr unsigned long HEAP_REALLOC_UNSUPPORTED_FLAGS = | constexpr unsigned long HEAP_REALLOC_UNSUPPORTED_FLAGS = | ||||
(~HEAP_ALLOCATE_SUPPORTED_FLAGS); | (~HEAP_ALLOCATE_SUPPORTED_FLAGS); | ||||
extern "C" { | extern "C" { | ||||
LPVOID WINAPI HeapAlloc(HANDLE hHeap, DWORD dwFlags, size_t dwBytes); | LPVOID WINAPI HeapAlloc(HANDLE hHeap, DWORD dwFlags, size_t dwBytes); | ||||
LPVOID WINAPI HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, | LPVOID WINAPI HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, | ||||
size_t dwBytes); | size_t dwBytes); | ||||
BOOL WINAPI HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem); | BOOL WINAPI HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem); | ||||
size_t WINAPI HeapSize(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem); | size_t WINAPI HeapSize(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem); | ||||
BOOL WINAPI HeapValidate(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem); | BOOL WINAPI HeapValidate(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem); | ||||
HANDLE WINAPI GetProcessHeap(); | |||||
_declspec(dllimport) HGLOBAL WINAPI GlobalAlloc(UINT uFlags, SIZE_T dwBytes); | |||||
rnk: I guess I prefer the spelling `__declspec`, since it's in the implementer's namespace. | |||||
_declspec(dllimport) HGLOBAL WINAPI GlobalFree(HGLOBAL hMem); | |||||
_declspec(dllimport) HGLOBAL WINAPI GlobalSize(HGLOBAL hMem); | |||||
_declspec(dllimport) HGLOBAL WINAPI | |||||
GlobalReAlloc(HGLOBAL hMem, SIZE_T dwBytes, UINT uFlags); | |||||
_declspec(dllimport) HGLOBAL WINAPI GlobalLock(HGLOBAL hMem); | |||||
_declspec(dllimport) HGLOBAL WINAPI GlobalUnlock(HGLOBAL hMem); | |||||
_declspec(dllimport) HLOCAL WINAPI LocalAlloc(UINT uFlags, SIZE_T dwBytes); | |||||
_declspec(dllimport) HLOCAL WINAPI LocalFree(HLOCAL hMem); | |||||
_declspec(dllimport) HLOCAL WINAPI LocalSize(HLOCAL hMem); | |||||
_declspec(dllimport) HLOCAL WINAPI | |||||
LocalReAlloc(HLOCAL hMem, size_t dwBytes, UINT uFlags); | |||||
_declspec(dllimport) HLOCAL WINAPI LocalLock(HLOCAL hMem); | |||||
_declspec(dllimport) HLOCAL WINAPI LocalUnlock(HLOCAL hMem); | |||||
} | } | ||||
using namespace __asan; | using namespace __asan; | ||||
// MT: Simply defining functions with the same signature in *.obj | // MT: Simply defining functions with the same signature in *.obj | ||||
// files overrides the standard functions in the CRT. | // files overrides the standard functions in the CRT. | ||||
// MD: Memory allocation functions are defined in the CRT .dll, | // MD: Memory allocation functions are defined in the CRT .dll, | ||||
// so we have to intercept them before they are called for the first time. | // so we have to intercept them before they are called for the first time. | ||||
#if ASAN_DYNAMIC | #if ASAN_DYNAMIC | ||||
# define ALLOCATION_FUNCTION_ATTRIBUTE | #define ALLOCATION_FUNCTION_ATTRIBUTE | ||||
#else | #else | ||||
# define ALLOCATION_FUNCTION_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE | #define ALLOCATION_FUNCTION_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE | ||||
#endif | #endif | ||||
extern "C" { | extern "C" { | ||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
size_t _msize(void *ptr) { | size_t _msize(void *ptr) { | ||||
GET_CURRENT_PC_BP_SP; | GET_CURRENT_PC_BP_SP; | ||||
(void)sp; | (void)sp; | ||||
return asan_malloc_usable_size(ptr, pc, bp); | return asan_malloc_usable_size(ptr, pc, bp); | ||||
} | } | ||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
size_t _msize_base(void *ptr) { | size_t _msize_base(void *ptr) { return _msize(ptr); } | ||||
rnkUnsubmitted Not Done ReplyInline ActionsMost of the changes in this file are from clang-format. I don't mind a few, but this is too many. Can you revert the non-functional whitespace changes far from the code you are editing? You can use git checkout -p to speed this up. I also encourage you to run run check-sanitizer on Linux. The ASan codebase has its own linter script that doesn't run on Windows, and sometimes disagrees with clang-format. rnk: Most of the changes in this file are from clang-format. I don't mind a few, but this is too… | |||||
mcgovAuthorUnsubmitted I can do all this when I reactivate the review. I'm going to abandon this one for now and return to the Global/Local Alloc work after a few other changes are submitted. We have a set of patches that will fix After some discussion with @iakronqu I'm going to pull this review for now and add these interceptors back until after these other patches are submitted. mcgov: I can do all this when I reactivate the review. I'm going to abandon this one for now and… | |||||
return _msize(ptr); | |||||
} | |||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void free(void *ptr) { | void free(void *ptr) { | ||||
GET_STACK_TRACE_FREE; | GET_STACK_TRACE_FREE; | ||||
return asan_free(ptr, &stack, FROM_MALLOC); | return asan_free(ptr, &stack, FROM_MALLOC); | ||||
} | } | ||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void _free_dbg(void *ptr, int) { | void _free_dbg(void *ptr, int) { free(ptr); } | ||||
free(ptr); | |||||
} | |||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void _free_base(void *ptr) { | void _free_base(void *ptr) { free(ptr); } | ||||
free(ptr); | |||||
} | |||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void *malloc(size_t size) { | void *malloc(size_t size) { | ||||
GET_STACK_TRACE_MALLOC; | GET_STACK_TRACE_MALLOC; | ||||
return asan_malloc(size, &stack); | return asan_malloc(size, &stack); | ||||
} | } | ||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void *_malloc_base(size_t size) { | void *_malloc_base(size_t size) { return malloc(size); } | ||||
return malloc(size); | |||||
} | |||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void *_malloc_dbg(size_t size, int, const char *, int) { | void *_malloc_dbg(size_t size, int, const char *, int) { return malloc(size); } | ||||
return malloc(size); | |||||
} | |||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void *calloc(size_t nmemb, size_t size) { | void *calloc(size_t nmemb, size_t size) { | ||||
GET_STACK_TRACE_MALLOC; | GET_STACK_TRACE_MALLOC; | ||||
return asan_calloc(nmemb, size, &stack); | return asan_calloc(nmemb, size, &stack); | ||||
} | } | ||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void *_calloc_base(size_t nmemb, size_t size) { | void *_calloc_base(size_t nmemb, size_t size) { return calloc(nmemb, size); } | ||||
return calloc(nmemb, size); | |||||
} | |||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void *_calloc_dbg(size_t nmemb, size_t size, int, const char *, int) { | void *_calloc_dbg(size_t nmemb, size_t size, int, const char *, int) { | ||||
return calloc(nmemb, size); | return calloc(nmemb, size); | ||||
} | } | ||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void *_calloc_impl(size_t nmemb, size_t size, int *errno_tmp) { | void *_calloc_impl(size_t nmemb, size_t size, int *errno_tmp) { | ||||
return calloc(nmemb, size); | return calloc(nmemb, size); | ||||
} | } | ||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void *realloc(void *ptr, size_t size) { | void *realloc(void *ptr, size_t size) { | ||||
GET_STACK_TRACE_MALLOC; | GET_STACK_TRACE_MALLOC; | ||||
return asan_realloc(ptr, size, &stack); | return asan_realloc(ptr, size, &stack); | ||||
} | } | ||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void *_realloc_dbg(void *ptr, size_t size, int) { | void *_realloc_dbg(void *ptr, size_t size, int) { | ||||
UNREACHABLE("_realloc_dbg should not exist!"); | UNREACHABLE("_realloc_dbg should not exist!"); | ||||
return 0; | return 0; | ||||
} | } | ||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void *_realloc_base(void *ptr, size_t size) { | void *_realloc_base(void *ptr, size_t size) { return realloc(ptr, size); } | ||||
return realloc(ptr, size); | |||||
} | |||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void *_recalloc(void *p, size_t n, size_t elem_size) { | void *_recalloc(void *p, size_t n, size_t elem_size) { | ||||
if (!p) | if (!p) | ||||
return calloc(n, elem_size); | return calloc(n, elem_size); | ||||
const size_t size = n * elem_size; | const size_t size = n * elem_size; | ||||
if (elem_size != 0 && size / elem_size != n) | if (elem_size != 0 && size / elem_size != n) | ||||
return 0; | return 0; | ||||
Show All 24 Lines | |||||
ALLOCATION_FUNCTION_ATTRIBUTE | ALLOCATION_FUNCTION_ATTRIBUTE | ||||
void *_expand_dbg(void *memblock, size_t size) { | void *_expand_dbg(void *memblock, size_t size) { | ||||
return _expand(memblock, size); | return _expand(memblock, size); | ||||
} | } | ||||
// TODO(timurrrr): Might want to add support for _aligned_* allocation | // TODO(timurrrr): Might want to add support for _aligned_* allocation | ||||
// functions to detect a bit more bugs. Those functions seem to wrap malloc(). | // functions to detect a bit more bugs. Those functions seem to wrap malloc(). | ||||
int _CrtDbgReport(int, const char*, int, | int _CrtDbgReport(int, const char *, int, const char *, const char *, ...) { | ||||
const char*, const char*, ...) { | |||||
ShowStatsAndAbort(); | ShowStatsAndAbort(); | ||||
} | } | ||||
int _CrtDbgReportW(int reportType, const wchar_t*, int, | int _CrtDbgReportW(int reportType, const wchar_t *, int, const wchar_t *, | ||||
const wchar_t*, const wchar_t*, ...) { | const wchar_t *, ...) { | ||||
ShowStatsAndAbort(); | ShowStatsAndAbort(); | ||||
} | } | ||||
int _CrtSetReportMode(int, int) { | int _CrtSetReportMode(int, int) { return 0; } | ||||
return 0; | |||||
} | |||||
} // extern "C" | } // extern "C" | ||||
#define OWNED_BY_RTL(heap, memory) \ | #define OWNED_BY_RTL(heap, memory) \ | ||||
(!__sanitizer_get_ownership(memory) && HeapValidate(heap, 0, memory)) | (!__sanitizer_get_ownership(memory) && HeapValidate(heap, 0, memory)) | ||||
INTERCEPTOR_WINAPI(size_t, HeapSize, HANDLE hHeap, DWORD dwFlags, | INTERCEPTOR_WINAPI(size_t, HeapSize, HANDLE hHeap, DWORD dwFlags, | ||||
LPCVOID lpMem) { | LPCVOID lpMem) { | ||||
// If the RTL allocators are hooked we need to check whether the ASAN | // If the RTL allocators are hooked we need to check whether the ASAN | ||||
▲ Show 20 Lines • Show All 124 Lines • ▼ Show 20 Lines | if (flags()->windows_hook_rtl_allocators) { | ||||
if (ownershipState == ASAN && !only_asan_supported_flags) { | if (ownershipState == ASAN && !only_asan_supported_flags) { | ||||
// Conversion to unsupported flags allocation, | // Conversion to unsupported flags allocation, | ||||
// transfer this allocation back to the original allocator. | // transfer this allocation back to the original allocator. | ||||
void *replacement_alloc = allocFunc(hHeap, dwFlags, dwBytes); | void *replacement_alloc = allocFunc(hHeap, dwFlags, dwBytes); | ||||
size_t old_usable_size = 0; | size_t old_usable_size = 0; | ||||
if (replacement_alloc) { | if (replacement_alloc) { | ||||
old_usable_size = asan_malloc_usable_size(lpMem, pc, bp); | old_usable_size = asan_malloc_usable_size(lpMem, pc, bp); | ||||
REAL(memcpy)(replacement_alloc, lpMem, | REAL(memcpy) | ||||
Min<size_t>(dwBytes, old_usable_size)); | (replacement_alloc, lpMem, Min<size_t>(dwBytes, old_usable_size)); | ||||
asan_free(lpMem, &stack, FROM_MALLOC); | asan_free(lpMem, &stack, FROM_MALLOC); | ||||
} | } | ||||
return replacement_alloc; | return replacement_alloc; | ||||
} | } | ||||
CHECK((ownershipState == ASAN || ownershipState == NEITHER) && | CHECK((ownershipState == ASAN || ownershipState == NEITHER) && | ||||
only_asan_supported_flags); | only_asan_supported_flags); | ||||
// At this point we should either be ASAN owned with ASAN supported flags | // At this point we should either be ASAN owned with ASAN supported flags | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | |||||
// However, hooking them is necessary to hook Windows heap | // However, hooking them is necessary to hook Windows heap | ||||
// allocations with detours and their definitions are unlikely to change. | // allocations with detours and their definitions are unlikely to change. | ||||
// Comments in /minkernel/ntos/rtl/heappublic.c indicate that these functions | // Comments in /minkernel/ntos/rtl/heappublic.c indicate that these functions | ||||
// are part of the heap's public interface. | // are part of the heap's public interface. | ||||
typedef unsigned long LOGICAL; | typedef unsigned long LOGICAL; | ||||
// This function is documented as part of the Driver Development Kit but *not* | // This function is documented as part of the Driver Development Kit but *not* | ||||
// the Windows Development Kit. | // the Windows Development Kit. | ||||
LOGICAL RtlFreeHeap(void* HeapHandle, DWORD Flags, | LOGICAL RtlFreeHeap(void *HeapHandle, DWORD Flags, void *BaseAddress); | ||||
mstorsjoUnsubmitted Not Done ReplyInline ActionsI'm getting conflicts here when I try to apply the patch locally on master, not sure what that stems from... mstorsjo: I'm getting conflicts here when I try to apply the patch locally on master, not sure what that… | |||||
void* BaseAddress); | |||||
// This function is documented as part of the Driver Development Kit but *not* | // This function is documented as part of the Driver Development Kit but *not* | ||||
// the Windows Development Kit. | // the Windows Development Kit. | ||||
void* RtlAllocateHeap(void* HeapHandle, DWORD Flags, size_t Size); | void *RtlAllocateHeap(void *HeapHandle, DWORD Flags, size_t Size); | ||||
// This function is completely undocumented. | // This function is completely undocumented. | ||||
void* | void *RtlReAllocateHeap(void *HeapHandle, DWORD Flags, void *BaseAddress, | ||||
RtlReAllocateHeap(void* HeapHandle, DWORD Flags, void* BaseAddress, | |||||
size_t Size); | size_t Size); | ||||
// This function is completely undocumented. | // This function is completely undocumented. | ||||
size_t RtlSizeHeap(void* HeapHandle, DWORD Flags, void* BaseAddress); | size_t RtlSizeHeap(void *HeapHandle, DWORD Flags, void *BaseAddress); | ||||
INTERCEPTOR_WINAPI(size_t, RtlSizeHeap, HANDLE HeapHandle, DWORD Flags, | INTERCEPTOR_WINAPI(size_t, RtlSizeHeap, HANDLE HeapHandle, DWORD Flags, | ||||
void* BaseAddress) { | void *BaseAddress) { | ||||
if (!flags()->windows_hook_rtl_allocators || | if (!flags()->windows_hook_rtl_allocators || | ||||
UNLIKELY(!asan_inited || OWNED_BY_RTL(HeapHandle, BaseAddress))) { | UNLIKELY(!asan_inited || OWNED_BY_RTL(HeapHandle, BaseAddress))) { | ||||
return REAL(RtlSizeHeap)(HeapHandle, Flags, BaseAddress); | return REAL(RtlSizeHeap)(HeapHandle, Flags, BaseAddress); | ||||
} | } | ||||
GET_CURRENT_PC_BP_SP; | GET_CURRENT_PC_BP_SP; | ||||
(void)sp; | (void)sp; | ||||
return asan_malloc_usable_size(BaseAddress, pc, bp); | return asan_malloc_usable_size(BaseAddress, pc, bp); | ||||
} | } | ||||
INTERCEPTOR_WINAPI(BOOL, RtlFreeHeap, HANDLE HeapHandle, DWORD Flags, | INTERCEPTOR_WINAPI(BOOL, RtlFreeHeap, HANDLE HeapHandle, DWORD Flags, | ||||
void* BaseAddress) { | void *BaseAddress) { | ||||
// Heap allocations happen before this function is hooked, so we must fall | // 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 | // back to the original function if the pointer is not from the ASAN heap, or | ||||
// unsupported flags are provided. | // unsupported flags are provided. | ||||
if (!flags()->windows_hook_rtl_allocators || | if (!flags()->windows_hook_rtl_allocators || | ||||
UNLIKELY((HEAP_FREE_UNSUPPORTED_FLAGS & Flags) != 0 || | UNLIKELY((HEAP_FREE_UNSUPPORTED_FLAGS & Flags) != 0 || | ||||
OWNED_BY_RTL(HeapHandle, BaseAddress))) { | OWNED_BY_RTL(HeapHandle, BaseAddress))) { | ||||
return REAL(RtlFreeHeap)(HeapHandle, Flags, BaseAddress); | return REAL(RtlFreeHeap)(HeapHandle, Flags, BaseAddress); | ||||
} | } | ||||
GET_STACK_TRACE_FREE; | GET_STACK_TRACE_FREE; | ||||
asan_free(BaseAddress, &stack, FROM_MALLOC); | asan_free(BaseAddress, &stack, FROM_MALLOC); | ||||
return true; | return true; | ||||
} | } | ||||
INTERCEPTOR_WINAPI(void*, RtlAllocateHeap, HANDLE HeapHandle, DWORD Flags, | INTERCEPTOR_WINAPI(void *, RtlAllocateHeap, HANDLE HeapHandle, DWORD Flags, | ||||
size_t Size) { | size_t Size) { | ||||
// If the ASAN runtime is not initialized, or we encounter an unsupported | // If the ASAN runtime is not initialized, or we encounter an unsupported | ||||
// flag, fall back to the original allocator. | // flag, fall back to the original allocator. | ||||
if (!flags()->windows_hook_rtl_allocators || | if (!flags()->windows_hook_rtl_allocators || | ||||
UNLIKELY(!asan_inited || | UNLIKELY(!asan_inited || | ||||
(Flags & HEAP_ALLOCATE_UNSUPPORTED_FLAGS) != 0)) { | (Flags & HEAP_ALLOCATE_UNSUPPORTED_FLAGS) != 0)) { | ||||
return REAL(RtlAllocateHeap)(HeapHandle, Flags, Size); | return REAL(RtlAllocateHeap)(HeapHandle, Flags, Size); | ||||
} | } | ||||
GET_STACK_TRACE_MALLOC; | GET_STACK_TRACE_MALLOC; | ||||
void *p; | void *p; | ||||
// Reading MSDN suggests that the *entire* usable allocation is zeroed out. | // Reading MSDN suggests that the *entire* usable allocation is zeroed out. | ||||
// Otherwise it is difficult to HeapReAlloc with HEAP_ZERO_MEMORY. | // Otherwise it is difficult to HeapReAlloc with HEAP_ZERO_MEMORY. | ||||
// https://blogs.msdn.microsoft.com/oldnewthing/20120316-00/?p=8083 | // https://blogs.msdn.microsoft.com/oldnewthing/20120316-00/?p=8083 | ||||
if (Flags & HEAP_ZERO_MEMORY) { | if (Flags & HEAP_ZERO_MEMORY) { | ||||
p = asan_calloc(Size, 1, &stack); | p = asan_calloc(Size, 1, &stack); | ||||
} else { | } else { | ||||
p = asan_malloc(Size, &stack); | p = asan_malloc(Size, &stack); | ||||
} | } | ||||
return p; | return p; | ||||
} | } | ||||
INTERCEPTOR_WINAPI(void*, RtlReAllocateHeap, HANDLE HeapHandle, DWORD Flags, | INTERCEPTOR_WINAPI(void *, RtlReAllocateHeap, HANDLE HeapHandle, DWORD Flags, | ||||
void* BaseAddress, size_t Size) { | void *BaseAddress, size_t Size) { | ||||
// If it's actually a heap block which was allocated before the ASAN runtime | // If it's actually a heap block which was allocated before the ASAN runtime | ||||
// came up, use the real RtlFreeHeap function. | // came up, use the real RtlFreeHeap function. | ||||
if (!flags()->windows_hook_rtl_allocators) | if (!flags()->windows_hook_rtl_allocators) | ||||
return REAL(RtlReAllocateHeap)(HeapHandle, Flags, BaseAddress, Size); | return REAL(RtlReAllocateHeap)(HeapHandle, Flags, BaseAddress, Size); | ||||
return SharedReAlloc(REAL(RtlReAllocateHeap), REAL(RtlSizeHeap), | return SharedReAlloc(REAL(RtlReAllocateHeap), REAL(RtlSizeHeap), | ||||
REAL(RtlFreeHeap), REAL(RtlAllocateHeap), HeapHandle, | REAL(RtlFreeHeap), REAL(RtlAllocateHeap), HeapHandle, | ||||
Flags, BaseAddress, Size); | Flags, BaseAddress, Size); | ||||
} | } | ||||
// FIXED and ZEROINIT correspond to LMEM_FIXED/GMEM_FIXED | |||||
// and LMEM_ZEROINIT/GMEM_ZEROINIT (as provided in the documentation). | |||||
// In case, if these values change then FIXED and ZEROINIT | |||||
// will have to be updated accordingly. | |||||
#define FIXED 0x0000 | |||||
#define ZEROINIT 0x0040 | |||||
constexpr unsigned long SHARED_ALLOC_SUPPORTED_FLAGS = (FIXED | ZEROINIT); | |||||
constexpr unsigned long SHARED_ALLOC_UNSUPPORTED_FLAGS = | |||||
(~SHARED_ALLOC_SUPPORTED_FLAGS); | |||||
INTERCEPTOR_WINAPI(HGLOBAL, GlobalAlloc, UINT uFlags, SIZE_T dwBytes) { | |||||
// If we encounter an unsupported flag, then we fall | |||||
// back to the original allocator. | |||||
if (uFlags & SHARED_ALLOC_UNSUPPORTED_FLAGS) { | |||||
return REAL(GlobalAlloc)(uFlags, dwBytes); | |||||
} | |||||
GET_STACK_TRACE_MALLOC; | |||||
if (uFlags & ZEROINIT) | |||||
return asan_calloc(dwBytes, 1, &stack); | |||||
else | |||||
return asan_malloc(dwBytes, &stack); | |||||
} | |||||
INTERCEPTOR_WINAPI(HGLOBAL, GlobalFree, HGLOBAL hMem) { | |||||
// If the memory we are trying to free is not owned | |||||
// by ASan heap, then fall back to the original GlobalFree. | |||||
if (OWNED_BY_RTL(GetProcessHeap(), hMem)) { | |||||
return REAL(GlobalFree)(hMem); | |||||
} | |||||
GET_STACK_TRACE_FREE; | |||||
asan_free(hMem, &stack, FROM_MALLOC); | |||||
return nullptr; | |||||
} | |||||
INTERCEPTOR_WINAPI(SIZE_T, GlobalSize, HGLOBAL hMem) { | |||||
// We need to check whether the ASAN allocator owns the pointer | |||||
// we're about to use. Allocations might occur before interception | |||||
// takes place, so if it is not owned by RTL heap, the we can | |||||
// pass it to ASAN heap for inspection. | |||||
if (!asan_inited || OWNED_BY_RTL(GetProcessHeap(), hMem)) | |||||
return REAL(GlobalSize)(hMem); | |||||
GET_CURRENT_PC_BP_SP; | |||||
(void)sp; | |||||
return asan_malloc_usable_size(hMem, pc, bp); | |||||
} | |||||
namespace __asan { | |||||
using GlobalLocalAlloc = HANDLE(WINAPI *)(UINT, SIZE_T); | |||||
using GlobalLocalRealloc = HANDLE(WINAPI *)(HANDLE, SIZE_T, UINT); | |||||
using GlobalLocalSize = SIZE_T(WINAPI *)(HANDLE); | |||||
using GlobalLocalFree = HANDLE(WINAPI *)(HANDLE); | |||||
using GlobalLocalLock = LPVOID(WINAPI *)(HANDLE); | |||||
using GlobalLocalUnlock = LPVOID(WINAPI *)(HANDLE); | |||||
enum class AllocationOwnership { | |||||
OWNED_BY_UNKNOWN, | |||||
OWNED_BY_ASAN, | |||||
OWNED_BY_RTL, | |||||
OWNED_BY_GLOBAL_OR_LOCAL, | |||||
OWNED_BY_GLOBAL_OR_LOCAL_HANDLE, | |||||
}; | |||||
void *RtlToAsan(void *mPtr, size_t old_size, size_t dwBytes, | |||||
GlobalLocalFree freeFunc, BufferedStackTrace *stack) { | |||||
// Transfer from RTL owned allocation to ASAN owned allocation | |||||
void *replacement_alloc; | |||||
replacement_alloc = asan_calloc(dwBytes, 1, stack); | |||||
if (replacement_alloc) { | |||||
if (old_size == ((SIZE_T)0) - 1) { | |||||
asan_free(replacement_alloc, stack, FROM_MALLOC); | |||||
return nullptr; | |||||
} | |||||
REAL(memcpy)(replacement_alloc, mPtr, old_size); | |||||
freeFunc((HANDLE)mPtr); | |||||
} | |||||
return replacement_alloc; | |||||
} | |||||
void *AsanToRtl(void *mPtr, UINT uFlags, SIZE_T dwBytes, | |||||
GlobalLocalAlloc allocFunc, GlobalLocalLock lockFunc, | |||||
GlobalLocalLock unlockFunc, BufferedStackTrace *stack, uptr pc, | |||||
uptr bp) { | |||||
// Transfer from ASAN owned allocation to RTL owned allocation | |||||
void *replacement_alloc; | |||||
HANDLE mem = allocFunc(uFlags, dwBytes); | |||||
// GlobalLock/LocalLock return a pointer to the memory owned by the mem | |||||
// handle. We need the pointer to copy the data over from the ASAN owned | |||||
// memory. | |||||
replacement_alloc = (void *)lockFunc(mem); | |||||
size_t old_usable_size = 0; | |||||
if (replacement_alloc) { | |||||
old_usable_size = asan_malloc_usable_size(mPtr, pc, bp); | |||||
REAL(memcpy) | |||||
(replacement_alloc, mPtr, Min<size_t>(dwBytes, old_usable_size)); | |||||
asan_free(mPtr, stack, FROM_MALLOC); | |||||
} | |||||
unlockFunc(mem); | |||||
return replacement_alloc; | |||||
} | |||||
void *ReAllocToAsan(UINT uFlags, void *mPtr, size_t dwBytes, uptr pc, uptr bp, | |||||
BufferedStackTrace *stack) { | |||||
// GlobalAlloc, LocalAlloc, GlocalReAlloc and LocalReAlloc all | |||||
// accept 0 sized allocations. Passing a zero size into asan_realloc will | |||||
// free the allocation. To avoid this and keep behavior consistent, fudge | |||||
// the size if zero (asan_malloc already does this). | |||||
if (dwBytes == 0) | |||||
dwBytes = 1; | |||||
size_t old_size; | |||||
if (uFlags & ZEROINIT) | |||||
old_size = asan_malloc_usable_size(mPtr, pc, bp); | |||||
void *ptr = asan_realloc(mPtr, dwBytes, stack); | |||||
if (ptr == nullptr) | |||||
return nullptr; | |||||
if (uFlags & ZEROINIT) { | |||||
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; | |||||
} | |||||
AllocationOwnership CheckGlobalLocalHeapOwnership( | |||||
HANDLE hMem, GlobalLocalLock lockFunc, GlobalLocalUnlock unlockFunc) { | |||||
/* To figure the validity of hMem, we use GlobalLock/LocalLock. Those two | |||||
* functions can return three things: (1) the pointer that's passed in, in | |||||
* which case it is a pointer owned by the Global/Local heap (2) the pointer | |||||
* to the allocated object if it's a Global/Local heap HANDLE (3) nullptr if | |||||
* it's a pointer which does not belong to the Global/Local heap Using these | |||||
* three return types, we figure out if the pointer is TYPE_VALID_PTR or | |||||
* TYPE_HANDLE or TYPE_UNKNOWN_PTR | |||||
* | |||||
* NOTE: As an implementation detail, movable memory objects also live on the | |||||
* heap. HeapValidate will return true if given a moveable memory handle. | |||||
* | |||||
*/ | |||||
// Do this first to avoid expensive checks if the pointer is owned by ASAN. | |||||
if (__sanitizer_get_ownership(hMem)) { | |||||
return AllocationOwnership::OWNED_BY_ASAN; | |||||
} | |||||
// It is not safe to pass wild pointers to GlobalLock/LocalLock. | |||||
if (HeapValidate(GetProcessHeap(), 0, hMem)) { | |||||
void *ptr = lockFunc(hMem); | |||||
// We don't care whether ptr is moved after this point as we're just trying | |||||
// to determine where it came from. | |||||
unlockFunc(hMem); | |||||
if (ptr == hMem) { | |||||
return AllocationOwnership::OWNED_BY_GLOBAL_OR_LOCAL; | |||||
} else if (ptr != nullptr) { | |||||
return AllocationOwnership::OWNED_BY_GLOBAL_OR_LOCAL_HANDLE; | |||||
} | |||||
} | |||||
return AllocationOwnership::OWNED_BY_UNKNOWN; | |||||
} | |||||
void *ReAllocGlobalLocal(GlobalLocalRealloc reallocFunc, | |||||
GlobalLocalSize sizeFunc, GlobalLocalFree freeFunc, | |||||
GlobalLocalAlloc allocFunc, GlobalLocalLock lockFunc, | |||||
GlobalLocalUnlock unlockFunc, HANDLE hMem, | |||||
DWORD dwBytes, UINT uFlags) { | |||||
CHECK(reallocFunc && sizeFunc && freeFunc && allocFunc); | |||||
GET_STACK_TRACE_MALLOC; | |||||
GET_CURRENT_PC_BP_SP; | |||||
(void)sp; | |||||
bool only_asan_supported_flags = | |||||
(SHARED_ALLOC_UNSUPPORTED_FLAGS & uFlags) == 0; | |||||
AllocationOwnership ownershipState = | |||||
CheckGlobalLocalHeapOwnership(hMem, lockFunc, unlockFunc); | |||||
// If this global block which was allocated before the ASAN | |||||
// runtime came up OR if mPtr is invalid, use the real GlobalReAlloc function. | |||||
if (UNLIKELY(!asan_inited) || | |||||
ownershipState == AllocationOwnership::OWNED_BY_GLOBAL_OR_LOCAL_HANDLE) { | |||||
return reallocFunc(hMem, dwBytes, uFlags); | |||||
} | |||||
// Since hMem is not a handle to moveable memory we may safely cast it to | |||||
// pointer. | |||||
void *mPtr = (void *)hMem; | |||||
if (ownershipState == AllocationOwnership::OWNED_BY_GLOBAL_OR_LOCAL || | |||||
(ownershipState == AllocationOwnership::OWNED_BY_UNKNOWN && | |||||
!only_asan_supported_flags)) { | |||||
if (only_asan_supported_flags) { | |||||
// if this is a conversion to ASAN supported flags, transfer this | |||||
// allocation to the ASAN allocator | |||||
return RtlToAsan(mPtr, sizeFunc(mPtr), dwBytes, freeFunc, &stack); | |||||
} else { | |||||
// owned by this heap or neither with unsupported ASAN flags, | |||||
// just pass back to original allocator | |||||
CHECK(ownershipState == AllocationOwnership::OWNED_BY_GLOBAL_OR_LOCAL || | |||||
ownershipState == AllocationOwnership::OWNED_BY_UNKNOWN); | |||||
CHECK(!only_asan_supported_flags); | |||||
return reallocFunc(mPtr, dwBytes, uFlags); | |||||
} | |||||
} | |||||
if (ownershipState == AllocationOwnership::OWNED_BY_ASAN && | |||||
!only_asan_supported_flags) { | |||||
// Conversion to unsupported flags allocation, | |||||
// transfer this allocation back to the original allocator. | |||||
return AsanToRtl(mPtr, uFlags, dwBytes, allocFunc, lockFunc, unlockFunc, | |||||
&stack, pc, bp); | |||||
} | |||||
CHECK((ownershipState == AllocationOwnership::OWNED_BY_ASAN || | |||||
ownershipState == AllocationOwnership::OWNED_BY_RTL || | |||||
ownershipState == AllocationOwnership::OWNED_BY_UNKNOWN) && | |||||
only_asan_supported_flags); | |||||
return ReAllocToAsan(uFlags, mPtr, dwBytes, pc, bp, &stack); | |||||
} | |||||
} // namespace __asan | |||||
INTERCEPTOR_WINAPI(HGLOBAL, GlobalReAlloc, HGLOBAL hMem, DWORD dwBytes, | |||||
UINT uFlags) { | |||||
return ReAllocGlobalLocal( | |||||
(GlobalLocalRealloc)REAL(GlobalReAlloc), | |||||
(GlobalLocalSize)REAL(GlobalSize), (GlobalLocalFree)REAL(GlobalFree), | |||||
(GlobalLocalAlloc)REAL(GlobalAlloc), (GlobalLocalLock)GlobalLock, | |||||
(GlobalLocalUnlock)GlobalUnlock, (HANDLE)hMem, dwBytes, uFlags); | |||||
} | |||||
INTERCEPTOR_WINAPI(HLOCAL, LocalAlloc, UINT uFlags, SIZE_T uBytes) { | |||||
// If we encounter an unsupported flag, then we fall | |||||
// back to the original allocator. | |||||
if (uFlags & SHARED_ALLOC_UNSUPPORTED_FLAGS) { | |||||
return REAL(LocalAlloc)(uFlags, uBytes); | |||||
} | |||||
GET_STACK_TRACE_MALLOC; | |||||
if (uFlags & ZEROINIT) | |||||
return asan_calloc(uBytes, 1, &stack); | |||||
else | |||||
return asan_malloc(uBytes, &stack); | |||||
} | |||||
INTERCEPTOR_WINAPI(HLOCAL, LocalFree, HGLOBAL hMem) { | |||||
// If the memory we are trying to free is not owned | |||||
// ASan heap, then fall back to the original LocalFree. | |||||
if (OWNED_BY_RTL(GetProcessHeap(), hMem)) { | |||||
return REAL(LocalFree)(hMem); | |||||
} | |||||
GET_STACK_TRACE_FREE; | |||||
asan_free(hMem, &stack, FROM_MALLOC); | |||||
return nullptr; | |||||
} | |||||
INTERCEPTOR_WINAPI(SIZE_T, LocalSize, HGLOBAL hMem) { | |||||
// We need to check whether the ASAN allocator owns the pointer | |||||
// we're about to use. Allocations might occur before interception | |||||
// takes place, so if it is not owned by RTL heap, the we can | |||||
// pass it to ASAN heap for inspection. | |||||
if (!asan_inited || OWNED_BY_RTL(GetProcessHeap(), hMem)) | |||||
return REAL(LocalSize)(hMem); | |||||
GET_CURRENT_PC_BP_SP; | |||||
(void)sp; | |||||
return asan_malloc_usable_size(hMem, pc, bp); | |||||
} | |||||
INTERCEPTOR_WINAPI(HLOCAL, LocalReAlloc, HGLOBAL hMem, DWORD dwBytes, | |||||
UINT uFlags) { | |||||
return ReAllocGlobalLocal( | |||||
(GlobalLocalRealloc)REAL(LocalReAlloc), (GlobalLocalSize)REAL(LocalSize), | |||||
(GlobalLocalFree)REAL(LocalFree), (GlobalLocalAlloc)REAL(LocalAlloc), | |||||
(GlobalLocalLock)LocalLock, (GlobalLocalUnlock)LocalUnlock, (HANDLE)hMem, | |||||
dwBytes, uFlags); | |||||
} | |||||
namespace __asan { | namespace __asan { | ||||
static void TryToOverrideFunction(const char *fname, uptr new_func) { | static void TryToOverrideFunction(const char *fname, uptr new_func) { | ||||
// Failure here is not fatal. The CRT may not be present, and different CRT | // Failure here is not fatal. The CRT may not be present, and different CRT | ||||
// versions use different symbols. | // versions use different symbols. | ||||
if (!__interception::OverrideFunction(fname, new_func)) | if (!__interception::OverrideFunction(fname, new_func)) | ||||
VPrintf(2, "Failed to override function %s\n", fname); | VPrintf(2, "Failed to override function %s\n", fname); | ||||
} | } | ||||
Show All 15 Lines | #if defined(ASAN_DYNAMIC) | ||||
TryToOverrideFunction("_recalloc_base", (uptr)_recalloc); | TryToOverrideFunction("_recalloc_base", (uptr)_recalloc); | ||||
TryToOverrideFunction("_recalloc_crt", (uptr)_recalloc); | TryToOverrideFunction("_recalloc_crt", (uptr)_recalloc); | ||||
TryToOverrideFunction("_msize", (uptr)_msize); | TryToOverrideFunction("_msize", (uptr)_msize); | ||||
TryToOverrideFunction("_msize_base", (uptr)_msize); | TryToOverrideFunction("_msize_base", (uptr)_msize); | ||||
TryToOverrideFunction("_expand", (uptr)_expand); | TryToOverrideFunction("_expand", (uptr)_expand); | ||||
TryToOverrideFunction("_expand_base", (uptr)_expand); | TryToOverrideFunction("_expand_base", (uptr)_expand); | ||||
if (flags()->windows_hook_rtl_allocators) { | if (flags()->windows_hook_rtl_allocators) { | ||||
INTERCEPT_FUNCTION(GlobalAlloc); | |||||
INTERCEPT_FUNCTION(GlobalFree); | |||||
INTERCEPT_FUNCTION(GlobalSize); | |||||
INTERCEPT_FUNCTION(GlobalReAlloc); | |||||
INTERCEPT_FUNCTION(LocalAlloc); | |||||
INTERCEPT_FUNCTION(LocalFree); | |||||
INTERCEPT_FUNCTION(LocalSize); | |||||
INTERCEPT_FUNCTION(LocalReAlloc); | |||||
INTERCEPT_FUNCTION(HeapSize); | INTERCEPT_FUNCTION(HeapSize); | ||||
INTERCEPT_FUNCTION(HeapFree); | INTERCEPT_FUNCTION(HeapFree); | ||||
INTERCEPT_FUNCTION(HeapReAlloc); | INTERCEPT_FUNCTION(HeapReAlloc); | ||||
INTERCEPT_FUNCTION(HeapAlloc); | INTERCEPT_FUNCTION(HeapAlloc); | ||||
// Undocumented functions must be intercepted by name, not by symbol. | // Undocumented functions must be intercepted by name, not by symbol. | ||||
__interception::OverrideFunction("RtlSizeHeap", (uptr)WRAP(RtlSizeHeap), | __interception::OverrideFunction("RtlSizeHeap", (uptr)WRAP(RtlSizeHeap), | ||||
(uptr *)&REAL(RtlSizeHeap)); | (uptr *)&REAL(RtlSizeHeap)); | ||||
Show All 33 Lines |
I guess I prefer the spelling __declspec, since it's in the implementer's namespace.