mirror of
https://github.com/tailscale/tailscale.git
synced 2026-11-06 01:23:10 -05:00
5cf27272255e4db70e2db87932fe9f07fa6e730c
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1334899864 |
ssh/tailssh: stop killing session processes on graceful disconnects
Monitor teardown SIGHUPed and SIGKILLed every remembered child group when the transport closed. sshd signals nothing at a non-PTY session end; background jobs with redirected stdio survive and reparent to init. Clients rely on that: VS Code Remote-SSH starts its remote server as a plain background job in the session's group and expects it to outlive connection drops, and tailscale upgrades run as non-PTY execs whose work should survive a daemon restart. Use an explicit graceful monitor close to preserve session jobs on ordinary disconnect and daemon shutdown. Close PTY masters normally, letting the terminal driver signal the foreground group, but do not signal non-PTY jobs. Remove Linux Pdeathsig from session children; forwarding children retain it and always die with the connection. Commit duration and recording-failure termination to the monitor before publishing cancellation or writing the termination message. The monitor owns per-session HUP-to-KILL escalation even after the leader exits or the connection closes. Revocation arms connection-wide cleanup. Pin every termination target's leader until after SIGKILL, including during teardown, so group signaling cannot hit reused PIDs. Select teardown targets explicitly and leave graceful survivors alone. Unexpected RPC loss fails closed. If a termination mandate cannot be delivered, close the RPC rather than subsequently requesting graceful teardown; this may also kill sibling jobs on a broken channel. Keep both the escalation and monitor shutdown waits bounded. Add coverage for default-HUP graceful survivors, actual duration and recording-failure termination followed by immediate disconnect, sibling survival, HUP-exiting leaders with immune group members, broken RPCs, and leader pinning through teardown's SIGKILL pass. Fixes #22076 Signed-off-by: James Tucker <james@tailscale.com> Change-Id: I70b4ef40035330dd542d256ba79d889a12426a74 |
||
|
|
5a05ac11d5 |
ssh/tailssh: bound session teardown so shutdown can't wedge on it
Bound the server shutdown wait and the output drain after a session process exits. Descendants can keep stdio open after the session process exits, so neither wait can depend on EOF alone. Abort the SSH channel on drain timeout without sending EOF concurrently with an outstanding output write. Close all PTY master descriptors on cancellation and let the terminal driver send SIGHUP. Duplicate the masters with CLOEXEC and O_NONBLOCK before wrapping them with os.NewFile so the runtime poller can interrupt pending I/O on Close. Keep the PTY child's blocking mode intact. Use RawConn.Control for duplication and window changes to synchronize with Close and avoid restoring blocking mode on the original master through File.Fd. Non-PTY sessions receive SIGHUP explicitly. Keep SIGKILL escalation for monitor sessions and add it for directly launched sessions. Use os.Process.Signal on the direct path to synchronize signaling with reaping. Make the escalation delay configurable alongside the shutdown and I/O drain timeouts. Use a process-completion channel to stop the cancellation worker on normal exit, and join the worker before sending exit status so any cancellation message is written before channel teardown. Cover normal completion and cancellation through live monitored SSH sessions. Fixes tailscale/corp#50380 Fixes #22059 Updates #20628 Updates #21827 Signed-off-by: James Tucker <james@tailscale.com> Change-Id: I2a66741ba156a607dc3df70a79e140f19737b7a5 |
||
|
|
de16943cbf |
ssh/tailssh: add user-credential forwarding children and monitor dial/listen ops
Add the one-shot "be-child ssh-fwd" children that perform forwarded socket work, and the monitor and client plumbing for the socket ops. The conn and session flows do not use the ops yet. The next commit routes them. - fwdchild.go: a child re-execed by the monitor, born unprivileged at clone. The monitor applies the connection's bound identity as SysProcAttr.Credential, so the kernel's DAC sees the connection user's permissions. It dials a unix socket, or creates one with umask-at-bind mode and optional unlink-if-owned, or binds a TCP listener, or creates the agent-forwarding socket in a user-owned directory. The result returns to the monitor over an inherited socketpair as one status line with at most one descriptor attached. - monitor.go: handleDial and handleListen run the ops on one-shot children with a spawn-to-result deadline, monFwdOpDeadline. On expiry the child's group is SIGKILLed. killChild reaps such a child with a bounded non-blocking wait. A child left in uninterruptible sleep is abandoned in its own group rather than blocking the loop goroutine. The dial and listen placeholder replies are gone, and their not-implemented test with them. - monitor_client.go: Dial, Listen, and ListenAgent over the existing request-ID plumbing. - incubator.go, monitor_linux.go, monitor_other.go: registration and per-platform spawn attributes for the fwd children. Tests drive real children across uid boundaries. They cover dial and bind round trips, unix and TCP listens, agent-listen directory hygiene, unlink-if-owned, peer-credential assertions, and the no-identity-keys-on-the-wire shape of every request. Forwarding children spawn with an empty environment through spawnRPCChild: they take everything from argv and the result socket, and the daemon's environment must not reach a child running as the connection's user. Updates tailscale/corp#50380 Signed-off-by: James Tucker <james@tailscale.com> |
||
|
|
ed7903582b |
ssh/tailssh: add monitor RPC codec and SCM_RIGHTS fd passing
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> |