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>
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
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
}
// 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
}
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.
import (
"bytes"
+ "compress/bzip2"
"crypto/md5"
"errors"
"fmt"
}, {
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,
}
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)
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
}
}
}
+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 }