Skip to content

Commit 5770296

Browse files
ssh: don't assume packet plaintext size
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 <[email protected]> Reviewed-by: Julie Qiu <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/crypto/+/368814 Trust: Roland Shoemaker <[email protected]> Trust: Katie Hockman <[email protected]> Run-TryBot: Roland Shoemaker <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Julie Qiu <[email protected]> Reviewed-by: Katie Hockman <[email protected]>
1 parent ae814b3 commit 5770296

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

ssh/cipher.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,10 @@ func (c *gcmCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error)
394394
}
395395
c.incIV()
396396

397+
if len(plain) == 0 {
398+
return nil, errors.New("ssh: empty packet")
399+
}
400+
397401
padding := plain[0]
398402
if padding < 4 {
399403
// padding is a byte, so it automatically satisfies
@@ -710,6 +714,10 @@ func (c *chacha20Poly1305Cipher) readCipherPacket(seqNum uint32, r io.Reader) ([
710714
plain := c.buf[4:contentEnd]
711715
s.XORKeyStream(plain, plain)
712716

717+
if len(plain) == 0 {
718+
return nil, errors.New("ssh: empty packet")
719+
}
720+
713721
padding := plain[0]
714722
if padding < 4 {
715723
// padding is a byte, so it automatically satisfies

ssh/cipher_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ import (
88
"bytes"
99
"crypto"
1010
"crypto/rand"
11+
"encoding/binary"
12+
"io"
1113
"testing"
14+
15+
"golang.org/x/crypto/chacha20"
16+
"golang.org/x/crypto/internal/poly1305"
1217
)
1318

1419
func TestDefaultCiphersExist(t *testing.T) {
@@ -129,3 +134,98 @@ func TestCBCOracleCounterMeasure(t *testing.T) {
129134
lastRead = bytesRead
130135
}
131136
}
137+
138+
func TestCVE202143565(t *testing.T) {
139+
tests := []struct {
140+
cipher string
141+
constructPacket func(packetCipher) io.Reader
142+
}{
143+
{
144+
cipher: gcmCipherID,
145+
constructPacket: func(client packetCipher) io.Reader {
146+
internalCipher := client.(*gcmCipher)
147+
b := &bytes.Buffer{}
148+
prefix := [4]byte{}
149+
if _, err := b.Write(prefix[:]); err != nil {
150+
t.Fatal(err)
151+
}
152+
internalCipher.buf = internalCipher.aead.Seal(internalCipher.buf[:0], internalCipher.iv, []byte{}, prefix[:])
153+
if _, err := b.Write(internalCipher.buf); err != nil {
154+
t.Fatal(err)
155+
}
156+
internalCipher.incIV()
157+
158+
return b
159+
},
160+
},
161+
{
162+
cipher: chacha20Poly1305ID,
163+
constructPacket: func(client packetCipher) io.Reader {
164+
internalCipher := client.(*chacha20Poly1305Cipher)
165+
b := &bytes.Buffer{}
166+
167+
nonce := make([]byte, 12)
168+
s, err := chacha20.NewUnauthenticatedCipher(internalCipher.contentKey[:], nonce)
169+
if err != nil {
170+
t.Fatal(err)
171+
}
172+
var polyKey, discardBuf [32]byte
173+
s.XORKeyStream(polyKey[:], polyKey[:])
174+
s.XORKeyStream(discardBuf[:], discardBuf[:]) // skip the next 32 bytes
175+
176+
internalCipher.buf = make([]byte, 4+poly1305.TagSize)
177+
binary.BigEndian.PutUint32(internalCipher.buf, 0)
178+
ls, err := chacha20.NewUnauthenticatedCipher(internalCipher.lengthKey[:], nonce)
179+
if err != nil {
180+
t.Fatal(err)
181+
}
182+
ls.XORKeyStream(internalCipher.buf, internalCipher.buf[:4])
183+
if _, err := io.ReadFull(rand.Reader, internalCipher.buf[4:4]); err != nil {
184+
t.Fatal(err)
185+
}
186+
187+
s.XORKeyStream(internalCipher.buf[4:], internalCipher.buf[4:4])
188+
189+
var tag [poly1305.TagSize]byte
190+
poly1305.Sum(&tag, internalCipher.buf[:4], &polyKey)
191+
192+
copy(internalCipher.buf[4:], tag[:])
193+
194+
if _, err := b.Write(internalCipher.buf); err != nil {
195+
t.Fatal(err)
196+
}
197+
198+
return b
199+
},
200+
},
201+
}
202+
203+
for _, tc := range tests {
204+
mac := "hmac-sha2-256"
205+
206+
kr := &kexResult{Hash: crypto.SHA1}
207+
algs := directionAlgorithms{
208+
Cipher: tc.cipher,
209+
MAC: mac,
210+
Compression: "none",
211+
}
212+
client, err := newPacketCipher(clientKeys, algs, kr)
213+
if err != nil {
214+
t.Fatalf("newPacketCipher(client, %q, %q): %v", tc.cipher, mac, err)
215+
}
216+
server, err := newPacketCipher(clientKeys, algs, kr)
217+
if err != nil {
218+
t.Fatalf("newPacketCipher(client, %q, %q): %v", tc.cipher, mac, err)
219+
}
220+
221+
b := tc.constructPacket(client)
222+
223+
wantErr := "ssh: empty packet"
224+
_, err = server.readCipherPacket(0, b)
225+
if err == nil {
226+
t.Fatalf("readCipherPacket(%q, %q): didn't fail with empty packet", tc.cipher, mac)
227+
} else if err.Error() != wantErr {
228+
t.Fatalf("readCipherPacket(%q, %q): unexpected error, got %q, want %q", tc.cipher, mac, err, wantErr)
229+
}
230+
}
231+
}

0 commit comments

Comments
 (0)