Please tick this box to confirm you have reviewed the above.
What version of ripgrep are you using?
ripgrep 15.2.0 (rev e89fff8)
features:+pcre2
simd(compile):+SSE2,-SSSE3,-AVX2
simd(runtime):+SSE2,+SSSE3,+AVX2
PCRE2 10.45 is available (JIT is available)
How did you install ripgrep?
I originally encountered this bug in the rg
bundled with OpenAI Codex. That binary is byte-for-byte identical with the one in https://github.com/BurntSushi/ripgrep/releases/download/15.2.0/ripgrep-15.2.0-x86_64-unknown-linux-musl.tar.gz and I've reproduced the bug from that independently of any Codex dependency. For the analysis below, I built rg-15.2 with debug symbols included by way of CROSS_CONTAINER_ENGINE=podman CARGO_PROFILE_RELEASE_DEBUG=true ~/.cargo/bin/cross build --release --target x86_64-unknown-linux-musl
.
What operating system are you using ripgrep on?
OpenSUSE Tumbleweed Linux x86_64
Describe your bug.
Ripgrep built for x86_64-unknown-linux-musl occasionally crashes with a SIGSEGV when searching very-large trees at a high degree of concurrency. The crashing line is an integrity assertion regarding heap metadata inside MUSL's mallocng, in a calloc
call made from opendir
. The complete backtrace is below.
What are the steps to reproduce the behavior?
Having a sufficiently large search tree seems to be essential for reproduction. Run the attached generate_repro_tree.py. This is an LLM-written program which produces a tree full of random files which mimic the statistics of the repo in which I originally encountered the bug. It will produce a tree containing roughly 20GiB of data across 1.8M files.
Then from the root of that tree, run rg
in a loop, searching for some arbitrary literal string that isn't present in the tree: while true; do rg tnoheueunotshisnthukoethnsueothnsiuothonesuioseuinth; done
. On my 24-core system, having enough free RAM for the search tree to fit in the kernel's block cache, it typically takes about a minute for the SIGSEGV to appear.
What is the actual behavior?
I get a coredump with the following backtrace:
#0 get_meta () at ../src_musl/src/malloc/mallocng/meta.h:141
#1 __malloc_allzerop () at ../src_musl/src/malloc/mallocng/malloc.c:384
#2 0x00007f71f8381b2d in calloc () at ../src_musl/src/malloc/calloc.c:41
#3 0x00007f71f83810f4 in opendir () at ../src_musl/src/dirent/opendir.c:15
#4 0x00007f71f835c133 in std::sys::fs::unix::readdir::{closure#0} () at library/std/src/sys/fs/unix.rs:2081
#5 std::sys::helpers::small_c_string::run_with_cstr_stack<*mut libc::unix::DIR> () at library/std/src/sys/helpers/small_c_string.rs:48
#6 std::sys::helpers::small_c_string::run_with_cstr<*mut libc::unix::DIR> () at library/std/src/sys/helpers/small_c_string.rs:28
#7 std::sys::helpers::small_c_string::run_path_with_cstr<*mut libc::unix::DIR> () at library/std/src/sys/helpers/small_c_string.rs:18
#8 std::sys::fs::unix::readdir () at library/std/src/sys/fs/unix.rs:2081
#9 std::sys::fs::read_dir () at library/std/src/sys/fs/mod.rs:68
#10 0x00007f71f8206b5c in std::fs::read_dir<&std::path::Path> (path=...)
at /home/dfranke/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/fs.rs:3265
#11 ignore::walk::Work::read_dir (self=0x7f71f5bfeb20) at crates/ignore/src/walk.rs:1551
#12 ignore::walk::Worker::run_one (self=0x7f71f5bfef08, work=...) at crates/ignore/src/walk.rs:1749
#13 ignore::walk::Worker::run (self=...) at crates/ignore/src/walk.rs:1697
#14 0x00007f71f821c866 in ignore::walk::{impl#15}::visit::{closure#0}::{closure#1}::{closure#0} () at crates/ignore/src/walk.rs:1463
#15 std::sys::backtrace::__rust_begin_short_backtrace (f=...)
at /home/dfranke/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:166
#16 0x00007f71f8224596 in std::thread::lifecycle::spawn_unchecked::{closure#1}::{closure#0}
() at /home/dfranke/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/lifecycle.rs:70
#17 core::panic::unwind_safe::{impl#23}::call_once<(), std::thread::lifecycle::spawn_unchecked::{closure#1}::{closure_env#0}> (self=...)
at /home/dfranke/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:275
#18 std::panicking::catch_unwind::do_call>, ()> (data=)
at /home/dfranke/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581
#19 std::panicking::catch_unwind<(), core::panic::unwind_safe::AssertUnwindSafe>> (f=...)
at /home/dfranke/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544
#20 std::panic::catch_unwind>, ()> (f=...)
at /home/dfranke/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359
#21 std::thread::lifecycle::spawn_unchecked::{closure#1} ()
at /home/dfranke/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/lifecycle.rs:68
#22 core::ops::function::FnOnce::call_once, ()> () at /home/dfranke/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250
#23 0x00007f71f8361fcf in alloc::boxed::{impl#31}::call_once<(), (dyn core::ops::function::FnOnce<(), Output=()> + core::marker::Send), alloc::alloc::Global> ()
at library/alloc/src/boxed.rs:2275
#24 std::sys::thread::unix::{impl#2}::new::thread_start () at library/std/src/sys/thread/unix.rs:118
#25 0x00007f71f8388788 in start () at ../src_musl/src/thread/pthread_create.c:207
#26 0x00007f71f8389e6c in __clone () at ../src_musl/src/thread/x86_64/clone.s:22
Here is the core dump and the corresponding rg binary which produced it.
What is the expected behavior?
Not a segfault.
Please tick this box to confirm you have reviewed the above.
What version of ripgrep are you using?
ripgrep 15.2.0 (rev e89fff8)
features:+pcre2
simd(compile):+SSE2,-SSSE3,-AVX2
simd(runtime):+SSE2,+SSSE3,+AVX2
PCRE2 10.45 is available (JIT is available)
How did you install ripgrep?
I originally encountered this bug in the
rg
bundled with OpenAI Codex. That binary is byte-for-byte identical with the one in https://github.com/BurntSushi/ripgrep/releases/download/15.2.0/ripgrep-15.2.0-x86_64-unknown-linux-musl.tar.gz and I've reproduced the bug from that independently of any Codex dependency. For the analysis below, I built rg-15.2 with debug symbols included by way ofCROSS_CONTAINER_ENGINE=podman CARGO_PROFILE_RELEASE_DEBUG=true ~/.cargo/bin/cross build --release --target x86_64-unknown-linux-musl
.What operating system are you using ripgrep on?
OpenSUSE Tumbleweed Linux x86_64
Describe your bug.
Ripgrep built for x86_64-unknown-linux-musl occasionally crashes with a SIGSEGV when searching very-large trees at a high degree of concurrency. The crashing line is an integrity assertion regarding heap metadata inside MUSL's mallocng, in a
calloc
call made fromopendir
. The complete backtrace is below.What are the steps to reproduce the behavior?
Having a sufficiently large search tree seems to be essential for reproduction. Run the attached generate_repro_tree.py. This is an LLM-written program which produces a tree full of random files which mimic the statistics of the repo in which I originally encountered the bug. It will produce a tree containing roughly 20GiB of data across 1.8M files.
Then from the root of that tree, run
rg
in a loop, searching for some arbitrary literal string that isn't present in the tree:while true; do rg tnoheueunotshisnthukoethnsueothnsiuothonesuioseuinth; done
. On my 24-core system, having enough free RAM for the search tree to fit in the kernel's block cache, it typically takes about a minute for the SIGSEGV to appear.What is the actual behavior?
I get a coredump with the following backtrace:
Here is the core dump and the corresponding rg binary which produced it.
What is the expected behavior?
Not a segfault.
Facts Only
ripgrep version 15.2.0 (rev e89fff8).
Target architecture: x8664-unknown-linux-musl.
Operating system: OpenSUSE Tumbleweed Linux x8664.
Event: Occasional SIGSEGV crashes during searches of very large directory trees.
Error location: Integrity assertion regarding heap metadata inside MUSL's mallocng.
Triggering function: calloc called from opendir.
Hardware environment: 24-core system.
Reproduction method: A Python script (generatereprotree.py) creating ~20GiB of data across 1.8M files.
Reproduction step: Running rg in a loop searching for a non-existent literal string.
Observed time to crash: Approximately one minute.
Origin of initial encounter: Binary bundled with OpenAI Codex.
Executive Summary
Ripgrep 15.2.0, specifically the version compiled for the x8664-unknown-linux-musl target, exhibits intermittent segmentation faults when processing exceptionally large file systems. The crashes occur during high-concurrency searches and are linked to heap metadata integrity failures within the MUSL C library's mallocng implementation, specifically triggered during a calloc operation within opendir.
The issue is reproducible on a 24-core OpenSUSE Tumbleweed system using a generated directory tree containing 1.8 million files and 20GiB of data. While the bug was first identified in a version of ripgrep bundled with OpenAI Codex, it has been independently verified using the official MUSL release binary and a custom build with debug symbols. The precise nature of the crash suggests a low-level memory management conflict between the tool's high-concurrency directory walking and the MUSL allocator's metadata assertions.
Full Take
This is a technical bug report detailing a memory corruption or concurrency race condition in a low-level system library (MUSL) when stressed by a high-performance tool (ripgrep). Because this involves a specific compilation target and a niche allocator (mallocng), it represents a classic "edge-case" failure where the intersection of extreme scale (1.8M files) and high parallelism (24 cores) exposes a flaw that remains invisible in standard use.
The methodology provided is rigorous: the reporter provides a deterministic way to recreate the environment via a script and identifies the specific binary hash to ensure consistency. This is a high-signal report because it isolates the failure to the allocator's integrity check rather than a logic error in the ripgrep application code itself.
The root cause likely lies in the interaction between Rust's multi-threaded directory walking and the specific way MUSL handles heap metadata during rapid-fire allocations in a concurrent environment. This echoes a long history of "heisenbugs" in C libraries where performance optimizations in memory allocators introduce subtle race conditions that only emerge under extreme pressure.
The implications are primarily for users of MUSL-based Linux distributions (like Alpine) or those deploying statically linked binaries in containers. It highlights the risk of "invisible" instabilities in foundational infrastructure that only manifest at scale.
Bridge Questions: Does this crash persist if the mallocng allocator is disabled in favor of the legacy MUSL allocator? Would a reduction in concurrency (limiting the number of threads) eliminate the SIGSEGV?
Counterstrike Scan: A bad actor pushing this narrative would attempt to frame this as a fundamental insecurity in Rust or a widespread instability in MUSL to discourage their adoption. This content does not match that pattern; it is a disciplined, transparent engineering report aimed at a fix.
Sentinel — Human
This text exhibits strong indicators of human authorship, characterized by deep, specific technical detail and a narrative structure focused on reproducible problem-solving.
