/* -*- C++ -*- */ /* * @file winwrapper.cpp * @brief Replaces malloc family on Windows with custom versions. * @author Emery Berger * @note Copyright (C) 2011-2015 by Emery Berger, University of Massachusetts Amherst. */ /* To use this library, you only need to define the following allocation functions: - xxmalloc - xxfree - xxmalloc_usable_size - xxmalloc_lock - xxmalloc_unlock See the extern "C" block below for function prototypes and more details. YOU SHOULD NOT NEED TO MODIFY ANY OF THE CODE HERE TO SUPPORT ANY ALLOCATOR. LIMITATIONS: - This wrapper assumes that the underlying allocator will do "the right thing" when xxfree() is called with a pointer internal to an allocated object. Header-based allocators, for example, need not apply. - This wrapper also assumes that there is some way to lock all the heaps used by a given allocator; however, such support is only required by programs that also call fork(). In case your program does not, the lock and unlock calls given below can be no-ops. */ #include #include #include #include #include #include "x86jump.h" extern "C" { void * xxmalloc (size_t); void xxfree (void *); // Takes a pointer and returns how much space it holds. size_t xxmalloc_usable_size (void *); // Locks the heap(s), used prior to any invocation of fork(). void xxmalloc_lock (void); // Unlocks the heap(s), after fork(). void xxmalloc_unlock (void); } #ifdef _DEBUG #error "This library must be compiled in release mode." #endif #define WIN32_LEAN_AND_MEAN #if !defined(_M_IX86) && !defined(_M_X64) #error "Hoard currently only supports x86-based architectures on Windows." #endif #if (_WIN32_WINNT < 0x0500) #define _WIN32_WINNT 0x0500 #endif #pragma inline_depth(255) #pragma warning(disable: 4273) #pragma warning(disable: 4098) // Library conflict. #pragma warning(disable: 4355) // 'this' used in base member initializer list. #pragma warning(disable: 4074) // initializers put in compiler reserved area. #define WINWRAPPER_PREFIX(x) winwrapper_##x class Patch { public: const char *import; // import name of patch routine FARPROC replacement; // pointer to replacement function FARPROC original; // pointer to original function bool patched; // did we actually execute this patch? unsigned char codebytes[sizeof(X86Jump)]; // original code storage }; static bool PatchMe(); static void UnpatchMe(); extern "C" void executeRegisteredFunctions(); /// Initialize everything. extern "C" void InitializeWinWrapper() { // Allocate (and leak) something from the old Windows heap. HeapAlloc (GetProcessHeap(), 0, 1); PatchMe(); } /// Mr. Gorbachev, tear down this process. extern "C" void FinalizeWinWrapper() { TerminateProcess(GetCurrentProcess(), 0); } extern "C" { __declspec(dllexport) int ReferenceWinWrapperStub; void * WINWRAPPER_PREFIX(_expand) (void * ptr) { return nullptr; } void * WINWRAPPER_PREFIX(_expand_dbg)(void *userData, size_t newSize, int blockType, const char *filename, int linenumber) { return nullptr; } static void * WINWRAPPER_PREFIX(realloc) (void * ptr, size_t sz) { // null ptr = act like a malloc. if (ptr == nullptr) { return xxmalloc(sz); } // 0 size = free. We return a small object. This behavior is // apparently required under Mac OS X and optional under POSIX. if (sz == 0) { xxfree (ptr); return xxmalloc(1); } auto originalSize = xxmalloc_usable_size (ptr); auto minSize = (originalSize < sz) ? originalSize : sz; // Don't change size if the object is shrinking by less than half. if ((originalSize / 2 < sz) && (sz <= originalSize)) { // Do nothing. return ptr; } auto * buf = xxmalloc (sz); if (buf != nullptr) { // Successful malloc. // Copy the contents of the original object // up to the size of the new block. memcpy (buf, ptr, minSize); xxfree (ptr); } // Return a pointer to the new one. return buf; } static void * WINWRAPPER_PREFIX(_recalloc)(void * memblock, size_t num, size_t size) { const auto requestedSize = num * size; auto * ptr = WINWRAPPER_PREFIX(realloc)(memblock, requestedSize); if (ptr != nullptr) { const auto actualSize = xxmalloc_usable_size(ptr); if (actualSize > requestedSize) { // Clear out any memory after the end of the requested chunk. memset (static_cast(ptr) + requestedSize, 0, actualSize - requestedSize); } } return ptr; } static void * WINWRAPPER_PREFIX(calloc)(size_t num, size_t size) { auto * ptr = xxmalloc (num * size); if (ptr) { memset (ptr, 0, num * size); } return ptr; } char * WINWRAPPER_PREFIX(strdup) (const char * s) { char * newString = nullptr; if (s != nullptr) { auto len = strlen(s) + 1; if ((newString = (char *) xxmalloc(len))) { memcpy (newString, s, len); } } return newString; } //// Exit handling. const int MAX_EXIT_FUNCTIONS = 2048; _onexit_t exitFunctions[MAX_EXIT_FUNCTIONS]; static int exitFunctionsRegistered = 0; void WINWRAPPER_PREFIX(exit)(int status) { executeRegisteredFunctions(); TerminateProcess(GetCurrentProcess(), status); } void WINWRAPPER_PREFIX(_exit)(int status) { TerminateProcess(GetCurrentProcess(), status); } _onexit_t WINWRAPPER_PREFIX(_onexit)(_onexit_t fn) { if (exitFunctionsRegistered >= MAX_EXIT_FUNCTIONS) { return nullptr; } else { exitFunctions[exitFunctionsRegistered] = fn; exitFunctionsRegistered++; return fn; } } int WINWRAPPER_PREFIX(atexit)(void (*fn)(void)) { if (WINWRAPPER_PREFIX(_onexit)((_onexit_t) fn) == nullptr) { return ENOMEM; } else { return 0; } } void WINWRAPPER_PREFIX(_cexit)() { executeRegisteredFunctions(); } void WINWRAPPER_PREFIX(_c_exit)() { } /// Execute all registered functions in LIFO order. void executeRegisteredFunctions() { for (int i = exitFunctionsRegistered; i > 0; i--) { (*exitFunctions[i-1])(); } exitFunctionsRegistered = 0; } void * WINWRAPPER_PREFIX(_calloc_dbg)(size_t num, size_t size, int blockType, const char *filename, int linenumber) { return WINWRAPPER_PREFIX(calloc)(num, size); } void * WINWRAPPER_PREFIX(_malloc_dbg)(size_t size, int blockType, const char *filename, int linenumber) { return xxmalloc(size); } void * WINWRAPPER_PREFIX(_realloc_dbg)(void *userData, size_t newSize, int blockType, const char *filename, int linenumber) { return WINWRAPPER_PREFIX(realloc)(userData, newSize); } void WINWRAPPER_PREFIX(_free_dbg)(void *userData, int blockType) { xxfree(userData); } size_t WINWRAPPER_PREFIX(_msize_dbg)(void *userData, int blockType) { return xxmalloc_usable_size(userData); } LPVOID WINWRAPPER_PREFIX(HeapAlloc)(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes) { if (hHeap == nullptr) { return nullptr; } if (dwFlags & HEAP_ZERO_MEMORY) { return WINWRAPPER_PREFIX(calloc)(1, dwBytes); } else { return xxmalloc(dwBytes); } } SIZE_T WINAPI WINWRAPPER_PREFIX(HeapCompact)(HANDLE hHeap, DWORD dwFlags) { // Stub: 4GB malloc should be enough for anyone :). return (1UL << 31); } HANDLE WINAPI WINWRAPPER_PREFIX(HeapCreate)(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize) { // Ignore all options and just return a bogus (non-null) handle. return (HANDLE) 0x1; } BOOL WINAPI WINWRAPPER_PREFIX(HeapDestroy)(HANDLE hHeap) { // For now, do nothing and claim we destroyed the heap. // NOTE: this potentially leaks memory if the user is creating // and destroying heaps rather than explicitly freeing objects. return TRUE; } BOOL WINWRAPPER_PREFIX(HeapFree)(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem) { xxfree(lpMem); return true; } LPVOID WINAPI WINWRAPPER_PREFIX(HeapReAlloc)(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes) { // Immediately fail if we are asked to realloc in place (since we can't guarantee it). if (dwFlags & HEAP_REALLOC_IN_PLACE_ONLY) { return nullptr; } // Use _recalloc to handle requests with HEAP_ZERO_MEMORY. if (dwFlags & HEAP_ZERO_MEMORY) { return WINWRAPPER_PREFIX(_recalloc)(lpMem, 1, dwBytes); } return WINWRAPPER_PREFIX(realloc)(lpMem, dwBytes); } BOOL WINAPI WINWRAPPER_PREFIX(HeapValidate)(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem) { // A stub that says the heap is fine. return TRUE; } SIZE_T WINAPI WINWRAPPER_PREFIX(HeapSize)(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem) { return xxmalloc_usable_size((void *) lpMem); } BOOL WINAPI WINWRAPPER_PREFIX(HeapWalk)(HANDLE hHeap, LPPROCESS_HEAP_ENTRY lpEntry) { // A stub that prevents actually walking the heap. return FALSE; } PVOID WINWRAPPER_PREFIX(RtlAllocateHeap)(PVOID HeapHandle, ULONG Flags, SIZE_T Size) { DWORD fl = (DWORD) Flags; return (PVOID) WINWRAPPER_PREFIX(HeapAlloc)(HeapHandle, fl, Size); } ULONG WINWRAPPER_PREFIX(RtlSizeHeap)(PVOID HeapHandle, ULONG Flags, PVOID MemoryPointer ) { return xxmalloc_usable_size(MemoryPointer); } PVOID WINWRAPPER_PREFIX(RtlCreateHeap)(ULONG Flags, PVOID HeapBase, SIZE_T ReserveSize, SIZE_T CommitSize, PVOID Lock, PVOID /* PRTL_HEAP_PARAMETERS */ Parameters) { // FIX ME: right now, it's just a bogus number. return (PVOID) 0x1; } BOOLEAN WINWRAPPER_PREFIX(RtlFreeHeap)(PVOID HeapHandle, ULONG Flags, PVOID HeapBase) { xxfree(HeapBase); return TRUE; } PVOID WINWRAPPER_PREFIX(RtlDestroyHeap)(PVOID HeapHandle) { return nullptr; } } /* ------------------------------------------------------------------------ */ #define INTERPOSE(x) {#x, (FARPROC) winwrapper_##x, false, 0} #define INTERPOSE2(x,y) {x, (FARPROC) y, false, 0} static Patch rls_patches[] = { // operator new, new[], delete, delete[]. // _WIN64 INTERPOSE2("??2@YAPEAX_K@Z", xxmalloc), INTERPOSE2("??_U@YAPEAX_K@Z", xxmalloc), INTERPOSE2("??3@YAXPEAX@Z", xxfree), INTERPOSE2("??_V@YAXPEAX@Z", xxfree), // non _WIN64 {"??2@YAPAXI@Z", (FARPROC) xxmalloc, false, 0}, {"??_U@YAPAXI@Z", (FARPROC) xxmalloc, false, 0}, {"??3@YAXPAX@Z", (FARPROC) xxfree, false, 0}, {"??_V@YAXPAX@Z", (FARPROC) xxfree, false, 0}, // Debug versions. INTERPOSE(_calloc_dbg), INTERPOSE(_expand_dbg), INTERPOSE(_free_dbg), INTERPOSE(_malloc_dbg), INTERPOSE(_msize_dbg), INTERPOSE(_realloc_dbg), // the nothrow variants new, new[], delete, delete[] {"??2@YAPAXIABUnothrow_t@std@@@Z", (FARPROC) xxmalloc, false, 0}, {"??_U@YAPAXIABUnothrow_t@std@@@Z", (FARPROC) xxmalloc, false, 0}, {"??3@YAXPAXABUnothrow_t@std@@@Z", (FARPROC) xxfree, false, 0}, {"??_V@YAXPAXABUnothrow_t@std@@@Z", (FARPROC) xxfree, false, 0}, // Other malloc API friends. {"_msize", (FARPROC) xxmalloc_usable_size, false, 0}, INTERPOSE(calloc), {"_calloc_base",(FARPROC) WINWRAPPER_PREFIX(calloc),false, 0}, {"_calloc_crt",(FARPROC) WINWRAPPER_PREFIX(calloc), false, 0}, {"_calloc_impl",(FARPROC) WINWRAPPER_PREFIX(calloc), false, 0}, INTERPOSE(_expand), INTERPOSE2("malloc", xxmalloc), INTERPOSE2("_malloc_base", xxmalloc), INTERPOSE2("_malloc_crt", xxmalloc), INTERPOSE2("_malloc_impl", xxmalloc), INTERPOSE(realloc), {"_realloc_base",(FARPROC) WINWRAPPER_PREFIX(realloc),false, 0}, {"_realloc_crt",(FARPROC) WINWRAPPER_PREFIX(realloc),false, 0}, {"_realloc_impl",(FARPROC) WINWRAPPER_PREFIX(realloc),false, 0}, INTERPOSE2("free", xxfree), INTERPOSE2("_free_base", xxfree), INTERPOSE2("_free_crt", xxfree), INTERPOSE2("_free_impl", xxfree), INTERPOSE(_recalloc), {"_recalloc_base", (FARPROC) WINWRAPPER_PREFIX(_recalloc),false, 0}, {"_recalloc_crt", (FARPROC) WINWRAPPER_PREFIX(_recalloc),false, 0}, {"_recalloc_impl", (FARPROC) WINWRAPPER_PREFIX(_recalloc),false, 0}, #if 1 INTERPOSE(exit), INTERPOSE(_exit), INTERPOSE(_onexit), INTERPOSE(atexit), INTERPOSE(_cexit), INTERPOSE(_c_exit), #endif INTERPOSE(strdup) #if 1 // RTL Heap API ,{"RtlAllocateHeap", (FARPROC) WINWRAPPER_PREFIX(RtlAllocateHeap), false, 0}, // {"RtlCreateHeap", (FARPROC) WINWRAPPER_PREFIX(RtlCreateHeap), false, 0}, // {"RtlDestroyHeap", (FARPROC) WINWRAPPER_PREFIX(RtlDestroyHeap), false, 0}, {"RtlFreeHeap", (FARPROC) WINWRAPPER_PREFIX(RtlFreeHeap), false, 0}, {"RtlSizeHeap", (FARPROC) WINWRAPPER_PREFIX(RtlSizeHeap), false, 0} #endif #if 1 // Windows Heap API ,{"HeapAlloc", (FARPROC) WINWRAPPER_PREFIX(HeapAlloc),false, 0}, {"HeapCompact", (FARPROC) WINWRAPPER_PREFIX(HeapCompact),false, 0}, // {"HeapCreate", (FARPROC) WINWRAPPER_PREFIX(HeapWalk),false, 0}, // {"HeapDestroy", (FARPROC) WINWRAPPER_PREFIX(HeapWalk),false, 0}, {"HeapFree", (FARPROC) WINWRAPPER_PREFIX(HeapFree),false, 0}, // HeapLock // HeapQueryInformation {"HeapReAlloc", (FARPROC) WINWRAPPER_PREFIX(HeapReAlloc),false, 0}, // HeapSetInformation {"HeapSize", (FARPROC) WINWRAPPER_PREFIX(HeapSize),false, 0}, // HeapUnlock {"HeapValidate", (FARPROC) WINWRAPPER_PREFIX(HeapValidate),false, 0}, {"HeapWalk", (FARPROC) WINWRAPPER_PREFIX(HeapWalk),false, 0} #endif }; static void PatchIt (Patch *patch) { // Change rights on CRT Library module to execute/read/write. MEMORY_BASIC_INFORMATION mbi_thunk; VirtualQuery((void*)patch->original, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION)); VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, PAGE_EXECUTE_READWRITE, &mbi_thunk.Protect); // Patch CRT library original routine: // save original code bytes for exit restoration // write jmp (at least 5 bytes long) to original. memcpy (patch->codebytes, patch->original, sizeof(X86Jump)); auto * patchloc = (unsigned char*)patch->original; new (patchloc) X86Jump (patch->replacement); // Reset CRT library code to original page protection. VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, mbi_thunk.Protect, &mbi_thunk.Protect); } static void UnpatchIt (Patch *patch) { if (patch->patched) { // Change rights on CRT Library module to execute/read/write. MEMORY_BASIC_INFORMATION mbi_thunk; VirtualQuery((void*)patch->original, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION)); VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, PAGE_EXECUTE_READWRITE, &mbi_thunk.Protect); // Restore original CRT routine. memcpy (patch->original, patch->codebytes, sizeof(X86Jump)); // Reset CRT library code to original page protection. VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, mbi_thunk.Protect, &mbi_thunk.Protect); } } static bool PatchMe() { bool patchedIn = false; // We walk through all modules loaded by the process and patch the relevant entry points. auto pid = GetCurrentProcessId(); auto hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); DWORD cbNeeded; const auto MaxModules = 8192; HMODULE hMods[MaxModules]; if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) { for (auto i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) { TCHAR szModName[MAX_PATH] = { 0 }; LPTSTR pszBuffer = szModName; if (GetModuleFileName(hMods[i], pszBuffer, sizeof(szModName) / sizeof(TCHAR))) { // Only patch CRT libraries. if (!(_tcsstr(pszBuffer, _T("CRT")) || _tcsstr(pszBuffer, _T("crt")))) { continue; } HMODULE RlsCRTLibrary = GetModuleHandle(pszBuffer); // Patch all relevant release CRT Library entry points. if (RlsCRTLibrary) { for (auto j = 0; j < sizeof(rls_patches) / sizeof(*rls_patches); j++) { if (rls_patches[j].original = GetProcAddress(RlsCRTLibrary, rls_patches[j].import)) { // Got one: patch it. PatchIt(&rls_patches[j]); rls_patches[j].patched = true; patchedIn = true; } } } } } } return patchedIn; } static void UnpatchMe() { for (int i = 0; i < sizeof(rls_patches) / sizeof(*rls_patches); i++) { UnpatchIt(&rls_patches[i]); } }