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 9d6d7c5ad Fix copyOfRange end index in x0017 strong encryption header
(#786)
9d6d7c5ad is described below
commit 9d6d7c5ada1ef8f1e8f5f9964868013189facadf
Author: KALI 834X <[email protected]>
AuthorDate: Wed Jul 15 23:47:54 2026 +0530
Fix copyOfRange end index in x0017 strong encryption header (#786)
---
.../zip/X0017_StrongEncryptionHeader.java | 20 ++++-----
.../zip/X0017_StrongEncryptionHeaderTest.java | 49 ++++++++++++++++++++++
2 files changed, 57 insertions(+), 12 deletions(-)
diff --git
a/src/main/java/org/apache/commons/compress/archivers/zip/X0017_StrongEncryptionHeader.java
b/src/main/java/org/apache/commons/compress/archivers/zip/X0017_StrongEncryptionHeader.java
index b0f20ae25..d934f74cb 100644
---
a/src/main/java/org/apache/commons/compress/archivers/zip/X0017_StrongEncryptionHeader.java
+++
b/src/main/java/org/apache/commons/compress/archivers/zip/X0017_StrongEncryptionHeader.java
@@ -364,9 +364,8 @@ public void parseFileFormat(final byte[] data, final int
offset, final int lengt
assertMinimalLength(4, length);
final int ivSize = ZipShort.getValue(data, offset);
assertDynamicLengthFits("ivSize", ivSize, 4, length);
- assertMinimalLength(offset + 4, ivSize);
// TODO: what is at offset + 2?
- this.ivData = Arrays.copyOfRange(data, offset + 4, ivSize);
+ this.ivData = Arrays.copyOfRange(data, offset + 4, offset + 4 +
ivSize);
assertMinimalLength(16 + ivSize, length); // up to and including
erdSize
// TODO: what is at offset + 4 + ivSize?
@@ -377,8 +376,7 @@ public void parseFileFormat(final byte[] data, final int
offset, final int lengt
final int erdSize = ZipShort.getValue(data, offset + ivSize + 14);
assertDynamicLengthFits("erdSize", erdSize, ivSize + 16, length);
- assertMinimalLength(offset + ivSize + 16, erdSize);
- this.erdData = Arrays.copyOfRange(data, offset + ivSize + 16, erdSize);
+ this.erdData = Arrays.copyOfRange(data, offset + ivSize + 16, offset +
ivSize + 16 + erdSize);
assertMinimalLength(16 + 4 + ivSize + erdSize, length);
this.rcount = ZipLong.getValue(data, offset + ivSize + 16 + erdSize);
@@ -389,10 +387,8 @@ public void parseFileFormat(final byte[] data, final int
offset, final int lengt
if (vSize < 4) {
throw new ZipException("Invalid X0017_StrongEncryptionHeader:
vSize " + vSize + " is too small to hold CRC");
}
- assertMinimalLength(offset + ivSize + 22 + erdSize, vSize - 4);
- this.vData = Arrays.copyOfRange(data, offset + ivSize + 22 +
erdSize, vSize - 4);
- assertMinimalLength(offset + ivSize + 22 + erdSize + vSize - 4, 4);
- this.vCRC32 = Arrays.copyOfRange(data, offset + ivSize + 22 +
erdSize + vSize - 4, 4);
+ this.vData = Arrays.copyOfRange(data, offset + ivSize + 22 +
erdSize, offset + ivSize + 22 + erdSize + vSize - 4);
+ this.vCRC32 = Arrays.copyOfRange(data, offset + ivSize + 22 +
erdSize + vSize - 4, offset + ivSize + 22 + erdSize + vSize);
} else {
assertMinimalLength(ivSize + 20 + erdSize + 6, length); // up to
and including resize
this.hashAlg =
HashAlgorithm.getAlgorithmByCode(ZipShort.getValue(data, offset + ivSize + 20 +
erdSize));
@@ -405,8 +401,8 @@ public void parseFileFormat(final byte[] data, final int
offset, final int lengt
// TODO: this looks suspicious, 26 rather than 24 would be "after"
resize
assertDynamicLengthFits("resize", resize, ivSize + 24 + erdSize,
length);
//
- this.recipientKeyHash = Arrays.copyOfRange(data, offset + ivSize +
24 + erdSize, this.hashSize);
- this.keyBlob = Arrays.copyOfRange(data, offset + ivSize + 24 +
erdSize + this.hashSize, resize - this.hashSize);
+ this.recipientKeyHash = Arrays.copyOfRange(data, offset + ivSize +
24 + erdSize, offset + ivSize + 24 + erdSize + this.hashSize);
+ this.keyBlob = Arrays.copyOfRange(data, offset + ivSize + 24 +
erdSize + this.hashSize, offset + ivSize + 24 + erdSize + resize);
assertMinimalLength(ivSize + 26 + erdSize + resize + 2, length);
final int vSize = ZipShort.getValue(data, offset + ivSize + 26 +
erdSize + resize);
@@ -416,8 +412,8 @@ public void parseFileFormat(final byte[] data, final int
offset, final int lengt
// TODO: these offsets look even more suspicious, the constant
should likely be 28 rather than 22
assertDynamicLengthFits("vSize", vSize, ivSize + 22 + erdSize +
resize, length);
//
- this.vData = Arrays.copyOfRange(data, offset + ivSize + 22 +
erdSize + resize, vSize - 4);
- this.vCRC32 = Arrays.copyOfRange(data, offset + ivSize + 22 +
erdSize + resize + vSize - 4, 4);
+ this.vData = Arrays.copyOfRange(data, offset + ivSize + 22 +
erdSize + resize, offset + ivSize + 22 + erdSize + resize + vSize - 4);
+ this.vCRC32 = Arrays.copyOfRange(data, offset + ivSize + 22 +
erdSize + resize + vSize - 4, offset + ivSize + 22 + erdSize + resize + vSize);
}
// validate values?
diff --git
a/src/test/java/org/apache/commons/compress/archivers/zip/X0017_StrongEncryptionHeaderTest.java
b/src/test/java/org/apache/commons/compress/archivers/zip/X0017_StrongEncryptionHeaderTest.java
new file mode 100644
index 000000000..6eaf707db
--- /dev/null
+++
b/src/test/java/org/apache/commons/compress/archivers/zip/X0017_StrongEncryptionHeaderTest.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.compress.archivers.zip;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.zip.ZipException;
+
+import org.junit.jupiter.api.Test;
+
+class X0017_StrongEncryptionHeaderTest {
+
+ /**
+ * A crafted 0x0017 local extra field whose {@code hashSize} is smaller
than the offset of the recipient key hash used to make
+ * {@link java.util.Arrays#copyOfRange} throw {@link
IllegalArgumentException} ({@code fromIndex > toIndex}). That runtime exception
is not caught by
+ * {@link ExtraFieldUtils#fillExtraField} (which only maps {@link
ArrayIndexOutOfBoundsException}), so it used to escape parsing uncaught. It
must now be
+ * reported as a {@link ZipException} like every other corrupt extra field.
+ */
+ @Test
+ void testParseFileFormatRejectsCorruptRecipientKeyHash() {
+ // [id 0x0017][len
66][ivSize=8]...[erdSize=28]...[rcount=1]...[hashSize=0][resize=0][vSize=3]
+ final byte[] data = new byte[70];
+ data[0] = 0x17;
+ data[1] = 0x00;
+ data[2] = 66; // field length
+ data[4] = 8; // ivSize
+ data[26] = 28; // erdSize
+ data[56] = 1; // rcount
+ // hashSize @ 62..64 and resize @ 64..66 stay 0
+ data[66] = 3; // vSize, < 4 so parsing stops with a ZipException once
the offsets are read correctly
+ assertThrows(ZipException.class, () -> ExtraFieldUtils.parse(data,
true));
+ }
+}