archive/tar, archive/zip: disable insecure file name checks with GODEBUG

Add GODEBUG=tarinsecurepath=1 and GODEBUG=zipinsecurepath=1 settings
to disable file name validation.

For #55356.

Change-Id: Iaacdc629189493e7ea3537a81660215a59dd40a4
Reviewed-on: https://go-review.googlesource.com/c/go/+/452495
Reviewed-by: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Damien Neil <dneil@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
This commit is contained in:
Damien Neil 2022-11-21 11:32:39 -08:00
parent f60c77026b
commit 85a2c19b32
6 changed files with 64 additions and 1 deletions

View File

@ -293,6 +293,10 @@ proxyHandler := &httputil.ReverseProxy{
Programs that want to operate on archives containing insecure file names may Programs that want to operate on archives containing insecure file names may
ignore this error. ignore this error.
</p> </p>
<p>
Insecure tar file name checks may be entirely disabled by setting the
<code>GODEBUG=tarinsecurepath=1</code> environment variable.
</p>
</dd> </dd>
</dl><!-- archive/tar --> </dl><!-- archive/tar -->
@ -308,6 +312,10 @@ proxyHandler := &httputil.ReverseProxy{
Programs that want to operate on archives containing insecure file names may Programs that want to operate on archives containing insecure file names may
ignore this error. ignore this error.
</p> </p>
<p>
Insecure zip file name checks may be entirely disabled by setting the
<code>GODEBUG=zipinsecurepath=1</code> environment variable.
</p>
<p><!-- CL 449955 --> <p><!-- CL 449955 -->
Reading from a directory file that contains file data will now return an error. Reading from a directory file that contains file data will now return an error.
The zip specification does not permit directory files to contain file data, The zip specification does not permit directory files to contain file data,

View File

@ -13,6 +13,7 @@ package tar
import ( import (
"errors" "errors"
"fmt" "fmt"
"internal/godebug"
"io/fs" "io/fs"
"math" "math"
"path" "path"
@ -26,6 +27,8 @@ import (
// architectures. If a large value is encountered when decoding, the result // architectures. If a large value is encountered when decoding, the result
// stored in Header will be the truncated version. // stored in Header will be the truncated version.
var tarinsecurepath = godebug.New("tarinsecurepath")
var ( var (
ErrHeader = errors.New("archive/tar: invalid tar header") ErrHeader = errors.New("archive/tar: invalid tar header")
ErrWriteTooLong = errors.New("archive/tar: write too long") ErrWriteTooLong = errors.New("archive/tar: write too long")

View File

@ -60,7 +60,7 @@ func (tr *Reader) Next() (*Header, error) {
} }
hdr, err := tr.next() hdr, err := tr.next()
tr.err = err tr.err = err
if err == nil && !filepath.IsLocal(hdr.Name) { if err == nil && tarinsecurepath.Value() != "1" && !filepath.IsLocal(hdr.Name) {
err = ErrInsecurePath err = ErrInsecurePath
} }
return hdr, err return hdr, err

View File

@ -1617,6 +1617,7 @@ func TestFileReader(t *testing.T) {
} }
func TestInsecurePaths(t *testing.T) { func TestInsecurePaths(t *testing.T) {
t.Setenv("GODEBUG", "tarinsecurepath=0")
for _, path := range []string{ for _, path := range []string{
"../foo", "../foo",
"/foo", "/foo",
@ -1652,3 +1653,22 @@ func TestInsecurePaths(t *testing.T) {
} }
} }
} }
func TestDisableInsecurePathCheck(t *testing.T) {
t.Setenv("GODEBUG", "tarinsecurepath=1")
var buf bytes.Buffer
tw := NewWriter(&buf)
const name = "/foo"
tw.WriteHeader(&Header{
Name: name,
})
tw.Close()
tr := NewReader(&buf)
h, err := tr.Next()
if err != nil {
t.Fatalf("tr.Next with tarinsecurepath=1: got err %v, want nil", err)
}
if h.Name != name {
t.Fatalf("tr.Next with tarinsecurepath=1: got name %q, want %q", h.Name, name)
}
}

View File

@ -10,6 +10,7 @@ import (
"errors" "errors"
"hash" "hash"
"hash/crc32" "hash/crc32"
"internal/godebug"
"io" "io"
"io/fs" "io/fs"
"os" "os"
@ -21,6 +22,8 @@ import (
"time" "time"
) )
var zipinsecurepath = godebug.New("zipinsecurepath")
var ( var (
ErrFormat = errors.New("zip: not a valid zip file") ErrFormat = errors.New("zip: not a valid zip file")
ErrAlgorithm = errors.New("zip: unsupported compression algorithm") ErrAlgorithm = errors.New("zip: unsupported compression algorithm")
@ -108,6 +111,9 @@ func NewReader(r io.ReaderAt, size int64) (*Reader, error) {
// Zip permits an empty file name field. // Zip permits an empty file name field.
continue continue
} }
if zipinsecurepath.Value() == "1" {
continue
}
// The zip specification states that names must use forward slashes, // The zip specification states that names must use forward slashes,
// so consider any backslashes in the name insecure. // so consider any backslashes in the name insecure.
if !filepath.IsLocal(f.Name) || strings.Contains(f.Name, `\`) { if !filepath.IsLocal(f.Name) || strings.Contains(f.Name, `\`) {

View File

@ -1290,6 +1290,7 @@ func TestFSModTime(t *testing.T) {
} }
func TestCVE202127919(t *testing.T) { func TestCVE202127919(t *testing.T) {
t.Setenv("GODEBUG", "zipinsecurepath=0")
// Archive containing only the file "../test.txt" // Archive containing only the file "../test.txt"
data := []byte{ data := []byte{
0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x08, 0x00, 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x08, 0x00,
@ -1411,6 +1412,7 @@ func TestCVE202139293(t *testing.T) {
} }
func TestCVE202141772(t *testing.T) { func TestCVE202141772(t *testing.T) {
t.Setenv("GODEBUG", "zipinsecurepath=0")
// Archive contains a file whose name is exclusively made up of '/', '\' // Archive contains a file whose name is exclusively made up of '/', '\'
// characters, or "../", "..\" paths, which would previously cause a panic. // characters, or "../", "..\" paths, which would previously cause a panic.
// //
@ -1586,6 +1588,7 @@ func TestIssue54801(t *testing.T) {
} }
func TestInsecurePaths(t *testing.T) { func TestInsecurePaths(t *testing.T) {
t.Setenv("GODEBUG", "zipinsecurepath=0")
for _, path := range []string{ for _, path := range []string{
"../foo", "../foo",
"/foo", "/foo",
@ -1616,3 +1619,26 @@ func TestInsecurePaths(t *testing.T) {
} }
} }
} }
func TestDisableInsecurePathCheck(t *testing.T) {
t.Setenv("GODEBUG", "zipinsecurepath=1")
var buf bytes.Buffer
zw := NewWriter(&buf)
const name = "/foo"
_, err := zw.Create(name)
if err != nil {
t.Fatalf("zw.Create(%q) = %v", name, err)
}
zw.Close()
zr, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
if err != nil {
t.Fatalf("NewReader with zipinsecurepath=1: got err %v, want nil", err)
}
var gotPaths []string
for _, f := range zr.File {
gotPaths = append(gotPaths, f.Name)
}
if want := []string{name}; !reflect.DeepEqual(gotPaths, want) {
t.Errorf("NewReader with zipinsecurepath=1: got files %q, want %q", gotPaths, want)
}
}