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-bcel.git


The following commit(s) were added to refs/heads/master by this push:
     new 53c52fa4 Utility.decode fixed 3x buffer breaks the encode/decode round 
trip and throws unchecked AIOOBE (f021).
53c52fa4 is described below

commit 53c52fa4220049325f4d619d1d4373e33106b1cc
Author: Gary Gregory <[email protected]>
AuthorDate: Fri Sep 4 18:16:39 2026 -0400

    Utility.decode fixed 3x buffer breaks the encode/decode round trip and
    throws unchecked AIOOBE (f021).
---
 src/changes/changes.xml                            |  1 +
 .../java/org/apache/bcel/classfile/Utility.java    | 26 ++++++++------
 .../bcel/classfile/UtilityEncodeDecodeTest.java    | 40 ++++++++++++++++++++++
 3 files changed, 57 insertions(+), 10 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 1169a340..d1763bc6 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -106,6 +106,7 @@ The <action> type attribute can be add,update,fix,remove.
       <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">ClassPath.getBytes() sizes its buffer from the forged ZIP 
uncompressed-size field (f016).</action>
       <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">PMG attribute ignores declared length, enabling BCEL-vs-JVM parse 
divergence (f019).</action>
       <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">Signature.matchGJIdent recurses unboundedly on nested generic 
signatures (Signature.translate) (f020).</action>
+      <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">Utility.decode fixed 3x buffer breaks the encode/decode round trip and 
throws unchecked AIOOBE (f021).</action>
       <!-- ADD -->
       <action                  type="add" dev="ggregory" due-to="nbauma109, 
Gary Gregory">Add support for permitted subclasses #493.</action>
       <action                  type="add" dev="ggregory" due-to="nbauma109, 
Gary Gregory">Add RecordComponentInfo.getAttribute(byte tag)#494.</action>
diff --git a/src/main/java/org/apache/bcel/classfile/Utility.java 
b/src/main/java/org/apache/bcel/classfile/Utility.java
index d0a5482d..244954e7 100644
--- a/src/main/java/org/apache/bcel/classfile/Utility.java
+++ b/src/main/java/org/apache/bcel/classfile/Utility.java
@@ -31,13 +31,14 @@ import java.io.PrintWriter;
 import java.io.Reader;
 import java.io.Writer;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
 
 import org.apache.bcel.Const;
 import org.apache.bcel.util.ByteSequence;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.BoundedInputStream;
 import org.apache.commons.lang3.ArrayFill;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -54,6 +55,12 @@ public abstract class Utility {
      */
     private static final int MAX_SIGNATURE_NESTING = 512;
 
+    /**
+     * The maximum number of bytes that {@link #decode(String, boolean)} will 
decompress. Guards against decompression bombs: the compressed input is
+     * attacker-controlled and a small input can decompress to an enormous 
size.
+     */
+    private static final int MAX_DECODED_LENGTH = 64 * 1024 * 1024;
+
     /**
      * Decode characters into bytes. Used by <a 
href="Utility.html#decode(java.lang.String, boolean)">decode()</a>
      */
@@ -688,10 +695,10 @@ public abstract class Utility {
      * @param s The string to convert.
      * @param uncompress use gzip to uncompress the stream of bytes.
      * @return The decoded byte array.
-     * @throws IOException Thrown if there's a gzip exception.
+     * @throws IOException Thrown if there's a gzip exception or the 
decompressed data exceeds {@code MAX_DECODED_LENGTH}.
      */
     public static byte[] decode(final String s, final boolean uncompress) 
throws IOException {
-        byte[] bytes;
+        final byte[] bytes;
         try (JavaReader jr = new JavaReader(new 
CharArrayReader(s.toCharArray())); ByteArrayOutputStream bos = new 
ByteArrayOutputStream()) {
             int ch;
             while ((ch = jr.read()) >= 0) {
@@ -700,14 +707,13 @@ public abstract class Utility {
             bytes = bos.toByteArray();
         }
         if (uncompress) {
-            final GZIPInputStream gis = new GZIPInputStream(new 
ByteArrayInputStream(bytes));
-            final byte[] tmp = new byte[bytes.length * 3]; // Rough estimate
-            int count = 0;
-            int b;
-            while ((b = gis.read()) >= 0) {
-                tmp[count++] = (byte) b;
+            // @formatter:off
+            try (BoundedInputStream gis = BoundedInputStream.builder()
+                    .setInputStream(new GZIPInputStream(new 
ByteArrayInputStream(bytes)))
+                    .setMaxCount(MAX_DECODED_LENGTH + 1).get()) {
+                return IOUtils.toByteArray(gis);
             }
-            bytes = Arrays.copyOf(tmp, count);
+            // @formatter:on
         }
         return bytes;
     }
diff --git 
a/src/test/java/org/apache/bcel/classfile/UtilityEncodeDecodeTest.java 
b/src/test/java/org/apache/bcel/classfile/UtilityEncodeDecodeTest.java
new file mode 100644
index 00000000..87ceb06c
--- /dev/null
+++ b/src/test/java/org/apache/bcel/classfile/UtilityEncodeDecodeTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.bcel.classfile;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link Utility}.
+ */
+class UtilityEncodeDecodeTest {
+
+    /**
+     * Zero-filled data compresses far better than the fixed 3:1 ratio the 
decode buffer used to assume; the encode/decode round trip must still hold.
+     */
+    @Test
+    void testEncodeDecodeRoundTripHighCompressionRatio() throws Exception {
+        final byte[] original = new byte[4096];
+        final String encoded = Utility.encode(original, true);
+        assertArrayEquals(original, Utility.decode(encoded, true));
+    }
+}

Reply via email to