2626import java .io .ByteArrayInputStream ;
2727import java .io .ByteArrayOutputStream ;
2828import java .io .IOException ;
29+ import java .nio .file .Files ;
2930import java .nio .file .Path ;
31+ import java .nio .file .Paths ;
3032
3133import org .apache .bcel .Const ;
3234import org .apache .bcel .Repository ;
3739import org .apache .bcel .generic .Type ;
3840import org .apache .bcel .util .ClassPath ;
3941import org .apache .bcel .util .SyntheticRepository ;
42+ import org .apache .commons .lang3 .SystemProperties ;
4043import org .junit .jupiter .api .BeforeAll ;
4144import org .junit .jupiter .api .Test ;
4245import org .junit .jupiter .api .io .TempDir ;
4851 */
4952class JavaClassTest {
5053
51- private static final String CLASS_NAME = "TargetClass" ;
54+ private static final String INTERFACE_NAME_A = "InterfaceA" ;
55+
56+ private static final String INTERFACE_NAME_B = "InterfaceB" ;
5257
58+ private static final String CLASS_NAME = "TargetClass" ;
5359// Doesn't compile due to cyclic inheritance
5460// private interface InterfaceA extends InterfaceB {
5561// }
@@ -60,14 +66,20 @@ class JavaClassTest {
6066 @ TempDir
6167 static Path tempDir ;
6268
69+ static Path tempClassFile ;
70+
71+ static Path tempIntefaceAFile ;
72+
73+ static Path tempIntefaceBFile ;
74+
6375 @ BeforeAll
6476 static void beforeAll () throws Exception {
6577 // Create InterfaceA that extends InterfaceB (will create cycle)
66- writeInterfaceA ();
78+ tempIntefaceAFile = writeInterfaceA ();
6779 // Create InterfaceB that extends InterfaceA (completes the cycle)
68- writeInterfaceB ();
80+ tempIntefaceBFile = writeInterfaceB ();
6981 // Create a class that implements InterfaceA
70- writeTargetClass ();
82+ tempClassFile = writeTargetClass ();
7183 // Cycle: InterfaceA -> InterfaceB -> InterfaceA -> ...
7284 }
7385
@@ -90,23 +102,27 @@ static byte[] toByteArray(final ClassGen cg) throws IOException {
90102 return baos .toByteArray ();
91103 }
92104
93- private static void writeInterfaceA () throws Exception {
105+ private static Path writeInterfaceA () throws Exception {
94106 // Create InterfaceA that extends InterfaceB
95- final ClassGen cg = new ClassGen ("InterfaceA" , "java.lang.Object" , "InterfaceA.java" , Const .ACC_PUBLIC | Const .ACC_INTERFACE | Const .ACC_ABSTRACT ,
96- new String [] { "InterfaceB" });
97- cg .getJavaClass ().dump (tempDir .resolve ("InterfaceA.class" ).toString ());
107+ final ClassGen classGen = new ClassGen (INTERFACE_NAME_A , "java.lang.Object" , INTERFACE_NAME_A + ".java" ,
108+ Const .ACC_PUBLIC | Const .ACC_INTERFACE | Const .ACC_ABSTRACT , new String [] { INTERFACE_NAME_B });
109+ final Path path = tempDir .resolve (INTERFACE_NAME_A + ".class" );
110+ classGen .getJavaClass ().dump (path .toString ());
111+ return path ;
98112 }
99113
100- private static void writeInterfaceB () throws Exception {
114+ private static Path writeInterfaceB () throws Exception {
101115 // Create InterfaceB that extends InterfaceA
102- final ClassGen cg = new ClassGen ("InterfaceB" , "java.lang.Object" , "InterfaceB.java" , Const .ACC_PUBLIC | Const .ACC_INTERFACE | Const .ACC_ABSTRACT ,
103- new String [] { "InterfaceA" });
104- cg .getJavaClass ().dump (tempDir .resolve ("InterfaceB.class" ).toString ());
116+ final ClassGen classGen = new ClassGen (INTERFACE_NAME_B , "java.lang.Object" , INTERFACE_NAME_B + ".java" ,
117+ Const .ACC_PUBLIC | Const .ACC_INTERFACE | Const .ACC_ABSTRACT , new String [] { INTERFACE_NAME_A });
118+ final Path path = tempDir .resolve (INTERFACE_NAME_B + ".class" );
119+ classGen .getJavaClass ().dump (path .toString ());
120+ return path ;
105121 }
106122
107- private static void writeTargetClass () throws Exception {
123+ private static Path writeTargetClass () throws Exception {
108124 // Create a class that implements InterfaceA
109- final ClassGen cg = new ClassGen (CLASS_NAME , "java.lang.Object" , "VulnerableClass .java" , Const .ACC_PUBLIC , new String [] { "InterfaceA" });
125+ final ClassGen cg = new ClassGen (CLASS_NAME , "java.lang.Object" , CLASS_NAME + " .java" , Const .ACC_PUBLIC , new String [] { INTERFACE_NAME_A });
110126 // Add default constructor
111127 final InstructionList il = new InstructionList ();
112128 final MethodGen constructor = new MethodGen (Const .ACC_PUBLIC , Type .VOID , Type .NO_ARGS , new String [] {}, "<init>" , CLASS_NAME , il , cg .getConstantPool ());
@@ -119,7 +135,9 @@ private static void writeTargetClass() throws Exception {
119135 cg .addMethod (constructor .getMethod ());
120136 il .dispose ();
121137 // Create the class file
122- cg .getJavaClass ().dump (tempDir .resolve (CLASS_NAME + ".class" ).toString ());
138+ final Path path = tempDir .resolve (CLASS_NAME + ".class" );
139+ cg .getJavaClass ().dump (path .toString ());
140+ return path ;
123141 }
124142
125143 private Field findFieldDoesNotExist (final Class <?> clazz ) throws ClassNotFoundException {
@@ -149,11 +167,26 @@ void testFindFieldCustomClass() throws Exception {
149167 }
150168
151169 @ Test
152- void testFindFieldCustomInterface1 () throws ClassNotFoundException {
170+ void testFindFieldCustomInterface1 () throws IOException , ClassNotFoundException {
153171 // Set up repository to load classes from the malicious_classes directory
154- final String classPath = tempDir .toString () + System . getProperty ( "path.separator" ) + System . getProperty ( "java.class.path" );
172+ final String classPath = tempDir .toString () + SystemProperties . getPathSeparator ( ) + SystemProperties . getJavaClassPath ( );
155173 Repository .setRepository (SyntheticRepository .getInstance (new ClassPath (classPath )));
156174 assertThrows (ClassFormatException .class , () -> Repository .lookupClass (CLASS_NAME ).findField ("nonExistentField" , Type .INT ));
175+ // sanity check
176+ final Path targetDir = Paths .get ("target/test-classes" );
177+ final Path targetClassFile = targetDir .resolve (CLASS_NAME + ".class" );
178+ final Path targetInterfaceA = targetDir .resolve (INTERFACE_NAME_A + ".class" );
179+ final Path targetInterfaceB = targetDir .resolve (INTERFACE_NAME_B + ".class" );
180+ try {
181+ Files .copy (tempClassFile , targetClassFile );
182+ Files .copy (tempIntefaceAFile , targetInterfaceA );
183+ Files .copy (tempIntefaceBFile , targetInterfaceB );
184+ assertThrows (ClassCircularityError .class , () -> Class .forName (CLASS_NAME ));
185+ } finally {
186+ Files .delete (targetClassFile );
187+ Files .delete (targetInterfaceA );
188+ Files .delete (targetInterfaceB );
189+ }
157190 }
158191
159192 @ Test
@@ -195,5 +228,4 @@ void testGetAllInterfaces(final Class<?> clazz) throws ClassNotFoundException {
195228 void testGetSuperClassesAll (final Class <?> clazz ) throws ClassNotFoundException {
196229 assertNotNull (Repository .lookupClass (clazz .getName ()).getSuperClasses ());
197230 }
198-
199231}
0 commit comments