Skip to content

Commit 2d3cd51

Browse files
committed
archive/tar: disable prefix field in Writer
The proper fix for the Writer is too involved to be done in time for Go 1.8. Instead, we do a localized fix that simply disables the prefix encoding logic. While this will prevent some legitimate uses of prefix, it will ensure that we don't keep outputting invalid GNU format files that have the prefix field populated. For headers with long filenames that could have used the prefix field, they will be promoted to use the PAX format, which ensures that we will still be able to encode all headers that we were able to do before. Updates #12594 Fixes #17630 Fixes #9683 Change-Id: Ia97b524ac69865390e2ae8bb0dfb664d40a05add Reviewed-on: https://go-review.googlesource.com/32234 Reviewed-by: Russ Cox <[email protected]> Run-TryBot: Joe Tsai <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent eed2bb7 commit 2d3cd51

File tree

4 files changed

+98
-7
lines changed

4 files changed

+98
-7
lines changed
Binary file not shown.
0 Bytes
Binary file not shown.

src/archive/tar/writer.go

+33-1
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,41 @@ func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error {
170170
formatNumeric(ustar.DevMajor(), hdr.Devmajor, paxNone)
171171
formatNumeric(ustar.DevMinor(), hdr.Devminor, paxNone)
172172

173+
// TODO(dsnet): The logic surrounding the prefix field is broken when trying
174+
// to encode the header as GNU format. The challenge with the current logic
175+
// is that we are unsure what format we are using at any given moment until
176+
// we have processed *all* of the fields. The problem is that by the time
177+
// all fields have been processed, some work has already been done to handle
178+
// each field under the assumption that it is for one given format or
179+
// another. In some situations, this causes the Writer to be confused and
180+
// encode a prefix field when the format being used is GNU. Thus, producing
181+
// an invalid tar file.
182+
//
183+
// As a short-term fix, we disable the logic to use the prefix field, which
184+
// will force the badly generated GNU files to become encoded as being
185+
// the PAX format.
186+
//
187+
// As an alternative fix, we could hard-code preferPax to be true. However,
188+
// this is problematic for the following reasons:
189+
// * The preferPax functionality is not tested at all.
190+
// * This can result in headers that try to use both the GNU and PAX
191+
// features at the same time, which is also wrong.
192+
//
193+
// The proper fix for this is to use a two-pass method:
194+
// * The first pass simply determines what set of formats can possibly
195+
// encode the given header.
196+
// * The second pass actually encodes the header as that given format
197+
// without worrying about violating the format.
198+
//
199+
// See the following:
200+
// https://golang.org/issue/12594
201+
// https://golang.org/issue/17630
202+
// https://golang.org/issue/9683
203+
const usePrefix = false
204+
173205
// try to use a ustar header when only the name is too long
174206
_, paxPathUsed := paxHeaders[paxPath]
175-
if !tw.preferPax && len(paxHeaders) == 1 && paxPathUsed {
207+
if usePrefix && !tw.preferPax && len(paxHeaders) == 1 && paxPathUsed {
176208
prefix, suffix, ok := splitUSTARPath(hdr.Name)
177209
if ok {
178210
// Since we can encode in USTAR format, disable PAX header.

src/archive/tar/writer_test.go

+65-6
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,13 @@ func TestWriter(t *testing.T) {
133133
contents: strings.Repeat("\x00", 4<<10),
134134
}},
135135
}, {
136-
// The truncated test file was produced using these commands:
137-
// dd if=/dev/zero bs=1048576 count=16384 > (longname/)*15 /16gig.txt
138-
// tar -b 1 -c -f- (longname/)*15 /16gig.txt | dd bs=512 count=8 > writer-big-long.tar
136+
// This truncated file was produced using this library.
137+
// It was verified to work with GNU tar 1.27.1 and BSD tar 3.1.2.
138+
// dd if=/dev/zero bs=1G count=16 >> writer-big-long.tar
139+
// gnutar -xvf writer-big-long.tar
140+
// bsdtar -xvf writer-big-long.tar
141+
//
142+
// This file is in PAX format.
139143
file: "testdata/writer-big-long.tar",
140144
entries: []*entry{{
141145
header: &Header{
@@ -153,9 +157,15 @@ func TestWriter(t *testing.T) {
153157
contents: strings.Repeat("\x00", 4<<10),
154158
}},
155159
}, {
156-
// This file was produced using gnu tar 1.17
157-
// gnutar -b 4 --format=ustar (longname/)*15 + file.txt
158-
file: "testdata/ustar.tar",
160+
// TODO(dsnet): The Writer output should match the following file.
161+
// To fix an issue (see https://golang.org/issue/12594), we disabled
162+
// prefix support, which alters the generated output.
163+
/*
164+
// This file was produced using gnu tar 1.17
165+
// gnutar -b 4 --format=ustar (longname/)*15 + file.txt
166+
file: "testdata/ustar.tar"
167+
*/
168+
file: "testdata/ustar.issue12594.tar", // This is a valid tar file, but not expected
159169
entries: []*entry{{
160170
header: &Header{
161171
Name: strings.Repeat("longname/", 15) + "file.txt",
@@ -586,3 +596,52 @@ func TestSplitUSTARPath(t *testing.T) {
586596
}
587597
}
588598
}
599+
600+
// TestIssue12594 tests that the Writer does not attempt to populate the prefix
601+
// field when encoding a header in the GNU format. The prefix field is valid
602+
// in USTAR and PAX, but not GNU.
603+
func TestIssue12594(t *testing.T) {
604+
names := []string{
605+
"0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/file.txt",
606+
"0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/file.txt",
607+
"0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/333/file.txt",
608+
"0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/file.txt",
609+
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/file.txt",
610+
"/home/support/.openoffice.org/3/user/uno_packages/cache/registry/com.sun.star.comp.deployment.executable.PackageRegistryBackend",
611+
}
612+
613+
for i, name := range names {
614+
var b bytes.Buffer
615+
616+
tw := NewWriter(&b)
617+
if err := tw.WriteHeader(&Header{
618+
Name: name,
619+
Uid: 1 << 25, // Prevent USTAR format
620+
}); err != nil {
621+
t.Errorf("test %d, unexpected WriteHeader error: %v", i, err)
622+
}
623+
if err := tw.Close(); err != nil {
624+
t.Errorf("test %d, unexpected Close error: %v", i, err)
625+
}
626+
627+
// The prefix field should never appear in the GNU format.
628+
var blk block
629+
copy(blk[:], b.Bytes())
630+
prefix := string(blk.USTAR().Prefix())
631+
if i := strings.IndexByte(prefix, 0); i >= 0 {
632+
prefix = prefix[:i] // Truncate at the NUL terminator
633+
}
634+
if blk.GetFormat() == formatGNU && len(prefix) > 0 && strings.HasPrefix(name, prefix) {
635+
t.Errorf("test %d, found prefix in GNU format: %s", i, prefix)
636+
}
637+
638+
tr := NewReader(&b)
639+
hdr, err := tr.Next()
640+
if err != nil {
641+
t.Errorf("test %d, unexpected Next error: %v", i, err)
642+
}
643+
if hdr.Name != name {
644+
t.Errorf("test %d, hdr.Name = %s, want %s", i, hdr.Name, name)
645+
}
646+
}
647+
}

0 commit comments

Comments
 (0)