aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/code_intelligence/jazzer/api/Jazzer.java')
-rw-r--r--src/main/java/com/code_intelligence/jazzer/api/Jazzer.java268
1 files changed, 268 insertions, 0 deletions
diff --git a/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java b/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java
new file mode 100644
index 00000000..aad9ae01
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java
@@ -0,0 +1,268 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.api;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.InvocationTargetException;
+import java.security.SecureRandom;
+
+/**
+ * Static helper methods that hooks can use to provide feedback to the fuzzer.
+ */
+public final class Jazzer {
+ private static final Class<?> JAZZER_INTERNAL;
+
+ private static final MethodHandle ON_FUZZ_TARGET_READY;
+
+ private static final MethodHandle TRACE_STRCMP;
+ private static final MethodHandle TRACE_STRSTR;
+ private static final MethodHandle TRACE_MEMCMP;
+ private static final MethodHandle TRACE_PC_INDIR;
+
+ static {
+ Class<?> jazzerInternal = null;
+ MethodHandle onFuzzTargetReady = null;
+ MethodHandle traceStrcmp = null;
+ MethodHandle traceStrstr = null;
+ MethodHandle traceMemcmp = null;
+ MethodHandle tracePcIndir = null;
+ try {
+ jazzerInternal = Class.forName("com.code_intelligence.jazzer.runtime.JazzerInternal");
+ MethodType onFuzzTargetReadyType = MethodType.methodType(void.class, Runnable.class);
+ onFuzzTargetReady = MethodHandles.publicLookup().findStatic(
+ jazzerInternal, "registerOnFuzzTargetReadyCallback", onFuzzTargetReadyType);
+ Class<?> traceDataFlowNativeCallbacks =
+ Class.forName("com.code_intelligence.jazzer.runtime.TraceDataFlowNativeCallbacks");
+
+ // Use method handles for hints as the calls are potentially performance critical.
+ MethodType traceStrcmpType =
+ MethodType.methodType(void.class, String.class, String.class, int.class, int.class);
+ traceStrcmp = MethodHandles.publicLookup().findStatic(
+ traceDataFlowNativeCallbacks, "traceStrcmp", traceStrcmpType);
+ MethodType traceStrstrType =
+ MethodType.methodType(void.class, String.class, String.class, int.class);
+ traceStrstr = MethodHandles.publicLookup().findStatic(
+ traceDataFlowNativeCallbacks, "traceStrstr", traceStrstrType);
+ MethodType traceMemcmpType =
+ MethodType.methodType(void.class, byte[].class, byte[].class, int.class, int.class);
+ traceMemcmp = MethodHandles.publicLookup().findStatic(
+ traceDataFlowNativeCallbacks, "traceMemcmp", traceMemcmpType);
+ MethodType tracePcIndirType = MethodType.methodType(void.class, int.class, int.class);
+ tracePcIndir = MethodHandles.publicLookup().findStatic(
+ traceDataFlowNativeCallbacks, "tracePcIndir", tracePcIndirType);
+ } catch (ClassNotFoundException ignore) {
+ // Not running in the context of the agent. This is fine as long as no methods are called on
+ // this class.
+ } catch (NoSuchMethodException | IllegalAccessException e) {
+ // This should never happen as the Jazzer API is loaded from the agent and thus should always
+ // match the version of the runtime classes.
+ System.err.println("ERROR: Incompatible version of the Jazzer API detected, please update.");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ JAZZER_INTERNAL = jazzerInternal;
+ ON_FUZZ_TARGET_READY = onFuzzTargetReady;
+ TRACE_STRCMP = traceStrcmp;
+ TRACE_STRSTR = traceStrstr;
+ TRACE_MEMCMP = traceMemcmp;
+ TRACE_PC_INDIR = tracePcIndir;
+ }
+
+ private Jazzer() {}
+
+ /**
+ * A 32-bit random number that hooks can use to make pseudo-random choices
+ * between multiple possible mutations they could guide the fuzzer towards.
+ * Hooks <b>must not</b> base the decision whether or not to report a finding
+ * on this number as this will make findings non-reproducible.
+ * <p>
+ * This is the same number that libFuzzer uses as a seed internally, which
+ * makes it possible to deterministically reproduce a previous fuzzing run by
+ * supplying the seed value printed by libFuzzer as the value of the
+ * {@code -seed}.
+ */
+ public static final int SEED = getLibFuzzerSeed();
+
+ /**
+ * Instructs the fuzzer to guide its mutations towards making {@code current} equal to {@code
+ * target}.
+ * <p>
+ * If the relation between the raw fuzzer input and the value of {@code current} is relatively
+ * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to
+ * achieve equality.
+ *
+ * @param current a non-constant string observed during fuzz target execution
+ * @param target a string that {@code current} should become equal to, but currently isn't
+ * @param id a (probabilistically) unique identifier for this particular compare hint
+ */
+ public static void guideTowardsEquality(String current, String target, int id) {
+ if (TRACE_STRCMP == null) {
+ return;
+ }
+ try {
+ TRACE_STRCMP.invokeExact(current, target, 1, id);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Instructs the fuzzer to guide its mutations towards making {@code current} equal to {@code
+ * target}.
+ * <p>
+ * If the relation between the raw fuzzer input and the value of {@code current} is relatively
+ * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to
+ * achieve equality.
+ *
+ * @param current a non-constant byte array observed during fuzz target execution
+ * @param target a byte array that {@code current} should become equal to, but currently isn't
+ * @param id a (probabilistically) unique identifier for this particular compare hint
+ */
+ public static void guideTowardsEquality(byte[] current, byte[] target, int id) {
+ if (TRACE_MEMCMP == null) {
+ return;
+ }
+ try {
+ TRACE_MEMCMP.invokeExact(current, target, 1, id);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Instructs the fuzzer to guide its mutations towards making {@code haystack} contain {@code
+ * needle} as a substring.
+ * <p>
+ * If the relation between the raw fuzzer input and the value of {@code haystack} is relatively
+ * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to
+ * satisfy the substring check.
+ *
+ * @param haystack a non-constant string observed during fuzz target execution
+ * @param needle a string that should be contained in {@code haystack} as a substring, but
+ * currently isn't
+ * @param id a (probabilistically) unique identifier for this particular compare hint
+ */
+ public static void guideTowardsContainment(String haystack, String needle, int id) {
+ if (TRACE_STRSTR == null) {
+ return;
+ }
+ try {
+ TRACE_STRSTR.invokeExact(haystack, needle, id);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Instructs the fuzzer to attain as many possible values for the absolute value of {@code state}
+ * as possible.
+ * <p>
+ * Call this function from a fuzz target or a hook to help the fuzzer track partial progress
+ * (e.g. by passing the length of a common prefix of two lists that should become equal) or
+ * explore different values of state that is not directly related to code coverage (see the
+ * MazeFuzzer example).
+ * <p>
+ * <b>Note:</b> This hint only takes effect if the fuzzer is run with the argument
+ * {@code -use_value_profile=1}.
+ *
+ * @param state a numeric encoding of a state that should be varied by the fuzzer
+ * @param id a (probabilistically) unique identifier for this particular state hint
+ */
+ public static void exploreState(byte state, int id) {
+ if (TRACE_PC_INDIR == null) {
+ return;
+ }
+ // We only use the lower 7 bits of state, which allows for 128 different state values tracked
+ // per id. The particular amount of 7 bits of state is also used in libFuzzer's
+ // TracePC::HandleCmp:
+ // https://github.com/llvm/llvm-project/blob/c12d49c4e286fa108d4d69f1c6d2b8d691993ffd/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L390
+ // This value should be large enough for most use cases (e.g. tracking the length of a prefix in
+ // a comparison) while being small enough that the bitmap isn't filled up too quickly
+ // (65536 bits / 128 bits per id = 512 ids).
+
+ // We use tracePcIndir as a way to set a bit in libFuzzer's value profile bitmap. In
+ // TracePC::HandleCallerCallee, which is what this function ultimately calls through to, the
+ // lower 12 bits of each argument are combined into a 24-bit index into the bitmap, which is
+ // then reduced modulo a 16-bit prime. To keep the modulo bias small, we should fill as many
+ // of the relevant bits as possible.
+
+ // We pass state in the lowest bits of the caller address, which is used to form the lowest bits
+ // of the bitmap index. This should result in the best caching behavior as state is expected to
+ // change quickly in consecutive runs and in this way all its bitmap entries would be located
+ // close to each other in memory.
+ int lowerBits = (state & 0x7f) | (id << 7);
+ int upperBits = id >>> 5;
+ try {
+ TRACE_PC_INDIR.invokeExact(upperBits, lowerBits);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Make Jazzer report the provided {@link Throwable} as a finding.
+ * <p>
+ * <b>Note:</b> This method must only be called from a method hook. In a
+ * fuzz target, simply throw an exception to trigger a finding.
+ * @param finding the finding that Jazzer should report
+ */
+ public static void reportFindingFromHook(Throwable finding) {
+ try {
+ JAZZER_INTERNAL.getMethod("reportFindingFromHook", Throwable.class).invoke(null, finding);
+ } catch (NullPointerException | IllegalAccessException | NoSuchMethodException e) {
+ // We can only reach this point if the runtime is not on the classpath, e.g. in case of a
+ // reproducer. Just throw the finding.
+ rethrowUnchecked(finding);
+ } catch (InvocationTargetException e) {
+ rethrowUnchecked(e.getCause());
+ }
+ }
+
+ /**
+ * Register a callback to be executed right before the fuzz target is executed for the first time.
+ * <p>
+ * This can be used to disable hooks until after Jazzer has been fully initializing, e.g. to
+ * prevent Jazzer internals from triggering hooks on Java standard library classes.
+ *
+ * @param callback the callback to execute
+ */
+ public static void onFuzzTargetReady(Runnable callback) {
+ try {
+ ON_FUZZ_TARGET_READY.invokeExact(callback);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static int getLibFuzzerSeed() {
+ // The Jazzer driver sets this property based on the value of libFuzzer's -seed command-line
+ // option, which allows for fully reproducible fuzzing runs if set. If not running in the
+ // context of the driver, fall back to a random number instead.
+ String rawSeed = System.getProperty("jazzer.internal.seed");
+ if (rawSeed == null) {
+ return new SecureRandom().nextInt();
+ }
+ // If jazzer.internal.seed is set, we expect it to be a valid integer.
+ return Integer.parseUnsignedInt(rawSeed);
+ }
+
+ // Rethrows a (possibly checked) exception while avoiding a throws declaration.
+ @SuppressWarnings("unchecked")
+ private static <T extends Throwable> void rethrowUnchecked(Throwable t) throws T {
+ throw(T) t;
+ }
+}