1From d064ed520a7cc6b480f9565e30751e695d394f4e Mon Sep 17 00:00:00 2001 2From: Damien Neil <dneil@google.com> 3Date: Fri, 2 Sep 2022 20:45:18 -0700 4Subject: [PATCH] archive/tar: limit size of headers 5 6Set a 1MiB limit on special file blocks (PAX headers, GNU long names, 7GNU link names), to avoid reading arbitrarily large amounts of data 8into memory. 9 10Thanks to Adam Korczynski (ADA Logics) and OSS-Fuzz for reporting 11this issue. 12 13Fixes CVE-2022-2879 14Updates #54853 15Fixes #55925 16 17Change-Id: I85136d6ff1e0af101a112190e027987ab4335680 18Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1565555 19Reviewed-by: Tatiana Bradley <tatianabradley@google.com> 20Run-TryBot: Roland Shoemaker <bracewell@google.com> 21Reviewed-by: Roland Shoemaker <bracewell@google.com> 22(cherry picked from commit 6ee768cef6b82adf7a90dcf367a1699ef694f3b2) 23Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1590622 24Reviewed-by: Damien Neil <dneil@google.com> 25Reviewed-by: Julie Qiu <julieqiu@google.com> 26Reviewed-on: https://go-review.googlesource.com/c/go/+/438500 27Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org> 28Reviewed-by: Carlos Amedee <carlos@golang.org> 29Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> 30Run-TryBot: Carlos Amedee <carlos@golang.org> 31TryBot-Result: Gopher Robot <gobot@golang.org> 32 33CVE: CVE-2022-2879 34Upstream-Status: Backport [0a723816cd205576945fa57fbdde7e6532d59d08] 35Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com> 36--- 37 src/archive/tar/format.go | 4 ++++ 38 src/archive/tar/reader.go | 14 ++++++++++++-- 39 src/archive/tar/reader_test.go | 8 +++++++- 40 src/archive/tar/writer.go | 3 +++ 41 src/archive/tar/writer_test.go | 27 +++++++++++++++++++++++++++ 42 5 files changed, 53 insertions(+), 3 deletions(-) 43 44diff --git a/src/archive/tar/format.go b/src/archive/tar/format.go 45index cfe24a5..6642364 100644 46--- a/src/archive/tar/format.go 47+++ b/src/archive/tar/format.go 48@@ -143,6 +143,10 @@ const ( 49 blockSize = 512 // Size of each block in a tar stream 50 nameSize = 100 // Max length of the name field in USTAR format 51 prefixSize = 155 // Max length of the prefix field in USTAR format 52+ 53+ // Max length of a special file (PAX header, GNU long name or link). 54+ // This matches the limit used by libarchive. 55+ maxSpecialFileSize = 1 << 20 56 ) 57 58 // blockPadding computes the number of bytes needed to pad offset up to the 59diff --git a/src/archive/tar/reader.go b/src/archive/tar/reader.go 60index 1b1d5b4..f645af8 100644 61--- a/src/archive/tar/reader.go 62+++ b/src/archive/tar/reader.go 63@@ -103,7 +103,7 @@ func (tr *Reader) next() (*Header, error) { 64 continue // This is a meta header affecting the next header 65 case TypeGNULongName, TypeGNULongLink: 66 format.mayOnlyBe(FormatGNU) 67- realname, err := io.ReadAll(tr) 68+ realname, err := readSpecialFile(tr) 69 if err != nil { 70 return nil, err 71 } 72@@ -293,7 +293,7 @@ func mergePAX(hdr *Header, paxHdrs map[string]string) (err error) { 73 // parsePAX parses PAX headers. 74 // If an extended header (type 'x') is invalid, ErrHeader is returned 75 func parsePAX(r io.Reader) (map[string]string, error) { 76- buf, err := io.ReadAll(r) 77+ buf, err := readSpecialFile(r) 78 if err != nil { 79 return nil, err 80 } 81@@ -826,6 +826,16 @@ func tryReadFull(r io.Reader, b []byte) (n int, err error) { 82 return n, err 83 } 84 85+// readSpecialFile is like io.ReadAll except it returns 86+// ErrFieldTooLong if more than maxSpecialFileSize is read. 87+func readSpecialFile(r io.Reader) ([]byte, error) { 88+ buf, err := io.ReadAll(io.LimitReader(r, maxSpecialFileSize+1)) 89+ if len(buf) > maxSpecialFileSize { 90+ return nil, ErrFieldTooLong 91+ } 92+ return buf, err 93+} 94+ 95 // discard skips n bytes in r, reporting an error if unable to do so. 96 func discard(r io.Reader, n int64) error { 97 // If possible, Seek to the last byte before the end of the data section. 98diff --git a/src/archive/tar/reader_test.go b/src/archive/tar/reader_test.go 99index 789ddc1..926dc3d 100644 100--- a/src/archive/tar/reader_test.go 101+++ b/src/archive/tar/reader_test.go 102@@ -6,6 +6,7 @@ package tar 103 104 import ( 105 "bytes" 106+ "compress/bzip2" 107 "crypto/md5" 108 "errors" 109 "fmt" 110@@ -625,9 +626,14 @@ func TestReader(t *testing.T) { 111 } 112 defer f.Close() 113 114+ var fr io.Reader = f 115+ if strings.HasSuffix(v.file, ".bz2") { 116+ fr = bzip2.NewReader(fr) 117+ } 118+ 119 // Capture all headers and checksums. 120 var ( 121- tr = NewReader(f) 122+ tr = NewReader(fr) 123 hdrs []*Header 124 chksums []string 125 rdbuf = make([]byte, 8) 126diff --git a/src/archive/tar/writer.go b/src/archive/tar/writer.go 127index e80498d..893eac0 100644 128--- a/src/archive/tar/writer.go 129+++ b/src/archive/tar/writer.go 130@@ -199,6 +199,9 @@ func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error { 131 flag = TypeXHeader 132 } 133 data := buf.String() 134+ if len(data) > maxSpecialFileSize { 135+ return ErrFieldTooLong 136+ } 137 if err := tw.writeRawFile(name, data, flag, FormatPAX); err != nil || isGlobal { 138 return err // Global headers return here 139 } 140diff --git a/src/archive/tar/writer_test.go b/src/archive/tar/writer_test.go 141index a00f02d..4e709e5 100644 142--- a/src/archive/tar/writer_test.go 143+++ b/src/archive/tar/writer_test.go 144@@ -1006,6 +1006,33 @@ func TestIssue12594(t *testing.T) { 145 } 146 } 147 148+func TestWriteLongHeader(t *testing.T) { 149+ for _, test := range []struct { 150+ name string 151+ h *Header 152+ }{{ 153+ name: "name too long", 154+ h: &Header{Name: strings.Repeat("a", maxSpecialFileSize)}, 155+ }, { 156+ name: "linkname too long", 157+ h: &Header{Linkname: strings.Repeat("a", maxSpecialFileSize)}, 158+ }, { 159+ name: "uname too long", 160+ h: &Header{Uname: strings.Repeat("a", maxSpecialFileSize)}, 161+ }, { 162+ name: "gname too long", 163+ h: &Header{Gname: strings.Repeat("a", maxSpecialFileSize)}, 164+ }, { 165+ name: "PAX header too long", 166+ h: &Header{PAXRecords: map[string]string{"GOLANG.x": strings.Repeat("a", maxSpecialFileSize)}}, 167+ }} { 168+ w := NewWriter(io.Discard) 169+ if err := w.WriteHeader(test.h); err != ErrFieldTooLong { 170+ t.Errorf("%v: w.WriteHeader() = %v, want ErrFieldTooLong", test.name, err) 171+ } 172+ } 173+} 174+ 175 // testNonEmptyWriter wraps an io.Writer and ensures that 176 // Write is never called with an empty buffer. 177 type testNonEmptyWriter struct{ io.Writer } 178