diff --git a/dlls/kernel32/tests/heap.c b/dlls/kernel32/tests/heap.c index 56f9744..eb79476 100644 --- a/dlls/kernel32/tests/heap.c +++ b/dlls/kernel32/tests/heap.c @@ -24,6 +24,7 @@ #include "windef.h" #include "winbase.h" +#include "winternl.h" #include "wine/test.h" #define MAGIC_DEAD 0xdeadbeef @@ -467,6 +468,142 @@ static void test_HeapQueryInformation(void) ok(info == 0 || info == 1 || info == 2, "expected 0, 1 or 2, got %u\n", info); } +static void test_heap_tail_check_ex(size_t bufsize1, size_t bufsize2, size_t overrun, int extra_alloc) +{ + char *p, *q, *r; + BOOL valid; + SIZE_T size; + HANDLE heap; + + heap = HeapCreate(HEAP_GROWABLE|HEAP_FREE_CHECKING_ENABLED, 0, 0); + ok(heap != NULL, "can't allocate heap?\n"); + + p = HeapAlloc(heap, 0, bufsize1); + ok(p != NULL, "RtlAllocateHeap failed?\n"); + + valid = HeapValidate(heap, 0, p); + ok(valid, "Validate failed before overrun?\n"); + + size = HeapSize(heap, 0, p); + ok(size == bufsize1, "Size failed before overrun?\n"); + + if (extra_alloc) { + /* Allocating extra block so resize can't be done in place */ + r = HeapAlloc(heap, 0, 1); + } + + q = HeapReAlloc(heap, 0, p, bufsize2); + ok(q != NULL, "ReAllocate failed before overrun?\n"); + + valid = HeapValidate(heap, 0, q); + ok(valid, "Validate failed before overrun?\n"); + + size = HeapSize(heap, 0, q); + ok(size == bufsize2, "Size failed before overrun? (was %d, wanted %d)\n", (int)size, bufsize2); + + trace("test_heap_tail_check_ex(%x, %x, %d, %d); writing to %p, expect one valgrind error and four corrupt arena warnings.\n", + bufsize1, bufsize2, overrun, extra_alloc, q+(bufsize2+overrun-1)); + q[bufsize2 + overrun - 1] = 0; + + p = HeapReAlloc(heap, 0, q, bufsize2+1); + todo_wine + ok(p == NULL, "ReAllocate did not fail after overrun? (on realloc from %x to %x, %d byte overrun)\n", bufsize2, bufsize2+1, overrun); + + valid = HeapValidate(heap, 0, q); + todo_wine + ok(!valid, "Validate did not fail after overrun?\n"); + + size = HeapSize(heap, 0, p); + todo_wine + ok(size == -1, "Size did not fail after overrun?\n"); + + valid = HeapFree(heap, 0, q); + todo_wine + ok(!valid, "Free did not fail after overrun?\n"); + + if (extra_alloc) { + valid = HeapFree(heap, 0, r); + ok(valid, "Free of extra block failed?\n"); + } + + ok(HeapDestroy(heap), "Can't destroy heap?\n"); +} + +static void test_heap_tail_check(size_t bufsize1, size_t bufsize2, size_t overrun) +{ + test_heap_tail_check_ex(bufsize1, bufsize2, overrun, 0); +} + +static ULONG (WINAPI * pRtlGetNtGlobalFlags)(void); + +static HMODULE hntdll; + +#define NTDLL_GET_PROC(func) \ + p ## func = (void*)GetProcAddress(hntdll, #func); \ + if(!p ## func) { \ + trace("GetProcAddress(%s) failed\n", #func); \ + FreeLibrary(hntdll); \ + return FALSE; \ + } + +static BOOL InitFunctionPtrs(void) +{ + hntdll = LoadLibraryA("ntdll.dll"); + if(!hntdll) { + trace("Could not load ntdll.dll\n"); + return FALSE; + } + NTDLL_GET_PROC(RtlGetNtGlobalFlags) + return TRUE; +} + +#undef NTDLL_GET_PROC + +static void test_free_overrun(void) +{ + DWORD mask = FLG_HEAP_ENABLE_FREE_CHECK; + if (broken((pRtlGetNtGlobalFlags() & mask) == 0)) { + skip("\ +Debug heap not enabled, skipping heap free padding overrun test. To enable,\n\ +run in windbg; use event filters to avoid breaking on each error.\n\ +This is only needed on Windows; on Wine, we can enable debug heap in the test.\n"); + return; + } + + /* Use odd sizes (or sizes under 16) so there will be some padding, + * so overruns can be detected. + */ + + /* Small arenas */ + test_heap_tail_check(0, 0, 1); + test_heap_tail_check(1, 0, 1); + test_heap_tail_check(0, 1, 1); + test_heap_tail_check(1, 1, 1); + + /* Small arena resize downwards and upwards */ + test_heap_tail_check(127, 35, 1); + test_heap_tail_check(35, 127, 1); + /* Ditto, but force 'hard' realloc case; needed to cover all non-failure RtlReAllocateHeap code paths in wine. */ + test_heap_tail_check_ex(35, 127, 1, 1); + + if (broken(1)) { + skip("windows does not support guard bytes for large arenas?\n"); + } else { + /* Large arenas */ + test_heap_tail_check(0x7f001, 0x7f001, 1); + + /* Large arena resize downwards and upwards */ + test_heap_tail_check(0x7f001, 0x7f101, 1); + test_heap_tail_check(0x7f101, 0x7f001, 1); + test_heap_tail_check(0x100001, 0x200001, 1); + test_heap_tail_check(0x200001, 0x100001, 1); + + /* Large vs. small resize */ + test_heap_tail_check(0x7f001, 1, 1); + test_heap_tail_check(1, 0x7f001, 1); + } +} + START_TEST(heap) { test_heap(); @@ -480,4 +617,10 @@ START_TEST(heap) test_sized_HeapReAlloc((1 << 20), (2 << 20)); test_sized_HeapReAlloc((1 << 20), 1); test_HeapQueryInformation(); + + /* Test overrun behavior */ + if (!InitFunctionPtrs()) + return; + test_free_overrun(); } + diff --git a/dlls/ntdll/heap.c b/dlls/ntdll/heap.c index adaf693..89b4d22 100644 --- a/dlls/ntdll/heap.c +++ b/dlls/ntdll/heap.c @@ -85,6 +85,7 @@ typedef struct #define ARENA_INUSE_FILLER 0x55 #define ARENA_FREE_FILLER 0xaa +#define ARENA_PAD_FILLER(odd) ((odd) ? 0xee : 0xfe) /* everything is aligned on 8 byte boundaries (16 for Win64) */ #define ALIGNMENT (2*sizeof(void*)) @@ -159,10 +160,9 @@ static HEAP *processHeap; /* main process heap */ static BOOL HEAP_IsRealArena( HEAP *heapPtr, DWORD flags, LPCVOID block, BOOL quiet ); -/* mark a block of memory as free for debugging purposes */ -static inline void mark_block_free( void *ptr, SIZE_T size ) +/* mark a block of memory as inaccessible for debugging purposes */ +static inline void mark_block_noaccess( void *ptr, SIZE_T size ) { - if (TRACE_ON(heap) || WARN_ON(heap)) memset( ptr, ARENA_FREE_FILLER, size ); #if defined(VALGRIND_MAKE_MEM_NOACCESS) VALGRIND_DISCARD( VALGRIND_MAKE_MEM_NOACCESS( ptr, size )); #elif defined( VALGRIND_MAKE_NOACCESS) @@ -200,6 +200,16 @@ static inline void mark_block_uninitialized( void *ptr, SIZE_T size ) } } +/* mark a block of memory as free for debugging purposes */ +static inline void mark_block_free( void *ptr, SIZE_T size ) +{ + if (TRACE_ON(heap) || WARN_ON(heap)) { + mark_block_uninitialized( ptr, size ); + memset( ptr, ARENA_FREE_FILLER, size ); + } + mark_block_noaccess( ptr, size ); +} + /* clear contents of a block of memory */ static inline void clear_block( void *ptr, SIZE_T size ) { @@ -273,6 +283,30 @@ static RTL_CRITICAL_SECTION_DEBUG process_heap_critsect_debug = 0, 0, { (DWORD_PTR)(__FILE__ ": main process heap section") } }; +/*********************************************************************** + * HEAP_FillTail + * If heap checking enabled, fill tail with appropriate guard pattern. + * Mark tail as inaccessible to valgrind. + * + * PARAMS + * heap [I] Heap this block is associated with + * p [I] the block whose tail to fill + * size [I] Payload size of the memory block + * filler_size [I] Unused bytes at end of block + */ +static void HEAP_FillTail( HEAP *heap, PVOID p, SIZE_T size, SIZE_T filler_size ) +{ + unsigned char *tail = ((unsigned char *)p) + size; + SIZE_T j; + + if (heap->flags & HEAP_FREE_CHECKING_ENABLED) { + mark_block_uninitialized(tail, filler_size); + for (j=0; jdata_size = size; arena->block_size = block_size; arena->size = ARENA_LARGE_SIZE; @@ -691,6 +726,7 @@ static void *realloc_large_block( HEAP *heap, DWORD flags, void *ptr, SIZE_T siz if ((flags & HEAP_ZERO_MEMORY) && size > arena->data_size) memset( (char *)ptr + arena->data_size, 0, size - arena->data_size ); arena->data_size = size; + HEAP_FillTail( heap, arena + 1, size, arena->block_size - sizeof(*arena) - size ); return ptr; } if (flags & HEAP_REALLOC_IN_PLACE_ONLY) return NULL; @@ -1256,6 +1292,16 @@ HANDLE WINAPI RtlCreateHeap( ULONG flags, PVOID addr, SIZE_T totalSize, SIZE_T c /* Allocate the heap block */ + /* Enable debug heap if user requests it. (Don't enable it for + * the virtual heap, which is created before NtCurrentTab() can + * be called. + * FIXME: figure out better way to detect virtual heap creation + * than comparing flags to HEAP_NO_SERIALIZE. + */ + if ((flags != HEAP_NO_SERIALIZE) && + (NtCurrentTeb()->Peb->NtGlobalFlag & FLG_HEAP_ENABLE_FREE_CHECK)) { + flags |= HEAP_FREE_CHECKING_ENABLED; + } if (!totalSize) { totalSize = HEAP_DEF_SIZE; @@ -1421,12 +1467,10 @@ PVOID WINAPI RtlAllocateHeap( HANDLE heap, ULONG flags, SIZE_T size ) notify_alloc( pInUse + 1, size, flags & HEAP_ZERO_MEMORY ); if (flags & HEAP_ZERO_MEMORY) - { clear_block( pInUse + 1, size ); - mark_block_uninitialized( (char *)(pInUse + 1) + size, pInUse->unused_bytes ); - } else - mark_block_uninitialized( pInUse + 1, pInUse->size & ARENA_SIZE_MASK ); + mark_block_uninitialized( pInUse + 1, size ); + HEAP_FillTail( heap, pInUse + 1, size, pInUse->unused_bytes ); if (!(flags & HEAP_NO_SERIALIZE)) RtlLeaveCriticalSection( &heapPtr->critSection ); @@ -1635,14 +1679,12 @@ PVOID WINAPI RtlReAllocateHeap( HANDLE heap, ULONG flags, PVOID ptr, SIZE_T size if (size > oldActualSize) { if (flags & HEAP_ZERO_MEMORY) - { clear_block( (char *)(pArena + 1) + oldActualSize, size - oldActualSize ); - mark_block_uninitialized( (char *)(pArena + 1) + size, pArena->unused_bytes ); - } else mark_block_uninitialized( (char *)(pArena + 1) + oldActualSize, - (pArena->size & ARENA_SIZE_MASK) - oldActualSize ); + (pArena->size & ARENA_SIZE_MASK) - pArena->unused_bytes - oldActualSize ); } + HEAP_FillTail( heapPtr, pArena + 1, size, pArena->unused_bytes ); /* Return the new arena */