Skip to content

Commit c307601

Browse files
author
duke
committedFeb 13, 2024
Automatic merge of jdk:master into master
2 parents 145e4d6 + 628cd8a commit c307601

File tree

2 files changed

+341
-3
lines changed

2 files changed

+341
-3
lines changed
 

‎src/java.base/share/classes/java/util/zip/ZipInputStream.java

+59-3
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants
9292

9393
private ZipCoder zc;
9494

95+
// Flag to indicate readEnd should expect 64 bit Data Descriptor size fields
96+
private boolean expect64BitDataDescriptor;
97+
9598
/**
9699
* Check to make sure that this stream has not been closed
97100
*/
@@ -521,6 +524,13 @@ private ZipEntry readLOC() throws IOException {
521524
}
522525
e.method = get16(tmpbuf, LOCHOW);
523526
e.xdostime = get32(tmpbuf, LOCTIM);
527+
528+
// Expect 32-bit Data Descriptor size fields by default
529+
expect64BitDataDescriptor = false;
530+
531+
long csize = get32(tmpbuf, LOCSIZ);
532+
long size = get32(tmpbuf, LOCLEN);
533+
524534
if ((flag & 8) == 8) {
525535
/* "Data Descriptor" present */
526536
if (e.method != DEFLATED) {
@@ -529,15 +539,17 @@ private ZipEntry readLOC() throws IOException {
529539
}
530540
} else {
531541
e.crc = get32(tmpbuf, LOCCRC);
532-
e.csize = get32(tmpbuf, LOCSIZ);
533-
e.size = get32(tmpbuf, LOCLEN);
542+
e.csize = csize;
543+
e.size = size;
534544
}
535545
len = get16(tmpbuf, LOCEXT);
536546
if (len > 0) {
537547
byte[] extra = new byte[len];
538548
readFully(extra, 0, len);
539549
e.setExtra0(extra,
540550
e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL, true);
551+
// Determine if readEnd should expect 64-bit size fields in the Data Descriptor
552+
expect64BitDataDescriptor = expect64BitDataDescriptor(extra, flag, csize, size);
541553
}
542554
return e;
543555
}
@@ -577,7 +589,8 @@ private void readEnd(ZipEntry e) throws IOException {
577589
if ((flag & 8) == 8) {
578590
/* "Data Descriptor" present */
579591
if (inf.getBytesWritten() > ZIP64_MAGICVAL ||
580-
inf.getBytesRead() > ZIP64_MAGICVAL) {
592+
inf.getBytesRead() > ZIP64_MAGICVAL ||
593+
expect64BitDataDescriptor) {
581594
// ZIP64 format
582595
readFully(tmpbuf, 0, ZIP64_EXTHDR);
583596
long sig = get32(tmpbuf, 0);
@@ -625,6 +638,49 @@ private void readEnd(ZipEntry e) throws IOException {
625638
}
626639
}
627640

641+
/**
642+
* Determine whether the {@link #readEnd(ZipEntry)} method should interpret the
643+
* 'compressed size' and 'uncompressed size' fields of the Data Descriptor record
644+
* as 64-bit numbers instead of the regular 32-bit numbers.
645+
*
646+
* Returns true if the LOC has the 'streaming mode' flag set, at least one of the
647+
* 'compressed size' and 'uncompressed size' are set to the Zip64 magic value
648+
* 0xFFFFFFFF, and the LOC's extra field contains a Zip64 Extended Information Field.
649+
*
650+
* @param extra the LOC extra field to look for a Zip64 field in
651+
* @param flag the value of the 'general purpose bit flag' field in the LOC
652+
* @param csize the value of the 'compressed size' field in the LOC
653+
* @param size the value of the 'uncompressed size' field in the LOC
654+
*/
655+
private boolean expect64BitDataDescriptor(byte[] extra, int flag, long csize, long size) {
656+
// The LOC's 'general purpose bit flag' 3 must indicate use of a Data Descriptor
657+
if ((flag & 8) == 0) {
658+
return false;
659+
}
660+
661+
// At least one LOC size field must be marked for Zip64
662+
if (csize != ZIP64_MAGICVAL && size != ZIP64_MAGICVAL) {
663+
return false;
664+
}
665+
666+
// Look for a Zip64 field
667+
int headerSize = 2 * Short.BYTES; // id + size
668+
if (extra != null) {
669+
for (int i = 0; i + headerSize < extra.length;) {
670+
int id = get16(extra, i);
671+
int dsize = get16(extra, i + Short.BYTES);
672+
if (i + headerSize + dsize > extra.length) {
673+
return false; // Invalid size
674+
}
675+
if (id == ZIP64_EXTID) {
676+
return true;
677+
}
678+
i += headerSize + dsize;
679+
}
680+
}
681+
return false;
682+
}
683+
628684
/*
629685
* Reads bytes, blocking until all bytes are read.
630686
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8303866
27+
* @summary ZipInputStream should read 8-byte data descriptors if the LOC has
28+
* a ZIP64 extended information extra field
29+
* @run junit Zip64DataDescriptor
30+
*/
31+
32+
33+
import org.junit.jupiter.api.Test;
34+
import org.junit.jupiter.api.BeforeEach;
35+
36+
import java.io.ByteArrayInputStream;
37+
import java.io.ByteArrayOutputStream;
38+
import java.io.IOException;
39+
import java.io.OutputStream;
40+
import java.nio.ByteBuffer;
41+
import java.nio.ByteOrder;
42+
import java.nio.charset.StandardCharsets;
43+
import java.util.HexFormat;
44+
import java.util.zip.*;
45+
46+
import static org.junit.jupiter.api.Assertions.*;
47+
48+
public class Zip64DataDescriptor {
49+
50+
// A byte array holding a small-sized Zip64 ZIP file, described below
51+
private byte[] zip64File;
52+
53+
// A byte array holding a ZIP used for testing invalid Zip64 extra fields
54+
private byte[] invalidZip64;
55+
56+
@BeforeEach
57+
public void setup() throws IOException {
58+
/*
59+
* Structure of the ZIP64 file used below . Note the presence
60+
* of a Zip64 extended information extra field and the
61+
* Data Descriptor having 8-byte values for csize and size.
62+
*
63+
* The file was produced using the zip command on MacOS
64+
* (zip 3.0, by Info-ZIP), in streamming mode (to enable Zip64),
65+
* using the -fd option (to force the use of data descriptors)
66+
*
67+
* The following command was used:
68+
* <pre>echo hello | zip -fd > hello.zip</pre>
69+
*
70+
* ------ Local File Header ------
71+
* 000000 signature 0x04034b50
72+
* 000004 version 45
73+
* 000006 flags 0x0008
74+
* 000008 method 8 Deflated
75+
* 000010 time 0xb180 22:12
76+
* 000012 date 0x565c 2023-02-28
77+
* 000014 crc 0x00000000
78+
* 000018 csize -1
79+
* 000022 size -1
80+
* 000026 nlen 1
81+
* 000028 elen 20
82+
* 000030 name 1 bytes '-'
83+
* 000031 ext id 0x0001 Zip64 extended information extra field
84+
* 000033 ext size 16
85+
* 000035 z64 size 0
86+
* 000043 z64 csize 0
87+
*
88+
* ------ File Data ------
89+
* 000051 data 8 bytes
90+
*
91+
* ------ Data Desciptor ------
92+
* 000059 signature 0x08074b50
93+
* 000063 crc 0x363a3020
94+
* 000067 csize 8
95+
* 000075 size 6
96+
* 000083 ...
97+
*/
98+
99+
String hex = """
100+
504b03042d000800080080b15c5600000000ffffffffffffffff01001400
101+
2d0100100000000000000000000000000000000000cb48cdc9c9e7020050
102+
4b070820303a3608000000000000000600000000000000504b01021e032d
103+
000800080080b15c5620303a360800000006000000010000000000000001
104+
000000b011000000002d504b050600000000010001002f00000053000000
105+
0000""";
106+
107+
zip64File = HexFormat.of().parseHex(hex.replaceAll("\n", ""));
108+
109+
// Create the ZIP file used for testing that invalid Zip64 extra fields are ignored
110+
// This ZIP has the regular 4-bit data descriptor
111+
112+
byte[] extra = new byte[Long.BYTES + Long.BYTES + Short.BYTES * 2]; // Size of a regular Zip64 extra field
113+
ByteBuffer buffer = ByteBuffer.wrap(extra).order(ByteOrder.LITTLE_ENDIAN);
114+
buffer.putShort(0, (short) 123); // Not processed by ZipEntry.setExtra
115+
buffer.putShort(Short.BYTES, (short) (extra.length - 4));
116+
117+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
118+
try (ZipOutputStream zo = new ZipOutputStream(baos)) {
119+
ZipEntry ze = new ZipEntry("-");
120+
ze.setExtra(extra);
121+
zo.putNextEntry(ze);
122+
zo.write("hello\n".getBytes(StandardCharsets.UTF_8));
123+
}
124+
125+
invalidZip64 = baos.toByteArray();
126+
127+
// Set Zip64 magic values on compressed and uncompressed size fields
128+
ByteBuffer.wrap(invalidZip64).order(ByteOrder.LITTLE_ENDIAN)
129+
.putInt(ZipFile.LOCSIZ, 0xFFFFFFFF)
130+
.putInt(ZipFile.LOCLEN, 0xFFFFFFFF);
131+
132+
// Set the Zip64 header ID 0x1 on the extra field in the invalid file
133+
setExtraHeaderId((short) 0x1);
134+
}
135+
136+
/*
137+
* Verify that small-sized Zip64 entries can be parsed by ZipInputStream
138+
*/
139+
@Test
140+
public void shouldReadZip64Descriptor() throws IOException {
141+
readZipInputStream(zip64File);
142+
}
143+
144+
/*
145+
* For maximal backward compatibility when reading Zip64 descriptors, invalid
146+
* Zip64 extra data sizes should be ignored
147+
*/
148+
@Test
149+
public void shouldIgnoreInvalidExtraSize() throws IOException {
150+
setExtraSize((short) 42);
151+
readZipInputStream(invalidZip64);
152+
}
153+
154+
/*
155+
* Files with Zip64 magic values but no Zip64 field should be ignored
156+
* when considering 8 byte data descriptors
157+
*/
158+
@Test
159+
public void shouldIgnoreNoZip64Header() throws IOException {
160+
setExtraSize((short) 123);
161+
readZipInputStream(invalidZip64);
162+
}
163+
164+
/*
165+
* Theoretically, ZIP files may exist with ZIP64 format, but with 4-byte
166+
* data descriptors. Such files will fail to parse, as demonstrated by this test.
167+
*/
168+
@Test
169+
public void shouldFailParsingZip64With4ByteDataDescriptor() throws IOException {
170+
ZipException ex = assertThrows(ZipException.class, () -> {
171+
readZipInputStream(invalidZip64);
172+
});
173+
174+
String msg = String.format("Expected exeption message to contain 'invalid entry size', was %s",
175+
ex.getMessage());
176+
assertTrue(ex.getMessage().contains("invalid entry size"), msg);
177+
}
178+
179+
/*
180+
* Validate that an extra data size exceeding the length of the extra field is ignored
181+
*/
182+
@Test
183+
public void shouldIgnoreExcessiveExtraSize() throws IOException {
184+
185+
setExtraSize(Short.MAX_VALUE);
186+
187+
188+
readZipInputStream(invalidZip64);
189+
}
190+
191+
/*
192+
* Validate that the Data Descriptor is read with 32-bit fields if neither the
193+
* LOC's 'uncompressed size' or 'compressed size' fields have the Zip64 magic value,
194+
* even when there is a Zip64 field in the extra field.
195+
*/
196+
@Test
197+
public void shouldIgnoreNoMagicMarkers() throws IOException {
198+
// Set compressed and uncompressed size fields to zero
199+
ByteBuffer.wrap(invalidZip64).order(ByteOrder.LITTLE_ENDIAN)
200+
.putInt(ZipFile.LOCSIZ, 0)
201+
.putInt(ZipFile.LOCLEN, 0);
202+
203+
204+
readZipInputStream(invalidZip64);
205+
}
206+
207+
/*
208+
* Validate that an extra data size exceeding the length of the extra field is ignored
209+
*/
210+
@Test
211+
public void shouldIgnoreTrucatedZip64Extra() throws IOException {
212+
213+
truncateZip64();
214+
215+
readZipInputStream(invalidZip64);
216+
}
217+
218+
/**
219+
* Update the Extra field header ID of the invalid file
220+
*/
221+
private void setExtraHeaderId(short id) {
222+
// Set the header ID on the extra field
223+
ByteBuffer buffer = ByteBuffer.wrap(invalidZip64).order(ByteOrder.LITTLE_ENDIAN);
224+
int nlen = buffer.getShort(ZipFile.LOCNAM);
225+
buffer.putShort(ZipFile.LOCHDR + nlen, id);
226+
}
227+
228+
/**
229+
* Updates the 16-bit 'data size' field of the Zip64 extended information field,
230+
* potentially to an invalid value.
231+
* @param size the value to set in the 'data size' field.
232+
*/
233+
private void setExtraSize(short size) {
234+
ByteBuffer buffer = ByteBuffer.wrap(invalidZip64).order(ByteOrder.LITTLE_ENDIAN);
235+
// Compute the offset to the Zip64 data block size field
236+
short nlen = buffer.getShort(ZipFile.LOCNAM);
237+
int dataSizeOffset = ZipFile.LOCHDR + nlen + Short.BYTES;
238+
buffer.putShort(dataSizeOffset, size);
239+
}
240+
241+
/**
242+
* Puts a truncated Zip64 field (just the tag) at the end of the LOC extra field.
243+
* The beginning of the extra field is filled with a generic extra field containing
244+
* just zeros.
245+
*/
246+
private void truncateZip64() {
247+
ByteBuffer buffer = ByteBuffer.wrap(invalidZip64).order(ByteOrder.LITTLE_ENDIAN);
248+
// Get the LOC name and extra sizes
249+
short nlen = buffer.getShort(ZipFile.LOCNAM);
250+
short elen = buffer.getShort(ZipFile.LOCEXT);
251+
int cenOffset = ZipFile.LOCHDR + nlen + elen;
252+
253+
// Zero out the extra field
254+
int estart = ZipFile.LOCHDR + nlen;
255+
buffer.put(estart, new byte[elen]);
256+
// Put a generic extra field in the start
257+
buffer.putShort(estart, (short) 42);
258+
buffer.putShort(estart + Short.BYTES, (short) (elen - 4 - 2));
259+
// Put a truncated (just the tag) Zip64 field at the end
260+
buffer.putShort(cenOffset - Short.BYTES, (short) 0x0001);
261+
}
262+
263+
/*
264+
* Consume and verify the ZIP file using ZipInputStream
265+
*/
266+
private void readZipInputStream(byte[] zip) throws IOException {
267+
try (ZipInputStream in = new ZipInputStream(new ByteArrayInputStream(zip))) {
268+
// Read the ZIP entry, this calls readLOC
269+
ZipEntry e = in.getNextEntry();
270+
271+
// Sanity check the zip entry
272+
assertNotNull(e, "Missing zip entry");
273+
assertEquals("-", e.getName());
274+
275+
// Read the entry data, this causes readEND to parse the data descriptor
276+
assertEquals("hello\n", new String(in.readAllBytes(), StandardCharsets.UTF_8));
277+
278+
// There should only be a single zip entry
279+
assertNull(in.getNextEntry(), "Unexpected additional zip entry");
280+
}
281+
}
282+
}

0 commit comments

Comments
 (0)
Please sign in to comment.