improved security by encoding NULL values; double free mitigation on by default; more precise free list corruption detection

This commit is contained in:
daan 2019-11-21 15:21:23 -08:00
parent 1674d551ff
commit 74dbfc30be
6 changed files with 49 additions and 36 deletions

View File

@ -7,8 +7,7 @@ set(CMAKE_CXX_STANDARD 17)
option(MI_OVERRIDE "Override the standard malloc interface" ON) option(MI_OVERRIDE "Override the standard malloc interface" ON)
option(MI_INTERPOSE "Use interpose to override standard malloc on macOS" ON) option(MI_INTERPOSE "Use interpose to override standard malloc on macOS" ON)
option(MI_DEBUG_FULL "Use full internal heap invariant checking in DEBUG mode" OFF) option(MI_DEBUG_FULL "Use full internal heap invariant checking in DEBUG mode" OFF)
option(MI_SECURE "Use security mitigations (like guard pages, allocation randomization, and free-list corruption detection)" OFF) option(MI_SECURE "Use full security mitigations (like guard pages, allocation randomization, double-free mitigation, and free-list corruption detection)" OFF)
option(MI_SECURE_FULL "Use full security mitigations, may be more expensive (includes double-free mitigation)" OFF)
option(MI_USE_CXX "Use the C++ compiler to compile the library" OFF) option(MI_USE_CXX "Use the C++ compiler to compile the library" OFF)
option(MI_SEE_ASM "Generate assembly files" OFF) option(MI_SEE_ASM "Generate assembly files" OFF)
option(MI_LOCAL_DYNAMIC_TLS "Use slightly slower, dlopen-compatible TLS mechanism (Unix)" OFF) option(MI_LOCAL_DYNAMIC_TLS "Use slightly slower, dlopen-compatible TLS mechanism (Unix)" OFF)
@ -70,15 +69,9 @@ if(MI_OVERRIDE MATCHES "ON")
endif() endif()
endif() endif()
if(MI_SECURE_FULL MATCHES "ON") if(MI_SECURE MATCHES "ON")
message(STATUS "Set full secure build (may be more expensive) (MI_SECURE_FULL=ON)") message(STATUS "Set full secure build (MI_SECURE=ON)")
list(APPEND mi_defines MI_SECURE=4) list(APPEND mi_defines MI_SECURE=4)
set(MI_SECURE "ON")
else()
if(MI_SECURE MATCHES "ON")
message(STATUS "Set secure build (MI_SECURE=ON)")
list(APPEND mi_defines MI_SECURE=3)
endif()
endif() endif()
if(MI_SEE_ASM MATCHES "ON") if(MI_SEE_ASM MATCHES "ON")
@ -92,7 +85,7 @@ if(MI_CHECK_FULL MATCHES "ON")
endif() endif()
if(MI_DEBUG_FULL MATCHES "ON") if(MI_DEBUG_FULL MATCHES "ON")
message(STATUS "Set debug level to full invariant checking (MI_DEBUG_FULL=ON)") message(STATUS "Set debug level to full internal invariant checking (MI_DEBUG_FULL=ON)")
list(APPEND mi_defines MI_DEBUG=3) # full invariant checking list(APPEND mi_defines MI_DEBUG=3) # full invariant checking
endif() endif()

View File

