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

ggregory 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 517763b41 [COMPRESS-713] Unchecked pre-decremental notation in 
for-loop as array index causes ArrayOutOfBounds access (#755)
517763b41 is described below

commit 517763b41defe0a885c10cd5b8e78c1a6bcc5b1e
Author: Gary Gregory <[email protected]>
AuthorDate: Tue Dec 9 16:39:13 2025 -0500

    [COMPRESS-713] Unchecked pre-decremental notation in for-loop as array 
index causes ArrayOutOfBounds access (#755)
    
    * [COMPRESS-713] Unchecked pre-decremental notation in for-loop as array
    index causes ArrayOutOfBounds access
    
    Use a general try-catch up the call stack instead of checking for a bad
    index in a possibly tight loop in
    LZWInputStream.expandCodeToOutputStack(int, boolean)
    
    * Properly resets `previousCode` upon clear
    
    * Adapt expected exception
    
    * fix: checkstyle
    
    ---------
    
    Co-authored-by: Piotr P. Karwasz <[email protected]>
---
 .../archivers/zip/UnshrinkingInputStream.java      |  3 +
 .../archivers/zip/ZipArchiveInputStream.java       |  6 +-
 .../archivers/zip/UnshrinkingInputStreamTest.java  | 79 ++++++++++++++++++++++
 .../compress/archivers/zip/ZipCompress713Test.java | 57 ++++++++++++++++
 4 files changed, 144 insertions(+), 1 deletion(-)

diff --git 
a/src/main/java/org/apache/commons/compress/archivers/zip/UnshrinkingInputStream.java
 
b/src/main/java/org/apache/commons/compress/archivers/zip/UnshrinkingInputStream.java
index 95f2a0c73..fbcc626cb 100644
--- 
a/src/main/java/org/apache/commons/compress/archivers/zip/UnshrinkingInputStream.java
+++ 
b/src/main/java/org/apache/commons/compress/archivers/zip/UnshrinkingInputStream.java
@@ -128,5 +128,8 @@ private void partialClear() {
                 setPrefix(i, UNUSED_PREFIX);
             }
         }
+        // Resets previous code
+        // See COMPRESS-713
+        resetPreviousCode();
     }
 }
diff --git 
a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
 
b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
index 7c77fefcc..d24048786 100644
--- 
a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
+++ 
b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
@@ -1076,7 +1076,11 @@ public int read(final byte[] buffer, final int offset, 
final int length) throws
             read = readDeflated(buffer, offset, length);
         } else if (method == ZipMethod.UNSHRINKING.getCode() || method == 
ZipMethod.IMPLODING.getCode() || method == ZipMethod.ENHANCED_DEFLATED.getCode()
                 || method == ZipMethod.BZIP2.getCode() || 
ZipMethod.isZstd(method) || method == ZipMethod.XZ.getCode()) {
-            read = current.checkInputStream().read(buffer, offset, length);
+            try {
+                read = current.checkInputStream().read(buffer, offset, length);
+            } catch (final RuntimeException e) {
+                throw new ArchiveException(e);
+            }
         } else {
             throw new 
UnsupportedZipFeatureException(ZipMethod.getMethodByCode(method), 
current.entry);
         }
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/zip/UnshrinkingInputStreamTest.java
 
b/src/test/java/org/apache/commons/compress/archivers/zip/UnshrinkingInputStreamTest.java
new file mode 100644
index 000000000..c22e452af
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/compress/archivers/zip/UnshrinkingInputStreamTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.apache.commons.compress.compressors.CompressorException;
+import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.Issue;
+
+class UnshrinkingInputStreamTest {
+
+    private static final int CONTROL_CODE = 256;
+    private static final int CLEAR_SUBCODE = 2;
+    private static final int NEW_CODE = 257;
+
+    /**
+     * Encodes a sequence of 9-bit codes into bytes, LSB-first, as used by
+     * PKZIP's “Shrunk” (Unshrink) method.
+     */
+    private static byte[] encodeLsbFirst9Bit(final int... codes) {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        int bitBuffer = 0;
+        int bitCount = 0;
+
+        for (int code : codes) {
+            bitBuffer |= (code & 0x1FF) << bitCount;
+            bitCount += 9;
+
+            while (bitCount >= 8) {
+                out.write(bitBuffer & 0xFF);
+                bitBuffer >>>= 8;
+                bitCount -= 8;
+            }
+        }
+
+        if (bitCount > 0) {
+            out.write(bitBuffer & 0xFF);
+        }
+        return out.toByteArray();
+    }
+
+    @Test
+    @Issue("COMPRESS-713")
+    public void testCompress713() throws IOException {
+        final byte[] data = encodeLsbFirst9Bit(
+                'A',
+                'B',           // defines 257 = "AB"
+                NEW_CODE,      // 257  ("AB")
+                CONTROL_CODE,  // 256  (control)
+                CLEAR_SUBCODE, // subcode 2 (partial clear)
+                NEW_CODE       // 257 again, now undefined and treated as 
prevCode + firstChar(prevCode)
+        );
+        try (UnshrinkingInputStream inputStream = new 
UnshrinkingInputStream(new ByteArrayInputStream(data))) {
+            assertThrows(CompressorException.class, () -> inputStream.read(new 
byte[1024]));
+        }
+    }
+
+}
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/zip/ZipCompress713Test.java
 
b/src/test/java/org/apache/commons/compress/archivers/zip/ZipCompress713Test.java
new file mode 100644
index 000000000..9e8833020
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/compress/archivers/zip/ZipCompress713Test.java
@@ -0,0 +1,57 @@
+/*
+ * 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.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+
+import org.apache.commons.compress.CompressException;
+import org.apache.commons.io.function.IOConsumer;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests https://issues.apache.org/jira/browse/COMPRESS-713
+ */
+public class ZipCompress713Test {
+
+    @Test
+    public void testIllegalArrayIndex() throws IOException {
+        final byte[] data = { 80, 75, 3, 4, 19, 7, 0, 1, 1, 0, -1, 1, 120, 8, 
84, -99, 3, 48, 45, 1, 119, -70, 110, 61, 65, 104, 0, 0, 0, 0, 59, -4, -1, -1, 
-1,
+                -1, -33, 0, -1, 0, 5, 0, -1, -1, -1, -1, -1, -1, -1, 0, 122 };
+        try (ZipArchiveInputStream inputStream = new ZipArchiveInputStream(new 
ByteArrayInputStream(data))) {
+            inputStream.getNextEntry();
+            // Either the compressor fails properly or the archiver intercepts 
the unchecked exception
+            assertThrows(CompressException.class, () -> inputStream.read(new 
byte[1024]));
+            assertThrows(EOFException.class, () -> inputStream.getNextEntry());
+        }
+    }
+
+    @Test
+    public void testTruncated() throws IOException {
+        final byte[] data = { 80, 75, 3, 4, 19, 7, 0, 1, 1, 0, -1, 1, 120, 8, 
84, -99, 3, 48, 45, 1, 119, -70, 110, 61, 65, 104, 0, 0, 0, 0, 59, -4, -1, -1, 
-1,
+                -1, -33, 0, -1, 0, 5, 0, -1, -1, -1, -1, -1, -1, -1, 0, 122 };
+        try (ZipArchiveInputStream inputStream = new ZipArchiveInputStream(new 
ByteArrayInputStream(data))) {
+            assertThrows(EOFException.class, () -> 
inputStream.forEach(IOConsumer.noop()));
+        }
+    }
+}

Reply via email to