ssh: fix proxycommand stdin deadlock when server closes first#1642
Open
techwolf359 wants to merge 1 commit into
Open
ssh: fix proxycommand stdin deadlock when server closes first#1642techwolf359 wants to merge 1 commit into
techwolf359 wants to merge 1 commit into
Conversation
proxyDirect uses two goroutines with wg.Wait() requiring both to finish. When the server closes the connection, the server->client goroutine exits, but the client->server goroutine blocks forever reading from os.Stdin — stdin is a pipe from the SSH client, which won't close until the ProxyCommand exits, which won't happen until both goroutines finish. Calling os.Stdin.Close() from the server goroutine does not reliably interrupt a blocked read() syscall on macOS when stdin is a pipe. Fix by returning as soon as either goroutine finishes. When the server closes, the process exits and the OS reclaims the blocked goroutine. The ProxyCommand's only job is to relay bytes; once one side closes there is nothing more to do. Also extracts proxyDirectWithIO(host, port, in, out) to make the logic testable without depending on os.Stdin/os.Stdout, and adds a regression test that reproduces the deadlock and verifies the fix. Fixes: smallstep#1641
|
|
elliesalimi
approved these changes
Jun 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1641
Summary
step ssh proxycommandhangs when the SSH server closes the connection before the client has closed stdin. The hang lasts until an OS-level timeout (~60s on macOS) kills the process.Suspected cause
proxyDirectusessync.WaitGroupwaiting for both goroutines to finish:stdin→server): blocked readingos.Stdin, which is a pipe from the SSH clientserver→client): finishes when the server closes the connectionWhen the server closes, goroutine 2 exits. But goroutine 1 is stuck:
os.Stdinwon't close because the SSH client is waiting for the ProxyCommand to exit, and the ProxyCommand won't exit because goroutine 1 is still running — deadlock.Calling
os.Stdin.Close()from goroutine 2 does not reliably interrupt a blockedread()syscall on macOS when stdin is a pipe.Fix
Return as soon as either goroutine completes. The ProxyCommand's job is to relay bytes; once one side closes there is nothing more to do. The process exits and the OS reclaims the blocked goroutine.
Changes
proxyDirectWithIO(host, port, in, out)to enable testing without depending onos.Stdin/os.Stdoutwg.Wait()(wait for both) with<-done(return on first)TestProxyDirectExitsWhenServerCloses— reproduces the deadlock and verifies the fix (fails before, passes after)Testing