That ripgrep Segfault Is Probably a Kernel Bug
A crash in ripgrep's static musl builds traces to a suspected memory-management race in Linux 7.0.
A segfault report landed in the ripgrep tracker in late July that every heavy CLI user should read, and not because ripgrep did anything wrong. Issue #3494 describes the official x86_64-unknown-linux-musl build of ripgrep 15.2.0 occasionally dying with SIGSEGV while searching a ~20 GiB tree of 1.8 million files. The crash site is an integrity assertion deep inside musl's allocator, in a calloc call made from opendir. Follow the investigation to the end and the evidence points somewhere much more uncomfortable: a race in the Linux kernel's memory-management fast path.
That conclusion is still contested — more on that below — but the shape of the bug matters either way. Ripgrep is written in Rust. Musl is one of the most carefully audited C libraries in existence. If the analysis holds up, both are innocent, and the crash is the kernel transiently serving a thread the zero page where its own freshly written data should be. Memory safety in your language and your libc buys you nothing against that.
A store that vanishes ten instructions later
The reporter, dfoxfranke, did the kind of legwork most bug reports never get: a script that generates a realistic 1.8M-file tree, a debug build, a core dump, and then a standalone analysis built on five successively refined instrumented builds of musl. The instrumentation caught something that should be impossible: a thread writes heap metadata to a freshly faulted anonymous page, an immediate re-read sees the value, and a re-read roughly ten instructions later sees zeros. A /proc/self/pagemap capture at the moment of the mismatch showed the address backed by PFN 0 — the kernel's zero page. On x86, a thread's own store cannot become invisible to its own later load. Something replaced the page underneath it.
The proposed mechanism is a race between two kernel paths that ripgrep's parallel directory walker exercises constantly. Path one: a thread faults on a new anonymous page and the per-VMA-lock fast path — the fine-grained fault handling that landed in Linux 6.4 to stop mmap_lock from strangling multithreaded processes — installs the PTE and returns. Path two: another thread's munmap (musl freeing memory after closedir) zaps PTEs and broadcasts a TLB shootdown. If the shootdown IPI lands in the window between the new PTE being published and the faulting thread's write actually retiring, the write can land somewhere the kernel then throws away.
The version matrix is suggestive: the crash reproduces on Linux 7.0.12 but not on 6.8, 6.18.35, or 6.19.10, and the analysis fingers a January 2026 rework of the munmap teardown path — the new PTE-table-reclaim-during-zap code in mm/memory.c — as the change that opened or widened the window. As of the analysis, nothing in mainline through 7.2-rc1 fixes it.
The skeptics have a point too
The Hacker News thread pushed back, and the pushback is worth taking seriously. Andy Lutomirski — an x86/mm kernel developer, posting as amluto — agreed the symptom is real ("a zero-page PTE was present when it shouldn't have been") but doubted the specific per-VMA-lock story, floating a paging-structure-cache flush bug or a transient zap-path exposure instead. He also flagged that the analysis was heavily LLM-assisted and showed the classic failure mode of LLM debugging: confident narrative where "we don't know yet" is the honest answer. Another commenter hammered a Ryzen 5800X on kernel 7.1.5 for ten minutes without a single crash; so far the reproduction lives on one Zen 5 Threadripper.
So the honest status is: the effect is real and well-evidenced, the root cause is plausibly-but-not-provably the kernel, and nobody from the mm subsystem has bisected it yet. That's not a dunk on the reporter — instrumenting musl's allocator five different ways is genuinely good work — but treat the mechanism as a strong hypothesis, not a finding.
Why musl builds are the canary
The interesting question for practitioners isn't "is ripgrep broken" — it isn't — but why this surfaces in the static musl binary specifically. Two reasons, and they generalize.
First, allocator behavior. Musl's mallocng returns memory to the kernel aggressively, so a workload that churns through 1.8 million opendir/closedir cycles across 24 threads generates a firehose of mmap/munmap traffic. Glibc's arena allocator hoards freed memory and rarely unmaps, so the same kernel race exists under glibc but the window almost never gets exercised. Second, ripgrep ships jemalloc as the Rust global allocator in its 64-bit musl builds precisely because mallocng is slow — but that only covers Rust-side allocations. Libc-internal calls like opendir's calloc still go through musl, straight into the hot zone.
That combination — static musl binary, massively parallel syscall-heavy workload, bleeding-edge kernel — describes an awful lot of CI infrastructure. Static musl builds are the default distribution format for half the Rust and Go tooling ecosystem because they run anywhere. The trade-off nobody prices in is that they route you through allocator paths the glibc monoculture never stresses.
What to actually do
If you run big parallel searches — code indexing, secret scanning, monorepo-wide grep in CI — on kernels 7.0 or newer, and you see one-in-a-thousand segfaults from static binaries, don't burn a week bisecting your own code. Check the kernel first. Concretely: pin CI runners to a 6.x LTS kernel until this is resolved, or prefer distro-packaged glibc builds of your tools on affected hosts, or reduce fault/unmap concurrency with rg --threads 1 as a diagnostic (the crash needs high parallelism to fire). And when you hit a heisencrash in a memory-safe binary, capture the kernel version in the report — it's now a first-class suspect.
The bigger lesson is about where the risk migrated. The kernel's mm subsystem has spent three years trading lock granularity for scalability — per-VMA locks in 6.4, and now PTE-table reclaim during zap in 7.0 — and each step multiplies the interleavings that only show up under workloads like this one. Rust took memory corruption out of the application layer; the remaining bugs live below everyone's abstractions, they're hardware-and-timing dependent, and a 24-core desktop running a week-old kernel is now the test rig that finds them. Expect more reports like #3494, and expect most of them to spend their first month blamed on the wrong layer.
Sources & further reading
- x86_64-unknown-linux-musl binaries occasionally segfault during very-large searches — github.com
- Analysis of one crazy segfault in ripgrep — github.com
- RipGrep musl binaries occasionally segfault during very-large searches — news.ycombinator.com
Lenn writes about cloud platforms, Kubernetes internals, and the infrastructure decisions that quietly make or break engineering organizations. Based in Berlin's vibrant tech scene, they have a talent for turning dense platform-engineering topics into prose that people actually finish reading.
Discussion 1
kernel memory race in the hot path explains the musl crash. if this holds up, it's a nasty one for static binaries.