From aaf8da9abaf3bc30233c3cade9a1ed7702bb5e74 Mon Sep 17 00:00:00 2001 From: Daan Date: Tue, 17 Jun 2025 19:49:09 -0700 Subject: [PATCH] potential fix for use-after-free of the tld on unsafe arenas destroy --- include/mimalloc/internal.h | 2 +- src/arena.c | 15 +++++++-------- src/init.c | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/include/mimalloc/internal.h b/include/mimalloc/internal.h index fd6c8d5d..46274567 100644 --- a/include/mimalloc/internal.h +++ b/include/mimalloc/internal.h @@ -223,7 +223,7 @@ void* _mi_arenas_alloc_aligned(mi_subproc_t* subproc, size_t size, size_ void _mi_arenas_free(void* p, size_t size, mi_memid_t memid); bool _mi_arenas_contain(const void* p); void _mi_arenas_collect(bool force_purge, bool visit_all, mi_tld_t* tld); -void _mi_arenas_unsafe_destroy_all(mi_tld_t* tld); +void _mi_arenas_unsafe_destroy_all(mi_subproc_t* subproc); mi_page_t* _mi_arenas_page_alloc(mi_heap_t* heap, size_t block_size, size_t page_alignment); void _mi_arenas_page_free(mi_page_t* page, mi_tld_t* tld); diff --git a/src/arena.c b/src/arena.c index b5fc095c..3d78fd91 100644 --- a/src/arena.c +++ b/src/arena.c @@ -993,7 +993,7 @@ void _mi_arenas_page_unabandon(mi_page_t* page) { Arena free ----------------------------------------------------------- */ static void mi_arena_schedule_purge(mi_arena_t* arena, size_t slice_index, size_t slices); -static void mi_arenas_try_purge(bool force, bool visit_all, mi_tld_t* tld); +static void mi_arenas_try_purge(bool force, bool visit_all, mi_subproc_t* subproc, size_t tseq); void _mi_arenas_free(void* p, size_t size, mi_memid_t memid) { if (p==NULL) return; @@ -1054,7 +1054,7 @@ void _mi_arenas_free(void* p, size_t size, mi_memid_t memid) { // Purge the arenas; if `force_purge` is true, amenable parts are purged even if not yet expired void _mi_arenas_collect(bool force_purge, bool visit_all, mi_tld_t* tld) { - mi_arenas_try_purge(force_purge, visit_all, tld); + mi_arenas_try_purge(force_purge, visit_all, tld->subproc, tld->thread_seq); } @@ -1109,9 +1109,9 @@ static void mi_arenas_unsafe_destroy(mi_subproc_t* subproc) { // destroy owned arenas; this is unsafe and should only be done using `mi_option_destroy_on_exit` // for dynamic libraries that are unloaded and need to release all their allocated memory. -void _mi_arenas_unsafe_destroy_all(mi_tld_t* tld) { - mi_arenas_unsafe_destroy(tld->subproc); - _mi_arenas_collect(true /* force purge */, true /* visit all*/, tld); // purge non-owned arenas +void _mi_arenas_unsafe_destroy_all(mi_subproc_t* subproc) { + mi_arenas_unsafe_destroy(subproc); + mi_arenas_try_purge(true /* force purge */, true /* visit all*/, subproc, 0 /* thread seq */); // purge non-owned arenas } @@ -1775,14 +1775,13 @@ static bool mi_arena_try_purge(mi_arena_t* arena, mi_msecs_t now, bool force) } -static void mi_arenas_try_purge(bool force, bool visit_all, mi_tld_t* tld) +static void mi_arenas_try_purge(bool force, bool visit_all, mi_subproc_t* subproc, size_t tseq) { // try purge can be called often so try to only run when needed const long delay = mi_arena_purge_delay(); if (_mi_preloading() || delay <= 0) return; // nothing will be scheduled // check if any arena needs purging? - mi_subproc_t* subproc = tld->subproc; const mi_msecs_t now = _mi_clock_now(); const mi_msecs_t arenas_expire = mi_atomic_loadi64_acquire(&subproc->purge_expire); if (!visit_all && !force && (arenas_expire == 0 || arenas_expire > now)) return; @@ -1796,7 +1795,7 @@ static void mi_arenas_try_purge(bool force, bool visit_all, mi_tld_t* tld) { // increase global expire: at most one purge per delay cycle if (arenas_expire > now) { mi_atomic_storei64_release(&subproc->purge_expire, now + (delay/10)); } - const size_t arena_start = tld->thread_seq % max_arena; + const size_t arena_start = tseq % max_arena; size_t max_purge_count = (visit_all ? max_arena : (max_arena/4)+1); bool all_visited = true; bool any_purged = false; diff --git a/src/init.c b/src/init.c index 52a8f11c..eb508bc9 100644 --- a/src/init.c +++ b/src/init.c @@ -778,7 +778,7 @@ void mi_cdecl mi_process_done(void) mi_attr_noexcept { if (mi_option_is_enabled(mi_option_destroy_on_exit)) { mi_heap_collect(heap, true /* force */); _mi_heap_unsafe_destroy_all(heap); // forcefully release all memory held by all heaps (of this thread only!) - _mi_arenas_unsafe_destroy_all(heap->tld); + _mi_arenas_unsafe_destroy_all(_mi_subproc_main()); _mi_page_map_unsafe_destroy(_mi_subproc_main()); } //_mi_page_map_unsafe_destroy(_mi_subproc_main());