mi_free() crashes on foreign pointers in release MI_SECURE=OFF#1285
mi_free() crashes on foreign pointers in release MI_SECURE=OFF#1285jaschiu wants to merge 1 commit intomicrosoft:dev3from
MI_SECURE=OFF#1285Conversation
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.
@microsoft-github-policy-service agree |
|
Thanks for bringing this up. So, generally, the idea of the release build is to link with an application that will only pass valid pointers to I think if the btw. we also have |
Hi, on my Gentoo Linux system where I have Librewolf (a fork of firefox) use mimalloc via
LD_PRELOAD, I noticed that when running it with mimalloc secure mode off, it segfaults. I had my LLM vibecode this PR and the below description.Summary
In a standard
CMAKE_BUILD_TYPE=Releasebuild withMI_SECURE=OFF(and noMI_DEBUG, non-Apple),mi_free(p)dereferences a NULL pagemap submap pointer and SIGSEGVs wheneverpis a valid-but-foreign pointer — i.e. a pointer that was not allocated out of a mimalloc arena. Every other production allocator (glibc malloc, jemalloc, tcmalloc) tolerates such calls; mimalloc's own debug /MI_SECURE/__APPLE__builds already do as well. Only the default release Linux fast path is affected.This is observable in the wild:
LD_PRELOAD=libmimalloc.so librewolf(LibreWolf 150.0.1, identical for Firefox with any Mesa-accelerated build) reliably segfaults duringgbm_create_device→dlopen("libLLVM.so.20.1"), because an LLVM static initializer callsfree()on a pointer that lives on a Mesa/llvmpipe worker thread's stack (or its pthread TCB page). I assume most Mesa-accelerated Firefox/Chromium setups on Linux hit this if they build mimalloc withoutMI_SECURE.Repro (tested versions: v3.1.5, v3.2.8, v3.3.2, current
dev3tip (8e4230a) — all affected)Environment:
CFLAGS=-march=native -O3 -flto ...(reproduces with plain-O2 -march=nativetoo)MI_PAGE_MAP_FLAT=0)Steps:
Backtrace (
RelWithDebInfo)Faulting instruction:
mov (%rax,%rcx,8),%rdiwithrax=0x0(submap pointer),rcx=0xf02(sub_idx) → SIGSEGV atsi_addr = 0x7810. Matches the strace-visible signaturesi_addr=0x6650/0x7810/etc. (differentsub_idxper run).Pointer provenance
/proc/<pid>/mapsshows0x7fffcf029320lying in a ~1008 KiB anonymousrw-pregion bracketed by---pguard pages — the canonical Mesa/llvmpipe worker-thread stack layout. It is NOT inside the mimalloc arena range (0x7fff85cd0000-0x7fffc5cd0000in this run). Glibc'sfree()on this same pointer is a no-op (or a graceful "invalid pointer" error) — LibreWolf runs fine withoutLD_PRELOADand withLD_PRELOAD=libmimalloc-secure.so.Root cause
include/mimalloc/internal.h:658-664:_mi_unchecked_ptr_pageunconditionally dereferences_mi_page_map_at(idx):The
// NULL if p==NULLcomment is incorrect — the load crashes on anypwhose page-map submap slot is NULL, which happens for every pointer outside mimalloc's committed arena slices.mi_free_ex(src/free.c:170-171) is already written to toleratepage == NULL, and_mi_checked_ptr_page(include/mimalloc/internal.h:647-653) literally adds oneif mi_unlikely(sub == NULL) return NULL;. The graceful contract exists — the release Linux fast path is the only call site that skips it.Proposed fix (one hunk)
Cost: one predicted-not-taken
test/jeperfree. Benchmarked no measurable change intest-stressruntime on this box. Brings release Linux builds into behavioral parity with glibc / jemalloc / tcmalloc and with mimalloc's ownMI_SECURE=ON/MI_DEBUG/__APPLE__builds.Regression test
Added
test/test-free-foreign.ccoveringmi_free(NULL),mi_free(stack_ptr),mi_free(static_ptr),mi_free(arbitrary_va), and a roundtripmi_malloc→mi_freeafterward. Registered via the existingforeach(TEST_NAME api api-fill stress free-foreign)loop in the top-levelCMakeLists.txt. All 5 subtests pass with the fix; thestack_ptr/static_ptr/arbitrary_vasubtests crash without it in the default release build.Verification
On the repro machine (3 consecutive trials per row,
timeout 12 librewolf -no-remote --profile <tmp> about:blank):LD_PRELOADMI_SECURE=OFFMI_SECURE=ONMI_SECURE=OFFMI_SECURE=ONMI_SECURE=OFF+ the patch aboveMI_SECURE=OFFMI_SECURE=OFF+ the patch abovedev3@8e4230a9 +MI_SECURE=OFFdev3@8e4230a9 +MI_SECURE=OFF+ the patch aboveThe patch applies cleanly via
git applyagainst v3.2.8, v3.3.2, and currentdev3tip; the hunk in_mi_ptr_pagehasn't been touched upstream since v3.1.5. Fullctestsuite (test-api,test-api-fill,test-stress,test-free-foreign,test-stress-dynamic) is 5/5 pass on both v3.3.2 anddev3after the patch.Why this wasn't caught earlier
LD_PRELOADuse applications that don't dlopen LLVM/Mesa after mimalloc init (e.g. server workloads, benchmarks).mimalloc-3.1.5ebuild exposed ahardenedUSE flag that mapped to-DMI_SECURE=ON; Firefox/LibreWolf users withhardenedset (a common Gentoo default) implicitly used the checked path and never hit this.mimalloc-3.2.8-r1ebuild hard-codes-DMI_SECURE=OFF, newly exposing release users to the fast path.