Skip to content

Commit

Permalink
compress/gzip: fix stack exhaustion bug in Reader.Read
Browse files Browse the repository at this point in the history
Replace recursion with iteration in Reader.Read to avoid stack
exhaustion when there are a large number of files.

Fixes CVE-2022-30631
Fixes golang#53168

Change-Id: I47d8afe3f2d40b0213ab61431df9b221794dbfe0
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1455673
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Julie Qiu <julieqiu@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/417067
Reviewed-by: Heschi Kreinick <heschi@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
  • Loading branch information
Tatiana Bradley authored and jproberts committed Aug 10, 2022
1 parent 366c776 commit 5d7d53b
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 31 deletions.
60 changes: 29 additions & 31 deletions src/compress/gzip/gunzip.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,42 +248,40 @@ func (z *Reader) Read(p []byte) (n int, err error) {
return 0, z.err
}

n, z.err = z.decompressor.Read(p)
z.digest = crc32.Update(z.digest, crc32.IEEETable, p[:n])
z.size += uint32(n)
if z.err != io.EOF {
// In the normal case we return here.
return n, z.err
}
for n == 0 {
n, z.err = z.decompressor.Read(p)
z.digest = crc32.Update(z.digest, crc32.IEEETable, p[:n])
z.size += uint32(n)
if z.err != io.EOF {
// In the normal case we return here.
return n, z.err
}

// Finished file; check checksum and size.
if _, err := io.ReadFull(z.r, z.buf[:8]); err != nil {
z.err = noEOF(err)
return n, z.err
}
digest := le.Uint32(z.buf[:4])
size := le.Uint32(z.buf[4:8])
if digest != z.digest || size != z.size {
z.err = ErrChecksum
return n, z.err
}
z.digest, z.size = 0, 0
// Finished file; check checksum and size.
if _, err := io.ReadFull(z.r, z.buf[:8]); err != nil {
z.err = noEOF(err)
return n, z.err
}
digest := le.Uint32(z.buf[:4])
size := le.Uint32(z.buf[4:8])
if digest != z.digest || size != z.size {
z.err = ErrChecksum
return n, z.err
}
z.digest, z.size = 0, 0

// File is ok; check if there is another.
if !z.multistream {
return n, io.EOF
}
z.err = nil // Remove io.EOF
// File is ok; check if there is another.
if !z.multistream {
return n, io.EOF
}
z.err = nil // Remove io.EOF

if _, z.err = z.readHeader(); z.err != nil {
return n, z.err
if _, z.err = z.readHeader(); z.err != nil {
return n, z.err
}
}

// Read from next file, if necessary.
if n > 0 {
return n, nil
}
return z.Read(p)
return n, nil
}

// Close closes the Reader. It does not close the underlying io.Reader.
Expand Down
16 changes: 16 additions & 0 deletions src/compress/gzip/gunzip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,3 +569,19 @@ func TestTruncatedStreams(t *testing.T) {
}
}
}

func TestCVE202230631(t *testing.T) {
var empty = []byte{0x1f, 0x8b, 0x08, 0x00, 0xa7, 0x8f, 0x43, 0x62, 0x00,
0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
r := bytes.NewReader(bytes.Repeat(empty, 4e6))
z, err := NewReader(r)
if err != nil {
t.Fatalf("NewReader: got %v, want nil", err)
}
// Prior to CVE-2022-30631 fix, this would cause an unrecoverable panic due
// to stack exhaustion.
_, err = z.Read(make([]byte, 10))
if err != io.EOF {
t.Errorf("Reader.Read: got %v, want %v", err, io.EOF)
}
}

0 comments on commit 5d7d53b

Please sign in to comment.