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 ae0a0c5f Signature.matchGJIdent recurses unboundedly on nested generic 
signatures (Signature.translate) (f020).
ae0a0c5f is described below

commit ae0a0c5fedd307ada0eadc87cc22085f3c291d9b
Author: Gary Gregory <[email protected]>
AuthorDate: Fri Sep 4 17:56:41 2026 -0400

    Signature.matchGJIdent recurses unboundedly on nested generic signatures
    (Signature.translate) (f020).
---
 src/changes/changes.xml                               |  1 +
 .../java/org/apache/bcel/classfile/Signature.java     | 19 ++++++++++++++++---
 .../java/org/apache/bcel/classfile/SignatureTest.java | 13 +++++++++++++
 3 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 6062f03e..1169a340 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -105,6 +105,7 @@ The <action> type attribute can be add,update,fix,remove.
       <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">Class2HTML builds output file paths from the unvalidated class name 
(f015).</action>
       <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>
       <!-- 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/Signature.java 
b/src/main/java/org/apache/bcel/classfile/Signature.java
index 1d40ef7d..68b12c88 100644
--- a/src/main/java/org/apache/bcel/classfile/Signature.java
+++ b/src/main/java/org/apache/bcel/classfile/Signature.java
@@ -81,14 +81,27 @@ public final class Signature extends Attribute {
         return s.startsWith("<") && s.indexOf(':') > 0;
     }
 
+    /**
+     * The maximum nesting depth of a signature accepted by {@link 
#translate(String)}. Guards against a
+     * {@link StackOverflowError} from deeply nested, attacker-supplied 
generic signatures.
+     */
+    private static final int MAX_NESTING_DEPTH = 512;
+
     private static void matchGJIdent(final MyByteArrayInputStream in, final 
StringBuilder buf) {
+        matchGJIdent(in, buf, 0);
+    }
+
+    private static void matchGJIdent(final MyByteArrayInputStream in, final 
StringBuilder buf, final int depth) {
+        if (depth > MAX_NESTING_DEPTH) {
+            throw new IllegalArgumentException("Illegal signature: " + 
in.getData() + " exceeds maximum nesting depth " + MAX_NESTING_DEPTH);
+        }
         int ch;
         matchIdent(in, buf);
         ch = in.read();
         if (ch == '<' || ch == '(') { // Parameterized or method
             // System.out.println("Enter <");
             buf.append((char) ch);
-            matchGJIdent(in, buf);
+            matchGJIdent(in, buf, depth + 1);
             while ((ch = in.read()) != '>' && ch != ')') { // List of 
parameters
                 if (ch == -1) {
                     throw new IllegalArgumentException("Illegal signature: " + 
in.getData() + " reaching EOF");
@@ -96,7 +109,7 @@ public final class Signature extends Attribute {
                 // System.out.println("Still no >");
                 buf.append(", ");
                 in.unread();
-                matchGJIdent(in, buf); // Recursive call
+                matchGJIdent(in, buf, depth + 1); // Recursive call
             }
             // System.out.println("Exit >");
             buf.append((char) ch);
@@ -106,7 +119,7 @@ public final class Signature extends Attribute {
         ch = in.read();
         if (identStart(ch)) {
             in.unread();
-            matchGJIdent(in, buf);
+            matchGJIdent(in, buf, depth + 1);
         } else if (ch == ')') {
             in.unread();
         } else if (ch != ';') {
diff --git a/src/test/java/org/apache/bcel/classfile/SignatureTest.java 
b/src/test/java/org/apache/bcel/classfile/SignatureTest.java
index 94697134..dac7fc8a 100644
--- a/src/test/java/org/apache/bcel/classfile/SignatureTest.java
+++ b/src/test/java/org/apache/bcel/classfile/SignatureTest.java
@@ -41,6 +41,19 @@ class SignatureTest extends AbstractTest {
         assertThrowsExactly(IllegalArgumentException.class, () -> 
Signature.translate("<>"));
     }
 
+    /**
+     * Deeply nested attacker-supplied signatures must fail fast instead of 
overflowing the stack.
+     */
+    @Test
+    void testDeeplyNestedSignature() {
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < 100_000; i++) {
+            sb.append("A<");
+        }
+        final String deep = sb.toString();
+        assertThrowsExactly(IllegalArgumentException.class, () -> 
Signature.translate(deep));
+    }
+
     @Test
     void testMap() throws Exception {
         final JavaClass jc = Repository.lookupClass(Map.class);

Reply via email to