Repository navigation
Give a namespaced action a /proc that matches its PID namespace - #3024
Merged
Merged
Conversation
Completes the no-per-PR-OCI-builds intent: image-off-pr moved image.yaml off PRs and TraceMachina#2877 moved the nix integration job off PRs, but main.yaml's integration-tests (docker-compose image build + run_integration_tests.sh) fell between them and still ran on every PR. Defer to nightly + merge-to-main, matching TraceMachina#2880's pattern. Fork-local until folded into the upstream OCI/nightly set. (cherry picked from commit 46e058d)
With use_namespaces an action is PID 1 of its own PID namespace, but /proc stayed the host's, so an action looking up a child it forked under /proc/<pid> read whichever host process had that number. The worker now always takes a private mount namespace as well, and the PID 1 child mounts a fresh proc over /proc before it execs, with the host /proc's locked flags mirrored so a user namespace accepts it. Where the host refuses the mount, as an unprivileged container that masks paths under /proc does, a startup probe warns and the action keeps the host's /proc as before. The probe shares its child-side code with namespaces_supported instead of repeating it. Fixes TraceMachina#3003 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…c tests Right after `sleep 30 &` the child can still be the shell, so reading its command line raced the exec and failed about once in a few hundred runs. The script now waits for the child to report `sleep 30`. The host leak check compared /proc/1/comm with "sh", which is wrong wherever the enclosing PID 1 is a shell; it now compares the test process's /proc mounts before and after. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Without use_mount_namespace the mount namespace only carries the fresh /proc, but entering it and making it private had become mandatory, so a syscall policy that denies mount failed the namespace probe and the worker refused to start where it ran before. Now a refused CLONE_NEWNS or a refused private remount there falls back to the namespaces the action had before the fresh /proc, which is then skipped; with use_mount_namespace both stay required for the root mask. A test installs a seccomp filter denying mount in the pre-exec hook to show the action still runs as PID 1, and that the masked variant still fails with EPERM. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
|
This branch was successfully deployed
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.
What and why
With
use_namespaces, an action runs as PID 1 of its own PID namespace, but/procstayed the host's, which lists processes by their host PIDs. An action that forked a child and looked it up under/proc/<pid>got whichever host process had that number, or nothing: the reporter's profiler read/proc/<its own PID>/exe, hit an unrelated host process, and died withPermissionErrorbefore it started its target.Now the worker always takes a mount namespace alongside the PID, user, IPC and UTS ones, makes it private so nothing in it reaches the host, and the PID 1 child mounts a fresh
procover/procright before it execs. The mount carriesnosuid,nodev,noexecplus whatever read-only and atime flags the host's/prochas, because inside a user namespace the kernel refuses a newprocthat is more permissive than the locked one it covers.use_mount_namespacekeeps its meaning (mask the root action directory, private/tmp); it is no longer the only way to get a mount namespace.Where the host refuses the mount, which an unprivileged container that masks paths under
/procdoes, a new startup probe (fresh_proc_supported) logs a warning and the action keeps the host's/proc, as it had before. Withoutuse_mount_namespacethe mount namespace itself is optional too: if a syscall policy refusesCLONE_NEWNSor themountthat makes it private, the action gets exactly the namespaces it got before this change, and the fresh/procis skipped. Withuse_mount_namespaceboth stay required, since the root mask needs them. The probe code is shared withnamespaces_supportednow, instead of a second copy of the unshare-and-map sequence. Persistent worker processes get the same/proc, since they are started through the same hook.The
use_namespacesdoc comment, the sandbox description on the security-hardening page and the persistent worker pool's comment say what happens now. The config reference renders only a field's first paragraph, so regenerating it produced no diff.Fixes #3003
How was this verified?
namespace_utils_test.rsrun the issue's shape inside the namespaces: a shell forkssleep, reads/proc/<child>/cmdline, and counts the processes/proclists. They assert PID 1 under/procis the shell, the child's command line issleep 30(the script waits up to five seconds for the forked child to exec, since right after the fork it can still be the shell), and fewer than ten processes are listed; the first also checks that the test process's own/procmounts in/proc/self/mountinfoare the same before and after, which catches a leaked mount without assuming anything about what the host's PID 1 is. The two tests pass 400 consecutive runs, and pass insideunshare --user --pid --fork --mount-proc sh -c ..., where PID 1 is a shell. One runs without the root mask, the other with the mask and a private/tmp. With themount_proccall temporarily replaced byOk(())both fail on "PID 1 under /proc is not the action" (the host's init shows through); with it, both pass.mountwith EPERM, then runs an action withoutuse_mount_namespaceand asserts it still runs as PID 1 of its namespace, and that the same filter withuse_mount_namespacefails the spawn with EPERM. On the previous revision the first half fails with EPERM, since the private-mount step had become mandatory.cargo test -p nativelink-worker --profile=smol: all ten test binaries pass, 231 tests, including the namespace, running-actions-manager and persistent-worker suites that go through the changed hook.bazel test //nativelink-worker/... //nativelink-config/...with the rustfmt and clippy aspects: 15 of 15 targets pass. The first run stopped on a nightly rustfmt difference in the test file, which pre-commit then fixed.pre-commit run --files <every touched file>: rustfmt, typos and vale pass (vale also lints the config doc comment).bun --filter @nativelink/docs lint:anchorsandlint:snippetspass;gen:config-reference mainreports no diff for the reason above.unshare --user --map-current-user --pid --fork --mount-proc truesucceeds), so the fallback path for a host that refuses it was exercised only through the probe's error decoding, not against a real refusal.running_actions_manager.rsinstalls, unchanged in how it is called.Risk
Every namespaced action now gets a mount namespace, which it did not have without
use_mount_namespace. Inside a user namespace an action could never mount into the host's mount namespace anyway, so the only observable change for an action is/procitself: a tool that read host processes through/proc/<hostpid>from inside a namespaced action no longer finds them. That is the behaviour the issue asks for, and it is what Bazel's own linux-sandbox does. A deployment whose environment refuses the mount keeps the old behaviour and gets one warning at startup; nothing fails that passed before. Not on a hot path: onestatvfsand onemountper action spawn.AI assistance
An agent (Claude Code) wrote the change and tests under b7r6's direction; b7r6 reviews every line before it goes upstream.
🤖 Generated with Claude Code