• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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