forked from mirrors/tar-split
archive/tar: fix bugs with sparseFileReader
The sparseFileReader is prone to two different forms of denial-of-service attacks: * A malicious tar file can cause an infinite loop * A malicious tar file can cause arbitrary panics This results because of poor error checking/handling, which this CL fixes. While we are at it, add a plethora of unit tests to test for possible malicious inputs. Change-Id: I2f9446539d189f3c1738a1608b0ad4859c1be929 Reviewed-on: https://go-review.googlesource.com/15115 Reviewed-by: Andrew Gerrand <adg@golang.org> Run-TryBot: Andrew Gerrand <adg@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
440ba9e519
commit
af15385a0d
2 changed files with 258 additions and 127 deletions
|
@ -10,6 +10,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
@ -560,80 +561,155 @@ func TestSparseEndToEnd(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type sparseFileReadTest struct {
|
||||
sparseData []byte
|
||||
sparseMap []sparseEntry
|
||||
realSize int64
|
||||
expected []byte
|
||||
}
|
||||
|
||||
var sparseFileReadTests = []sparseFileReadTest{
|
||||
{
|
||||
sparseData: []byte("abcde"),
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 0, numBytes: 2},
|
||||
{offset: 5, numBytes: 3},
|
||||
},
|
||||
realSize: 8,
|
||||
expected: []byte("ab\x00\x00\x00cde"),
|
||||
},
|
||||
{
|
||||
sparseData: []byte("abcde"),
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 0, numBytes: 2},
|
||||
{offset: 5, numBytes: 3},
|
||||
},
|
||||
realSize: 10,
|
||||
expected: []byte("ab\x00\x00\x00cde\x00\x00"),
|
||||
},
|
||||
{
|
||||
sparseData: []byte("abcde"),
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 1, numBytes: 3},
|
||||
{offset: 6, numBytes: 2},
|
||||
},
|
||||
realSize: 8,
|
||||
expected: []byte("\x00abc\x00\x00de"),
|
||||
},
|
||||
{
|
||||
sparseData: []byte("abcde"),
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 1, numBytes: 3},
|
||||
{offset: 6, numBytes: 2},
|
||||
},
|
||||
realSize: 10,
|
||||
expected: []byte("\x00abc\x00\x00de\x00\x00"),
|
||||
},
|
||||
{
|
||||
sparseData: []byte(""),
|
||||
sparseMap: nil,
|
||||
realSize: 2,
|
||||
expected: []byte("\x00\x00"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestSparseFileReader(t *testing.T) {
|
||||
for i, test := range sparseFileReadTests {
|
||||
r := bytes.NewReader(test.sparseData)
|
||||
nb := int64(r.Len())
|
||||
sfr := &sparseFileReader{
|
||||
rfr: ®FileReader{r: r, nb: nb},
|
||||
sp: test.sparseMap,
|
||||
pos: 0,
|
||||
tot: test.realSize,
|
||||
}
|
||||
if sfr.numBytes() != nb {
|
||||
t.Errorf("test %d: Before reading, sfr.numBytes() = %d, want %d", i, sfr.numBytes(), nb)
|
||||
}
|
||||
buf, err := ioutil.ReadAll(sfr)
|
||||
var vectors = []struct {
|
||||
realSize int64 // Real size of the output file
|
||||
sparseMap []sparseEntry // Input sparse map
|
||||
sparseData string // Input compact data
|
||||
expected string // Expected output data
|
||||
err error // Expected error outcome
|
||||
}{{
|
||||
realSize: 8,
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 0, numBytes: 2},
|
||||
{offset: 5, numBytes: 3},
|
||||
},
|
||||
sparseData: "abcde",
|
||||
expected: "ab\x00\x00\x00cde",
|
||||
}, {
|
||||
realSize: 10,
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 0, numBytes: 2},
|
||||
{offset: 5, numBytes: 3},
|
||||
},
|
||||
sparseData: "abcde",
|
||||
expected: "ab\x00\x00\x00cde\x00\x00",
|
||||
}, {
|
||||
realSize: 8,
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 1, numBytes: 3},
|
||||
{offset: 6, numBytes: 2},
|
||||
},
|
||||
sparseData: "abcde",
|
||||
expected: "\x00abc\x00\x00de",
|
||||
}, {
|
||||
realSize: 8,
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 1, numBytes: 3},
|
||||
{offset: 6, numBytes: 0},
|
||||
{offset: 6, numBytes: 0},
|
||||
{offset: 6, numBytes: 2},
|
||||
},
|
||||
sparseData: "abcde",
|
||||
expected: "\x00abc\x00\x00de",
|
||||
}, {
|
||||
realSize: 10,
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 1, numBytes: 3},
|
||||
{offset: 6, numBytes: 2},
|
||||
},
|
||||
sparseData: "abcde",
|
||||
expected: "\x00abc\x00\x00de\x00\x00",
|
||||
}, {
|
||||
realSize: 10,
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 1, numBytes: 3},
|
||||
{offset: 6, numBytes: 2},
|
||||
{offset: 8, numBytes: 0},
|
||||
{offset: 8, numBytes: 0},
|
||||
{offset: 8, numBytes: 0},
|
||||
{offset: 8, numBytes: 0},
|
||||
},
|
||||
sparseData: "abcde",
|
||||
expected: "\x00abc\x00\x00de\x00\x00",
|
||||
}, {
|
||||
realSize: 2,
|
||||
sparseMap: []sparseEntry{},
|
||||
sparseData: "",
|
||||
expected: "\x00\x00",
|
||||
}, {
|
||||
realSize: -2,
|
||||
sparseMap: []sparseEntry{},
|
||||
err: ErrHeader,
|
||||
}, {
|
||||
realSize: -10,
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 1, numBytes: 3},
|
||||
{offset: 6, numBytes: 2},
|
||||
},
|
||||
sparseData: "abcde",
|
||||
err: ErrHeader,
|
||||
}, {
|
||||
realSize: 10,
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 1, numBytes: 3},
|
||||
{offset: 6, numBytes: 5},
|
||||
},
|
||||
sparseData: "abcde",
|
||||
err: ErrHeader,
|
||||
}, {
|
||||
realSize: 35,
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 1, numBytes: 3},
|
||||
{offset: 6, numBytes: 5},
|
||||
},
|
||||
sparseData: "abcde",
|
||||
err: io.ErrUnexpectedEOF,
|
||||
}, {
|
||||
realSize: 35,
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 1, numBytes: 3},
|
||||
{offset: 6, numBytes: -5},
|
||||
},
|
||||
sparseData: "abcde",
|
||||
err: ErrHeader,
|
||||
}, {
|
||||
realSize: 35,
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: math.MaxInt64, numBytes: 3},
|
||||
{offset: 6, numBytes: -5},
|
||||
},
|
||||
sparseData: "abcde",
|
||||
err: ErrHeader,
|
||||
}, {
|
||||
realSize: 10,
|
||||
sparseMap: []sparseEntry{
|
||||
{offset: 1, numBytes: 3},
|
||||
{offset: 2, numBytes: 2},
|
||||
},
|
||||
sparseData: "abcde",
|
||||
err: ErrHeader,
|
||||
}}
|
||||
|
||||
for i, v := range vectors {
|
||||
r := bytes.NewReader([]byte(v.sparseData))
|
||||
rfr := ®FileReader{r: r, nb: int64(len(v.sparseData))}
|
||||
|
||||
var sfr *sparseFileReader
|
||||
var err error
|
||||
var buf []byte
|
||||
|
||||
sfr, err = newSparseFileReader(rfr, v.sparseMap, v.realSize)
|
||||
if err != nil {
|
||||
t.Errorf("test %d: Unexpected error: %v", i, err)
|
||||
goto fail
|
||||
}
|
||||
if e := test.expected; !bytes.Equal(buf, e) {
|
||||
t.Errorf("test %d: Contents = %v, want %v", i, buf, e)
|
||||
if sfr.numBytes() != int64(len(v.sparseData)) {
|
||||
t.Errorf("test %d, numBytes() before reading: got %d, want %d", i, sfr.numBytes(), len(v.sparseData))
|
||||
}
|
||||
buf, err = ioutil.ReadAll(sfr)
|
||||
if err != nil {
|
||||
goto fail
|
||||
}
|
||||
if string(buf) != v.expected {
|
||||
t.Errorf("test %d, ReadAll(): got %q, want %q", i, string(buf), v.expected)
|
||||
}
|
||||
if sfr.numBytes() != 0 {
|
||||
t.Errorf("test %d: After draining the reader, numBytes() was nonzero", i)
|
||||
t.Errorf("test %d, numBytes() after reading: got %d, want %d", i, sfr.numBytes(), 0)
|
||||
}
|
||||
|
||||
fail:
|
||||
if err != v.err {
|
||||
t.Errorf("test %d, unexpected error: got %v, want %v", i, err, v.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -646,10 +722,10 @@ func TestSparseIncrementalRead(t *testing.T) {
|
|||
r := bytes.NewReader(sparseData)
|
||||
nb := int64(r.Len())
|
||||
sfr := &sparseFileReader{
|
||||
rfr: ®FileReader{r: r, nb: nb},
|
||||
sp: sparseMap,
|
||||
pos: 0,
|
||||
tot: int64(len(expected)),
|
||||
rfr: ®FileReader{r: r, nb: nb},
|
||||
sp: sparseMap,
|
||||
pos: 0,
|
||||
total: int64(len(expected)),
|
||||
}
|
||||
|
||||
// We'll read the data 6 bytes at a time, with a hole of size 10 at
|
||||
|
@ -747,6 +823,11 @@ func TestUninitializedRead(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
// TODO(dsnet): TestNegativeHdrSize, TestIssue10968, and TestIssue11169 tests
|
||||
// that Reader properly handles corrupted tar files. Given the increasing number
|
||||
// of invalid/malicious that can crash Reader, we should modify TestReader to
|
||||
// be able to test that intentionally corrupt tar files don't succeed or crash.
|
||||
|
||||
// Negative header size should not cause panic.
|
||||
// Issues 10959 and 10960.
|
||||
func TestNegativeHdrSize(t *testing.T) {
|
||||
|
@ -771,14 +852,11 @@ func TestIssue10968(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
r := NewReader(f)
|
||||
_, err = r.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = io.Copy(ioutil.Discard, r)
|
||||
if err != io.ErrUnexpectedEOF {
|
||||
t.Fatalf("expected %q, got %q", io.ErrUnexpectedEOF, err)
|
||||
if err == nil {
|
||||
t.Fatal("Unexpected success")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue