Skip to content

Commit

Permalink
ssh: don't assume packet plaintext size
Browse files Browse the repository at this point in the history
When reading GCM and ChaChaPoly1305 packets, don't make assumptions
about the size of the enciphered plaintext. This fixes two panics
caused by standards non-compliant malformed packets.

Thanks to Rod Hynes, Psiphon Inc. for reporting this issue.

Fixes golang/go#49932
Fixes CVE-2021-43565

Change-Id: I660cff39d197e0d04ec44d11d792b22d954df2ef
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1262659
Reviewed-by: Katie Hockman <katiehockman@google.com>
Reviewed-by: Julie Qiu <julieqiu@google.com>
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/368814
Trust: Roland Shoemaker <roland@golang.org>
Trust: Katie Hockman <katie@golang.org>
Run-TryBot: Roland Shoemaker <roland@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Julie Qiu <julie@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
  • Loading branch information
rolandshoemaker committed Dec 2, 2021
1 parent ae814b3 commit 5770296
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 0 deletions.
8 changes: 8 additions & 0 deletions ssh/cipher.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,10 @@ func (c *gcmCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error)
}
c.incIV()

if len(plain) == 0 {
return nil, errors.New("ssh: empty packet")
}

padding := plain[0]
if padding < 4 {
// padding is a byte, so it automatically satisfies
Expand Down Expand Up @@ -710,6 +714,10 @@ func (c *chacha20Poly1305Cipher) readCipherPacket(seqNum uint32, r io.Reader) ([
plain := c.buf[4:contentEnd]
s.XORKeyStream(plain, plain)

if len(plain) == 0 {
return nil, errors.New("ssh: empty packet")
}

padding := plain[0]
if padding < 4 {
// padding is a byte, so it automatically satisfies
Expand Down
100 changes: 100 additions & 0 deletions ssh/cipher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import (
"bytes"
"crypto"
"crypto/rand"
"encoding/binary"
"io"
"testing"

"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/internal/poly1305"
)

func TestDefaultCiphersExist(t *testing.T) {
Expand Down Expand Up @@ -129,3 +134,98 @@ func TestCBCOracleCounterMeasure(t *testing.T) {
lastRead = bytesRead
}
}

func TestCVE202143565(t *testing.T) {
tests := []struct {
cipher string
constructPacket func(packetCipher) io.Reader
}{
{
cipher: gcmCipherID,
constructPacket: func(client packetCipher) io.Reader {
internalCipher := client.(*gcmCipher)
b := &bytes.Buffer{}
prefix := [4]byte{}
if _, err := b.Write(prefix[:]); err != nil {
t.Fatal(err)
}
internalCipher.buf = internalCipher.aead.Seal(internalCipher.buf[:0], internalCipher.iv, []byte{}, prefix[:])
if _, err := b.Write(internalCipher.buf); err != nil {
t.Fatal(err)
}
internalCipher.incIV()

return b
},
},
{
cipher: chacha20Poly1305ID,
constructPacket: func(client packetCipher) io.Reader {
internalCipher := client.(*chacha20Poly1305Cipher)
b := &bytes.Buffer{}

nonce := make([]byte, 12)
s, err := chacha20.NewUnauthenticatedCipher(internalCipher.contentKey[:], nonce)
if err != nil {
t.Fatal(err)
}
var polyKey, discardBuf [32]byte
s.XORKeyStream(polyKey[:], polyKey[:])
s.XORKeyStream(discardBuf[:], discardBuf[:]) // skip the next 32 bytes

internalCipher.buf = make([]byte, 4+poly1305.TagSize)
binary.BigEndian.PutUint32(internalCipher.buf, 0)
ls, err := chacha20.NewUnauthenticatedCipher(internalCipher.lengthKey[:], nonce)
if err != nil {
t.Fatal(err)
}
ls.XORKeyStream(internalCipher.buf, internalCipher.buf[:4])
if _, err := io.ReadFull(rand.Reader, internalCipher.buf[4:4]); err != nil {
t.Fatal(err)
}

s.XORKeyStream(internalCipher.buf[4:], internalCipher.buf[4:4])

var tag [poly1305.TagSize]byte
poly1305.Sum(&tag, internalCipher.buf[:4], &polyKey)

copy(internalCipher.buf[4:], tag[:])

if _, err := b.Write(internalCipher.buf); err != nil {
t.Fatal(err)
}

return b
},
},
}

for _, tc := range tests {
mac := "hmac-sha2-256"

kr := &kexResult{Hash: crypto.SHA1}
algs := directionAlgorithms{
Cipher: tc.cipher,
MAC: mac,
Compression: "none",
}
client, err := newPacketCipher(clientKeys, algs, kr)
if err != nil {
t.Fatalf("newPacketCipher(client, %q, %q): %v", tc.cipher, mac, err)
}
server, err := newPacketCipher(clientKeys, algs, kr)
if err != nil {
t.Fatalf("newPacketCipher(client, %q, %q): %v", tc.cipher, mac, err)
}

b := tc.constructPacket(client)

wantErr := "ssh: empty packet"
_, err = server.readCipherPacket(0, b)
if err == nil {
t.Fatalf("readCipherPacket(%q, %q): didn't fail with empty packet", tc.cipher, mac)
} else if err.Error() != wantErr {
t.Fatalf("readCipherPacket(%q, %q): unexpected error, got %q, want %q", tc.cipher, mac, err, wantErr)
}
}
}

0 comments on commit 5770296

Please sign in to comment.