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