Skip to content

chore(cli): build mimalloc in secure mode - #1356

Merged
gregmagolan merged 2 commits into
mainfrom
mimalloc-secure-diagnostic
Jul 29, 2026
Merged

chore(cli): build mimalloc in secure mode#1356
gregmagolan merged 2 commits into
mainfrom
mimalloc-secure-diagnostic

Conversation

@gregmagolan

@gregmagolan gregmagolan commented Jul 29, 2026

Copy link
Copy Markdown
Member

Follow-up to #1355 (mimalloc). Builds mimalloc with its secure feature and makes detected heap corruption fatal.

Moving off musl's mallocng loses a diagnostic property we were relying on. mallocng validates a check byte on every allocation, so heap corruption aborted the process by itself — that is exactly what produced the a_crash/enframe abort used to identify the intermittent lint segfault as heap corruption in the first place. A default mimalloc build performs no equivalent check, so the same corruption would now pass unnoticed and resurface later as an unrelated failure.

Secure build. MI_SECURE=4 gives guard pages around metadata, encoded free lists, randomized placement, and double-free detection. Verified present in the built x86_64-unknown-linux-musl release binary: corrupted free list entry of size %zub at %p, double free detected of block %p with size %zu, and corrupted meta-data in thread-free list.

Abort on detection. Detection alone is not enough: mimalloc's default handler aborts only on EFAULT (corrupted metadata, corrupted thread-free list, and — under secure — a detected buffer overflow), while a double free (EAGAIN) or a free of an invalid pointer (EINVAL) is reported and then execution continues. Continuing on a corrupted heap is what makes this class of bug so hard to trace: the eventual crash lands somewhere unrelated, long after the write that caused it. This registers an error handler via mi_register_error that aborts on every corruption code, so a detection is fatal where it happens. The SIGABRT is caught by the crash handler, which reports it with a resolvable address.

Setting MIMALLOC_SHOW_ERRORS=1 on a diagnostic run additionally prints the message saying what was detected (show_errors defaults off in a release build). The abort happens either way.


Changes are visible to end-users: yes

  • Searched for relevant documentation and updated as needed: no (behavior is documented on the allocator item in main.rs)

  • Breaking change (forces users to change their own code or config): no

  • Suggested release notes appear below: yes

  • chore: the allocator is now built in secure mode and aborts when it detects heap corruption (a smashed free list, a double free, corrupted metadata) rather than continuing on a corrupted heap. Set MIMALLOC_SHOW_ERRORS=1 to also print what was detected.

Test plan

  • New test case added: an end-to-end test drives a deliberate double free through the internal crash-test hook and asserts the allocator reports it (double free detected) and the process dies by SIGABRT with a crash report — the path that was previously silent.
  • Covered by existing test cases (full AXL suite: 910 passing; crash-handler suite: 6 passing).
  • Manual: the x86_64-unknown-linux-musl release target builds via --config=release; mi_register_error and all three secure detectors are linked into the binary, and it runs normally (aspect --version).

Note on cost

MI_SECURE=4 adds guard pages and free-list encoding, which is not free. If the perf hit is material we can land this as a diagnostic-only release and revert, the same way #1352 was scoped.

Moving off musl's mallocng lost a diagnostic property: mallocng validates
a check byte on every allocation, so heap corruption aborted the process
by itself. That is what produced the a_crash/enframe abort we used to
identify the intermittent lint segfault as heap corruption. A default
mimalloc build performs no equivalent check, so the same corruption would
now pass unnoticed and surface later as an unrelated failure.

