Skip to content

Commit 4aedbf5

Browse files
committed
archive/zip: fix reading, writing of zip64 archives
Read zip files that contain only 64-bit header offset, not 64-bit sizes. Fixes #13367. Read zip files that contain completely unexpected Extra fields, provided we do not need to find 64-bit size or header offset information there. Fixes #13166. Write zip file entries with 0xFFFFFFFF uncompressed data bytes correctly (must use zip64 header, since that's the magic indicator). Fixes new TestZip64EdgeCase. (Noticed while working on the CL.) Change-Id: I84a22b3995fafab8052b99de8094a9f35a25de5b Reviewed-on: https://go-review.googlesource.com/18317 Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 5d8442a commit 4aedbf5

File tree

4 files changed

+85
-25
lines changed

4 files changed

+85
-25
lines changed

src/archive/zip/reader.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -283,39 +283,59 @@ func readDirectoryHeader(f *File, r io.Reader) error {
283283
f.Extra = d[filenameLen : filenameLen+extraLen]
284284
f.Comment = string(d[filenameLen+extraLen:])
285285

286+
needUSize := f.UncompressedSize == ^uint32(0)
287+
needCSize := f.CompressedSize == ^uint32(0)
288+
needHeaderOffset := f.headerOffset == int64(^uint32(0))
289+
286290
if len(f.Extra) > 0 {
291+
// Best effort to find what we need.
292+
// Other zip authors might not even follow the basic format,
293+
// and we'll just ignore the Extra content in that case.
287294
b := readBuf(f.Extra)
288295
for len(b) >= 4 { // need at least tag and size
289296
tag := b.uint16()
290297
size := b.uint16()
291298
if int(size) > len(b) {
292-
return ErrFormat
299+
break
293300
}
294301
if tag == zip64ExtraId {
295-
// update directory values from the zip64 extra block
302+
// update directory values from the zip64 extra block.
303+
// They should only be consulted if the sizes read earlier
304+
// are maxed out.
305+
// See golang.org/issue/13367.
296306
eb := readBuf(b[:size])
297-
if len(eb) >= 8 {
307+
308+
if needUSize {
309+
needUSize = false
310+
if len(eb) < 8 {
311+
return ErrFormat
312+
}
298313
f.UncompressedSize64 = eb.uint64()
299314
}
300-
if len(eb) >= 8 {
315+
if needCSize {
316+
needCSize = false
317+
if len(eb) < 8 {
318+
return ErrFormat
319+
}
301320
f.CompressedSize64 = eb.uint64()
302321
}
303-
if len(eb) >= 8 {
322+
if needHeaderOffset {
323+
needHeaderOffset = false
324+
if len(eb) < 8 {
325+
return ErrFormat
326+
}
304327
f.headerOffset = int64(eb.uint64())
305328
}
329+
break
306330
}
307331
b = b[size:]
308332
}
309-
// Should have consumed the whole header.
310-
// But popular zip & JAR creation tools are broken and
311-
// may pad extra zeros at the end, so accept those
312-
// too. See golang.org/issue/8186.
313-
for _, v := range b {
314-
if v != 0 {
315-
return ErrFormat
316-
}
317-
}
318333
}
334+
335+
if needUSize || needCSize || needHeaderOffset {
336+
return ErrFormat
337+
}
338+
319339
return nil
320340
}
321341

src/archive/zip/struct.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ func (h *FileHeader) SetMode(mode os.FileMode) {
235235

236236
// isZip64 reports whether the file size exceeds the 32 bit limit
237237
func (fh *FileHeader) isZip64() bool {
238-
return fh.CompressedSize64 > uint32max || fh.UncompressedSize64 > uint32max
238+
return fh.CompressedSize64 >= uint32max || fh.UncompressedSize64 >= uint32max
239239
}
240240

241241
func msdosModeToFileMode(m uint32) (mode os.FileMode) {

src/archive/zip/writer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func (w *Writer) Close() error {
7878
b.uint16(h.ModifiedTime)
7979
b.uint16(h.ModifiedDate)
8080
b.uint32(h.CRC32)
81-
if h.isZip64() || h.offset > uint32max {
81+
if h.isZip64() || h.offset >= uint32max {
8282
// the file needs a zip64 header. store maxint in both
8383
// 32 bit size fields (and offset later) to signal that the
8484
// zip64 extra header should be used.

src/archive/zip/zip_test.go

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,24 @@ func TestZip64(t *testing.T) {
237237
testZip64DirectoryRecordLength(buf, t)
238238
}
239239

240+
func TestZip64EdgeCase(t *testing.T) {
241+
if testing.Short() {
242+
t.Skip("slow test; skipping")
243+
}
244+
// Test a zip file with uncompressed size 0xFFFFFFFF.
245+
// That's the magic marker for a 64-bit file, so even though
246+
// it fits in a 32-bit field we must use the 64-bit field.
247+
// Go 1.5 and earlier got this wrong,
248+
// writing an invalid zip file.
249+
const size = 1<<32 - 1 - int64(len("END\n")) // before the "END\n" part
250+
buf := testZip64(t, size)
251+
testZip64DirectoryRecordLength(buf, t)
252+
}
253+
240254
func testZip64(t testing.TB, size int64) *rleBuffer {
241255
const chunkSize = 1024
242256
chunks := int(size / chunkSize)
243-
// write 2^32 bytes plus "END\n" to a zip file
257+
// write size bytes plus "END\n" to a zip file
244258
buf := new(rleBuffer)
245259
w := NewWriter(buf)
246260
f, err := w.CreateHeader(&FileHeader{
@@ -261,6 +275,12 @@ func testZip64(t testing.TB, size int64) *rleBuffer {
261275
t.Fatal("write chunk:", err)
262276
}
263277
}
278+
if frag := int(size % chunkSize); frag > 0 {
279+
_, err := f.Write(chunk[:frag])
280+
if err != nil {
281+
t.Fatal("write chunk:", err)
282+
}
283+
}
264284
end := []byte("END\n")
265285
_, err = f.Write(end)
266286
if err != nil {
@@ -287,6 +307,12 @@ func testZip64(t testing.TB, size int64) *rleBuffer {
287307
t.Fatal("read:", err)
288308
}
289309
}
310+
if frag := int(size % chunkSize); frag > 0 {
311+
_, err := io.ReadFull(rc, chunk[:frag])
312+
if err != nil {
313+
t.Fatal("read:", err)
314+
}
315+
}
290316
gotEnd, err := ioutil.ReadAll(rc)
291317
if err != nil {
292318
t.Fatal("read end:", err)
@@ -298,14 +324,14 @@ func testZip64(t testing.TB, size int64) *rleBuffer {
298324
if err != nil {
299325
t.Fatal("closing:", err)
300326
}
301-
if size == 1<<32 {
327+
if size+int64(len("END\n")) >= 1<<32-1 {
302328
if got, want := f0.UncompressedSize, uint32(uint32max); got != want {
303-
t.Errorf("UncompressedSize %d, want %d", got, want)
329+
t.Errorf("UncompressedSize %#x, want %#x", got, want)
304330
}
305331
}
306332

307333
if got, want := f0.UncompressedSize64, uint64(size)+uint64(len(end)); got != want {
308-
t.Errorf("UncompressedSize64 %d, want %d", got, want)
334+
t.Errorf("UncompressedSize64 %#x, want %#x", got, want)
309335
}
310336

311337
return buf
@@ -377,9 +403,14 @@ func testValidHeader(h *FileHeader, t *testing.T) {
377403
}
378404

379405
b := buf.Bytes()
380-
if _, err = NewReader(bytes.NewReader(b), int64(len(b))); err != nil {
406+
zf, err := NewReader(bytes.NewReader(b), int64(len(b)))
407+
if err != nil {
381408
t.Fatalf("got %v, expected nil", err)
382409
}
410+
zh := zf.File[0].FileHeader
411+
if zh.Name != h.Name || zh.Method != h.Method || zh.UncompressedSize64 != uint64(len("hi")) {
412+
t.Fatalf("got %q/%d/%d expected %q/%d/%d", zh.Name, zh.Method, zh.UncompressedSize64, h.Name, h.Method, len("hi"))
413+
}
383414
}
384415

385416
// Issue 4302.
@@ -392,20 +423,29 @@ func TestHeaderInvalidTagAndSize(t *testing.T) {
392423
h := FileHeader{
393424
Name: filename,
394425
Method: Deflate,
395-
Extra: []byte(ts.Format(time.RFC3339Nano)), // missing tag and len
426+
Extra: []byte(ts.Format(time.RFC3339Nano)), // missing tag and len, but Extra is best-effort parsing
396427
}
397428
h.SetModTime(ts)
398429

399-
testInvalidHeader(&h, t)
430+
testValidHeader(&h, t)
400431
}
401432

402433
func TestHeaderTooShort(t *testing.T) {
403434
h := FileHeader{
404435
Name: "foo.txt",
405436
Method: Deflate,
406-
Extra: []byte{zip64ExtraId}, // missing size
437+
Extra: []byte{zip64ExtraId}, // missing size and second half of tag, but Extra is best-effort parsing
407438
}
408-
testInvalidHeader(&h, t)
439+
testValidHeader(&h, t)
440+
}
441+
442+
func TestHeaderIgnoredSize(t *testing.T) {
443+
h := FileHeader{
444+
Name: "foo.txt",
445+
Method: Deflate,
446+
Extra: []byte{zip64ExtraId & 0xFF, zip64ExtraId >> 8, 24, 0, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, // bad size but shouldn't be consulted
447+
}
448+
testValidHeader(&h, t)
409449
}
410450

411451
// Issue 4393. It is valid to have an extra data header

0 commit comments

Comments
 (0)