Compare commits

...

28 Commits

Author SHA1 Message Date
Daan Leijen
343a747f2f merge from dev 2022-04-20 17:34:47 -07:00
Daan Leijen
31473c8e37 merge from dev 2022-04-20 17:34:06 -07:00
Daan Leijen
24ef590532 Call SymInitialize at process start as it is single threaded 2022-04-20 17:25:24 -07:00
Daan Leijen
f2a2eb4ad0 merge from dev 2022-04-20 17:16:25 -07:00
Daan Leijen
b9e44dfa78 Merge branch 'dev-trace' of https://github.com/microsoft/mimalloc into dev-trace 2022-04-14 16:59:43 -07:00
Daan Leijen
b2fe83fa2c Merge branch 'dev' into dev-trace 2022-04-14 16:59:36 -07:00
Daan
6d852d9ff5 fix trace on windows if symbol initialization fails 2022-04-14 13:51:32 -07:00
Daan Leijen
1270eec6c0 merge from dev 2022-04-10 13:19:26 -07:00
Daan
0dafa1e0a0 Merge branch 'dev' into dev-trace 2022-04-08 13:48:57 -07:00
daan
04ab0f639e Merge branch 'dev' into dev-trace 2022-02-05 17:54:39 -08:00
Daan
5fcb2615a8 add overflow test 2022-02-03 19:16:23 -08:00
Daan
95a8196490 fix compilation on macOS 2022-02-03 19:15:10 -08:00
Daan
636931874f merge from dev 2022-02-03 15:59:32 -08:00
daan
f690711539 fix debug freed bytes fill non-owning thread free 2022-01-10 16:44:12 -08:00
daan
ad47cab97c merge from dev 2022-01-10 16:22:34 -08:00
daan
83f8451e62 merge from dev 2021-12-15 08:48:17 -08:00
daan
b91198826c merge from dev 2021-12-15 08:37:06 -08:00
Daan Leijen
9b8bb5b6d6 fix prototype 2021-12-11 17:10:00 -08:00
Daan
7a7a774257 better backtrace 2021-12-10 17:31:24 -08:00
Daan
72ca23d14f faster backtrace; show predecessor blocks on block overflow 2021-12-10 17:22:02 -08:00
Daan
5739714b8d faster backtrace; show predecessor blocks on block overflow 2021-12-10 17:16:37 -08:00
Daan
b6e2b6e975 enable traces on apple 2021-12-10 12:08:41 -08:00
Daan Leijen
65b2cebcef improve stacktrace on linux 2021-12-10 11:42:54 -08:00
Daan Leijen
28893a6c1b improve padding and error messages 2021-12-10 11:09:19 -08:00
Daan Leijen
ea75c745e1 add tracing on linux and freebsd 2021-12-09 17:26:13 -08:00
Daan Leijen
e125c04081 Merge branch 'dev' into dev-trace 2021-12-09 16:18:28 -08:00
Daan Leijen
8c04558af8 improve padding extra 2021-12-09 16:04:22 -08:00
Daan Leijen
a84df3795a add support for extra padding and backtraces 2021-12-09 14:19:41 -08:00
12 changed files with 454 additions and 128 deletions

View File

