From 773cb8c3b524a4acf2793f91c557845810a192fd Mon Sep 17 00:00:00 2001 From: Thomas Fitzsimmons Date: Fri, 26 Jun 2026 19:32:43 -0400 Subject: [PATCH] 8186464: ZipFile cannot read some InfoZip ZIP64 zip files Co-authored-by: Andrew John Hughes Backport-of: 02b9452ed39eccdfe3210e65b17d4759333c0f15 --- .../src/com/sun/nio/zipfs/ZipFileSystem.java | 66 +++++++++++-------- jdk/src/share/native/java/util/zip/zip_util.c | 25 +++++-- jdk/test/java/util/zip/ZipFile/ReadZip.java | 65 +++++++++++++++++- 3 files changed, 118 insertions(+), 38 deletions(-) diff --git a/jdk/src/share/demo/nio/zipfs/src/com/sun/nio/zipfs/ZipFileSystem.java b/jdk/src/share/demo/nio/zipfs/src/com/sun/nio/zipfs/ZipFileSystem.java index d3c68e5e06a..fd249461e8d 100644 --- a/jdk/src/share/demo/nio/zipfs/src/com/sun/nio/zipfs/ZipFileSystem.java +++ b/jdk/src/share/demo/nio/zipfs/src/com/sun/nio/zipfs/ZipFileSystem.java @@ -92,6 +92,7 @@ public class ZipFileSystem extends FileSystem { private final boolean createNew; // create a new zip if not exists private static final boolean isWindows = System.getProperty("os.name").startsWith("Windows"); + private final boolean forceEnd64; // a threshold, in bytes, to decide whether to create a temp file // for outputstream of a zip entry @@ -112,12 +113,13 @@ public class ZipFileSystem extends FileSystem { if (this.defaultDir.charAt(0) != '/') throw new IllegalArgumentException("default dir should be absolute"); + this.forceEnd64 = "true".equals(env.get("forceZIP64End")); this.provider = provider; this.zfpath = zfpath; if (Files.notExists(zfpath)) { if (createNew) { try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) { - new END().write(os, 0); + new END().write(os, 0, forceEnd64); } } else { throw new FileSystemNotFoundException(zfpath.toString()); @@ -1014,28 +1016,36 @@ private END findEND() throws IOException end.cenoff = ENDOFF(buf); end.comlen = ENDCOM(buf); end.endpos = pos + i; - if (end.cenlen == ZIP64_MINVAL || - end.cenoff == ZIP64_MINVAL || - end.centot == ZIP64_MINVAL32) - { - // need to find the zip64 end; - byte[] loc64 = new byte[ZIP64_LOCHDR]; - if (readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR) - != loc64.length) { - return end; - } - long end64pos = ZIP64_LOCOFF(loc64); - byte[] end64buf = new byte[ZIP64_ENDHDR]; - if (readFullyAt(end64buf, 0, end64buf.length, end64pos) - != end64buf.length) { - return end; - } - // end64 found, re-calcualte everything. - end.cenlen = ZIP64_ENDSIZ(end64buf); - end.cenoff = ZIP64_ENDOFF(end64buf); - end.centot = (int)ZIP64_ENDTOT(end64buf); // assume total < 2g - end.endpos = end64pos; + // try if there is zip64 end; + byte[] loc64 = new byte[ZIP64_LOCHDR]; + if (end.endpos < ZIP64_LOCHDR || + readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR) + != loc64.length || + !locator64SigAt(loc64, 0)) { + return end; + } + long end64pos = ZIP64_LOCOFF(loc64); + byte[] end64buf = new byte[ZIP64_ENDHDR]; + if (readFullyAt(end64buf, 0, end64buf.length, end64pos) + != end64buf.length || + !end64SigAt(end64buf, 0)) { + return end; + } + // end64 found, + long cenlen64 = ZIP64_ENDSIZ(end64buf); + long cenoff64 = ZIP64_ENDOFF(end64buf); + long centot64 = ZIP64_ENDTOT(end64buf); + // double-check + if (cenlen64 != end.cenlen && end.cenlen != ZIP64_MINVAL || + cenoff64 != end.cenoff && end.cenoff != ZIP64_MINVAL || + centot64 != end.centot && end.centot != ZIP64_MINVAL32) { + return end; } + // to use the end64 values + end.cenlen = cenlen64; + end.cenoff = cenoff64; + end.centot = (int)centot64; // assume total < 2g + end.endpos = end64pos; return end; } } @@ -1201,7 +1211,7 @@ private long copyLOCEntry(Entry e, boolean updateHeader, // sync the zip file system, if there is any udpate private void sync() throws IOException { - //System.out.printf("->sync(%s) starting....!%n", toString()); + // System.out.printf("->sync(%s) starting....!%n", toString()); // check ex-closer if (!exChClosers.isEmpty()) { for (ExChannelCloser ecc : exChClosers) { @@ -1292,7 +1302,7 @@ private void sync() throws IOException { } end.centot = elist.size(); end.cenlen = written - end.cenoff; - end.write(os, written); + end.write(os, written, forceEnd64); } if (!streams.isEmpty()) { // @@ -1849,8 +1859,8 @@ static class END { long endpos; int disktot; - void write(OutputStream os, long offset) throws IOException { - boolean hasZip64 = false; + void write(OutputStream os, long offset, boolean forceEnd64) throws IOException { + boolean hasZip64 = forceEnd64; // false; long xlen = cenlen; long xoff = cenoff; if (xlen >= ZIP64_MINVAL) { @@ -1875,8 +1885,8 @@ void write(OutputStream os, long offset) throws IOException { writeShort(os, 45); // version needed to extract writeInt(os, 0); // number of this disk writeInt(os, 0); // central directory start disk - writeLong(os, centot); // number of directory entires on disk - writeLong(os, centot); // number of directory entires + writeLong(os, centot); // number of directory entries on disk + writeLong(os, centot); // number of directory entries writeLong(os, cenlen); // length of central directory writeLong(os, cenoff); // offset of central directory diff --git a/jdk/src/share/native/java/util/zip/zip_util.c b/jdk/src/share/native/java/util/zip/zip_util.c index a5f164f750b..30596d5baf6 100644 --- a/jdk/src/share/native/java/util/zip/zip_util.c +++ b/jdk/src/share/native/java/util/zip/zip_util.c @@ -385,6 +385,9 @@ findEND64(jzfile *zip, void *end64buf, jlong endpos) { char loc64[ZIP64_LOCHDR]; jlong end64pos; + if (endpos < ZIP64_LOCHDR) { + return -1; + } if (readFullyAt(zip->zfd, loc64, ZIP64_LOCHDR, endpos - ZIP64_LOCHDR) == -1) { return -1; // end64 locator not found } @@ -567,6 +570,7 @@ readCEN(jzfile *zip, jint knownTotal) { /* Following are unsigned 32-bit */ jlong endpos, end64pos, cenpos, cenlen, cenoff, total64; + jlong cenlen64, cenoff64, centot64; /* Following are unsigned 16-bit */ jint total, tablelen, i, j; unsigned char *cenbuf = NULL; @@ -594,13 +598,20 @@ readCEN(jzfile *zip, jint knownTotal) cenlen = ENDSIZ(endbuf); cenoff = ENDOFF(endbuf); total = ENDTOT(endbuf); - if (cenlen == ZIP64_MAGICVAL || cenoff == ZIP64_MAGICVAL || - total == ZIP64_MAGICCOUNT) { - unsigned char end64buf[ZIP64_ENDHDR]; - if ((end64pos = findEND64(zip, end64buf, endpos)) != -1) { - cenlen = ZIP64_ENDSIZ(end64buf); - cenoff = ZIP64_ENDOFF(end64buf); - total64 = ZIP64_ENDTOT(end64buf); + unsigned char end64buf[ZIP64_ENDHDR]; + if ((end64pos = findEND64(zip, end64buf, endpos)) != -1) { + // end64 candidate found, + cenlen64 = ZIP64_ENDSIZ(end64buf); + cenoff64 = ZIP64_ENDOFF(end64buf); + centot64 = ZIP64_ENDTOT(end64buf); + // double-check + if ((cenlen64 == cenlen || cenlen == ZIP64_MAGICVAL) && + (cenoff64 == cenoff || cenoff == ZIP64_MAGICVAL) && + (centot64 == total || total == ZIP64_MAGICCOUNT)) { + // to use the end64 values + cenlen = cenlen64; + cenoff = cenoff64; + total64 = centot64; /* ZIP64 size, offset and total-count fields are unsigned 64-bit * values. Sizes and offsets that do not fit in signed jlong * (i.e., >= 2^63), or total values that do not fit in jint, are diff --git a/jdk/test/java/util/zip/ZipFile/ReadZip.java b/jdk/test/java/util/zip/ZipFile/ReadZip.java index ffe8a8ed712..9b380003893 100644 --- a/jdk/test/java/util/zip/ZipFile/ReadZip.java +++ b/jdk/test/java/util/zip/ZipFile/ReadZip.java @@ -22,7 +22,7 @@ */ /* @test - * @bug 4241361 4842702 4985614 6646605 5032358 6923692 6233323 8144977 8184993 + * @bug 4241361 4842702 4985614 6646605 5032358 6923692 6233323 8144977 8184993 8186464 * @summary Make sure we can read a zip file. @key randomness * @run main/othervm ReadZip @@ -31,12 +31,24 @@ */ import java.io.*; +import java.net.URI; import java.nio.file.Files; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.zip.*; +import sun.misc.IOUtils; + +import static java.nio.charset.StandardCharsets.US_ASCII; + public class ReadZip { private static void unreached (Object o) throws Exception @@ -144,8 +156,6 @@ public static void main(String args[]) throws Exception { newZip.delete(); } - - // Throw a FNF exception when read a non-existing zip file try { unreached (new ZipFile( new File(System.getProperty("test.src", "."), @@ -153,5 +163,54 @@ public static void main(String args[]) throws Exception { + String.valueOf(new java.util.Random().nextInt()) + ".zip"))); } catch (FileNotFoundException fnfe) {} + + // read a zip file with ZIP64 end + Path path = Paths.get(System.getProperty("test.dir", ""), "end64.zip"); + try { + URI uri = URI.create("jar:" + path.toUri()); + Map env = new HashMap<>(); + env.put("create", "true"); + env.put("forceZIP64End", "true"); + try (FileSystem fs = FileSystems.newFileSystem(uri, env)) { + Files.write(fs.getPath("hello"), "hello".getBytes()); + } + try (ZipFile zf = new ZipFile(path.toFile())) { + if (!"hello".equals(new String(IOUtils.readAllBytes(zf.getInputStream(new ZipEntry("hello"))), + US_ASCII))) + throw new RuntimeException("zipfile: read entry failed"); + } catch (IOException x) { + throw new RuntimeException("zipfile: zip64 end failed"); + } + try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap())) { + if (!"hello".equals(new String(Files.readAllBytes(fs.getPath("hello"))))) + throw new RuntimeException("zipfs: read entry failed"); + } catch (IOException x) { + throw new RuntimeException("zipfile: zip64 end failed"); + } + } finally { + Files.deleteIfExists(path); + } + + // read a zip file created via "echo hello | zip dst.zip -", which uses + // ZIP64 end record + if (Files.notExists(Paths.get("/usr/bin/zip"))) + return; + try { + Process zip = new ProcessBuilder("zip", path.toString().toString(), "-").start(); + OutputStream os = zip.getOutputStream(); + os.write("hello".getBytes(US_ASCII)); + os.close(); + zip.waitFor(); + if (zip.exitValue() == 0 && Files.exists(path)) { + try (ZipFile zf = new ZipFile(path.toFile())) { + if (!"hello".equals(new String(IOUtils.readAllBytes(zf.getInputStream(new ZipEntry("-")))))) + throw new RuntimeException("zipfile: read entry failed"); + } catch (IOException x) { + throw new RuntimeException("zipfile: zip64 end failed"); + } + } + } finally { + Files.deleteIfExists(path); + } } }