@ -275,14 +275,20 @@ static inline mi_segment_t* _mi_page_segment(const mi_page_t* page) {
return segment; return segment;
} }
// Get the page containing the pointer // used internally
static inline mi_page_t* _mi_segment_page_of(const mi_segment_t* segment, const void* p) { static inline uintptr_t _mi_segment_page_idx_of(const mi_segment_t* segment, const void* p) {
// if (segment->page_size > MI_SEGMENT_SIZE) return &segment->pages[0]; // huge pages // if (segment->page_size > MI_SEGMENT_SIZE) return &segment->pages[0]; // huge pages
ptrdiff_t diff = (uint8_t*)p - (uint8_t*)segment; ptrdiff_t diff = (uint8_t*)p - (uint8_t*)segment;
mi_assert_internal(diff >= 0 && diff < MI_SEGMENT_SIZE); mi_assert_internal(diff >= 0 && diff < MI_SEGMENT_SIZE);
uintptr_t idx = (uintptr_t)diff >> segment->page_shift; uintptr_t idx = (uintptr_t)diff >> segment->page_shift;
mi_assert_internal(idx < segment->capacity); mi_assert_internal(idx < segment->capacity);
mi_assert_internal(segment->page_kind <= MI_PAGE_MEDIUM || idx == 0); mi_assert_internal(segment->page_kind <= MI_PAGE_MEDIUM || idx == 0);
return idx;
}
// Get the page containing the pointer
static inline mi_page_t* _mi_segment_page_of(const mi_segment_t* segment, const void* p) {
uintptr_t idx = _mi_segment_page_idx_of(segment, p);
return &((mi_segment_t*)segment)->pages[idx]; return &((mi_segment_t*)segment)->pages[idx];
} }
@ -373,53 +379,67 @@ static inline void mi_page_set_has_aligned(mi_page_t* page, bool has_aligned) {
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// Encoding/Decoding the free list next pointers // Encoding/Decoding the free list next pointers
// Note: we pass a `null` value to be used as the `NULL` value for the
// end of a free list. This is to prevent the cookie itself to ever
// be present among user blocks (as `cookie^0==cookie`).
// ------------------------------------------------------------------- // -------------------------------------------------------------------
static inline bool mi_is_in_same_segment(const void* p, const void* q) { static inline bool mi_is_in_same_segment(const void* p, const void* q) {
return (_mi_ptr_segment(p) == _mi_ptr_segment(q)); return (_mi_ptr_segment(p) == _mi_ptr_segment(q));
} }
static inline mi_block_t* mi_block_nextx( uintptr_t cookie, const mi_block_t* block ) { static inline bool mi_is_in_same_page(const void* p, const void* q) {
mi_segment_t* segmentp = _mi_ptr_segment(p);
mi_segment_t* segmentq = _mi_ptr_segment(q);
if (segmentp != segmentq) return false;
uintptr_t idxp = _mi_segment_page_idx_of(segmentp, p);
uintptr_t idxq = _mi_segment_page_idx_of(segmentq, q);
return (idxp == idxq);
}
static inline mi_block_t* mi_block_nextx( const void* null, const mi_block_t* block, uintptr_t cookie ) {
#ifdef MI_ENCODE_FREELIST #ifdef MI_ENCODE_FREELIST
return (mi_block_t*)(block->next ^ cookie); mi_block_t* b = (mi_block_t*)(block->next ^ cookie);
if (mi_unlikely((void*)b==null)) { b = NULL; }
return b;
#else #else
UNUSED(cookie); UNUSED(cookie); UNUSED(null);
return (mi_block_t*)block->next; return (mi_block_t*)block->next;
#endif #endif
} }
static inline void mi_block_set_nextx(uintptr_t cookie, mi_block_t* block, const mi_block_t* next) { static inline void mi_block_set_nextx(const void* null, mi_block_t* block, const mi_block_t* next, uintptr_t cookie) {
#ifdef MI_ENCODE_FREELIST #ifdef MI_ENCODE_FREELIST
if (mi_unlikely(next==NULL)) { next = (mi_block_t*)null; }
block->next = (mi_encoded_t)next ^ cookie; block->next = (mi_encoded_t)next ^ cookie;
#else #else
UNUSED(cookie); UNUSED(cookie); UNUSED(null);
block->next = (mi_encoded_t)next; block->next = (mi_encoded_t)next;
#endif #endif
} }
static inline mi_block_t* mi_block_next(const mi_page_t* page, const mi_block_t* block) { static inline mi_block_t* mi_block_next(const mi_page_t* page, const mi_block_t* block) {
#ifdef MI_ENCODE_FREELIST #ifdef MI_ENCODE_FREELIST
mi_block_t* next = mi_block_nextx(page->cookie,block); mi_block_t* next = mi_block_nextx(page,block,page->cookie);
// check for free list corruption: is `next` at least in our segment range? // check for free list corruption: is `next` at least in our segment range?
// TODO: it is better to check if it is actually inside our page but that is more expensive // TODO: check if `next` is `page->block_size` aligned?
// to calculate. Perhaps with a relative free list this becomes feasible? if (next!=NULL && !mi_is_in_same_page(block, next)) {
if (next!=NULL && !mi_is_in_same_segment(block, next)) {
_mi_fatal_error("corrupted free list entry of size %zub at %p: value 0x%zx\n", page->block_size, block, (uintptr_t)next); _mi_fatal_error("corrupted free list entry of size %zub at %p: value 0x%zx\n", page->block_size, block, (uintptr_t)next);
next = NULL; next = NULL;
} }
return next; return next;
#else #else
UNUSED(page); UNUSED(page);
return mi_block_nextx(0, block); return mi_block_nextx(page,block,0);
#endif #endif
} }
static inline void mi_block_set_next(const mi_page_t* page, mi_block_t* block, const mi_block_t* next) { static inline void mi_block_set_next(const mi_page_t* page, mi_block_t* block, const mi_block_t* next) {
#ifdef MI_ENCODE_FREELIST #ifdef MI_ENCODE_FREELIST
mi_block_set_nextx(page->cookie,block,next); mi_block_set_nextx(page,block,next, page->cookie);
#else #else
UNUSED(page); UNUSED(page);
mi_block_set_nextx(0, block, next); mi_block_set_nextx(page,block, next,0);
#endif #endif
} }

View File

