Repository navigation
Conversation
Refresh vendored mimalloc from 3.3.2 to 3.4.4, including the upstream fix for free(NULL) before allocator initialization (microsoft/mimalloc#1341). Preserve and document local patches and refresh SBOM and integrity metadata. Signed-off-by: Felix Rath <felixm.rath@gmail.com>
Link an ELF preinit fixture against the production allocator so global interposition is exercised before constructors. Run the regression with the production checks in scripts/test.sh. Signed-off-by: Felix Rath <felixm.rath@gmail.com>
|
Thanks for opening this — it has been seen, and it is queued. This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence. Current review status: working through a backlog. What that means for this PR, concretely:
Things that will genuinely speed it up whenever review does happen:
If this fixes a bug, a reproduction we can run is worth more than a description of the symptom. Thanks for contributing, and sorry in advance for the wait. |
|
Thanks for the allocator startup reproduction and for separating the pre-init regression from the broader failing checks. I have read the update and its validation limits. The vendored mimalloc update remains pending review; the un-compared full-suite failures are not being treated as baseline-green evidence. |
|
Thank you, @futile -- this is an excellent report and an even better fix. Your analysis is spot on: with a recent glibc, newlocale() can call free(NULL) before mimalloc's constructor runs, and our current release allocator then reads a page map that does not exist yet. We reproduced it with our production allocator flags and a preinit free(NULL), exactly as your test does, and confirmed that the upstream fix (microsoft/mimalloc@b2226c1) makes it go away. We also checked your vendored tree against the upstream v3.4.4 tag -- it matches exactly apart from the two documented local edits, which made the review much easier. One thing shapes how we land it: we vendor third-party code ourselves, and main now carries a second local mimalloc patch (a macOS memory-footprint change in src/prim/unix/prim.c, which is why this branch conflicts). We are deciding between a small backport of the upstream fix and the full 3.4.4 refresh, and will come back here shortly with the route. Either way your diagnosis and your preinit regression test are the basis, with credit to you. Thank you for the careful write-up and the reproduce-first test. |
|
Thank you again, @futile. As promised, here is the route we took. We went with a full refresh rather than a backport, and since we vendor third-party code ourselves, it lands as a maintainer PR: #2731. It vendors mimalloc v3.5.3, upstream's latest published release (newer than the 3.4.4 in this branch, and it contains microsoft/mimalloc@b2226c1, the fix you identified), and keeps the two local changes main still needs: the macOS decommit change in src/prim/unix/prim.c and your comment rewording in the zone file, which our vendored-code gate still requires. The reproducible-banner edit is no longer needed, because upstream dropped the build clock in v3.4.5. Each remaining local change is now documented in vendored/mimalloc/PATCHES.md and pinned by a check that fails without it. Your preinit test is the heart of the new contract test: tests/test_prod_allocator_contract.sh links a free(NULL)-from-.preinit_array fixture against the production allocator object, exactly as your test does, and runs on every Linux and macOS leg. On Linux it fails on current main (segfault, every run, with gcc and clang) and passes on the new branch. You are credited as co-author on both commits. We will close this PR once #2731 is merged. Thank you for the diagnosis, the upstream pointers and the reproduce-first test -- they made this a well-understood fix. |
|
Thank you very much for the detailed updates, the credit on the commits, and for the nice, improved solution! Feel free to close as you like :) |
With mimalloc 3.3.2's release build, free(NULL) before the allocator's constructor dereferenced a page map that does not exist yet. glibc 2.44's newlocale() does exactly that during libstdc++ start-up, so every Linux binary died with SIGSEGV before main (reported with a reproduction in #2562). Upstream fixed it in b2226c19 (mimalloc #1341), first released in v3.4.4; v3.5.3, upstream's latest published release, contains it. Vendored from microsoft/mimalloc tag v3.5.3 (tag object deac3dcf, commit d4881d338125e1cb7c47ba4cfb398d6f7c0c8d45): LICENSE, include/ and src/, written blob by blob from a bare clone. Before the local patches were reapplied, the staged LICENSE blob and include/ and src/ trees had the tag's exact object ids (50 files; src/arena-meta.c is gone upstream). Afterwards the only differences are two documented hunks: - src/prim/unix/prim.c: macOS always recommits on decommit, carried from 63fb7b3 (the phys_footprint fix). - src/prim/osx/alloc-override-zone.c: one comment reworded, because scripts/security-vendored.sh rejects the upstream "fork()" spelling even inside a comment. No code change. The reproducible-banner patch (no __DATE__/__TIME__) is dropped: upstream removed the build clock in v3.4.5 (mimalloc #1355). The two Makefile comments that pointed at that patch now say so. The production object is built with -DMI_PROFILE=0: mimalloc 3.5.2+ compiles its allocation-sampling profiler in by default, with a sample countdown on the allocation path, and the product never uses it. vendored/mimalloc/PATCHES.md records the pin, a re-verification recipe, the check that pins each patch and the refresh procedure; THIRD_PARTY.md points at it, the SBOM version is 3.5.3 and scripts/vendored-checksums.txt is regenerated (mimalloc lines only; security-vendored.sh passes). Measured on Apple M5 Pro (macOS 26.6), full indexes, 2 interleaved reps, main fceb25d (3.3.2) -> this change: TypeScript peak phys_footprint 1.07/1.01 -> 1.04/1.04 GB, peak RSS 1.87/1.84 -> 1.85/1.86 GB, wall 30.6/29.1 -> 29.3/30.0 s; django footprint 0.41/0.41 -> 0.38/0.39 GB, RSS 0.59/0.59 -> 0.58/0.60 GB, wall 6.5/6.2 -> 6.5/6.5 s. The graphs are identical: full node and edge hashes, node and edge census, no dangling edges (django 68022 nodes / 322464 edges, TypeScript 306239 / 835984). The same tree without the recommit patch peaks at 2.09/2.04 GB footprint on TypeScript, so 63fb7b3 still does its job. Linux arm64 (glibc 2.39, gcc 13.3): both trees build without warnings; django peak RSS 0.67/0.68 GB on main, 0.69/0.68 GB with this change. Co-authored-by: Felix Rath <felixm.rath@gmail.com> Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The unit suites link mimalloc's debug build without the global override, so they could not see the #2562 start-up crash, and nothing guarded the vendored allocator's local behaviour: prod_mimalloc.o is compiled with -w and without CFLAGS_COMMON, so -Wdate-time never reaches the vendored sources, and the macOS recommit patch had no test at all. tests/test_prod_allocator_contract.sh links three small fixtures against $BUILD_DIR/prod_mimalloc.o itself: 1. free(NULL) from an ELF .preinit_array hook, before every constructor (Linux). Also asserts malloc returns a mimalloc-owned block, so a fixture that did not link the override fails instead of passing. 2. mi_options_print_out's first line is exactly "vX.Y.Z" with no build clock, and the object is the release build (debug level 0). Upstream dropped the clock in v3.4.5; this keeps a refresh from bringing it back. The banner also has no "profiling" line, which pins -DMI_PROFILE=0. 3. _mi_prim_decommit sets needs_recommit on macOS and leaves upstream's release value elsewhere (local patch 1 in vendored/mimalloc/PATCHES.md). scripts/test.sh runs it right after Step 5 builds the production binary, on every Linux and macOS leg; other platforms skip with the reason. Linux arm64 (glibc 2.39), gcc 13.3 and clang 18.1, 4 runs each: main's 3.3.2 object segfaults in fixture 1 (exit 139) 4/4 with both compilers; this branch passes all three fixtures 4/4 with both. macOS arm64: reverting the recommit patch turns fixture 3 red (needs_recommit=0); an object with a git-describe suffix in the banner, built with MI_DEBUG=2, or built with MI_PROFILE=1 turns fixture 2 red. The preinit reproduction follows the test Felix Rath wrote for #2562. Co-authored-by: Felix Rath <felixm.rath@gmail.com> Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
What does this PR do?
Fix a Linux startup segfault exposed by glibc 2.44 by updating vendored mimalloc from 3.3.2 to 3.4.4 (upstream commit
1f06f694972279bbc7ec72902e8570f5784d0fc9).During libstdc++ initialization, glibc's
newlocale()can call the globally interposedfree(NULL)before mimalloc's constructor runs. The old release allocator dereferences its uninitialized page map, leading to this SIGSEGV immediately and reproducibly when the cli binary starts:mimalloc 3.4.4, the version this PR updates to, includes the upstream fix:
Changes
include/,src/, andLICENSEfrom the upstream release. The existing reproducible-banner patch is preserved; one macOS comment is reworded to avoid a false positive in the vendored security scanner. Both local differences and the immutable source pin are documented invendored/mimalloc/PATCHES.md.prod_mimalloc.owith production allocator interposition. It callsfree(NULL)before allocator initialization and then checks normal allocation/free inmain. Wire it into the production checks inscripts/test.sh.The normal sanitizer runner does not enable global allocator interposition, so it cannot cover this startup regression. The new fixture fails with SIGSEGV (exit 139) when linked to the original mimalloc 3.3.2 production object, and passes with 3.4.4.
Validation
Tested locally on x86_64 Linux with GCC 16.2.0 and glibc 2.44:
scripts/build.sh; production binary--version.bash tests/test_mimalloc_startup.sh build/c, with the failing old-allocator comparison described above.scripts/test.sh --suites "$(build/tests/test-runner --list-suites)" BUILD_DIR=build/tests(ASan + UBSan build). The log contains 832 assertion-failure lines, many from probe fixtures reporting no graph DB, recovering UBSan diagnostics, and a LeakSanitizer report of 13,399 bytes in 11 allocations. These additional failures have not been compared against clean upstream main; they are not claimed to be baseline failures or unrelated to this change.scripts/smoke-local.sh build/c/codebase-memory-mcp, including indexing, MCP queries, install/uninstall, and daemon cleanup. The UI-only leg was skipped because the frontend was not built.cli --progress index_status --project ...form, against a freshly indexed private fixture rather than an existing user index.scripts/security-audit.shandscripts/security-vendored.sh. The broadermake -f Makefile.cbm securitytarget also passed source, binary-string, source-only UI, sandbox-install, and strace network checks, but stopped on a 10-second MCP fuzz timeout (shell injection semicolon). That exact input passed three isolated retries; the subsequent fullscripts/security-fuzz.shretry passed 32/32. The combined security target itself is not claimed as passing, and built-frontend integrity was not verified.Existing baseline failures
The following checks fail on this branch and were reproduced on a clean checkout of upstream main
bf93f0b7c3b82683b3ac3b00bb0342465878cdf8, with the same local tool versions:bash tests/test_version_metadata_contract.sh: the Scoop manifest pin is0.11.0, equal to the latest stable tag; the contract requires changing that surface's classification frompin:0.11.0toreleaseand dropping itsPIN_REASONSentry. This stops the defaultscripts/test.shbefore the C suites, hence the separate all-suite invocation above.src/mcp/mcp.c:962reports a possible null dereference ofallowed. Reproduced independently withmake -f Makefile.cbm lint-cppcheck LINT_SRCS=src/mcp/mcp.c. The branch's full lint gate also fails; formatting, NOLINT, memory-core checks, and applicable diff-scoped clang-tidy passed.The full
scripts/lint.shaudit additionally encounters GCC-only warning flags passed to Clang and pre-existing findings in unchanged files. This is not a claim that the full unpatched test/lint suite or GitHub CI was run. Baseline failures were not changed or suppressed in this focused allocator fix. The local pre-commit gate was bypassed because these seemed potentially unrelated to me (the human), and I wanted to see what PR CI says/what discussion says.AI disclosure
This contribution and description were prepared with an OpenAI coding agent in Pi on behalf of Felix Rath (
futile). Felix is the accountable human contributor and explicitly authorized the DCO sign-off on every commit. No private agent-session links are included.Checklist
git commit -s);scripts/check-dco.sh upstream/main..HEADpassed for both commits.