/* The wrapper functions in this file work like regular malloc() and free(), * but store check values before and after the allocation. This helps to catch * any buffer overrun errors in the test cases. */ #include "malloc_wrappers.h" #include #include #include #include #define GUARD_SIZE (sizeof(size_t)*3) #define PREFIX_SIZE (sizeof(size_t)*2) #define CHECK1 ((size_t)0xDEADBEEF) #define CHECK2 ((size_t)0x600DCAFE) #ifndef MAX_ALLOC_BYTES #define MAX_ALLOC_BYTES 16*1024*1024 #endif #ifndef DEBUG_MALLOC #define DEBUG_MALLOC 0 #endif static size_t g_alloc_count = 0; static size_t g_alloc_bytes = 0; static size_t g_max_alloc_bytes = MAX_ALLOC_BYTES; #ifdef LLVMFUZZER /* LLVM libsanitizer has a realloc() implementation that always copies * the whole memory block, even if there would be space to expand it in * place. This gets pretty slow when fuzzing, so this wrapper limits the * realloc() calls by rounding allocation size upwards. Real world * realloc() implementations are hopefully smarter. */ static size_t round_blocksize(size_t size) { if (size < 256) { return size; } else { return (size + 1023) / 1024 * 1024; } } #else static size_t round_blocksize(size_t size) { return size; } #endif /* Allocate memory and place check values before and after. */ void* malloc_with_check(size_t size) { char *buf = NULL; if (size <= g_max_alloc_bytes - g_alloc_bytes) { buf = malloc(round_blocksize(size + GUARD_SIZE)); } if (buf) { ((size_t*)buf)[0] = size; ((size_t*)buf)[1] = CHECK1; ((size_t*)(buf + size))[2] = CHECK2; g_alloc_count++; g_alloc_bytes += size; if (DEBUG_MALLOC) fprintf(stderr, "Alloc 0x%04x/%u\n", (unsigned)(uintptr_t)(buf + PREFIX_SIZE), (unsigned)size); return buf + PREFIX_SIZE; } else { if (DEBUG_MALLOC) fprintf(stderr, "malloc(%u) failed\n", (unsigned)size); return NULL; } } /* Free memory allocated with malloc_with_check() and do the checks. */ void free_with_check(void *mem) { if (mem) { char *buf = (char*)mem - PREFIX_SIZE; size_t size = ((size_t*)buf)[0]; if (DEBUG_MALLOC) fprintf(stderr, "Release 0x%04x/%u\n", (unsigned)(uintptr_t)mem, (unsigned)size); assert(((size_t*)buf)[1] == CHECK1); assert(((size_t*)(buf + size))[2] == CHECK2); assert(g_alloc_count > 0); assert(g_alloc_bytes >= size); ((size_t*)buf)[1] = 0; ((size_t*)(buf + size))[2] = 0; g_alloc_count--; g_alloc_bytes -= size; free(buf); } } /* Reallocate block and check / write guard values */ void* realloc_with_check(void *ptr, size_t size) { if (!ptr && size) { /* Allocate new block and write guard values */ return malloc_with_check(size); } else if (ptr && size) { /* Change block size */ char *buf = (char*)ptr - PREFIX_SIZE; size_t oldsize = ((size_t*)buf)[0]; assert(((size_t*)buf)[1] == CHECK1); assert(((size_t*)(buf + oldsize))[2] == CHECK2); assert(g_alloc_count > 0); assert(g_alloc_bytes >= oldsize); if (size <= g_max_alloc_bytes - (g_alloc_bytes - oldsize)) { size_t new_rounded = round_blocksize(size + GUARD_SIZE); size_t old_rounded = round_blocksize(oldsize + GUARD_SIZE); if (new_rounded != old_rounded) { buf = realloc(buf, new_rounded); } } else { buf = NULL; } if (!buf) { if (DEBUG_MALLOC) fprintf(stderr, "Realloc 0x%04x/%u to %u failed\n", (unsigned)(uintptr_t)ptr, (unsigned)oldsize, (unsigned)size); return NULL; } ((size_t*)buf)[0] = size; ((size_t*)buf)[1] = CHECK1; ((size_t*)(buf + size))[2] = CHECK2; g_alloc_bytes -= oldsize; g_alloc_bytes += size; if (DEBUG_MALLOC) fprintf(stderr, "Realloc 0x%04x/%u to 0x%04x/%u\n", (unsigned)(uintptr_t)ptr, (unsigned)oldsize, (unsigned)(uintptr_t)(buf + PREFIX_SIZE), (unsigned)size); return buf + PREFIX_SIZE; } else if (ptr && !size) { /* Deallocate */ free_with_check(ptr); return NULL; } else { /* No action */ return NULL; } } /* Return total number of allocations not yet released */ size_t get_alloc_count() { return g_alloc_count; } /* Return allocated size for a pointer returned from malloc(). */ size_t get_allocation_size(const void *mem) { char *buf = (char*)mem - PREFIX_SIZE; return ((size_t*)buf)[0]; } /* Get total number of allocated bytes */ size_t get_alloc_bytes() { return g_alloc_bytes; } /* Set limit for allocation size */ void set_max_alloc_bytes(size_t max_bytes) { g_max_alloc_bytes = max_bytes; } size_t get_max_alloc_bytes() { return g_max_alloc_bytes; }