Build mimalloc with its secure feature (MI_SECURE=4): guard pages around
metadata, encoded free lists, randomized placement, and double-free
detection. Verified the detectors are present in the musl release binary
("corrupted free list entry", "double free detected", "corrupted
meta-data in thread-free list").

Detection is silent unless mimalloc is told to report: show_errors and
abort_on_error are read from the environment at process start, so a run
that wants the diagnostic must be launched with MIMALLOC_SHOW_ERRORS=1
MIMALLOC_ABORT_ON_ERROR=1. Documented on the allocator item; setting them
from inside main does not work, as mimalloc reads its options before main
runs.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 676b4683ac

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread crates/aspect-cli/src/main.rs Outdated
@aspect-workflows

aspect-workflows Bot commented Jul 29, 2026

Copy link
Copy Markdown

✨ Aspect Workflows Tasks

📅 Wed Jul 29 02:24:53 UTC 2026

🔄 1 in progress task

  • 🔄 delivery-gha-debug [delivery] · ⏱ 10s · 🐙 GitHub Actions · ☑️ Check
    💬 Resolving targets to deliver...

❌ 1 failed task

  • ❌ delivery-uncacheable [delivery] · ⏱ 19.5s · 🐙 GitHub Actions
    💬 failed in deliver · Delivery failed (1 delivery fail)

⚠️ 2 flagged tasks

  • ⚠️ delivery-gha [delivery] · ⏱ 31.6s · 🐙 GitHub Actions · ☑️ Check
    💬 Delivery complete (1 delivered · 2 warn · 3 skipped)
  • ⚠️ delivery-uncacheable-warn [delivery] · ⏱ 13.4s · 🐙 GitHub Actions
    💬 Delivery complete (1 warn)

✅ 27 successful tasks

  • ✅ axl-smoke-gha-bootstrap [build] · ⏱ 19.3s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (1 built)
  • ✅ run-axl-smoke [run] · ⏱ 16.5s · 🐙 GitHub Actions · ☑️ Check
    💬 Ran //examples/deliverable:py_deliverable
  • ✅ run-axl-smoke-2 [run] · ⏱ 13.7s · 🐙 GitHub Actions · ☑️ Check
    💬 Ran //examples/deliverable:sh_deliverable
  • ✅ axl-tests-gha-bootstrap [build] · ⏱ 16.8s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (1 built)
  • ✅ build-gha-debug [build] · ⏱ 5m 16s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (166 built)
  • ✅ build-gha [build] · ⏱ 47s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (166 built)
  • ✅ build-gha-ephemeral [build] · ⏱ 33s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (9 built)
  • ✅ buildifier-gha-debug [buildifier] · ⏱ 1m 1s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ buildifier-gha [buildifier] · ⏱ 39.6s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-gha-debug [format] · ⏱ 1m 23s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-format-repeat-task [format] · ⏱ 1m 45s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-format-repeat-task-2 [format] · ⏱ 10.2s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-format-repeat-task-3 [format] · ⏱ 8.8s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-format-repeat-task-4 [format] · ⏱ 8.7s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-gha [format] · ⏱ 1m 32s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ gazelle-gha-debug [gazelle] · ⏱ 35.6s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ gazelle-from-source-gha-debug [gazelle] · ⏱ 2m 20s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ gazelle-from-source-gha [gazelle] · ⏱ 2m 14s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ gazelle-gha [gazelle] · ⏱ 26.1s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ init-shell [build] · ⏱ 33.7s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (10 built)
  • ✅ lint-gha-debug [lint] · ⏱ 32.2s · 🐙 GitHub Actions · ☑️ Check
    💬 Lint complete (clean)
  • ✅ lint-gha [lint] · ⏱ 1m 3s · 🐙 GitHub Actions · ☑️ Check
    💬 Lint complete (clean)
  • ✅ test-gha-debug [test] · ⏱ 22.2s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (26/26 passed · 26 cached)
  • ✅ test-gha-coverage [test] · ⏱ 29s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed · 1 cached)
  • ✅ test-gha-target-pattern-file [test] · ⏱ 27.1s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed · 1 cached)
  • ✅ test-gha [test] · ⏱ 2m 1s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (26/26 passed · 25 cached)
  • ✅ test-gha-ephemeral [test] · ⏱ 49.6s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)

🔁 Reproduce

❌ delivery (delivery-uncacheable · delivery-gha · delivery-uncacheable-warn)

# --mode=always --track-state=false for off-runner with no state backend.
aspect delivery \
  --commit-sha=93ceab9bc54679313acc8f09c485d9eb0351d753 \
  --mode=always \
  --track-state=false \
  --dry-run=true

Install aspect: aspect.build/docs/cli/install


⏱ Last updated Wed Jul 29 02:34:06 UTC 2026 · 📊 GitHub API quota 1,746/15,000 (12% used, resets in 23m)
🚀 Powered by Aspect CLI (v0.0.0-dev)  |  Aspect Build · X · LinkedIn · YouTube

mimalloc has no abort_on_error option — the earlier instruction to set
MIMALLOC_ABORT_ON_ERROR was wrong and would have been silently ignored.
mimalloc's default handler aborts only on EFAULT (corrupted metadata,
corrupted thread-free list, and under the secure build a detected buffer
overflow); a double free (EAGAIN) or a free of an invalid pointer
(EINVAL) is reported and then execution continues.

Register an error handler that aborts on every corruption code, so a
detection is fatal at the point it happens rather than letting a
corrupted heap run on and fail somewhere unrelated. The SIGABRT is caught
by the crash handler, which reports it with a resolvable address.

Adds a double-free trigger to the internal crash-test hook and an
end-to-end test asserting the allocator reports it and the process dies
by SIGABRT — the case that was previously silent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gregmagolan
gregmagolan merged commit cbca707 into main Jul 29, 2026
71 checks passed
@gregmagolan
gregmagolan deleted the mimalloc-secure-diagnostic branch July 29, 2026 02:39
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.

1 participant