Repository navigation
Let the broker create native fork children's control sockets - #1758
Draft
Weiteng Chen (CvvT) wants to merge 7 commits into
Draft
Weiteng Chen (CvvT) wants to merge 7 commits into
Weiteng Chen (CvvT) wants to merge 7 commits into
Conversation
Weiteng Chen (CvvT)
force-pushed
the
weiteng/broker-fork-socket
branch
from
October 29, 2026 23:52
747065c to
e337088
Compare
A Linux runner that forks natively used to create its clone's control socket pair itself and send the broker the descriptor number of the broker's end inside the clone, which the broker then pulled out of the clone with pidfd_getfd. That needed a dumpable runner and a Yama ptrace_scope below 2, had the broker reach into an untrusted runner's descriptor table at a number the runner chose, and needed the runner's syscall filter to allow socketpair. Now the runner asks the broker for its pending child's connection before cloning (ProcessRequest::ConnectChild). The broker creates the pair, keeps its end with the pending child, and delivers the other end over the parent's shared-object socket; the clone inherits it, and adoption takes the broker's end by the child's broker-issued process ID. - Protocol: ConnectChild(child) answered with the delivery token. - Broker host: routes it to ProcessLauncher::connect_child after checking the child is the caller's pending child (new BrokerProcess::check_pending_child); launchers that cannot deliver objects keep the default, which reports it unsupported. - Userland broker: ChildConnections registers each association's shared-object sender and keeps at most one connection per parent, as a process has at most one pending child; adoption no longer uses pidfds to take a descriptor, keeping its waitid and supervision checks. ExistingRunnerDescriptor now holds only the process ID. - Runner: ForkRunner::create_control_channel receives the pending child; the Linux runner takes the delivered socket and moves it to the control-socket number in the clone. Its syscall filter no longer allows socketpair, and native_fork_supported checks only for pidfds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ending child The broker now enables SO_PASSCRED on its end of a clone's control socket and, before adopting the clone, checks that the process named in the runner descriptor sent the first data on it, binding the adopted PID to the connection. The broker's end is kept with the pending child in the broker core, so it is dropped when the child is cancelled, started without it, or replaced, and is handed to launch_existing atomically with the pending-child check. This replaces check_pending_child, the launcher's pending map, and the Windows connection stubs. pidfd_open is shared from the Linux platform crate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ProcessLauncher declares its connection as an associated type, so launchers see their own concrete type; the broker core keeps the type erasure private and refuses a connection of the wrong type without taking the child. The token under which the broker delivers a shared object becomes SharedObjectToken in the protocol. The userland broker separates creating a child's control connection from delivering it, so SharedObjectSenders::send delivers any host object. native_fork_supported only probed pidfd_open (Linux 5.3), but the userland broker already requires Linux 5.11 to spawn any runner (close_range with CLOSE_RANGE_CLOEXEC), and fork requires that broker, so it is removed. Also trims ProcessLauncher and seccomp comments and drops a trivial descriptor round-trip test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Weiteng Chen (CvvT)
force-pushed
the
weiteng/broker-fork-socket
branch
from
October 30, 2026 22:50
173ed0d to
ba500e9
Compare
The kernel stamps SCM_CREDENTIALS with a PID number taken when data is sent, so data queued before the broker checked the named clone could carry a PID that was since reused. The broker now pins the clone (an unreaped child, via waitid WNOWAIT), requires the connection's queue to be empty, and admits the clone with a byte it waits for before sending. Only then does it accept the first data, whose stamp must name the clone. Also: - Parse credentials as libc::ucred; rustix's UCred cannot hold the PID 0 Linux stamps on data from senders it cannot name. - Return an AuthenticatedClone that adopt consumes, so adoption cannot skip authentication. - Map clone adoption failures to EPERM, EIO, or EAGAIN instead of always EPERM. - Share one take path for pending children in broker core, renaming connect_child_process there to attach_child_connection. - Unregister shared-object senders through an RAII guard. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…registry lock On Linux 6.5 and later, the broker enables SO_PASSPIDFD with SO_PASSCRED on its end of a clone's control socket, so the kernel attaches the sender's pidfd to the clone's first data. The pidfd refers to the sending process itself, not a PID number that may be reused, so the broker admits the clone at once and needs no pin, empty-queue check, or admission wait: it requires the stamped PID to name the clone and keeps the pidfd. Older kernels, which reject SO_PASSPIDFD with ENOPROTOOPT, keep the existing protocol, whose pin now opens a pidfd for the named clone. Either way, adopt checks the clone's pidfd under the runner registry lock, under which runners become supervised. Authentication may wait, during which a supervised runner with the named PID could be reaped by its owner, deregistered, and its PID reused by a process that is not the broker's child; adopting that process would let its teardown SIGKILL whatever later takes the PID. Only supervision reaps runners, so a pidfd that still refers to an unsupervised running child keeps its PID until the broker registers it. pidfd_open and waitid(P_PIDFD) need Linux 5.3 and 5.4, within the userland broker's existing Linux 5.11 requirement. Also: - Close the descriptors that even a peek installs from SCM_RIGHTS, and reject data whose control messages were truncated. - Send shared objects under the senders lock, so dropping a registration stops delivery. - Return NotConnected rather than panic when a closed Windows runner endpoint is served. - Rename ConnectChild and connect_child_process to RequestChildConnection and request_child_connection. - Fix stale comments on runner descriptors, clone adoption errors, and the admission wait. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…the request SenderIdentifyingStream in the Linux transport now owns the broker's end of a native clone's control connection: it enables SO_PASSPIDFD where the kernel supports it and falls back to process-ID identification, whose pin, empty-queue check, and admission byte it runs itself. The userland broker only supplies the pin, and adopts the returned stream and pidfd. Control-message parsing moves to control_message.rs. Also: - Rename RequestChildConnection to CreateChildConnection, documenting UnknownObject for a process with no such pending child. - Drop the runner's socketpair seccomp rule, now that the broker creates the pair. - Keep shared-object senders on the process launcher. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…private Tests: - Drop tests that only restated a match: the clone adoption error mapping and the missing-sender-pidfd mapping. - Test pin_running_child directly, and test adoption through SenderIdentifyingStream::pair, so the broker's tests no longer force a sender identification mode. Drop cases the transport tests already cover. - Drop the duplicated admit-then-send block from the admission-wait test. Transport: - Fall back to process-ID identification on the same socket pair instead of creating a second one when the kernel rejects SO_PASSPIDFD. - Make SenderIdentification private, move pair_identified_by into the tests, and drop the unused identification getter. - Shorten the verify_first_sender documentation, leaving the broker's running-child requirement to adopt. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🤖 SemverChecks 🤖 Click for details |
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.
A Linux runner that forks natively used to create its clone's control socket pair itself and send the broker the descriptor number of the broker's end inside the clone, which the broker then pulled out of the clone with
pidfd_getfd. That needed a dumpable runner and a Yamaptrace_scopebelow 2, had the broker reach into an untrusted runner's descriptor table at a number the runner chose, and needed the runner's syscall filter to allow socketpair.Now the broker creates the pair, keeps its end with the pending child, and delivers the other end over the parent's shared-object socket; the clone inherits it.