Skip to content

Commit c8100d4

Browse files
Yuri NesterenkoRealCLanger
authored andcommitted
8302483: Enhance ZIP performance
Reviewed-by: mbalao Backport-of: 05661fdcb4ced0c7c2e9eab3464c2447f38c94c3
1 parent 6e026e5 commit c8100d4

File tree

4 files changed

+196
-8
lines changed

4 files changed

+196
-8
lines changed

src/java.base/share/classes/java/util/zip/ZipFile.java

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import jdk.internal.ref.CleanerFactory;
7070
import jdk.internal.vm.annotation.Stable;
7171
import sun.nio.cs.UTF_8;
72+
import sun.security.action.GetBooleanAction;
7273
import sun.security.util.SignatureFileVerifier;
7374

7475
import static java.util.zip.ZipConstants64.*;
@@ -121,6 +122,12 @@ public class ZipFile implements ZipConstants, Closeable {
121122
*/
122123
public static final int OPEN_DELETE = 0x4;
123124

125+
/**
126+
* Flag which specifies whether the validation of the Zip64 extra
127+
* fields should be disabled
128+
*/
129+
private static final boolean disableZip64ExtraFieldValidation =
130+
GetBooleanAction.privilegedGetProperty("jdk.util.zip.disableZip64ExtraFieldValidation");
124131
/**
125132
* Opens a zip file for reading.
126133
*
@@ -1195,6 +1202,16 @@ private int checkAndAddEntry(int pos, int index)
11951202
if (entryPos + nlen > cen.length - ENDHDR) {
11961203
zerror("invalid CEN header (bad header size)");
11971204
}
1205+
1206+
int elen = CENEXT(cen, pos);
1207+
if (elen > 0 && !disableZip64ExtraFieldValidation) {
1208+
long extraStartingOffset = pos + CENHDR + nlen;
1209+
if ((int)extraStartingOffset != extraStartingOffset) {
1210+
zerror("invalid CEN header (bad extra offset)");
1211+
}
1212+
checkExtraFields(pos, (int)extraStartingOffset, elen);
1213+
}
1214+
11981215
try {
11991216
ZipCoder zcp = zipCoderForPos(pos);
12001217
int hash = zcp.checkedHash(cen, entryPos, nlen);
@@ -1211,6 +1228,119 @@ private int checkAndAddEntry(int pos, int index)
12111228
return nlen;
12121229
}
12131230

1231+
/**
1232+
* Validate the Zip64 Extra block fields
1233+
* @param startingOffset Extra Field starting offset within the CEN
1234+
* @param extraFieldLen Length of this Extra field
1235+
* @throws ZipException If an error occurs validating the Zip64 Extra
1236+
* block
1237+
*/
1238+
private void checkExtraFields(int cenPos, int startingOffset,
1239+
int extraFieldLen) throws ZipException {
1240+
// Extra field Length cannot exceed 65,535 bytes per the PKWare
1241+
// APP.note 4.4.11
1242+
if (extraFieldLen > 0xFFFF) {
1243+
zerror("invalid extra field length");
1244+
}
1245+
// CEN Offset where this Extra field ends
1246+
int extraEndOffset = startingOffset + extraFieldLen;
1247+
if (extraEndOffset > cen.length) {
1248+
zerror("Invalid CEN header (extra data field size too long)");
1249+
}
1250+
int currentOffset = startingOffset;
1251+
while (currentOffset < extraEndOffset) {
1252+
int tag = get16(cen, currentOffset);
1253+
currentOffset += Short.BYTES;
1254+
1255+
int tagBlockSize = get16(cen, currentOffset);
1256+
int tagBlockEndingOffset = currentOffset + tagBlockSize;
1257+
1258+
// The ending offset for this tag block should not go past the
1259+
// offset for the end of the extra field
1260+
if (tagBlockEndingOffset > extraEndOffset) {
1261+
zerror("Invalid CEN header (invalid zip64 extra data field size)");
1262+
}
1263+
currentOffset += Short.BYTES;
1264+
1265+
if (tag == ZIP64_EXTID) {
1266+
// Get the compressed size;
1267+
long csize = CENSIZ(cen, cenPos);
1268+
// Get the uncompressed size;
1269+
long size = CENLEN(cen, cenPos);
1270+
checkZip64ExtraFieldValues(currentOffset, tagBlockSize,
1271+
csize, size);
1272+
}
1273+
currentOffset += tagBlockSize;
1274+
}
1275+
}
1276+
1277+
/**
1278+
* Validate the Zip64 Extended Information Extra Field (0x0001) block
1279+
* size and that the uncompressed size and compressed size field
1280+
* values are not negative.
1281+
* Note: As we do not use the LOC offset or Starting disk number
1282+
* field value we will not validate them
1283+
* @param off the starting offset for the Zip64 field value
1284+
* @param blockSize the size of the Zip64 Extended Extra Field
1285+
* @param csize CEN header compressed size value
1286+
* @param size CEN header uncompressed size value
1287+
* @throws ZipException if an error occurs
1288+
*/
1289+
private void checkZip64ExtraFieldValues(int off, int blockSize, long csize,
1290+
long size)
1291+
throws ZipException {
1292+
byte[] cen = this.cen;
1293+
// Validate the Zip64 Extended Information Extra Field (0x0001)
1294+
// length.
1295+
if (!isZip64ExtBlockSizeValid(blockSize)) {
1296+
zerror("Invalid CEN header (invalid zip64 extra data field size)");
1297+
}
1298+
// Check the uncompressed size is not negative
1299+
// Note we do not need to check blockSize is >= 8 as
1300+
// we know its length is at least 8 from the call to
1301+
// isZip64ExtBlockSizeValid()
1302+
if ((size == ZIP64_MAGICVAL)) {
1303+
if(get64(cen, off) < 0) {
1304+
zerror("Invalid zip64 extra block size value");
1305+
}
1306+
}
1307+
// Check the compressed size is not negative
1308+
if ((csize == ZIP64_MAGICVAL) && (blockSize >= 16)) {
1309+
if (get64(cen, off + 8) < 0) {
1310+
zerror("Invalid zip64 extra block compressed size value");
1311+
}
1312+
}
1313+
}
1314+
1315+
/**
1316+
* Validate the size and contents of a Zip64 extended information field
1317+
* The order of the Zip64 fields is fixed, but the fields MUST
1318+
* only appear if the corresponding LOC or CEN field is set to 0xFFFF:
1319+
* or 0xFFFFFFFF:
1320+
* Uncompressed Size - 8 bytes
1321+
* Compressed Size - 8 bytes
1322+
* LOC Header offset - 8 bytes
1323+
* Disk Start Number - 4 bytes
1324+
* See PKWare APP.Note Section 4.5.3 for more details
1325+
*
1326+
* @param blockSize the Zip64 Extended Information Extra Field size
1327+
* @return true if the extra block size is valid; false otherwise
1328+
*/
1329+
private static boolean isZip64ExtBlockSizeValid(int blockSize) {
1330+
/*
1331+
* As the fields must appear in order, the block size indicates which
1332+
* fields to expect:
1333+
* 8 - uncompressed size
1334+
* 16 - uncompressed size, compressed size
1335+
* 24 - uncompressed size, compressed sise, LOC Header offset
1336+
* 28 - uncompressed size, compressed sise, LOC Header offset,
1337+
* and Disk start number
1338+
*/
1339+
return switch(blockSize) {
1340+
case 8, 16, 24, 28 -> true;
1341+
default -> false;
1342+
};
1343+
}
12141344
private int getEntryHash(int index) { return entries[index]; }
12151345
private int getEntryNext(int index) { return entries[index + 1]; }
12161346
private int getEntryPos(int index) { return entries[index + 2]; }

src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -3063,6 +3063,11 @@ private void readExtra(ZipFileSystem zipfs) throws IOException {
30633063
if (extra == null)
30643064
return;
30653065
int elen = extra.length;
3066+
// Extra field Length cannot exceed 65,535 bytes per the PKWare
3067+
// APP.note 4.4.11
3068+
if (elen > 0xFFFF) {
3069+
throw new ZipException("invalid extra field length");
3070+
}
30663071
int off = 0;
30673072
int newOff = 0;
30683073
boolean hasZip64LocOffset = false;
@@ -3072,26 +3077,40 @@ private void readExtra(ZipFileSystem zipfs) throws IOException {
30723077
int tag = SH(extra, pos);
30733078
int sz = SH(extra, pos + 2);
30743079
pos += 4;
3075-
if (pos + sz > elen) // invalid data
3076-
break;
3080+
if (pos + sz > elen) { // invalid data
3081+
throw new ZipException("Invalid CEN header (invalid zip64 extra data field size)");
3082+
}
30773083
switch (tag) {
30783084
case EXTID_ZIP64 :
3085+
// Check to see if we have a valid block size
3086+
if (!isZip64ExtBlockSizeValid(sz)) {
3087+
throw new ZipException("Invalid CEN header (invalid zip64 extra data field size)");
3088+
}
30793089
if (size == ZIP64_MINVAL) {
30803090
if (pos + 8 > elen) // invalid zip64 extra
30813091
break; // fields, just skip
30823092
size = LL(extra, pos);
3093+
if (size < 0) {
3094+
throw new ZipException("Invalid zip64 extra block size value");
3095+
}
30833096
pos += 8;
30843097
}
30853098
if (csize == ZIP64_MINVAL) {
30863099
if (pos + 8 > elen)
30873100
break;
30883101
csize = LL(extra, pos);
3102+
if (csize < 0) {
3103+
throw new ZipException("Invalid zip64 extra block compressed size value");
3104+
}
30893105
pos += 8;
30903106
}
30913107
if (locoff == ZIP64_MINVAL) {
30923108
if (pos + 8 > elen)
30933109
break;
30943110
locoff = LL(extra, pos);
3111+
if (locoff < 0) {
3112+
throw new ZipException("Invalid zip64 extra block LOC offset value");
3113+
}
30953114
}
30963115
break;
30973116
case EXTID_NTFS:
@@ -3149,6 +3168,36 @@ private void readExtra(ZipFileSystem zipfs) throws IOException {
31493168
extra = null;
31503169
}
31513170

3171+
/**
3172+
* Validate the size and contents of a Zip64 extended information field
3173+
* The order of the Zip64 fields is fixed, but the fields MUST
3174+
* only appear if the corresponding LOC or CEN field is set to 0xFFFF:
3175+
* or 0xFFFFFFFF:
3176+
* Uncompressed Size - 8 bytes
3177+
* Compressed Size - 8 bytes
3178+
* LOC Header offset - 8 bytes
3179+
* Disk Start Number - 4 bytes
3180+
* See PKWare APP.Note Section 4.5.3 for more details
3181+
*
3182+
* @param blockSize the Zip64 Extended Information Extra Field size
3183+
* @return true if the extra block size is valid; false otherwise
3184+
*/
3185+
private static boolean isZip64ExtBlockSizeValid(int blockSize) {
3186+
/*
3187+
* As the fields must appear in order, the block size indicates which
3188+
* fields to expect:
3189+
* 8 - uncompressed size
3190+
* 16 - uncompressed size, compressed size
3191+
* 24 - uncompressed size, compressed sise, LOC Header offset
3192+
* 28 - uncompressed size, compressed sise, LOC Header offset,
3193+
* and Disk start number
3194+
*/
3195+
return switch(blockSize) {
3196+
case 8, 16, 24, 28 -> true;
3197+
default -> false;
3198+
};
3199+
}
3200+
31523201
/**
31533202
* Read the LOC extra field to obtain the Info-ZIP Extended Timestamp fields
31543203
* @param zipfs The Zip FS to use

test/jdk/java/util/zip/TestExtraTime.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -29,6 +29,8 @@
2929
*/
3030

3131
import java.io.*;
32+
import java.nio.ByteBuffer;
33+
import java.nio.ByteOrder;
3234
import java.nio.file.Files;
3335
import java.nio.file.Path;
3436
import java.nio.file.Paths;
@@ -59,7 +61,14 @@ public static void main(String[] args) throws Throwable{
5961

6062
TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");
6163

62-
for (byte[] extra : new byte[][] { null, new byte[] {1, 2, 3}}) {
64+
// A structurally valid extra data example
65+
byte[] sampleExtra = new byte[Short.BYTES*3];
66+
ByteBuffer.wrap(sampleExtra).order(ByteOrder.LITTLE_ENDIAN)
67+
.putShort((short) 123) // ID: 123
68+
.putShort((short) Short.BYTES) // Size: 2
69+
.putShort((short) 42); // Data: Two bytes
70+
71+
for (byte[] extra : new byte[][] { null, sampleExtra}) {
6372

6473
// ms-dos 1980 epoch problem
6574
test0(FileTime.from(10, TimeUnit.MILLISECONDS), null, null, null, extra);

test/jdk/java/util/zip/ZipFile/CorruptedZipFiles.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -114,13 +114,13 @@ public static void main(String[] args) throws Exception {
114114
err.println("corrupted CENEXT 1");
115115
bad = good.clone();
116116
bad[cenpos+CENEXT]++;
117-
checkZipException(bad, ".*bad header size.*");
117+
checkZipException(bad, ".*invalid zip64 extra data field size.*");
118118

119119
err.println("corrupted CENEXT 2");
120120
bad = good.clone();
121121
bad[cenpos+CENEXT] = (byte)0xfd;
122122
bad[cenpos+CENEXT+1] = (byte)0xfd;
123-
checkZipException(bad, ".*bad header size.*");
123+
checkZipException(bad, ".*extra data field size too long.*");
124124

125125
err.println("corrupted CENCOM");
126126
bad = good.clone();

0 commit comments

Comments
 (0)