From 98466b8e66c62838f34b509f019d8d50bee1aca7 Mon Sep 17 00:00:00 2001 From: kewton Date: Tue, 13 Oct 2026 10:50:17 +0900 Subject: [PATCH 1/3] Add Linux Landlock write sandbox backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Cargo.lock | 34 ++- Cargo.toml | 3 + SECURITY.md | 14 +- docs/guide/README.md | 4 +- docs/guide/en/cli-reference.md | 12 +- docs/guide/ja/cli-reference.md | 7 +- src/bin/gui_server/delegate.rs | 44 ++- src/cli.rs | 7 +- src/tools/os_sandbox.rs | 216 ++++++++++++-- src/tools/os_sandbox/landlock.rs | 445 +++++++++++++++++++++++++++++ tests/issue502_os_sandbox_write.rs | 273 ++++++++++++++++-- 11 files changed, 996 insertions(+), 63 deletions(-) create mode 100644 src/tools/os_sandbox/landlock.rs diff --git a/Cargo.lock b/Cargo.lock index 66984c6b..303cabe6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,6 +285,7 @@ dependencies = [ "crossterm", "globset", "ignore", + "landlock", "libc", "regex", "reqwest", @@ -388,6 +389,26 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -810,7 +831,7 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -836,6 +857,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "landlock" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cca98e95f35b29d469dade6724c6f96cec9236640f745a0e99b0334ec320ab1" +dependencies = [ + "enumflags2", + "libc", + "thiserror", +] + [[package]] name = "libc" version = "0.2.186" diff --git a/Cargo.toml b/Cargo.toml index d4266b9d..f946b36b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,3 +60,6 @@ required-features = ["gui"] name = "gui_server" path = "tests/gui_server.rs" required-features = ["gui"] + +[target.'cfg(target_os = "linux")'.dependencies] +landlock = "0.4" diff --git a/SECURITY.md b/SECURITY.md index b113ede1..d3faa07c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -77,10 +77,15 @@ before the shell starts. an inode outside the workspace can still be written through. Seatbelt decides by path, so it allows writing the workspace path even though the inode lives outside; the sandbox only denies *creating* a new hardlink to an outside - inode. + inode. On Linux that creation needs Landlock ABI2 (kernel 5.19); on an older + kernel the link is not closed and is named in the `bash_sandbox_status` + reason. - Backend availability is probed once at run start. On macOS the backend is Seatbelt through `sandbox-exec`; under a nested sandbox it reports `rc=71` and - is unavailable. Other platforms have no backend in this stage. + is unavailable. On Linux the backend is Landlock; it requires ABI1 (kernel + 5.13) and is unavailable below that, when Landlock is disabled, or when a + seccomp filter denies the ruleset syscalls. Other platforms have no backend in + this stage. - `auto` continues with lexical checks only when the backend is unavailable, warns once at startup, and records `bash_sandbox_status` with `applied=false` and a reason. `required` instead fails Bash and verifier commands with @@ -88,8 +93,9 @@ before the shell starts. reports "sandboxed" when the sandbox was not applied. - The dependency caches (cargo registry/git/package-cache, npm, pip, Playwright browsers) stay writable so builds and tests work; they remain a contamination - path outside the workspace. pnpm and yarn stores are not allowed in this - stage. + path outside the workspace. On macOS they live under `~/Library/Caches` and + `~/.cargo`; on Linux under `~/.cargo`, `~/.npm`, and `~/.cache`. pnpm and yarn + stores are not allowed in this stage. macOS `mktemp` ignores `TMPDIR` and targets the per-user temp directory, which the sandbox denies. A `mktemp` inside the run temp directory (`mktemp -p diff --git a/docs/guide/README.md b/docs/guide/README.md index 3107b3a5..76940754 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -25,7 +25,7 @@ Recommended route / 推奨順路: Trial, Gate 1, and separate status, history, and result pages - [Extensions](en/extensions.md) — four layers, boundaries, registration, and the supply lifecycle -- [CLI reference](en/cli-reference.md) — all 66 public flags, defaults, and +- [CLI reference](en/cli-reference.md) — all 67 public flags, defaults, and conflicts - [Plan YAML editing](en/plan-yaml.md) — commented templates, offline validation, next commands, and recovery diffs @@ -53,7 +53,7 @@ walkthrough. Read the [security model](../../SECURITY.md) before enabling - [GUI 入門](../user/getting-started-gui.md) — 前提確認、サンプル Trial、Gate 1、 分離された実行状況・履歴・結果ページ - [拡張](ja/extensions.md) — 4 レイヤー、境界、登録導線、供給ライフサイクル -- [CLI リファレンス](ja/cli-reference.md) — 公開されている全 66 フラグ、既定値、排他関係 +- [CLI リファレンス](ja/cli-reference.md) — 公開されている全 67 フラグ、既定値、排他関係 - [Plan YAML の編集](ja/plan-yaml.md) — コメント付き template、offline 検証、次コマンド、 recovery 差分 - [スラッシュコマンド](ja/slash-commands.md) — 一般レジストリで受け付ける全 24 コマンド名と diff --git a/docs/guide/en/cli-reference.md b/docs/guide/en/cli-reference.md index 83d14b1a..ca1cb51f 100644 --- a/docs/guide/en/cli-reference.md +++ b/docs/guide/en/cli-reference.md @@ -23,7 +23,7 @@ before parsing finishes or in the resolved configuration after parsing; see [Conflicts and combinations](#conflicts-and-combinations). Clap also generates `-h`/`--help` and `-V`/`--version`. They are not part of the -66 application flags below. The hidden `--completion-contract-json ` is an +67 application flags below. The hidden `--completion-contract-json ` is an internal integration surface and is intentionally not a public user flag. ## Flag reference @@ -79,6 +79,7 @@ internal integration surface and is intentionally not a public user flag. | `--style` | `