mirror of
https://github.com/tailscale/tailscale.git
synced 2026-10-29 22:49:17 -04:00
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
56 lines
1.7 KiB
Go
56 lines
1.7 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build linux && !android
|
|
|
|
package tailssh
|
|
|
|
import (
|
|
"math"
|
|
"syscall"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// monChildAttr returns the SysProcAttr for one monitor child. A ctty
|
|
// child gets Setsid+Setctty with the PTY child endpoint as fd 0; Setpgid is
|
|
// set only for non-ctty children. Pdeathsig is advisory: it fires on
|
|
// the exit of the creating thread, not the process, per golang/go#27505.
|
|
func monChildAttr(ctty bool) *syscall.SysProcAttr {
|
|
return &syscall.SysProcAttr{
|
|
Setpgid: !ctty,
|
|
Setsid: ctty,
|
|
Setctty: ctty,
|
|
Pdeathsig: unix.SIGHUP,
|
|
}
|
|
}
|
|
|
|
// scrubFds marks every open descriptor from 3 up close-on-exec, so the
|
|
// monitor cannot leak an inherited or late-created descriptor into a
|
|
// child it execs. close_range(CLOSE_RANGE_CLOEXEC) does this in one
|
|
// atomic syscall on kernels 5.11 and later; older kernels fall back to
|
|
// a /proc/self/fd enumeration.
|
|
func scrubFds() error {
|
|
if err := unix.CloseRange(3, math.MaxUint32, unix.CLOSE_RANGE_CLOEXEC); err == nil {
|
|
return nil
|
|
}
|
|
return scrubFdsByScan("/proc/self/fd")
|
|
}
|
|
|
|
// fwdChildAttr returns the SysProcAttr for a one-shot forwarding child
|
|
// whose identity must be applied, that is a root monitor's child. The
|
|
// Credential carries the connection's bound identity from ma. The child
|
|
// leads its own group so the op deadline and teardown can kill the
|
|
// whole group, and Pdeathsig is set as in monChildAttr.
|
|
func fwdChildAttr(ma monitorArgs) *syscall.SysProcAttr {
|
|
return &syscall.SysProcAttr{
|
|
Credential: &syscall.Credential{
|
|
Uid: ma.uid,
|
|
Gid: ma.gid,
|
|
Groups: fwdCredGroups(ma.gid, ma.gids),
|
|
},
|
|
Setpgid: true,
|
|
Pdeathsig: unix.SIGHUP,
|
|
}
|
|
}
|