一个搜索循环,跑了大概一分钟,ripgrep 就自己崩了。不是找不到文件,也不是内存耗尽,而是 SIGSEGV——段错误,程序被系统硬生生打断。触发条件也不复杂:一棵 20GiB、约 180万个文件的目录树,一台 24 核机器,内存足够把整棵树装进内核缓存,然后写一个死循环反复搜索一个根本不存在的字符串。

这不是某个用户环境出了问题,而是 ripgrep 15.2.0 官方发布的 x86_64-unknown-linux-musl 静态二进制的固有行为。更值得注意的是,报告者最先是在 OpenAI Codex 内置的 rg 里撞见这个崩溃的——而这只是故事的开头。

现场:一分钟内自爆的堆

报告者用带调试符号重新编译的版本(cross build --release --target x86_64-unknown-linux-musl,podman 容器,启用 debug 符号)抓到了完整的 coredump。栈回溯很干净:ignore crate 的多线程 worker 在遍历目录时调用 opendir,opendir 内部调用 calloc 申请内存,calloc 走到 musl 新分配器 mallocng 的 __malloc_allzerop,在 get_meta(meta.h:141)这一行,一条堆元数据完整性断言失败,进程直接中止。

崩溃链条:五步到SIGSEGV 并发目录 遍历 opendir 调用 calloc 申请内存 mallocng 堆元数据检查 断言失败 SIGSEGV 触发规模:20GiB / 约180万文件 / 24核 / 约1分钟内出现 版本:ripgrep 15.2.0(rev e89fff8),PCRE2 10.45,x86_64-unknown-linux-musl

mallocng:诚实的分配器,还是脆弱的分配器

musl 近年用 mallocng 取代旧的分配器实现,核心设计是一旦发现堆元数据不一致,就主动 abort,而不是像老式分配器那样悄悄产生未定义行为、留下一个更隐蔽的漏洞。这次崩溃更像是分配器在诚实地报警,而不是被人利用的攻击入口。

问题是,报警响了,但没人知道火在哪儿烧。检索到的技术分析给出了几种可能:mallocng 自身在高并发 opendir/calloc 路径上还没被充分测试过的竞争条件;也可能是 ripgrep 或其依赖通过 unsafe/FFI、越界写、双重释放,反手把 malloc 的元数据踩坏了。这两种解释指向完全不同的责任方,而截至目前,--threads 1 对比、ASan、Valgrind 这些能区分两者的诊断手段,都还没人跑出结果。

  • 风险.根因未定,意味着现在没人能保证换个版本、换个参数就一定不崩。

发现路径不简单:从AI工具到官方二进制

这次 bug 最先冒出来的地方,不是某个开发者本地测试,而是 OpenAI Codex 内置的 rg 二进制。报告者做了一件很扎实的事:核对字节,确认 Codex 里那份 rg 和 ripgrep 官方发布的 15.2.0 musl tar.gz 逐字节相同,然后完全脱离 Codex,独立复现出同一个崩溃。这排除了"这是 Codex 自己搞出来的毛病"这种可能——问题在 ripgrep/musl 这一层,Codex 只是恰好把它带到了聚光灯下。

一个bug,两条不知情的分发链 ripgrep官方发布 musl静态二进制(15.2.0) OpenAI Codex 原封不动打包同一份二进制 终端用户:不知道自己在用ripgrep,更不知道在用musl

静态二进制的省心账,现在要付利息

Rust 生态偏爱 musl 静态编译,图的是一份二进制走遍所有发行版,不用操心 glibc 版本对不上。这套逻辑本身没错,但它有个前提:libc 层面足够可靠。这次的 mallocng 断言失败说明,超大规模、高并发的目录遍历,是一段几乎没什么人日常会撞上的压力测试区间——用户越少,覆盖越薄,bug 潜伏得越久。

静态链接换来的可移植性,替不了底层分配器没被验证过的那段并发路径。

更该被记住的一点,是这个 bug 的传播方式。ripgrep 维护者 BurntSushi 目前还没就此给出结论,musl 上游是否已经知情、是否有过类似 issue,也没有下文。但不管最终判定是 musl 的锅还是 ignore crate 的锅,它已经通过 Codex 这样的 AI 编程助手,原样传给了一批完全不知道自己在跑 ripgrep 的用户。这条链路才是这次事件里最值得记一笔的地方——底层依赖出问题,不再只影响直接使用它的人,而是顺着二进制打包这条隐形通道,一路传到最外层、最不设防的终端。


接下来值得盯的,不是这次崩溃本身修没修好,而是三件事:musl 上游会不会认下这是分配器的并发缺陷;ripgrep 会不会调整下一版 musl 构建策略;Codex 这类工具会不会换一版 rg,或者干脆切回 glibc 构建。这些答案出来之前,180万个文件那道题,还没解完。