Skip to content

[Go 1.24.0] CVE-2026-39830: ssh: fix deadlock on unexpected channel responses - #19

Open
Atharva-Shinde wants to merge 2 commits into
openshift-sustaining:sustaining-v0.48.0from
Atharva-Shinde:sustaining-v0.48.0
Open

[Go 1.24.0] CVE-2026-39830: ssh: fix deadlock on unexpected channel responses#19
Atharva-Shinde wants to merge 2 commits into
openshift-sustaining:sustaining-v0.48.0from
Atharva-Shinde:sustaining-v0.48.0

Conversation

@Atharva-Shinde

@Atharva-Shinde Atharva-Shinde commented Aug 19, 2026

Copy link
Copy Markdown

Process

git fetch upstream
git switch -c sustaining-v0.48.0 upstream/sustaining-v0.48.0
git am /tmp/fork-maintainer/GO-2026-5017/v0.48.0-GO-2026-5017-0.patch
git am /tmp/fork-maintainer/GO-2026-5017/v0.48.0-GO-2026-5017-1.patch

Conflicts

No conflicts — patches applied cleanly.

Patch Source

v0.48.0-GO-2026-5017-0.patch
v0.48.0-GO-2026-5017-1.patch

Verification

GOTOOLCHAIN=go1.24.0 go vet ./...   # passed
  • Runtime gates: none
Unit tests output
  crypto git:(sustaining-v0.48.0) go test ./...
ok      golang.org/x/crypto/acme        9.837s
ok      golang.org/x/crypto/acme/autocert       0.732s
?       golang.org/x/crypto/acme/autocert/internal/acmetest     [no test files]
?       golang.org/x/crypto/acme/internal/acmeprobe     [no test files]
ok      golang.org/x/crypto/argon2      0.481s
ok      golang.org/x/crypto/bcrypt      2.558s
ok      golang.org/x/crypto/blake2b     2.417s
ok      golang.org/x/crypto/blake2s     4.838s
ok      golang.org/x/crypto/blowfish    1.665s
ok      golang.org/x/crypto/bn256       2.931s
ok      golang.org/x/crypto/cast5       2.554s
ok      golang.org/x/crypto/chacha20    4.444s
ok      golang.org/x/crypto/chacha20poly1305    3.659s
ok      golang.org/x/crypto/cryptobyte  0.865s
?       golang.org/x/crypto/cryptobyte/asn1     [no test files]
ok      golang.org/x/crypto/curve25519  4.010s
ok      golang.org/x/crypto/ed25519     3.241s
ok      golang.org/x/crypto/hkdf        5.195s
ok      golang.org/x/crypto/internal/alias      5.559s
ok      golang.org/x/crypto/internal/poly1305   5.462s
?       golang.org/x/crypto/internal/testenv    [no test files]
ok      golang.org/x/crypto/internal/wycheproof 7.062s
?       golang.org/x/crypto/internal/wycheproof/internal/dsa    [no test files]
ok      golang.org/x/crypto/md4 5.071s
ok      golang.org/x/crypto/nacl/auth   4.897s
ok      golang.org/x/crypto/nacl/box    5.004s
ok      golang.org/x/crypto/nacl/secretbox      5.291s
ok      golang.org/x/crypto/nacl/sign   5.407s
ok      golang.org/x/crypto/ocsp        5.550s
ok      golang.org/x/crypto/openpgp     6.442s
ok      golang.org/x/crypto/openpgp/armor       5.504s
ok      golang.org/x/crypto/openpgp/clearsign   16.030s
ok      golang.org/x/crypto/openpgp/elgamal     5.486s
?       golang.org/x/crypto/openpgp/errors      [no test files]
ok      golang.org/x/crypto/openpgp/packet      5.425s
ok      golang.org/x/crypto/openpgp/s2k 6.429s
ok      golang.org/x/crypto/otr 5.588s
ok      golang.org/x/crypto/pbkdf2      5.142s
ok      golang.org/x/crypto/pkcs12      4.951s
ok      golang.org/x/crypto/pkcs12/internal/rc2 5.109s
?       golang.org/x/crypto/poly1305    [no test files]
ok      golang.org/x/crypto/ripemd160   5.121s
ok      golang.org/x/crypto/salsa20     5.449s
ok      golang.org/x/crypto/salsa20/salsa       5.516s
ok      golang.org/x/crypto/scrypt      5.567s
ok      golang.org/x/crypto/sha3        8.114s
ok      golang.org/x/crypto/ssh 8.100s
--- FAIL: TestAgent (0.52s)
    client_test.go:51: /usr/bin/ssh-agent -s failed: exit status 255
        unix_listener_tmp: path "/var/folders/4h/0l0dk3xj74z658xnht91fk7m0000gn/T/TestAgent3947482976/001/.ssh/agent/s.RZMcyhGgGE.agent.rq1UJbFtHv" too long for Unix domain socket
        main: Couldn't prepare agent socket
--- FAIL: TestCert (0.02s)
    client_test.go:51: /usr/bin/ssh-agent -s failed: exit status 255
        unix_listener_tmp: path "/var/folders/4h/0l0dk3xj74z658xnht91fk7m0000gn/T/TestCert2911727940/001/.ssh/agent/s.RZMcyhGgGE.agent.WX8uWQICWZ" too long for Unix domain socket
        main: Couldn't prepare agent socket
