You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
step ssh proxycommand hangs ~60s when server closes connection before stdin closes #1641
step ssh proxycommand hangs for approximately 60 seconds when the SSH server closes the connection before the client has closed stdin. This occurs when the server rejects the connection mid-session (e.g. PAM account check failure after certificate authentication succeeds), sending a SSH2_MSG_USERAUTH_BANNER followed by a disconnect.
The process eventually exits due to an OS-level timeout, but the hang makes it appear to the user that the connection is still in progress.
Suspected Cause
The deadlock is in proxyDirect / proxyDirectWithIO in command/ssh/proxycommand.go:
varwg sync.WaitGroupwg.Add(1)
gofunc() {
io.Copy(conn, os.Stdin) // goroutine 1: blocks reading stdinconn.CloseWrite()
wg.Done()
}()
wg.Add(1)
gofunc() {
io.Copy(os.Stdout, conn) // goroutine 2: exits when server closesconn.CloseRead()
wg.Done()
}()
wg.Wait() // waits for both — never returns
When the server closes the TCP connection:
Goroutine 2 exits and calls conn.CloseRead()
Goroutine 1 is blocked reading from os.Stdin
os.Stdin is a pipe from the SSH client process, which hasn't closed because it's waiting for the ProxyCommand to exit
The ProxyCommand is waiting for both goroutines — deadlock
Calling os.Stdin.Close() from goroutine 2 does not reliably interrupt a blocked read() syscall on macOS when stdin is a pipe.
Reproduction
// Start a TCP server that sends data and immediately closesln, _:=net.Listen("tcp", "127.0.0.1:0")
gofunc() {
conn, _:=ln.Accept()
conn.Write([]byte("hello"))
conn.Close()
}()
// Simulate a stdin that never closes (SSH client waiting for ProxyCommand)stdinR, _:=io.Pipe() // write end intentionally left open// This hangs indefinitelyproxyDirectWithIO("127.0.0.1", port, stdinR, io.Discard)
Suggested Fix
In command/ssh/proxycommand.go, I think we can return early when either goroutine completes rather than waiting for both. Once one side closes, the ProxyCommand has nothing more to do — returning allows the OS to reclaim the blocked goroutine.
Test
A regression test could simulate a TCP server that sends data and immediately closes, while holding stdin open indefinitely. Before the fix, proxyDirectWithIO would hang; after, it should return promptly.
Activity
Fix proxycommand hang when server closes before stdin closes