Repository navigation
Conversation
A nested grant's symlink check ran before bwrap re-resolved the grant's path, so a program with write access to the parent could swap in a link between them. Where bwrap lists --bind-fd and the kernel has openat2, each grant is now opened once as an O_PATH descriptor, a nested one beneath its parent with RESOLVE_NO_SYMLINKS | RESOLVE_BENEATH, and bound with --bind-fd. Descriptors are mapped only in the child after fork (command-fds), never inheritable in the host. Elsewhere grants stay bound by path, as before, and BubblewrapBackend::binds_by_descriptor reports which is in effect.
The bubblewrap backend installed no seccomp filter, so keyctl, bpf, perf, io_uring, mount, setns, file handles and similar calls stayed reachable from a jail. A deny list compiled with seccompiler now goes to bwrap as two --add-seccomp-fd filters on pipes mapped into the child: EPERM for the deny list, ENOSYS for clone3 so the C library falls back to a clone whose flags the filter can check. ptrace stays allowed on Linux 4.8 and later; unshare and clone are denied only with a CLONE_NEW* flag. A bwrap without --add-seccomp-fd, or an architecture other than x86_64 and aarch64, leaves the backend unavailable rather than starting jails unfiltered.
A jail that allows network shared the host's network namespace, so host loopback services and abstract sockets were reachable from it and the jail stayed Process, which native_jail refuses when process isolation is required. Every registry-installed stdio MCP server was refused on Linux for that reason. When a root-owned pasta and nsenter are found and pass a probe, pasta now starts a holder that owns a fresh user and network namespace, and the launcher enters it with nsenter by namespace descriptor before it runs bwrap. pasta closes every inherited descriptor, so it cannot be bwrap's parent; the holder keeps the argument file, grant and seccomp descriptors intact. pasta runs only with no port forwarding, no gateway mapping and DNS through one forwarded address, and the jail gets its own resolv.conf. The holder lives exactly as long as bwrap through --sync-fd, so no pasta outlives a jail, even a killed one. Such a jail reports Kernel. Without pasta nothing changes: the host network is shared and the jail stays Process. slirp4netns is not used: it only attaches to an existing namespace, which Ubuntu's AppArmor user-namespace restriction refuses.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
Tiny Sweeper reviewFindingsNo active actionable findings. Advisory notes
Resolved this pass
Verdict: Incomplete · Priority: medium Diagnostics and supporting detailsFull summaryPull request purpose (title): Give allow_net bwrap jails a private network through pasta. When a root-owned `pasta` and `nsenter` are found at fixed trusted paths and pass a probe, an `allow_net` bubblewrap jail now runs in a private user and network namespace owned by a `pasta` holder; the launcher enters those namespaces with `nsenter --user --net --preserve-credentials` by namespace descriptor, and `bwrap` runs without `--unshare-net`. The jail gets its own `/etc/resolv.conf` via `--ro-bind-data`, and the holder's writer is handed to `bwrap` through `--sync-fd` so no `pasta` outlives a jail. `isolation_for` reports `Kernel` for such a jail while `isolation()` stays `Process`. Independently, every jail starts under a compiled seccomp deny list, grants are bound from descriptors where `bwrap` and the kernel support it, and a `bwrap` that cannot load the filters or an unsupported architecture leaves the backend unavailable. Where `pasta` is missing nothing changes, and a `pasta` start failure refuses the jail rather than falling back to the host network. The latest-push delta is unavailable. Unreviewed paths
Partial review reasons
Review snapshot
Completeness: Incomplete What changedOpen findings: the description lane reports a real defect — the launcher's fd keep-list arithmetic uses a count taken before the two namespace descriptors are appended, so those descriptors fall outside the keep list and are closed before `bwrap` runs (`--user=/proc/self/fd/N` is already consumed by `nsenter`, but the mismatch also silently changes the boundary). The tests lane flags a related concern: a bash-version-dependent `-ge` with a leading-zero operand in that same descriptor-closing loop, which only the existing leak test and the new grant-fd test would catch on a bash that rejects it. The description lane notes the earlier disputed python-probe quoting finding no longer applies to the current code; the commits lane reports no sensitive content in committed files with one earlier finding still open. The revision adds the pasta private-network path (holder, nsenter-by-descriptor entry, jail-local resolver, `--sync-fd` lifetime, `Kernel` isolation and suitability for `allow_net` jails where pasta works, refusal on pasta start failure), the compiled seccomp deny list on every jail with the backend unavailable when filters cannot load, descriptor-bound grants via `--bind-fd`/`--ro-bind-fd` with `openat2` (path binding where unavailable), a shell launcher with an fd keep-list, and a `bwrap-pasta-live` CI job that installs bubblewrap, passt and util-linux, lifts the AppArmor userns restriction, pins third-party actions by SHA, and runs the live suites with `TINYBOX_REQUIRE_PASTA=1`. Features
Tests
Before merge
Agent review detailscritique
security
tests
commits
description
Evidence and run details
|
ubuntu-latest is rolling to Ubuntu 24.04, where AppArmor denies unprivileged user namespaces to programs without a profile for them, which is every bwrap jail the job starts, so the backend found no working bwrap and every live test failed before reaching pasta. Lift it the way the native-resource job already does, and print the bwrap and pasta versions the run used.
On x86 with AVX2 pasta re-executes itself as pasta.avx2, so the live tests' lookup by process name found no pasta and both lifetime tests failed although the jail ran on its network. Match the name by prefix. A connect to the unmapped gateway is dropped rather than refused, so the reachability probe waited out the TCP timeout (132 s on CI); bound each connect to 5 s.
# Conflicts: # crates/tinybox-jail/src/bwrap/launcher_tests.rs
…p8-bwrap-pasta-net
…d/tinybox into feat/p8-bwrap-pasta-net
Stacked on #161 (which contains #160): it keeps their descriptor contract. Review it after those, or read only the last commit.
Summary
A bwrap jail that allows network shared the host's network namespace. So host loopback services and abstract unix sockets were reachable from it, and it stayed
Process, whichnative_jail::preparerefuses whenrequire_process_isolationis set. tinymcp and OpenHuman set exactly that for registry-installed stdio MCP servers, so every one of them was refused on Linux.When a root-owned
pasta(passt) andnsenterare found at fixed paths and pass a probe, anallow_netjail now gets a private network:pastastarts a holder (sh -c 'read _') that owns a fresh user, network and pid namespace.nsenter --user --net --preserve-credentials. It enters by namespace descriptor (/proc/self/fd/N), opened once after checking that the pid ispasta's child and that its network namespace is not the host's. Then it runsbwrap, without--unshare-net.pastais only ever started with--config-net --no-map-gw -T none -U none --dns-forward 169.254.1.1: no port forwarding either way, no gateway mapping to the host, DNS through one forwarded address. The jail gets its own/etc/resolv.confnaming that address, via--ro-bind-data. The host's resolv.conf (often a symlink into/run) is not bound.bwrap's--sync-fd. Whenbwrapexits, normally or killed, the holder sees end-of-file and exits, andpastawith it. Before the hand-over, dropping theNetworkkillspasta. So nopastaoutlives a jail.isolation_forreportsKernelfor such a jail. Without a workingpastanothing changes: the host network is shared, and the jail staysProcess.Why not
pasta -- bwrap, which the issue suggested:pastacloses every inherited descriptor above 2 at startup.bwrapwould then lose its argument file (--args 9), the--bind-fdgrant descriptors (#160) and the seccomp filters (#161). We confirmed this on ubuntu-latest ("Can't read --args data: Bad file descriptor").Why not
slirp4netns, or attachingpastato the namespacesbwrapcreates: both join an existing namespace. Ubuntu's AppArmor user-namespace restriction (apparmor_restrict_unprivileged_userns=1) refuses thatsetns, with "Operation not permitted" from both tools.slirp4netnshas no mode that creates the namespace itself, so it is left out.Related issue
Closes #109.
API or behavior changes
BubblewrapBackend::networks_privately().isolation_for/is_suitable_for_untrusted_jailnow returnKernel/ true for anallow_netjail whennetworks_privately().pasta: anallow_netjail no longer reaches host loopback services, the host through its gateway address, or host abstract sockets. Outbound connections and DNS work.pastaornsenteris missing.Validation
Commands actually run, on our Linux CI runner (no
pastainstalled there) at 4e06be4 (bubblewrap 0.9.0):cargo fmt --all -- --check: cleancargo clippy --all-targets --all-features -- -D warnings: 0 warnings (--keep-going, all targets)cargo build --all-targets --all-features: okcargo test --all-features: 1608 passed, 0 failed, 5 ignored;bwrap::pasta::tests9,bwrap::launcher::tests11,bwrap::tests17TINYBOX_REQUIRE_BWRAP=1:bwrap_isolation24/0,bwrap_fd_leak2/0,bwrap_pasta_live3/0 (all three skipped there: no pasta)That runner has no
pasta, so the live pasta tests skip there. The new CI jobbwrap-pasta-liveruns them on ubuntu-latest, withpasstinstalled andTINYBOX_REQUIRE_PASTA=1. The same shape was proven there first, by hand, with a probe:127.0.0.1refused; gateway address refused; host abstract socket refused;--args/--bind-fddescriptors intact;pastaleft afterbwrapexits or is SIGKILLed.Tests
pasta_tests,launcher_tests,mod_tests):pastaflags allowed, plus a forbidden-flag list;/procchildren,PPidand route-table parsing;pasta's child, or that shares this process's network;nsenterprefix by descriptor;--ro-bind-data/--sync-fdwiring, without--unshare-netand without the host resolv.conf;Kernelonly with a workingpasta.tests/bwrap_pasta_live.rs,live_*):pasta;pasta.bwrap_isolation.rs: the two tests that assumedallow_netshares the host network now expect isolation whennetworks_privately()is true, and the old behaviour otherwise.Pasta::startand the holder lookup against a realpasta. The live tests cover them, because they needpasta.Documentation
The bwrap module docs have a new "Private network" section and updated "What is claimed".
pasta.rsdocuments the design and why it is shaped this way.Checklist
#[allow(...)],#[ignore], or relaxed lints.envcontents in the diff or the description