• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 Code Intelligence GmbH
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.code_intelligence.jazzer.autofuzz;
16 
17 import com.code_intelligence.jazzer.api.AutofuzzConstructionException;
18 import com.code_intelligence.jazzer.api.AutofuzzInvocationException;
19 import com.code_intelligence.jazzer.api.FuzzedDataProvider;
20 import com.code_intelligence.jazzer.utils.SimpleGlobMatcher;
21 import com.code_intelligence.jazzer.utils.Utils;
22 import java.io.Closeable;
23 import java.io.UnsupportedEncodingException;
24 import java.lang.reflect.Constructor;
25 import java.lang.reflect.Executable;
26 import java.lang.reflect.Method;
27 import java.net.URLDecoder;
28 import java.util.Arrays;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.stream.Collectors;
33 import java.util.stream.Stream;
34 
35 public class FuzzTarget {
36   private static final long MAX_EXECUTIONS_WITHOUT_INVOCATION = 100;
37 
38   private static String methodReference;
39   private static Executable[] targetExecutables;
40   private static Map<Executable, Class<?>[]> throwsDeclarations;
41   private static Set<SimpleGlobMatcher> ignoredExceptionMatchers;
42   private static long executionsSinceLastInvocation = 0;
43   private static AutofuzzCodegenVisitor codegenVisitor;
44 
fuzzerInitialize(String[] args)45   public static void fuzzerInitialize(String[] args) {
46     if (args.length == 0 || !args[0].contains("::")) {
47       System.err.println(
48           "Expected the argument to --autofuzz to be a method reference (e.g. System.out::println)");
49       System.exit(1);
50     }
51     methodReference = args[0];
52     String[] parts = methodReference.split("::", 2);
53     String className = parts[0];
54     String methodNameAndOptionalDescriptor = parts[1];
55     String methodName;
56     String descriptor;
57     int descriptorStart = methodNameAndOptionalDescriptor.indexOf('(');
58     if (descriptorStart != -1) {
59       methodName = methodNameAndOptionalDescriptor.substring(0, descriptorStart);
60       // URL decode the descriptor to allow copy-pasting from javadoc links such as:
61       // https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#valueOf(char%5B%5D)
62       try {
63         descriptor =
64             URLDecoder.decode(methodNameAndOptionalDescriptor.substring(descriptorStart), "UTF-8");
65       } catch (UnsupportedEncodingException e) {
66         // UTF-8 is always supported.
67         e.printStackTrace();
68         System.exit(1);
69         return;
70       }
71     } else {
72       methodName = methodNameAndOptionalDescriptor;
73       descriptor = null;
74     }
75 
76     Class<?> targetClass;
77     try {
78       // Explicitly invoking static initializers to trigger some coverage in the code.
79       targetClass = Class.forName(className, true, ClassLoader.getSystemClassLoader());
80     } catch (ClassNotFoundException e) {
81       System.err.printf(
82           "Failed to find class %s for autofuzz, please ensure it is contained in the classpath "
83               + "specified with --cp and specify the full package name%n",
84           className);
85       e.printStackTrace();
86       System.exit(1);
87       return;
88     }
89 
90     boolean isConstructor = methodName.equals("new");
91     if (isConstructor) {
92       targetExecutables =
93           Arrays.stream(targetClass.getConstructors())
94               .filter(constructor
95                   -> descriptor == null
96                       || Utils.getReadableDescriptor(constructor).equals(descriptor))
97               .toArray(Executable[] ::new);
98     } else {
99       targetExecutables =
100           Arrays.stream(targetClass.getMethods())
101               .filter(method
102                   -> method.getName().equals(methodName)
103                       && (descriptor == null
104                           || Utils.getReadableDescriptor(method).equals(descriptor)))
105               .toArray(Executable[] ::new);
106     }
107     if (targetExecutables.length == 0) {
108       if (isConstructor) {
109         if (descriptor == null) {
110           System.err.printf(
111               "Failed to find accessible constructors in class %s for autofuzz.%n", className);
112         } else {
113           System.err.printf(
114               "Failed to find accessible constructors with signature %s in class %s for autofuzz.%n"
115                   + "Accessible constructors:%n%s",
116               descriptor, className,
117               Arrays.stream(targetClass.getConstructors())
118                   .map(method
119                       -> String.format("%s::new%s", method.getDeclaringClass().getName(),
120                           Utils.getReadableDescriptor(method)))
121                   .distinct()
122                   .collect(Collectors.joining(System.lineSeparator())));
123         }
124       } else {
125         if (descriptor == null) {
126           System.err.printf("Failed to find accessible methods named %s in class %s for autofuzz.%n"
127                   + "Accessible methods:%n%s",
128               methodName, className,
129               Arrays.stream(targetClass.getMethods())
130                   .map(method
131                       -> String.format(
132                           "%s::%s", method.getDeclaringClass().getName(), method.getName()))
133                   .distinct()
134                   .collect(Collectors.joining(System.lineSeparator())));
135         } else {
136           System.err.printf(
137               "Failed to find accessible methods named %s with signature %s in class %s for autofuzz.%n"
138                   + "Accessible methods with that name:%n%s",
139               methodName, descriptor, className,
140               Arrays.stream(targetClass.getMethods())
141                   .filter(method -> method.getName().equals(methodName))
142                   .map(method
143                       -> String.format("%s::%s%s", method.getDeclaringClass().getName(),
144                           method.getName(), Utils.getReadableDescriptor(method)))
145                   .distinct()
146                   .collect(Collectors.joining(System.lineSeparator())));
147         }
148       }
149       System.exit(1);
150     }
151 
152     ignoredExceptionMatchers = Arrays.stream(args)
153                                    .skip(1)
154                                    .filter(s -> s.contains("*"))
155                                    .map(SimpleGlobMatcher::new)
156                                    .collect(Collectors.toSet());
157 
158     List<Class<?>> alwaysIgnore =
159         Arrays.stream(args)
160             .skip(1)
161             .filter(s -> !s.contains("*"))
162             .map(name -> {
163               try {
164                 return ClassLoader.getSystemClassLoader().loadClass(name);
165               } catch (ClassNotFoundException e) {
166                 System.err.printf("Failed to find class '%s' specified in --autofuzz_ignore", name);
167                 System.exit(1);
168               }
169               throw new Error("Not reached");
170             })
171             .collect(Collectors.toList());
172     throwsDeclarations =
173         Arrays.stream(targetExecutables)
174             .collect(Collectors.toMap(method
175                 -> method,
176                 method
177                 -> Stream.concat(Arrays.stream(method.getExceptionTypes()), alwaysIgnore.stream())
178                        .toArray(Class[] ::new)));
179   }
180 
fuzzerTestOneInput(FuzzedDataProvider data)181   public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Throwable {
182     if (Meta.isDebug()) {
183       codegenVisitor = new AutofuzzCodegenVisitor();
184     }
185     Executable targetExecutable;
186     if (FuzzTarget.targetExecutables.length == 1) {
187       targetExecutable = FuzzTarget.targetExecutables[0];
188     } else {
189       targetExecutable = data.pickValue(FuzzTarget.targetExecutables);
190     }
191     Object returnValue = null;
192     try {
193       if (targetExecutable instanceof Method) {
194         returnValue = Meta.autofuzz(data, (Method) targetExecutable, codegenVisitor);
195       } else {
196         returnValue = Meta.autofuzz(data, (Constructor<?>) targetExecutable, codegenVisitor);
197       }
198       executionsSinceLastInvocation = 0;
199       if (codegenVisitor != null) {
200         System.err.println(codegenVisitor.generate());
201       }
202     } catch (AutofuzzConstructionException e) {
203       if (Meta.isDebug()) {
204         e.printStackTrace();
205       }
206       // Ignore exceptions thrown while constructing the parameters for the target method. We can
207       // only guess how to generate valid parameters and any exceptions thrown while doing so
208       // are most likely on us. However, if this happens too often, Autofuzz got stuck and we should
209       // let the user know.
210       executionsSinceLastInvocation++;
211       if (executionsSinceLastInvocation >= MAX_EXECUTIONS_WITHOUT_INVOCATION) {
212         System.err.printf("Failed to generate valid arguments to '%s' in %d attempts; giving up%n",
213             methodReference, executionsSinceLastInvocation);
214         System.exit(1);
215       } else if (executionsSinceLastInvocation == MAX_EXECUTIONS_WITHOUT_INVOCATION / 2) {
216         // The application under test might perform classpath modifications or create classes
217         // dynamically that implement interfaces or extend abstract classes. Rescanning the
218         // classpath might help with constructing objects.
219         Meta.rescanClasspath();
220       }
221     } catch (AutofuzzInvocationException e) {
222       executionsSinceLastInvocation = 0;
223       Throwable cause = e.getCause();
224       Class<?> causeClass = cause.getClass();
225       // Do not report exceptions declared to be thrown by the method under test.
226       for (Class<?> declaredThrow : throwsDeclarations.get(targetExecutable)) {
227         if (declaredThrow.isAssignableFrom(causeClass)) {
228           return;
229         }
230       }
231 
232       if (ignoredExceptionMatchers.stream().anyMatch(m -> m.matches(causeClass.getName()))) {
233         return;
234       }
235       cleanStackTraces(cause);
236       throw cause;
237     } catch (Throwable t) {
238       System.err.println("Unexpected exception encountered during autofuzz");
239       t.printStackTrace();
240       System.exit(1);
241     } finally {
242       if (returnValue instanceof Closeable) {
243         ((Closeable) returnValue).close();
244       }
245     }
246   }
247 
248   // Removes all stack trace elements that live in the Java standard library, internal JDK classes
249   // or the autofuzz package from the bottom of all stack frames.
cleanStackTraces(Throwable t)250   private static void cleanStackTraces(Throwable t) {
251     Throwable cause = t;
252     while (cause != null) {
253       StackTraceElement[] elements = cause.getStackTrace();
254       int firstInterestingPos;
255       for (firstInterestingPos = elements.length - 1; firstInterestingPos > 0;
256            firstInterestingPos--) {
257         String className = elements[firstInterestingPos].getClassName();
258         if (!className.startsWith("com.code_intelligence.jazzer.autofuzz")
259             && !className.startsWith("java.") && !className.startsWith("jdk.")) {
260           break;
261         }
262       }
263       cause.setStackTrace(Arrays.copyOfRange(elements, 0, firstInterestingPos + 1));
264       cause = cause.getCause();
265     }
266   }
267 }
268