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
+ }
}