Score: Score:CVE-2022-41715:4,CVE-2022-2880:5.3,CVE-2022-2879:6.2 Reference:https://go-review.googlesource.com/c/go/+/438501, https://go-review.googlesource.com/c/go/+/433695, https://go-review.googlesource.com/c/go/+/438500 Conflict:NA Reason: fix CVE-2022-41715,CVE-2022-2880,CVE-2022-2879 (cherry picked from commit 35fc18fe0e32f4e0889de907f6f8eb1adfe492c2)
280 lines
8.7 KiB
Diff
280 lines
8.7 KiB
Diff
From 61088cf9ed18b7b03dad384f2691a17f85fc24c5 Mon Sep 17 00:00:00 2001
|
|
From: Damien Neil <dneil@google.com>
|
|
Date: Fri, 2 Sep 2022 20:45:18 -0700
|
|
Subject: [PATCH] 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 #55925
|
|
|
|
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/+/1590622
|
|
Reviewed-by: Damien Neil <dneil@google.com>
|
|
Reviewed-by: Julie Qiu <julieqiu@google.com>
|
|
Reviewed-on: https://go-review.googlesource.com/c/go/+/438500
|
|
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
|
|
Reviewed-by: Carlos Amedee <carlos@golang.org>
|
|
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
|
|
Run-TryBot: Carlos Amedee <carlos@golang.org>
|
|
TryBot-Result: Gopher Robot <gobot@golang.org>
|
|
---
|
|
src/archive/tar/format.go | 4 +++
|
|
src/archive/tar/reader.go | 20 ++++++++---
|
|
src/archive/tar/reader_test.go | 11 +++++-
|
|
src/archive/tar/writer.go | 3 ++
|
|
src/archive/tar/writer_test.go | 27 ++++++++++++++
|
|
src/io/io.go | 64 ++++++++++++++++++++++++++++++++++
|
|
6 files changed, 123 insertions(+), 6 deletions(-)
|
|
|
|
diff --git a/src/archive/tar/format.go b/src/archive/tar/format.go
|
|
index cfe24a5..6642364 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 4f9135b..ec45ae3 100644
|
|
--- a/src/archive/tar/reader.go
|
|
+++ b/src/archive/tar/reader.go
|
|
@@ -104,7 +104,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 := ioutil.ReadAll(tr)
|
|
+ realname, err := readSpecialFile(tr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
@@ -294,7 +294,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 := ioutil.ReadAll(r)
|
|
+ buf, err := readSpecialFile(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
@@ -337,9 +337,9 @@ func parsePAX(r io.Reader) (map[string]string, error) {
|
|
// header in case further processing is required.
|
|
//
|
|
// The err will be set to io.EOF only when one of the following occurs:
|
|
-// * Exactly 0 bytes are read and EOF is hit.
|
|
-// * Exactly 1 block of zeros is read and EOF is hit.
|
|
-// * At least 2 blocks of zeros are read.
|
|
+// - Exactly 0 bytes are read and EOF is hit.
|
|
+// - Exactly 1 block of zeros is read and EOF is hit.
|
|
+// - At least 2 blocks of zeros are read.
|
|
func (tr *Reader) readHeader() (*Header, *block, error) {
|
|
// Two blocks of zero bytes marks the end of the archive.
|
|
if _, err := io.ReadFull(tr.r, tr.blk[:]); err != nil {
|
|
@@ -827,6 +827,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 f153b66..c68a859 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,
|
|
@@ -626,9 +630,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/writer.go b/src/archive/tar/writer.go
|
|
index e80498d..893eac0 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 30556d2..4bd69fd 100644
|
|
--- a/src/archive/tar/writer_test.go
|
|
+++ b/src/archive/tar/writer_test.go
|
|
@@ -1007,6 +1007,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 }
|
|
diff --git a/src/io/io.go b/src/io/io.go
|
|
index 3dea70b..f611ec9 100644
|
|
--- a/src/io/io.go
|
|
+++ b/src/io/io.go
|
|
@@ -14,6 +14,7 @@ package io
|
|
|
|
import (
|
|
"errors"
|
|
+ "sync"
|
|
)
|
|
|
|
// Seek whence values.
|
|
@@ -547,3 +548,66 @@ func (t *teeReader) Read(p []byte) (n int, err error) {
|
|
}
|
|
return
|
|
}
|
|
+
|
|
+// Discard is a Writer on which all Write calls succeed
|
|
+// without doing anything.
|
|
+var Discard Writer = discard{}
|
|
+
|
|
+type discard struct{}
|
|
+
|
|
+// discard implements ReaderFrom as an optimization so Copy to
|
|
+// io.Discard can avoid doing unnecessary work.
|
|
+var _ ReaderFrom = discard{}
|
|
+
|
|
+func (discard) Write(p []byte) (int, error) {
|
|
+ return len(p), nil
|
|
+}
|
|
+
|
|
+func (discard) WriteString(s string) (int, error) {
|
|
+ return len(s), nil
|
|
+}
|
|
+
|
|
+var blackHolePool = sync.Pool{
|
|
+ New: func() interface{} {
|
|
+ b := make([]byte, 8192)
|
|
+ return &b
|
|
+ },
|
|
+}
|
|
+
|
|
+func (discard) ReadFrom(r Reader) (n int64, err error) {
|
|
+ bufp := blackHolePool.Get().(*[]byte)
|
|
+ readSize := 0
|
|
+ for {
|
|
+ readSize, err = r.Read(*bufp)
|
|
+ n += int64(readSize)
|
|
+ if err != nil {
|
|
+ blackHolePool.Put(bufp)
|
|
+ if err == EOF {
|
|
+ return n, nil
|
|
+ }
|
|
+ return
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+// ReadAll reads from r until an error or EOF and returns the data it read.
|
|
+// A successful call returns err == nil, not err == EOF. Because ReadAll is
|
|
+// defined to read from src until EOF, it does not treat an EOF from Read
|
|
+// as an error to be reported.
|
|
+func ReadAll(r Reader) ([]byte, error) {
|
|
+ b := make([]byte, 0, 512)
|
|
+ for {
|
|
+ if len(b) == cap(b) {
|
|
+ // Add more capacity (let append pick how much).
|
|
+ b = append(b, 0)[:len(b)]
|
|
+ }
|
|
+ n, err := r.Read(b[len(b):cap(b)])
|
|
+ b = b[:len(b)+n]
|
|
+ if err != nil {
|
|
+ if err == EOF {
|
|
+ err = nil
|
|
+ }
|
|
+ return b, err
|
|
+ }
|
|
+ }
|
|
+}
|
|
--
|
|
2.33.0
|
|
|