Repository navigation
Resolve bwrap grant symlinks link by link, and fix the follow-ups - #167
Conversation
Four of the six follow-ups in tinyhumansai#104: - reject_symlinked_grants resolved a grant only below a prefix that names a writable root, so a host alias pointing into a directory inside the root (/aliases/v -> <root>/vendor, with vendor replaced by a planted link) was never checked. It now resolves each grant one component at a time, following every link, and refuses one that passes through a link living inside a writable root or grant. A link cycle is refused after 40 hops instead of being followed. - bwrap's base_dir compared an uncanonicalized $XDG_RUNTIME_DIR or TMPDIR against canonical grants, so a runtime directory reached through a symlink into a grant was accepted. Candidates are canonicalized before the check, and the canonical path is returned. - The module's planted-link test asserts INVALID_FILE_PATH instead of just is_err(). - The module and jail READMEs say that a grant through a link inside a writable root is refused, host-created links (pnpm, monorepos) included. The bus doc and the 1.20 spec no longer say a missing deny_read path is ignored: since tinyhumansai#107 bwrap refuses one inside a mount. Not in this change: fd-bound grants via openat2/--ro-bind-fd (item 1) and the known gaps in item 6 (seccomp, resource limits, late deny_read). Refs tinyhumansai#104
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
Tiny Sweeper reviewReviewed head: Verdict: Ready for maintainer review · Priority: none Diagnostics and supporting detailsFull summaryPull request purpose (title): Resolve bwrap grant symlinks link by link, and fix the follow-ups. Tiny Sweeper completed its review; deterministic results follow. Review snapshot
Completeness: Complete What changedWhole-pull-request behavior (the latest-push-delta section is unavailable: the review state has the prior reviewed head and rendered evidence but no trusted base-to-head diff, so no push delta is described here). `reject_symlinked_grants` in crates/tinybox-jail/src/jail.rs now canonicalizes the writable ancestors, then walks each grant component by component through a `pending` work queue, pushing symlink targets back onto the queue and clamping back-tracking `..` components lexically; a link encountered strictly inside a canonical writable path is refused with `InvalidInput` naming the link, a missing component ends the walk, and more than `MAX_LINK_HOPS` (40) links is refused as too many symlinks. This replaces the previous prefix-anchored `reject_symlink_below` helper and closes the host-alias-into-a-writable-subdirectory gap, while still allowing an alias into a real (non-link) directory of the root. In crates/tinybox-jail/src/bwrap/launcher.rs, `base_dir` now `filter_map`s candidates through `canonicalize`, so a runtime directory reached through a symlink into a grant is detected and the temp dir wins, and the canonical candidate is what is returned. In the module, crates/tinybox-module/src/tinybus_module/native_jail_tests.rs adds `assert_refused_as_an_invalid_path` and applies it to the planted-link cases so the failure kind, not merely failure, is checked. Documentation in crates/tinybox-jail/README.md, crates/tinybox-module/src/tinybus_module/README.md, crates/tinybox-bus/src/jail.rs and docs/specs/contract-1-20.md is updated to match, including the semantic shift for host-created links and the bwrap refusal of a missing denial inside a mount. Features
Tests
FindingsNo active actionable findings. Before mergeNone. How this fits togetherflowchart LR
n0["NativeJailConfig<br/>changed"]:::changed
n1["Jail<br/>changed"]:::changed
n2["..._planted_link_however_the_root_is_spelled<br/>changed"]:::changed
n3["...the_writable_root_cannot_redirect_a_grant<br/>changed"]:::changed
n4["...ded_per_jail_so_shared_network_is_refused<br/>changed"]:::changed
n5["tempdir"]:::impacted
n6["spawn_captured_with"]:::impacted
n7["prepare_with_resources"]:::impacted
n8["config"]:::impacted
n9["...cpu_limit_throttles_the_live_native_scope"]:::impacted
n10["...s_a_workload_that_touches_past_its_budget"]:::impacted
n2 -->|calls| n5
n2 -->|tests| n5
n3 -->|calls| n5
n3 -->|tests| n5
n3 -->|calls| n8
n3 -->|tests| n8
n4 -->|calls| n5
n4 -->|tests| n5
n4 -->|calls| n8
n4 -->|tests| n8
n6 -->|uses| n1
n7 -->|uses| n1
n8 -->|uses| n0
n9 -->|calls| n5
n9 -->|tests| n5
n9 -->|calls| n6
n9 -->|tests| n6
n9 -->|calls| n7
n9 -->|tests| n7
n9 -->|calls| n8
n9 -->|tests| n8
n10 -->|calls| n5
n10 -->|tests| n5
n10 -->|calls| n6
classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Agent review detailscritique
security
tests
commits
description
Evidence and run details
|
Summary
Four of the six follow-ups from the #99 security review (#104):
reject_symlinked_grantsonly checked a grant below a prefix that names a writable root. So a host alias pointing into a directory inside the root (/aliases/v -> <root>/vendor, withvendorreplaced by a planted link) was never checked. Each grant is now resolved one component at a time, following every link, and refused if a link on the way lives inside a writable root or grant. A link cycle is refused after 40 hops instead of being followed.base_dircanonicalization (item 2). bwrap compared an uncanonicalized$XDG_RUNTIME_DIRorTMPDIRagainst canonical grants, so a runtime directory reached through a symlink into a grant was accepted. Candidates are now canonicalized before the check, and the canonical path is returned.INVALID_FILE_PATH, not justis_err().deny_readpath is "ignored": since Refuse missing deny_read paths before bwrap spawn #107, bwrap refuses one inside a mount.Related issue
Part of #104. Item 1 (fd-bound grants via
openat2+--ro-bind-fd) is #160, and item 6's seccomp filter is #161. What is left of item 6 (resource limits on legacy bwrap jails, a latedeny_read) is tracked there.API or behavior changes
InvalidInput, orINVALID_FILE_PATHfrom the module). That includes a host alias into the root's subdirectories, which used to pass.Validation
On our Linux CI runner (bubblewrap 0.9.0):
cargo build: ok.cargo test -p tinybox-jail -p tinybox-module: 480 passed, 0 failed, with no failures unique to this branch.jail.rswith this branch's tests:a_host_alias_into_a_writable_root_cannot_reach_a_planted_linkfails;an_args_directory_reached_through_a_symlink_into_a_grant_is_refusedfails.cargo fmtand two test-helper lint fixes:cargo fmt --all -- --check: clean.cargo clippy --all-targets --all-features -- -D warnings: 0 errors, same as main.cargo build: ok.cargo test -p tinybox-module native_jail: 21 passed.Tests
jail.rsfails the nested-alias test; dropping the canonicalization fails thebase_dirtest.Documentation
crates/tinybox-module/src/tinybus_module/README.md,crates/tinybox-jail/README.md,tinybox-bus/src/jail.rsanddocs/specs/contract-1-20.md.Checklist
#[allow(...)],#[ignore], or relaxed lints.envcontents in the diff or the description