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 21 import com.code_intelligence.jazzer.agent.Agent; 22 import java.io.IOException; 23 import java.nio.file.Files; 24 import java.nio.file.Path; 25 import java.nio.file.Paths; 26 import java.security.SecureRandom; 27 import java.util.List; 28 import net.bytebuddy.agent.ByteBuddyAgent; 29 30 public class Driver { 31 // Accessed from jazzer_main.cpp. 32 @SuppressWarnings("unused") start(byte[][] nativeArgs)33 private static int start(byte[][] nativeArgs) throws IOException { 34 List<String> args = Utils.fromNativeArgs(nativeArgs); 35 36 final boolean spawnsSubprocesses = args.stream().anyMatch( 37 arg -> arg.startsWith("-fork=") || arg.startsWith("-jobs=") || arg.startsWith("-merge=")); 38 if (spawnsSubprocesses) { 39 if (!System.getProperty("jazzer.coverage_report", "").isEmpty()) { 40 err.println( 41 "WARN: --coverage_report does not support parallel fuzzing and has been disabled"); 42 System.clearProperty("jazzer.coverage_report"); 43 } 44 if (!System.getProperty("jazzer.coverage_dump", "").isEmpty()) { 45 err.println( 46 "WARN: --coverage_dump does not support parallel fuzzing and has been disabled"); 47 System.clearProperty("jazzer.coverage_dump"); 48 } 49 50 String idSyncFileArg = System.getProperty("jazzer.id_sync_file", ""); 51 Path idSyncFile; 52 if (idSyncFileArg.isEmpty()) { 53 // Create an empty temporary file used for coverage ID synchronization and 54 // pass its path to the agent in every child process. This requires adding 55 // the argument to argv for it to be picked up by libFuzzer, which then 56 // forwards it to child processes. 57 idSyncFile = Files.createTempFile("jazzer-", ""); 58 args.add("--id_sync_file=" + idSyncFile.toAbsolutePath()); 59 } else { 60 // Creates the file, truncating it if it exists. 61 idSyncFile = Files.write(Paths.get(idSyncFileArg), new byte[] {}); 62 } 63 // This wouldn't run in case we exit the process with _Exit, but the parent process of a -fork 64 // run is expected to exit with a regular exit(0), which does cause JVM shutdown hooks to run: 65 // https://github.com/llvm/llvm-project/blob/940e178c0018b32af2f1478d331fc41a92a7dac7/compiler-rt/lib/fuzzer/FuzzerFork.cpp#L491 66 idSyncFile.toFile().deleteOnExit(); 67 } 68 69 // Jazzer's hooks use deterministic randomness and thus require a seed. Search for the last 70 // occurrence of a "-seed" argument as that is the one that is used by libFuzzer. If none is 71 // set, generate one and pass it to libFuzzer so that a fuzzing run can be reproduced simply by 72 // setting the seed printed by libFuzzer. 73 String seed = args.stream().reduce( 74 null, (prev, cur) -> cur.startsWith("-seed=") ? cur.substring("-seed=".length()) : prev); 75 if (seed == null) { 76 seed = Integer.toUnsignedString(new SecureRandom().nextInt()); 77 // Only add the -seed argument to the command line if not running in a mode 78 // that spawns subprocesses. These would inherit the same seed, which might 79 // make them less effective. 80 if (!spawnsSubprocesses) { 81 args.add("-seed=" + seed); 82 } 83 } 84 System.setProperty("jazzer.seed", seed); 85 86 if (args.stream().noneMatch(arg -> arg.startsWith("-rss_limit_mb="))) { 87 args.add(getDefaultRssLimitMbArg()); 88 } 89 90 // Do *not* modify system properties beyond this point - initializing Opt parses them as a side 91 // effect. 92 93 if (Opt.hooks) { 94 Agent.premain(null, ByteBuddyAgent.install()); 95 } 96 97 return FuzzTargetRunner.startLibFuzzer(args); 98 } 99 getDefaultRssLimitMbArg()100 private static String getDefaultRssLimitMbArg() { 101 // Java OutOfMemoryErrors are strictly more informative than libFuzzer's out of memory crashes. 102 // We thus want to scale the default libFuzzer memory limit, which includes all memory used by 103 // the process including Jazzer's native and non-native memory footprint, such that: 104 // 1. we never reach it purely by allocating memory on the Java heap; 105 // 2. it is still reached if the fuzz target allocates excessively on the native heap. 106 // As a heuristic, we set the overall memory limit to 2 * the maximum size of the Java heap and 107 // add a fixed 1 GiB on top for the fuzzer's own memory usage. 108 long maxHeapInBytes = Runtime.getRuntime().maxMemory(); 109 return "-rss_limit_mb=" + ((2 * maxHeapInBytes / (1024 * 1024)) + 1024); 110 } 111 } 112