aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 4b11909bc9..e609c15f27 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 }