• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Code Intelligence GmbH
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.code_intelligence.jazzer.driver;
18 
19 import static java.lang.System.err;
20 import static java.lang.System.exit;
21 import static java.lang.System.out;
22 
23 import com.code_intelligence.jazzer.api.FuzzedDataProvider;
24 import com.code_intelligence.jazzer.autofuzz.FuzzTarget;
25 import com.code_intelligence.jazzer.instrumentor.CoverageRecorder;
26 import com.code_intelligence.jazzer.runtime.CoverageMap;
27 import com.code_intelligence.jazzer.runtime.FuzzedDataProviderImpl;
28 import com.code_intelligence.jazzer.runtime.JazzerInternal;
29 import com.code_intelligence.jazzer.runtime.RecordingFuzzedDataProvider;
30 import com.code_intelligence.jazzer.runtime.SignalHandler;
31 import com.code_intelligence.jazzer.runtime.UnsafeProvider;
32 import com.code_intelligence.jazzer.utils.ExceptionUtils;
33 import com.code_intelligence.jazzer.utils.ManifestUtils;
34 import com.github.fmeum.rules_jni.RulesJni;
35 import java.io.IOException;
36 import java.lang.invoke.MethodHandle;
37 import java.lang.invoke.MethodHandles;
38 import java.lang.reflect.InvocationTargetException;
39 import java.lang.reflect.Method;
40 import java.lang.reflect.Modifier;
41 import java.math.BigInteger;
42 import java.security.MessageDigest;
43 import java.security.NoSuchAlgorithmException;
44 import java.util.Arrays;
45 import java.util.Base64;
46 import java.util.Collections;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Locale;
50 import java.util.Set;
51 import sun.misc.Unsafe;
52 
53 /**
54  * Executes a fuzz target and reports findings.
55  *
56  * <p>This class maintains global state (both native and non-native) and thus cannot be used
57  * concurrently.
58  */
59 public final class FuzzTargetRunner {
60   static {
61     RulesJni.loadLibrary("jazzer_driver", FuzzTargetRunner.class);
62   }
63 
64   private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe();
65   private static final long BYTE_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
66 
67   // Default value of the libFuzzer -error_exitcode flag.
68   private static final int LIBFUZZER_ERROR_EXIT_CODE = 77;
69   private static final String AUTOFUZZ_FUZZ_TARGET =
70       "com.code_intelligence.jazzer.autofuzz.FuzzTarget";
71   private static final String FUZZER_TEST_ONE_INPUT = "fuzzerTestOneInput";
72   private static final String FUZZER_INITIALIZE = "fuzzerInitialize";
73   private static final String FUZZER_TEARDOWN = "fuzzerTearDown";
74 
75   private static final Set<Long> ignoredTokens = new HashSet<>(Opt.ignore);
76   private static final FuzzedDataProviderImpl fuzzedDataProvider =
77       FuzzedDataProviderImpl.withNativeData();
78   private static final Class<?> fuzzTargetClass;
79   private static final MethodHandle fuzzTarget;
80   public static final boolean useFuzzedDataProvider;
81   private static final ReproducerTemplate reproducerTemplate;
82 
83   static {
84     String targetClassName = determineFuzzTargetClassName();
85 
86     // FuzzTargetRunner is loaded by the bootstrap class loader since Driver installs the agent
87     // before invoking FuzzTargetRunner.startLibFuzzer. We can't load the fuzz target with that
88     // class loader - we have to use the class loader that loaded Driver. This would be
89     // straightforward to do in Java 9+, but requires the use of reflection to maintain
90     // compatibility with Java 8, which doesn't have StackWalker.
91     //
92     // Note that we can't just move the agent initialization so that FuzzTargetRunner is loaded by
93     // Driver's class loader: The agent and FuzzTargetRunner have to share the native library that
94     // contains libFuzzer and that library needs to be available in the bootstrap class loader
95     // since instrumentation applied to Java standard library classes still needs to be able to call
96     // libFuzzer hooks. A fundamental JNI restriction is that a native library can't be shared
97     // between two different class loaders, so FuzzTargetRunner is thus forced to be loaded in the
98     // bootstrap class loader, which makes this ugly code block necessary.
99     // We also can't use the system class loader since Driver may be loaded by a custom class loader
100     // if not invoked from the native driver.
101     Class<?> driverClass;
102     try {
103       Class<?> reflectionClass = Class.forName("sun.reflect.Reflection");
104       try {
105         driverClass =
106             (Class<?>) reflectionClass.getMethod("getCallerClass", int.class).invoke(null, 2);
107       } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
108         throw new IllegalStateException(e);
109       }
110     } catch (ClassNotFoundException e) {
111       // sun.reflect.Reflection is no longer available after Java 8, use StackWalker.
112       try {
113         Class<?> stackWalker = Class.forName("java.lang.StackWalker");
114         Class<? extends Enum<?>> stackWalkerOption =
115             (Class<? extends Enum<?>>) Class.forName("java.lang.StackWalker$Option");
116         Enum<?> retainClassReferences =
117             Arrays.stream(stackWalkerOption.getEnumConstants())
118                 .filter(v -> v.name().equals("RETAIN_CLASS_REFERENCE"))
119                 .findFirst()
120                 .orElseThrow(()
121                                  -> new IllegalStateException(
122                                      "No RETAIN_CLASS_REFERENCE in java.lang.StackWalker$Option"));
123         Object stackWalkerInstance = stackWalker.getMethod("getInstance", stackWalkerOption)
124                                          .invoke(null, retainClassReferences);
125         Method stackWalkerGetCallerClass = stackWalker.getMethod("getCallerClass");
126         driverClass = (Class<?>) stackWalkerGetCallerClass.invoke(stackWalkerInstance);
127       } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException
128           | InvocationTargetException ex) {
129         throw new IllegalStateException(ex);
130       }
131     }
132 
133     try {
134       ClassLoader driverClassLoader = driverClass.getClassLoader();
135       driverClassLoader.setDefaultAssertionStatus(true);
136       fuzzTargetClass = Class.forName(targetClassName, false, driverClassLoader);
137     } catch (ClassNotFoundException e) {
138       err.print("ERROR: ");
139       e.printStackTrace(err);
140       exit(1);
141       throw new IllegalStateException("Not reached");
142     }
143     // Inform the agent about the fuzz target class. Important note: This has to be done *before*
144     // the class is initialized so that hooks can enable themselves in time for the fuzz target's
145     // static initializer.
146     JazzerInternal.onFuzzTargetReady(targetClassName);
147 
148     Method bytesFuzzTarget = targetPublicStaticMethodOrNull(FUZZER_TEST_ONE_INPUT, byte[].class);
149     Method dataFuzzTarget =
150         targetPublicStaticMethodOrNull(FUZZER_TEST_ONE_INPUT, FuzzedDataProvider.class);
151     if ((bytesFuzzTarget != null) == (dataFuzzTarget != null)) {
152       err.printf(
153           "ERROR: %s must define exactly one of the following two functions:%n", targetClassName);
154       err.println("public static void fuzzerTestOneInput(byte[] ...)");
155       err.println("public static void fuzzerTestOneInput(FuzzedDataProvider ...)");
156       err.println(
157           "Note: Fuzz targets returning boolean are no longer supported; exceptions should be thrown instead of returning true.");
158       exit(1);
159     }
160     try {
161       if (bytesFuzzTarget != null) {
162         useFuzzedDataProvider = false;
163         fuzzTarget = MethodHandles.publicLookup().unreflect(bytesFuzzTarget);
164       } else {
165         useFuzzedDataProvider = true;
166         fuzzTarget = MethodHandles.publicLookup().unreflect(dataFuzzTarget);
167       }
168     } catch (IllegalAccessException e) {
169       throw new RuntimeException(e);
170     }
171     reproducerTemplate = new ReproducerTemplate(fuzzTargetClass.getName(), useFuzzedDataProvider);
172 
173     Method initializeNoArgs = targetPublicStaticMethodOrNull(FUZZER_INITIALIZE);
174     Method initializeWithArgs = targetPublicStaticMethodOrNull(FUZZER_INITIALIZE, String[].class);
175     try {
176       if (initializeWithArgs != null) {
initializeWithArgs.invoke(null, (Object) Opt.targetArgs.toArray(new String[] {}))177         initializeWithArgs.invoke(null, (Object) Opt.targetArgs.toArray(new String[] {}));
178       } else if (initializeNoArgs != null) {
179         initializeNoArgs.invoke(null);
180       }
181     } catch (IllegalAccessException | InvocationTargetException e) {
182       err.print("== Java Exception in fuzzerInitialize: ");
183       e.printStackTrace(err);
184       exit(1);
185     }
186 
187     if (Opt.hooks) {
188       // libFuzzer will clear the coverage map after this method returns and keeps no record of the
189       // coverage accumulated so far (e.g. by static initializers). We record it here to keep it
190       // around for JaCoCo coverage reports.
CoverageRecorder.updateCoveredIdsWithCoverageMap()191       CoverageRecorder.updateCoveredIdsWithCoverageMap();
192     }
193 
194     Runtime.getRuntime().addShutdownHook(new Thread(FuzzTargetRunner::shutdown));
195   }
196 
197   /**
198    * A test-only convenience wrapper around {@link #runOne(long, int)}.
199    */
runOne(byte[] data)200   static int runOne(byte[] data) {
201     long dataPtr = UNSAFE.allocateMemory(data.length);
202     UNSAFE.copyMemory(data, BYTE_ARRAY_OFFSET, null, dataPtr, data.length);
203     try {
204       return runOne(dataPtr, data.length);
205     } finally {
206       UNSAFE.freeMemory(dataPtr);
207     }
208   }
209 
210   /**
211    * Executes the user-provided fuzz target once.
212    *
213    * @param dataPtr a native pointer to beginning of the input provided by the fuzzer for this
214    *     execution
215    * @param dataLength length of the fuzzer input
216    * @return the value that the native LLVMFuzzerTestOneInput function should return. Currently,
217    *         this is always 0. The function may exit the process instead of returning.
218    */
runOne(long dataPtr, int dataLength)219   private static int runOne(long dataPtr, int dataLength) {
220     Throwable finding = null;
221     byte[] data = null;
222     try {
223       if (useFuzzedDataProvider) {
224         fuzzedDataProvider.setNativeData(dataPtr, dataLength);
225         fuzzTarget.invokeExact((FuzzedDataProvider) fuzzedDataProvider);
226       } else {
227         data = copyToArray(dataPtr, dataLength);
228         fuzzTarget.invokeExact(data);
229       }
230     } catch (Throwable uncaughtFinding) {
231       finding = uncaughtFinding;
232     }
233     // Explicitly reported findings take precedence over uncaught exceptions.
234     if (JazzerInternal.lastFinding != null) {
235       finding = JazzerInternal.lastFinding;
236       JazzerInternal.lastFinding = null;
237     }
238     if (finding == null) {
239       return 0;
240     }
241     if (Opt.hooks) {
242       finding = ExceptionUtils.preprocessThrowable(finding);
243     }
244 
245     long dedupToken = Opt.dedup ? ExceptionUtils.computeDedupToken(finding) : 0;
246     // Opt.keepGoing implies Opt.dedup.
247     if (Opt.keepGoing > 1 && !ignoredTokens.add(dedupToken)) {
248       return 0;
249     }
250 
251     err.println();
252     err.print("== Java Exception: ");
253     finding.printStackTrace(err);
254     if (Opt.dedup) {
255       // Has to be printed to stdout as it is parsed by libFuzzer when minimizing a crash. It does
256       // not necessarily have to appear at the beginning of a line.
257       // https://github.com/llvm/llvm-project/blob/4c106c93eb68f8f9f201202677cd31e326c16823/compiler-rt/lib/fuzzer/FuzzerDriver.cpp#L342
258       out.printf(Locale.ROOT, "DEDUP_TOKEN: %016x%n", dedupToken);
259     }
260     err.println("== libFuzzer crashing input ==");
261     printCrashingInput();
262     // dumpReproducer needs to be called after libFuzzer printed its final stats as otherwise it
263     // would report incorrect coverage - the reproducer generation involved rerunning the fuzz
264     // target.
265     dumpReproducer(data);
266 
267     if (Opt.keepGoing == 1 || Long.compareUnsigned(ignoredTokens.size(), Opt.keepGoing) >= 0) {
268       // Reached the maximum amount of findings to keep going for, crash after shutdown. We use
269       // _Exit rather than System.exit to not trigger libFuzzer's exit handlers.
270       shutdown();
271       _Exit(LIBFUZZER_ERROR_EXIT_CODE);
272       throw new IllegalStateException("Not reached");
273     }
274     return 0;
275   }
276 
277   /*
278    * Starts libFuzzer via LLVMFuzzerRunDriver.
279    *
280    * Note: Must be public rather than package-private as it is loaded in a different class loader
281    * than Driver.
282    */
startLibFuzzer(List<String> args)283   public static int startLibFuzzer(List<String> args) {
284     SignalHandler.initialize();
285     return startLibFuzzer(Utils.toNativeArgs(args));
286   }
287 
shutdown()288   private static void shutdown() {
289     if (!Opt.coverageDump.isEmpty() || !Opt.coverageReport.isEmpty()) {
290       int[] everCoveredIds = CoverageMap.getEverCoveredIds();
291       if (!Opt.coverageDump.isEmpty()) {
292         CoverageRecorder.dumpJacocoCoverage(everCoveredIds, Opt.coverageDump);
293       }
294       if (!Opt.coverageReport.isEmpty()) {
295         CoverageRecorder.dumpCoverageReport(everCoveredIds, Opt.coverageReport);
296       }
297     }
298 
299     Method teardown = targetPublicStaticMethodOrNull(FUZZER_TEARDOWN);
300     if (teardown == null) {
301       return;
302     }
303     err.println("calling fuzzerTearDown function");
304     try {
305       teardown.invoke(null);
306     } catch (InvocationTargetException e) {
307       // An exception in fuzzerTearDown is a regular finding.
308       err.print("== Java Exception in fuzzerTearDown: ");
309       e.getCause().printStackTrace(err);
310       _Exit(LIBFUZZER_ERROR_EXIT_CODE);
311     } catch (Throwable t) {
312       // Any other exception is an error.
313       t.printStackTrace(err);
314       _Exit(1);
315     }
316   }
317 
determineFuzzTargetClassName()318   private static String determineFuzzTargetClassName() {
319     if (!Opt.autofuzz.isEmpty()) {
320       return AUTOFUZZ_FUZZ_TARGET;
321     }
322     if (!Opt.targetClass.isEmpty()) {
323       return Opt.targetClass;
324     }
325     String manifestTargetClass = ManifestUtils.detectFuzzTargetClass();
326     if (manifestTargetClass != null) {
327       return manifestTargetClass;
328     }
329     err.println("Missing argument --target_class=<fuzz_target_class>");
330     exit(1);
331     throw new IllegalStateException("Not reached");
332   }
333 
dumpReproducer(byte[] data)334   private static void dumpReproducer(byte[] data) {
335     if (data == null) {
336       assert useFuzzedDataProvider;
337       fuzzedDataProvider.reset();
338       data = fuzzedDataProvider.consumeRemainingAsBytes();
339     }
340     MessageDigest digest;
341     try {
342       digest = MessageDigest.getInstance("SHA-1");
343     } catch (NoSuchAlgorithmException e) {
344       throw new IllegalStateException("SHA-1 not available", e);
345     }
346     String dataSha1 = toHexString(digest.digest(data));
347 
348     if (!Opt.autofuzz.isEmpty()) {
349       fuzzedDataProvider.reset();
350       FuzzTarget.dumpReproducer(fuzzedDataProvider, Opt.reproducerPath, dataSha1);
351       return;
352     }
353 
354     String base64Data;
355     if (useFuzzedDataProvider) {
356       fuzzedDataProvider.reset();
357       FuzzedDataProvider recordingFuzzedDataProvider =
358           RecordingFuzzedDataProvider.makeFuzzedDataProviderProxy(fuzzedDataProvider);
359       try {
360         fuzzTarget.invokeExact(recordingFuzzedDataProvider);
361         if (JazzerInternal.lastFinding == null) {
362           err.println("Failed to reproduce crash when rerunning with recorder");
363         }
364       } catch (Throwable ignored) {
365         // Expected.
366       }
367       try {
368         base64Data = RecordingFuzzedDataProvider.serializeFuzzedDataProviderProxy(
369             recordingFuzzedDataProvider);
370       } catch (IOException e) {
371         err.print("ERROR: Failed to create reproducer: ");
372         e.printStackTrace(err);
373         // Don't let libFuzzer print a native stack trace.
374         _Exit(1);
375         throw new IllegalStateException("Not reached");
376       }
377     } else {
378       base64Data = Base64.getEncoder().encodeToString(data);
379     }
380 
381     reproducerTemplate.dumpReproducer(base64Data, dataSha1);
382   }
383 
targetPublicStaticMethodOrNull(String name, Class<?>... parameterTypes)384   private static Method targetPublicStaticMethodOrNull(String name, Class<?>... parameterTypes) {
385     try {
386       Method method = fuzzTargetClass.getMethod(name, parameterTypes);
387       if (!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers())) {
388         return null;
389       }
390       return method;
391     } catch (NoSuchMethodException e) {
392       return null;
393     }
394   }
395 
396   /**
397    * Convert a byte array to a lower-case hex string.
398    *
399    * <p>The returned hex string always has {@code 2 * bytes.length} characters.
400    *
401    * @param bytes the bytes to convert
402    * @return a lower-case hex string representing the bytes
403    */
toHexString(byte[] bytes)404   private static String toHexString(byte[] bytes) {
405     String unpadded = new BigInteger(1, bytes).toString(16);
406     int numLeadingZeroes = 2 * bytes.length - unpadded.length();
407     return String.join("", Collections.nCopies(numLeadingZeroes, "0")) + unpadded;
408   }
409 
410   // Accessed by fuzz_target_runner.cpp.
411   @SuppressWarnings("unused")
dumpAllStackTraces()412   private static void dumpAllStackTraces() {
413     ExceptionUtils.dumpAllStackTraces();
414   }
415 
copyToArray(long ptr, int length)416   private static byte[] copyToArray(long ptr, int length) {
417     // TODO: Use Unsafe.allocateUninitializedArray instead once Java 9 is the base.
418     byte[] array = new byte[length];
419     UNSAFE.copyMemory(null, ptr, array, BYTE_ARRAY_OFFSET, length);
420     return array;
421   }
422 
423   /**
424    * Starts libFuzzer via LLVMFuzzerRunDriver.
425    *
426    * @param args command-line arguments encoded in UTF-8 (not null-terminated)
427    * @return the return value of LLVMFuzzerRunDriver
428    */
startLibFuzzer(byte[][] args)429   private static native int startLibFuzzer(byte[][] args);
430 
431   /**
432    * Causes libFuzzer to write the current input to disk as a crashing input and emit some
433    * information about it to stderr.
434    */
printCrashingInput()435   private static native void printCrashingInput();
436 
437   /**
438    * Immediately terminates the process without performing any cleanup.
439    *
440    * <p>Neither JVM shutdown hooks nor native exit handlers are called. This method does not return.
441    *
442    * <p>This method provides a way to exit Jazzer without triggering libFuzzer's exit hook that
443    * prints the "fuzz target exited" error message. It should thus be preferred over
444    * {@link System#exit} in any situation where Jazzer encounters an error after the fuzz target has
445    * started running.
446    *
447    * @param exitCode the exit code
448    */
_Exit(int exitCode)449   private static native void _Exit(int exitCode);
450 }
451