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 7fe29408 BCELifier interpolates attacker class/package names into 
generated Java source unescaped (f022).
7fe29408 is described below

commit 7fe29408bd6e04705f02edbec1ffbd6bd50d938f
Author: Gary Gregory <[email protected]>
AuthorDate: Fri Sep 4 18:19:54 2026 -0400

    BCELifier interpolates attacker class/package names into generated Java
    source unescaped (f022).
---
 src/changes/changes.xml                            |  1 +
 src/main/java/org/apache/bcel/util/BCELifier.java  | 35 ++++++++++++++++++++--
 .../java/org/apache/bcel/util/BCELifierTest.java   | 12 ++++++++
 3 files changed, 46 insertions(+), 2 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index d1763bc6..da811fd8 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -107,6 +107,7 @@ The <action> type attribute can be add,update,fix,remove.
       <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>
+      <action                  type="fix" dev="ggregory" due-to="Gary 
Gregory">BCELifier interpolates attacker class/package names into generated 
Java source unescaped (f022).</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/util/BCELifier.java 
b/src/main/java/org/apache/bcel/util/BCELifier.java
index a280c5d5..ab869363 100644
--- a/src/main/java/org/apache/bcel/util/BCELifier.java
+++ b/src/main/java/org/apache/bcel/util/BCELifier.java
@@ -82,6 +82,37 @@ public class BCELifier extends 
org.apache.bcel.classfile.EmptyVisitor {
         return escaped;
     }
 
+    /**
+     * Checks that a name from the parsed class file is a dotted sequence of 
valid Java identifiers before it is
+     * emitted in identifier position of the generated source. The class file 
format allows characters in names (for
+     * example braces, parentheses or newlines) that the Java language does 
not, so an unchecked name from a crafted
+     * class file could inject arbitrary code into the generated program.
+     *
+     * @param name the class or package name to check.
+     * @return {@code name} if it is safe to emit as a Java identifier.
+     * @throws IllegalArgumentException if the name is not a dotted sequence 
of valid Java identifiers.
+     */
+    private static String checkJavaName(final String name) {
+        boolean expectStart = true;
+        for (int i = 0; i < name.length(); i++) {
+            final char ch = name.charAt(i);
+            if (expectStart) {
+                if (!Character.isJavaIdentifierStart(ch)) {
+                    throw new IllegalArgumentException("Invalid Java 
identifier in class file: " + Utility.convertString(name));
+                }
+                expectStart = false;
+            } else if (ch == '.') {
+                expectStart = true;
+            } else if (!Character.isJavaIdentifierPart(ch)) {
+                throw new IllegalArgumentException("Invalid Java identifier in 
class file: " + Utility.convertString(name));
+            }
+        }
+        if (expectStart) {
+            throw new IllegalArgumentException("Invalid Java identifier in 
class file: " + Utility.convertString(name));
+        }
+        return name;
+    }
+
     // Needs to be accessible from unit test code
     static JavaClass getJavaClass(final String name) throws 
ClassNotFoundException, IOException {
         JavaClass javaClass;
@@ -218,7 +249,7 @@ public class BCELifier extends 
org.apache.bcel.classfile.EmptyVisitor {
     }
 
     private void printMain() {
-        final String className = clazz.getClassName();
+        final String className = checkJavaName(clazz.getClassName());
         printWriter.println("  public static void main(String[] args) throws 
Exception {");
         printWriter.println("    " + className + "Creator creator = new " + 
className + "Creator();");
         printWriter.println("    creator.create(new FileOutputStream(\"" + 
Utility.convertString(className) + ".class\"));");
@@ -267,7 +298,7 @@ public class BCELifier extends 
org.apache.bcel.classfile.EmptyVisitor {
 
     @Override
     public void visitJavaClass(final JavaClass clazz) {
-        String className = clazz.getClassName();
+        String className = checkJavaName(clazz.getClassName());
         final String superName = clazz.getSuperclassName();
         final String packageName = clazz.getPackageName();
         final String inter = 
Utility.printArray(escape(clazz.getInterfaceNames()), false, true);
diff --git a/src/test/java/org/apache/bcel/util/BCELifierTest.java 
b/src/test/java/org/apache/bcel/util/BCELifierTest.java
index 2ab563e0..cd605564 100644
--- a/src/test/java/org/apache/bcel/util/BCELifierTest.java
+++ b/src/test/java/org/apache/bcel/util/BCELifierTest.java
@@ -21,6 +21,7 @@ package org.apache.bcel.util;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.BufferedInputStream;
@@ -193,6 +194,17 @@ class BCELifierTest extends AbstractTest {
         assertFalse(source.contains('"' + toEscapeSource + '"'), source);
     }
 
+    @Test
+    void testClassNameRejectedWhenNotJavaIdentifier() {
+        // Class file names may contain characters the Java language forbids 
(JVMS 4.2.2 bans only . ; [ /).
+        // BCELifier must refuse to emit such a name in identifier position 
rather than let a crafted
+        // this_class inject statements into the generated source.
+        final ClassGen cg = new ClassGen("Evil {}\nclass Injected {}//", 
"java.lang.Object", "Evil.java", Const.ACC_PUBLIC | Const.ACC_SUPER,
+            new String[] {});
+        final BCELifier bcelifier = new BCELifier(cg.getJavaClass(), new 
ByteArrayOutputStream());
+        assertThrows(IllegalArgumentException.class, bcelifier::start);
+    }
+
     private void testClassOnPath(final String javaClassFileName) throws 
Exception {
         final File workDir = new File("target", getClass().getSimpleName());
         Files.createDirectories(workDir.getParentFile().toPath());

Reply via email to