ssh: reject unexpected message types on established channels ch.msg is only read while the channel open or a channel request with a reply is pending, so anything the default arm of channel.handlePacket delivered to it was never consumed. The blocking send there let a misbehaving peer fill the buffer with well-formed but unexpected message types carrying a valid channel id and stall the mux read loop, deadlocking the whole connection. No conforming peer sends such messages during the connection protocol. Treat them as a protocol error and tear the connection down, as handleUnknownChannelPacket already does for the same messages when the channel id is not in use. Fixes CVE-2026-56855 Fixes golang/go#81317 Change-Id: I87420dfe68fcb62a17df4b47dc5ffb6ccd72ba26 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/826524 Reviewed-by: Roland Shoemaker <roland@golang.org> 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: Nicholas Husin <husin@google.com>
diff --git a/ssh/channel.go b/ssh/channel.go index 0d9dac9..d6010fd 100644 --- a/ssh/channel.go +++ b/ssh/channel.go
@@ -519,7 +519,8 @@ default: } default: - ch.msg <- msg + // No other message type is expected on an established channel. + return fmt.Errorf("ssh: unexpected message type %d on channel %d", packet[0], ch.localId) } return nil }
diff --git a/ssh/mux_test.go b/ssh/mux_test.go index b844082..e23fd0e 100644 --- a/ssh/mux_test.go +++ b/ssh/mux_test.go
@@ -5,9 +5,11 @@ package ssh import ( + "encoding/binary" "errors" "fmt" "io" + "strings" "sync" "testing" "time" @@ -1360,6 +1362,54 @@ } } +func TestChannelBogusMessageTearsDownMux(t *testing.T) { + // A message that is well-formed on the wire but is not a type expected on + // an established channel is a protocol error that tears the mux down. + clientMux, serverMux := muxPair() + defer serverMux.Close() + defer clientMux.Close() + + serverRes := make(chan *channel, 1) + go func() { + newCh, ok := <-serverMux.incomingChannels + if !ok { + close(serverRes) + return + } + c, _, err := newCh.Accept() + if err != nil { + close(serverRes) + return + } + serverRes <- c.(*channel) + }() + + if _, err := clientMux.openChannel("chan", nil); err != nil { + t.Fatalf("openChannel: %v", err) + } + serverCh := <-serverRes + if serverCh == nil { + t.Fatal("server did not accept channel") + } + + // Craft a packet that the client mux routes to clientCh (bytes 1:5 hold the + // peer's channel id) and that decode accepts, but whose type is unexpected + // on a channel. The service-string length equals the channel id, so the + // same four bytes serve both as the routing id and as the string length. + id := serverCh.remoteId + packet := []byte{msgServiceRequest, 0, 0, 0, 0} + binary.BigEndian.PutUint32(packet[1:], id) + packet = append(packet, make([]byte, id)...) + if err := serverMux.conn.writePacket(packet); err != nil { + t.Fatalf("writePacket: %v", err) + } + + // The mux loop exits with the protocol error. + if err := clientMux.Wait(); err == nil || !strings.Contains(err.Error(), "unexpected message type") { + t.Fatalf("mux error = %v, want unexpected message type", err) + } +} + func TestChannelCloseIdempotent(t *testing.T) { a, b := memPipe() defer a.Close()