This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-compress.git


The following commit(s) were added to refs/heads/master by this push:
     new 48bb67e0c Reject negative ZIP64 offsets in 
positionAtCentralDirectory64 (#794)
48bb67e0c is described below

commit 48bb67e0c2c4823d21840923e768ef8baadc87aa
Author: KALI 834X <[email protected]>
AuthorDate: Sun Aug 9 17:24:21 2026 +0530

    Reject negative ZIP64 offsets in positionAtCentralDirectory64 (#794)
---
 .../commons/compress/archivers/zip/ZipFile.java    | 15 +++++-
 .../compress/archivers/zip/ZipFileTest.java        | 53 ++++++++++++++++++++++
 2 files changed, 67 insertions(+), 1 deletion(-)

diff --git 
a/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java 
b/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
index 5bbb4737d..01273063a 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
@@ -1417,12 +1417,19 @@ private void positionAtCentralDirectory64() throws 
IOException {
             dwordBbuf.rewind();
             IOUtils.readFully(archive, dwordBbuf);
             final long relativeOffsetOfEOCD = 
ZipEightByteInteger.getLongValue(dwordBuf);
+            if (relativeOffsetOfEOCD < 0) {
+                throw new ArchiveException("Broken archive, ZIP64 end of 
central directory locator with negative offset");
+            }
             ((ZipSplitReadOnlySeekableByteChannel) 
archive).position(diskNumberOfEOCD, relativeOffsetOfEOCD);
         } else {
             skipBytes(ZIP64_EOCDL_LOCATOR_OFFSET - ZipConstants.WORD /* 
signature has already been read */);
             dwordBbuf.rewind();
             IOUtils.readFully(archive, dwordBbuf);
-            archive.position(ZipEightByteInteger.getLongValue(dwordBuf));
+            final long relativeOffsetOfEOCD = 
ZipEightByteInteger.getLongValue(dwordBuf);
+            if (relativeOffsetOfEOCD < 0) {
+                throw new ArchiveException("Broken archive, ZIP64 end of 
central directory locator with negative offset");
+            }
+            archive.position(relativeOffsetOfEOCD);
         }
 
         wordBbuf.rewind();
@@ -1442,6 +1449,9 @@ private void positionAtCentralDirectory64() throws 
IOException {
             dwordBbuf.rewind();
             IOUtils.readFully(archive, dwordBbuf);
             centralDirectoryStartRelativeOffset = 
ZipEightByteInteger.getLongValue(dwordBuf);
+            if (centralDirectoryStartRelativeOffset < 0) {
+                throw new ArchiveException("Broken archive, ZIP64 end of 
central directory record with negative central directory offset");
+            }
             ((ZipSplitReadOnlySeekableByteChannel) 
archive).position(centralDirectoryStartDiskNumber, 
centralDirectoryStartRelativeOffset);
         } else {
             skipBytes(ZIP64_EOCD_CFD_LOCATOR_OFFSET - ZipConstants.WORD /* 
signature has already been read */);
@@ -1449,6 +1459,9 @@ private void positionAtCentralDirectory64() throws 
IOException {
             IOUtils.readFully(archive, dwordBbuf);
             centralDirectoryStartDiskNumber = 0;
             centralDirectoryStartRelativeOffset = 
ZipEightByteInteger.getLongValue(dwordBuf);
+            if (centralDirectoryStartRelativeOffset < 0) {
+                throw new ArchiveException("Broken archive, ZIP64 end of 
central directory record with negative central directory offset");
+            }
             archive.position(centralDirectoryStartRelativeOffset);
         }
     }
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java 
b/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java
index 05d211c8e..9c19b0503 100644
--- a/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/zip/ZipFileTest.java
@@ -1124,4 +1124,57 @@ void testZstdInputStreamErrorCloseWhenGc() throws 
Exception {
             }
         }
     }
+
+    private static byte[] createZip64Archive() throws IOException {
+        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(bos)) {
+            zos.setUseZip64(Zip64Mode.Always);
+            zos.putArchiveEntry(new ZipArchiveEntry("a.txt"));
+            zos.write("hello".getBytes(UTF_8));
+            zos.closeArchiveEntry();
+        }
+        return bos.toByteArray();
+    }
+
+    private static int indexOfSignature(final byte[] data, final byte[] 
signature) {
+        for (int i = 0; i + signature.length <= data.length; i++) {
+            int j = 0;
+            while (j < signature.length && data[i + j] == signature[j]) {
+                j++;
+            }
+            if (j == signature.length) {
+                return i;
+            }
+        }
+        return fail("signature not found");
+    }
+
+    /**
+     * The ZIP64 offsets are 8-byte signed values, so a crafted archive can 
make them negative. Feeding such a value straight to
+     * {@link java.nio.channels.SeekableByteChannel#position(long)} used to 
throw a raw {@link IllegalArgumentException} out of the {@link ZipFile} 
constructor,
+     * which only declares {@link IOException}.
+     */
+    @Test
+    void testZip64NegativeOffsetsAreRejected() throws Exception {
+        final byte[] valid = createZip64Archive();
+        // A well-formed ZIP64 archive still opens.
+        try (ZipFile zf = ZipFile.builder().setByteArray(valid).get()) {
+            assertNotNull(zf.getEntry("a.txt"));
+        }
+        // Negative "relative offset of the ZIP64 end of central directory 
record" inside the locator.
+        final byte[] badLocator = valid.clone();
+        writeNegativeLongAt(badLocator, indexOfSignature(badLocator, 
ZipArchiveOutputStream.ZIP64_EOCD_LOC_SIG) + 8);
+        assertThrows(ArchiveException.class, () -> 
ZipFile.builder().setByteArray(badLocator).get());
+        // Negative "offset of start of central directory" inside the ZIP64 
end of central directory record.
+        final byte[] badRecord = valid.clone();
+        writeNegativeLongAt(badRecord, indexOfSignature(badRecord, 
ZipArchiveOutputStream.ZIP64_EOCD_SIG) + 48);
+        assertThrows(ArchiveException.class, () -> 
ZipFile.builder().setByteArray(badRecord).get());
+    }
+
+    private static void writeNegativeLongAt(final byte[] data, final int 
offset) {
+        for (int i = 0; i < 8; i++) {
+            data[offset + i] = 0;
+        }
+        data[offset + 7] = (byte) 0x80; // little-endian sign byte -> the 
8-byte value is negative
+    }
 }

Reply via email to