diff --git a/libc/bionic/malloc_debug_check.cpp b/libc/bionic/malloc_debug_check.cpp index 0575595fd..faf61bf5c 100644 --- a/libc/bionic/malloc_debug_check.cpp +++ b/libc/bionic/malloc_debug_check.cpp @@ -326,14 +326,19 @@ static inline void add_to_backlog(hdr_t* hdr) { } } -extern "C" void* chk_malloc(size_t size) { +extern "C" void* chk_malloc(size_t bytes) { // log_message("%s: %s\n", __FILE__, __FUNCTION__); - hdr_t* hdr = static_cast(Malloc(malloc)(sizeof(hdr_t) + size + sizeof(ftr_t))); + size_t size = sizeof(hdr_t) + bytes + sizeof(ftr_t); + if (size < bytes) { // Overflow + errno = ENOMEM; + return NULL; + } + hdr_t* hdr = static_cast(Malloc(malloc)(size)); if (hdr) { hdr->base = hdr; hdr->bt_depth = get_backtrace(hdr->bt, MAX_BACKTRACE_DEPTH); - add(hdr, size); + add(hdr, bytes); return user(hdr); } return NULL; @@ -411,15 +416,15 @@ extern "C" void chk_free(void* ptr) { } } -extern "C" void* chk_realloc(void* ptr, size_t size) { +extern "C" void* chk_realloc(void* ptr, size_t bytes) { // log_message("%s: %s\n", __FILE__, __FUNCTION__); if (!ptr) { - return chk_malloc(size); + return chk_malloc(bytes); } #ifdef REALLOC_ZERO_BYTES_FREE - if (!size) { + if (!bytes) { chk_free(ptr); return NULL; } @@ -432,7 +437,7 @@ extern "C" void* chk_realloc(void* ptr, size_t size) { int depth = get_backtrace(bt, MAX_BACKTRACE_DEPTH); if (hdr->tag == BACKLOG_TAG) { log_message("+++ REALLOCATION %p SIZE %d OF FREED MEMORY!\n", - user(hdr), size, hdr->size); + user(hdr), bytes, hdr->size); log_message("+++ ALLOCATION %p SIZE %d ALLOCATED HERE:\n", user(hdr), hdr->size); log_backtrace(hdr->bt, hdr->bt_depth); @@ -451,47 +456,54 @@ extern "C" void* chk_realloc(void* ptr, size_t size) { del_from_backlog(hdr); } else { log_message("+++ REALLOCATION %p SIZE %d IS CORRUPTED OR NOT ALLOCATED VIA TRACKER!\n", - user(hdr), size); + user(hdr), bytes); log_backtrace(bt, depth); // just get a whole new allocation and leak the old one - return Malloc(realloc)(0, size); - // return realloc(user(hdr), size); // assuming it was allocated externally + return Malloc(realloc)(0, bytes); + // return realloc(user(hdr), bytes); // assuming it was allocated externally } } + size_t size = sizeof(hdr_t) + bytes + sizeof(ftr_t); + if (size < bytes) { // Overflow + errno = ENOMEM; + return NULL; + } if (hdr->base != hdr) { // An allocation from memalign, so create another allocation and // copy the data out. - void* newMem = Malloc(malloc)(sizeof(hdr_t) + size + sizeof(ftr_t)); - if (newMem) { - memcpy(newMem, hdr, sizeof(hdr_t) + hdr->size); - Malloc(free)(hdr->base); - hdr = static_cast(newMem); - } else { - Malloc(free)(hdr->base); - hdr = NULL; + void* newMem = Malloc(malloc)(size); + if (newMem == NULL) { + return NULL; } + memcpy(newMem, hdr, sizeof(hdr_t) + hdr->size); + Malloc(free)(hdr->base); + hdr = static_cast(newMem); } else { - hdr = static_cast(Malloc(realloc)(hdr, sizeof(hdr_t) + size + sizeof(ftr_t))); + hdr = static_cast(Malloc(realloc)(hdr, size)); } if (hdr) { hdr->base = hdr; hdr->bt_depth = get_backtrace(hdr->bt, MAX_BACKTRACE_DEPTH); - add(hdr, size); + add(hdr, bytes); return user(hdr); } - return NULL; } -extern "C" void* chk_calloc(int nmemb, size_t size) { +extern "C" void* chk_calloc(size_t nmemb, size_t bytes) { // log_message("%s: %s\n", __FILE__, __FUNCTION__); - size_t total_size = nmemb * size; - hdr_t* hdr = static_cast(Malloc(calloc)(1, sizeof(hdr_t) + total_size + sizeof(ftr_t))); + size_t total_bytes = nmemb * bytes; + size_t size = sizeof(hdr_t) + total_bytes + sizeof(ftr_t); + if (size < total_bytes || (nmemb && SIZE_MAX / nmemb < bytes)) { // Overflow + errno = ENOMEM; + return NULL; + } + hdr_t* hdr = static_cast(Malloc(calloc)(1, size)); if (hdr) { hdr->base = hdr; hdr->bt_depth = get_backtrace(hdr->bt, MAX_BACKTRACE_DEPTH); - add(hdr, total_size); + add(hdr, total_bytes); return user(hdr); } return NULL; @@ -509,6 +521,33 @@ extern "C" size_t chk_malloc_usable_size(const void* ptr) { return hdr->size; } +extern "C" struct mallinfo chk_mallinfo() { + return Malloc(mallinfo)(); +} + +extern "C" int chk_posix_memalign(void** memptr, size_t alignment, size_t size) { + if ((alignment & (alignment - 1)) != 0) { + return EINVAL; + } + int saved_errno = errno; + *memptr = chk_memalign(alignment, size); + errno = saved_errno; + return (*memptr != NULL) ? 0 : ENOMEM; +} + +extern "C" void* chk_pvalloc(size_t bytes) { + size_t pagesize = sysconf(_SC_PAGESIZE); + size_t size = (bytes + pagesize - 1) & ~(pagesize - 1); + if (size < bytes) { // Overflow + return NULL; + } + return chk_memalign(pagesize, size); +} + +extern "C" void* chk_valloc(size_t size) { + return chk_memalign(sysconf(_SC_PAGESIZE), size); +} + static void ReportMemoryLeaks() { // Use /proc/self/exe link to obtain the program name for logging // purposes. If it's not available, we set it to "". diff --git a/libc/bionic/malloc_debug_common.cpp b/libc/bionic/malloc_debug_common.cpp index 77ec080fe..6677c22e3 100644 --- a/libc/bionic/malloc_debug_common.cpp +++ b/libc/bionic/malloc_debug_common.cpp @@ -26,19 +26,17 @@ * SUCH DAMAGE. */ -/* - * Contains definition of structures, global variables, and implementation of - * routines that are used by malloc leak detection code and other components in - * the system. The trick is that some components expect these data and - * routines to be defined / implemented in libc.so library, regardless - * whether or not MALLOC_LEAK_CHECK macro is defined. To make things even - * more tricky, malloc leak detection code, implemented in - * libc_malloc_debug.so also requires access to these variables and routines - * (to fill allocation entry hash table, for example). So, all relevant - * variables and routines are defined / implemented here and exported - * to all, leak detection code and other components via dynamic (libc.so), - * or static (libc.a) linking. - */ +// Contains definition of structures, global variables, and implementation of +// routines that are used by malloc leak detection code and other components in +// the system. The trick is that some components expect these data and +// routines to be defined / implemented in libc.so library, regardless +// whether or not MALLOC_LEAK_CHECK macro is defined. To make things even +// more tricky, malloc leak detection code, implemented in +// libc_malloc_debug.so also requires access to these variables and routines +// (to fill allocation entry hash table, for example). So, all relevant +// variables and routines are defined / implemented here and exported +// to all, leak detection code and other components via dynamic (libc.so), +// or static (libc.a) linking. #include "malloc_debug_common.h" @@ -56,7 +54,16 @@ static HashTable g_hash_table; // Support for malloc debugging. // Table for dispatching malloc calls, initialized with default dispatchers. static const MallocDebug __libc_malloc_default_dispatch __attribute__((aligned(32))) = { - Malloc(malloc), Malloc(free), Malloc(calloc), Malloc(realloc), Malloc(memalign), Malloc(malloc_usable_size), + Malloc(calloc), + Malloc(free), + Malloc(mallinfo), + Malloc(malloc), + Malloc(malloc_usable_size), + Malloc(memalign), + Malloc(posix_memalign), + Malloc(pvalloc), + Malloc(realloc), + Malloc(valloc), }; // Selector of dispatch table to use for dispatching malloc calls. @@ -97,176 +104,174 @@ static int g_malloc_debug_level = 0; // ============================================================================= static int hash_entry_compare(const void* arg1, const void* arg2) { - int result; + int result; - const HashEntry* e1 = *static_cast(arg1); - const HashEntry* e2 = *static_cast(arg2); + const HashEntry* e1 = *static_cast(arg1); + const HashEntry* e2 = *static_cast(arg2); - // if one or both arg pointers are null, deal gracefully - if (e1 == NULL) { - result = (e2 == NULL) ? 0 : 1; - } else if (e2 == NULL) { - result = -1; + // if one or both arg pointers are null, deal gracefully + if (e1 == NULL) { + result = (e2 == NULL) ? 0 : 1; + } else if (e2 == NULL) { + result = -1; + } else { + size_t nbAlloc1 = e1->allocations; + size_t nbAlloc2 = e2->allocations; + size_t size1 = e1->size & ~SIZE_FLAG_MASK; + size_t size2 = e2->size & ~SIZE_FLAG_MASK; + size_t alloc1 = nbAlloc1 * size1; + size_t alloc2 = nbAlloc2 * size2; + + // sort in descending order by: + // 1) total size + // 2) number of allocations + // + // This is used for sorting, not determination of equality, so we don't + // need to compare the bit flags. + if (alloc1 > alloc2) { + result = -1; + } else if (alloc1 < alloc2) { + result = 1; } else { - size_t nbAlloc1 = e1->allocations; - size_t nbAlloc2 = e2->allocations; - size_t size1 = e1->size & ~SIZE_FLAG_MASK; - size_t size2 = e2->size & ~SIZE_FLAG_MASK; - size_t alloc1 = nbAlloc1 * size1; - size_t alloc2 = nbAlloc2 * size2; - - // sort in descending order by: - // 1) total size - // 2) number of allocations - // - // This is used for sorting, not determination of equality, so we don't - // need to compare the bit flags. - if (alloc1 > alloc2) { - result = -1; - } else if (alloc1 < alloc2) { - result = 1; - } else { - if (nbAlloc1 > nbAlloc2) { - result = -1; - } else if (nbAlloc1 < nbAlloc2) { - result = 1; - } else { - result = 0; - } - } + if (nbAlloc1 > nbAlloc2) { + result = -1; + } else if (nbAlloc1 < nbAlloc2) { + result = 1; + } else { + result = 0; + } } - return result; + } + return result; } -/* - * Retrieve native heap information. - * - * "*info" is set to a buffer we allocate - * "*overallSize" is set to the size of the "info" buffer - * "*infoSize" is set to the size of a single entry - * "*totalMemory" is set to the sum of all allocations we're tracking; does - * not include heap overhead - * "*backtraceSize" is set to the maximum number of entries in the back trace - */ +// Retrieve native heap information. +// +// "*info" is set to a buffer we allocate +// "*overallSize" is set to the size of the "info" buffer +// "*infoSize" is set to the size of a single entry +// "*totalMemory" is set to the sum of all allocations we're tracking; does +// not include heap overhead +// "*backtraceSize" is set to the maximum number of entries in the back trace +// ============================================================================= // Exported for use by ddms. +// ============================================================================= extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize, - size_t* infoSize, size_t* totalMemory, size_t* backtraceSize) { - // don't do anything if we have invalid arguments - if (info == NULL || overallSize == NULL || infoSize == NULL || - totalMemory == NULL || backtraceSize == NULL) { - return; + size_t* infoSize, size_t* totalMemory, size_t* backtraceSize) { + // Don't do anything if we have invalid arguments. + if (info == NULL || overallSize == NULL || infoSize == NULL || + totalMemory == NULL || backtraceSize == NULL) { + return; + } + *totalMemory = 0; + + ScopedPthreadMutexLocker locker(&g_hash_table.lock); + if (g_hash_table.count == 0) { + *info = NULL; + *overallSize = 0; + *infoSize = 0; + *backtraceSize = 0; + return; + } + + HashEntry** list = static_cast(Malloc(malloc)(sizeof(void*) * g_hash_table.count)); + + // Get the entries into an array to be sorted. + size_t index = 0; + for (size_t i = 0 ; i < HASHTABLE_SIZE ; ++i) { + HashEntry* entry = g_hash_table.slots[i]; + while (entry != NULL) { + list[index] = entry; + *totalMemory = *totalMemory + ((entry->size & ~SIZE_FLAG_MASK) * entry->allocations); + index++; + entry = entry->next; } - *totalMemory = 0; + } - ScopedPthreadMutexLocker locker(&g_hash_table.lock); - - if (g_hash_table.count == 0) { - *info = NULL; - *overallSize = 0; - *infoSize = 0; - *backtraceSize = 0; - return; - } - - HashEntry** list = static_cast(Malloc(malloc)(sizeof(void*) * g_hash_table.count)); - - // get the entries into an array to be sorted - int index = 0; - for (size_t i = 0 ; i < HASHTABLE_SIZE ; ++i) { - HashEntry* entry = g_hash_table.slots[i]; - while (entry != NULL) { - list[index] = entry; - *totalMemory = *totalMemory + - ((entry->size & ~SIZE_FLAG_MASK) * entry->allocations); - index++; - entry = entry->next; - } - } - - // XXX: the protocol doesn't allow variable size for the stack trace (yet) - *infoSize = (sizeof(size_t) * 2) + (sizeof(uintptr_t) * BACKTRACE_SIZE); - *overallSize = *infoSize * g_hash_table.count; - *backtraceSize = BACKTRACE_SIZE; - - // now get a byte array big enough for this - *info = static_cast(Malloc(malloc)(*overallSize)); - - if (*info == NULL) { - *overallSize = 0; - Malloc(free)(list); - return; - } - - qsort(list, g_hash_table.count, sizeof(void*), hash_entry_compare); - - uint8_t* head = *info; - const int count = g_hash_table.count; - for (int i = 0 ; i < count ; ++i) { - HashEntry* entry = list[i]; - size_t entrySize = (sizeof(size_t) * 2) + (sizeof(uintptr_t) * entry->numEntries); - if (entrySize < *infoSize) { - /* we're writing less than a full entry, clear out the rest */ - memset(head + entrySize, 0, *infoSize - entrySize); - } else { - /* make sure the amount we're copying doesn't exceed the limit */ - entrySize = *infoSize; - } - memcpy(head, &(entry->size), entrySize); - head += *infoSize; - } + // XXX: the protocol doesn't allow variable size for the stack trace (yet) + *infoSize = (sizeof(size_t) * 2) + (sizeof(uintptr_t) * BACKTRACE_SIZE); + *overallSize = *infoSize * g_hash_table.count; + *backtraceSize = BACKTRACE_SIZE; + // now get a byte array big enough for this + *info = static_cast(Malloc(malloc)(*overallSize)); + if (*info == NULL) { + *overallSize = 0; Malloc(free)(list); + return; + } + + qsort(list, g_hash_table.count, sizeof(void*), hash_entry_compare); + + uint8_t* head = *info; + const size_t count = g_hash_table.count; + for (size_t i = 0 ; i < count ; ++i) { + HashEntry* entry = list[i]; + size_t entrySize = (sizeof(size_t) * 2) + (sizeof(uintptr_t) * entry->numEntries); + if (entrySize < *infoSize) { + // We're writing less than a full entry, clear out the rest. + memset(head + entrySize, 0, *infoSize - entrySize); + } else { + // Make sure the amount we're copying doesn't exceed the limit. + entrySize = *infoSize; + } + memcpy(head, &(entry->size), entrySize); + head += *infoSize; + } + + Malloc(free)(list); } -// Exported for use by ddms. extern "C" void free_malloc_leak_info(uint8_t* info) { - Malloc(free)(info); + Malloc(free)(info); } -extern "C" struct mallinfo mallinfo() { - return Malloc(mallinfo)(); -} - -extern "C" void* valloc(size_t bytes) { - return Malloc(valloc)(bytes); -} - -extern "C" void* pvalloc(size_t bytes) { - return Malloc(pvalloc)(bytes); -} - -extern "C" int posix_memalign(void** memptr, size_t alignment, size_t size) { - return Malloc(posix_memalign)(memptr, alignment, size); -} - -extern "C" void* malloc(size_t bytes) { - return __libc_malloc_dispatch->malloc(bytes); +// ============================================================================= +// Allocation functions +// ============================================================================= +extern "C" void* calloc(size_t n_elements, size_t elem_size) { + return __libc_malloc_dispatch->calloc(n_elements, elem_size); } extern "C" void free(void* mem) { - __libc_malloc_dispatch->free(mem); + __libc_malloc_dispatch->free(mem); } -extern "C" void* calloc(size_t n_elements, size_t elem_size) { - return __libc_malloc_dispatch->calloc(n_elements, elem_size); +extern "C" struct mallinfo mallinfo() { + return __libc_malloc_dispatch->mallinfo(); } -extern "C" void* realloc(void* oldMem, size_t bytes) { - return __libc_malloc_dispatch->realloc(oldMem, bytes); -} - -extern "C" void* memalign(size_t alignment, size_t bytes) { - return __libc_malloc_dispatch->memalign(alignment, bytes); +extern "C" void* malloc(size_t bytes) { + return __libc_malloc_dispatch->malloc(bytes); } extern "C" size_t malloc_usable_size(const void* mem) { - return __libc_malloc_dispatch->malloc_usable_size(mem); + return __libc_malloc_dispatch->malloc_usable_size(mem); } -/* We implement malloc debugging only in libc.so, so code below - * must be excluded if we compile this file for static libc.a - */ +extern "C" void* memalign(size_t alignment, size_t bytes) { + return __libc_malloc_dispatch->memalign(alignment, bytes); +} + +extern "C" int posix_memalign(void** memptr, size_t alignment, size_t size) { + return __libc_malloc_dispatch->posix_memalign(memptr, alignment, size); +} + +extern "C" void* pvalloc(size_t bytes) { + return __libc_malloc_dispatch->pvalloc(bytes); +} + +extern "C" void* realloc(void* oldMem, size_t bytes) { + return __libc_malloc_dispatch->realloc(oldMem, bytes); +} + +extern "C" void* valloc(size_t bytes) { + return __libc_malloc_dispatch->valloc(bytes); +} + +// We implement malloc debugging only in libc.so, so the code below +// must be excluded if we compile this file for static libc.a #ifndef LIBC_STATIC #include #include @@ -275,227 +280,222 @@ extern "C" size_t malloc_usable_size(const void* mem) { template static void InitMallocFunction(void* malloc_impl_handler, FunctionType* func, const char* prefix, const char* suffix) { - char symbol[128]; - snprintf(symbol, sizeof(symbol), "%s_%s", prefix, suffix); - *func = reinterpret_cast(dlsym(malloc_impl_handler, symbol)); - if (*func == NULL) { - error_log("%s: dlsym(\"%s\") failed", getprogname(), symbol); - } + char symbol[128]; + snprintf(symbol, sizeof(symbol), "%s_%s", prefix, suffix); + *func = reinterpret_cast(dlsym(malloc_impl_handler, symbol)); + if (*func == NULL) { + error_log("%s: dlsym(\"%s\") failed", getprogname(), symbol); + } } static void InitMalloc(void* malloc_impl_handler, MallocDebug* table, const char* prefix) { - __libc_format_log(ANDROID_LOG_INFO, "libc", "%s: using libc.debug.malloc %d (%s)\n", - getprogname(), g_malloc_debug_level, prefix); + __libc_format_log(ANDROID_LOG_INFO, "libc", "%s: using libc.debug.malloc %d (%s)\n", + getprogname(), g_malloc_debug_level, prefix); - InitMallocFunction(malloc_impl_handler, &table->malloc, prefix, "malloc"); - InitMallocFunction(malloc_impl_handler, &table->free, prefix, "free"); - InitMallocFunction(malloc_impl_handler, &table->calloc, prefix, "calloc"); - InitMallocFunction(malloc_impl_handler, &table->realloc, prefix, "realloc"); - InitMallocFunction(malloc_impl_handler, &table->memalign, prefix, "memalign"); - InitMallocFunction(malloc_impl_handler, &table->malloc_usable_size, prefix, "malloc_usable_size"); + InitMallocFunction(malloc_impl_handler, &table->calloc, prefix, "calloc"); + InitMallocFunction(malloc_impl_handler, &table->free, prefix, "free"); + InitMallocFunction(malloc_impl_handler, &table->mallinfo, prefix, "mallinfo"); + InitMallocFunction(malloc_impl_handler, &table->malloc, prefix, "malloc"); + InitMallocFunction(malloc_impl_handler, &table->malloc_usable_size, prefix, "malloc_usable_size"); + InitMallocFunction(malloc_impl_handler, &table->memalign, prefix, "memalign"); + InitMallocFunction(malloc_impl_handler, &table->posix_memalign, prefix, "posix_memalign"); + InitMallocFunction(malloc_impl_handler, &table->pvalloc, prefix, "pvalloc"); + InitMallocFunction(malloc_impl_handler, &table->realloc, prefix, "realloc"); + InitMallocFunction(malloc_impl_handler, &table->valloc, prefix, "valloc"); } -/* Initializes memory allocation framework once per process. */ +// Initializes memory allocation framework once per process. static void malloc_init_impl() { - const char* so_name = NULL; - MallocDebugInit malloc_debug_initialize = NULL; - unsigned int qemu_running = 0; - unsigned int memcheck_enabled = 0; - char env[PROP_VALUE_MAX]; - char memcheck_tracing[PROP_VALUE_MAX]; - char debug_program[PROP_VALUE_MAX]; + const char* so_name = NULL; + MallocDebugInit malloc_debug_initialize = NULL; + unsigned int qemu_running = 0; + unsigned int memcheck_enabled = 0; + char env[PROP_VALUE_MAX]; + char memcheck_tracing[PROP_VALUE_MAX]; + char debug_program[PROP_VALUE_MAX]; - /* Get custom malloc debug level. Note that emulator started with - * memory checking option will have priority over debug level set in - * libc.debug.malloc system property. */ - if (__system_property_get("ro.kernel.qemu", env) && atoi(env)) { - qemu_running = 1; - if (__system_property_get("ro.kernel.memcheck", memcheck_tracing)) { - if (memcheck_tracing[0] != '0') { - // Emulator has started with memory tracing enabled. Enforce it. - g_malloc_debug_level = 20; - memcheck_enabled = 1; - } - } + // Get custom malloc debug level. Note that emulator started with + // memory checking option will have priority over debug level set in + // libc.debug.malloc system property. + if (__system_property_get("ro.kernel.qemu", env) && atoi(env)) { + qemu_running = 1; + if (__system_property_get("ro.kernel.memcheck", memcheck_tracing)) { + if (memcheck_tracing[0] != '0') { + // Emulator has started with memory tracing enabled. Enforce it. + g_malloc_debug_level = 20; + memcheck_enabled = 1; + } } + } - /* If debug level has not been set by memcheck option in the emulator, - * lets grab it from libc.debug.malloc system property. */ - if (g_malloc_debug_level == 0 && __system_property_get("libc.debug.malloc", env)) { - g_malloc_debug_level = atoi(env); + // If debug level has not been set by memcheck option in the emulator, + // lets grab it from libc.debug.malloc system property. + if (g_malloc_debug_level == 0 && __system_property_get("libc.debug.malloc", env)) { + g_malloc_debug_level = atoi(env); + } + + // Debug level 0 means that we should use default allocation routines. + if (g_malloc_debug_level == 0) { + return; + } + + // If libc.debug.malloc.program is set and is not a substring of progname, + // then exit. + if (__system_property_get("libc.debug.malloc.program", debug_program)) { + if (!strstr(getprogname(), debug_program)) { + return; } + } - /* Debug level 0 means that we should use default allocation routines. */ - if (g_malloc_debug_level == 0) { - return; + // mksh is way too leaky. http://b/7291287. + if (g_malloc_debug_level >= 10) { + if (strcmp(getprogname(), "sh") == 0 || strcmp(getprogname(), "/system/bin/sh") == 0) { + return; } + } - /* If libc.debug.malloc.program is set and is not a substring of progname, - * then exit. - */ - if (__system_property_get("libc.debug.malloc.program", debug_program)) { - if (!strstr(getprogname(), debug_program)) { - return; - } - } - - // mksh is way too leaky. http://b/7291287. - if (g_malloc_debug_level >= 10) { - if (strcmp(getprogname(), "sh") == 0 || strcmp(getprogname(), "/system/bin/sh") == 0) { - return; - } - } - - // Choose the appropriate .so for the requested debug level. - switch (g_malloc_debug_level) { - case 1: - case 5: - case 10: - so_name = "libc_malloc_debug_leak.so"; - break; - case 20: - // Quick check: debug level 20 can only be handled in emulator. - if (!qemu_running) { - error_log("%s: Debug level %d can only be set in emulator\n", - getprogname(), g_malloc_debug_level); - return; - } - // Make sure that memory checking has been enabled in emulator. - if (!memcheck_enabled) { - error_log("%s: Memory checking is not enabled in the emulator\n", - getprogname()); - return; - } - so_name = "libc_malloc_debug_qemu.so"; - break; - default: - error_log("%s: Debug level %d is unknown\n", getprogname(), g_malloc_debug_level); - return; - } - - // Load .so that implements the required malloc debugging functionality. - void* malloc_impl_handle = dlopen(so_name, RTLD_LAZY); - if (malloc_impl_handle == NULL) { - error_log("%s: Missing module %s required for malloc debug level %d: %s", - getprogname(), so_name, g_malloc_debug_level, dlerror()); - return; - } - - // Initialize malloc debugging in the loaded module. - malloc_debug_initialize = reinterpret_cast(dlsym(malloc_impl_handle, - "malloc_debug_initialize")); - if (malloc_debug_initialize == NULL) { - error_log("%s: Initialization routine is not found in %s\n", - getprogname(), so_name); - dlclose(malloc_impl_handle); - return; - } - if (malloc_debug_initialize(&g_hash_table) == -1) { - dlclose(malloc_impl_handle); - return; - } - - if (g_malloc_debug_level == 20) { - // For memory checker we need to do extra initialization. - typedef int (*MemCheckInit)(int, const char*); - MemCheckInit memcheck_initialize = - reinterpret_cast(dlsym(malloc_impl_handle, - "memcheck_initialize")); - if (memcheck_initialize == NULL) { - error_log("%s: memcheck_initialize routine is not found in %s\n", - getprogname(), so_name); - dlclose(malloc_impl_handle); - return; - } - - if (memcheck_initialize(MALLOC_ALIGNMENT, memcheck_tracing)) { - dlclose(malloc_impl_handle); - return; - } - } - - // Initialize malloc dispatch table with appropriate routines. - static MallocDebug malloc_dispatch_table __attribute__((aligned(32))) = { - Malloc(malloc), - Malloc(free), - Malloc(calloc), - Malloc(realloc), - Malloc(memalign), - Malloc(malloc_usable_size) - }; - - switch (g_malloc_debug_level) { - case 1: - InitMalloc(malloc_impl_handle, &malloc_dispatch_table, "leak"); - break; - case 5: - InitMalloc(malloc_impl_handle, &malloc_dispatch_table, "fill"); - break; - case 10: - InitMalloc(malloc_impl_handle, &malloc_dispatch_table, "chk"); - break; - case 20: - InitMalloc(malloc_impl_handle, &malloc_dispatch_table, "qemu_instrumented"); - break; - default: - break; - } - - // Make sure dispatch table is initialized - if ((malloc_dispatch_table.malloc == NULL) || - (malloc_dispatch_table.free == NULL) || - (malloc_dispatch_table.calloc == NULL) || - (malloc_dispatch_table.realloc == NULL) || - (malloc_dispatch_table.memalign == NULL) || - (malloc_dispatch_table.malloc_usable_size == NULL)) { - error_log("%s: some symbols for libc.debug.malloc level %d were not found (see above)", + // Choose the appropriate .so for the requested debug level. + switch (g_malloc_debug_level) { + case 1: + case 5: + case 10: + so_name = "libc_malloc_debug_leak.so"; + break; + case 20: + // Quick check: debug level 20 can only be handled in emulator. + if (!qemu_running) { + error_log("%s: Debug level %d can only be set in emulator\n", getprogname(), g_malloc_debug_level); - dlclose(malloc_impl_handle); - } else { - __libc_malloc_dispatch = &malloc_dispatch_table; - libc_malloc_impl_handle = malloc_impl_handle; + return; + } + // Make sure that memory checking has been enabled in emulator. + if (!memcheck_enabled) { + error_log("%s: Memory checking is not enabled in the emulator\n", getprogname()); + return; + } + so_name = "libc_malloc_debug_qemu.so"; + break; + default: + error_log("%s: Debug level %d is unknown\n", getprogname(), g_malloc_debug_level); + return; + } + + // Load .so that implements the required malloc debugging functionality. + void* malloc_impl_handle = dlopen(so_name, RTLD_LAZY); + if (malloc_impl_handle == NULL) { + error_log("%s: Missing module %s required for malloc debug level %d: %s", + getprogname(), so_name, g_malloc_debug_level, dlerror()); + return; + } + + // Initialize malloc debugging in the loaded module. + malloc_debug_initialize = reinterpret_cast(dlsym(malloc_impl_handle, + "malloc_debug_initialize")); + if (malloc_debug_initialize == NULL) { + error_log("%s: Initialization routine is not found in %s\n", getprogname(), so_name); + dlclose(malloc_impl_handle); + return; + } + if (malloc_debug_initialize(&g_hash_table) == -1) { + dlclose(malloc_impl_handle); + return; + } + + if (g_malloc_debug_level == 20) { + // For memory checker we need to do extra initialization. + typedef int (*MemCheckInit)(int, const char*); + MemCheckInit memcheck_initialize = + reinterpret_cast(dlsym(malloc_impl_handle, "memcheck_initialize")); + if (memcheck_initialize == NULL) { + error_log("%s: memcheck_initialize routine is not found in %s\n", + getprogname(), so_name); + dlclose(malloc_impl_handle); + return; } + + if (memcheck_initialize(MALLOC_ALIGNMENT, memcheck_tracing)) { + dlclose(malloc_impl_handle); + return; + } + } + + // No need to init the dispatch table because we can only get + // here if debug level is 1, 5, 10, or 20. + static MallocDebug malloc_dispatch_table __attribute__((aligned(32))); + switch (g_malloc_debug_level) { + case 1: + InitMalloc(malloc_impl_handle, &malloc_dispatch_table, "leak"); + break; + case 5: + InitMalloc(malloc_impl_handle, &malloc_dispatch_table, "fill"); + break; + case 10: + InitMalloc(malloc_impl_handle, &malloc_dispatch_table, "chk"); + break; + case 20: + InitMalloc(malloc_impl_handle, &malloc_dispatch_table, "qemu_instrumented"); + break; + default: + break; + } + + // Make sure dispatch table is initialized + if ((malloc_dispatch_table.calloc == NULL) || + (malloc_dispatch_table.free == NULL) || + (malloc_dispatch_table.mallinfo == NULL) || + (malloc_dispatch_table.malloc == NULL) || + (malloc_dispatch_table.malloc_usable_size == NULL) || + (malloc_dispatch_table.memalign == NULL) || + (malloc_dispatch_table.posix_memalign == NULL) || + (malloc_dispatch_table.pvalloc == NULL) || + (malloc_dispatch_table.realloc == NULL) || + (malloc_dispatch_table.valloc == NULL)) { + error_log("%s: some symbols for libc.debug.malloc level %d were not found (see above)", + getprogname(), g_malloc_debug_level); + dlclose(malloc_impl_handle); + } else { + __libc_malloc_dispatch = &malloc_dispatch_table; + libc_malloc_impl_handle = malloc_impl_handle; + } } static void malloc_fini_impl() { - // Our BSD stdio implementation doesn't close the standard streams, it only flushes them. - // And it doesn't do that until its atexit handler is run, and we run first! - // It's great that other unclosed FILE*s show up as malloc leaks, but we need to manually - // clean up the standard streams ourselves. - fclose(stdin); - fclose(stdout); - fclose(stderr); + // Our BSD stdio implementation doesn't close the standard streams, it only flushes them. + // And it doesn't do that until its atexit handler is run, and we run first! + // It's great that other unclosed FILE*s show up as malloc leaks, but we need to manually + // clean up the standard streams ourselves. + fclose(stdin); + fclose(stdout); + fclose(stderr); - if (libc_malloc_impl_handle != NULL) { - MallocDebugFini malloc_debug_finalize = - reinterpret_cast(dlsym(libc_malloc_impl_handle, - "malloc_debug_finalize")); - if (malloc_debug_finalize != NULL) { - malloc_debug_finalize(g_malloc_debug_level); - } + if (libc_malloc_impl_handle != NULL) { + MallocDebugFini malloc_debug_finalize = + reinterpret_cast(dlsym(libc_malloc_impl_handle, "malloc_debug_finalize")); + if (malloc_debug_finalize != NULL) { + malloc_debug_finalize(g_malloc_debug_level); } + } } #endif // !LIBC_STATIC -/* Initializes memory allocation framework. - * This routine is called from __libc_init routines implemented - * in libc_init_static.c and libc_init_dynamic.c files. - */ +// Initializes memory allocation framework. +// This routine is called from __libc_init routines implemented +// in libc_init_static.c and libc_init_dynamic.c files. extern "C" __LIBC_HIDDEN__ void malloc_debug_init() { -#if defined(USE_DL_PREFIX) && !defined(LIBC_STATIC) +#if !defined(LIBC_STATIC) static pthread_once_t malloc_init_once_ctl = PTHREAD_ONCE_INIT; if (pthread_once(&malloc_init_once_ctl, malloc_init_impl)) { error_log("Unable to initialize malloc_debug component."); } -#endif // USE_DL_PREFIX && !LIBC_STATIC +#endif // !LIBC_STATIC } extern "C" __LIBC_HIDDEN__ void malloc_debug_fini() { -#if defined(USE_DL_PREFIX) && !defined(LIBC_STATIC) +#if !defined(LIBC_STATIC) static pthread_once_t malloc_fini_once_ctl = PTHREAD_ONCE_INIT; if (pthread_once(&malloc_fini_once_ctl, malloc_fini_impl)) { error_log("Unable to finalize malloc_debug component."); } -#endif // USE_DL_PREFIX && !LIBC_STATIC +#endif // !LIBC_STATIC } diff --git a/libc/bionic/malloc_debug_common.h b/libc/bionic/malloc_debug_common.h index 21cb44cff..8052a1775 100644 --- a/libc/bionic/malloc_debug_common.h +++ b/libc/bionic/malloc_debug_common.h @@ -83,19 +83,27 @@ struct HashTable { }; /* Entry in malloc dispatch table. */ -typedef void* (*MallocDebugMalloc)(size_t); -typedef void (*MallocDebugFree)(void*); typedef void* (*MallocDebugCalloc)(size_t, size_t); -typedef void* (*MallocDebugRealloc)(void*, size_t); -typedef void* (*MallocDebugMemalign)(size_t, size_t); +typedef void (*MallocDebugFree)(void*); +typedef struct mallinfo (*MallocDebugMallinfo)(); +typedef void* (*MallocDebugMalloc)(size_t); typedef size_t (*MallocDebugMallocUsableSize)(const void*); +typedef void* (*MallocDebugMemalign)(size_t, size_t); +typedef int (*MallocDebugPosixMemalign)(void**, size_t, size_t); +typedef void* (*MallocDebugPvalloc)(size_t); +typedef void* (*MallocDebugRealloc)(void*, size_t); +typedef void* (*MallocDebugValloc)(size_t); struct MallocDebug { - MallocDebugMalloc malloc; - MallocDebugFree free; MallocDebugCalloc calloc; - MallocDebugRealloc realloc; - MallocDebugMemalign memalign; + MallocDebugFree free; + MallocDebugMallinfo mallinfo; + MallocDebugMalloc malloc; MallocDebugMallocUsableSize malloc_usable_size; + MallocDebugMemalign memalign; + MallocDebugPosixMemalign posix_memalign; + MallocDebugPvalloc pvalloc; + MallocDebugRealloc realloc; + MallocDebugValloc valloc; }; typedef bool (*MallocDebugInit)(HashTable*); diff --git a/libc/bionic/malloc_debug_leak.cpp b/libc/bionic/malloc_debug_leak.cpp index aa7c072d7..2cc38ccf5 100644 --- a/libc/bionic/malloc_debug_leak.cpp +++ b/libc/bionic/malloc_debug_leak.cpp @@ -250,6 +250,33 @@ extern "C" size_t fill_malloc_usable_size(const void* mem) { return Malloc(malloc_usable_size)(mem); } +extern "C" struct mallinfo fill_mallinfo() { + return Malloc(mallinfo)(); +} + +extern "C" int fill_posix_memalign(void** memptr, size_t alignment, size_t size) { + if ((alignment & (alignment - 1)) != 0) { + return EINVAL; + } + int saved_errno = errno; + *memptr = fill_memalign(alignment, size); + errno = saved_errno; + return (*memptr != NULL) ? 0 : ENOMEM; +} + +extern "C" void* fill_pvalloc(size_t bytes) { + size_t pagesize = sysconf(_SC_PAGESIZE); + size_t size = (bytes + pagesize - 1) & ~(pagesize - 1); + if (size < bytes) { // Overflow + return NULL; + } + return fill_memalign(pagesize, size); +} + +extern "C" void* fill_valloc(size_t size) { + return fill_memalign(sysconf(_SC_PAGESIZE), size); +} + // ============================================================================= // malloc leak functions // ============================================================================= @@ -265,6 +292,7 @@ extern "C" void* leak_malloc(size_t bytes) { size_t size = bytes + sizeof(AllocationEntry); if (size < bytes) { // Overflow. + errno = ENOMEM; return NULL; } @@ -327,6 +355,7 @@ extern "C" void* leak_calloc(size_t n_elements, size_t elem_size) { // Fail on overflow - just to be safe even though this code runs only // within the debugging C library, not the production one. if (n_elements && SIZE_MAX / n_elements < elem_size) { + errno = ENOMEM; return NULL; } size_t size = n_elements * elem_size; @@ -350,6 +379,7 @@ extern "C" void* leak_realloc(void* oldMem, size_t bytes) { } else if (header->guard != GUARD) { debug_log("WARNING bad header guard: '0x%x'! and invalid entry: %p\n", header->guard, header->entry); + errno = ENOMEM; return NULL; } @@ -358,8 +388,8 @@ extern "C" void* leak_realloc(void* oldMem, size_t bytes) { size_t oldSize = header->entry->size & ~SIZE_FLAG_MASK; size_t copySize = (oldSize <= bytes) ? oldSize : bytes; memcpy(newMem, oldMem, copySize); + leak_free(oldMem); } - leak_free(oldMem); return newMem; } @@ -428,3 +458,30 @@ extern "C" size_t leak_malloc_usable_size(const void* mem) { } return 0; } + +extern "C" struct mallinfo leak_mallinfo() { + return Malloc(mallinfo)(); +} + +extern "C" int leak_posix_memalign(void** memptr, size_t alignment, size_t size) { + if ((alignment & (alignment - 1)) != 0) { + return EINVAL; + } + int saved_errno = errno; + *memptr = leak_memalign(alignment, size); + errno = saved_errno; + return (*memptr != NULL) ? 0 : ENOMEM; +} + +extern "C" void* leak_pvalloc(size_t bytes) { + size_t pagesize = sysconf(_SC_PAGESIZE); + size_t size = (bytes + pagesize - 1) & ~(pagesize - 1); + if (size < bytes) { // Overflow + return NULL; + } + return leak_memalign(pagesize, size); +} + +extern "C" void* leak_valloc(size_t size) { + return leak_memalign(sysconf(_SC_PAGESIZE), size); +} diff --git a/libc/bionic/malloc_debug_qemu.cpp b/libc/bionic/malloc_debug_qemu.cpp index 2dda7679b..427248268 100644 --- a/libc/bionic/malloc_debug_qemu.cpp +++ b/libc/bionic/malloc_debug_qemu.cpp @@ -573,12 +573,16 @@ static void test_access_violation(const MallocDesc* desc) { // API routines // ============================================================================= -extern "C" void* qemu_instrumented_malloc(size_t bytes); -extern "C" void qemu_instrumented_free(void* mem); -extern "C" void* qemu_instrumented_calloc(size_t n_elements, size_t elem_size); -extern "C" void* qemu_instrumented_realloc(void* mem, size_t bytes); -extern "C" void* qemu_instrumented_memalign(size_t alignment, size_t bytes); -extern "C" size_t qemu_instrumented_malloc_usable_size(const void* mem); +extern "C" void* qemu_instrumented_calloc(size_t, size_t); +extern "C" void qemu_instrumented_free(void*); +extern "C" struct mallinfo qemu_instrumented_mallinfo(); +extern "C" void* qemu_instrumented_malloc(size_t); +extern "C" size_t qemu_instrumented_malloc_usable_size(const void*); +extern "C" void* qemu_instrumented_memalign(size_t, size_t); +extern "C" int qemu_instrumented_posix_memalign(void**, size_t, size_t); +extern "C" void* qemu_instrumented_pvalloc(size_t); +extern "C" void* qemu_instrumented_realloc(void*, size_t); +extern "C" void* qemu_instrumented_valloc(size_t); /* Initializes malloc debugging instrumentation for the emulator. * This routine is called from malloc_init_impl routine implemented in @@ -680,10 +684,17 @@ extern "C" void* qemu_instrumented_malloc(size_t bytes) { desc.prefix_size = DEFAULT_PREFIX_SIZE; desc.requested_bytes = bytes; desc.suffix_size = DEFAULT_SUFFIX_SIZE; - desc.ptr = Malloc(malloc)(mallocdesc_alloc_size(&desc)); + size_t size = mallocdesc_alloc_size(&desc); + if (size < bytes) { // Overflow + qemu_error_log(" malloc: malloc(%zu) overflow caused failure.", + malloc_pid, getpid(), bytes); + errno = ENOMEM; + return NULL; + } + desc.ptr = Malloc(malloc)(size); if (desc.ptr == NULL) { - qemu_error_log(" malloc(%zd): malloc(%u) failed.", - malloc_pid, getpid(), bytes, mallocdesc_alloc_size(&desc)); + qemu_error_log(" malloc(%zu): malloc(%u) failed.", + malloc_pid, getpid(), bytes, size); return NULL; } @@ -692,12 +703,13 @@ extern "C" void* qemu_instrumented_malloc(size_t bytes) { log_mdesc(error, &desc, ": malloc: notify_malloc failed for ", malloc_pid, getpid()); Malloc(free)(desc.ptr); + errno = ENOMEM; return NULL; } else { #if TEST_ACCESS_VIOLATIONS test_access_violation(&desc); #endif // TEST_ACCESS_VIOLATIONS - log_mdesc(info, &desc, "+++ malloc(%zd) -> ", + log_mdesc(info, &desc, "+++ malloc(%zu) -> ", malloc_pid, getpid(), bytes); return mallocdesc_user_ptr(&desc); } @@ -754,13 +766,16 @@ extern "C" void* qemu_instrumented_calloc(size_t n_elements, size_t elem_size) { if (n_elements == 0 || elem_size == 0) { // Just let go zero bytes allocation. qemu_info_log("::: : Zero calloc redir to malloc", - malloc_pid, getpid()); + malloc_pid, getpid()); return qemu_instrumented_malloc(0); } // Fail on overflow - just to be safe even though this code runs only // within the debugging C library, not the production one. if (n_elements && SIZE_MAX / n_elements < elem_size) { + qemu_error_log(" calloc: calloc(%zu, %zu) overflow caused failure.", + malloc_pid, getpid(), n_elements, elem_size); + errno = ENOMEM; return NULL; } @@ -786,6 +801,12 @@ extern "C" void* qemu_instrumented_calloc(size_t n_elements, size_t elem_size) { } desc.requested_bytes = n_elements * elem_size; size_t total_size = desc.requested_bytes + desc.prefix_size + desc.suffix_size; + if (total_size < desc.requested_bytes) { // Overflow + qemu_error_log(" calloc: calloc(%zu, %zu) overflow caused failure.", + malloc_pid, getpid(), n_elements, elem_size); + errno = ENOMEM; + return NULL; + } size_t total_elements = total_size / elem_size; total_size %= elem_size; if (total_size != 0) { @@ -795,22 +816,23 @@ extern "C" void* qemu_instrumented_calloc(size_t n_elements, size_t elem_size) { } desc.ptr = Malloc(calloc)(total_elements, elem_size); if (desc.ptr == NULL) { - error_log(" calloc: calloc(%zd(%zd), %zd) (prx=%u, sfx=%u) failed.", + error_log(" calloc: calloc(%zu(%zu), %zu) (prx=%u, sfx=%u) failed.", malloc_pid, getpid(), n_elements, total_elements, elem_size, desc.prefix_size, desc.suffix_size); return NULL; } if (notify_qemu_malloc(&desc)) { - log_mdesc(error, &desc, ": calloc(%zd(%zd), %zd): notify_malloc failed for ", + log_mdesc(error, &desc, ": calloc(%zu(%zu), %zu): notify_malloc failed for ", malloc_pid, getpid(), n_elements, total_elements, elem_size); Malloc(free)(desc.ptr); + errno = ENOMEM; return NULL; } else { #if TEST_ACCESS_VIOLATIONS test_access_violation(&desc); #endif // TEST_ACCESS_VIOLATIONS - log_mdesc(info, &desc, "### calloc(%zd(%zd), %zd) -> ", + log_mdesc(info, &desc, "### calloc(%zu(%zu), %zu) -> ", malloc_pid, getpid(), n_elements, total_elements, elem_size); return mallocdesc_user_ptr(&desc); } @@ -823,22 +845,17 @@ extern "C" void* qemu_instrumented_calloc(size_t n_elements, size_t elem_size) { * should not expect that pointer returned after shrinking will remain the same. */ extern "C" void* qemu_instrumented_realloc(void* mem, size_t bytes) { - MallocDesc new_desc; - MallocDesc cur_desc; - size_t to_copy; - void* ret; - if (mem == NULL) { // Nothing to realloc. just do regular malloc. - qemu_info_log("::: : realloc(%p, %zd) redir to malloc", - malloc_pid, getpid(), mem, bytes); + qemu_info_log("::: : realloc(%p, %zu) redir to malloc", + malloc_pid, getpid(), mem, bytes); return qemu_instrumented_malloc(bytes); } if (bytes == 0) { // This is a "free" condition. - qemu_info_log("::: : realloc(%p, %zd) redir to free and malloc", - malloc_pid, getpid(), mem, bytes); + qemu_info_log("::: : realloc(%p, %zu) redir to free and malloc", + malloc_pid, getpid(), mem, bytes); qemu_instrumented_free(mem); // This is what realloc does for a "free" realloc. @@ -846,10 +863,12 @@ extern "C" void* qemu_instrumented_realloc(void* mem, size_t bytes) { } // Query emulator for the reallocating block information. + MallocDesc cur_desc; if (query_qemu_malloc_info(mem, &cur_desc, 2)) { // Note that this violation should be already caught in the emulator. - error_log(": realloc(%p, %zd) query_info failed.", + error_log(": realloc(%p, %zu) query_info failed.", malloc_pid, getpid(), mem, bytes); + errno = ENOMEM; return NULL; } @@ -861,8 +880,9 @@ extern "C" void* qemu_instrumented_realloc(void* mem, size_t bytes) { * for this memory block. Note that this violation should be already caught * in the emulator.*/ if (mem != mallocdesc_user_ptr(&cur_desc)) { - log_mdesc(error, &cur_desc, ": realloc(%p, %zd) is invalid for ", + log_mdesc(error, &cur_desc, ": realloc(%p, %zu) is invalid for ", malloc_pid, getpid(), mem, bytes); + errno = ENOMEM; return NULL; } @@ -872,31 +892,38 @@ extern "C" void* qemu_instrumented_realloc(void* mem, size_t bytes) { * for this block that is stored in the emulator. */ // Initialize descriptor for the new block. + MallocDesc new_desc; new_desc.prefix_size = DEFAULT_PREFIX_SIZE; new_desc.requested_bytes = bytes; new_desc.suffix_size = DEFAULT_SUFFIX_SIZE; - new_desc.ptr = Malloc(malloc)(mallocdesc_alloc_size(&new_desc)); - if (new_desc.ptr == NULL) { - log_mdesc(error, &cur_desc, ": realloc(%p, %zd): malloc(%u) failed on ", - malloc_pid, getpid(), mem, bytes, - mallocdesc_alloc_size(&new_desc)); + size_t new_size = mallocdesc_alloc_size(&new_desc); + if (new_size < bytes) { // Overflow + qemu_error_log(": realloc(%p, %zu): malloc(%u) failed due to overflow", + malloc_pid, getpid(), mem, bytes, new_size); + errno = ENOMEM; return NULL; } - ret = mallocdesc_user_ptr(&new_desc); + new_desc.ptr = Malloc(malloc)(new_size); + if (new_desc.ptr == NULL) { + log_mdesc(error, &cur_desc, ": realloc(%p, %zu): malloc(%u) failed on ", + malloc_pid, getpid(), mem, bytes, new_size); + return NULL; + } + void* new_mem = mallocdesc_user_ptr(&new_desc); // Copy user data from old block to the new one. - to_copy = bytes < cur_desc.requested_bytes ? bytes : - cur_desc.requested_bytes; + size_t to_copy = bytes < cur_desc.requested_bytes ? bytes : cur_desc.requested_bytes; if (to_copy != 0) { - memcpy(ret, mallocdesc_user_ptr(&cur_desc), to_copy); + memcpy(new_mem, mallocdesc_user_ptr(&cur_desc), to_copy); } // Register new block with emulator. if (notify_qemu_malloc(&new_desc)) { - log_mdesc(error, &new_desc, ": realloc(%p, %zd) notify_malloc failed -> ", + log_mdesc(error, &new_desc, ": realloc(%p, %zu) notify_malloc failed -> ", malloc_pid, getpid(), mem, bytes); log_mdesc(error, &cur_desc, " <- "); Malloc(free)(new_desc.ptr); + errno = ENOMEM; return NULL; } @@ -906,21 +933,22 @@ extern "C" void* qemu_instrumented_realloc(void* mem, size_t bytes) { // Free old block. if (notify_qemu_free(mem)) { - log_mdesc(error, &cur_desc, ": realloc(%p, %zd): notify_free failed for ", + log_mdesc(error, &cur_desc, ": realloc(%p, %zu): notify_free failed for ", malloc_pid, getpid(), mem, bytes); /* Since we registered new decriptor with the emulator, we need * to unregister it before freeing newly allocated block. */ notify_qemu_free(mallocdesc_user_ptr(&new_desc)); Malloc(free)(new_desc.ptr); + errno = ENOMEM; return NULL; } Malloc(free)(cur_desc.ptr); - log_mdesc(info, &new_desc, "=== : realloc(%p, %zd) -> ", + log_mdesc(info, &new_desc, "=== : realloc(%p, %zu) -> ", malloc_pid, getpid(), mem, bytes); log_mdesc(info, &cur_desc, " <- "); - return ret; + return new_mem; } /* This routine serves as entry point for 'memalign'. @@ -931,28 +959,38 @@ extern "C" void* qemu_instrumented_memalign(size_t alignment, size_t bytes) { if (bytes == 0) { // Just let go zero bytes allocation. - qemu_info_log("::: : memalign(%zx, %zd) redir to malloc", + qemu_info_log("::: : memalign(%zx, %zu) redir to malloc", malloc_pid, getpid(), alignment, bytes); return qemu_instrumented_malloc(0); } - /* Prefix size for aligned allocation must be equal to the alignment used - * for allocation in order to ensure proper alignment of the returned - * pointer, in case that alignment requirement is greater than prefix - * size. */ - desc.prefix_size = alignment > DEFAULT_PREFIX_SIZE ? alignment : - DEFAULT_PREFIX_SIZE; + // Prefix size for aligned allocation must be equal to the alignment used + // for allocation in order to ensure proper alignment of the returned + // pointer. in case that alignment requirement is greater than prefix + // size. + if (alignment < DEFAULT_PREFIX_SIZE) { + alignment = DEFAULT_PREFIX_SIZE; + } else if (alignment & (alignment - 1)) { + alignment = 1L << (31 - __builtin_clz(alignment)); + } + desc.prefix_size = alignment; desc.requested_bytes = bytes; desc.suffix_size = DEFAULT_SUFFIX_SIZE; - desc.ptr = Malloc(memalign)(desc.prefix_size, mallocdesc_alloc_size(&desc)); + size_t size = mallocdesc_alloc_size(&desc); + if (size < bytes) { // Overflow + qemu_error_log(" memalign(%zx, %zu): malloc(%u) failed due to overflow.", + malloc_pid, getpid(), alignment, bytes, size); + + return NULL; + } + desc.ptr = Malloc(memalign)(desc.prefix_size, size); if (desc.ptr == NULL) { - error_log(" memalign(%zx, %zd): malloc(%u) failed.", - malloc_pid, getpid(), alignment, bytes, - mallocdesc_alloc_size(&desc)); + error_log(" memalign(%zx, %zu): malloc(%u) failed.", + malloc_pid, getpid(), alignment, bytes, size); return NULL; } if (notify_qemu_malloc(&desc)) { - log_mdesc(error, &desc, ": memalign(%zx, %zd): notify_malloc failed for ", + log_mdesc(error, &desc, ": memalign(%zx, %zu): notify_malloc failed for ", malloc_pid, getpid(), alignment, bytes); Malloc(free)(desc.ptr); return NULL; @@ -962,7 +1000,7 @@ extern "C" void* qemu_instrumented_memalign(size_t alignment, size_t bytes) { test_access_violation(&desc); #endif // TEST_ACCESS_VIOLATIONS - log_mdesc(info, &desc, "@@@ memalign(%zx, %zd) -> ", + log_mdesc(info, &desc, "@@@ memalign(%zx, %zu) -> ", malloc_pid, getpid(), alignment, bytes); return mallocdesc_user_ptr(&desc); } @@ -990,3 +1028,34 @@ extern "C" size_t qemu_instrumented_malloc_usable_size(const void* mem) { /* during instrumentation, we can't really report anything more than requested_bytes */ return cur_desc.requested_bytes; } + +extern "C" struct mallinfo qemu_instrumented_mallinfo() { + return Malloc(mallinfo)(); +} + +extern "C" int qemu_instrumented_posix_memalign(void** memptr, size_t alignment, size_t size) { + if ((alignment & (alignment - 1)) != 0) { + qemu_error_log(" posix_memalign(%p, %zu, %zu): invalid alignment.", + malloc_pid, getpid(), memptr, alignment, size); + return EINVAL; + } + int saved_errno = errno; + *memptr = qemu_instrumented_memalign(alignment, size); + errno = saved_errno; + return (*memptr != NULL) ? 0 : ENOMEM; +} + +extern "C" void* qemu_instrumented_pvalloc(size_t bytes) { + size_t pagesize = sysconf(_SC_PAGESIZE); + size_t size = (bytes + pagesize - 1) & ~(pagesize - 1); + if (size < bytes) { // Overflow + qemu_error_log(" pvalloc(%zu): overflow (%zu).", + malloc_pid, getpid(), bytes, size); + return NULL; + } + return qemu_instrumented_memalign(pagesize, size); +} + +extern "C" void* qemu_instrumented_valloc(size_t size) { + return qemu_instrumented_memalign(sysconf(_SC_PAGESIZE), size); +} diff --git a/libc/upstream-dlmalloc/malloc.c b/libc/upstream-dlmalloc/malloc.c index 3ef9b61a6..4362f49ff 100644 --- a/libc/upstream-dlmalloc/malloc.c +++ b/libc/upstream-dlmalloc/malloc.c @@ -5317,12 +5317,19 @@ void* dlvalloc(size_t bytes) { return dlmemalign(pagesz, bytes); } +/* BEGIN android-changed: added overflow check */ void* dlpvalloc(size_t bytes) { size_t pagesz; + size_t size; ensure_initialization(); pagesz = mparams.page_size; - return dlmemalign(pagesz, (bytes + pagesz - SIZE_T_ONE) & ~(pagesz - SIZE_T_ONE)); + size = (bytes + pagesz - SIZE_T_ONE) & ~(pagesz - SIZE_T_ONE); + if (size < bytes) { + return NULL; + } + return dlmemalign(pagesz, size); } +/* END android-change */ void** dlindependent_calloc(size_t n_elements, size_t elem_size, void* chunks[]) { diff --git a/tests/malloc_test.cpp b/tests/malloc_test.cpp index 12a5ffab8..ed98f15d3 100644 --- a/tests/malloc_test.cpp +++ b/tests/malloc_test.cpp @@ -16,18 +16,26 @@ #include +#include +#include #include #include +#include TEST(malloc, malloc_std) { // Simple malloc test. void *ptr = malloc(100); ASSERT_TRUE(ptr != NULL); ASSERT_LE(100U, malloc_usable_size(ptr)); - free(ptr); } +TEST(malloc, malloc_overflow) { + errno = 0; + ASSERT_EQ(NULL, malloc(SIZE_MAX)); + ASSERT_EQ(ENOMEM, errno); +} + TEST(malloc, calloc_std) { // Simple calloc test. size_t alloc_len = 100; @@ -37,24 +45,67 @@ TEST(malloc, calloc_std) { for (size_t i = 0; i < alloc_len; i++) { ASSERT_EQ(0, ptr[i]); } - free(ptr); } +TEST(malloc, calloc_illegal) { + errno = 0; + ASSERT_EQ(NULL, calloc(-1, 100)); + ASSERT_EQ(ENOMEM, errno); +} + +TEST(malloc, calloc_overflow) { + errno = 0; + ASSERT_EQ(NULL, calloc(1, SIZE_MAX)); + ASSERT_EQ(ENOMEM, errno); + errno = 0; + ASSERT_EQ(NULL, calloc(SIZE_MAX, SIZE_MAX)); + ASSERT_EQ(ENOMEM, errno); + errno = 0; + ASSERT_EQ(NULL, calloc(2, SIZE_MAX)); + ASSERT_EQ(ENOMEM, errno); + errno = 0; + ASSERT_EQ(NULL, calloc(SIZE_MAX, 2)); + ASSERT_EQ(ENOMEM, errno); +} + TEST(malloc, memalign_multiple) { // Memalign test where the alignment is any value. for (size_t i = 0; i <= 12; i++) { for (size_t alignment = 1 << i; alignment < (1U << (i+1)); alignment++) { - char *ptr = (char*)memalign(alignment, 100); - ASSERT_TRUE(ptr != NULL) << alignment; - ASSERT_LE(100U, malloc_usable_size(ptr)); - ASSERT_EQ(0, (intptr_t)ptr % (1 << i)); - + char *ptr = reinterpret_cast(memalign(alignment, 100)); + ASSERT_TRUE(ptr != NULL) << "Failed at alignment " << alignment; + ASSERT_LE(100U, malloc_usable_size(ptr)) << "Failed at alignment " << alignment; + ASSERT_EQ(0U, reinterpret_cast(ptr) % ((1U << i))) + << "Failed at alignment " << alignment; free(ptr); } } } +TEST(malloc, memalign_overflow) { + ASSERT_EQ(NULL, memalign(4096, SIZE_MAX)); +} + +TEST(malloc, memalign_non_power2) { + void* ptr; + for (size_t align = 0; align <= 256; align++) { + ptr = memalign(align, 1024); + ASSERT_TRUE(ptr != NULL) << "Failed at align " << align; + free(ptr); + } +} + +TEST(malloc, posix_memalign_non_power2) { + void* ptr; + ASSERT_EQ(EINVAL, posix_memalign(&ptr, 17, 1024)); +} + +TEST(malloc, posix_memalign_overflow) { + void* ptr; + ASSERT_NE(0, posix_memalign(&ptr, 16, SIZE_MAX)); +} + TEST(malloc, memalign_realloc) { // Memalign and then realloc the pointer a couple of times. for (size_t alignment = 1; alignment <= 4096; alignment <<= 1) { @@ -87,7 +138,6 @@ TEST(malloc, memalign_realloc) { for (size_t i = 0; i < 250; i++) { ASSERT_EQ(0x67, ptr[i]); } - free(ptr); } } @@ -105,7 +155,6 @@ TEST(malloc, malloc_realloc_larger) { for (size_t i = 0; i < 100; i++) { ASSERT_EQ(67, ptr[i]); } - free(ptr); } @@ -122,7 +171,6 @@ TEST(malloc, malloc_realloc_smaller) { for (size_t i = 0; i < 100; i++) { ASSERT_EQ(67, ptr[i]); } - free(ptr); } @@ -161,9 +209,9 @@ TEST(malloc, malloc_multiple_realloc) { for (size_t i = 0; i < 150; i++) { ASSERT_EQ(0x23, ptr[i]); } - free(ptr); } + TEST(malloc, calloc_realloc_larger) { // Realloc to a larger size, calloc is used for the original allocation. char *ptr = (char *)calloc(1, 100); @@ -176,7 +224,6 @@ TEST(malloc, calloc_realloc_larger) { for (size_t i = 0; i < 100; i++) { ASSERT_EQ(0, ptr[i]); } - free(ptr); } @@ -192,7 +239,6 @@ TEST(malloc, calloc_realloc_smaller) { for (size_t i = 0; i < 100; i++) { ASSERT_EQ(0, ptr[i]); } - free(ptr); } @@ -230,21 +276,42 @@ TEST(malloc, calloc_multiple_realloc) { for (size_t i = 0; i < 150; i++) { ASSERT_EQ(0, ptr[i]); } - free(ptr); } -TEST(malloc, posix_memalign_non_power2) { - void* ptr; - - ASSERT_EQ(EINVAL, posix_memalign(&ptr, 17, 1024)); +TEST(malloc, realloc_overflow) { + errno = 0; + ASSERT_EQ(NULL, realloc(NULL, SIZE_MAX)); + ASSERT_EQ(ENOMEM, errno); + void* ptr = malloc(100); + ASSERT_TRUE(ptr != NULL); + errno = 0; + ASSERT_EQ(NULL, realloc(ptr, SIZE_MAX)); + ASSERT_EQ(ENOMEM, errno); + free(ptr); } -TEST(malloc, memalign_non_power2) { - void* ptr; - for (size_t align = 0; align <= 256; align++) { - ptr = memalign(align, 1024); - ASSERT_TRUE(ptr != NULL) << "Failed at align " << align; - free(ptr); - } +TEST(malloc, pvalloc_std) { + size_t pagesize = sysconf(_SC_PAGESIZE); + void* ptr = pvalloc(100); + ASSERT_TRUE(ptr != NULL); + ASSERT_TRUE((reinterpret_cast(ptr) & (pagesize-1)) == 0); + ASSERT_LE(pagesize, malloc_usable_size(ptr)); + free(ptr); +} + +TEST(malloc, pvalloc_overflow) { + ASSERT_EQ(NULL, pvalloc(SIZE_MAX)); +} + +TEST(malloc, valloc_std) { + size_t pagesize = sysconf(_SC_PAGESIZE); + void* ptr = pvalloc(100); + ASSERT_TRUE(ptr != NULL); + ASSERT_TRUE((reinterpret_cast(ptr) & (pagesize-1)) == 0); + free(ptr); +} + +TEST(malloc, valloc_overflow) { + ASSERT_EQ(NULL, valloc(SIZE_MAX)); }