Skip to content

[security][tools] Bash の閉じ込めの検査が、引用していない CR を区切りとして扱い、/ を含まない相対の語を候補にしないため、外向き symlink を通して外を読み書きできる(#602・#603 のレビューで発見) #613

Closed

Description

@Kewton

背景

#602・#603 の merge 前レビュー(H-06、2026-10-08)で見つけた、Bash の閉じ込めの検査の既存の見逃し 2 種類です。#602・#603 の前の develop にもあり、この 2 本では直していません。
着手前調査(H-09、2026-10-08)で、#602・#603 の merge 後の develop(e7c17f6c)を基準に、判定の関数を tempdir の fixture に対して呼んで確かめ、設計を 1 つに絞りました。shell は起動しておらず、ワークスペースの外の実際の読み書きは試していません。

問題

値は R A W P S V の順(R:2 段目の閉じ込めの判定、A:書き込みと認識、W:書き込み側の拒否、P:保護 path、S:秘密情報、V:bash:verify の自動許可)。⏎ は CR(\r)。

1. 引用していない CR

bash は CR で語を分けません(語を分けるのは空白・タブ・改行と演算子だけ)。読み取り側と書き込み側の字句解析は、CR を改行と同じ区切りとして扱うので、外向き symlink の名前に CR が入っていると、検査は別の語を確かめ、外を読み書きできます。

fixture:ワークスペースの中の sub/cr⏎link と cr⏎dir は外のディレクトリを指す symlink、lfcr⏎(名前の最後が CR)は外のファイルを指す symlink。

入力(例) 基準 e7c17f6c 期待
echo x > sub/cr⏎link/f 0 1 0 0 0 0 R=1・W=1
cat sub/cr⏎link/secret 0 0 0 0 0 1 R=1
cd cr⏎dir && echo x > f 0 1 0 0 0 0 R=1・W=1
tee sub/cr⏎link/f、cp a.txt sub/cr⏎link/f 0 1 0 0 0 0 R=1・W=1
cd cr⏎dir; touch f、pushd cr⏎dir && touch f 0 1 0 0 0 0 R=1・W=1
cd cr⏎dir && cat secret 0 0 0 0 0 0 R=1
cat sub/cr⏎link/secret⏎+改行(CRLF の行末) 0 0 0 0 0 1 R=1
cat lfcr⏎+改行(CRLF の行末) 0 0 0 0 0 1 R=1(設計 2 も要る)

2. / を含まない相対の語

静的な path の判定が / を必須にしているので、/ を含まない相対の語は候補になりません。その語自体が外を指す symlink だと、外を読めます。cd があるコマンドでだけ、別の経路(作業ディレクトリの候補の検査)が / のない語を確かめています(cd sub && cat lf は今も R=1)。

fixture:ワークスペースの中の lf・space lf・wide lf(全角空白)・linked-outside は外を指す symlink、dangling は行き先のない symlink。

入力(例) 基準 e7c17f6c 期待
cat lf、head -n1 lf、grep x lf、diff a.txt lf 0 0 0 0 0 1 R=1
cat "space lf"、cat space\ lf、cat wide lf 0 0 0 0 0 1(エスケープは 0 0 0 0 0 0) R=1
dd if=lf、grep --file=lf a.txt、make IN=lf 0 0 0 0 0 1 R=1
cat < lf、cat lf | head、(cat lf)、true && cat lf 0 0 0 0 0 1 または 0 0 0 0 0 0 R=1
cp lf a2.txt 0 1 0 0 0 0 R=1
source lf、. lf、python3 lf 0 0 0 0 0 1 R=1
ls linked-outside、cat dangling 0 0 0 0 0 1 R=1

書き込み側(echo x > lf、tee lf、cp a.txt lf)は今も W=1 で、この Issue では変えません。

影響

ワークスペースの中の symlink を通して、外のファイルを読めます。1 は外に書き込むこともできます。Gate 1 の閉じ込めの約束が、字句の形によって守られません。

設計(H-09 で決めたもの)

設計 1:CR を語の文字とする読み方を足し、今の読み方と両方で判定する

3 案を比べました。

案 内容 判断
A:CR を語の文字にするだけ bash と同じ読み方に置き換える 取らない。true⏎rm 保護 path の P と A が 1→0、V が 0→1 になり、true⏎tee 外の絶対パス の A・W が 1→0、V が 0→1 になる(bash では true⏎rm というコマンドが見つからず何も起きないが、印が緩む向きに変わる)
B:両方の読み方で判定する CR を語の文字とする読み方(bash と同じ)を足し、今の読み方(CR を区切りとする)も残して、どちらかで拒否・認識したら拒否・認識とする 推奨
C:引用していない CR を一律に拒否する CR を含むコマンドを、閉じ込めを証明できないとして拒否する 取らない。最も単純だが、CRLF の行末のコマンドをすべて拒否する方針の変更になる(記録では 0 件)

設計 2:/ を含まない語は、symlink のときだけ確かめる(案 A)

2 案を比べました。

比べる点 案 A:symlink のときだけ確かめる(推奨) 案 B:存在すればすべて確かめる
見逃し 表 2 をすべて拒否 案 A と同じ(普通のファイル・ディレクトリは、親が中なら必ず中に解決するので差がない)
誤拒否:外向き symlink と同じ名前のコマンド名・文字列 拒否する(node x.js、echo node。その名前の外向き symlink があるときだけ) 案 A と同じ
誤拒否:存在しない語 なし 引用した $ を含む語を、動的な path として拒否する(awk -F= '{print $1}' a.txt が 0→1)。ほかにも、今の確かめ方の字面の検査に当たる語を拒否しうる
ファイルシステムを見る回数 語 × 作業ディレクトリの候補ごとに lstat 1 回。symlink のときだけ解決する 存在する語ごとに解決(realpath)、存在しない語は親をたどる
速さ(試作、1030 件、debug) 0.14 秒 → 0.20 秒(設計 1 の 2 つの読み方を含む) 0.25 秒
TOCTOU 確かめた後に名前が symlink に変わる形は見逃す(今の / を含む候補と同じ。静的な検査の限界) 案 A と同じ
作業ディレクトリの候補 ワークスペースの直下と、決まった各 cd の行き先で確かめる。行き先を決められないときは、今の相対の候補と同じく拒否する 同じ
= の右側(#603 の設計 3) 右側に / がないときも同じ形で確かめる(dd if=lf)。/ があるときは #603 の扱いのまま 同じ
  • 対象の語:展開($・バッククォート)も glob もない静的な語で、引用を外した文字列に / がないもの。空白・全角空白・CR を含んでよい。除くもの:空の語、.、..、~ で始まる語、segment の先頭の代入(NAME=値)。コマンド名の位置とオプションは除かない(cd がある時の今の検査と同じ。symlink でなければ判定に影響しない)。
  • = の右側:先頭の代入でない語の最初の = の右側が、空でなく / を含まないなら、同じ対象として確かめる。
  • 確かめ方:ワークスペースの直下と各作業ディレクトリの候補に名前をつないだ path を lstat し、symlink なら(または、存在しない・ディレクトリでない以外の理由で lstat に失敗したら)、今の literal の確かめ方(解決して中にあるか。行き先がなければ拒否)で確かめる。symlink でない・存在しないなら今どおり許可。
  • 書き込み側は変えない(/ を含まない書き込み先は今も確かめている)。

試作での確認(H-09)

設計 1(案 B)と設計 2(案 A)を試作し、1030 件(新しい形 138 件、H-03・H-05・H-06 の形、記録の 436 件)を流しました。値が変わったのは 76 件で、すべて厳しい向き(R か W が 0→1)でした。緩む向きの変化は 0 件です。

誤拒否の見積り

  • 対象:dev-reports と workspace/management の jsonl(495 ファイル)に残る agent の Bash コマンド 436 件(重複なし)。
  • CR を含むもの:0 件(jsonl に CR も \r の escape も 0 件)。設計 1 の誤拒否は 0 件。
  • / を含まない語を引数に持つもの:335 件(138 種類。多いのは -la・-f・2・-p・f・src、npm の package 定義のファイル名など)。すべて lstat の対象になるが、拒否されるのは、その名前がワークスペースの中の外向き symlink のときだけ。
  • 記録にはファイルシステムの状態がないので、記録に残る 22 個の作業用ワークスペースの symlink を数えた:320 個で、すべて npm の実行ファイルのディレクトリ(node_modules の下の bin)の中で、/ を含む path、外向きは 0 個、ワークスペースの直下には 0 個。よって推奨案の誤拒否は 0 件と見積もった。
  • fixture の直下に外向きの node symlink を置いた試作では、記録の 9 件(node -p …、node smoke-check.js など)が拒否された。これが、外向き symlink と同じ名前のコマンドを使うときの誤拒否の形で、閉じ込めの約束として受け入れる。

許可のまま(誤って拒否しない形)

入力(例) 基準の値
cat README.md、cargo test、ls src、ls -la、git status 0 0 0 0 0 1
echo x > out.txt、touch newfile、cp a.txt c.txt 0 1 0 0 0 0
cd sub && cat f、cd sub && ls 0 0 0 0 0 0
make CC=gcc、cargo build --features=gui、dd if=input.txt of=out.bin 0 0 0 0 0 1
cat inlink(中を指す symlink)、echo node(外向きの node がないとき) 0 0 0 0 0 1
awk -F= '{print $1}' a.txt、FOO=lf cargo test 0 0 0 0 0 1
CRLF の行末:cat a.txt⏎+改行、cargo test⏎+改行、git status⏎+改行git diff⏎+改行 0 0 0 0 0 1
CRLF の行末:cd sub⏎+改行cat f⏎+改行、echo x > out.txt⏎+改行 0 0 0 0 0 0、0 1 0 0 0 0

受け入れ条件

対象ファイル

  • src/tools/bash/path_tokens.rs
  • src/tools/bash/read_guard.rs
  • src/tools/bash.rs
  • src/tools/bash_write_guard.rs
  • src/tools/bash_write_guard/working_directory.rs
  • tests/issue582_bash_read_candidates.rs
  • tests/issue567_bash_glob_write_targets.rs
  • tests/corpus/apps/issue582-bash-read-candidates/fixtures/decision-cases.jsonl

参照

根拠(基準コミット e7c17f6c)

  • 読み取り側で CR を区切りにする:src/tools/bash/path_tokens.rs:156
  • 書き込み側で CR を区切りにする:src/tools/bash_write_guard.rs:729
  • 静的な path に / を必須にする:src/tools/bash/path_tokens.rs:359、使う側:src/tools/bash/read_guard.rs:122-153(push_static)、:158-165(equals_right_hand_side)
  • 候補を確かめる所:src/tools/bash.rs:796-835
  • cd があるときだけ / のない語を確かめる:src/tools/bash_write_guard/working_directory.rs:141-147・:244-253
  • 書き込み先の集め方:src/tools/bash_write_guard.rs:223-227・:229-260・:309-361
  • 引用した $ を含む語を動的として拒否する確かめ方(案 B の誤拒否の原因):src/tools/path_guard.rs:230-238
  • 自動許可が has_recognized_mutation に頼る:src/tools/allow_policy.rs:122-142
  • 調査の記録:ローカルの workspace/tmp/1007/wave1/results/H-09/report.md(proto.diff、h09_probe.rs、cases.py)

関連(参考)

Activity

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

    @Kewton
    OwnerAuthor

    PR #636 で develop に merge しました(merge commit 2a078de、CI・acceptance・Next.js domain oracles success)。引用していない CR を語の文字とする読み方を足して両方で判定し、/ を含まない相対の語は symlink のときだけ確かめるようにしました。merge 前レビュー(H-10)は blocker 0。レビューで見つかった既存の穴は #635・#628 で扱います。

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions