1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.bcel.classfile;
21
22 import static org.junit.jupiter.api.Assertions.assertNotNull;
23 import static org.junit.jupiter.api.Assertions.assertNull;
24 import static org.junit.jupiter.api.Assertions.assertThrows;
25
26 import java.io.ByteArrayInputStream;
27 import java.io.ByteArrayOutputStream;
28 import java.io.IOException;
29 import java.nio.file.Path;
30
31 import org.apache.bcel.Const;
32 import org.apache.bcel.Repository;
33 import org.apache.bcel.generic.ClassGen;
34 import org.apache.bcel.generic.InstructionFactory;
35 import org.apache.bcel.generic.InstructionList;
36 import org.apache.bcel.generic.MethodGen;
37 import org.apache.bcel.generic.Type;
38 import org.apache.bcel.util.ClassPath;
39 import org.apache.bcel.util.SyntheticRepository;
40 import org.junit.jupiter.api.BeforeAll;
41 import org.junit.jupiter.api.Test;
42 import org.junit.jupiter.api.io.TempDir;
43 import org.junit.jupiter.params.ParameterizedTest;
44 import org.junit.jupiter.params.provider.MethodSource;
45
46
47
48
49 class JavaClassTest {
50
51 private static final String CLASS_NAME = "TargetClass";
52
53
54
55
56
57
58
59
60 @TempDir
61 static Path tempDir;
62
63 @BeforeAll
64 static void beforeAll() throws Exception {
65
66 writeInterfaceA();
67
68 writeInterfaceB();
69
70 writeTargetClass();
71
72 }
73
74 static byte[] createClass(final String name, final String extendsClass) throws IOException {
75 return toByteArray(new ClassGen(name, extendsClass, name + ".java", Const.ACC_PUBLIC, new String[] {}));
76 }
77
78 static byte[] createClass(final String name, final String extendsClass, final String implementsInterface) throws IOException {
79 return toByteArray(new ClassGen(name, extendsClass, name + ".java", Const.ACC_PUBLIC, new String[] { implementsInterface }));
80 }
81
82 static byte[] createInterface(final String name, final String extendsInterface) throws IOException {
83 return toByteArray(new ClassGen(name, "java.lang.Object", name + ".java", Const.ACC_PUBLIC | Const.ACC_INTERFACE | Const.ACC_ABSTRACT,
84 new String[] { extendsInterface }));
85 }
86
87 static byte[] toByteArray(final ClassGen cg) throws IOException {
88 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
89 cg.getJavaClass().dump(baos);
90 return baos.toByteArray();
91 }
92
93 private static void writeInterfaceA() throws Exception {
94
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());
98 }
99
100 private static void writeInterfaceB() throws Exception {
101
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());
105 }
106
107 private static void writeTargetClass() throws Exception {
108
109 final ClassGen cg = new ClassGen(CLASS_NAME, "java.lang.Object", "VulnerableClass.java", Const.ACC_PUBLIC, new String[] { "InterfaceA" });
110
111 final InstructionList il = new InstructionList();
112 final MethodGen constructor = new MethodGen(Const.ACC_PUBLIC, Type.VOID, Type.NO_ARGS, new String[] {}, "<init>", CLASS_NAME, il, cg.getConstantPool());
113 final InstructionFactory factory = new InstructionFactory(cg);
114 il.append(InstructionFactory.createLoad(Type.OBJECT, 0));
115 il.append(factory.createInvoke("java.lang.Object", "<init>", Type.VOID, Type.NO_ARGS, Const.INVOKESPECIAL));
116 il.append(InstructionFactory.createReturn(Type.VOID));
117 constructor.setMaxStack();
118 constructor.setMaxLocals();
119 cg.addMethod(constructor.getMethod());
120 il.dispose();
121
122 cg.getJavaClass().dump(tempDir.resolve(CLASS_NAME + ".class").toString());
123 }
124
125 private Field findFieldDoesNotExist(final Class<?> clazz) throws ClassNotFoundException {
126 return Repository.lookupClass(clazz.getName()).findField("nonExistentField", Type.INT);
127 }
128
129 @Test
130 void testFindFieldCustomClass() throws Exception {
131 final byte[] classABytes = createClass("CyclicClassA", "CyclicClassB");
132 final byte[] classBBytes = createInterface("CyclicClassB", "CyclicClassA");
133 final byte[] testClassBytes = createClass("CyclicTestClass", "CyclicClassA");
134 final JavaClass interfaceA = new ClassParser(new ByteArrayInputStream(classABytes), "CyclicClassA.class").parse();
135 final JavaClass interfaceB = new ClassParser(new ByteArrayInputStream(classBBytes), "CyclicClassB.class").parse();
136 final JavaClass testClass = new ClassParser(new ByteArrayInputStream(testClassBytes), "CyclicTestClass.class").parse();
137 final SyntheticRepository repo = SyntheticRepository.getInstance();
138 try {
139 repo.storeClass(interfaceA);
140 repo.storeClass(interfaceB);
141 repo.storeClass(testClass);
142 Repository.setRepository(repo);
143 assertThrows(ClassFormatException.class, () -> testClass.findField("nonExistentField", Type.INT));
144 } finally {
145 repo.removeClass(interfaceA);
146 repo.removeClass(interfaceB);
147 repo.removeClass(testClass);
148 }
149 }
150
151 @Test
152 void testFindFieldCustomInterface1() throws ClassNotFoundException {
153
154 final String classPath = tempDir.toString() + System.getProperty("path.separator") + System.getProperty("java.class.path");
155 Repository.setRepository(SyntheticRepository.getInstance(new ClassPath(classPath)));
156 assertThrows(ClassFormatException.class, () -> Repository.lookupClass(CLASS_NAME).findField("nonExistentField", Type.INT));
157 }
158
159 @Test
160 void testFindFieldCustomInterface2() throws Exception {
161 final byte[] interfaceABytes = createInterface("CyclicInterfaceA", "CyclicInterfaceB");
162 final byte[] interfaceBBytes = createInterface("CyclicInterfaceB", "CyclicInterfaceA");
163 final byte[] testClassBytes = createClass("CyclicTestClass", "java.lang.Object", "CyclicInterfaceA");
164 final JavaClass interfaceA = new ClassParser(new ByteArrayInputStream(interfaceABytes), "CyclicInterfaceA.class").parse();
165 final JavaClass interfaceB = new ClassParser(new ByteArrayInputStream(interfaceBBytes), "CyclicInterfaceB.class").parse();
166 final JavaClass testClass = new ClassParser(new ByteArrayInputStream(testClassBytes), "CyclicTestClass.class").parse();
167 final SyntheticRepository repo = SyntheticRepository.getInstance();
168 try {
169 repo.storeClass(interfaceA);
170 repo.storeClass(interfaceB);
171 repo.storeClass(testClass);
172 Repository.setRepository(repo);
173 assertThrows(ClassFormatException.class, () -> testClass.findField("nonExistentField", Type.INT));
174 } finally {
175 repo.removeClass(interfaceA);
176 repo.removeClass(interfaceB);
177 repo.removeClass(testClass);
178 }
179 }
180
181 @ParameterizedTest
182 @MethodSource("org.apache.bcel.Java8PublicClasses#getAll")
183 void testFindFieldJavaLang(final Class<?> clazz) throws ClassNotFoundException {
184 assertNull(findFieldDoesNotExist(clazz));
185 }
186
187 @ParameterizedTest
188 @MethodSource("org.apache.bcel.Java8PublicClasses#getAll")
189 void testGetAllInterfaces(final Class<?> clazz) throws ClassNotFoundException {
190 assertNotNull(Repository.lookupClass(clazz.getName()).getAllInterfaces());
191 }
192
193 @ParameterizedTest
194 @MethodSource("org.apache.bcel.Java8PublicClasses#getAll")
195 void testGetSuperClassesAll(final Class<?> clazz) throws ClassNotFoundException {
196 assertNotNull(Repository.lookupClass(clazz.getName()).getSuperClasses());
197 }
198
199 }