diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 07e30e325..d894594d7 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -129,7 +129,6 @@ java_fuzz_target_test( "src/main/java/com/example/MazeFuzzer.java", ], allowed_findings = ["com.example.MazeFuzzer$$TreasureFoundException"], - fuzzer_args = ["-use_value_profile=1"], target_class = "com.example.MazeFuzzer", ) diff --git a/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java b/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java index 44d4dd075..72dc8aa9c 100644 --- a/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java +++ b/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java @@ -32,10 +32,12 @@ public final class Jazzer { 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; private static final MethodHandle COUNTERS_TRACKER_ALLOCATE; private static final MethodHandle COUNTERS_TRACKER_SET_RANGE; + private static final MethodHandle COUNTERS_TRACKER_SET_COUNTER; + + private static final byte[] EXPLORE_BUCKET_VALUES = {1, 2, 3, 4, 8, 16, 32, (byte) 128}; /** * Default number of counters allocated for each call site of a method that requires registering a @@ -53,9 +55,9 @@ public final class Jazzer { MethodHandle traceStrcmp = null; MethodHandle traceStrstr = null; MethodHandle traceMemcmp = null; - MethodHandle tracePcIndir = null; MethodHandle countersTrackerAllocate = null; MethodHandle countersTrackerSetRange = null; + MethodHandle countersTrackerSetCounter = null; try { jazzerInternal = Class.forName("com.code_intelligence.jazzer.runtime.JazzerInternal"); MethodType onFuzzTargetReadyType = MethodType.methodType(void.class, Runnable.class); @@ -82,10 +84,6 @@ public final class Jazzer { traceMemcmp = MethodHandles.publicLookup() .findStatic(traceDataFlowNativeCallbacks, "traceMemcmp", traceMemcmpType); - MethodType tracePcIndirType = MethodType.methodType(void.class, int.class, int.class); - tracePcIndir = - MethodHandles.publicLookup() - .findStatic(traceDataFlowNativeCallbacks, "tracePcIndir", tracePcIndirType); Class> countersTracker = Class.forName("com.code_intelligence.jazzer.runtime.ExtraCountersTracker"); @@ -96,6 +94,10 @@ public final class Jazzer { MethodType setRangeType = MethodType.methodType(void.class, int.class, int.class); countersTrackerSetRange = MethodHandles.publicLookup().findStatic(countersTracker, "setCounterRange", setRangeType); + MethodType setCounterType = + MethodType.methodType(void.class, int.class, int.class, byte.class); + countersTrackerSetCounter = + MethodHandles.publicLookup().findStatic(countersTracker, "setCounter", setCounterType); } catch (ClassNotFoundException ignore) { // Not running in the context of the agent. This is fine as long as no methods are called on // this class. @@ -111,9 +113,9 @@ public final class Jazzer { TRACE_STRCMP = traceStrcmp; TRACE_STRSTR = traceStrstr; TRACE_MEMCMP = traceMemcmp; - TRACE_PC_INDIR = tracePcIndir; COUNTERS_TRACKER_ALLOCATE = countersTrackerAllocate; COUNTERS_TRACKER_SET_RANGE = countersTrackerSetRange; + COUNTERS_TRACKER_SET_COUNTER = countersTrackerSetCounter; } private Jazzer() {} @@ -206,48 +208,31 @@ public static void guideTowardsContainment(String haystack, String needle, int i } /** - * Instructs the fuzzer to attain as many possible values for the absolute value of {@code state} - * as possible. + * Instructs the fuzzer to attain as many possible values for {@code state} as possible. * *
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). * - *
Note: This hint only takes effect if the fuzzer is run with the argument {@code - * -use_value_profile=1}. + *
Each unique state value is tracked via libFuzzer's counter bucketing mechanism, enabling us + * to represent 8 different states for each coverage counter. As a result, all 256 byte values are + * distinguished by mapping each to a unique (counter, bucket) pair across 32 counters. See: + * https://github.com/llvm/llvm-project/blob/972e73b812cb7b6dd349c7c07daae73314f29e8f/compiler-rt/lib/fuzzer/FuzzerTracePC.h#L213-L235 * * @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) { + if (COUNTERS_TRACKER_ALLOCATE == 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 (JazzerApiException e) { - throw e; + COUNTERS_TRACKER_ALLOCATE.invokeExact(id, 32); + int unsignedState = state & 0xff; + int counterIndex = unsignedState >> 3; + byte counterValue = EXPLORE_BUCKET_VALUES[unsignedState & 0x7]; + COUNTERS_TRACKER_SET_COUNTER.invokeExact(id, counterIndex, counterValue); } catch (Throwable e) { throw new JazzerApiException("exploreState: " + e.getMessage(), e); } @@ -258,6 +243,8 @@ public static void exploreState(byte state, int id) { * generated call-site identifiers. During instrumentation, calls to this method are replaced with * calls to {@link #exploreState(byte, int)} using a unique id for each call site. * + *
Without instrumentation, this is a no-op. + * * @param state a numeric encoding of a state that should be varied by the fuzzer * @see #exploreState(byte, int) */