Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion examples/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)

Expand Down
57 changes: 22 additions & 35 deletions src/main/java/com/code_intelligence/jazzer/api/Jazzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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");
Expand All @@ -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.
Expand All @@ -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() {}
Expand Down Expand Up @@ -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.
*
* <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}.
* <p>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);
}
Expand All @@ -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.
*
* <p>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)
*/
Expand Down