xref: /aosp_15_r20/external/jazzer-api/launcher/jvm_tooling.cpp (revision 33edd6723662ea34453766bfdca85dbfdd5342b8)
1 // Copyright 2021 Code Intelligence GmbH
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "jvm_tooling.h"
16 
17 #if defined(_ANDROID)
18 #include <dlfcn.h>
19 #elif defined(__APPLE__)
20 #include <mach-o/dyld.h>
21 #elif defined(_WIN32)
22 #define WIN32_LEAN_AND_MEAN
23 #include <windows.h>
24 #else  // Assume Linux
25 #include <unistd.h>
26 #endif
27 
28 #include <cstdlib>
29 #include <fstream>
30 #include <iostream>
31 #include <memory>
32 #include <string>
33 #include <vector>
34 
35 #include "absl/strings/str_format.h"
36 #include "absl/strings/str_join.h"
37 #include "absl/strings/str_replace.h"
38 #include "absl/strings/str_split.h"
39 #include "tools/cpp/runfiles/runfiles.h"
40 
41 std::string FLAGS_cp = ".";
42 std::string FLAGS_jvm_args;
43 std::string FLAGS_additional_jvm_args;
44 std::string FLAGS_agent_path;
45 
46 #if defined(_WIN32) || defined(_WIN64)
47 #define ARG_SEPARATOR ";"
48 constexpr auto kPathSeparator = '\\';
49 #else
50 #define ARG_SEPARATOR ":"
51 constexpr auto kPathSeparator = '/';
52 #endif
53 
54 namespace {
55 constexpr auto kJazzerBazelRunfilesPath =
56     "jazzer/src/main/java/com/code_intelligence/jazzer/"
57     "jazzer_standalone_deploy.jar";
58 constexpr auto kJazzerFileName = "jazzer_standalone.jar";
59 
60 // Returns the absolute path to the current executable. Compared to argv[0],
61 // this path can always be used to locate the Jazzer JAR next to it, even when
62 // Jazzer is executed from PATH.
getExecutablePath()63 std::string getExecutablePath() {
64   char buf[655536];
65 #if defined(__APPLE__)
66   uint32_t buf_size = sizeof(buf);
67   uint32_t read_bytes = buf_size - 1;
68   bool failed = (_NSGetExecutablePath(buf, &buf_size) != 0);
69 #elif defined(_WIN32)
70   DWORD read_bytes = GetModuleFileNameA(NULL, buf, sizeof(buf));
71   bool failed = (read_bytes == 0);
72 #elif defined(_ANDROID)
73   bool failed = true;
74   uint32_t read_bytes = 0;
75 #else  // Assume Linux
76   ssize_t read_bytes = readlink("/proc/self/exe", buf, sizeof(buf));
77   bool failed = (read_bytes == -1);
78 #endif
79   if (failed) {
80     return "";
81   }
82   buf[read_bytes] = '\0';
83   return {buf};
84 }
85 
dirFromFullPath(const std::string & path)86 std::string dirFromFullPath(const std::string &path) {
87   const auto pos = path.rfind(kPathSeparator);
88   if (pos != std::string::npos) {
89     return path.substr(0, pos);
90   }
91   return "";
92 }
93 
94 // getInstrumentorAgentPath searches for the fuzzing instrumentation agent and
95 // returns the location if it is found. Otherwise it calls exit(0).
getInstrumentorAgentPath()96 std::string getInstrumentorAgentPath() {
97   // User provided agent location takes precedence.
98   if (!FLAGS_agent_path.empty()) {
99     if (std::ifstream(FLAGS_agent_path).good()) return FLAGS_agent_path;
100     std::cerr << "ERROR: Could not find " << kJazzerFileName << " at \""
101               << FLAGS_agent_path << "\"" << std::endl;
102     exit(1);
103   }
104 
105   auto executable_path = getExecutablePath();
106 
107   if (!executable_path.empty()) {
108     // First check if we are running inside the Bazel tree and use the agent
109     // runfile.
110     using bazel::tools::cpp::runfiles::Runfiles;
111     std::string error;
112     std::unique_ptr<Runfiles> runfiles(Runfiles::Create(
113         std::string(executable_path), BAZEL_CURRENT_REPOSITORY, &error));
114     if (runfiles != nullptr) {
115       auto bazel_path = runfiles->Rlocation(kJazzerBazelRunfilesPath);
116       if (!bazel_path.empty() && std::ifstream(bazel_path).good())
117         return bazel_path;
118     }
119 
120     // If the agent is not in the bazel path we look next to the jazzer binary.
121     const auto dir = dirFromFullPath(executable_path);
122     auto agent_path =
123         absl::StrFormat("%s%c%s", dir, kPathSeparator, kJazzerFileName);
124     if (std::ifstream(agent_path).good()) return agent_path;
125   }
126 
127   std::cerr << "ERROR: Could not find " << kJazzerFileName
128             << ". Please provide the pathname via the --agent_path flag."
129             << std::endl;
130   exit(1);
131 }
132 
133 // Splits a string at the ARG_SEPARATOR unless it is escaped with a backslash.
134 // Backslash itself can be escaped with another backslash.
splitEscaped(const std::string & str)135 std::vector<std::string> splitEscaped(const std::string &str) {
136   // Protect \\ and \<separator> against splitting.
137   const std::string BACKSLASH_BACKSLASH_REPLACEMENT =
138       "%%JAZZER_BACKSLASH_BACKSLASH_REPLACEMENT%%";
139   const std::string BACKSLASH_SEPARATOR_REPLACEMENT =
140       "%%JAZZER_BACKSLASH_SEPARATOR_REPLACEMENT%%";
141   std::string protected_str =
142       absl::StrReplaceAll(str, {{"\\\\", BACKSLASH_BACKSLASH_REPLACEMENT}});
143   protected_str = absl::StrReplaceAll(
144       protected_str, {{"\\" ARG_SEPARATOR, BACKSLASH_SEPARATOR_REPLACEMENT}});
145 
146   std::vector<std::string> parts = absl::StrSplit(protected_str, ARG_SEPARATOR);
147   std::transform(parts.begin(), parts.end(), parts.begin(),
148                  [&BACKSLASH_SEPARATOR_REPLACEMENT,
149                   &BACKSLASH_BACKSLASH_REPLACEMENT](const std::string &part) {
150                    return absl::StrReplaceAll(
151                        part,
152                        {
153                            {BACKSLASH_SEPARATOR_REPLACEMENT, ARG_SEPARATOR},
154                            {BACKSLASH_BACKSLASH_REPLACEMENT, "\\"},
155                        });
156                  });
157 
158   return parts;
159 }
160 }  // namespace
161 
162 namespace jazzer {
163 
164 #if defined(_ANDROID)
165 typedef jint (*JNI_CreateJavaVM_t)(JavaVM **, JNIEnv **, void *);
LoadAndroidVMLibs()166 JNI_CreateJavaVM_t LoadAndroidVMLibs() {
167   std::cout << "Loading Android libraries" << std::endl;
168 
169   void *art_so = nullptr;
170   art_so = dlopen("libnativehelper.so", RTLD_NOW);
171 
172   if (art_so == nullptr) {
173     std::cerr << "Could not find ART library" << std::endl;
174     exit(1);
175   }
176 
177   typedef void *(*JniInvocationCreate_t)();
178   JniInvocationCreate_t JniInvocationCreate =
179       reinterpret_cast<JniInvocationCreate_t>(
180           dlsym(art_so, "JniInvocationCreate"));
181   if (JniInvocationCreate == nullptr) {
182     std::cout << "JniInvocationCreate is null" << std::endl;
183     exit(1);
184   }
185 
186   void *impl = JniInvocationCreate();
187   typedef bool (*JniInvocationInit_t)(void *, const char *);
188   JniInvocationInit_t JniInvocationInit =
189       reinterpret_cast<JniInvocationInit_t>(dlsym(art_so, "JniInvocationInit"));
190   if (JniInvocationInit == nullptr) {
191     std::cout << "JniInvocationInit is null" << std::endl;
192     exit(1);
193   }
194 
195   JniInvocationInit(impl, nullptr);
196 
197   constexpr char create_jvm_symbol[] = "JNI_CreateJavaVM";
198   typedef jint (*JNI_CreateJavaVM_t)(JavaVM **, JNIEnv **, void *);
199   JNI_CreateJavaVM_t JNI_CreateArtVM =
200       reinterpret_cast<JNI_CreateJavaVM_t>(dlsym(art_so, create_jvm_symbol));
201   if (JNI_CreateArtVM == nullptr) {
202     std::cout << "JNI_CreateJavaVM is null" << std::endl;
203     exit(1);
204   }
205 
206   return JNI_CreateArtVM;
207 }
208 #endif
209 
GetClassPath()210 std::string GetClassPath() {
211   // combine class path from command line flags and JAVA_FUZZER_CLASSPATH env
212   // variable
213   std::string class_path = absl::StrFormat("-Djava.class.path=%s", FLAGS_cp);
214   const auto class_path_from_env = std::getenv("JAVA_FUZZER_CLASSPATH");
215   if (class_path_from_env) {
216     class_path += absl::StrCat(ARG_SEPARATOR, class_path_from_env);
217   }
218 
219   class_path += absl::StrCat(ARG_SEPARATOR, getInstrumentorAgentPath());
220   return class_path;
221 }
222 
JVM()223 JVM::JVM() {
224   std::string class_path = GetClassPath();
225 
226   std::vector<JavaVMOption> options;
227   options.push_back(
228       JavaVMOption{.optionString = const_cast<char *>(class_path.c_str())});
229 
230 #if !defined(_ANDROID)
231   // Set the maximum heap size to a value that is slightly smaller than
232   // libFuzzer's default rss_limit_mb. This prevents erroneous oom reports.
233   options.push_back(JavaVMOption{.optionString = (char *)"-Xmx1800m"});
234   // Preserve and emit stack trace information even on hot paths.
235   // This may hurt performance, but also helps find flaky bugs.
236   options.push_back(
237       JavaVMOption{.optionString = (char *)"-XX:-OmitStackTraceInFastThrow"});
238   // Optimize GC for high throughput rather than low latency.
239   options.push_back(JavaVMOption{.optionString = (char *)"-XX:+UseParallelGC"});
240   // CriticalJNINatives has been removed in JDK 18.
241   options.push_back(
242       JavaVMOption{.optionString = (char *)"-XX:+IgnoreUnrecognizedVMOptions"});
243   options.push_back(
244       JavaVMOption{.optionString = (char *)"-XX:+CriticalJNINatives"});
245 #endif
246 
247   std::vector<std::string> java_opts_args;
248   const char *java_opts = std::getenv("JAVA_OPTS");
249   if (java_opts != nullptr) {
250     // Mimic the behavior of the JVM when it sees JAVA_TOOL_OPTIONS.
251     std::cerr << "Picked up JAVA_OPTS: " << java_opts << std::endl;
252 
253     java_opts_args = absl::StrSplit(java_opts, ' ');
254     for (const std::string &java_opt : java_opts_args) {
255       options.push_back(
256           JavaVMOption{.optionString = const_cast<char *>(java_opt.c_str())});
257     }
258   }
259 
260   // Add additional jvm options set through command line flags.
261   // Keep the vectors in scope as they contain the strings backing the C strings
262   // added to options.
263   std::vector<std::string> jvm_args;
264   if (!FLAGS_jvm_args.empty()) {
265     jvm_args = splitEscaped(FLAGS_jvm_args);
266     for (const auto &arg : jvm_args) {
267       options.push_back(
268           JavaVMOption{.optionString = const_cast<char *>(arg.c_str())});
269     }
270   }
271 
272   std::vector<std::string> additional_jvm_args;
273   if (!FLAGS_additional_jvm_args.empty()) {
274     additional_jvm_args = splitEscaped(FLAGS_additional_jvm_args);
275     for (const auto &arg : additional_jvm_args) {
276       options.push_back(
277           JavaVMOption{.optionString = const_cast<char *>(arg.c_str())});
278     }
279   }
280 
281 #if !defined(_ANDROID)
282   jint jni_version = JNI_VERSION_1_8;
283 #else
284   jint jni_version = JNI_VERSION_1_6;
285 #endif
286 
287   JavaVMInitArgs jvm_init_args = {.version = jni_version,
288                                   .nOptions = (int)options.size(),
289                                   .options = options.data(),
290                                   .ignoreUnrecognized = JNI_FALSE};
291 
292 #if !defined(_ANDROID)
293   int ret = JNI_CreateJavaVM(&jvm_, (void **)&env_, &jvm_init_args);
294 #else
295   JNI_CreateJavaVM_t CreateArtVM = LoadAndroidVMLibs();
296   if (CreateArtVM == nullptr) {
297     std::cerr << "JNI_CreateJavaVM for Android not found" << std::endl;
298     exit(1);
299   }
300 
301   std::cout << "Starting Art VM" << std::endl;
302   int ret = CreateArtVM(&jvm_, (JNIEnv_ **)&env_, &jvm_init_args);
303 #endif
304 
305   if (ret != JNI_OK) {
306     throw std::runtime_error(
307         absl::StrFormat("JNI_CreateJavaVM returned code %d", ret));
308   }
309 }
310 
GetEnv() const311 JNIEnv &JVM::GetEnv() const { return *env_; }
312 
~JVM()313 JVM::~JVM() { jvm_->DestroyJavaVM(); }
314 }  // namespace jazzer
315