プログラミング
RipGrepのmuslバイナリが、非常に大規模な検索中に時折セグメンテーション違反を起こす
RipGrep musl binaries occasionally segfault during very-large searches (github.com)
要約
x86_64-unknown-linux-musl向けにコンパイルされたRipGrepのバイナリが、非常に大規模なディレクトリツリーを高い並行性で検索する際に、mallocngのヒープメタデータ整合性アサーションでセグメンテーション違反(SIGSEGV)を起こすという問題が報告されています。この問題は、MUSLのcalloc関数内のopendir呼び出し中に発生し、再現するには大規模なファイルツリーとループ検索が必要です。
全文翻訳
x86_64-unknown-linux-musl バイナリが、非常に大規模な検索中に時折セグメンテーション違反を起こす
説明
非常に大規模な検索ツリーを高い並行性で検索する際に、x86_64-unknown-linux-musl向けにビルドされたRipgrepが時折SIGSEGVでクラッシュします。
クラッシュする行は、opendirからのcalloc呼び出しにおける、MUSLのmallocng内のヒープメタデータに関する整合性アサーションです。以下に完全なバックトレースを示します。
再現手順
十分な大きさの検索ツリーを用意することが再現に不可欠です。
添付のgenerate_repro_tree.pyを実行してください。これは、LLMによって書かれたプログラムで、元々バグに遭遇したリポジトリの統計情報を模倣したランダムなファイルでいっぱいのツリーを生成します。約180万ファイル、約20ギビバイトのデータを含むツリーが生成されます。
その後、そのツリーのルートから、ループでrgを実行し、ツリー内に存在しない任意の文字列を検索します。
while true; do rg tnoheueunotshisnthukoethnsueothnsiuothonesuioseuinth; done
私の24コアシステムでは、検索ツリーがカーネルのブロックキャッシュに収まるだけの十分な空きRAMがある場合、SIGSEGVが出現するまで通常約1分かかります。
実際の挙動
以下のバックトレースを伴うコアダンプが得られます。
#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<ignore::walk::{impl#15}::visit::{closure#0}::{closure#1}::{closure_env#0}, ()> (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}<ignore::walk::{impl#15}::visit::{closure#0}::{closure#1}::{closure_env#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}<ignore::walk::{impl#15}::visit::{closure#0}::{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<core::panic::unwind_safe::AssertUnwindSafe<std::thread::lifecycle::spawn_unchecked::{closure#1}::{closure_env#0}<ignore::walk::{impl#15}::visit::{closure#0}::{closure#1}::{closure_env#0}, ()>>, ()> (data=<error reading variable: Cannot access memory at address 0x0>)
#19 std::panicking::catch_unwind<(), core::panic::unwind_safe::AssertUnwindSafe<std::thread::lifecycle::spawn_unchecked::{closure#1}::{closure_env#0}<ignore::walk::{impl#15}::visit::{closure#0}::{closure#1}::{closure_env#0}, ()>>> (f=...) at /home/dfranke/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581
#20 std::panic::catch_unwind<core::panic::unwind_safe::AssertUnwindSafe<std::thread::lifecycle::spawn_unchecked::{closure#1}::{closure_env#0}<ignore::walk::{impl#15}::visit::{closure#0}::{closure#1}::{closure_env#0}, ()>>, ()> (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}<ignore::walk::{impl#15}::visit::{closure#0}::{closure#1}::{closure_env#0}, ()> () 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<std::thread::lifecycle::spawn_unchecked::{closure_env#1}<ignore::walk::{impl#15}::visit::{closure#0}::{closure#1}::{closure_env#0}, ()>, ()> () 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
ここにコアダンプと、それによって生成された対応するrgバイナリがあります。
期待される挙動
セグメンテーション違反が発生しないこと。