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());