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 bf49bcfb8 Reject out-of-range cpUTF8Prefix in CpBands.parseCpUtf8 
(#800)
bf49bcfb8 is described below

commit bf49bcfb809baedcf23b7eeff59256f4d93830d3
Author: KALI 834X <[email protected]>
AuthorDate: Wed Aug 12 17:51:40 2026 +0530

    Reject out-of-range cpUTF8Prefix in CpBands.parseCpUtf8 (#800)
---
 .../compress/harmony/unpack200/CpBands.java        | 11 +++-
 .../compress/harmony/unpack200/CpBandsTest.java    | 69 ++++++++++++++++++++++
 2 files changed, 78 insertions(+), 2 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/compress/harmony/unpack200/CpBands.java 
b/src/main/java/org/apache/commons/compress/harmony/unpack200/CpBands.java
index 11b5328b6..19d19c095 100644
--- a/src/main/java/org/apache/commons/compress/harmony/unpack200/CpBands.java
+++ b/src/main/java/org/apache/commons/compress/harmony/unpack200/CpBands.java
@@ -703,12 +703,19 @@ private void parseCpUtf8(final InputStream in) throws 
IOException, Pack200Except
         bigSuffixCount = 0;
         for (int i = 1; i < cpUTF8Count; i++) {
             final String lastString = cpUTF8[i - 1];
+            // prefix is decoded with the signed DELTA5 codec, so a corrupt 
archive can make it negative or
+            // larger than the previous string. Reject it here instead of 
letting String.substring throw an
+            // unchecked StringIndexOutOfBoundsException out of the declared 
Pack200Exception contract.
+            final int prefixLength = i > 1 ? prefix[i - 2] : 0;
+            if (prefixLength < 0 || prefixLength > lastString.length()) {
+                throw new Pack200Exception("cpUTF8Prefix value out of range: " 
+ prefixLength);
+            }
             if (suffix[i - 1] == 0) {
                 // The big suffix stuff hasn't been tested, and I'll be
                 // surprised if it works first time w/o errors ...
-                cpUTF8[i] = lastString.substring(0, i > 1 ? prefix[i - 2] : 0) 
+ new String(bigSuffixData[bigSuffixCount++]);
+                cpUTF8[i] = lastString.substring(0, prefixLength) + new 
String(bigSuffixData[bigSuffixCount++]);
             } else {
-                cpUTF8[i] = lastString.substring(0, i > 1 ? prefix[i - 2] : 0) 
+ new String(data, charCount, suffix[i - 1]);
+                cpUTF8[i] = lastString.substring(0, prefixLength) + new 
String(data, charCount, suffix[i - 1]);
                 charCount += suffix[i - 1];
             }
             mapUTF8.put(cpUTF8[i], Integer.valueOf(i));
diff --git 
a/src/test/java/org/apache/commons/compress/harmony/unpack200/CpBandsTest.java 
b/src/test/java/org/apache/commons/compress/harmony/unpack200/CpBandsTest.java
new file mode 100644
index 000000000..f8e28afd8
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/compress/harmony/unpack200/CpBandsTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.harmony.unpack200;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.apache.commons.compress.harmony.pack200.Codec;
+import org.apache.commons.compress.harmony.pack200.Pack200Exception;
+import org.junit.jupiter.api.Test;
+
+class CpBandsTest extends AbstractBandsTest {
+
+    private final class CpUTF8Header extends MockSegmentHeader {
+
+        CpUTF8Header(final Segment segment) {
+            super(segment);
+        }
+
+        @Override
+        public int getCpUTF8Count() {
+            return 3;
+        }
+    }
+
+    private final class CpUTF8Segment extends MockSegment {
+
+        private final SegmentHeader header = new CpUTF8Header(this);
+
+        @Override
+        public SegmentHeader getSegmentHeader() {
+            return header;
+        }
+    }
+
+    @Test
+    void testParseCpUtf8RejectsOutOfRangePrefix() throws Exception {
+        // The cpUTF8Prefix band is decoded with the signed DELTA5 codec, so a 
corrupt archive can make a
+        // prefix larger than the preceding string (or negative). Used 
directly as a String.substring end
+        // index that raised a raw StringIndexOutOfBoundsException out of the 
declared Pack200Exception
+        // contract; it must now be rejected as corrupt input.
+        final CpBands bands = new CpBands(new CpUTF8Segment());
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        baos.write(Codec.DELTA5.encode(new int[] { 1000 }));    // 
cpUTF8Prefix (count cpUTF8Count - 2)
+        baos.write(Codec.UNSIGNED5.encode(new int[] { 1, 1 })); // 
cpUTF8Suffix (count cpUTF8Count - 1)
+        baos.write(Codec.CHAR3.encode(new int[] { 'a', 'b' }));  // 
cp_Utf8_chars
+        final InputStream in = new ByteArrayInputStream(baos.toByteArray());
+        assertThrows(Pack200Exception.class, () -> bands.read(in));
+    }
+}

Reply via email to