--- FAIL: TestAuth (0.01s)
    client_test.go:51: /usr/bin/ssh-agent -s failed: exit status 255
        unix_listener_tmp: path "/var/folders/4h/0l0dk3xj74z658xnht91fk7m0000gn/T/TestAuth1397800937/001/.ssh/agent/s.RZMcyhGgGE.agent.EGwQjdIuCf" too long for Unix domain socket
        main: Couldn't prepare agent socket
--- FAIL: TestLockOpenSSHAgent (0.01s)
    client_test.go:51: /usr/bin/ssh-agent -s failed: exit status 255
        unix_listener_tmp: path "/var/folders/4h/0l0dk3xj74z658xnht91fk7m0000gn/T/TestLockOpenSSHAgent2117164912/001/.ssh/agent/s.RZMcyhGgGE.agent.m5LglVk5tn" too long for Unix domain socket
        main: Couldn't prepare agent socket
2026/08/19 16:36:24 agent 23: agent: incorrect passphrase
--- FAIL: TestAgentExtensions (0.01s)
    client_test.go:51: /usr/bin/ssh-agent -s failed: exit status 255
        unix_listener_tmp: path "/var/folders/4h/0l0dk3xj74z658xnht91fk7m0000gn/T/TestAgentExtensions3807839265/001/.ssh/agent/s.RZMcyhGgGE.agent.3d61XODOBm" too long for Unix domain socket
        main: Couldn't prepare agent socket
2026/08/19 16:36:24 agent 25: agent: constraint extensions are present but not supported
--- FAIL: TestSetupForwardAgent (0.01s)
    client_test.go:51: /usr/bin/ssh-agent -s failed: exit status 255
        unix_listener_tmp: path "/var/folders/4h/0l0dk3xj74z658xnht91fk7m0000gn/T/TestSetupForwardAgent2170836517/001/.ssh/agent/s.RZMcyhGgGE.agent.WQPJdRUIEz" too long for Unix domain socket
        main: Couldn't prepare agent socket
FAIL
FAIL    golang.org/x/crypto/ssh/agent   5.216s
ok      golang.org/x/crypto/ssh/internal/bcrypt_pbkdf   5.066s
ok      golang.org/x/crypto/ssh/knownhosts      4.616s
?       golang.org/x/crypto/ssh/terminal        [no test files]
ok      golang.org/x/crypto/ssh/test    8.363s
ok      golang.org/x/crypto/tea 4.986s
ok      golang.org/x/crypto/twofish     5.219s
ok      golang.org/x/crypto/x509roots/nss       5.223s
ok      golang.org/x/crypto/xtea        5.248s
ok      golang.org/x/crypto/xts 5.217s
FAIL

Previously, the mux implementation handled global request responses by
blocking until the response could be sent to the globalResponses channel.
Since this channel has a buffer size of 1, unsolicited responses from a
server (or responses arriving after a timeout) would fill the buffer.
Subsequent unsolicited responses would block handleGlobalPacket, stalling
the entire connection's read loop and causing a denial of service.

This change modifies handleGlobalPacket to use a non-blocking send. If
no goroutine is waiting for a response (or the buffer is full), the
message is dropped. This aligns with OpenSSH behavior, which ignores
unexpected global responses.

Additionally, SendRequest now drains the globalResponses channel after
acquiring the mutex but before sending the request. This ensures that
any stale responses or "spam" buffered just before the lock was acquired
are discarded, preventing race conditions where a legitimate request
might otherwise consume an unrelated response.

This issue was found during a security audit by NCC Group Cryptography
Services, sponsored by Teleport.

Fixes golang/go#79564
Fixes CVE-2026-39830

Change-Id: Ia0c46355203d557eadcd432c10b87c8a044e1089
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/781640
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Neal Patel <nealpatel@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Previously, channel.handlePacket sent channelRequestSuccess and
channelRequestFailure messages to ch.msg unconditionally via the default
arm of its type switch. Because ch.msg is a bounded buffer (chanSize),
a peer that sends a burst of unsolicited channel request responses for
an open, idle channel fills the buffer and blocks the mux read loop on
the next send. That stalls all packet processing on the connection,
and because readLoop then backs up on t.incoming, closing the
underlying net.Conn does not unblock either goroutine: user code
observes Close() returning promptly while Wait() hangs and the mux,
readLoop, and kexLoop goroutines leak permanently.

This change mirrors the fix for the mux-level SendRequest path: a
sentRequestPending atomic gate is set while a SendRequest with
WantReply is in flight, handlePacket drops responses when the gate is
closed, and uses a non-blocking send otherwise. SendRequest drains
any spurious response that slipped through before discarding it, so
the caller always observes the reply to its own request.

This aligns with OpenSSH, which silently ignores channel confirm
messages that do not match a pending request.

Fixes golang/go#79564
Fixes CVE-2026-39830

Change-Id: I15e2add4bf7876bb0c6f921f8b57203d97e83f47
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/781664
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Neal Patel <nealpatel@google.com>
Reviewed-by: Neal Patel <nealpatel@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants