xref: /aosp_15_r20/external/jazzer-api/src/main/java/com/code_intelligence/jazzer/driver/Driver.java (revision 33edd6723662ea34453766bfdca85dbfdd5342b8)
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