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 com.code_intelligence.jazzer.runtime.Constants.IS_ANDROID; 20 import static java.lang.System.exit; 21 22 import com.code_intelligence.jazzer.agent.AgentInstaller; 23 import com.code_intelligence.jazzer.driver.junit.JUnitRunner; 24 import com.code_intelligence.jazzer.utils.Log; 25 import java.io.File; 26 import java.io.IOException; 27 import java.nio.file.Files; 28 import java.nio.file.Path; 29 import java.nio.file.Paths; 30 import java.security.SecureRandom; 31 import java.util.List; 32 import java.util.Optional; 33 34 public class Driver { start(List<String> args, boolean spawnsSubprocesses)35 public static int start(List<String> args, boolean spawnsSubprocesses) throws IOException { 36 if (IS_ANDROID) { 37 if (!System.getProperty("jazzer.autofuzz", "").isEmpty()) { 38 Log.error("--autofuzz is not supported for Android"); 39 return 1; 40 } 41 if (!System.getProperty("jazzer.coverage_report", "").isEmpty()) { 42 Log.warn("--coverage_report is not supported for Android and has been disabled"); 43 System.clearProperty("jazzer.coverage_report"); 44 } 45 if (!System.getProperty("jazzer.coverage_dump", "").isEmpty()) { 46 Log.warn("--coverage_dump is not supported for Android and has been disabled"); 47 System.clearProperty("jazzer.coverage_dump"); 48 } 49 } 50 51 if (spawnsSubprocesses) { 52 if (!System.getProperty("jazzer.coverage_report", "").isEmpty()) { 53 Log.warn("--coverage_report does not support parallel fuzzing and has been disabled"); 54 System.clearProperty("jazzer.coverage_report"); 55 } 56 if (!System.getProperty("jazzer.coverage_dump", "").isEmpty()) { 57 Log.warn("--coverage_dump does not support parallel fuzzing and has been disabled"); 58 System.clearProperty("jazzer.coverage_dump"); 59 } 60 61 String idSyncFileArg = System.getProperty("jazzer.id_sync_file", ""); 62 Path idSyncFile; 63 if (idSyncFileArg.isEmpty()) { 64 // Create an empty temporary file used for coverage ID synchronization and 65 // pass its path to the agent in every child process. This requires adding 66 // the argument to argv for it to be picked up by libFuzzer, which then 67 // forwards it to child processes. 68 if (!IS_ANDROID) { 69 idSyncFile = Files.createTempFile("jazzer-", ""); 70 } else { 71 File f = File.createTempFile("jazzer-", "", new File("/data/local/tmp/")); 72 idSyncFile = f.toPath(); 73 } 74 75 args.add("--id_sync_file=" + idSyncFile.toAbsolutePath()); 76 } else { 77 // Creates the file, truncating it if it exists. 78 idSyncFile = Files.write(Paths.get(idSyncFileArg), new byte[] {}); 79 } 80 // This wouldn't run in case we exit the process with _Exit, but the parent process of a -fork 81 // run is expected to exit with a regular exit(0), which does cause JVM shutdown hooks to run: 82 // https://github.com/llvm/llvm-project/blob/940e178c0018b32af2f1478d331fc41a92a7dac7/compiler-rt/lib/fuzzer/FuzzerFork.cpp#L491 83 idSyncFile.toFile().deleteOnExit(); 84 } 85 86 if (args.stream().anyMatch("-merge_inner=1" ::equals)) { 87 System.setProperty("jazzer.internal.merge_inner", "true"); 88 } 89 90 // Jazzer's hooks use deterministic randomness and thus require a seed. Search for the last 91 // occurrence of a "-seed" argument as that is the one that is used by libFuzzer. If none is 92 // set, generate one and pass it to libFuzzer so that a fuzzing run can be reproduced simply by 93 // setting the seed printed by libFuzzer. 94 String seed = args.stream().reduce( 95 null, (prev, cur) -> cur.startsWith("-seed=") ? cur.substring("-seed=".length()) : prev); 96 if (seed == null) { 97 seed = Integer.toUnsignedString(new SecureRandom().nextInt()); 98 // Only add the -seed argument to the command line if not running in a mode 99 // that spawns subprocesses. These would inherit the same seed, which might 100 // make them less effective. 101 if (!spawnsSubprocesses) { 102 args.add("-seed=" + seed); 103 } 104 } 105 System.setProperty("jazzer.internal.seed", seed); 106 107 if (args.stream().noneMatch(arg -> arg.startsWith("-rss_limit_mb="))) { 108 args.add(getDefaultRssLimitMbArg()); 109 } 110 111 // Do not modify properties beyond this point, loading Opt locks in their values. 112 if (!Opt.instrumentOnly.isEmpty()) { 113 boolean instrumentationSuccess = OfflineInstrumentor.instrumentJars(Opt.instrumentOnly); 114 if (!instrumentationSuccess) { 115 exit(1); 116 } 117 exit(0); 118 } 119 120 Driver.class.getClassLoader().setDefaultAssertionStatus(true); 121 122 if (!Opt.autofuzz.isEmpty()) { 123 AgentInstaller.install(Opt.hooks); 124 FuzzTargetHolder.fuzzTarget = FuzzTargetHolder.AUTOFUZZ_FUZZ_TARGET; 125 return FuzzTargetRunner.startLibFuzzer(args); 126 } 127 128 String targetClassName = FuzzTargetFinder.findFuzzTargetClassName(); 129 if (targetClassName == null) { 130 Log.error("Missing argument --target_class=<fuzz_target_class>"); 131 exit(1); 132 } 133 134 // The JUnitRunner calls AgentInstaller.install itself after modifying flags affecting the 135 // agent. 136 if (JUnitRunner.isSupported()) { 137 Optional<JUnitRunner> runner = JUnitRunner.create(targetClassName, args); 138 if (runner.isPresent()) { 139 return runner.get().run(); 140 } 141 } 142 143 // Installing the agent after the following "findFuzzTarget" leads to an asan error 144 // in it on "Class.forName(targetClassName)", but only during native fuzzing. 145 AgentInstaller.install(Opt.hooks); 146 FuzzTargetHolder.fuzzTarget = FuzzTargetFinder.findFuzzTarget(targetClassName); 147 return FuzzTargetRunner.startLibFuzzer(args); 148 } 149 getDefaultRssLimitMbArg()150 private static String getDefaultRssLimitMbArg() { 151 // Java OutOfMemoryErrors are strictly more informative than libFuzzer's out of memory crashes. 152 // We thus want to scale the default libFuzzer memory limit, which includes all memory used by 153 // the process including Jazzer's native and non-native memory footprint, such that: 154 // 1. we never reach it purely by allocating memory on the Java heap; 155 // 2. it is still reached if the fuzz target allocates excessively on the native heap. 156 // As a heuristic, we set the overall memory limit to 2 * the maximum size of the Java heap and 157 // add a fixed 1 GiB on top for the fuzzer's own memory usage. 158 long maxHeapInBytes = Runtime.getRuntime().maxMemory(); 159 return "-rss_limit_mb=" + ((2 * maxHeapInBytes / (1024 * 1024)) + 1024); 160 } 161 } 162