From 9beb694f9766a2c69fe6c89cfa6cf653a32b5a27 Mon Sep 17 00:00:00 2001 From: Neal Patel Date: Tue, 19 May 2026 20:31:25 -0400 Subject: [PATCH] ssh: prevent malformed exit-status panic A malicious server can crash any client by sending a truncated exit-status payload, which triggers a panic in binary.BigEndian.Uint32. Change-Id: Iff4fe182b40210c99d46562f6c3b5ecf38bb98b4 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/780181 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Nicola Murino Reviewed-by: Junyang Shao Reviewed-by: David Chase --- ssh/session.go | 3 +++ ssh/session_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/ssh/session.go b/ssh/session.go index acef622..ac62955 100644 --- a/ssh/session.go +++ b/ssh/session.go @@ -423,6 +423,9 @@ func (s *Session) wait(reqs <-chan *Request) error { for msg := range reqs { switch msg.Type { case "exit-status": + if len(msg.Payload) < 4 { + return errors.New("ssh: malformed exit-status request") + } wm.status = int(binary.BigEndian.Uint32(msg.Payload)) case "exit-signal": var sigval struct { diff --git a/ssh/session_test.go b/ssh/session_test.go index 807a913..a6815bc 100644 --- a/ssh/session_test.go +++ b/ssh/session_test.go @@ -334,6 +334,25 @@ func TestExitWithoutStatusOrSignal(t *testing.T) { } } +func TestExitStatusShortPayload(t *testing.T) { + for _, payload := range [][]byte{nil, {0}, {0, 0}, {0, 0, 0}} { + conn := dial(exitStatusShortPayloadHandler(payload), t) + session, err := conn.NewSession() + if err != nil { + t.Fatalf("unable to request new session: %v", err) + } + if err := session.Shell(); err != nil { + t.Fatalf("unable to execute command: %v", err) + } + err = session.Wait() + if err == nil { + t.Fatalf("expected error for payload length %d", len(payload)) + } + session.Close() + conn.Close() + } +} + // windowTestBytes is the number of bytes that we'll send to the SSH server. const windowTestBytes = 16000 * 200 @@ -482,6 +501,17 @@ func exitWithoutSignalOrStatus(ch Channel, in <-chan *Request, t *testing.T) { readLine(shell, t) } +func exitStatusShortPayloadHandler(payload []byte) serverType { + return func(ch Channel, in <-chan *Request, t *testing.T) { + defer ch.Close() + shell := newServerShell(ch, in, "> ") + readLine(shell, t) + if _, err := ch.SendRequest("exit-status", false, payload); err != nil { + t.Errorf("unable to send status: %v", err) + } + } +} + func shellHandler(ch Channel, in <-chan *Request, t *testing.T) { defer ch.Close() // this string is returned to stdout