Repository navigation
[tests][mutants] 変異試験が防御を外したまま kill(-1) などを実行し、利用者の全プロセスを止める #673
Copy link
Copy link
Closed
Description
背景
#667 の merge 前の検証(L-46、2026-10-14 02:54〜)で、leader が macOS 上で cargo-mutants を流しました。02:56:57 に変異 src/bounded_process.rs: replace signalable_process_group -> Option<i32> with Some(1) の試験 binary が kill(-1, SIGTERM) を実行し、利用者のプロセスすべて(leader・worker・CommandMate・Docker・Finder など、launchd の記録で SIGTERM 102 件)が終了しました。mac は利用者の再起動で復旧しています。
- unified log:
exited due to SIGTERM | sent by commandagent-d4ecb0d8a0f17cf6[86533] - mutants の出力:
/Volumes/SSD_NX/tmp/wave1-20261007/mutants-667-l46/mutants.out/(Terminated: 15で中断)
製品の防御は正しく働いています。問題は、変異が防御を外したまま、本物の kill を実行できる環境で試験が走ったことです。
関連:#667
問題
- P1:signal の宛先を守る関数を変異させると、
kill(-1)(同じ利用者の全プロセス)やkill(0)(自分のグループ)が実際に実行されます。src/bounded_process.rs:405signalable_process_groupは宛先の値そのものを返し、呼び出し側はkill(-pgid, …)を送ります。Some(1)→kill(-1, …)(今回の原因)、Some(0)→kill(0, …)(自分のグループ)、Some(-1)→kill(+1, …)(init。利用者の権限では EPERM)。src/bin/gui_server/trial_process.rs:430group_is_signalableは、変異でtrueになると防御が外れます。
- P2:危険な値をわざと渡す試験は、防御が変異すると本物の危険な命令になります。
src/bin/gui_server/trial_process.rs:517send_group_signal(1, Signal::Kill):group_is_signalable→trueの変異でkill(-1, SIGKILL)が実行されます(今回の SIGTERM より被害が大きい)。src/bounded_process.rs:789terminate_process_group_by_pid(1):同じ形です([security][tools] OS の隔離の中から制御端末へ TIOCSTI で入力を注入でき、/dev/pts の他の端末へ書ける #667 の HEAD では:861)。
- P3:再帰削除(
remove_dir_all)9 箇所は、変異でパスが変わったときの被害をまだ確かめていません。src/bin/gui_server/session_paths.rs:300、src/model_probe.rs:234、src/planner/pack/supply.rs:714・:717、src/tools/dir_fd.rs:414・:523、src/tools/edit.rs:308、src/tools/os_sandbox.rs:612、src/tools/write.rs:129
- P4:危険な命令の呼び出しが新しく増えても、検知する仕組みがありません。
- P5(H-31):signal の宛先が広がらない変異でも、試験の外に影響が残ります。
- プロセスの回収漏れ:子の session を作る処理([security][tools] OS の隔離の中から制御端末へ TIOCSTI で入力を注入でき、/dev/pts の他の端末へ書ける #667 の
configure_child_session)を空にする変異では、子が PID = PGID のグループを作らず、終了処理のkill(-child.id())が空振りします。TUI の試験(src/tui/profile_transport_tests.rs:223)が起動する終わらない試験用サーバーが残ります。cargo-mutants の--timeoutは残ったプロセスの回収を保証しません。 - 実 HOME の継承:pager の試験(
tests/issue502_os_sandbox_write.rs:1727付近)のgit init・git commitは隔離の外で動き、利用者の HOME と Git の設定(hook・template・署名)を引き継ぎます。
- プロセスの回収漏れ:子の session を作る処理([security][tools] OS の隔離の中から制御端末へ TIOCSTI で入力を注入でき、/dev/pts の他の端末へ書ける #667 の
根拠(develop 72807847)
signal を送る箇所:
src/bounded_process.rs:422・:427・:482・:502(kill(-pgid, …)、防御:405)src/bin/gui_server/trial_process.rs:446・:469(防御:430)src/planner/runner/acceptance.rs:1327・:1368(pid > 1の確認なし。pid は子プロセスから得た値で、実害は確認していない)src/preflight.rs:175(kill(pid, SIGTERM)、lsof で得た正の pid)src/planner/runner/tests/support/process.rs:97(kill(-pgid, 0)、試験の補助)
cargo-mutants 27.1.0 の挙動(scratchpad の小さな crate で cargo mutants --list を出して確認。試験は流していない):
assert!(pgid > 1)の中の比較は変異されない(マクロの中は対象外)。- 戻り値が
io::Result<()>で、assert!とkillを同じ関数に置くと、作られる変異はOk(())に置き換える・-を消す・==を!=にする、の 3 つだけで、どれも危険な宛先にならない。 - 戻り値が値の関数(
-> i32など)では、関数全体を-1などに置き換える変異でassert!ごと飛ばされる。 - この確認は小さな crate の 3 本についてのもので、製品の全関数の安全の証明ではない。版・対象 OS・features が変わったら確かめ直す。
.cargo/mutants.tomlのexclude_reは、コマンドの--reでは解除されない(H-31。cargo-mutants のsrc/options.rs)。
対処の方向
- 除外リスト(すぐ効く応急処置):
.cargo/mutants.tomlのexclude_reにsignalable_process_groupとgroup_is_signalableを入れる。理由と障害の日付をコメントに書く。 - 変異されない最後の防御(signal):
killを呼ぶ関数そのもの(戻り値が()かio::Result<()>)で、syscall に渡す値を変換した後に検査する。既存のErr・bail!の経路は残し、assert!は変異されない最後の砦として足す。- 複数の
kill、正の pid に送るpreflight.rs:175、signal 0 の生存確認(acceptance.rs:1368、trial_process.rs:469、試験の補助)は別々に分類する。pgid > 1は、無関係な正の pid でないこと(所有)の確認にはならない。 - 各関数と、値を決める上流の関数の変異を
cargo mutants --listで全件確かめる。
- 変異されない最後の防御(再帰削除):P3 の 9 箇所を、本体の cleanup と試験の補助に分け、到達経路と変異で起こりうる最悪の結果で分類する。防御は
path.starts_with(temp_root)では足りない(..、祖先の symlink、root そのものの削除、root を作る上流の変異)。所有する run・staging の厳密な子であることを実パスと所有の証跡で確かめ、確かめた値をそのまま削除する。pack の staging など製品で必要な削除先は、一律に temp へ変えない。 - 隔離した場所での確認:
- 隔離環境の中だけで、確認用の設定(除外を外したもの)を明示して流す。危険な関数の変異が実際に列挙・実行されたことを確かめる(通常の設定のまま
--reを付けても 0 本になる)。GUI の試験にはguifeature が要る。 - 合格の条件は caught だけではない。0・1・負の値などを実際に syscall の直前の防御まで届け、そこで拒否され、危険な syscall が出ていない証拠を取る(今の危険値の試験は、上流の selector の assert が先に失敗し、最後の防御まで届かない変異がある)。
- 番兵のプロセスは同じ UID・同じ PID namespace の別のグループに置く。正当な対象のグループと孫は終了し、番兵は生き残ることを確かめる。
- Docker は host の PID namespace・
--privileged・host の HOME や端末・Docker socket を共有しない。 - 確かめた関数から、通常の設定の除外を外す。
- 隔離環境の中だけで、確認用の設定(除外を外したもの)を明示して流す。危険な関数の変異が実際に列挙・実行されたことを確かめる(通常の設定のまま
- 危険な命令の一覧と検知:
- 対象の種類:signal(
libc::kill・killpg、Rust・nix の wrapper、外部のkill)、削除(再帰削除と単一ファイルの削除)、上書き・truncate・rename、権限の変更、端末のioctl・termios・共有 fd の flags、外部の shell・interpreter(sh -c、Python の中の命令)。 - 範囲:
srcだけでなくtests・build.rs・実行スクリプト、および防御を外しうる上流の関数。 - 一覧(関数、防御の有無、除外の理由)をリポジトリに置き、grep の結果と食い違ったら失敗する試験を加える。直接の API 名の grep では alias・wrapper・
sh -cの中を落とすので、検出の限界を文書に書く。一覧の照合は検知であって、実行の隔離ではない。
- 対象の種類:signal(
- 開発ハーネスの規則:
docs/dev/parallel-dev-harness.mdに次を加える。- mutants の対象は brief に書いた関数だけにする。広げるときは PM に確認する。
- run ごとに新しい実行スクリプトと出力先を用意する。流す前に
cargo mutants --listを出し、PM が変異の名前と置換の中身を全件照合する(件数だけでは足りない)。 - signal・削除・子の session を変える変異は、最後の防御を置くまで隔離環境(Docker、macOS だけの試験は使い捨ての VM や専用アカウント)で流す。
- host で流す試験は、試験名の絞り込みを最小にし、秘密のない専用の HOME・Git 設定・既知の実行ファイルで動かす。
GITHUB_STEP_SUMMARYは未設定か新しい run の所有ファイルに限る。外側のGIT_*は bounded child の許可リストで落ちるので、実際の子の環境まで確かめる。 --jobs 1・heavy-lock・利用者の在席は、隔離や回収の代わりにならない。
受け入れ条件
-
.cargo/mutants.tomlがあり、cargo mutants --listにsignalable_process_groupとgroup_is_signalableの変異が出ない(4 の確認が済んで外した場合は、その証跡がある) - signal を送るすべての箇所を分類し、
killを呼ぶ関数そのものに、変換後の値を検査する変異されない防御がある。各関数と上流の関数の変異をcargo mutants --listで全件確かめた -
remove_dir_allの 9 箇所を分類し、必要な箇所に「所有する root の厳密な子」を実パスで確かめる防御を置いた(不要とした箇所は理由を書いた) - 隔離環境の中で、確認用の設定で危険な関数の変異が実際に実行され、危険な値が最後の防御で拒否され、危険な syscall が出ていない証跡がある。番兵(同じ UID・同じ PID namespace・別のグループ)が生き残り、正当な対象のグループと孫は終了した
- 子の session を変える変異で、試験が起動したプロセスが残らないことを隔離環境で確かめた(または回収の仕組みを入れた)
- host で流す試験が、利用者の HOME・Git の設定・hook に届かないことを、実際の子の環境で確かめた
- 危険な命令の一覧と grep の結果の食い違いで失敗する試験がある(二点測定:一覧に無い
killを足すと失敗する)。検出の限界を文書に書いた -
docs/dev/parallel-dev-harness.mdに mutants の規則を書いた - 製品の挙動(timeout・中断でのプロセスグループの終了、preflight の確認、既存の
Err・bail!)は変わっていない。既存の試験が通る - 検証・受入・証跡のゲートを緩めていない。成長上限の baseline を上げていない
対象ファイル
.cargo/mutants.toml(新規)src/bounded_process.rssrc/bin/gui_server/trial_process.rssrc/planner/runner/acceptance.rssrc/preflight.rs- P3 の分類で防御を置くファイル(
remove_dir_allの 9 箇所のうち必要なもの) docs/dev/dangerous-commands.md(新規。危険な命令の一覧:関数、防御の有無、除外の理由、検出の限界)tests/issue673_dangerous_command_inventory.rs(新規。一覧と grep の結果の食い違いで失敗する試験)docs/dev/parallel-dev-harness.md
参照
- PM の ledger(ローカル):
workspace/tmp/1007/wave1/ledger.md(2026-10-14 08:20 以降の行) - L-46 の mutants の記録(ローカル):
workspace/tmp/1007/wave1/results/L-46/mutants-macos.txt - H-31 の手順の安全性レビュー(ローカル):
workspace/tmp/1007/wave1/results/H-31/report.md(表の 1〜10 を本文の P5・対処の方向 2〜6 に反映)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
Merge pull request #678 from Kewton/feature/issue-673-tests-mutants-k…
Kewton commentedon Oct 14, 2026
PR #678 で develop に merge しました(merge commit
3d4144db6952a6c6546ee8a22e31fc823365e4b1)。merge 後の develop の CI・acceptance・Next.js domain oracles は success です。.cargo/mutants.tomlの除外、signal を送る関数のkillの直前の変異できないassert!、再帰削除を確かめた実パスだけにする形、危険な命令の一覧docs/dev/dangerous-commands.mdと grep との食い違いで落ちる試験、harness §3a(mutants の規則)。install_for_testの root が削除先から独立していない(試験だけの経路)tests/**/*.shの読み込みsession_paths.rs:314の NotFound の判定を確かめる試験(missed 3 本)