mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-18 02:48:40 +00:00
ssh/tailssh: close tty on session close
We were only closing on side of the pty/tty pair. Close the other side too. Thanks to @fritterhoff for reporting and debugging the issue! Fixes #8119 Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
parent
03e848e3b5
commit
0ca8bf1e26
@ -452,7 +452,7 @@ func (ss *sshSession) launchProcess() error {
|
|||||||
return ss.startWithStdPipes()
|
return ss.startWithStdPipes()
|
||||||
}
|
}
|
||||||
ss.ptyReq = &ptyReq
|
ss.ptyReq = &ptyReq
|
||||||
pty, err := ss.startWithPTY()
|
pty, tty, err := ss.startWithPTY()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -461,10 +461,13 @@ func (ss *sshSession) launchProcess() error {
|
|||||||
// dup.
|
// dup.
|
||||||
ptyDup, err := syscall.Dup(int(pty.Fd()))
|
ptyDup, err := syscall.Dup(int(pty.Fd()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
pty.Close()
|
||||||
|
tty.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go resizeWindow(ptyDup /* arbitrary fd */, winCh)
|
go resizeWindow(ptyDup /* arbitrary fd */, winCh)
|
||||||
|
|
||||||
|
ss.tty = tty
|
||||||
ss.stdin = pty
|
ss.stdin = pty
|
||||||
ss.stdout = os.NewFile(uintptr(ptyDup), pty.Name())
|
ss.stdout = os.NewFile(uintptr(ptyDup), pty.Name())
|
||||||
ss.stderr = nil // not available for pty
|
ss.stderr = nil // not available for pty
|
||||||
@ -544,17 +547,16 @@ var opcodeShortName = map[uint8]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startWithPTY starts cmd with a pseudo-terminal attached to Stdin, Stdout and Stderr.
|
// startWithPTY starts cmd with a pseudo-terminal attached to Stdin, Stdout and Stderr.
|
||||||
func (ss *sshSession) startWithPTY() (ptyFile *os.File, err error) {
|
func (ss *sshSession) startWithPTY() (ptyFile, tty *os.File, err error) {
|
||||||
ptyReq := ss.ptyReq
|
ptyReq := ss.ptyReq
|
||||||
cmd := ss.cmd
|
cmd := ss.cmd
|
||||||
if cmd == nil {
|
if cmd == nil {
|
||||||
return nil, errors.New("nil ss.cmd")
|
return nil, nil, errors.New("nil ss.cmd")
|
||||||
}
|
}
|
||||||
if ptyReq == nil {
|
if ptyReq == nil {
|
||||||
return nil, errors.New("nil ss.ptyReq")
|
return nil, nil, errors.New("nil ss.ptyReq")
|
||||||
}
|
}
|
||||||
|
|
||||||
var tty *os.File
|
|
||||||
ptyFile, tty, err = pty.Open()
|
ptyFile, tty, err = pty.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("pty.Open: %w", err)
|
err = fmt.Errorf("pty.Open: %w", err)
|
||||||
@ -568,7 +570,7 @@ func (ss *sshSession) startWithPTY() (ptyFile *os.File, err error) {
|
|||||||
}()
|
}()
|
||||||
ptyRawConn, err := tty.SyscallConn()
|
ptyRawConn, err := tty.SyscallConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("SyscallConn: %w", err)
|
return nil, nil, fmt.Errorf("SyscallConn: %w", err)
|
||||||
}
|
}
|
||||||
var ctlErr error
|
var ctlErr error
|
||||||
if err := ptyRawConn.Control(func(fd uintptr) {
|
if err := ptyRawConn.Control(func(fd uintptr) {
|
||||||
@ -615,10 +617,10 @@ func (ss *sshSession) startWithPTY() (ptyFile *os.File, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, fmt.Errorf("ptyRawConn.Control: %w", err)
|
return nil, nil, fmt.Errorf("ptyRawConn.Control: %w", err)
|
||||||
}
|
}
|
||||||
if ctlErr != nil {
|
if ctlErr != nil {
|
||||||
return nil, fmt.Errorf("ptyRawConn.Control func: %w", ctlErr)
|
return nil, nil, fmt.Errorf("ptyRawConn.Control func: %w", ctlErr)
|
||||||
}
|
}
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
Setctty: true,
|
Setctty: true,
|
||||||
@ -642,7 +644,7 @@ func (ss *sshSession) startWithPTY() (ptyFile *os.File, err error) {
|
|||||||
if err = cmd.Start(); err != nil {
|
if err = cmd.Start(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return ptyFile, nil
|
return ptyFile, tty, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// startWithStdPipes starts cmd with os.Pipe for Stdin, Stdout and Stderr.
|
// startWithStdPipes starts cmd with os.Pipe for Stdin, Stdout and Stderr.
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
gossh "github.com/tailscale/golang-x-crypto/ssh"
|
gossh "github.com/tailscale/golang-x-crypto/ssh"
|
||||||
@ -811,6 +812,7 @@ type sshSession struct {
|
|||||||
stdout io.ReadCloser
|
stdout io.ReadCloser
|
||||||
stderr io.Reader // nil for pty sessions
|
stderr io.Reader // nil for pty sessions
|
||||||
ptyReq *ssh.Pty // non-nil for pty sessions
|
ptyReq *ssh.Pty // non-nil for pty sessions
|
||||||
|
tty *os.File // non-nil for pty sessions, must be closed after process exits
|
||||||
|
|
||||||
// We use this sync.Once to ensure that we only terminate the process once,
|
// We use this sync.Once to ensure that we only terminate the process once,
|
||||||
// either it exits itself or is terminated
|
// either it exits itself or is terminated
|
||||||
@ -1087,6 +1089,7 @@ func (ss *sshSession) run() {
|
|||||||
}
|
}
|
||||||
go ss.killProcessOnContextDone()
|
go ss.killProcessOnContextDone()
|
||||||
|
|
||||||
|
var processDone atomic.Bool
|
||||||
go func() {
|
go func() {
|
||||||
defer ss.stdin.Close()
|
defer ss.stdin.Close()
|
||||||
if _, err := io.Copy(rec.writer("i", ss.stdin), ss); err != nil {
|
if _, err := io.Copy(rec.writer("i", ss.stdin), ss); err != nil {
|
||||||
@ -1104,9 +1107,12 @@ func (ss *sshSession) run() {
|
|||||||
defer ss.stdout.Close()
|
defer ss.stdout.Close()
|
||||||
_, err := io.Copy(rec.writer("o", ss), ss.stdout)
|
_, err := io.Copy(rec.writer("o", ss), ss.stdout)
|
||||||
if err != nil && !errors.Is(err, io.EOF) {
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
logf("stdout copy: %v", err)
|
isErrBecauseProcessExited := processDone.Load() && errors.Is(err, syscall.EIO)
|
||||||
|
if !isErrBecauseProcessExited {
|
||||||
|
logf("stdout copy: %v, %T", err)
|
||||||
ss.cancelCtx(err)
|
ss.cancelCtx(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if openOutputStreams.Add(-1) == 0 {
|
if openOutputStreams.Add(-1) == 0 {
|
||||||
ss.CloseWrite()
|
ss.CloseWrite()
|
||||||
}
|
}
|
||||||
@ -1124,7 +1130,12 @@ func (ss *sshSession) run() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ss.tty != nil {
|
||||||
|
// If running a tty session, close the tty when the session is done.
|
||||||
|
defer ss.tty.Close()
|
||||||
|
}
|
||||||
err = ss.cmd.Wait()
|
err = ss.cmd.Wait()
|
||||||
|
processDone.Store(true)
|
||||||
// This will either make the SSH Termination goroutine be a no-op,
|
// This will either make the SSH Termination goroutine be a no-op,
|
||||||
// or itself will be a no-op because the process was killed by the
|
// or itself will be a no-op because the process was killed by the
|
||||||
// aforementioned goroutine.
|
// aforementioned goroutine.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user