xref: /aosp_15_r20/external/jazzer-api/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java (revision 33edd6723662ea34453766bfdca85dbfdd5342b8)
1 /*
2  * Copyright 2023 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 com.code_intelligence.jazzer.driver.Constants.JAZZER_FINDING_EXIT_CODE;
20 import static com.code_intelligence.jazzer.runtime.Constants.IS_ANDROID;
21 import static java.lang.System.exit;
22 import static java.util.stream.Collectors.joining;
23 
24 import com.code_intelligence.jazzer.api.FuzzedDataProvider;
25 import com.code_intelligence.jazzer.autofuzz.FuzzTarget;
26 import com.code_intelligence.jazzer.instrumentor.CoverageRecorder;
27 import com.code_intelligence.jazzer.mutation.ArgumentsMutator;
28 import com.code_intelligence.jazzer.runtime.FuzzTargetRunnerNatives;
29 import com.code_intelligence.jazzer.runtime.JazzerInternal;
30 import com.code_intelligence.jazzer.utils.Log;
31 import com.code_intelligence.jazzer.utils.UnsafeProvider;
32 import java.io.ByteArrayInputStream;
33 import java.io.ByteArrayOutputStream;
34 import java.io.IOException;
35 import java.lang.invoke.MethodHandle;
36 import java.lang.invoke.MethodHandles;
37 import java.lang.reflect.InvocationTargetException;
38 import java.lang.reflect.Method;
39 import java.lang.reflect.Modifier;
40 import java.math.BigInteger;
41 import java.nio.charset.StandardCharsets;
42 import java.security.MessageDigest;
43 import java.security.NoSuchAlgorithmException;
44 import java.util.ArrayList;
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 java.util.function.Predicate;
52 import java.util.stream.Stream;
53 import sun.misc.Unsafe;
54 
55 /**
56  * Executes a fuzz target and reports findings.
57  *
58  * <p>This class maintains global state (both native and non-native) and thus cannot be used
59  * concurrently.
60  */
61 public final class FuzzTargetRunner {
62   private static final String OPENTEST4J_TEST_ABORTED_EXCEPTION =
63       "org.opentest4j.TestAbortedException";
64 
65   private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe();
66 
67   private static final long BYTE_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
68 
69   // Possible return values for the libFuzzer callback runOne.
70   private static final int LIBFUZZER_CONTINUE = 0;
71   private static final int LIBFUZZER_RETURN_FROM_DRIVER = -2;
72 
73   private static boolean invalidCorpusFileWarningShown = false;
74   private static final Set<Long> ignoredTokens = new HashSet<>(Opt.ignore);
75   private static final FuzzedDataProviderImpl fuzzedDataProvider =
76       FuzzedDataProviderImpl.withNativeData();
77   private static final MethodHandle fuzzTargetMethod;
78   private static final boolean useFuzzedDataProvider;
79   // Reused in every iteration analogous to JUnit's PER_CLASS lifecycle.
80   private static final Object fuzzTargetInstance;
81   private static final Method fuzzerTearDown;
82   private static final ArgumentsMutator mutator;
83   private static final ReproducerTemplate reproducerTemplate;
84   private static Predicate<Throwable> findingHandler;
85 
86   static {
87     FuzzTargetHolder.FuzzTarget fuzzTarget = FuzzTargetHolder.fuzzTarget;
88     Class<?> fuzzTargetClass = fuzzTarget.method.getDeclaringClass();
89 
90     // The method may not be accessible - JUnit test classes and methods are usually declared
91     // without access modifiers and thus package-private.
92     fuzzTarget.method.setAccessible(true);
93     try {
94       fuzzTargetMethod = MethodHandles.lookup().unreflect(fuzzTarget.method);
95     } catch (IllegalAccessException e) {
96       throw new IllegalStateException(e);
97     }
98     useFuzzedDataProvider = fuzzTarget.usesFuzzedDataProvider();
99     if (!useFuzzedDataProvider && IS_ANDROID) {
100       Log.error("Android fuzz targets must use " + FuzzedDataProvider.class.getName());
101       exit(1);
102       throw new IllegalStateException("Not reached");
103     }
104 
105     fuzzerTearDown = fuzzTarget.tearDown.orElse(null);
106     reproducerTemplate = new ReproducerTemplate(fuzzTargetClass.getName(), useFuzzedDataProvider);
107 
fuzzTargetClass.getName()108     JazzerInternal.onFuzzTargetReady(fuzzTargetClass.getName());
109 
110     try {
111       fuzzTargetInstance = fuzzTarget.newInstance.call();
112     } catch (Throwable t) {
113       Log.finding(t);
114       exit(1);
115       throw new IllegalStateException("Not reached");
116     }
117 
118     if (Opt.experimentalMutator) {
119       if (Modifier.isStatic(fuzzTarget.method.getModifiers())) {
120         mutator = ArgumentsMutator.forStaticMethodOrThrow(fuzzTarget.method);
121       } else {
122         mutator = ArgumentsMutator.forInstanceMethodOrThrow(fuzzTargetInstance, fuzzTarget.method);
123       }
124       Log.info("Using experimental mutator: " + mutator);
125     } else {
126       mutator = null;
127     }
128 
129     if (Opt.hooks) {
130       // libFuzzer will clear the coverage map after this method returns and keeps no record of the
131       // coverage accumulated so far (e.g. by static initializers). We record it here to keep it
132       // around for JaCoCo coverage reports.
CoverageRecorder.updateCoveredIdsWithCoverageMap()133       CoverageRecorder.updateCoveredIdsWithCoverageMap();
134     }
135 
136     Runtime.getRuntime().addShutdownHook(new Thread(FuzzTargetRunner::shutdown));
137   }
138 
139   /**
140    * A test-only convenience wrapper around {@link #runOne(long, int)}.
141    */
runOne(byte[] data)142   static int runOne(byte[] data) {
143     long dataPtr = UNSAFE.allocateMemory(data.length);
144     UNSAFE.copyMemory(data, BYTE_ARRAY_OFFSET, null, dataPtr, data.length);
145     try {
146       return runOne(dataPtr, data.length);
147     } finally {
148       UNSAFE.freeMemory(dataPtr);
149     }
150   }
151 
152   /**
153    * Executes the user-provided fuzz target once.
154    *
155    * @param dataPtr    a native pointer to beginning of the input provided by the fuzzer for this
156    *                   execution
157    * @param dataLength length of the fuzzer input
158    * @return the value that the native LLVMFuzzerTestOneInput function should return. Currently,
159    * this is always 0. The function may exit the process instead of returning.
160    */
runOne(long dataPtr, int dataLength)161   private static int runOne(long dataPtr, int dataLength) {
162     Throwable finding = null;
163     byte[] data;
164     Object argument;
165     if (Opt.experimentalMutator) {
166       // TODO: Instead of copying the native data and then reading it in, consider the following
167       //  optimizations if they turn out to be worthwhile in benchmarks:
168       //  1. Let libFuzzer pass in a null pointer if the byte array hasn't changed since the last
169       //     call to our custom mutator and skip the read entirely.
170       //  2. Implement a InputStream backed by Unsafe to avoid the copyToArray overhead.
171       byte[] buf = copyToArray(dataPtr, dataLength);
172       boolean readExactly = mutator.read(new ByteArrayInputStream(buf));
173 
174       // All inputs constructed by the mutator framework can be read exactly, existing corpus files
175       // may not be valid for the current fuzz target anymore, though. In this case, print a warning
176       // once.
177       if (!(invalidCorpusFileWarningShown || readExactly || isFixedLibFuzzerInput(buf))) {
178         invalidCorpusFileWarningShown = true;
179         Log.warn("Some files in the seed corpus do not match the fuzz target signature. "
180             + "This indicates that they were generated with a different signature and may cause issues reproducing previous findings.");
181       }
182       data = null;
183       argument = null;
184     } else if (useFuzzedDataProvider) {
185       fuzzedDataProvider.setNativeData(dataPtr, dataLength);
186       data = null;
187       argument = fuzzedDataProvider;
188     } else {
189       data = copyToArray(dataPtr, dataLength);
190       argument = data;
191     }
192     try {
193       if (Opt.experimentalMutator) {
194         // No need to detach as we are currently reading in the mutator state from bytes in every
195         // iteration.
196         mutator.invoke(false);
197       } else if (fuzzTargetInstance == null) {
198         fuzzTargetMethod.invoke(argument);
199       } else {
200         fuzzTargetMethod.invoke(fuzzTargetInstance, argument);
201       }
202     } catch (Throwable uncaughtFinding) {
203       finding = uncaughtFinding;
204     }
205 
206     // When using libFuzzer's -merge flag, only the coverage of the current input is relevant, not
207     // whether it is crashing. Since every crash would cause a restart of the process and thus the
208     // JVM, we can optimize this case by not crashing.
209     //
210     // Incidentally, this makes the behavior of fuzz targets relying on global states more
211     // consistent: Rather than resetting the global state after every crashing input and thus
212     // dependent on the particular ordering of the inputs, we never reset it.
213     if (Opt.mergeInner) {
214       return LIBFUZZER_CONTINUE;
215     }
216 
217     // Explicitly reported findings take precedence over uncaught exceptions.
218     if (JazzerInternal.lastFinding != null) {
219       finding = JazzerInternal.lastFinding;
220       JazzerInternal.lastFinding = null;
221     }
222     // Allow skipping invalid inputs in fuzz tests by using e.g. JUnit's assumeTrue.
223     if (finding == null || finding.getClass().getName().equals(OPENTEST4J_TEST_ABORTED_EXCEPTION)) {
224       return LIBFUZZER_CONTINUE;
225     }
226     if (Opt.hooks) {
227       finding = ExceptionUtils.preprocessThrowable(finding);
228     }
229 
230     long dedupToken = Opt.dedup ? ExceptionUtils.computeDedupToken(finding) : 0;
231     if (Opt.dedup && !ignoredTokens.add(dedupToken)) {
232       return LIBFUZZER_CONTINUE;
233     }
234 
235     if (findingHandler != null) {
236       // We still print the libFuzzer crashing input information, which also dumps the crashing
237       // input as a side effect.
238       printCrashingInput();
239       if (findingHandler.test(finding)) {
240         return LIBFUZZER_CONTINUE;
241       } else {
242         return LIBFUZZER_RETURN_FROM_DRIVER;
243       }
244     }
245 
246     // The user-provided fuzz target method has returned. Any further exits are on us and should not
247     // result in a "fuzz target exited" warning being printed by libFuzzer.
248     temporarilyDisableLibfuzzerExitHook();
249 
250     Log.finding(finding);
251     if (Opt.dedup) {
252       // Has to be printed to stdout as it is parsed by libFuzzer when minimizing a crash. It does
253       // not necessarily have to appear at the beginning of a line.
254       // https://github.com/llvm/llvm-project/blob/4c106c93eb68f8f9f201202677cd31e326c16823/compiler-rt/lib/fuzzer/FuzzerDriver.cpp#L342
255       Log.structuredOutput(String.format(Locale.ROOT, "DEDUP_TOKEN: %016x", dedupToken));
256     }
257     Log.println("== libFuzzer crashing input ==");
258     printCrashingInput();
259     // dumpReproducer needs to be called after libFuzzer printed its final stats as otherwise it
260     // would report incorrect coverage - the reproducer generation involved rerunning the fuzz
261     // target.
262     // It doesn't support @FuzzTest fuzz targets, but these come with an integrated regression test
263     // that satisfies the same purpose.
264     // It also doesn't support the experimental mutator yet as that requires implementing Java code
265     // generation for mutators.
266     if (fuzzTargetInstance == null && !Opt.experimentalMutator) {
267       dumpReproducer(data);
268     }
269 
270     if (!Opt.dedup || Long.compareUnsigned(ignoredTokens.size(), Opt.keepGoing) >= 0) {
271       // Reached the maximum amount of findings to keep going for, crash after shutdown. We use
272       // _Exit rather than System.exit to not trigger libFuzzer's exit handlers.
273       if (!Opt.autofuzz.isEmpty() && Opt.dedup) {
274         Log.println("");
275         Log.info(String.format(
276             "To continue fuzzing past this particular finding, rerun with the following additional argument:"
277                 + "%n%n    --ignore=%s%n%n"
278                 + "To ignore all findings of this kind, rerun with the following additional argument:"
279                 + "%n%n    --autofuzz_ignore=%s",
280             ignoredTokens.stream()
281                 .map(token -> Long.toUnsignedString(token, 16))
282                 .collect(joining(",")),
283             Stream.concat(Opt.autofuzzIgnore.stream(), Stream.of(finding.getClass().getName()))
284                 .collect(joining(","))));
285       }
286       System.exit(JAZZER_FINDING_EXIT_CODE);
287       throw new IllegalStateException("Not reached");
288     }
289     return LIBFUZZER_CONTINUE;
290   }
291 
isFixedLibFuzzerInput(byte[] input)292   private static boolean isFixedLibFuzzerInput(byte[] input) {
293     // Detect special libFuzzer inputs which can not be processed by the mutator framework.
294     // libFuzzer always uses an empty input, and one with a single line feed (10) to indicate
295     // end of initial corpus file processing.
296     return input.length == 0 || (input.length == 1 && input[0] == 10);
297   }
298 
299   // Called via JNI, being passed data from LLVMFuzzerCustomMutator.
300   @SuppressWarnings("unused")
mutateOne(long data, int size, int maxSize, int seed)301   private static int mutateOne(long data, int size, int maxSize, int seed) {
302     mutate(data, size, seed);
303     return writeToMemory(mutator, data, maxSize);
304   }
305 
mutate(long data, int size, int seed)306   private static void mutate(long data, int size, int seed) {
307     // libFuzzer sends the input "\n" when there are no corpus entries. We use that as a signal to
308     // initialize the mutator instead of just reading that trivial input to produce a more
309     // interesting value.
310     if (size == 1 && UNSAFE.getByte(data) == '\n') {
311       mutator.init(seed);
312     } else {
313       // TODO: See the comment on earlier calls to read for potential optimizations.
314       mutator.read(new ByteArrayInputStream(copyToArray(data, size)));
315       mutator.mutate(seed);
316     }
317   }
318 
319   private static long crossOverCount = 0;
320 
321   // Called via JNI, being passed data from LLVMFuzzerCustomCrossOver.
322   @SuppressWarnings("unused")
crossOver( long data1, int size1, long data2, int size2, long out, int maxOutSize, int seed)323   private static int crossOver(
324       long data1, int size1, long data2, int size2, long out, int maxOutSize, int seed) {
325     // Custom cross over and custom mutate are the only mutators registered in
326     // libFuzzer, hence cross over is picked as often as mutate, which is way
327     // too frequently. Without custom mutate, cross over would be picked from
328     // the list of default mutators, so ~1/12 of the time. This also seems too
329     // much and is reduced to a configurable frequency, default 1/100, here,
330     // mutate is used in the other cases.
331     if (Opt.experimentalCrossOverFrequency != 0
332         && crossOverCount++ % Opt.experimentalCrossOverFrequency == 0) {
333       mutator.crossOver(new ByteArrayInputStream(copyToArray(data1, size1)),
334           new ByteArrayInputStream(copyToArray(data2, size2)), seed);
335     } else {
336       mutate(data1, size1, seed);
337     }
338     return writeToMemory(mutator, out, maxOutSize);
339   }
340 
341   @SuppressWarnings("SameParameterValue")
writeToMemory(ArgumentsMutator mutator, long out, int maxOutSize)342   private static int writeToMemory(ArgumentsMutator mutator, long out, int maxOutSize) {
343     ByteArrayOutputStream baos = new ByteArrayOutputStream();
344     // TODO: Instead of writing to a byte array and then copying that array's contents into
345     //  memory, consider introducing an OutputStream backed by Unsafe.
346     mutator.write(baos);
347     byte[] mutatedBytes = baos.toByteArray();
348     int newSize = Math.min(mutatedBytes.length, maxOutSize);
349     UNSAFE.copyMemory(mutatedBytes, BYTE_ARRAY_OFFSET, null, out, newSize);
350     return newSize;
351   }
352 
353   /*
354    * Starts libFuzzer via LLVMFuzzerRunDriver.
355    */
startLibFuzzer(List<String> args)356   public static int startLibFuzzer(List<String> args) {
357     // We always define LLVMFuzzerCustomMutator, but only use it when --experimental_mutator is
358     // specified. libFuzzer contains logic that disables --len_control when it finds the custom
359     // mutator symbol:
360     // https://github.com/llvm/llvm-project/blob/da3623de2411dd931913eb510e94fe846c929c24/compiler-rt/lib/fuzzer/FuzzerDriver.cpp#L202-L207
361     // We thus have to explicitly set --len_control to its default value when not using the new
362     // mutator.
363     // TODO: libFuzzer still emits a message about --len_control being disabled by default even if
364     //  we override it via a flag. We may want to patch this out.
365     if (!Opt.experimentalMutator) {
366       // args may not be mutable.
367       args = new ArrayList<>(args);
368       // https://github.com/llvm/llvm-project/blob/da3623de2411dd931913eb510e94fe846c929c24/compiler-rt/lib/fuzzer/FuzzerFlags.def#L19
369       args.add("-len_control=100");
370     }
371 
372     for (String arg : args.subList(1, args.size())) {
373       if (!arg.startsWith("-")) {
374         Log.info("using inputs from: " + arg);
375       }
376     }
377 
378     if (!IS_ANDROID) {
379       SignalHandler.initialize();
380     }
381     return startLibFuzzer(
382         args.stream().map(str -> str.getBytes(StandardCharsets.UTF_8)).toArray(byte[][] ::new));
383   }
384 
385   /**
386    * Registers a custom handler for findings.
387    *
388    * @param findingHandler a consumer for the finding that returns true if the fuzzer should
389    *                       continue fuzzing and false if it should return from
390    *                       {@link FuzzTargetRunner#startLibFuzzer(List)}.
391    */
registerFindingHandler(Predicate<Throwable> findingHandler)392   public static void registerFindingHandler(Predicate<Throwable> findingHandler) {
393     FuzzTargetRunner.findingHandler = findingHandler;
394   }
395 
shutdown()396   private static void shutdown() {
397     if (!Opt.coverageDump.isEmpty() || !Opt.coverageReport.isEmpty()) {
398       if (!Opt.coverageDump.isEmpty()) {
399         CoverageRecorder.dumpJacocoCoverage(Opt.coverageDump);
400       }
401       if (!Opt.coverageReport.isEmpty()) {
402         CoverageRecorder.dumpCoverageReport(Opt.coverageReport);
403       }
404     }
405 
406     if (fuzzerTearDown == null) {
407       return;
408     }
409     Log.info("calling fuzzerTearDown function");
410     try {
411       fuzzerTearDown.invoke(null);
412     } catch (InvocationTargetException e) {
413       Log.finding(e.getCause());
414       System.exit(JAZZER_FINDING_EXIT_CODE);
415     } catch (Throwable t) {
416       Log.error(t);
417       System.exit(1);
418     }
419   }
420 
dumpReproducer(byte[] data)421   private static void dumpReproducer(byte[] data) {
422     if (data == null) {
423       assert useFuzzedDataProvider;
424       fuzzedDataProvider.reset();
425       data = fuzzedDataProvider.consumeRemainingAsBytes();
426     }
427     MessageDigest digest;
428     try {
429       digest = MessageDigest.getInstance("SHA-1");
430     } catch (NoSuchAlgorithmException e) {
431       throw new IllegalStateException("SHA-1 not available", e);
432     }
433     String dataSha1 = toHexString(digest.digest(data));
434 
435     if (!Opt.autofuzz.isEmpty()) {
436       fuzzedDataProvider.reset();
437       FuzzTarget.dumpReproducer(fuzzedDataProvider, Opt.reproducerPath, dataSha1);
438       return;
439     }
440 
441     String base64Data;
442     if (useFuzzedDataProvider) {
443       fuzzedDataProvider.reset();
444       FuzzedDataProvider recordingFuzzedDataProvider =
445           RecordingFuzzedDataProvider.makeFuzzedDataProviderProxy(fuzzedDataProvider);
446       try {
447         fuzzTargetMethod.invokeExact(recordingFuzzedDataProvider);
448         if (JazzerInternal.lastFinding == null) {
449           Log.warn("Failed to reproduce crash when rerunning with recorder");
450         }
451       } catch (Throwable ignored) {
452         // Expected.
453       }
454       try {
455         base64Data = RecordingFuzzedDataProvider.serializeFuzzedDataProviderProxy(
456             recordingFuzzedDataProvider);
457       } catch (IOException e) {
458         Log.error("Failed to create reproducer", e);
459         // Don't let libFuzzer print a native stack trace.
460         System.exit(1);
461         throw new IllegalStateException("Not reached");
462       }
463     } else {
464       base64Data = Base64.getEncoder().encodeToString(data);
465     }
466 
467     reproducerTemplate.dumpReproducer(base64Data, dataSha1);
468   }
469 
470   /**
471    * Convert a byte array to a lower-case hex string.
472    *
473    * <p>The returned hex string always has {@code 2 * bytes.length} characters.
474    *
475    * @param bytes the bytes to convert
476    * @return a lower-case hex string representing the bytes
477    */
toHexString(byte[] bytes)478   private static String toHexString(byte[] bytes) {
479     String unpadded = new BigInteger(1, bytes).toString(16);
480     int numLeadingZeroes = 2 * bytes.length - unpadded.length();
481     return String.join("", Collections.nCopies(numLeadingZeroes, "0")) + unpadded;
482   }
483 
484   // Accessed by fuzz_target_runner.cpp.
485   @SuppressWarnings("unused")
dumpAllStackTraces()486   private static void dumpAllStackTraces() {
487     ExceptionUtils.dumpAllStackTraces();
488   }
489 
copyToArray(long ptr, int length)490   private static byte[] copyToArray(long ptr, int length) {
491     // TODO: Use Unsafe.allocateUninitializedArray instead once Java 9 is the base.
492     byte[] array = new byte[length];
493     UNSAFE.copyMemory(null, ptr, array, BYTE_ARRAY_OFFSET, length);
494     return array;
495   }
496 
497   /**
498    * Starts libFuzzer via LLVMFuzzerRunDriver.
499    *
500    * @param args command-line arguments encoded in UTF-8 (not null-terminated)
501    * @return the return value of LLVMFuzzerRunDriver
502    */
startLibFuzzer(byte[][] args)503   private static int startLibFuzzer(byte[][] args) {
504     return FuzzTargetRunnerNatives.startLibFuzzer(
505         args, FuzzTargetRunner.class, Opt.experimentalMutator);
506   }
507 
508   /**
509    * Causes libFuzzer to write the current input to disk as a crashing input and emit some
510    * information about it to stderr.
511    */
printCrashingInput()512   public static void printCrashingInput() {
513     FuzzTargetRunnerNatives.printCrashingInput();
514   }
515 
516   /**
517    * Returns the debug string of the current mutator.
518    * If no mutator is used, returns null.
519    */
mutatorDebugString()520   public static String mutatorDebugString() {
521     return mutator != null ? mutator.toString() : null;
522   }
523 
524   /**
525    * Returns whether the current mutator has detected invalid corpus files.
526    * If no mutator is used, returns false.
527    */
invalidCorpusFilesPresent()528   public static boolean invalidCorpusFilesPresent() {
529     return mutator != null && invalidCorpusFileWarningShown;
530   }
531 
532   /**
533    * Disables libFuzzer's fuzz target exit detection until the next call to {@link #runOne}.
534    *
535    * <p>Calling {@link System#exit} after having called this method will not trigger libFuzzer's
536    * exit hook that would otherwise print the "fuzz target exited" error message. This method should
537    * thus only be called after control has returned from the user-provided fuzz target.
538    */
temporarilyDisableLibfuzzerExitHook()539   private static void temporarilyDisableLibfuzzerExitHook() {
540     FuzzTargetRunnerNatives.temporarilyDisableLibfuzzerExitHook();
541   }
542 }
543