Skip to content

#656 [security][tools] Bash と検証コマンドの書き込みを Linux の landlock で閉じ込める(1b) - #666

Merged
Kewton merged 3 commits into
developfrom
feature/issue-656-security-tools-bash-linux-landlock-1-b
Oct 13, 2026
Merged

Kewton merged 3 commits into
developfrom
feature/issue-656-security-tools-bash-linux-landlock-1-b

Conversation

@Kewton

@Kewton Kewton commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

概要

#502 の第 1 段 b です。1a(#655)で作った隔離の葉モジュールに、Linux の backend として landlock を足し、Bash と検証コマンドの書き込みを閉じ込めます。あわせて、GUI の delegate を --sandbox required で起動し、--sandbox を公開のフラグにします。

Refs #656

変更(develop 比 11 ファイル、3 commit)

  • landlock の backend(src/tools/os_sandbox/landlock.rs、新規の葉モジュール)
    • 依存は landlock 0.4(lock では 0.4.7)だけで、Linux だけの依存です。
    • ruleset の作成と、許可する場所の fd を開くのは親で行います。子の pre_exec では prctl(PR_SET_NO_NEW_PRIVS) と landlock_restrict_self だけを呼び、確保・lock・ログをしません(コメントに明示)。
    • ABI1 が必須で、ABI2(refer)と ABI3(truncate)は best-effort です。ABI2・ABI3 が無いときは、閉じない操作を bash_sandbox_status の既存の reason に書きます。
    • 利用可否は run の開始時に 1 回だけ確かめます(ABI の問い合わせと、子での restrict_self の試し)。seccomp が拒んだ場合も「未適用」と理由を返します。
    • 判定は、syscall を呼ばない純粋な関数 decide と limitation_for に切り出し、単体試験で確かめています(ABI 0・Err・1・2・3・9)。
  • 配線(src/tools/os_sandbox.rs)
    • backend の選択を足しました。
    • auto で wrap が実行時に失敗したら、Unavailable に降格し、is_applied() を false にします。required は拒否します。
  • GUI の delegate(src/bin/gui_server/delegate.rs):--sandbox required を固定の引数に足し、単体試験を付けました(設計 4)。
  • --sandbox を公開のフラグに(src/cli.rs、H-23 の推奨):docs/guide/README.md のフラグの数と、ja・en の cli-reference を直しました。SECURITY.md も更新しました。
  • 試験(tests/issue502_os_sandbox_write.rs):Linux の適用の試験を足しました。
    • 対象:間接書き込みの形、外の inode への hardlink(ABI2)、os.truncate(ABI3)、システムの一時ディレクトリ、検証の入口、隔離の中で要る道具、依存のない crate の cargo build(ignored)、install 直後の status。
    • kernel の版と ABI は試験の出力に書きます。CI の環境変数があるときは、landlock が未適用なら失敗します(黙って外れません)。
    • ABI は detected_abi() とは独立に問い合わせて照合し、ABI ごとの試験の判定にもその値を使います。
  • 変えていないもの:字句検査、event の名前と schema(新しい event・field なし)、macOS(1a)の挙動、成長上限のファイルと baseline、deny.toml、.github/workflows/ci.yml。

検証(HEAD f446ffd8、develop fe9cc7a3 の上)

  • 15 ゲート:commandmate verify で 15/15 passed。
  • cargo deny check:exit 0(除外の追加なし。advisories・bans・licenses・sources とも ok)。
  • Linux の確認:cargo-zigbuild clippy --all-targets --target x86_64-unknown-linux-gnu は exit 0。
  • macOS の適用の試験:--include-ignored で 18/18、SKIP 0(1a の挙動は変わっていません)。
  • gui feature:cargo clippy --features gui --bin gui_server -- -D warnings は exit 0、delegate の単体試験は 1/1。
  • Linux の実行の確認(Docker、mount なし。arm64、kernel 7.0.14-linuxkit、rust:1.94-bookworm、既定の seccomp)
    • landlock は既定の seccomp のまま ABI 8 で使えました。
    • 隔離の試験(CI=true、--include-ignored)は、CI の runner と同じく toolchain を $HOME の下に置いた配置で 16/16 でした。
    • lib の試験は、os_sandbox と landlock が 12/12、bounded_process が 7 pass・1 ignored です(プロセスグループ・timeout は変わっていません)。
    • 隔離の中(required、本物の probe)で、次を確かめました。
      • Next.js の fixture(next 14.2.35)の npm ci --prefer-offline(キャッシュを温めた後):成功
      • next build:成功
      • Playwright 1.49.1 の Chromium の起動とページ操作:成功
      • 外への touch:Permission denied
  • 二点測定:新しい試験のファイルを develop に置くと、compile error になります(landlock のモジュールが無い)。そのため、検出力は mutants で示しました。
  • cargo mutants(Linux のコンテナの中。--jobs 1、--timeout 300、RUSTFLAGS="-A unused-variables")
    • 1 回目は caught 13・missed 9 でした。missed の中に、許可が増える向きの detected_abi → 1 があり、試験を足しました。
    • 最終は caught 20・missed 2・unviable 3 です。
      • missed の 2 件は、backend_note と limitation_note を None に置き換える変異です。ABI3 以上の kernel ではもともと None なので、区別できません。記録の文だけの変異で、許可は増えません。
      • unviable の 3 件は、Default を持たない型への置き換えです。

