subproc: mark supervisor fds CLOEXEC before helper exec - #351
ManoharPaturi wants to merge 1 commit into
Conversation
f05bb58 to
537fde3
Compare
|
Small branch hygiene fix: my local CI workflow file accidentally ended up in this branch — force-pushed the branch rebuilt from current master with only the intended change. The diff is now just the patch itself; the actions scan failure from earlier is gone. |
…elper exec systemExe() executes external helpers such as newuidmap and newgidmap without first marking unrelated supervisor descriptors close-on-exec. Listening sockets, accepted connections, log descriptors, and other inherited resources could therefore cross the helper execve() boundary unnecessarily. Mark all descriptors above stderr FD_CLOEXEC with nsjail's existing util::makeRangeCOE() before the exec - the same mechanism the pasta helper path already uses. The systemExe() error-reporting pipe is created with O_CLOEXEC, so it remains available to report an execve() failure and is closed automatically on success. If the descriptor sealing itself fails, helper execution is aborted rather than running with an unknown inherited descriptor set. Adds a regression test proving a supervisor-owned fd (123) is not visible to a helper executed through systemExe().
537fde3 to
16dad09
Compare
|
Reworked per review: replaced the manual close_range + /proc/self/fd block with util::makeRangeCOE(STDERR_FILENO + 1, ~0U) (the same primitive the pasta helper path uses), keeping the error pipe's O_CLOEXEC semantics intact, and failing closed if the sealing fails. Also added the suggested regression test (tests/systemexe_fd_test.cc, runs in make test): it occupies fd 123 in the supervisor and proves the helper no longer sees it — on master the helper observes the fd and the test fails, with the patch it passes. Verified with a plain build and under ASan/UBSan. |
|
@robertswiecki Could you take a look at this revision? Following review feedback, the change now uses |
Problem
systemExe()executes external helpers such asnewuidmapandnewgidmapwithout first marking unrelated supervisor descriptors close-on-exec. As a result, listening sockets, accepted connections, log descriptors, or other inherited resources can cross the helperexecve()boundary unnecessarily.Change
Before executing an external helper, mark all descriptors above stderr
FD_CLOEXECusing nsjail's existingutil::makeRangeCOE()helper — the same mechanism the pasta helper path already uses.The
systemExe()error-reporting pipe is already created withO_CLOEXEC. This means it remains available whenexecve()fails, allowing the child to report the failure, while it is automatically closed whenexecve()succeeds.If descriptor sealing itself fails, helper execution is aborted rather than executing with an unknown inherited descriptor set.
Regression test
tests/systemexe_fd_test.cc(wired intomake test) demonstrates the behavior before/after: it occupies fd 123 in the supervisor, then runs a helper throughsystemExe()that checks for/dev/fd/123in its own process.master: the helper observes fd 123 and exits 42 → the test failsVerified on Ubuntu 24.04: plain build + full test suite, and a full ASan/UBSan build with the suite run under the sanitizers — all green, no sanitizer findings.
Notes
close_range(CLOSE_RANGE_CLOEXEC), which requires kernel ≥ 5.9; if support for older kernels without a fallback is preferred (as incontainMakeFdsCOE()), that variant is easy to switch to.makeRangeCOE()shape — an earlier revision of this PR closed descriptors manually and clearedO_CLOEXECon the error pipe, which was strictly worse than the existing primitive.