Skip to content

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

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

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

Conversation

@Atharva-Shinde

Copy link
Copy Markdown
git checkout sustaining-v0.33.0
git am /tmp/fork-maintainer/GO-2026-5017/v0.33.0-GO-2026-5017-0.patch
vi ssh/mux_test.go
git am /tmp/fork-maintainer/GO-2026-5017/v0.33.0-GO-2026-5017-1.patch
vi ssh/mux_test.go

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

Unit tests output
crypto git:(sustaining-v0.33.0) go test ./...
ok      golang.org/x/crypto/acme        11.138s
ok      golang.org/x/crypto/acme/autocert       0.728s
?       golang.org/x/crypto/acme/autocert/internal/acmetest     [no test files]
?       golang.org/x/crypto/acme/internal/acmeprobe     [no test files]
?       golang.org/x/crypto/cryptobyte/asn1     [no test files]
ok      golang.org/x/crypto/argon2      0.469s
?       golang.org/x/crypto/internal/testenv    [no test files]
?       golang.org/x/crypto/internal/wycheproof/internal/dsa    [no test files]
?       golang.org/x/crypto/openpgp/errors      [no test files]
ok      golang.org/x/crypto/bcrypt      6.160s
ok      golang.org/x/crypto/blake2b     4.044s
ok      golang.org/x/crypto/blake2s     2.467s
ok      golang.org/x/crypto/blowfish    1.275s
ok      golang.org/x/crypto/bn256       4.567s
ok      golang.org/x/crypto/cast5       2.753s
ok      golang.org/x/crypto/chacha20    0.854s
ok      golang.org/x/crypto/chacha20poly1305    3.312s
ok      golang.org/x/crypto/cryptobyte  3.667s
ok      golang.org/x/crypto/curve25519  1.690s
ok      golang.org/x/crypto/ed25519     2.849s
ok      golang.org/x/crypto/hkdf        5.191s
ok      golang.org/x/crypto/internal/alias      5.568s
ok      golang.org/x/crypto/internal/poly1305   5.491s
?       golang.org/x/crypto/poly1305    [no test files]
ok      golang.org/x/crypto/internal/wycheproof 7.271s
ok      golang.org/x/crypto/md4 5.446s
ok      golang.org/x/crypto/nacl/auth   5.767s
ok      golang.org/x/crypto/nacl/box    5.049s
ok      golang.org/x/crypto/nacl/secretbox      5.070s
ok      golang.org/x/crypto/nacl/sign   5.368s
ok      golang.org/x/crypto/ocsp        5.314s
ok      golang.org/x/crypto/openpgp     6.116s
ok      golang.org/x/crypto/openpgp/armor       5.357s
?       golang.org/x/crypto/ssh/terminal        [no test files]
ok      golang.org/x/crypto/openpgp/clearsign   12.934s
ok      golang.org/x/crypto/openpgp/elgamal     5.017s
ok      golang.org/x/crypto/openpgp/packet      4.935s
ok      golang.org/x/crypto/openpgp/s2k 5.985s
ok      golang.org/x/crypto/otr 5.255s
ok      golang.org/x/crypto/pbkdf2      5.051s
ok      golang.org/x/crypto/pkcs12      4.686s
ok      golang.org/x/crypto/pkcs12/internal/rc2 5.029s
ok      golang.org/x/crypto/ripemd160   5.030s
ok      golang.org/x/crypto/salsa20     5.180s
ok      golang.org/x/crypto/salsa20/salsa       5.431s
ok      golang.org/x/crypto/scrypt      5.424s
ok      golang.org/x/crypto/sha3        6.877s
--- FAIL: TestParseRSAModulusTooLarge (0.00s)
    keys_test.go:219: unexpected error message: got "ssh: no key found", want substring "rsa modulus too large"
seen &ssh.rsaPublicKey{N:20061140430650092691700129633418895153104099440152571948017228333944623487573521043236463535114811698275540013288855432311158126420471092062212564127370505763070250534466667545557854500626223850938086149993496716374117138731870544047779767066551724960262812037558993204799983435256152044565444388235895913343541217854379451044881161906995318571403079108766806750251846358097485205923957951642423646975888286438168592280219572188076697918703120158605105665730880552260696034697922765719453812717299592545244658127905527709856843276462192052069837242904720629896824162245468755516134327672099510173872004590681225925311, E:65537}
seen &ssh.dsaPublicKey{Parameters:dsa.Parameters{P:175721116665844170017669647669946518528413457312022711032734739623005755152443227429734217171261249553795678598220451924215157415901674587334166902511516864954496725567843523544375611356526453671701341914365481186446303719656617243678901998494782637847628119023876650244387440525171537115728772449014481872071, Q:832165918110642932865296677897233797470791928577, G:45710335010743906175172359887947291464946349254698214926126198012254417027163534549930512227720529453527812583090856824470895904385213843846478788439650923858707625528883721182368810782074418622955266070744886120496437975321603647691691785855608495718858707179099826464685770134027417598116971534482882955896}, Y:156527414827976273333470358776490302124383937379242530509323609715449789732336069914169957260987424440278372270165376235659769589602997045461900077747446091360682548418273726245607350047870147307811900550045251583664693054861184446136606830815561121439743970433898383744356459189167096821175929531468417688750}
FAIL
FAIL    golang.org/x/crypto/ssh 5.972s
ok      golang.org/x/crypto/ssh/agent   4.649s
ok      golang.org/x/crypto/ssh/internal/bcrypt_pbkdf   4.979s
ok      golang.org/x/crypto/ssh/knownhosts      4.490s
ok      golang.org/x/crypto/ssh/test    7.899s
ok      golang.org/x/crypto/tea 4.762s
ok      golang.org/x/crypto/twofish     5.000s
ok      golang.org/x/crypto/x509roots/nss       4.993s
ok      golang.org/x/crypto/xtea        5.036s
ok      golang.org/x/crypto/xts 5.046s
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