mirror of
https://github.com/tailscale/tailscale.git
synced 2026-10-31 07:29:36 -04:00
New self-contained code for the per-connection privileged ssh-monitor. No existing behavior or wiring changes. Later commits wire the forwarding and session paths onto it. rpcFrame is the newline-delimited JSON wire struct with request IDs. It covers dial, listen, exec, signal, and ping requests, ok and err replies, and unsolicited exit notifications. rpcConn is the framing codec over one socketpair end. Reads go only through ReadMsgUnix with a CmsgSpace(3*sizeof(int)) OOB buffer. A plain Read or an undersized buffer silently drops ancillary data, and the kernel closes the dropped descriptors. A stream read never returns data beyond an SCM_RIGHTS-carrying message, so a descriptor arrives with the first chunk of a split line and survives reassembly. Writes attach descriptors to the first byte of a line and never to an empty payload. A short write resends the remainder as plain data, because resending the ancillary data would duplicate the descriptors. Received descriptors are marked close-on-exec at parse time, by the codec rather than by the stdlib's platform-dependent handling. Herald attribution validates a whole read's herald batch before queueing any set for a frame, so a failed batch closes its sets exactly once and never double-closes a set already queued for a frame. Lines are capped at 256 KiB, and NUL bytes and frames carrying more descriptors than their op declares are rejected on receive. A read timeout returns os.ErrDeadlineExceeded and preserves any partially received line, so the monitor's poll loop can sweep between reads without losing the frame. A read timeout is the only error that preserves state. Every other error closes all held descriptors and clears the buffer. sendFds, recvFds, and wrapUnixConn are shared by the monitor and client sides. wrapUnixConn consumes its fd, because os.NewFile's finalizer cannot be neutralized from outside. Tests exercise ping and fd round-trips with liveness and FD_CLOEXEC checks, multi-fd frames, kernel framing pins for first-chunk ancillary delivery and undersized-OOB MSG_CTRUNC rejection, 64 KiB-plus split-line reassembly under a shrunken SO_SNDBUF, oversized and NUL rejection, the short-write resend path, the read-timeout state-preservation contract, and 50-way multiplexed request/reply traffic. The monitor scrubs every descriptor from 3 up to close-on-exec at entry and again before each exec. Go's exec closes nothing beyond the descriptors it passes, so a single non-close-on-exec fd anywhere in the spawner's table would otherwise reach every child the monitor execs. close_range(CLOSE_RANGE_CLOEXEC) does the sweep in one syscall on kernels 5.11+; older kernels scan the self-fd directory. A new test injects a non-close-on-exec pipe at spawn and pins the clean-child contract. spawnRPCChild takes the child's environment explicitly: a nil env gives the child an empty environment, never the spawner's. The monitor child is spawned with the daemon's environment; user-identity children are spawned with none, so the daemon's environment never reaches a child running as the connection's user. Updates tailscale/corp#50380 Signed-off-by: James Tucker <james@tailscale.com>