@ -21,6 +21,7 @@ option(MI_BUILD_OBJECT "Build object library" ON)
option(MI_BUILD_TESTS "Build test executables" ON) option(MI_BUILD_TESTS "Build test executables" ON)
option(MI_DEBUG_TSAN "Build with thread sanitizer (needs clang)" OFF) option(MI_DEBUG_TSAN "Build with thread sanitizer (needs clang)" OFF)
option(MI_DEBUG_UBSAN "Build with undefined-behavior sanitizer (needs clang++)" OFF) option(MI_DEBUG_UBSAN "Build with undefined-behavior sanitizer (needs clang++)" OFF)
option(MI_DEBUG_TRACE "Store allocation stack trace in each heap block to debug heap block overflows or corruption" OFF)
option(MI_SKIP_COLLECT_ON_EXIT, "Skip collecting memory on program exit" OFF) option(MI_SKIP_COLLECT_ON_EXIT, "Skip collecting memory on program exit" OFF)
# deprecated options # deprecated options
@ -28,6 +29,9 @@ option(MI_CHECK_FULL "Use full internal invariant checking in DEBUG mode
option(MI_INSTALL_TOPLEVEL "Install directly into $CMAKE_INSTALL_PREFIX instead of PREFIX/lib/mimalloc-version (deprecated)" OFF) option(MI_INSTALL_TOPLEVEL "Install directly into $CMAKE_INSTALL_PREFIX instead of PREFIX/lib/mimalloc-version (deprecated)" OFF)
option(MI_USE_LIBATOMIC "Explicitly link with -latomic (on older systems) (deprecated and detected automatically)" OFF) option(MI_USE_LIBATOMIC "Explicitly link with -latomic (on older systems) (deprecated and detected automatically)" OFF)
set(MI_PADDING_EXTRA 0 CACHE STRING "Specify extra bytes for padding in each heap block (to debug heap block overflows)")
include(GNUInstallDirs) include(GNUInstallDirs)
include("cmake/mimalloc-config-version.cmake") include("cmake/mimalloc-config-version.cmake")
@ -128,6 +132,17 @@ if(MI_DEBUG_FULL)
list(APPEND mi_defines MI_DEBUG=3) # full invariant checking list(APPEND mi_defines MI_DEBUG=3) # full invariant checking
endif() endif()
if(MI_DEBUG_TRACE)
message(STATUS "Enable allocation trace in each heap block (MI_DEBUG_TRACE=ON)")
list(APPEND mi_defines MI_DEBUG_TRACE=1)
set(CMAKE_ENABLE_EXPORTS TRUE)
endif()
if(MI_PADDING_EXTRA)
message(STATUS "Add extra debug padding to each heap block (MI_PADDING_EXTRA=${MI_PADDING_EXTRA})")
list(APPEND mi_defines MI_PADDING_EXTRA=${MI_PADDING_EXTRA})
endif()
if(NOT MI_PADDING) if(NOT MI_PADDING)
message(STATUS "Disable padding of heap blocks in debug mode (MI_PADDING=OFF)") message(STATUS "Disable padding of heap blocks in debug mode (MI_PADDING=OFF)")
list(APPEND mi_defines MI_PADDING=0) list(APPEND mi_defines MI_PADDING=0)
@ -230,6 +245,12 @@ else()
if (MI_LIBATOMIC OR MI_USE_LIBATOMIC) if (MI_LIBATOMIC OR MI_USE_LIBATOMIC)
list(APPEND mi_libraries atomic) list(APPEND mi_libraries atomic)
endif() endif()
if(MI_DEBUG_TRACE)
find_library(MI_LIBEXECINFO execinfo)
if (MI_LIBEXECINFO)
list(APPEND mi_libraries ${MI_LIBEXECINFO})
endif()
endif()
endif() endif()
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------

View File

@ -116,7 +116,7 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>
<PreprocessorDefinitions>MI_DEBUG=3;%(PreprocessorDefinitions);</PreprocessorDefinitions> <PreprocessorDefinitions>MI_DEBUG_TRACE=1;MI_DEBUG=3;%(PreprocessorDefinitions);</PreprocessorDefinitions>
<CompileAs>CompileAsCpp</CompileAs> <CompileAs>CompileAsCpp</CompileAs>
<SupportJustMyCode>false</SupportJustMyCode> <SupportJustMyCode>false</SupportJustMyCode>
<LanguageStandard>Default</LanguageStandard> <LanguageStandard>Default</LanguageStandard>

View File

@ -116,7 +116,7 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>
<PreprocessorDefinitions>MI_DEBUG=3;%(PreprocessorDefinitions);</PreprocessorDefinitions> <PreprocessorDefinitions>MI_DEBUG_TRACE=1;MI_DEBUG=3;%(PreprocessorDefinitions);</PreprocessorDefinitions>
<CompileAs>CompileAsCpp</CompileAs> <CompileAs>CompileAsCpp</CompileAs>
<SupportJustMyCode>false</SupportJustMyCode> <SupportJustMyCode>false</SupportJustMyCode>
<LanguageStandard>stdcpp20</LanguageStandard> <LanguageStandard>stdcpp20</LanguageStandard>

View File

@ -16,6 +16,7 @@ terms of the MIT license. A copy of the license can be found in the file
#define mi_trace_message(...) #define mi_trace_message(...)
#endif #endif
#define MI_CACHE_LINE 64 #define MI_CACHE_LINE 64
#if defined(_MSC_VER) #if defined(_MSC_VER)
#pragma warning(disable:4127) // suppress constant conditional warning (due to MI_SECURE paths) #pragma warning(disable:4127) // suppress constant conditional warning (due to MI_SECURE paths)
@ -57,6 +58,11 @@ void _mi_trace_message(const char* fmt, ...);
void _mi_options_init(void); void _mi_options_init(void);
void _mi_error_message(int err, const char* fmt, ...); void _mi_error_message(int err, const char* fmt, ...);
#if MI_DEBUG_TRACE > 0
void _mi_stack_trace_capture(void** strace, size_t len, size_t skip);
void _mi_stack_trace_print(const char* msg, void** strace, size_t len, const mi_block_t* block, size_t bsize, size_t avail);
#endif
// random.c // random.c
void _mi_random_init(mi_random_ctx_t* ctx); void _mi_random_init(mi_random_ctx_t* ctx);
void _mi_random_split(mi_random_ctx_t* ctx, mi_random_ctx_t* new_ctx); void _mi_random_split(mi_random_ctx_t* ctx, mi_random_ctx_t* new_ctx);
@ -143,6 +149,7 @@ void* _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero) mi_att
void* _mi_heap_realloc_zero(mi_heap_t* heap, void* p, size_t newsize, bool zero) mi_attr_noexcept; void* _mi_heap_realloc_zero(mi_heap_t* heap, void* p, size_t newsize, bool zero) mi_attr_noexcept;
mi_block_t* _mi_page_ptr_unalign(const mi_segment_t* segment, const mi_page_t* page, const void* p); mi_block_t* _mi_page_ptr_unalign(const mi_segment_t* segment, const mi_page_t* page, const void* p);
bool _mi_free_delayed_block(mi_block_t* block); bool _mi_free_delayed_block(mi_block_t* block);
void _mi_show_block_trace_with_predecessor(const mi_page_t* page, const mi_block_t* block, const char* msg);
#if MI_DEBUG>1 #if MI_DEBUG>1
bool _mi_page_is_valid(mi_page_t* page); bool _mi_page_is_valid(mi_page_t* page);
@ -405,7 +412,7 @@ static inline uintptr_t _mi_ptr_cookie(const void* p) {
----------------------------------------------------------- */ ----------------------------------------------------------- */
static inline mi_page_t* _mi_heap_get_free_small_page(mi_heap_t* heap, size_t size) { static inline mi_page_t* _mi_heap_get_free_small_page(mi_heap_t* heap, size_t size) {
mi_assert_internal(size <= (MI_SMALL_SIZE_MAX + MI_PADDING_SIZE)); mi_assert_internal(size <= (MI_SMALL_SIZE_MAX + MI_PADDING_MINSIZE));
const size_t idx = _mi_wsize_from_size(size); const size_t idx = _mi_wsize_from_size(size);
mi_assert_internal(idx < MI_PAGES_DIRECT); mi_assert_internal(idx < MI_PAGES_DIRECT);
return heap->pages_free_direct[idx]; return heap->pages_free_direct[idx];
@ -648,7 +655,8 @@ static inline mi_block_t* mi_block_next(const mi_page_t* page, const mi_block_t*
// check for free list corruption: is `next` at least in the same page? // check for free list corruption: is `next` at least in the same page?
// TODO: check if `next` is `page->block_size` aligned? // TODO: check if `next` is `page->block_size` aligned?
if mi_unlikely(next!=NULL && !mi_is_in_same_page(block, next)) { if mi_unlikely(next!=NULL && !mi_is_in_same_page(block, next)) {
_mi_error_message(EFAULT, "corrupted free list entry of size %zub at %p: value 0x%zx\n", mi_page_block_size(page), block, (uintptr_t)next); _mi_show_block_trace_with_predecessor(page, block, "free block");
_mi_error_message(EFAULT, "corrupted free list entry of size %zu at %p: value 0x%zx\n", mi_page_block_size(page), block, (uintptr_t)next);
next = NULL; next = NULL;
} }
return next; return next;

View File

@ -60,9 +60,26 @@ terms of the MIT license. A copy of the license can be found in the file
#define MI_PADDING 1 #define MI_PADDING 1
#endif #endif
#if !defined(MI_DEBUG_TRACE) // store stack trace at each allocation
#define MI_DEBUG_TRACE (0)
#endif
#if !defined(MI_DEBUG_TRACE_LEN)
#define MI_DEBUG_TRACE_LEN (8) // store up to N frames if tracing is enabled
#endif
#if !defined(MI_PADDING_EXTRA) // use extra padding bytes? (so a stack trace can be preserved or next block corruption prevented)
#if MI_DEBUG_TRACE > 0
#define MI_PADDING_EXTRA (64)
#else
#define MI_PADDING_EXTRA (0)
#endif
#endif
// Encoded free lists allow detection of corrupted free lists // Encoded free lists allow detection of corrupted free lists
// and can detect buffer overflows, modify after free, and double `free`s. // and can detect buffer overflows, modify after free, and double `free`s.
// (It must be enabled if MI_PADDING is enabled as the same mechanism is used to encode the canary.)
#if (MI_SECURE>=3 || MI_DEBUG>=1 || MI_PADDING > 0) #if (MI_SECURE>=3 || MI_DEBUG>=1 || MI_PADDING > 0)
#define MI_ENCODE_FREELIST 1 #define MI_ENCODE_FREELIST 1
#endif #endif
@ -354,20 +371,40 @@ typedef struct mi_random_cxt_s {
} mi_random_ctx_t; } mi_random_ctx_t;
// In debug mode there is a padding structure at the end of the blocks to check for buffer overflows // If MI_PADDING is enabled, there is a padding structure at the end of the blocks to check for buffer overflows
// The full layout is of a block becomes:
//
// |--- data ---------|--- fill ----------|--- struct padding_s -----------------------------------------|
// |.. actual data .. | .. delta bytes .. | canary_lo | .. extra .. | canary | delta | .. stack trace .. |
//
// where the delta bytes are used to align the padding structure and to detect byte precise overflow.
// The `canary` is used to see if `delta` and `strace` are not corrupted, while `canary_lo` can
// detect overflow into the `extra` padding (where the stack trace could remain valid)
#if (MI_PADDING) #if (MI_PADDING)
typedef struct mi_padding_s { typedef struct mi_padding_s {
uint32_t canary; // encoded block value to check validity of the padding (in case of overflow) #if MI_PADDING_EXTRA > 0
uint32_t delta; // padding bytes before the block. (mi_usable_size(p) - delta == exact allocated bytes) uint32_t canary_lo; // extra canary to detect initial overflow
uint8_t extra[MI_PADDING_EXTRA];
#endif
uint32_t canary; // encoded block value to check validity of the delat (in case of overflow)
uint32_t delta; // padding bytes before the block. (mi_usable_size(p) - delta == exact allocated bytes)
#if (MI_DEBUG_TRACE > 0)
void* strace[MI_DEBUG_TRACE_LEN]; // stack trace at allocation time
#endif
} mi_padding_t; } mi_padding_t;
#define MI_PADDING_SIZE (sizeof(mi_padding_t)) #define MI_PADDING_MINSIZE (8) // 2*sizeof(uint32_t)
#define MI_PADDING_WSIZE ((MI_PADDING_SIZE + MI_INTPTR_SIZE - 1) / MI_INTPTR_SIZE) #define MI_PADDING_SIZE (sizeof(mi_padding_t))
#else #else
#define MI_PADDING_SIZE 0 #define MI_PADDING_MINSIZE (0)
#define MI_PADDING_WSIZE 0 #define MI_PADDING_SIZE (0)
#endif #endif
#define MI_PAGES_DIRECT (MI_SMALL_WSIZE_MAX + MI_PADDING_WSIZE + 1) // add 2 more for minimal padding (MI_PADDING && !MI_DEBUG_TRACE && MI_PADDING_EXTRA==0)
// since this is used in secure mode, we optimize this case by allowing
// `heap_malloc_small` to also work with `MI_WSMALL_SIZE_MAX + MI_PADDING_MINSIZE` sizes.
// see `init.c` where all are initialized with an empty page and the check at `heap_malloc_small`.
#define MI_PAGES_DIRECT (MI_SMALL_WSIZE_MAX + 1 + 2)
// A heap owns a set of pages. // A heap owns a set of pages.

View File

@ -68,6 +68,13 @@ extern inline void* _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t siz
mi_assert_internal(delta >= 0 && mi_page_usable_block_size(page) >= (size - MI_PADDING_SIZE + delta)); mi_assert_internal(delta >= 0 && mi_page_usable_block_size(page) >= (size - MI_PADDING_SIZE + delta));
padding->canary = (uint32_t)(mi_ptr_encode(page,block,page->keys)); padding->canary = (uint32_t)(mi_ptr_encode(page,block,page->keys));
padding->delta = (uint32_t)(delta); padding->delta = (uint32_t)(delta);
#if MI_PADDING_EXTRA > 0
padding->canary_lo = padding->canary;
memset(padding->extra, 0, sizeof(padding->extra));
#endif
#if (MI_DEBUG_TRACE)
_mi_stack_trace_capture(padding->strace, MI_DEBUG_TRACE_LEN, 2 /*frames to skip*/);
#endif
uint8_t* fill = (uint8_t*)padding - delta; uint8_t* fill = (uint8_t*)padding - delta;
const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // set at most N initial padding bytes const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // set at most N initial padding bytes
for (size_t i = 0; i < maxpad; i++) { fill[i] = MI_DEBUG_PADDING; } for (size_t i = 0; i < maxpad; i++) { fill[i] = MI_DEBUG_PADDING; }
@ -80,15 +87,25 @@ static inline mi_decl_restrict void* mi_heap_malloc_small_zero(mi_heap_t* heap,
mi_assert(heap != NULL); mi_assert(heap != NULL);
mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id()); // heaps are thread local mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id()); // heaps are thread local
mi_assert(size <= MI_SMALL_SIZE_MAX); mi_assert(size <= MI_SMALL_SIZE_MAX);
#if (MI_PADDING) void* p;
#if (MI_PADDING)
if (size == 0) { if (size == 0) {
size = sizeof(void*); size = sizeof(void*);
} }
#endif #endif
mi_page_t* page = _mi_heap_get_free_small_page(heap, size + MI_PADDING_SIZE); #if (MI_PADDING_EXTRA > 0 || MI_DEBUG_TRACE > 0)
void* p = _mi_page_malloc(heap, page, size + MI_PADDING_SIZE, zero); // with extra padding it is not guaranteed the size + MI_PADDING_SIZE <= MI_SMALL_SIZE_MAX + MI_PADDING_MINSIZE, so we need an extra check
mi_assert_internal(p == NULL || mi_usable_size(p) >= size); if (size + MI_PADDING_SIZE > MI_SMALL_SIZE_MAX) {
#if MI_STAT>1 p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE, zero);
}
else
#endif
{
mi_page_t* page = _mi_heap_get_free_small_page(heap, size + MI_PADDING_SIZE);
p = _mi_page_malloc(heap, page, size + MI_PADDING_SIZE, zero);
}
mi_assert_internal(p==NULL || mi_usable_size(p) >= size);
#if MI_STAT>1
if (p != NULL) { if (p != NULL) {
if (!mi_heap_is_initialized(heap)) { heap = mi_get_default_heap(); } if (!mi_heap_is_initialized(heap)) { heap = mi_get_default_heap(); }
mi_heap_stat_increase(heap, malloc, mi_usable_size(p)); mi_heap_stat_increase(heap, malloc, mi_usable_size(p));
@ -108,10 +125,11 @@ mi_decl_nodiscard extern inline mi_decl_restrict void* mi_malloc_small(size_t si
// The main allocation function // The main allocation function
extern inline void* _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept { extern inline void* _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept {
if mi_likely(size <= MI_SMALL_SIZE_MAX) { if mi_likely(size + MI_PADDING_SIZE <= MI_SMALL_SIZE_MAX + MI_PADDING_MINSIZE) {
return mi_heap_malloc_small_zero(heap, size, zero); return mi_heap_malloc_small_zero(heap, size, zero);
} }
else { else
{
mi_assert(heap!=NULL); mi_assert(heap!=NULL);
mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id()); // heaps are thread local mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id()); // heaps are thread local
void* const p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE, zero); // note: size can overflow but it is detected in malloc_generic void* const p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE, zero); // note: size can overflow but it is detected in malloc_generic
@ -148,6 +166,143 @@ mi_decl_nodiscard mi_decl_restrict void* mi_zalloc(size_t size) mi_attr_noexcept
} }
// ---------------------------------------------------------------------------
// Check for heap block overflow by setting up padding at the end of the block
// ---------------------------------------------------------------------------
#if (MI_PADDING>0) && defined(MI_ENCODE_FREELIST)
static mi_padding_t* mi_page_decode_padding(const mi_page_t* page, const mi_block_t* block, size_t* delta, size_t* bsize) {
*bsize = mi_page_usable_block_size(page);
mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + *bsize);
*delta = padding->delta;
if ((uint32_t)mi_ptr_encode(page, block, page->keys) == padding->canary && *delta <= *bsize) {
return padding;
}
else {
return NULL;
}
}
#if MI_DEBUG_TRACE > 0
static void _mi_show_block_trace(const mi_page_t* page, const mi_block_t* block, const char* msg) {
size_t bsize;
size_t delta;
mi_padding_t* padding = mi_page_decode_padding(page, block, &delta, &bsize);
if (padding != NULL) {
_mi_stack_trace_print(msg, &padding->strace[0], MI_DEBUG_TRACE_LEN, block, bsize, bsize - delta);
}
}
#else
static void _mi_show_block_trace(const mi_page_t* page, const mi_block_t* block, const char* msg) {
MI_UNUSED(page); MI_UNUSED(block); MI_UNUSED(msg);
}
#endif
// Return the exact usable size of a block. (whereas `mi_page_usable_block_size` returns the total available size without padding)
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) {
size_t bsize;
size_t delta;
bool ok = (mi_page_decode_padding(page, block, &delta, &bsize) != NULL);
mi_assert_internal(ok); mi_assert_internal(delta <= bsize);
return (ok ? bsize - delta : 0);
}
static bool mi_verify_padding(const mi_page_t* page, const mi_block_t* block, size_t* size, size_t* wrong) {
size_t bsize;
size_t delta;
const mi_padding_t* padding = mi_page_decode_padding(page, block, &delta, &bsize);
*size = *wrong = bsize;
if (padding == NULL) return false;
mi_assert_internal(bsize >= delta);
*size = bsize - delta;
uint8_t* fill = (uint8_t*)block + bsize - delta;
const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // check at most the first N padding bytes
for (size_t i = 0; i < maxpad; i++) {
if (fill[i] != MI_DEBUG_PADDING) {
*wrong = bsize - delta + i;
return false;
}
}
#if MI_PADDING_EXTRA > 0
if (padding->canary_lo != padding->canary) {
*wrong = bsize;
return false;
}
#endif
return true;
}
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {
size_t size;
size_t wrong;
if mi_unlikely(!mi_verify_padding(page,block,&size,&wrong)) {
_mi_show_block_trace_with_predecessor(page, block, NULL);
_mi_error_message(EFAULT, "buffer overflow in heap block %p of size %zu: write after %zu bytes\n", block, size, wrong );
}
}
// When a non-thread-local block is freed, it becomes part of the thread delayed free
// list that is freed later by the owning heap. If the exact usable size is too small to
// contain the pointer for the delayed list, then shrink the padding (by decreasing delta)
// so it will later not trigger an overflow error in `mi_free_block`.
// Returns the originally allocated byte size.
static size_t mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {
size_t bsize;
size_t delta;
mi_padding_t* padding = mi_page_decode_padding(page, block, &delta, &bsize);
mi_assert_internal(padding!=NULL);
if (padding == NULL) return 0;
mi_assert_internal(bsize > delta);
if (bsize <= delta) return 0;
const size_t avail = bsize - delta;
if (avail >= min_size) return avail; // usually already enough space
mi_assert_internal(bsize >= min_size);
if (bsize < min_size) return avail; // should never happen
size_t new_delta = (bsize - min_size);
mi_assert_internal(new_delta < bsize);
padding->delta = (uint32_t)new_delta;
return avail;
}
#else
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {
MI_UNUSED(page); MI_UNUSED(block);
}
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) {
MI_UNUSED(block);
return mi_page_usable_block_size(page);
}
static size_t mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {
MI_UNUSED(block); MI_UNUSED(min_size);
return mi_page_usable_block_size(page);
}
static void _mi_show_block_trace(const mi_page_t* page, const mi_block_t* block, const char* msg) {
MI_UNUSED(page); MI_UNUSED(block); MI_UNUSED(msg);
}
#endif
static const mi_block_t* mi_block_predecessor(const mi_page_t* page, const mi_block_t* block) {
const size_t bsize = page->xblock_size;
mi_assert_internal(bsize > 0);
if (bsize >= MI_HUGE_BLOCK_SIZE) return NULL;
const mi_block_t* prev = (const mi_block_t*)((uint8_t*)block - bsize);
uint8_t* pstart = _mi_segment_page_start(_mi_page_segment(page), page, bsize, NULL, NULL);
if (pstart > (uint8_t*)prev) return NULL;
return prev;
}
// Used if a free list is corrupted which is usually caused by the previous block(s)
void _mi_show_block_trace_with_predecessor(const mi_page_t* page, const mi_block_t* block, const char* msg) {
const mi_block_t* prev = mi_block_predecessor(page,block);
if (prev != NULL) {
_mi_show_block_trace(page, prev, "predecessor block");
}
_mi_show_block_trace(page, block, msg);
}
// ------------------------------------------------------ // ------------------------------------------------------
// Check for double free in secure and debug mode // Check for double free in secure and debug mode
// This is somewhat expensive so only enabled for secure mode 4 // This is somewhat expensive so only enabled for secure mode 4
@ -170,7 +325,8 @@ static mi_decl_noinline bool mi_check_is_double_freex(const mi_page_t* page, con
mi_list_contains(page, page->local_free, block) || mi_list_contains(page, page->local_free, block) ||
mi_list_contains(page, mi_page_thread_free(page), block)) mi_list_contains(page, mi_page_thread_free(page), block))
{ {
_mi_error_message(EAGAIN, "double free detected of block %p with size %zu\n", block, mi_page_block_size(page)); _mi_show_block_trace(page, block, NULL);
_mi_error_message(EAGAIN, "double free detected of block %p with size %zu\n", block, mi_page_usable_size_of(page,block));
return true; return true;
} }
return false; return false;
@ -179,7 +335,7 @@ static mi_decl_noinline bool mi_check_is_double_freex(const mi_page_t* page, con
static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) { static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) {
mi_block_t* n = mi_block_nextx(page, block, page->keys); // pretend it is freed, and get the decoded first field mi_block_t* n = mi_block_nextx(page, block, page->keys); // pretend it is freed, and get the decoded first field
if (((uintptr_t)n & (MI_INTPTR_SIZE-1))==0 && // quick check: aligned pointer? if (((uintptr_t)n & (MI_INTPTR_SIZE-1))==0 && // quick check: aligned pointer?
(n==NULL || mi_is_in_same_page(block, n))) // quick check: in same page or NULL? (n==NULL || mi_is_in_same_page(block, n))) // quick check: in same page or NULL?
{ {
// Suspicous: decoded value a in block is in the same page (or NULL) -- maybe a double free? // Suspicous: decoded value a in block is in the same page (or NULL) -- maybe a double free?
// (continue in separate function to improve code generation) // (continue in separate function to improve code generation)
@ -195,88 +351,6 @@ static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block
} }
#endif #endif
// ---------------------------------------------------------------------------
// Check for heap block overflow by setting up padding at the end of the block
// ---------------------------------------------------------------------------
#if (MI_PADDING>0) && defined(MI_ENCODE_FREELIST)
static bool mi_page_decode_padding(const mi_page_t* page, const mi_block_t* block, size_t* delta, size_t* bsize) {
*bsize = mi_page_usable_block_size(page);
const mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + *bsize);
*delta = padding->delta;
return ((uint32_t)mi_ptr_encode(page,block,page->keys) == padding->canary && *delta <= *bsize);
}
// Return the exact usable size of a block.
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) {
size_t bsize;
size_t delta;
bool ok = mi_page_decode_padding(page, block, &delta, &bsize);
mi_assert_internal(ok); mi_assert_internal(delta <= bsize);
return (ok ? bsize - delta : 0);
}
static bool mi_verify_padding(const mi_page_t* page, const mi_block_t* block, size_t* size, size_t* wrong) {
size_t bsize;
size_t delta;
bool ok = mi_page_decode_padding(page, block, &delta, &bsize);
*size = *wrong = bsize;
if (!ok) return false;
mi_assert_internal(bsize >= delta);
*size = bsize - delta;
uint8_t* fill = (uint8_t*)block + bsize - delta;
const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // check at most the first N padding bytes
for (size_t i = 0; i < maxpad; i++) {
if (fill[i] != MI_DEBUG_PADDING) {
*wrong = bsize - delta + i;
return false;
}
}
return true;
}
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {
size_t size;
size_t wrong;
if (!mi_verify_padding(page,block,&size,&wrong)) {
_mi_error_message(EFAULT, "buffer overflow in heap block %p of size %zu: write after %zu bytes\n", block, size, wrong );
}
}
// When a non-thread-local block is freed, it becomes part of the thread delayed free
// list that is freed later by the owning heap. If the exact usable size is too small to
// contain the pointer for the delayed list, then shrink the padding (by decreasing delta)
// so it will later not trigger an overflow error in `mi_free_block`.
static void mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {
size_t bsize;
size_t delta;
bool ok = mi_page_decode_padding(page, block, &delta, &bsize);
mi_assert_internal(ok);
if (!ok || (bsize - delta) >= min_size) return; // usually already enough space
mi_assert_internal(bsize >= min_size);
if (bsize < min_size) return; // should never happen
size_t new_delta = (bsize - min_size);
mi_assert_internal(new_delta < bsize);
mi_padding_t* padding = (mi_padding_t*)((uint8_t*)block + bsize);
padding->delta = (uint32_t)new_delta;
}
#else
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {
MI_UNUSED(page);
MI_UNUSED(block);
}
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) {
MI_UNUSED(block);
return mi_page_usable_block_size(page);
}
static void mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {
MI_UNUSED(page);
MI_UNUSED(block);
MI_UNUSED(min_size);
}
#endif
// only maintain stats for smaller objects if requested // only maintain stats for smaller objects if requested
#if (MI_STAT>0) #if (MI_STAT>0)
@ -331,9 +405,11 @@ static mi_decl_noinline void _mi_free_block_mt(mi_page_t* page, mi_block_t* bloc
// The padding check may access the non-thread-owned page for the key values. // The padding check may access the non-thread-owned page for the key values.
// that is safe as these are constant and the page won't be freed (as the block is not freed yet). // that is safe as these are constant and the page won't be freed (as the block is not freed yet).
mi_check_padding(page, block); mi_check_padding(page, block);
mi_padding_shrink(page, block, sizeof(mi_block_t)); // for small size, ensure we can fit the delayed thread pointers without triggering overflow detection const size_t avail = mi_padding_shrink(page, block, sizeof(mi_block_t)); // for small size, ensure we can fit the delayed thread pointers without triggering overflow detection
#if (MI_DEBUG!=0) #if (MI_DEBUG!=0)
memset(block, MI_DEBUG_FREED, mi_usable_size(block)); memset(block, MI_DEBUG_FREED, avail);
#else
MI_UNUSED(avail);
#endif #endif
// huge page segments are always abandoned and can be freed immediately // huge page segments are always abandoned and can be freed immediately
@ -392,7 +468,7 @@ static inline void _mi_free_block(mi_page_t* page, bool local, mi_block_t* block
if mi_unlikely(mi_check_is_double_free(page, block)) return; if mi_unlikely(mi_check_is_double_free(page, block)) return;
mi_check_padding(page, block); mi_check_padding(page, block);
#if (MI_DEBUG!=0) #if (MI_DEBUG!=0)
memset(block, MI_DEBUG_FREED, mi_page_block_size(page)); memset(block, MI_DEBUG_FREED, mi_page_usable_block_size(page));
#endif #endif
mi_block_set_next(page, block, page->local_free); mi_block_set_next(page, block, page->local_free);
page->local_free = block; page->local_free = block;
@ -476,7 +552,7 @@ void mi_free(void* p) mi_attr_noexcept
mi_check_padding(page, block); mi_check_padding(page, block);
mi_stat_free(page, block); mi_stat_free(page, block);
#if (MI_DEBUG!=0) #if (MI_DEBUG!=0)
memset(block, MI_DEBUG_FREED, mi_page_block_size(page)); memset(block, MI_DEBUG_FREED, mi_page_usable_block_size(page));
#endif #endif
mi_block_set_next(page, block, page->local_free); mi_block_set_next(page, block, page->local_free);
page->local_free = block; page->local_free = block;

View File

@ -30,15 +30,8 @@ const mi_page_t _mi_page_empty = {
NULL, NULL NULL, NULL
}; };
#define MI_PAGE_EMPTY() ((mi_page_t*)&_mi_page_empty) #define MI_PAGE_EMPTY() ((mi_page_t*)&_mi_page_empty)
#if (MI_PADDING>0) && (MI_INTPTR_SIZE >= 8)
#define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY(), MI_PAGE_EMPTY() }
#elif (MI_PADDING>0)
#define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY(), MI_PAGE_EMPTY(), MI_PAGE_EMPTY() } #define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY(), MI_PAGE_EMPTY(), MI_PAGE_EMPTY() }
#else
#define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY() }
#endif
// Empty page queues for every bin // Empty page queues for every bin
@ -497,7 +490,7 @@ static void mi_allocator_done(void) {
// Called once by the process loader // Called once by the process loader
static void mi_process_load(void) { static void mi_process_load(void) {
mi_heap_main_init(); mi_heap_main_init();
#if defined(MI_TLS_RECURSE_GUARD) #if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)
volatile mi_heap_t* dummy = _mi_heap_default; // access TLS to allocate it before setting tls_initialized to true; volatile mi_heap_t* dummy = _mi_heap_default; // access TLS to allocate it before setting tls_initialized to true;
MI_UNUSED(dummy); MI_UNUSED(dummy);
#endif #endif
@ -534,6 +527,25 @@ static void mi_detect_cpu_features(void) {
} }
#endif #endif
#if defined(_WIN32) && (MI_DEBUG_TRACE > 0)
#include <dbghelp.h>
static void mi_debug_init(void) {
if (SymInitialize(GetCurrentProcess(), NULL, TRUE) != TRUE) { // initialize here as it is single threaded.
_mi_warning_message("unable to initialize debug symbol information (error 0x%x)", GetLastError());
}
}
static void mi_debug_done(void) {
SymCleanup(GetCurrentProcess());
}
#else
static void mi_debug_init(void) {
// nothing
}
static void mi_debug_done(void) {
// nothing
}
#endif
// Initialize the process; called by thread_init or the process loader // Initialize the process; called by thread_init or the process loader
void mi_process_init(void) mi_attr_noexcept { void mi_process_init(void) mi_attr_noexcept {
// ensure we are called once // ensure we are called once
@ -550,6 +562,7 @@ void mi_process_init(void) mi_attr_noexcept {
_mi_verbose_message("debug level : %d\n", MI_DEBUG); _mi_verbose_message("debug level : %d\n", MI_DEBUG);
#endif #endif
_mi_verbose_message("secure level: %d\n", MI_SECURE); _mi_verbose_message("secure level: %d\n", MI_SECURE);
mi_debug_init();
mi_thread_init(); mi_thread_init();
#if defined(_WIN32) && !defined(MI_SHARED_LIB) #if defined(_WIN32) && !defined(MI_SHARED_LIB)
@ -558,7 +571,7 @@ void mi_process_init(void) mi_attr_noexcept {
// will not call _mi_thread_done on the (still executing) main thread. See issue #508. // will not call _mi_thread_done on the (still executing) main thread. See issue #508.
FlsSetValue(mi_fls_key, NULL); FlsSetValue(mi_fls_key, NULL);
#endif #endif
mi_stats_reset(); // only call stat reset *after* thread init (or the heap tld == NULL) mi_stats_reset(); // only call stat reset *after* thread init (or the heap tld == NULL)
if (mi_option_is_enabled(mi_option_reserve_huge_os_pages)) { if (mi_option_is_enabled(mi_option_reserve_huge_os_pages)) {
@ -604,6 +617,7 @@ static void mi_process_done(void) {
mi_stats_print(NULL); mi_stats_print(NULL);
} }
mi_allocator_done(); mi_allocator_done();
mi_debug_done();
_mi_verbose_message("process done: 0x%zx\n", _mi_heap_main.thread_id); _mi_verbose_message("process done: 0x%zx\n", _mi_heap_main.thread_id);
os_preloading = true; // don't call the C runtime anymore os_preloading = true; // don't call the C runtime anymore
} }

View File

@ -396,6 +396,93 @@ void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, co
} }
#endif #endif
// --------------------------------------------------------
// Stack traces
// --------------------------------------------------------
#if (MI_DEBUG_TRACE > 0) && defined(_WIN32)
void _mi_stack_trace_capture(void** strace, size_t len, size_t skip) {
CaptureStackBackTrace((DWORD)skip + 1, (DWORD)len, strace, NULL);
}
#include <dbghelp.h>
#pragma comment(lib,"dbghelp")
void _mi_stack_trace_print(const char* msg, void** strace, size_t len, const mi_block_t* block, size_t bsize, size_t avail) {
_mi_fprintf(NULL, NULL, "trace %s at %p of size %zu (%zub usable), allocated at:\n",
(msg==NULL ? "block" : msg), block, avail, bsize);
uintptr_t uninit = 0;
for( size_t i = 0; i < MI_INTPTR_SIZE; i++ ) {
uninit = (uninit << 8) | MI_DEBUG_UNINIT;
}
if (strace == NULL || uninit == (uintptr_t)strace[0]) {
_mi_fprintf(NULL, NULL, " (uninitialized trace)\n");
}
else {
PSYMBOL_INFO info = (PSYMBOL_INFO)_malloca(sizeof(SYMBOL_INFO) + 256 * sizeof(TCHAR));
if (info==NULL) return;
memset(info, 0, sizeof(info));
info->MaxNameLen = 255;
info->SizeOfStruct = sizeof(SYMBOL_INFO);
HANDLE current_process = GetCurrentProcess();
for (size_t i = 0; i < len && strace[i] != NULL; i++) {
if (SymFromAddr(current_process, (DWORD64)(strace[i]), 0, info)) {
_mi_fprintf(NULL, NULL, " %2zu: %8p: %s\n", i, strace[i], info->Name);
}
else {
_mi_fprintf(NULL, NULL, " %2zu: %8p: <unknown address: error: 0x%04x>\n", i, strace[i], GetLastError());
}
}
}
}
#elif (MI_DEBUG_TRACE > 0) && (defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__))
#include <execinfo.h>
#define MI_MAX_TRACE_LEN (64)
void _mi_stack_trace_capture(void** strace, size_t len, size_t skip) {
if (_mi_preloading()) return;
if (!mi_recurse_enter()) return; // needed for pthreads
void* trace[MI_MAX_TRACE_LEN];
size_t trace_len = skip + len;
if (trace_len > len) { trace_len = MI_MAX_TRACE_LEN; }
memset(trace,0,trace_len);
trace_len = backtrace(trace, trace_len);
for (size_t i = 0; i < len; i++) {
void* p = (i + skip < trace_len ? trace[i+skip] : NULL);
strace[i] = p;
}
mi_recurse_exit();
}
void _mi_stack_trace_print(const char* msg, void** strace, size_t len, const mi_block_t* block, size_t bsize, size_t avail) {
_mi_fprintf(NULL, NULL, "trace %s at %p of size %zu (%zub usable), allocated at:\n",
(msg==NULL ? "block" : msg), block, avail, bsize);
uintptr_t uninit = 0;
for( size_t i = 0; i < MI_INTPTR_SIZE; i++ ) {
uninit = (uninit << 8) | MI_DEBUG_UNINIT;
}
if (strace == NULL || uninit == (uintptr_t)strace[0]) {
_mi_fprintf(NULL, NULL, " (uninitialized trace)\n");
}
else {
while( len > 0 && strace[len-1] == NULL) { len--; }
if (len == 0) return;
char** names = backtrace_symbols(strace, len);
for (size_t i = 0; i < len && strace[i] != NULL; i++) {
_mi_fprintf(NULL, NULL, " %2zu: %8p: %s\n", i, strace[i], (names == NULL || names[i] == NULL ? "<unknown>" : names[i]));
}
// free(names); // avoid potential recursion and leak the trace
}
}
#else
void _mi_stack_trace_capture(void** strace, size_t len, size_t skip) {
MI_UNUSED(strace); MI_UNUSED(len); MI_UNUSED(skip);
}
void _mi_stack_trace_print(const char* msg, void** strace, size_t len, const mi_block_t* block, size_t bsize, size_t avail) {
MI_UNUSED(strace); MI_UNUSED(len); MI_UNUSED(block);
MI_UNUSED(bsize); MI_UNUSED(avail); MI_UNUSED(msg);
}
#endif
// -------------------------------------------------------- // --------------------------------------------------------
// Errors // Errors
// -------------------------------------------------------- // --------------------------------------------------------

View File

@ -15,6 +15,7 @@ if (NOT CMAKE_BUILD_TYPE)
endif() endif()
endif() endif()
# Import mimalloc (if installed) # Import mimalloc (if installed)
find_package(mimalloc 1.7 REQUIRED NO_SYSTEM_ENVIRONMENT_PATH) find_package(mimalloc 1.7 REQUIRED NO_SYSTEM_ENVIRONMENT_PATH)
message(STATUS "Found mimalloc installed at: ${MIMALLOC_LIBRARY_DIR} (${MIMALLOC_VERSION_DIR})") message(STATUS "Found mimalloc installed at: ${MIMALLOC_LIBRARY_DIR} (${MIMALLOC_VERSION_DIR})")

View File

@ -9,8 +9,11 @@
static void double_free1(); static void double_free1();
static void double_free2(); static void double_free2();
static void corrupt_free(); static void double_free3();
static void corrupt_free1();
static void corrupt_free2();
static void block_overflow1(); static void block_overflow1();
static void block_overflow2();
static void invalid_free(); static void invalid_free();
static void test_aslr(void); static void test_aslr(void);
static void test_process_info(void); static void test_process_info(void);
@ -24,12 +27,15 @@ int main() {
mi_version(); mi_version();
mi_stats_reset(); mi_stats_reset();
// detect double frees and heap corruption // detect double frees and heap corruption
// double_free1(); double_free1();
// double_free2(); // double_free2();
// corrupt_free(); // double_free3();
// corrupt_free1();
// corrupt_free2();
// block_overflow1(); // block_overflow1();
// block_overflow2();
// test_aslr(); // test_aslr();
// invalid_free(); invalid_free();
// test_reserved(); // test_reserved();
// negative_stat(); // negative_stat();
test_heap_walk(); test_heap_walk();
@ -63,7 +69,8 @@ int main() {
static void invalid_free() { static void invalid_free() {
free((void*)0xBADBEEF); free((void*)0xBADBEEF);
realloc((void*)0xBADBEEF,10); void* p = realloc((void*)0xBADBEEF,10);
free(p);
} }
static void block_overflow1() { static void block_overflow1() {
@ -72,6 +79,15 @@ static void block_overflow1() {
free(p); free(p);
} }
#define OVF_SIZE 100
static void block_overflow2() {
uint8_t* p = (uint8_t*)mi_malloc(30);
memset(p+30, 0, OVF_SIZE);
free(p);
}
// The double free samples come ArcHeap [1] by Insu Yun (issue #161) // The double free samples come ArcHeap [1] by Insu Yun (issue #161)
// [1]: https://arxiv.org/pdf/1903.00503.pdf // [1]: https://arxiv.org/pdf/1903.00503.pdf
@ -109,12 +125,35 @@ static void double_free2() {
fprintf(stderr, "p1: %p-%p, p2: %p-%p\n", p[4], (uint8_t*)(p[4]) + 917504, p[1], (uint8_t*)(p[1]) + 786432); fprintf(stderr, "p1: %p-%p, p2: %p-%p\n", p[4], (uint8_t*)(p[4]) + 917504, p[1], (uint8_t*)(p[1]) + 786432);
} }
static void double_free3() {
void* p1 = malloc(32);
void* p2 = malloc(32);
void* p3 = malloc(32);
free(p2);
free(p1);
free(p2);
free(p3);
}
static void corrupt_free1() {
void* p1 = malloc(32);
void* p2 = malloc(32);
void* p3 = malloc(32);
free(p2);
memset(p2, 0, 8); // corrupt free list entry
mi_collect(true);
p2 = malloc(32); // should trigger corrupted free list
free(p1);
free(p2);
free(p3);
}
// Try to corrupt the heap through buffer overflow // Try to corrupt the heap through buffer overflow
#define N 256 #define N 256
#define SZ 64 #define SZ 64
#define OVF_SZ 32
static void corrupt_free() { static void corrupt_free2() {
void* p[N]; void* p[N];
// allocate // allocate
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
@ -128,13 +167,18 @@ static void corrupt_free() {
// try to corrupt the free list // try to corrupt the free list
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
if (p[i] != NULL) { if (p[i] != NULL) {
memset(p[i], 0, SZ+8); memset(p[i], 0, SZ+OVF_SZ);
} }
} }
// allocate more.. trying to trigger an allocation from a corrupted entry // allocate more.. trying to trigger an allocation from a corrupted entry
// this may need many allocations to get there (if at all) // this may need many allocations to get there (if at all)
for (int i = 0; i < 4096; i++) { for (int i = 0; i < 4096; i++) {
malloc(SZ); void* p = malloc(SZ);
}
// free the rest
for (int i = 0; i < N; i++) {
free(p[i]);
p[i] = NULL;
} }
} }
@ -181,7 +225,7 @@ static void test_reserved(void) {
static void negative_stat(void) { static void negative_stat(void) {
int* p = mi_malloc(60000); int* p = (int*)mi_malloc(60000);
mi_stats_print_out(NULL, NULL); mi_stats_print_out(NULL, NULL);
*p = 100; *p = 100;
mi_free(p); mi_free(p);

37
test/test-overflow.cpp Normal file
View File

@ -0,0 +1,37 @@
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdint.h>
#include <new>
#include <vector>
#include <future>
#include <iostream>
#include <thread>
#include <assert.h>
static void block_overflow1(void) {
uint8_t* p = (uint8_t*)malloc(17);
p[18] = 0;
free(p);
uint8_t* q = (uint8_t*)malloc(17);
free(p);
free(q);
}
#define OVF_SIZE 100
static void block_overflow2(void) {
uint8_t* p = (uint8_t*)malloc(30);
memset(p+30, 0, OVF_SIZE);
free(p);
}
int main() {
printf("test overflow..\n");
block_overflow1();
block_overflow2();
printf("done..\n");
return 0;
}

View File

@ -103,7 +103,8 @@ static void* alloc_items(size_t items, random_t r) {
for (uintptr_t i = 0; i < items; i++) { for (uintptr_t i = 0; i < items; i++) {
p[i] = (items - i) ^ cookie; p[i] = (items - i) ^ cookie;
} }
} // if (pick(r)%1000 <= 1) { p[items+1] = 42; } // overflow
}
return p; return p;
} }