@ -26,16 +26,16 @@ terms of the MIT license. A copy of the license can be found in the file
// #define MI_SECURE 1 // guard page around metadata // #define MI_SECURE 1 // guard page around metadata
// #define MI_SECURE 2 // guard page around each mimalloc page // #define MI_SECURE 2 // guard page around each mimalloc page
// #define MI_SECURE 3 // encode free lists (detect corrupted free list (buffer overflow), and invalid pointer free) // #define MI_SECURE 3 // encode free lists (detect corrupted free list (buffer overflow), and invalid pointer free)
// #define MI_SECURE 4 // experimental, may be more expensive: checks for double free. (cmake -DMI_SECURE_FULL=ON) // #define MI_SECURE 4 // checks for double free. (may be more expensive)
#if !defined(MI_SECURE) #if !defined(MI_SECURE)
#define MI_SECURE 0 #define MI_SECURE 4
#endif #endif
// Define MI_DEBUG for debug mode // Define MI_DEBUG for debug mode
// #define MI_DEBUG 1 // basic assertion checks and statistics, check double free, corrupted free list, and invalid pointer free. // #define MI_DEBUG 1 // basic assertion checks and statistics, check double free, corrupted free list, and invalid pointer free.
// #define MI_DEBUG 2 // + internal assertion checks // #define MI_DEBUG 2 // + internal assertion checks
// #define MI_DEBUG 3 // + extensive internal invariant checking (cmake -DMI_CHECK_FULL=ON) // #define MI_DEBUG 3 // + extensive internal invariant checking (cmake -DMI_DEBUG_FULL=ON)
#if !defined(MI_DEBUG) #if !defined(MI_DEBUG)
#if !defined(NDEBUG) || defined(_DEBUG) #if !defined(NDEBUG) || defined(_DEBUG)
#define MI_DEBUG 2 #define MI_DEBUG 2

View File

@ -157,7 +157,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->cookie, block); // pretend it is freed, and get the decoded first field mi_block_t* n = mi_block_nextx(page, block, page->cookie); // 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_segment(block, n))) // quick check: in same segment or NULL? (n==NULL || mi_is_in_same_segment(block, n))) // quick check: in same segment or NULL?
{ {
@ -242,7 +242,7 @@ static mi_decl_noinline void _mi_free_block_mt(mi_page_t* page, mi_block_t* bloc
mi_block_t* dfree; mi_block_t* dfree;
do { do {
dfree = (mi_block_t*)heap->thread_delayed_free; dfree = (mi_block_t*)heap->thread_delayed_free;
mi_block_set_nextx(heap->cookie,block,dfree); mi_block_set_nextx(heap,block,dfree, heap->cookie);
} while (!mi_atomic_cas_ptr_weak(mi_atomic_cast(void*,&heap->thread_delayed_free), block, dfree)); } while (!mi_atomic_cas_ptr_weak(mi_atomic_cast(void*,&heap->thread_delayed_free), block, dfree));
} }

View File

@ -283,7 +283,7 @@ void _mi_heap_delayed_free(mi_heap_t* heap) {
// and free them all // and free them all
while(block != NULL) { while(block != NULL) {
mi_block_t* next = mi_block_nextx(heap->cookie,block); mi_block_t* next = mi_block_nextx(heap,block, heap->cookie);
// use internal free instead of regular one to keep stats etc correct // use internal free instead of regular one to keep stats etc correct
if (!_mi_free_delayed_block(block)) { if (!_mi_free_delayed_block(block)) {
// we might already start delayed freeing while another thread has not yet // we might already start delayed freeing while another thread has not yet
@ -291,7 +291,7 @@ void _mi_heap_delayed_free(mi_heap_t* heap) {
mi_block_t* dfree; mi_block_t* dfree;
do { do {
dfree = (mi_block_t*)heap->thread_delayed_free; dfree = (mi_block_t*)heap->thread_delayed_free;
mi_block_set_nextx(heap->cookie, block, dfree); mi_block_set_nextx(heap, block, dfree, heap->cookie);
} while (!mi_atomic_cas_ptr_weak(mi_atomic_cast(void*,&heap->thread_delayed_free), block, dfree)); } while (!mi_atomic_cas_ptr_weak(mi_atomic_cast(void*,&heap->thread_delayed_free), block, dfree));
} }
@ -356,7 +356,7 @@ void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq) {
#if MI_DEBUG>1 #if MI_DEBUG>1
// check there are no references left.. // check there are no references left..
for (mi_block_t* block = (mi_block_t*)pheap->thread_delayed_free; block != NULL; block = mi_block_nextx(pheap->cookie, block)) { for (mi_block_t* block = (mi_block_t*)pheap->thread_delayed_free; block != NULL; block = mi_block_nextx(pheap, block, pheap->cookie)) {
mi_assert_internal(_mi_ptr_page(block) != page); mi_assert_internal(_mi_ptr_page(block) != page);
} }
#endif #endif

View File

@ -15,9 +15,9 @@ int main() {
mi_version(); mi_version();
// detect double frees and heap corruption // detect double frees and heap corruption
//double_free1(); // double_free1();
//double_free2(); // double_free2();
//corrupt_free(); // corrupt_free();
void* p1 = malloc(78); void* p1 = malloc(78);
void* p2 = malloc(24); void* p2 = malloc(24);