1 // Copyright 2021 Code Intelligence GmbH 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.code_intelligence.jazzer.api; 16 17 import java.lang.invoke.MethodHandle; 18 import java.lang.invoke.MethodHandles; 19 import java.lang.invoke.MethodType; 20 import java.lang.reflect.InvocationTargetException; 21 import java.security.SecureRandom; 22 23 /** 24 * Static helper methods that hooks can use to provide feedback to the fuzzer. 25 */ 26 public final class Jazzer { 27 private static final Class<?> JAZZER_INTERNAL; 28 29 private static final MethodHandle ON_FUZZ_TARGET_READY; 30 31 private static final MethodHandle TRACE_STRCMP; 32 private static final MethodHandle TRACE_STRSTR; 33 private static final MethodHandle TRACE_MEMCMP; 34 private static final MethodHandle TRACE_PC_INDIR; 35 36 static { 37 Class<?> jazzerInternal = null; 38 MethodHandle onFuzzTargetReady = null; 39 MethodHandle traceStrcmp = null; 40 MethodHandle traceStrstr = null; 41 MethodHandle traceMemcmp = null; 42 MethodHandle tracePcIndir = null; 43 try { 44 jazzerInternal = Class.forName("com.code_intelligence.jazzer.runtime.JazzerInternal"); 45 MethodType onFuzzTargetReadyType = MethodType.methodType(void.class, Runnable.class); 46 onFuzzTargetReady = MethodHandles.publicLookup().findStatic( 47 jazzerInternal, "registerOnFuzzTargetReadyCallback", onFuzzTargetReadyType); 48 Class<?> traceDataFlowNativeCallbacks = 49 Class.forName("com.code_intelligence.jazzer.runtime.TraceDataFlowNativeCallbacks"); 50 51 // Use method handles for hints as the calls are potentially performance critical. 52 MethodType traceStrcmpType = 53 MethodType.methodType(void.class, String.class, String.class, int.class, int.class); 54 traceStrcmp = MethodHandles.publicLookup().findStatic( 55 traceDataFlowNativeCallbacks, "traceStrcmp", traceStrcmpType); 56 MethodType traceStrstrType = 57 MethodType.methodType(void.class, String.class, String.class, int.class); 58 traceStrstr = MethodHandles.publicLookup().findStatic( 59 traceDataFlowNativeCallbacks, "traceStrstr", traceStrstrType); 60 MethodType traceMemcmpType = 61 MethodType.methodType(void.class, byte[].class, byte[].class, int.class, int.class); 62 traceMemcmp = MethodHandles.publicLookup().findStatic( 63 traceDataFlowNativeCallbacks, "traceMemcmp", traceMemcmpType); 64 MethodType tracePcIndirType = MethodType.methodType(void.class, int.class, int.class); 65 tracePcIndir = MethodHandles.publicLookup().findStatic( 66 traceDataFlowNativeCallbacks, "tracePcIndir", tracePcIndirType); 67 } catch (ClassNotFoundException ignore) { 68 // Not running in the context of the agent. This is fine as long as no methods are called on 69 // this class. 70 } catch (NoSuchMethodException | IllegalAccessException e) { 71 // This should never happen as the Jazzer API is loaded from the agent and thus should always 72 // match the version of the runtime classes. 73 System.err.println("ERROR: Incompatible version of the Jazzer API detected, please update."); 74 e.printStackTrace(); 75 System.exit(1); 76 } 77 JAZZER_INTERNAL = jazzerInternal; 78 ON_FUZZ_TARGET_READY = onFuzzTargetReady; 79 TRACE_STRCMP = traceStrcmp; 80 TRACE_STRSTR = traceStrstr; 81 TRACE_MEMCMP = traceMemcmp; 82 TRACE_PC_INDIR = tracePcIndir; 83 } 84 Jazzer()85 private Jazzer() {} 86 87 /** 88 * A 32-bit random number that hooks can use to make pseudo-random choices 89 * between multiple possible mutations they could guide the fuzzer towards. 90 * Hooks <b>must not</b> base the decision whether or not to report a finding 91 * on this number as this will make findings non-reproducible. 92 * <p> 93 * This is the same number that libFuzzer uses as a seed internally, which 94 * makes it possible to deterministically reproduce a previous fuzzing run by 95 * supplying the seed value printed by libFuzzer as the value of the 96 * {@code -seed}. 97 */ 98 public static final int SEED = getLibFuzzerSeed(); 99 100 /** 101 * Instructs the fuzzer to guide its mutations towards making {@code current} equal to {@code 102 * target}. 103 * <p> 104 * If the relation between the raw fuzzer input and the value of {@code current} is relatively 105 * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to 106 * achieve equality. 107 * 108 * @param current a non-constant string observed during fuzz target execution 109 * @param target a string that {@code current} should become equal to, but currently isn't 110 * @param id a (probabilistically) unique identifier for this particular compare hint 111 */ guideTowardsEquality(String current, String target, int id)112 public static void guideTowardsEquality(String current, String target, int id) { 113 if (TRACE_STRCMP == null) { 114 return; 115 } 116 try { 117 TRACE_STRCMP.invokeExact(current, target, 1, id); 118 } catch (Throwable e) { 119 e.printStackTrace(); 120 } 121 } 122 123 /** 124 * Instructs the fuzzer to guide its mutations towards making {@code current} equal to {@code 125 * target}. 126 * <p> 127 * If the relation between the raw fuzzer input and the value of {@code current} is relatively 128 * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to 129 * achieve equality. 130 * 131 * @param current a non-constant byte array observed during fuzz target execution 132 * @param target a byte array that {@code current} should become equal to, but currently isn't 133 * @param id a (probabilistically) unique identifier for this particular compare hint 134 */ guideTowardsEquality(byte[] current, byte[] target, int id)135 public static void guideTowardsEquality(byte[] current, byte[] target, int id) { 136 if (TRACE_MEMCMP == null) { 137 return; 138 } 139 try { 140 TRACE_MEMCMP.invokeExact(current, target, 1, id); 141 } catch (Throwable e) { 142 e.printStackTrace(); 143 } 144 } 145 146 /** 147 * Instructs the fuzzer to guide its mutations towards making {@code haystack} contain {@code 148 * needle} as a substring. 149 * <p> 150 * If the relation between the raw fuzzer input and the value of {@code haystack} is relatively 151 * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to 152 * satisfy the substring check. 153 * 154 * @param haystack a non-constant string observed during fuzz target execution 155 * @param needle a string that should be contained in {@code haystack} as a substring, but 156 * currently isn't 157 * @param id a (probabilistically) unique identifier for this particular compare hint 158 */ guideTowardsContainment(String haystack, String needle, int id)159 public static void guideTowardsContainment(String haystack, String needle, int id) { 160 if (TRACE_STRSTR == null) { 161 return; 162 } 163 try { 164 TRACE_STRSTR.invokeExact(haystack, needle, id); 165 } catch (Throwable e) { 166 e.printStackTrace(); 167 } 168 } 169 170 /** 171 * Instructs the fuzzer to attain as many possible values for the absolute value of {@code state} 172 * as possible. 173 * <p> 174 * Call this function from a fuzz target or a hook to help the fuzzer track partial progress 175 * (e.g. by passing the length of a common prefix of two lists that should become equal) or 176 * explore different values of state that is not directly related to code coverage (see the 177 * MazeFuzzer example). 178 * <p> 179 * <b>Note:</b> This hint only takes effect if the fuzzer is run with the argument 180 * {@code -use_value_profile=1}. 181 * 182 * @param state a numeric encoding of a state that should be varied by the fuzzer 183 * @param id a (probabilistically) unique identifier for this particular state hint 184 */ exploreState(byte state, int id)185 public static void exploreState(byte state, int id) { 186 if (TRACE_PC_INDIR == null) { 187 return; 188 } 189 // We only use the lower 7 bits of state, which allows for 128 different state values tracked 190 // per id. The particular amount of 7 bits of state is also used in libFuzzer's 191 // TracePC::HandleCmp: 192 // https://github.com/llvm/llvm-project/blob/c12d49c4e286fa108d4d69f1c6d2b8d691993ffd/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L390 193 // This value should be large enough for most use cases (e.g. tracking the length of a prefix in 194 // a comparison) while being small enough that the bitmap isn't filled up too quickly 195 // (65536 bits / 128 bits per id = 512 ids). 196 197 // We use tracePcIndir as a way to set a bit in libFuzzer's value profile bitmap. In 198 // TracePC::HandleCallerCallee, which is what this function ultimately calls through to, the 199 // lower 12 bits of each argument are combined into a 24-bit index into the bitmap, which is 200 // then reduced modulo a 16-bit prime. To keep the modulo bias small, we should fill as many 201 // of the relevant bits as possible. 202 203 // We pass state in the lowest bits of the caller address, which is used to form the lowest bits 204 // of the bitmap index. This should result in the best caching behavior as state is expected to 205 // change quickly in consecutive runs and in this way all its bitmap entries would be located 206 // close to each other in memory. 207 int lowerBits = (state & 0x7f) | (id << 7); 208 int upperBits = id >>> 5; 209 try { 210 TRACE_PC_INDIR.invokeExact(upperBits, lowerBits); 211 } catch (Throwable e) { 212 e.printStackTrace(); 213 } 214 } 215 216 /** 217 * Make Jazzer report the provided {@link Throwable} as a finding. 218 * <p> 219 * <b>Note:</b> This method must only be called from a method hook. In a 220 * fuzz target, simply throw an exception to trigger a finding. 221 * @param finding the finding that Jazzer should report 222 */ reportFindingFromHook(Throwable finding)223 public static void reportFindingFromHook(Throwable finding) { 224 try { 225 JAZZER_INTERNAL.getMethod("reportFindingFromHook", Throwable.class).invoke(null, finding); 226 } catch (NullPointerException | IllegalAccessException | NoSuchMethodException e) { 227 // We can only reach this point if the runtime is not on the classpath, e.g. in case of a 228 // reproducer. Just throw the finding. 229 rethrowUnchecked(finding); 230 } catch (InvocationTargetException e) { 231 rethrowUnchecked(e.getCause()); 232 } 233 } 234 235 /** 236 * Register a callback to be executed right before the fuzz target is executed for the first time. 237 * <p> 238 * This can be used to disable hooks until after Jazzer has been fully initializing, e.g. to 239 * prevent Jazzer internals from triggering hooks on Java standard library classes. 240 * 241 * @param callback the callback to execute 242 */ onFuzzTargetReady(Runnable callback)243 public static void onFuzzTargetReady(Runnable callback) { 244 try { 245 ON_FUZZ_TARGET_READY.invokeExact(callback); 246 } catch (Throwable e) { 247 e.printStackTrace(); 248 } 249 } 250 getLibFuzzerSeed()251 private static int getLibFuzzerSeed() { 252 // The Jazzer driver sets this property based on the value of libFuzzer's -seed command-line 253 // option, which allows for fully reproducible fuzzing runs if set. If not running in the 254 // context of the driver, fall back to a random number instead. 255 String rawSeed = System.getProperty("jazzer.internal.seed"); 256 if (rawSeed == null) { 257 return new SecureRandom().nextInt(); 258 } 259 // If jazzer.internal.seed is set, we expect it to be a valid integer. 260 return Integer.parseUnsignedInt(rawSeed); 261 } 262 263 // Rethrows a (possibly checked) exception while avoiding a throws declaration. 264 @SuppressWarnings("unchecked") rethrowUnchecked(Throwable t)265 private static <T extends Throwable> void rethrowUnchecked(Throwable t) throws T { 266 throw(T) t; 267 } 268 } 269