]> Cypherpunks repositories - gostls13.git/commitdiff
archive/tar: limit size of headers
authorDamien Neil <dneil@google.com>
Sat, 3 Sep 2022 03:45:18 +0000 (20:45 -0700)
committerGopher Robot <gobot@golang.org>
Wed, 5 Oct 2022 20:40:43 +0000 (20:40 +0000)
Set a 1MiB limit on special file blocks (PAX headers, GNU long names,
GNU link names), to avoid reading arbitrarily large amounts of data
into memory.

Thanks to Adam Korczynski (ADA Logics) and OSS-Fuzz for reporting
this issue.

Fixes CVE-2022-2879
For #54853

Change-Id: I85136d6ff1e0af101a112190e027987ab4335680
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1565555
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
Run-TryBot: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/439355
Reviewed-by: Damien Neil <dneil@google.com>
Run-TryBot: Roland Shoemaker <roland@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Roland Shoemaker <roland@golang.org>

src/archive/tar/format.go
src/archive/tar/reader.go
src/archive/tar/reader_test.go
src/archive/tar/testdata/pax-bad-hdr-large.tar.bz2 [new file with mode: 0644]
src/archive/tar/writer.go
src/archive/tar/writer_test.go

index 21b9d9d4dbc628f7f18a370d7bb10f597b0efc70..8898c438b513e0ebfd628348f21ee77b0a129b53 100644 (file)
@@ -143,6 +143,10 @@ const (
        blockSize  = 512 // Size of each block in a tar stream
        nameSize   = 100 // Max length of the name field in USTAR format
        prefixSize = 155 // Max length of the prefix field in USTAR format
+
+       // Max length of a special file (PAX header, GNU long name or link).
+       // This matches the limit used by libarchive.
+       maxSpecialFileSize = 1 << 20
 )
 
 // blockPadding computes the number of bytes needed to pad offset up to the
index f1b35c34f6f25a8c6e46bc18d82f288dc39c7aa5..45848304ed9d0b300459f06f337c93b3e4bb6352 100644 (file)
@@ -103,7 +103,7 @@ func (tr *Reader) next() (*Header, error) {
                        continue // This is a meta header affecting the next header
                case TypeGNULongName, TypeGNULongLink:
                        format.mayOnlyBe(FormatGNU)
-                       realname, err := io.ReadAll(tr)
+                       realname, err := readSpecialFile(tr)
                        if err != nil {
                                return nil, err
                        }
@@ -293,7 +293,7 @@ func mergePAX(hdr *Header, paxHdrs map[string]string) (err error) {
 // parsePAX parses PAX headers.
 // If an extended header (type 'x') is invalid, ErrHeader is returned
 func parsePAX(r io.Reader) (map[string]string, error) {
-       buf, err := io.ReadAll(r)
+       buf, err := readSpecialFile(r)
        if err != nil {
                return nil, err
        }
@@ -828,6 +828,16 @@ func tryReadFull(r io.Reader, b []byte) (n int, err error) {
        return n, err
 }
 
+// readSpecialFile is like io.ReadAll except it returns
+// ErrFieldTooLong if more than maxSpecialFileSize is read.
+func readSpecialFile(r io.Reader) ([]byte, error) {
+       buf, err := io.ReadAll(io.LimitReader(r, maxSpecialFileSize+1))
+       if len(buf) > maxSpecialFileSize {
+               return nil, ErrFieldTooLong
+       }
+       return buf, err
+}
+
 // discard skips n bytes in r, reporting an error if unable to do so.
 func discard(r io.Reader, n int64) error {
        // If possible, Seek to the last byte before the end of the data section.
index a8e9b929d74750a2624ab8dcca007eb6628ade7c..247030da57dde3a6a7ffbf385ce295dd24b8d59a 100644 (file)
@@ -6,6 +6,7 @@ package tar
 
 import (
        "bytes"
+       "compress/bzip2"
        "crypto/md5"
        "errors"
        "fmt"
@@ -243,6 +244,9 @@ func TestReader(t *testing.T) {
        }, {
                file: "testdata/pax-bad-hdr-file.tar",
                err:  ErrHeader,
+       }, {
+               file: "testdata/pax-bad-hdr-large.tar.bz2",
+               err:  ErrFieldTooLong,
        }, {
                file: "testdata/pax-bad-mtime-file.tar",
                err:  ErrHeader,
@@ -625,9 +629,14 @@ func TestReader(t *testing.T) {
                        }
                        defer f.Close()
 
+                       var fr io.Reader = f
+                       if strings.HasSuffix(v.file, ".bz2") {
+                               fr = bzip2.NewReader(fr)
+                       }
+
                        // Capture all headers and checksums.
                        var (
-                               tr      = NewReader(f)
+                               tr      = NewReader(fr)
                                hdrs    []*Header
                                chksums []string
                                rdbuf   = make([]byte, 8)
diff --git a/src/archive/tar/testdata/pax-bad-hdr-large.tar.bz2 b/src/archive/tar/testdata/pax-bad-hdr-large.tar.bz2
new file mode 100644 (file)
index 0000000..06bf710
Binary files /dev/null and b/src/archive/tar/testdata/pax-bad-hdr-large.tar.bz2 differ
index 3729f7e82c192f685e43398a88d2777bde5c17e0..9b2e3e25d4ceb10920da917a0c711d679d7c7b4e 100644 (file)
@@ -199,6 +199,9 @@ func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error {
                        flag = TypeXHeader
                }
                data := buf.String()
+               if len(data) > maxSpecialFileSize {
+                       return ErrFieldTooLong
+               }
                if err := tw.writeRawFile(name, data, flag, FormatPAX); err != nil || isGlobal {
                        return err // Global headers return here
                }
index 48f35e5133cf78bb4066bf0a56d5d17b07808898..32af16e20f62c74db70798b7a03ba960fc66a617 100644 (file)
@@ -1004,6 +1004,33 @@ func TestIssue12594(t *testing.T) {
        }
 }
 
+func TestWriteLongHeader(t *testing.T) {
+       for _, test := range []struct {
+               name string
+               h    *Header
+       }{{
+               name: "name too long",
+               h:    &Header{Name: strings.Repeat("a", maxSpecialFileSize)},
+       }, {
+               name: "linkname too long",
+               h:    &Header{Linkname: strings.Repeat("a", maxSpecialFileSize)},
+       }, {
+               name: "uname too long",
+               h:    &Header{Uname: strings.Repeat("a", maxSpecialFileSize)},
+       }, {
+               name: "gname too long",
+               h:    &Header{Gname: strings.Repeat("a", maxSpecialFileSize)},
+       }, {
+               name: "PAX header too long",
+               h:    &Header{PAXRecords: map[string]string{"GOLANG.x": strings.Repeat("a", maxSpecialFileSize)}},
+       }} {
+               w := NewWriter(io.Discard)
+               if err := w.WriteHeader(test.h); err != ErrFieldTooLong {
+                       t.Errorf("%v: w.WriteHeader() = %v, want ErrFieldTooLong", test.name, err)
+               }
+       }
+}
+
 // testNonEmptyWriter wraps an io.Writer and ensures that
 // Write is never called with an empty buffer.
 type testNonEmptyWriter struct{ io.Writer }