Skip to content

[security][tools] Bash の書き込み検査が env・sudo・timeout などの前置きや { … } の中の書き込みを認識せず、ワークスペース外へ書き込める(#509 の調査で発見) #566

Description

@Kewton

背景

#509(bash_write_guard の変異テスト)の着手前調査(2026-09-30)で見つけた見逃しです。2026-10-01 の着手前調査(develop@87f7a77、#565・#567 の merge 後)で、原因と直し方を確かめました。判定の関数を呼んで結果を確かめただけで、ワークスペースの外への実際の書き込みは試していません。

問題(背景)

Bash の書き込み検査は、program を segment の先頭の語(代入を剥がした後)だけで決めます。そのため、前置きのコマンドや予約語の後ろの書き込みを認識しません。

入力(例) 今の判定
env tee /tmp/f、env FOO=1 cp a /tmp/f、sudo cp a /tmp/f、command cp a /tmp/f、nohup tee /tmp/f、timeout 5 cp a /tmp/f 許可
{ tee /tmp/f; }、! tee /tmp/f、if true; then tee /tmp/f; fi、while tee /tmp/f; do :; done 許可
env -C sub tee link/f(cwd を変える前置き。sub/link は外を指す symlink) 許可(/tmp 以外の外へも書ける)

影響(背景)

  • ワークスペースの外(主に /tmp。env -C では symlink の先のどこへでも)へ書き込めます。
  • 同じ抽出を使う bash:verify の自動許可と inspect の段の書き込み検査でも、env tee out.txt・timeout 5 tee out.txt が「書き込みなし」と判定され、自動許可されます。

決めた設計(2026-10-01)

  1. 前置きを剥がしてから program を決める。 新しい葉の module に前置きの表を置き、segment の語に不動点まで繰り返し当てる(深さの上限 16、超えたら拒否)。結果は「program が決まった」「何も実行しない」「剥がし方が決められない」の 3 種類。
  2. 前置きの表(値をとる option を区別する):env(-i・-・-0・-v・-u NAME・-P path・--・代入)、command(-p。-v・-V は実行しない)、builtin、exec(-c・-l・-a name)、nohup、sudo(flag と値をとる option。-l・-v・-k・-K・-V は実行しない。-e と sudoedit は operand が書き込み先)、doas、timeout・gtimeout(option と必須の時間)、nice、ionice、stdbuf、time(予約語と /usr/bin/time。-o file は書き込み先)、setsid、caffeinate、arch、!・{・}・if・then・elif・else・while・until・do・coproc・function NAME。
  3. 剥がし方が決められない形は拒否に倒す(正直な失敗)。ただし誤拒否を絞る:未知の option、値の有無が分からない option、env -S、cwd や shell を変える前置き(env -C・--chdir・sudo -s・sudo -i・sudo -D・sudo -R)、深さの上限を超えた形は、残りの語の basename を走査し、書き込みの program(tee・cp・mv・install・ln・mkdir・rm・touch・truncate・chmod・chown・cd)か前置きが 1 つでもあれば拒否する。無ければ今と同じく許可する。拒否は、今の cd - の拒否と同じ形で返す。
  4. 他の判定:書き込みありの判定(bash:verify の自動許可・inspect の段)は、剥がした後の書き込み先で判定する。剥がし方が決められない形は、走査に一致した場合だけ書き込みありにする。保護された path の検出は、剥がした後の書き込み先で行う。どれも厳しくなる向きで、緩む経路はない。timeout 600 cargo test・env FOO=1 cargo test などの検証コマンドの自動許可は変わらない。
  5. [security][tools] Bash の書き込み検査が同じコマンドの cd・pushd の後の cwd を追わず、symlink を通ってワークスペース外へ書き込める(#509 の調査で発見) #568 のための区別:前置きを「exec の前置き」(env・nohup・timeout・nice・sudo・doas・ionice・stdbuf・/usr/bin/time・setsid・caffeinate・arch)と「shell の前置き」(command・builtin・!・予約語・{・予約語の time)に分けて返す。exec の前置きの後の cd は外部の program で、shell の cwd を変えない。[security][tools] Bash の書き込み検査が env・sudo・timeout などの前置きや { … } の中の書き込みを認識せず、ワークスペース外へ書き込める(#509 の調査で発見) #566 では、どちらの cd も今と同じく "working directory" の対象として記録する(厳しい側)。字句解析(tokenizer)は変えない((・) は今と同じ区切り)。
  6. 範囲外:xargs、find -exec、sh -c、eval、su -c、watch、script -c は、文字列や stdin から command が決まる形なので [security][tools] Bash の字句検査では、プログラム経由の間接書き込みや検証コマンドの副作用を閉じ込められない #502 で扱う。表にない前置き(chrt・taskset・unshare・busybox など)は既知の限界として report に書く。
  7. イベントの名前・schema は変えない。

対象ファイル

  • src/tools/bash_write_guard.rs — write_targets の program の決め方を新しい module の呼び出しに置き換え、拒否の配線と書き込み先の表のテストを足す
  • src/tools/bash_write_guard/command_prefix.rs — 新規。前置きの表、剥がし方、決められない形の走査、単体テスト
  • tests/issue566_bash_command_prefix_write_targets.rs — 新規。path_confinement_rejection を呼ぶ結合テスト

受け入れ条件

  • 下の「拒否する形」がすべて拒否され、「許可のままの形」が許可のままである
  • 書き込みありの判定で、env tee out.txt・timeout 5 tee out.txt が true、timeout 600 cargo test・env FOO=1 cargo test が false になることをテストで固定した
  • 直す前の develop で新しいテストが失敗し、直した後で通ることを report に記録した(二点測定)
  • 新しい module だけで cargo mutants を実行し(下の「作業の場所」のコマンド、上限 30 分)、生き残った変異に理由を添えて report に書いた
  • 既存の結合テスト(下の「回帰で通す既存のテスト(参考)」)は変更せず、そのまま通る
  • 既存の検証・受入・証跡・リリースゲートを緩めていない(AGENTS.md の honest-failure 方針)
  • 成長上限(docs/dev/dev-guardrails.md)の baseline を引き上げていない

テストの設計(参考)

拒否する形(/tmp/f と、fixture の sub/link → 外):

  • 問題の表の形すべて
  • env -i・env -u HOME・env -u HOME FOO=1・env --・/usr/bin/env の tee /tmp/f、env -S "tee /tmp/f"
  • command -p tee /tmp/f、exec tee /tmp/f、exec -a x tee /tmp/f
  • sudo -u root・sudo -E・sudo -- の tee /tmp/f、sudo -s tee /tmp/f、sudo -e /tmp/f、doas -u root tee /tmp/f
  • timeout -s KILL 5・timeout -k 1 5・timeout --preserve-status 5s の tee /tmp/f
  • nice -n 5・nice -5・ionice -c 3・stdbuf -oL・time -p の tee /tmp/f、/usr/bin/time -o /tmp/f true
  • for x in a; do tee /tmp/f; done、true && { tee /tmp/f; }、f() { tee /tmp/f; }、coproc tee /tmp/f
  • 入れ子:env sudo timeout 5 tee /tmp/f など
  • 決められない形:sudo -X u tee /tmp/f(未知の option)

許可のままの形:

  • env FOO=1 tee out.txt、printf x | env tee out.txt、timeout 5 cp a.txt b.txt、env tee /dev/null
  • env FOO=1 cargo test、env RUST_LOG=debug cargo test、timeout 600 cargo test、timeout --foreground 600 cargo test、timeout --unknown 600 cargo test(決められないが走査に一致しない)
  • nice cargo build、nohup cargo build、time cargo test、{ cargo test; }、command -v cargo、command -v tee /tmp/f、sudo -l tee /tmp/f

保護された path:env FOO=1 tee <保護された path> が拒否される。

テストの関数名には、すべて command_prefix を含めます(変異テストの名前のフィルタで、単体テストと結合テストの両方が走るようにするため)。

回帰で通す既存のテスト(参考)

  • tests/issue567_bash_glob_write_targets.rs
  • tests/issue428_bash_path_tokens.rs
  • tests/bash_workspace_confinement.rs

作業の場所(参考)

  • ビルドとテストは CARGO_TARGET_DIR=/Volumes/SSD_NX/tmp/issue-566-20261001-01/target-566 と CARGO_INCREMENTAL=0 で行います。
  • 変異テストのコマンド(--lib は付けない。付けると結合テストが走らない):
    cargo mutants --file src/tools/bash_write_guard/command_prefix.rs --timeout-multiplier 2 --minimum-test-timeout 60 --jobs 3 -o /Volumes/SSD_NX/tmp/issue-566-20261001-01/mutants -- command_prefix
    ベースラインで単体テストと結合テストの両方が走ることを確かめます。TMPDIR も /Volumes/SSD_NX/tmp/issue-566-20261001-01/ の下に置きます。
  • 着手前調査の report:ローカルの workspace/tmp/1001/issue-566/results/H-01/report.md。probe:/Volumes/SSD_NX/tmp/issue-566-20261001-01/probe。

根拠(develop@87f7a77)

Activity

  1. added a commit that references this issue on Oct 2, 2026
  2. Kewton commented on Oct 2, 2026

    @Kewton
    OwnerAuthor

    PR #574(merge commit 1e193ae)で対応しました。Bash の書き込み検査で、env・sudo・timeout・nice などの前置きや { … }・!・予約語を剥がしてから program を決め、剥がし方が決められない形は残りの語を走査して書き込みがあれば拒否します。env -S / --split-string はどの綴りでも拒否します。merge 前レビューとその後の再確認で見つかった blocker 9 件はすべて直し、最後の再確認で blocker 0、15 ゲートは 15/15 pass です。前置きの区別(exec と shell)は #568 で入れます。ANSI-C 引用の見逃しは #575 で扱います。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions