compress/gzip: return unexpected EOF for certain truncated streams

For cases where RFC 1952 requires a field, the code returns the error
io.ErrUnexpectedEOF except in two places: for the FNAME flag or the
FCOMMENT flag. These flags expect a null-terminated string and
readString() may return an EOF if the Reader is truncated before a
null byte is found. For consistency with parsing other parts of the
header, this is converted to an unexpected EOF herein.

* Fixes #51417

see https://go-review.googlesource.com/c/go/+/14832/
This commit is contained in:
Phil Bracikowski 2022-03-01 15:22:13 -08:00 committed by Phil Bracikowski
parent d40e7bb174
commit 2e573cd961
2 changed files with 70 additions and 16 deletions

View File

@ -211,14 +211,14 @@ func (z *Reader) readHeader() (hdr Header, err error) {
var s string
if flg&flagName != 0 {
if s, err = z.readString(); err != nil {
return hdr, err
return hdr, noEOF(err)
}
hdr.Name = s
}
if flg&flagComment != 0 {
if s, err = z.readString(); err != nil {
return hdr, err
return hdr, noEOF(err)
}
hdr.Comment = s
}

View File

@ -359,6 +359,38 @@ var gunzipTests = []gunzipTest{
},
io.ErrUnexpectedEOF,
},
{
"hello.txt",
"gzip header with truncated name",
"hello world\n",
[]byte{
0x1f, 0x8b, 0x08, 0x08, 0xc8, 0x58, 0x13, 0x4a,
0x00, 0x03, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e,
0x74, 0x78, 0x74, 0x00, 0xcb, 0x48, 0xcd, 0xc9,
0xc9, 0x57, 0x28, 0xcf, 0x2f, 0xca, 0x49, 0xe1,
0x02, 0x00, 0x2d, 0x3b, 0x08, 0xaf, 0x0c, 0x00,
0x00, 0x00,
0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,
0x00, 0xff, 0x01,
},
io.ErrUnexpectedEOF,
},
{
"",
"gzip header with truncated comment",
"hello world\n",
[]byte{
0x1f, 0x8b, 0x08, 0x10, 0xc8, 0x58, 0x13, 0x4a,
0x00, 0x03, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e,
0x74, 0x78, 0x74, 0x00, 0xcb, 0x48, 0xcd, 0xc9,
0xc9, 0x57, 0x28, 0xcf, 0x2f, 0xca, 0x49, 0xe1,
0x02, 0x00, 0x2d, 0x3b, 0x08, 0xaf, 0x0c, 0x00,
0x00, 0x00,
0x1f, 0x8b, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0xff, 0x01,
},
io.ErrUnexpectedEOF,
},
}
func TestDecompressor(t *testing.T) {
@ -495,23 +527,45 @@ func TestNilStream(t *testing.T) {
}
func TestTruncatedStreams(t *testing.T) {
const data = "\x1f\x8b\b\x04\x00\tn\x88\x00\xff\a\x00foo bar\xcbH\xcd\xc9\xc9\xd7Q(\xcf/\xcaI\x01\x04:r\xab\xff\f\x00\x00\x00"
cases := []struct {
name string
data []byte
}{
{
name: "original",
data: []byte("\x1f\x8b\b\x04\x00\tn\x88\x00\xff\a\x00foo bar\xcbH\xcd\xc9\xc9\xd7Q(\xcf/\xcaI\x01\x04:r\xab\xff\f\x00\x00\x00"),
},
{
name: "truncated name",
data: []byte{
0x1f, 0x8b, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01,
},
},
{
name: "truncated comment",
data: []byte{
0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01,
},
},
}
// Intentionally iterate starting with at least one byte in the stream.
for i := 1; i < len(data)-1; i++ {
r, err := NewReader(strings.NewReader(data[:i]))
if err != nil {
if err != io.ErrUnexpectedEOF {
t.Errorf("NewReader(%d) on truncated stream: got %v, want %v", i, err, io.ErrUnexpectedEOF)
for _, tc := range cases {
for i := 1; i < len(tc.data); i++ {
r, err := NewReader(strings.NewReader(string(tc.data[:i])))
if err != nil {
if err != io.ErrUnexpectedEOF {
t.Errorf("NewReader(%s-%d) on truncated stream: got %v, want %v", tc.name, i, err, io.ErrUnexpectedEOF)
}
continue
}
_, err = io.Copy(io.Discard, r)
if ferr, ok := err.(*flate.ReadError); ok {
err = ferr.Err
}
if err != io.ErrUnexpectedEOF {
t.Errorf("io.Copy(%s-%d) on truncated stream: got %v, want %v", tc.name, i, err, io.ErrUnexpectedEOF)
}
continue
}
_, err = io.Copy(io.Discard, r)
if ferr, ok := err.(*flate.ReadError); ok {
err = ferr.Err
}
if err != io.ErrUnexpectedEOF {
t.Errorf("io.Copy(%d) on truncated stream: got %v, want %v", i, err, io.ErrUnexpectedEOF)
}
}
}