レビュー

  • H-27(merge 前レビュー):merge 可、blocker 0。
    • pre_exec は 2 つの syscall だけで、失敗すれば exec の前に spawn エラーになります。
    • HardRequirement のため、ABI より弱い ruleset が「applied」になる経路はありません。
    • macOS の経路と字句の判定は変わっていません(2507 行で差 0)。
  • 後続の論点(blocker ではありません)
    • F1(優先度高、1a と 1b に共通):隔離の中から制御端末へ TIOCSTI で入力を注入できます(legacy_tiocsti=1 の kernel で、端末から起動したとき)。別の Issue で、process_group(0) の代わりに setsid() を使い、ABI5 以上では /dev/tty・/dev/pts の IoctlDev を許可しないことを推奨。GUI の delegate と CI には届きません。
    • F2:/dev/pts を directory ごと許可しています。F1 と同じ Issue で /dev/tty だけに絞ることを推奨。
    • F3:~/.npm などのキャッシュが外を指す symlink だと、その先が書き込みの許可になります。canonicalize して $HOME の下のときだけ許可することを推奨(優先度中)。
    • F4:wrap の失敗による降格は event に出ません(優先度低)。
    • F5:設計 4 の「GUI の文書に required で止まることを書く」が未対応です。後続でまとめます。

確かめてほしい点

  • RUSTUP_HOME・CARGO_HOME:既存の env の許可リストは、この 2 つを子に渡しません。そのため、toolchain を $HOME の外に置く環境(rust の公式イメージなど)では、隔離の有無にかかわらず Bash の cargo が使えません。
  • delegate の単体試験は CI で走りません:GUI Dashboard の job は --test gui_server だけを流し、--bin gui_server の単体試験は流しません。手元では 1/1 でした。
  • landlock が実際に使えない kernel での auto と required の挙動は、判定を差し替える単体試験だけで確かめています。
  • landlock の ABI 5 以上では IoctlDev も閉じます(厳しくなる向き)。npm ci・next build・Playwright には影響がありませんでした。

CI の runner の kernel と landlock の ABI(受け入れ条件の記録)

  • 3 本目の commit fb3703ce(試験だけ)で、記録の方法と外し方を直しました。
    • GITHUB_STEP_SUMMARY があるとき、kernel と ABI を job summary に 1 行書きます(書けなくても試験は失敗させません)。
    • CI があるときは、ABI2(hardlink)・ABI3(truncate)の試験は、ABI が足りなければ SKIP せずに失敗します(手元では SKIP のまま)。
  • この PR の CI(run 38348095501、job CommandAgent Test and Guardrails、ubuntu-24.04、image 20261004.327.1、x86_64)の summary:
    • issue502/656 landlock: kernel=6.17.0-1022-azure abi=7
  • ABI 7 は 2 と 3 を超えます。上の CI ゲートがあるので、hardlink と truncate の試験は外れずに走り、pass しました。
  • 手元の Docker(arm64、kernel 7.0.14-linuxkit、既定の seccomp)では ABI 8 で、CI=true と GITHUB_STEP_SUMMARY を付けて 17/17 でした(summary に 1 行)。

🤖 Generated with Claude Code

Kewton and others added 3 commits October 13, 2026 11:11
Confine model Bash and verifier command writes on Linux with the landlock
crate as stage 1b of the #502 write-confinement track. The ruleset and the
allowed-location descriptors are opened in the parent; the child's pre_exec
calls only prctl(PR_SET_NO_NEW_PRIVS) and landlock_restrict_self. ABI1 is
required, ABI2/ABI3 are best-effort, and availability is probed once at run
start via the ABI query and a child restrict_self.

Make --sandbox a public flag and start the GUI delegate with --sandbox
required. Add Linux integration tests that fail under CI when Landlock is
unavailable and print the kernel release and ABI.

判断: ABI2/ABI3 欠如時に閉じない操作を bash_sandbox_status の既存 reason field に
記録するため、os_sandbox.rs に backend ノートを配線した(backend 選択の配線の範囲内の
最小追加。event の名前と schema は変えない)。
判断: Linux の依存キャッシュ場所(~/.cargo 等)は landlock.rs 内で解決し、
os_sandbox.rs の resolve_cache_paths は変えない。
判断: Landlock の利用可否は ABI 照会に加え、bounded_process 経由の子で
landlock_restrict_self を 1 回試す(seccomp で拒否された場合も未適用として扱う)。
判断: ABI 照会結果→Available/Unavailable と理由、および limitation_note の ABI 分岐を
syscall を呼ばない純粋関数 decide/limitation_for に切り出し、cfg(test) 単体試験で
ABI0・Err・1・2・3・9 と理由に kernel と ABI が入ることを確かめる。
判断: auto で probe は Available でも landlock::wrap が実行時に失敗したとき、
installed の availability を Unavailable に降格して is_applied() を false にする
(reconcile_workspace と同じ考え方)。required は従来どおり拒否。降格は Linux 専用。
判断: Linux の hardlink 試験は jsonl の symlink 経路ではなく、テスト tempdir 内の
「外」の inode への絶対パス link で ABI2 の refer を測る(ABI2 未満は記録して外す)。
判断: GUI delegate の --sandbox required の試験は gui feature の bin 単体試験として
追加(本 Issue の許可ファイルに tests/gui_server.rs が無いため)。

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Close the cargo-mutants survivors that let a wrong ABI or a blank backend
report slip through the Linux tests:

- Query the kernel ABI independently with
  libc::syscall(SYS_landlock_create_ruleset, null, 0, VERSION) and pin
  landlock::detected_abi() to it, so a mutated detected_abi (for example a
  constant 1) fails; gate the hardlink test on the independent ABI too.
- Add a truncate(2) test: with ABI >= 3, os.truncate of an outside inode must
  fail and leave the outside fixture unchanged; below ABI3 record the skip.
- Add a run-start status test: on Linux, os_sandbox::install must report
  backend == "landlock" and applied == true, reason == None on ABI3+, and the
  unclosed operations named on an older ABI.

判断: 実装は変えない。試験だけを追加し、ABI は backend を経由せず独立に照会する。
判断: truncate 試験は jsonl ではなく os.truncate(truncate(2))で ABI3 を測り、
ABI3 未満は理由を出して外す。
判断: status の試験は install(実 backend)で probe を通し、ABI 3 以上は reason == None、
未満は reason に閉じない操作名を要求する。

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Make the kernel and ABI visible in the PR CI log and stop a too-old ABI from
silently skipping the ABI2/ABI3 closures:

- Add a pure abi_gate(ci, abi, required_abi, kernel) decision (continue / skip /
  fail) and apply it to the hardlink (ABI2) and truncate (ABI3) tests: under CI
  a too-old ABI fails with the kernel and ABI in the message; locally it skips.
  A unit test covers ABI1 with/without CI and ABI3.
- Append one line, issue502/656 landlock: kernel=<v> abi=<n>, to
  $GITHUB_STEP_SUMMARY once per process when that variable is set. Best effort:
  a write failure never fails the test, and only the kernel version and ABI
  number are written.

判断: 実装・CI・scripts は変えず、tests/issue502_os_sandbox_write.rs だけを直す。
判断: 判定(CI の有無・ABI・必要 ABI → 続ける/SKIP/失敗)は syscall を呼ばない
abi_gate に切り出し、env を書き換えずに単体試験する。
判断: step summary への記録は std::sync::Once で 1 回だけ、失敗しても試験は落とさない。

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant