aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2022-09-02 20:45:18 -0700
committerCarlos Amedee <carlos@golang.org>2022-10-04 17:07:45 +0000
commit4fa773cdefd20be093c84f731be7d4febf5536fa (patch)
treea1c0f4cd5845220dd366c39a745cc968e094d35e
parentf6d844510d5f1e3b3098eba255d9b633d45eac3b (diff)
downloadgo-4fa773cdefd20be093c84f731be7d4febf5536fa.tar.gz
go-4fa773cdefd20be093c84f731be7d4febf5536fa.zip
[release-branch.go1.19] archive/tar: limit size of headers
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 Updates #54853 Fixes #55926 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> (cherry picked from commit 6ee768cef6b82adf7a90dcf367a1699ef694f3b2) Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1591053 Reviewed-by: Julie Qiu <julieqiu@google.com> Reviewed-by: Damien Neil <dneil@google.com> Reviewed-on: https://go-review.googlesource.com/c/go/+/438498 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Reviewed-by: Carlos Amedee <carlos@golang.org> Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org> Run-TryBot: Carlos Amedee <carlos@golang.org>
-rw-r--r--src/archive/tar/format.go4
-rw-r--r--src/archive/tar/reader.go14
-rw-r--r--src/archive/tar/reader_test.go11
-rw-r--r--src/archive/tar/testdata/pax-bad-hdr-large.tar.bz2bin0 -> 156 bytes
-rw-r--r--src/archive/tar/writer.go3
-rw-r--r--src/archive/tar/writer_test.go27
6 files changed, 56 insertions, 3 deletions
diff --git a/src/archive/tar/format.go b/src/archive/tar/format.go
index 21b9d9d4db..8898c438b5 100644
--- a/src/archive/tar/format.go
+++ b/src/archive/tar/format.go
@@ -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
diff --git a/src/archive/tar/reader.go b/src/archive/tar/reader.go
index f1b35c34f6..45848304ed 100644
--- a/src/archive/tar/reader.go
+++ b/src/archive/tar/reader.go
@@ -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.
diff --git a/src/archive/tar/reader_test.go b/src/archive/tar/reader_test.go
index f21a6065b4..140c736429 100644
--- a/src/archive/tar/reader_test.go
+++ b/src/archive/tar/reader_test.go
@@ -6,6 +6,7 @@ package tar
import (
"bytes"
+ "compress/bzip2"
"crypto/md5"
"errors"
"fmt"
@@ -244,6 +245,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
index 0000000000..06bf710d3a
--- /dev/null
+++ b/src/archive/tar/testdata/pax-bad-hdr-large.tar.bz2
Binary files differ
diff --git a/src/archive/tar/writer.go b/src/archive/tar/writer.go
index 3729f7e82c..9b2e3e25d4 100644
--- a/src/archive/tar/writer.go
+++ b/src/archive/tar/writer.go
@@ -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
}
diff --git a/src/archive/tar/writer_test.go b/src/archive/tar/writer_test.go
index da3fb89e65..640264984a 100644
--- a/src/archive/tar/writer_test.go
+++ b/src/archive/tar/writer_test.go
@@ -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 }