View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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   * Tests {@link JavaClass}.
48   */
49  class JavaClassTest {
50  
51      private static final String CLASS_NAME = "TargetClass";
52  
53  // Doesn't compile due to cyclic inheritance
54  //  private interface InterfaceA extends InterfaceB {
55  //  }
56  //
57  //  private interface InterfaceB extends InterfaceA {
58  //  }
59  
60      @TempDir
61      static Path tempDir;
62  
63      @BeforeAll
64      static void beforeAll() throws Exception {
65          // Create InterfaceA that extends InterfaceB (will create cycle)
66          writeInterfaceA();
67          // Create InterfaceB that extends InterfaceA (completes the cycle)
68          writeInterfaceB();
69          // Create a class that implements InterfaceA
70          writeTargetClass();
71          // Cycle: InterfaceA -> InterfaceB -> InterfaceA -> ...
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          // 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());
98      }
99  
100     private static void writeInterfaceB() throws Exception {
101         // 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());
105     }
106 
107     private static void writeTargetClass() throws Exception {
108         // 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" });
110         // Add default constructor
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         // Create the class file
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         // 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");
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 }