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:
Maisem Ali
2023-05-17 12:18:23 -07:00
committed by Maisem Ali
parent 03e848e3b5
commit 0ca8bf1e26
2 changed files with 24 additions and 11 deletions

View File

@@ -28,6 +28,7 @@ import (
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
gossh "github.com/tailscale/golang-x-crypto/ssh"
@@ -811,6 +812,7 @@ type sshSession struct {
stdout io.ReadCloser
stderr io.Reader // 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,
// either it exits itself or is terminated
@@ -1087,6 +1089,7 @@ func (ss *sshSession) run() {
}
go ss.killProcessOnContextDone()
var processDone atomic.Bool
go func() {
defer ss.stdin.Close()
if _, err := io.Copy(rec.writer("i", ss.stdin), ss); err != nil {
@@ -1104,8 +1107,11 @@ func (ss *sshSession) run() {
defer ss.stdout.Close()
_, err := io.Copy(rec.writer("o", ss), ss.stdout)
if err != nil && !errors.Is(err, io.EOF) {
logf("stdout copy: %v", err)
ss.cancelCtx(err)
isErrBecauseProcessExited := processDone.Load() && errors.Is(err, syscall.EIO)
if !isErrBecauseProcessExited {
logf("stdout copy: %v, %T", err)
ss.cancelCtx(err)
}
}
if openOutputStreams.Add(-1) == 0 {
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()
processDone.Store(true)
// 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
// aforementioned goroutine.