Repository navigation
Keep a contained process's signals inside the Linux sandbox, and let corepack fetch Yarn 2 and later - #185
Merged
Conversation
The contained process shares nvx's process group so that Ctrl-C at a terminal reaches everything it starts. A process group reaches across the sandbox's PID namespace, so the contained process could signal nvx and every process beside it. Measured on Linux 6.18 in a privileged container, an npm preinstall that ran kill(0, SIGKILL) killed nvx, the shell that started it and a sleep that shell had started. From Landlock ABI v6, Linux 6.12, the ruleset now scopes signals to the sandbox. The same preinstall ends only its own install, and the sleep and the shell run on. A signal sent into the sandbox is not restricted, so the terminal's Ctrl-C and Ctrl-Z, nvx's forwarding and npm stopping its own scripts work as before. In a pseudo-terminal with the scope in place, nvx npx -y http-server and nvx --strict npm run stopped 0.07 to 0.32 seconds after Ctrl-C, a contained node REPL answered, and Ctrl-Z and fg stopped and resumed the run. Below ABI v6 nothing can scope signals. There the contained process gets a process group of its own again, and the supervisor sends each signal it passes on to that whole group, the terminal's SIGINT included. Built to take that path on the same kernel, the two commands stopped 0.06 and 0.16 seconds after Ctrl-C. On those kernels a contained process that reads the terminal is stopped, and Ctrl-Z stops nvx while the contained process runs on. The ruleset also scopes abstract UNIX sockets. They have no path for the sandbox's filesystem view to hide, and network.mode open shares the host's network namespace. In open mode a contained process connected to an abstract socket a host process listened on, and to the one Xvfb listens on. Both now get EPERM. In proxy mode both got ECONNREFUSED before and after, because the sandbox has a network namespace of its own there. The supervisor reads the ABI through supervisorLandlockABI, so a test can run the older path on a newer kernel. The new privileged tests cover a contained kill(0, SIGKILL) beside a sentinel in nvx's group, an abstract socket in open mode, Ctrl-C through a pseudo-terminal under real npm run and npx on both paths, and npm passing SIGTERM to its script. The first two fail on the previous code. The Ctrl-C tests fail when the target is signalled alone, and the npm test fails when the contained process may not call kill(2).
corepack downloads Yarn 2 and later from repo.yarnpkg.com, and looks up Yarn's versions there when it is given a range such as yarn@4 or yarn@1. The default allowlist did not name the host. With corepack 0.36.0 in a Linux container, a contained corepack yarn@4 install and corepack yarn@1 install were both refused a connection to it and exited 77. default_allow now names repo.yarnpkg.com:443. Both commands install, and the audit log records the host under default_allow. corepack downloads pnpm and Yarn 1 from registry.npmjs.org and registry.yarnpkg.com, which the list already named, and a contained corepack pnpm@10 install worked before. The policy page, the FAQ and SECURITY.md now list the host.
Below Landlock ABI v6 the contained process runs in a process group of its own, so the terminal stops it with SIGTTIN when it reads the terminal. A stopped process acts on no signal but SIGKILL and SIGCONT, so the SIGINT the supervisor passed on for Ctrl-C stayed pending and the run went on. Measured on Linux 6.18 with the older path forced, a contained sh -c 'read x' stopped that way was still running 10 seconds after Ctrl-C in 3 runs of 3. The supervisor now sends SIGCONT to the group after each signal it passes on. The same process ends on one Ctrl-C. A Node script that had read a line needed two, and a Go program reading stdin, signalled the same way outside nvx, needed one to four in 40 runs. Both catch Ctrl-C, and the read they restart can stop them again before they have exited. The docs now say so. The new terminal test case runs only on the older path. It waits until the reader is stopped before it types Ctrl-C, and it fails without the SIGCONT. The abstract socket test decides whether to skip from the ABI the kernel reports. It used landlockScopesForABI, the function under test. The comment on supervisorInterruptSignal now covers a target in a group of its own.
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 changes
A contained process on Linux can no longer signal nvx or anything else outside the sandbox. The contained process shares nvx's process group so that Ctrl-C at a terminal reaches everything it starts. A process group reaches across the sandbox's PID namespace, so a contained
kill(0, SIGKILL)reached nvx and every process beside it.ownGroupinrunLandlockExecChild.network.mode: openshares the host's network namespace, so a contained process could reach them there. The other modes already had a network namespace of their own.corepack can fetch Yarn 2 and later.
default_allownow namesrepo.yarnpkg.com:443. corepack downloads Yarn 2 and later from there and looks up Yarn's versions there for a range such asyarn@4oryarn@1. It downloads pnpm and Yarn 1 from the two registries the list already named.Evidence
Measured 2026-10-07 on WSL2 kernel 6.18 (Landlock ABI 7) in a privileged container, with the real nvx binary.
kill(0, SIGKILL), with asleepin nvx's groupsleep, nvx and the shell killedsleepand shell alive, nvx exits 137sleepand shell alive, nvx exits 137nvx npx -y http-servernvx --strict npm run servenvx --strict nodeREPL answers typed inputsh -c 'read x'ends on one, a Node script that read a line on two. Without the SIGCONT, still running 10 s on in 3 runs of 3fgresumes itnetwork.mode: open, connect to a host abstract socket and to Xvfb'sEPERMnetwork.mode: proxy, the same two connectionsECONNREFUSEDECONNREFUSEDcorepack yarn@4 install(corepack 0.36.0)repo.yarnpkg.com:443, exit 77corepack yarn@1 installrepo.yarnpkg.com:443, exit 77corepack pnpm@10 installA Go program stopped on a terminal read and signalled the same way outside nvx needed one to four Ctrl-C in 40 runs. A contained process can still signal the supervisor, PID 1 in the sandbox, on main and on this branch. The supervisor passes a signal it handles back to the contained process, and in 3 runs of 3 the kernel dropped a SIGKILL sent to it from inside.
Tests
New privileged tests, all named
TestContainedProcess...so the CI filter picks them up:TestContainedProcessCannotSignalOutsideItsSandboxchecks a containedkill(0, SIGKILL)beside a sentinel in nvx's group, on the kernel's ABI and on ABI v5. It fails on main, where nvx and the sentinel are killed.TestContainedProcessCannotReachAHostAbstractSocketchecks open mode. It fails on main, where the connection succeeds.TestContainedProcessStopsOnCtrlCUnderNpmsends Ctrl-C through a real pty to a realnpm runscript and annpxbin on both paths. It fails with the target in a group of its own and signalled alone, which is how it was before Fix Ctrl-C, /tmp and port publishing in the Linux sandbox #178.TestContainedProcessNpmPassesSignalsToItsScriptchecks that npm passes SIGTERM to an exec'd script. It fails when the contained process may not call kill(2).TestContainedProcessGetsTheTerminalsInterruptOncenow runs on both paths, and on the ABI v5 path checks that Ctrl-C ends a target the terminal stopped. That case fails without the SIGCONT.TestTheDefaultAllowlistLetsCorepackFetchYarnfails without the allowlist entry.Verification
TestNetns|TestReap|TestSupervisorClone|TestContainedProcess|TestNvxTermination|TestGitMetadata) as root in the container gave 25 top-level PASS, 0 FAIL and 0 SKIP. main gave 21 PASS. The new and changed tests passed 5 runs in a row.go test -race ./...on Linux in the container passed.go test ./internal/nvx -count=1on Windows passed.gofmt -l internal cmdprints nothing.go vet ./...passed for windows, linux and darwin.npm run check:contentpassed,astro checkfound 0 errors andnpm run buildbuilt 9 pages, in a Linux container.