1 /* 2 * Copyright 2022 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 java.lang.System.err; 20 import static java.lang.System.exit; 21 import static java.lang.System.out; 22 23 import com.code_intelligence.jazzer.api.FuzzedDataProvider; 24 import com.code_intelligence.jazzer.autofuzz.FuzzTarget; 25 import com.code_intelligence.jazzer.instrumentor.CoverageRecorder; 26 import com.code_intelligence.jazzer.runtime.CoverageMap; 27 import com.code_intelligence.jazzer.runtime.FuzzedDataProviderImpl; 28 import com.code_intelligence.jazzer.runtime.JazzerInternal; 29 import com.code_intelligence.jazzer.runtime.RecordingFuzzedDataProvider; 30 import com.code_intelligence.jazzer.runtime.SignalHandler; 31 import com.code_intelligence.jazzer.runtime.UnsafeProvider; 32 import com.code_intelligence.jazzer.utils.ExceptionUtils; 33 import com.code_intelligence.jazzer.utils.ManifestUtils; 34 import com.github.fmeum.rules_jni.RulesJni; 35 import java.io.IOException; 36 import java.lang.invoke.MethodHandle; 37 import java.lang.invoke.MethodHandles; 38 import java.lang.reflect.InvocationTargetException; 39 import java.lang.reflect.Method; 40 import java.lang.reflect.Modifier; 41 import java.math.BigInteger; 42 import java.security.MessageDigest; 43 import java.security.NoSuchAlgorithmException; 44 import java.util.Arrays; 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 sun.misc.Unsafe; 52 53 /** 54 * Executes a fuzz target and reports findings. 55 * 56 * <p>This class maintains global state (both native and non-native) and thus cannot be used 57 * concurrently. 58 */ 59 public final class FuzzTargetRunner { 60 static { 61 RulesJni.loadLibrary("jazzer_driver", FuzzTargetRunner.class); 62 } 63 64 private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe(); 65 private static final long BYTE_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(byte[].class); 66 67 // Default value of the libFuzzer -error_exitcode flag. 68 private static final int LIBFUZZER_ERROR_EXIT_CODE = 77; 69 private static final String AUTOFUZZ_FUZZ_TARGET = 70 "com.code_intelligence.jazzer.autofuzz.FuzzTarget"; 71 private static final String FUZZER_TEST_ONE_INPUT = "fuzzerTestOneInput"; 72 private static final String FUZZER_INITIALIZE = "fuzzerInitialize"; 73 private static final String FUZZER_TEARDOWN = "fuzzerTearDown"; 74 75 private static final Set<Long> ignoredTokens = new HashSet<>(Opt.ignore); 76 private static final FuzzedDataProviderImpl fuzzedDataProvider = 77 FuzzedDataProviderImpl.withNativeData(); 78 private static final Class<?> fuzzTargetClass; 79 private static final MethodHandle fuzzTarget; 80 public static final boolean useFuzzedDataProvider; 81 private static final ReproducerTemplate reproducerTemplate; 82 83 static { 84 String targetClassName = determineFuzzTargetClassName(); 85 86 // FuzzTargetRunner is loaded by the bootstrap class loader since Driver installs the agent 87 // before invoking FuzzTargetRunner.startLibFuzzer. We can't load the fuzz target with that 88 // class loader - we have to use the class loader that loaded Driver. This would be 89 // straightforward to do in Java 9+, but requires the use of reflection to maintain 90 // compatibility with Java 8, which doesn't have StackWalker. 91 // 92 // Note that we can't just move the agent initialization so that FuzzTargetRunner is loaded by 93 // Driver's class loader: The agent and FuzzTargetRunner have to share the native library that 94 // contains libFuzzer and that library needs to be available in the bootstrap class loader 95 // since instrumentation applied to Java standard library classes still needs to be able to call 96 // libFuzzer hooks. A fundamental JNI restriction is that a native library can't be shared 97 // between two different class loaders, so FuzzTargetRunner is thus forced to be loaded in the 98 // bootstrap class loader, which makes this ugly code block necessary. 99 // We also can't use the system class loader since Driver may be loaded by a custom class loader 100 // if not invoked from the native driver. 101 Class<?> driverClass; 102 try { 103 Class<?> reflectionClass = Class.forName("sun.reflect.Reflection"); 104 try { 105 driverClass = 106 (Class<?>) reflectionClass.getMethod("getCallerClass", int.class).invoke(null, 2); 107 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 108 throw new IllegalStateException(e); 109 } 110 } catch (ClassNotFoundException e) { 111 // sun.reflect.Reflection is no longer available after Java 8, use StackWalker. 112 try { 113 Class<?> stackWalker = Class.forName("java.lang.StackWalker"); 114 Class<? extends Enum<?>> stackWalkerOption = 115 (Class<? extends Enum<?>>) Class.forName("java.lang.StackWalker$Option"); 116 Enum<?> retainClassReferences = 117 Arrays.stream(stackWalkerOption.getEnumConstants()) 118 .filter(v -> v.name().equals("RETAIN_CLASS_REFERENCE")) 119 .findFirst() 120 .orElseThrow(() 121 -> new IllegalStateException( 122 "No RETAIN_CLASS_REFERENCE in java.lang.StackWalker$Option")); 123 Object stackWalkerInstance = stackWalker.getMethod("getInstance", stackWalkerOption) 124 .invoke(null, retainClassReferences); 125 Method stackWalkerGetCallerClass = stackWalker.getMethod("getCallerClass"); 126 driverClass = (Class<?>) stackWalkerGetCallerClass.invoke(stackWalkerInstance); 127 } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException 128 | InvocationTargetException ex) { 129 throw new IllegalStateException(ex); 130 } 131 } 132 133 try { 134 ClassLoader driverClassLoader = driverClass.getClassLoader(); 135 driverClassLoader.setDefaultAssertionStatus(true); 136 fuzzTargetClass = Class.forName(targetClassName, false, driverClassLoader); 137 } catch (ClassNotFoundException e) { 138 err.print("ERROR: "); 139 e.printStackTrace(err); 140 exit(1); 141 throw new IllegalStateException("Not reached"); 142 } 143 // Inform the agent about the fuzz target class. Important note: This has to be done *before* 144 // the class is initialized so that hooks can enable themselves in time for the fuzz target's 145 // static initializer. 146 JazzerInternal.onFuzzTargetReady(targetClassName); 147 148 Method bytesFuzzTarget = targetPublicStaticMethodOrNull(FUZZER_TEST_ONE_INPUT, byte[].class); 149 Method dataFuzzTarget = 150 targetPublicStaticMethodOrNull(FUZZER_TEST_ONE_INPUT, FuzzedDataProvider.class); 151 if ((bytesFuzzTarget != null) == (dataFuzzTarget != null)) { 152 err.printf( 153 "ERROR: %s must define exactly one of the following two functions:%n", targetClassName); 154 err.println("public static void fuzzerTestOneInput(byte[] ...)"); 155 err.println("public static void fuzzerTestOneInput(FuzzedDataProvider ...)"); 156 err.println( 157 "Note: Fuzz targets returning boolean are no longer supported; exceptions should be thrown instead of returning true."); 158 exit(1); 159 } 160 try { 161 if (bytesFuzzTarget != null) { 162 useFuzzedDataProvider = false; 163 fuzzTarget = MethodHandles.publicLookup().unreflect(bytesFuzzTarget); 164 } else { 165 useFuzzedDataProvider = true; 166 fuzzTarget = MethodHandles.publicLookup().unreflect(dataFuzzTarget); 167 } 168 } catch (IllegalAccessException e) { 169 throw new RuntimeException(e); 170 } 171 reproducerTemplate = new ReproducerTemplate(fuzzTargetClass.getName(), useFuzzedDataProvider); 172 173 Method initializeNoArgs = targetPublicStaticMethodOrNull(FUZZER_INITIALIZE); 174 Method initializeWithArgs = targetPublicStaticMethodOrNull(FUZZER_INITIALIZE, String[].class); 175 try { 176 if (initializeWithArgs != null) { initializeWithArgs.invoke(null, (Object) Opt.targetArgs.toArray(new String[] {}))177 initializeWithArgs.invoke(null, (Object) Opt.targetArgs.toArray(new String[] {})); 178 } else if (initializeNoArgs != null) { 179 initializeNoArgs.invoke(null); 180 } 181 } catch (IllegalAccessException | InvocationTargetException e) { 182 err.print("== Java Exception in fuzzerInitialize: "); 183 e.printStackTrace(err); 184 exit(1); 185 } 186 187 if (Opt.hooks) { 188 // libFuzzer will clear the coverage map after this method returns and keeps no record of the 189 // coverage accumulated so far (e.g. by static initializers). We record it here to keep it 190 // around for JaCoCo coverage reports. CoverageRecorder.updateCoveredIdsWithCoverageMap()191 CoverageRecorder.updateCoveredIdsWithCoverageMap(); 192 } 193 194 Runtime.getRuntime().addShutdownHook(new Thread(FuzzTargetRunner::shutdown)); 195 } 196 197 /** 198 * A test-only convenience wrapper around {@link #runOne(long, int)}. 199 */ runOne(byte[] data)200 static int runOne(byte[] data) { 201 long dataPtr = UNSAFE.allocateMemory(data.length); 202 UNSAFE.copyMemory(data, BYTE_ARRAY_OFFSET, null, dataPtr, data.length); 203 try { 204 return runOne(dataPtr, data.length); 205 } finally { 206 UNSAFE.freeMemory(dataPtr); 207 } 208 } 209 210 /** 211 * Executes the user-provided fuzz target once. 212 * 213 * @param dataPtr a native pointer to beginning of the input provided by the fuzzer for this 214 * execution 215 * @param dataLength length of the fuzzer input 216 * @return the value that the native LLVMFuzzerTestOneInput function should return. Currently, 217 * this is always 0. The function may exit the process instead of returning. 218 */ runOne(long dataPtr, int dataLength)219 private static int runOne(long dataPtr, int dataLength) { 220 Throwable finding = null; 221 byte[] data = null; 222 try { 223 if (useFuzzedDataProvider) { 224 fuzzedDataProvider.setNativeData(dataPtr, dataLength); 225 fuzzTarget.invokeExact((FuzzedDataProvider) fuzzedDataProvider); 226 } else { 227 data = copyToArray(dataPtr, dataLength); 228 fuzzTarget.invokeExact(data); 229 } 230 } catch (Throwable uncaughtFinding) { 231 finding = uncaughtFinding; 232 } 233 // Explicitly reported findings take precedence over uncaught exceptions. 234 if (JazzerInternal.lastFinding != null) { 235 finding = JazzerInternal.lastFinding; 236 JazzerInternal.lastFinding = null; 237 } 238 if (finding == null) { 239 return 0; 240 } 241 if (Opt.hooks) { 242 finding = ExceptionUtils.preprocessThrowable(finding); 243 } 244 245 long dedupToken = Opt.dedup ? ExceptionUtils.computeDedupToken(finding) : 0; 246 // Opt.keepGoing implies Opt.dedup. 247 if (Opt.keepGoing > 1 && !ignoredTokens.add(dedupToken)) { 248 return 0; 249 } 250 251 err.println(); 252 err.print("== Java Exception: "); 253 finding.printStackTrace(err); 254 if (Opt.dedup) { 255 // Has to be printed to stdout as it is parsed by libFuzzer when minimizing a crash. It does 256 // not necessarily have to appear at the beginning of a line. 257 // https://github.com/llvm/llvm-project/blob/4c106c93eb68f8f9f201202677cd31e326c16823/compiler-rt/lib/fuzzer/FuzzerDriver.cpp#L342 258 out.printf(Locale.ROOT, "DEDUP_TOKEN: %016x%n", dedupToken); 259 } 260 err.println("== libFuzzer crashing input =="); 261 printCrashingInput(); 262 // dumpReproducer needs to be called after libFuzzer printed its final stats as otherwise it 263 // would report incorrect coverage - the reproducer generation involved rerunning the fuzz 264 // target. 265 dumpReproducer(data); 266 267 if (Opt.keepGoing == 1 || Long.compareUnsigned(ignoredTokens.size(), Opt.keepGoing) >= 0) { 268 // Reached the maximum amount of findings to keep going for, crash after shutdown. We use 269 // _Exit rather than System.exit to not trigger libFuzzer's exit handlers. 270 shutdown(); 271 _Exit(LIBFUZZER_ERROR_EXIT_CODE); 272 throw new IllegalStateException("Not reached"); 273 } 274 return 0; 275 } 276 277 /* 278 * Starts libFuzzer via LLVMFuzzerRunDriver. 279 * 280 * Note: Must be public rather than package-private as it is loaded in a different class loader 281 * than Driver. 282 */ startLibFuzzer(List<String> args)283 public static int startLibFuzzer(List<String> args) { 284 SignalHandler.initialize(); 285 return startLibFuzzer(Utils.toNativeArgs(args)); 286 } 287 shutdown()288 private static void shutdown() { 289 if (!Opt.coverageDump.isEmpty() || !Opt.coverageReport.isEmpty()) { 290 int[] everCoveredIds = CoverageMap.getEverCoveredIds(); 291 if (!Opt.coverageDump.isEmpty()) { 292 CoverageRecorder.dumpJacocoCoverage(everCoveredIds, Opt.coverageDump); 293 } 294 if (!Opt.coverageReport.isEmpty()) { 295 CoverageRecorder.dumpCoverageReport(everCoveredIds, Opt.coverageReport); 296 } 297 } 298 299 Method teardown = targetPublicStaticMethodOrNull(FUZZER_TEARDOWN); 300 if (teardown == null) { 301 return; 302 } 303 err.println("calling fuzzerTearDown function"); 304 try { 305 teardown.invoke(null); 306 } catch (InvocationTargetException e) { 307 // An exception in fuzzerTearDown is a regular finding. 308 err.print("== Java Exception in fuzzerTearDown: "); 309 e.getCause().printStackTrace(err); 310 _Exit(LIBFUZZER_ERROR_EXIT_CODE); 311 } catch (Throwable t) { 312 // Any other exception is an error. 313 t.printStackTrace(err); 314 _Exit(1); 315 } 316 } 317 determineFuzzTargetClassName()318 private static String determineFuzzTargetClassName() { 319 if (!Opt.autofuzz.isEmpty()) { 320 return AUTOFUZZ_FUZZ_TARGET; 321 } 322 if (!Opt.targetClass.isEmpty()) { 323 return Opt.targetClass; 324 } 325 String manifestTargetClass = ManifestUtils.detectFuzzTargetClass(); 326 if (manifestTargetClass != null) { 327 return manifestTargetClass; 328 } 329 err.println("Missing argument --target_class=<fuzz_target_class>"); 330 exit(1); 331 throw new IllegalStateException("Not reached"); 332 } 333 dumpReproducer(byte[] data)334 private static void dumpReproducer(byte[] data) { 335 if (data == null) { 336 assert useFuzzedDataProvider; 337 fuzzedDataProvider.reset(); 338 data = fuzzedDataProvider.consumeRemainingAsBytes(); 339 } 340 MessageDigest digest; 341 try { 342 digest = MessageDigest.getInstance("SHA-1"); 343 } catch (NoSuchAlgorithmException e) { 344 throw new IllegalStateException("SHA-1 not available", e); 345 } 346 String dataSha1 = toHexString(digest.digest(data)); 347 348 if (!Opt.autofuzz.isEmpty()) { 349 fuzzedDataProvider.reset(); 350 FuzzTarget.dumpReproducer(fuzzedDataProvider, Opt.reproducerPath, dataSha1); 351 return; 352 } 353 354 String base64Data; 355 if (useFuzzedDataProvider) { 356 fuzzedDataProvider.reset(); 357 FuzzedDataProvider recordingFuzzedDataProvider = 358 RecordingFuzzedDataProvider.makeFuzzedDataProviderProxy(fuzzedDataProvider); 359 try { 360 fuzzTarget.invokeExact(recordingFuzzedDataProvider); 361 if (JazzerInternal.lastFinding == null) { 362 err.println("Failed to reproduce crash when rerunning with recorder"); 363 } 364 } catch (Throwable ignored) { 365 // Expected. 366 } 367 try { 368 base64Data = RecordingFuzzedDataProvider.serializeFuzzedDataProviderProxy( 369 recordingFuzzedDataProvider); 370 } catch (IOException e) { 371 err.print("ERROR: Failed to create reproducer: "); 372 e.printStackTrace(err); 373 // Don't let libFuzzer print a native stack trace. 374 _Exit(1); 375 throw new IllegalStateException("Not reached"); 376 } 377 } else { 378 base64Data = Base64.getEncoder().encodeToString(data); 379 } 380 381 reproducerTemplate.dumpReproducer(base64Data, dataSha1); 382 } 383 targetPublicStaticMethodOrNull(String name, Class<?>... parameterTypes)384 private static Method targetPublicStaticMethodOrNull(String name, Class<?>... parameterTypes) { 385 try { 386 Method method = fuzzTargetClass.getMethod(name, parameterTypes); 387 if (!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers())) { 388 return null; 389 } 390 return method; 391 } catch (NoSuchMethodException e) { 392 return null; 393 } 394 } 395 396 /** 397 * Convert a byte array to a lower-case hex string. 398 * 399 * <p>The returned hex string always has {@code 2 * bytes.length} characters. 400 * 401 * @param bytes the bytes to convert 402 * @return a lower-case hex string representing the bytes 403 */ toHexString(byte[] bytes)404 private static String toHexString(byte[] bytes) { 405 String unpadded = new BigInteger(1, bytes).toString(16); 406 int numLeadingZeroes = 2 * bytes.length - unpadded.length(); 407 return String.join("", Collections.nCopies(numLeadingZeroes, "0")) + unpadded; 408 } 409 410 // Accessed by fuzz_target_runner.cpp. 411 @SuppressWarnings("unused") dumpAllStackTraces()412 private static void dumpAllStackTraces() { 413 ExceptionUtils.dumpAllStackTraces(); 414 } 415 copyToArray(long ptr, int length)416 private static byte[] copyToArray(long ptr, int length) { 417 // TODO: Use Unsafe.allocateUninitializedArray instead once Java 9 is the base. 418 byte[] array = new byte[length]; 419 UNSAFE.copyMemory(null, ptr, array, BYTE_ARRAY_OFFSET, length); 420 return array; 421 } 422 423 /** 424 * Starts libFuzzer via LLVMFuzzerRunDriver. 425 * 426 * @param args command-line arguments encoded in UTF-8 (not null-terminated) 427 * @return the return value of LLVMFuzzerRunDriver 428 */ startLibFuzzer(byte[][] args)429 private static native int startLibFuzzer(byte[][] args); 430 431 /** 432 * Causes libFuzzer to write the current input to disk as a crashing input and emit some 433 * information about it to stderr. 434 */ printCrashingInput()435 private static native void printCrashingInput(); 436 437 /** 438 * Immediately terminates the process without performing any cleanup. 439 * 440 * <p>Neither JVM shutdown hooks nor native exit handlers are called. This method does not return. 441 * 442 * <p>This method provides a way to exit Jazzer without triggering libFuzzer's exit hook that 443 * prints the "fuzz target exited" error message. It should thus be preferred over 444 * {@link System#exit} in any situation where Jazzer encounters an error after the fuzz target has 445 * started running. 446 * 447 * @param exitCode the exit code 448 */ _Exit(int exitCode)449 private static native void _Exit(int exitCode); 450 } 451