From 0c5f48d695da07bb0a1f6431c6551086d0100b80 Mon Sep 17 00:00:00 2001 From: Nate Brown Date: Tue, 17 Mar 2026 21:12:38 -0500 Subject: [PATCH] Fix sshd goroutine leak and other cleanup --- sshd/server.go | 13 +++++++------ sshd/session.go | 30 +++++++++++++++++++----------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/sshd/server.go b/sshd/server.go index a8b60ba7..4a077920 100644 --- a/sshd/server.go +++ b/sshd/server.go @@ -16,16 +16,13 @@ type SSHServer struct { config *ssh.ServerConfig l *logrus.Entry - certChecker *ssh.CertChecker - // Map of user -> authorized keys trustedKeys map[string]map[string]bool trustedCAs []ssh.PublicKey // List of available commands - helpCommand *Command - commands *radix.Tree - listener net.Listener + commands *radix.Tree + listener net.Listener // Locks the conns/counter to avoid concurrent map access connsLock sync.Mutex @@ -184,7 +181,11 @@ func (s *SSHServer) run() { if err != nil { l := s.l.WithError(err).WithField("remoteAddress", c.RemoteAddr()) - if conn != nil { + if conn == nil { + // conn is nil when the handshake failed before authentication + // close the raw TCP connection to avoid leaking the file descriptor. + c.Close() + } else { l = l.WithField("sshUser", conn.User()) conn.Close() } diff --git a/sshd/session.go b/sshd/session.go index 87cc216f..fca6f3ab 100644 --- a/sshd/session.go +++ b/sshd/session.go @@ -4,6 +4,7 @@ import ( "fmt" "sort" "strings" + "sync" "github.com/anmitsu/go-shlex" "github.com/armon/go-radix" @@ -13,11 +14,12 @@ import ( ) type session struct { - l *logrus.Entry - c *ssh.ServerConn - term *term.Terminal - commands *radix.Tree - exitChan chan bool + l *logrus.Entry + c *ssh.ServerConn + term *term.Terminal + commands *radix.Tree + exitChan chan struct{} + closeOnce sync.Once } func NewSession(commands *radix.Tree, conn *ssh.ServerConn, chans <-chan ssh.NewChannel, l *logrus.Entry) *session { @@ -25,7 +27,7 @@ func NewSession(commands *radix.Tree, conn *ssh.ServerConn, chans <-chan ssh.New commands: radix.NewFromMap(commands.ToMap()), l: l, c: conn, - exitChan: make(chan bool), + exitChan: make(chan struct{}), } s.commands.Insert("logout", &Command{ @@ -37,7 +39,10 @@ func NewSession(commands *radix.Tree, conn *ssh.ServerConn, chans <-chan ssh.New }, }) - go s.handleChannels(chans) + go func() { + s.handleChannels(chans) + s.Close() + }() return s } @@ -82,6 +87,7 @@ func (s *session) handleRequests(in <-chan *ssh.Request, channel ssh.Channel) { cErr := ssh.Unmarshal(req.Payload, &payload) if cErr != nil { req.Reply(false, nil) + channel.Close() return } @@ -123,11 +129,11 @@ func (s *session) createTerm(channel ssh.Channel) *term.Terminal { return "", 0, false } - go s.handleInput(channel) + go s.handleInput() return term } -func (s *session) handleInput(channel ssh.Channel) { +func (s *session) handleInput() { defer s.Close() w := &stringWriter{w: s.term} for { @@ -174,6 +180,8 @@ func (s *session) dispatchCommand(line string, w StringWriter) { } func (s *session) Close() { - s.c.Close() - s.exitChan <- true + s.closeOnce.Do(func() { + s.c.Close() + close(s.exitChan) + }) }