From 0117dee7dccbbd7803d88f65a2ce8bd686219ad3 Mon Sep 17 00:00:00 2001 From: Tatiana Bradley Date: Fri, 6 May 2022 11:25:06 -0400 Subject: [release-branch.go1.17] compress/gzip: fix stack exhaustion bug in Reader.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 Reviewed-by: Julie Qiu (cherry picked from commit cf498969c8a0bae9d7a24b98fc1f66c824a4775d) Reviewed-on: https://go-review.googlesource.com/c/go/+/417071 Reviewed-by: Heschi Kreinick Run-TryBot: Michael Knyszek TryBot-Result: Gopher Robot --- src/compress/gzip/gunzip.go | 60 +++++++++++++++++++--------------------- src/compress/gzip/gunzip_test.go | 16 +++++++++++ 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/compress/gzip/gunzip.go b/src/compress/gzip/gunzip.go index 924bce10b7c..237b2b928bf 100644 --- a/src/compress/gzip/gunzip.go +++ b/src/compress/gzip/gunzip.go @@ -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. diff --git a/src/compress/gzip/gunzip_test.go b/src/compress/gzip/gunzip_test.go index 17c23e8a9be..6fe8ddcf558 100644 --- a/src/compress/gzip/gunzip_test.go +++ b/src/compress/gzip/gunzip_test.go @@ -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) + } +} -- cgit v1.2.3-54-g00ecf