Commit 01e45c73 authored by Joe Tsai's avatar Joe Tsai Committed by Joe Tsai

archive/tar: fallback to pre-Go1.8 behavior on certain GNU files

Prior to Go1.8, the Writer had a bug where it would output
an invalid tar file in certain rare situations because the logic
incorrectly believed that the old GNU format had a prefix field.
This is wrong and leads to an output file that mangles the
atime and ctime fields, which are often left unused.

In order to continue reading tar files created by former, buggy
versions of Go, we skeptically parse the atime and ctime fields.
If we are unable to parse them and the prefix field looks like
an ASCII string, then we fallback on the pre-Go1.8 behavior
of treating these fields as the USTAR prefix field.

Note that this will not use the fallback logic for all possible
files generated by a pre-Go1.8 toolchain. If the generated file
happened to have a prefix field that parses as valid
atime and ctime fields (e.g., when they are valid octal strings),
then it is impossible to distinguish between an valid GNU file
and an invalid pre-Go1.8 file.

Fixes #21005

Change-Id: Iebf5c67c08e0e46da6ee41a2e8b339f84030dd90
Reviewed-on: https://go-review.googlesource.com/53635
Run-TryBot: Joe Tsai <thebrokentoaster@gmail.com>
Reviewed-by: 's avatarIan Lance Taylor <iant@golang.org>
parent b8519cd7
...@@ -463,26 +463,6 @@ func (tr *Reader) readHeader() (*Header, *block, error) { ...@@ -463,26 +463,6 @@ func (tr *Reader) readHeader() (*Header, *block, error) {
hdr.Typeflag = v7.TypeFlag()[0] hdr.Typeflag = v7.TypeFlag()[0]
hdr.Linkname = p.parseString(v7.LinkName()) hdr.Linkname = p.parseString(v7.LinkName())
// The atime and ctime fields are often left unused. Some versions of Go
// had a bug in the tar.Writer where it would output an invalid tar file
// in certain rare situations because the logic incorrectly believed that
// the old GNU format had a prefix field. This is wrong and leads to
// an outputted file that actually mangles the atime and ctime fields.
//
// In order to continue reading tar files created by a buggy writer, we
// try to parse the atime and ctime fields, but just return the zero value
// of time.Time when we cannot parse them.
//
// See https://golang.org/issues/12594
tryParseTime := func(b []byte) time.Time {
var p parser
n := p.parseNumeric(b)
if b[0] != 0x00 && p.err == nil {
return time.Unix(n, 0)
}
return time.Time{}
}
// Unpack format specific fields. // Unpack format specific fields.
if format > formatV7 { if format > formatV7 {
ustar := tr.blk.USTAR() ustar := tr.blk.USTAR()
...@@ -504,9 +484,43 @@ func (tr *Reader) readHeader() (*Header, *block, error) { ...@@ -504,9 +484,43 @@ func (tr *Reader) readHeader() (*Header, *block, error) {
hdr.AccessTime = time.Unix(p.parseNumeric(star.AccessTime()), 0) hdr.AccessTime = time.Unix(p.parseNumeric(star.AccessTime()), 0)
hdr.ChangeTime = time.Unix(p.parseNumeric(star.ChangeTime()), 0) hdr.ChangeTime = time.Unix(p.parseNumeric(star.ChangeTime()), 0)
case formatGNU: case formatGNU:
var p2 parser
gnu := tr.blk.GNU() gnu := tr.blk.GNU()
hdr.AccessTime = tryParseTime(gnu.AccessTime()) if b := gnu.AccessTime(); b[0] != 0 {
hdr.ChangeTime = tryParseTime(gnu.ChangeTime()) hdr.AccessTime = time.Unix(p2.parseNumeric(b), 0)
}
if b := gnu.ChangeTime(); b[0] != 0 {
hdr.ChangeTime = time.Unix(p2.parseNumeric(b), 0)
}
// Prior to Go1.8, the Writer had a bug where it would output
// an invalid tar file in certain rare situations because the logic
// incorrectly believed that the old GNU format had a prefix field.
// This is wrong and leads to an output file that mangles the
// atime and ctime fields, which are often left unused.
//
// In order to continue reading tar files created by former, buggy
// versions of Go, we skeptically parse the atime and ctime fields.
// If we are unable to parse them and the prefix field looks like
// an ASCII string, then we fallback on the pre-Go1.8 behavior
// of treating these fields as the USTAR prefix field.
//
// Note that this will not use the fallback logic for all possible
// files generated by a pre-Go1.8 toolchain. If the generated file
// happened to have a prefix field that parses as valid
// atime and ctime fields (e.g., when they are valid octal strings),
// then it is impossible to distinguish between an valid GNU file
// and an invalid pre-Go1.8 file.
//
// See https://golang.org/issues/12594
// See https://golang.org/issues/21005
if p2.err != nil {
hdr.AccessTime, hdr.ChangeTime = time.Time{}, time.Time{}
ustar := tr.blk.USTAR()
if s := p.parseString(ustar.Prefix()); isASCII(s) {
prefix = s
}
}
} }
if len(prefix) > 0 { if len(prefix) > 0 {
hdr.Name = prefix + "/" + hdr.Name hdr.Name = prefix + "/" + hdr.Name
......
...@@ -346,6 +346,15 @@ func TestReader(t *testing.T) { ...@@ -346,6 +346,15 @@ func TestReader(t *testing.T) {
}, { }, {
file: "testdata/issue12435.tar", file: "testdata/issue12435.tar",
err: ErrHeader, err: ErrHeader,
}, {
// Ensure that we can read back the original Header as written with
// a buggy pre-Go1.8 tar.Writer.
file: "testdata/invalid-go17.tar",
headers: []*Header{{
Name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/foo",
Uid: 010000000,
ModTime: time.Unix(0, 0),
}},
}} }}
for i, v := range vectors { for i, v := range vectors {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment