Repository navigation
account: home comes from the account, read only from a pinned getent or snug's own passwd (closes #641) - #646
Merged
Merged
Conversation
…ing symlinks (closes #637) bwrap bound grants by PATH, so a concurrent sandbox with rw on a parent could rename a symlink onto a resolved grant path between resolve and bind: measured 21 of 200 runs reading an ungranted file. Every non-/proc KindBind now compiles to --ro-bind-fd/--bind-fd from an fd snug opens at launch with openat2(O_PATH|O_CLOEXEC, RESOLVE_NO_SYMLINKS); a component turned into a symlink refuses the run, and a later swap cannot move the fd. /proc binds stay path form (a fresh procfs makes the fd's superblock differ; nothing renames inside procfs). No openat2 or no --ro-bind-fd in bwrap refuses, no fallback. Stage netns fds move to 250-252 for the budget; the runtime dir is canonicalised; --dry-run lists the bind sources and JSON gains bind_sources. Identity keys: the walked destination is stored and read with hostread.RequiredNoLinks. authorableLinks now trusts a root-owned link only in a root-owned directory not writable by group or others, since rename(2) keeps the owner. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nto #632 (refs #637) The browser FIFO is a KindBind like any other, so it now compiles to --bind-fd; TestDryRunArgvCarriesTheBrowserFIFOBind finds it through bwrap.bind_sources. bwrap.claude-login.txt and json.browser-bridge.diff regenerated: only bind lines and fd numbers moved. A FIFO bound by an O_PATH fd carries data host-ward, checked with bwrap directly. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…r binds (refs #637) loginArgvOps rewrites --*-bind-fd N guest to the host bwrap.bind_sources names for N, cross-checked against the document's mounts, so the two-selection diff ignores shifting fd numbers but still catches a real extra change; loginDryDests counts fd binds as required mounts. Each check fails on a wrong bind_sources host or access (mutation-checked). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…or snug's own passwd (closes #641) snug took its home from $HOME while every other account fact came from getent, so two homes could disagree, and HOME=/etc gave the payload a writable /etc (ld.so.preload honoured by the loader). - Account source: getent only at /usr/bin/getent or /bin/getent, root- owned all the way down (policy.TrustedExecutable over authorableLinks and trustedParent), run with an empty environment; never $PATH. Inside a snug sandbox (single-line uid map) snug parses its own generated /etc/passwd, accepted only as a read-only tmpfs file on the root's device, owned by the user, checked on the descriptor it reads. A plain user namespace, or a nested run without nss, refuses. - Home: getent's pw_dir. A $HOME resolving elsewhere refuses and names the fix; unset HOME takes pw_dir. A home that is a system tree refuses. The lookup runs before profiles load; HostAccountErr is gone. - claudeFiles uses pol.Home; the dry-run basename fallbacks are gone. - Tests: bin/snug-fixture (-tags snugfixture) replaces only the home for integration fixtures; a guard proves the shipped binary lacks it. Raw-userns harnesses build a snug-shaped authored passwd. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…#641) Inside a user namespace / reads as the overflow uid with mode 0555, and 'not owned by root or can be written by group or other (mode 0555)' read as if the mode were the problem. The refusal now names the one condition that failed; TestTrustedExecutable asserts each wording. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t yet (refs #637) canonicalRuntimeBase resolves $XDG_RUNTIME_DIR with EvalSymlinks, which fails where the directory does not exist; CI pins /run/user/1000 and has none, so every dry run naming a socket (agent, FIFO, door, podman) refused. plannedSocket is a label and now falls back to the unresolved base on ENOENT; the real run's openRuntimeDir still refuses there. TestPlannedSocketWithAMissingRuntimeBase fails without the fix. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…kips the --ro-bind-fd probe (refs #637) #637 has each process run `bwrap --help` once to check --ro-bind-fd. Under @net that probe runs in the stage, and TestASignalWhileThePayloadIsParkedNeverReleasesIt/net could catch it, SIGSTOP the stage, and wait for a sandbox the stopped stage never forks (engine CI: 'PRECONDITION: the parked sandbox pid 1 never appeared'). isSandboxBwrap skips a bwrap whose argv carries --help; the nested-pidns kill test, which counts bwraps, uses it too. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # .claude/design/INDEX.md
…missing (refs #641) CI failed TestShadowedAuthoredPasswdIsRefused: the forged file sits on ext4 there, so the mount rule refused with 'it is on a ext4 mount, not tmpfs' where the test pinned 'not read-only' (tmpfs here). Both are the rule refusing; the test now accepts any of its reasons. The refusal itself said 'this run's profiles generate none; select ... nss = true', the fix for a MISSING file: a file that is present but not snug's now reads 'the file there now is not it (<reason>); snug will not read an account from it'. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #641. Stacked on #642 (#637), which provides
trustedParent. The base isfix/637-bind-by-fd; retarget to main after #642 merges.Why
snug took its home from
$HOMEwhile every other account fact came from getent, so the two could disagree.HOME=/etcran with exit 0 and let the payload write/etc/ld.so.preload, which the next dynamically linked program honoured (measured by sandbox-policy).Rule (maintainer: refuse mismatch, unset HOME takes pw_dir, pinned getent, test fixture build, nested reads snug's own passwd)
/usr/bin/getentor/bin/getent. It must be root-owned with no group/other write all the way down (policy.TrustedExecutableoverauthorableLinks+trustedParent) and is run with an empty environment.$PATHis never searched./etc/passwditself, on the descriptor it reads. It must be a regular file owned by the user, on a read-only tmpfs mount on the same device as the root tmpfs, and contain exactly one 7-field line with uid/gid matching.nssrefuses and namesnss = true.pw_dir.$HOMEthat resolves elsewhere refuses:$HOME "X" is not this account's home directory "Y" (getent passwd); snug takes the home from the account. Unset HOME, or set HOME=Y.HOMEtakespw_dir./ /bin /boot /dev /etc /lib /lib64 /proc /run /sbin /snug /sys /tmp /usr /var) refuses.HostAccountErris gone.claudeFilesusespol.Home, and the dry-run basename fallbacks are gone.--dry-runnotes gainsnug: account: <name>, home <home> (from <source>).Tests
bin/snug-fixture(-tags snugfixture,SNUG_FIXTURE_ACCOUNT_HOME) replaces only the home for integration fixtures.TestShippedBinaryHasNoAccountFixturechecks the untagged binary lacks it (the tagged build has it as a control), and only thefixture/gateMakefile recipes name the tag.(from snug's generated /etc/passwd).AccountHomecases, the system-tree table (15 entries hard-coded), theTrustedExecutabletable (18 cases, including 65534).ChooseSource,AuthoredMountRule(measured snug mountinfo, plus a later bind over/etc), strict parser, owner/symlink/FIFO, never$PATH, empty environment.pol.Home.--no-seccomp, payload binds over/etc; positive control: getent shows the forged home); nested real run under the default seccomp pinned tofork/exec /proc/self/exe: operation not permitted.TestQuietRunsAreQuiet: the NOTES block may contain exactly the account line.ChooseSourcealways authored,TrustedExecutableaccepting 65534, and the mount check made inert each fail named tests.Verification
make gate: pass.make verify: 5 pass, 3 skip.make integrationbefore the rebase onto updated sandbox: bind every grant source by descriptor, opened without following symlinks (closes #637) #642: 319 pass, 3 skip, 2 fail. The 2 fails were the login-bridge tests sandbox: bind every grant source by descriptor, opened without following symlinks (closes #637) #642 since ported. A targeted run after the rebase (login bridge, account, nested, DNS, outer pidns, quiet run, preflight): ok.PATH: ignored, account(from /usr/bin/getent);LD_PRELOAD=/nonexistent.so: ignored;HOME=/etc: 77;HOME: 77 naming the fix;HOME=/home/michal/: accepted (same directory);env -i: home/home/michal.Residuals
XDG_CONFIG_HOMEstill picks the trusted profile directory, andXDG_DATA_HOMEthe engine store (environment authority).config,profile showanddoctorstill read$HOMEunchecked.--no-seccomp, and under it a payload with its own tmpfs root can choose an inner home. That grants nothing beyond the outer view.🤖 Generated with Claude Code