src/archive/tar/format.go | 4 ++++ src/archive/tar/reader.go | 14 ++++++++++++-- src/archive/tar/reader_test.go | 11 ++++++++++- src/archive/tar/writer.go | 3 +++ src/archive/tar/writer_test.go | 27 +++++++++++++++++++++++++++ diff --git a/src/archive/tar/format.go b/src/archive/tar/format.go index 21b9d9d4dbc628f7f18a370d7bb10f597b0efc70..8898c438b513e0ebfd628348f21ee77b0a129b53 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 4b11909bc9527d37af905cba8c33f495b83893ca..e609c15f27af73b0f41d1ed75f9945205db20d8d 100644 --- a/src/archive/tar/reader.go +++ b/src/archive/tar/reader.go @@ -103,7 +103,7 @@ } 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 @@ // 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 } @@ -826,6 +826,16 @@ if len(b) == n && err == io.EOF { err = nil } 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. diff --git a/src/archive/tar/reader_test.go b/src/archive/tar/reader_test.go index f21a6065b485706f3e2c2825f1fd8c04472fa275..140c736429120cdfc0648ff9c6b92552ba43a70b 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" @@ -243,6 +244,9 @@ }}, }, { 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 @@ t.Fatalf("unexpected error: %v", err) } 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 0000000000000000000000000000000000000000..06bf710d3ae4e96a27487124f08e30c9e318699d Binary files /dev/null and b/src/archive/tar/testdata/pax-bad-hdr-large.tar.bz2 differ diff --git a/src/archive/tar/writer.go b/src/archive/tar/writer.go index 3729f7e82c192f685e43398a88d2777bde5c17e0..9b2e3e25d4ceb10920da917a0c711d679d7c7b4e 100644 --- a/src/archive/tar/writer.go +++ b/src/archive/tar/writer.go @@ -199,6 +199,9 @@ name = path.Join(dir, "PaxHeaders.0", file) 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 da3fb89e65e51ecd17627d444af95b68e54f4596..640264984a96e5530591af4b4d0ebcb1e17b1cf1 100644 --- a/src/archive/tar/writer_test.go +++ b/src/archive/tar/writer_test.go @@ -1004,6 +1004,33 @@ } } } +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 }