// Copyright 2021 Code Intelligence GmbH // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "jvm_tooling.h" #if defined(_ANDROID) #include #elif defined(__APPLE__) #include #elif defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include #else // Assume Linux #include #endif #include #include #include #include #include #include #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" #include "tools/cpp/runfiles/runfiles.h" std::string FLAGS_cp = "."; std::string FLAGS_jvm_args; std::string FLAGS_additional_jvm_args; std::string FLAGS_agent_path; #if defined(_WIN32) || defined(_WIN64) #define ARG_SEPARATOR ";" constexpr auto kPathSeparator = '\\'; #else #define ARG_SEPARATOR ":" constexpr auto kPathSeparator = '/'; #endif namespace { constexpr auto kJazzerBazelRunfilesPath = "jazzer/src/main/java/com/code_intelligence/jazzer/" "jazzer_standalone_deploy.jar"; constexpr auto kJazzerFileName = "jazzer_standalone.jar"; // Returns the absolute path to the current executable. Compared to argv[0], // this path can always be used to locate the Jazzer JAR next to it, even when // Jazzer is executed from PATH. std::string getExecutablePath() { char buf[655536]; #if defined(__APPLE__) uint32_t buf_size = sizeof(buf); uint32_t read_bytes = buf_size - 1; bool failed = (_NSGetExecutablePath(buf, &buf_size) != 0); #elif defined(_WIN32) DWORD read_bytes = GetModuleFileNameA(NULL, buf, sizeof(buf)); bool failed = (read_bytes == 0); #elif defined(_ANDROID) bool failed = true; uint32_t read_bytes = 0; #else // Assume Linux ssize_t read_bytes = readlink("/proc/self/exe", buf, sizeof(buf)); bool failed = (read_bytes == -1); #endif if (failed) { return ""; } buf[read_bytes] = '\0'; return {buf}; } std::string dirFromFullPath(const std::string &path) { const auto pos = path.rfind(kPathSeparator); if (pos != std::string::npos) { return path.substr(0, pos); } return ""; } // getInstrumentorAgentPath searches for the fuzzing instrumentation agent and // returns the location if it is found. Otherwise it calls exit(0). std::string getInstrumentorAgentPath() { // User provided agent location takes precedence. if (!FLAGS_agent_path.empty()) { if (std::ifstream(FLAGS_agent_path).good()) return FLAGS_agent_path; std::cerr << "ERROR: Could not find " << kJazzerFileName << " at \"" << FLAGS_agent_path << "\"" << std::endl; exit(1); } auto executable_path = getExecutablePath(); if (!executable_path.empty()) { // First check if we are running inside the Bazel tree and use the agent // runfile. using bazel::tools::cpp::runfiles::Runfiles; std::string error; std::unique_ptr runfiles(Runfiles::Create( std::string(executable_path), BAZEL_CURRENT_REPOSITORY, &error)); if (runfiles != nullptr) { auto bazel_path = runfiles->Rlocation(kJazzerBazelRunfilesPath); if (!bazel_path.empty() && std::ifstream(bazel_path).good()) return bazel_path; } // If the agent is not in the bazel path we look next to the jazzer binary. const auto dir = dirFromFullPath(executable_path); auto agent_path = absl::StrFormat("%s%c%s", dir, kPathSeparator, kJazzerFileName); if (std::ifstream(agent_path).good()) return agent_path; } std::cerr << "ERROR: Could not find " << kJazzerFileName << ". Please provide the pathname via the --agent_path flag." << std::endl; exit(1); } // Splits a string at the ARG_SEPARATOR unless it is escaped with a backslash. // Backslash itself can be escaped with another backslash. std::vector splitEscaped(const std::string &str) { // Protect \\ and \ against splitting. const std::string BACKSLASH_BACKSLASH_REPLACEMENT = "%%JAZZER_BACKSLASH_BACKSLASH_REPLACEMENT%%"; const std::string BACKSLASH_SEPARATOR_REPLACEMENT = "%%JAZZER_BACKSLASH_SEPARATOR_REPLACEMENT%%"; std::string protected_str = absl::StrReplaceAll(str, {{"\\\\", BACKSLASH_BACKSLASH_REPLACEMENT}}); protected_str = absl::StrReplaceAll( protected_str, {{"\\" ARG_SEPARATOR, BACKSLASH_SEPARATOR_REPLACEMENT}}); std::vector parts = absl::StrSplit(protected_str, ARG_SEPARATOR); std::transform(parts.begin(), parts.end(), parts.begin(), [&BACKSLASH_SEPARATOR_REPLACEMENT, &BACKSLASH_BACKSLASH_REPLACEMENT](const std::string &part) { return absl::StrReplaceAll( part, { {BACKSLASH_SEPARATOR_REPLACEMENT, ARG_SEPARATOR}, {BACKSLASH_BACKSLASH_REPLACEMENT, "\\"}, }); }); return parts; } } // namespace namespace jazzer { #if defined(_ANDROID) typedef jint (*JNI_CreateJavaVM_t)(JavaVM **, JNIEnv **, void *); JNI_CreateJavaVM_t LoadAndroidVMLibs() { std::cout << "Loading Android libraries" << std::endl; void *art_so = nullptr; art_so = dlopen("libnativehelper.so", RTLD_NOW); if (art_so == nullptr) { std::cerr << "Could not find ART library" << std::endl; exit(1); } typedef void *(*JniInvocationCreate_t)(); JniInvocationCreate_t JniInvocationCreate = reinterpret_cast( dlsym(art_so, "JniInvocationCreate")); if (JniInvocationCreate == nullptr) { std::cout << "JniInvocationCreate is null" << std::endl; exit(1); } void *impl = JniInvocationCreate(); typedef bool (*JniInvocationInit_t)(void *, const char *); JniInvocationInit_t JniInvocationInit = reinterpret_cast(dlsym(art_so, "JniInvocationInit")); if (JniInvocationInit == nullptr) { std::cout << "JniInvocationInit is null" << std::endl; exit(1); } JniInvocationInit(impl, nullptr); constexpr char create_jvm_symbol[] = "JNI_CreateJavaVM"; typedef jint (*JNI_CreateJavaVM_t)(JavaVM **, JNIEnv **, void *); JNI_CreateJavaVM_t JNI_CreateArtVM = reinterpret_cast(dlsym(art_so, create_jvm_symbol)); if (JNI_CreateArtVM == nullptr) { std::cout << "JNI_CreateJavaVM is null" << std::endl; exit(1); } return JNI_CreateArtVM; } #endif std::string GetClassPath() { // combine class path from command line flags and JAVA_FUZZER_CLASSPATH env // variable std::string class_path = absl::StrFormat("-Djava.class.path=%s", FLAGS_cp); const auto class_path_from_env = std::getenv("JAVA_FUZZER_CLASSPATH"); if (class_path_from_env) { class_path += absl::StrCat(ARG_SEPARATOR, class_path_from_env); } class_path += absl::StrCat(ARG_SEPARATOR, getInstrumentorAgentPath()); return class_path; } JVM::JVM() { std::string class_path = GetClassPath(); std::vector options; options.push_back( JavaVMOption{.optionString = const_cast(class_path.c_str())}); #if !defined(_ANDROID) // Set the maximum heap size to a value that is slightly smaller than // libFuzzer's default rss_limit_mb. This prevents erroneous oom reports. options.push_back(JavaVMOption{.optionString = (char *)"-Xmx1800m"}); // Preserve and emit stack trace information even on hot paths. // This may hurt performance, but also helps find flaky bugs. options.push_back( JavaVMOption{.optionString = (char *)"-XX:-OmitStackTraceInFastThrow"}); // Optimize GC for high throughput rather than low latency. options.push_back(JavaVMOption{.optionString = (char *)"-XX:+UseParallelGC"}); // CriticalJNINatives has been removed in JDK 18. options.push_back( JavaVMOption{.optionString = (char *)"-XX:+IgnoreUnrecognizedVMOptions"}); options.push_back( JavaVMOption{.optionString = (char *)"-XX:+CriticalJNINatives"}); #endif std::vector java_opts_args; const char *java_opts = std::getenv("JAVA_OPTS"); if (java_opts != nullptr) { // Mimic the behavior of the JVM when it sees JAVA_TOOL_OPTIONS. std::cerr << "Picked up JAVA_OPTS: " << java_opts << std::endl; java_opts_args = absl::StrSplit(java_opts, ' '); for (const std::string &java_opt : java_opts_args) { options.push_back( JavaVMOption{.optionString = const_cast(java_opt.c_str())}); } } // Add additional jvm options set through command line flags. // Keep the vectors in scope as they contain the strings backing the C strings // added to options. std::vector jvm_args; if (!FLAGS_jvm_args.empty()) { jvm_args = splitEscaped(FLAGS_jvm_args); for (const auto &arg : jvm_args) { options.push_back( JavaVMOption{.optionString = const_cast(arg.c_str())}); } } std::vector additional_jvm_args; if (!FLAGS_additional_jvm_args.empty()) { additional_jvm_args = splitEscaped(FLAGS_additional_jvm_args); for (const auto &arg : additional_jvm_args) { options.push_back( JavaVMOption{.optionString = const_cast(arg.c_str())}); } } #if !defined(_ANDROID) jint jni_version = JNI_VERSION_1_8; #else jint jni_version = JNI_VERSION_1_6; #endif JavaVMInitArgs jvm_init_args = {.version = jni_version, .nOptions = (int)options.size(), .options = options.data(), .ignoreUnrecognized = JNI_FALSE}; #if !defined(_ANDROID) int ret = JNI_CreateJavaVM(&jvm_, (void **)&env_, &jvm_init_args); #else JNI_CreateJavaVM_t CreateArtVM = LoadAndroidVMLibs(); if (CreateArtVM == nullptr) { std::cerr << "JNI_CreateJavaVM for Android not found" << std::endl; exit(1); } std::cout << "Starting Art VM" << std::endl; int ret = CreateArtVM(&jvm_, (JNIEnv_ **)&env_, &jvm_init_args); #endif if (ret != JNI_OK) { throw std::runtime_error( absl::StrFormat("JNI_CreateJavaVM returned code %d", ret)); } } JNIEnv &JVM::GetEnv() const { return *env_; } JVM::~JVM() { jvm_->DestroyJavaVM(); } } // namespace jazzer