Skip to content

Commit

Permalink
[release-branch.go1.17] compress/gzip: fix stack exhaustion bug in Re…
Browse files Browse the repository at this point in the history
…ader.Read

Replace recursion with iteration in Reader.Read to avoid stack
exhaustion when there are a large number of files.

Fixes CVE-2022-30631
Fixes #53717
Updates #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>
(cherry picked from commit cf498969c8a0bae9d7a24b98fc1f66c824a4775d)
Reviewed-on: https://go-review.googlesource.com/c/go/+/417071
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 mknyszek committed Jul 12, 2022
1 parent ba8788e commit 0117dee
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 @@ -515,3 +515,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 0117dee

Please sign in to comment.