From 54615f105085928b90d7ea6fd7a3ce5d0014fb2c Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Sun, 3 May 2026 23:19:41 -0700 Subject: [PATCH] free: always use _mi_checked_ptr_page in _mi_ptr_page Release Linux builds with MI_SECURE=OFF (and no MI_DEBUG, not __APPLE__) used _mi_unchecked_ptr_page, which dereferences _mi_page_map_at(idx) without a NULL check. Any `mi_free(p)` on a pointer outside mimalloc's committed arena slices (e.g. a stack pointer or an allocation from a library that wasn't intercepted by the override) therefore SIGSEGVs, while glibc / jemalloc / tcmalloc / mimalloc-secure / mimalloc-debug / mimalloc-__APPLE__ all handle such calls gracefully. Observed via LD_PRELOAD=libmimalloc.so on LibreWolf 150 (Firefox derivative) on Linux: `gbm_create_device` dlopens libLLVM.so.20.1 whose static initializer calls `free()` on a Mesa/llvmpipe worker-thread stack pointer. Fix: route `_mi_ptr_page` through `_mi_checked_ptr_page` unconditionally. The extra cost is one predicted-not-taken branch per free; stress tests show no measurable regression. Adds a targeted `test-free-foreign` regression test. --- CMakeLists.txt | 2 +- include/mimalloc/internal.h | 4 --- test/test-free-foreign.c | 64 +++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 test/test-free-foreign.c diff --git a/CMakeLists.txt b/CMakeLists.txt index ee043aeac..9928543af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -764,7 +764,7 @@ if (MI_BUILD_TESTS) enable_testing() # static link tests - foreach(TEST_NAME api api-fill stress) + foreach(TEST_NAME api api-fill stress free-foreign) add_executable(mimalloc-test-${TEST_NAME} test/test-${TEST_NAME}.c) target_compile_definitions(mimalloc-test-${TEST_NAME} PRIVATE ${mi_defines}) target_compile_options(mimalloc-test-${TEST_NAME} PRIVATE ${mi_cflags}) diff --git a/include/mimalloc/internal.h b/include/mimalloc/internal.h index bf0b6975a..0691f2eb5 100644 --- a/include/mimalloc/internal.h +++ b/include/mimalloc/internal.h @@ -657,11 +657,7 @@ static inline mi_page_t* _mi_checked_ptr_page(const void* p) { static inline mi_page_t* _mi_ptr_page(const void* p) { mi_assert_internal(p==NULL || mi_is_in_heap_region(p)); - #if MI_DEBUG || MI_SECURE || defined(__APPLE__) return _mi_checked_ptr_page(p); - #else - return _mi_unchecked_ptr_page(p); - #endif } diff --git a/test/test-free-foreign.c b/test/test-free-foreign.c new file mode 100644 index 000000000..b7559947f --- /dev/null +++ b/test/test-free-foreign.c @@ -0,0 +1,64 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2026, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +/* +Regression test: `mi_free` must not crash when called on a pointer that was +not allocated by mimalloc. In LD_PRELOAD / override scenarios this is observed +in the wild when libraries with their own internal bookkeeping (e.g. LLVM via +Mesa's `gbm_create_device`) call `free()` on pointers that live on a thread +stack or on a static/TCB page whose pagemap submap entry is NULL. + +Every other production allocator (glibc, jemalloc, tcmalloc) tolerates such +calls, and mimalloc's debug/MI_SECURE/macOS paths already did. This test +exercises the release-Linux fast path that historically bypassed the NULL +submap check in `_mi_ptr_page`. +*/ + +#include +#include +#include + +#include "mimalloc.h" +#include "testhelper.h" + +static uint8_t g_static_buffer[4096]; + +int main(void) { + mi_option_disable(mi_option_verbose); + + CHECK_BODY("mi_free-NULL") { + mi_free(NULL); + result = true; + }; + + CHECK_BODY("mi_free-stack-pointer") { + uint8_t stack_buffer[256]; + memset(stack_buffer, 0, sizeof(stack_buffer)); + mi_free(&stack_buffer[16]); + result = true; + }; + + CHECK_BODY("mi_free-static-pointer") { + memset(g_static_buffer, 0, sizeof(g_static_buffer)); + mi_free(&g_static_buffer[16]); + result = true; + }; + + CHECK_BODY("mi_free-mmap-style-pointer") { + uintptr_t fake = (uintptr_t)&g_static_buffer[0] ^ (uintptr_t)0x1000; + mi_free((void*)fake); + result = true; + }; + + CHECK_BODY("mi_free-roundtrip-after-foreign") { + void* p = mi_malloc(128); + result = (p != NULL); + if (result) { mi_free(p); } + }; + + return print_test_summary(); +}