Skip to content

fix: free(NULL) crashes before initialization with the 2-level page map - #29

Closed
LucFauvel wants to merge 1 commit into
oven-sh:bun-dev3-v2from
LucFauvel:fix/free-null-before-init
Closed

fix: free(NULL) crashes before initialization with the 2-level page map#29
LucFauvel wants to merge 1 commit into
oven-sh:bun-dev3-v2from
LucFauvel:fix/free-null-before-init

Conversation

@LucFauvel

Copy link
Copy Markdown

fixes anthropics/claude-code#89370

Issue not reachable on upstream microsoft/mimalloc as _mi_aligned_ptr_page0 early NULL returns, while this fork is always via page map.

The static mi_page_map_empty used before _mi_page_map_init runs has submaps[0] == NULL. The default (non-secure, non-debug) build resolves a pointer through _mi_unchecked_ptr_page, which loads submaps[idx][sub_idx] without testing the sub-map for NULL, so free(NULL) -- where both indices are 0 -- dereferences address 0 instead of yielding a NULL page.

_mi_checked_ptr_page does test for it, which is why only the default release build is affected; MI_SECURE / MI_DEBUG builds are fine. The flat page map got this right in microsoft#1341 by pointing its empty map at a real one-element array; this does the same for the 2-level map.

Reachable in practice: glibc >= 2.44 ends newlocale() with an unconditional free() of a buffer that is NULL on the common path, and the C++ runtime calls newlocale() from a static initializer while building std::locale::classic(). With MI_OVERRIDE that free(NULL) reaches mimalloc before any allocation has happened, so before the page map exists, and the process dies in a static initializer before main.

Codegen is unchanged (mi_free is byte-identical); the fix costs 8 bytes of BSS.

… map

The static `mi_page_map_empty` used before `_mi_page_map_init` runs has
`submaps[0] == NULL`. The default (non-secure, non-debug) build resolves a
pointer through `_mi_unchecked_ptr_page`, which loads `submaps[idx][sub_idx]`
without testing the sub-map for NULL, so `free(NULL)` -- where both indices are
0 -- dereferences address 0 instead of yielding a NULL page.

`_mi_checked_ptr_page` does test for it, which is why only the default release
build is affected; MI_SECURE / MI_DEBUG builds are fine. The flat page map got
this right in microsoft#1341 by pointing its empty map at a real one-element array; this
does the same for the 2-level map.

Reachable in practice: glibc >= 2.44 ends `newlocale()` with an unconditional
`free()` of a buffer that is NULL on the common path, and the C++ runtime calls
`newlocale()` from a static initializer while building `std::locale::classic()`.
With MI_OVERRIDE that `free(NULL)` reaches mimalloc before any allocation has
happened, so before the page map exists, and the process dies in a static
initializer before `main`.

Codegen is unchanged (`mi_free` is byte-identical); the fix costs 8 bytes of BSS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@LucFauvel

LucFauvel commented Aug 25, 2026

Copy link
Copy Markdown
Author

Can't request review so @Jarred-Sumner if you can take a look. Seems to be breaking for more and more people on glibc 2.44. Upstream has made this bug unreachable (but latent) so an upstream sync might be better.

@LucFauvel LucFauvel closed this Aug 25, 2026
@LucFauvel

LucFauvel commented Aug 25, 2026

Copy link
Copy Markdown
Author

Duplicate to changes in #30

Closing.

@LucFauvel
LucFauvel deleted the fix/free-null-before-init branch August 25, 2026 03:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] claude segfaults, so does install.sh

1 participant