aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
Diffstat (limited to 'launcher')
-rw-r--r--launcher/BUILD.bazel99
-rw-r--r--launcher/android/AndroidManifest.xml21
-rw-r--r--launcher/android/BUILD.bazel32
-rw-r--r--launcher/fuzzed_data_provider_test.cpp101
-rw-r--r--launcher/jazzer_main.cpp109
-rw-r--r--launcher/jvm_tooling.cpp314
-rw-r--r--launcher/jvm_tooling.h51
-rw-r--r--launcher/jvm_tooling_test.cpp79
-rw-r--r--launcher/test_main.cpp23
-rw-r--r--launcher/testdata/BUILD.bazel6
-rw-r--r--launcher/testdata/test/ModifiedUtf8Encoder.java41
-rw-r--r--launcher/testdata/test/PropertyPrinter.java22
12 files changed, 898 insertions, 0 deletions
diff --git a/launcher/BUILD.bazel b/launcher/BUILD.bazel
new file mode 100644
index 00000000..50bd4772
--- /dev/null
+++ b/launcher/BUILD.bazel
@@ -0,0 +1,99 @@
+load("@build_bazel_apple_support//rules:universal_binary.bzl", "universal_binary")
+
+cc_library(
+ name = "jazzer_main",
+ srcs = ["jazzer_main.cpp"],
+ deps = [
+ ":jvm_tooling_lib",
+ "@com_google_absl//absl/strings",
+ "@fmeum_rules_jni//jni:libjvm",
+ ],
+)
+
+cc_library(
+ name = "jvm_tooling_lib",
+ srcs = ["jvm_tooling.cpp"],
+ hdrs = ["jvm_tooling.h"],
+ data = [
+ "//src/main/java/com/code_intelligence/jazzer:jazzer_standalone_deploy.jar",
+ ],
+ linkopts = select({
+ "@platforms//os:android": ["-ldl"],
+ "//conditions:default": [],
+ }),
+ deps = [
+ "@bazel_tools//tools/cpp/runfiles",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/strings:str_format",
+ "@fmeum_rules_jni//jni",
+ ],
+)
+
+cc_binary(
+ name = "jazzer_single_arch",
+ linkstatic = True,
+ tags = ["manual"],
+ visibility = ["//launcher/android:__pkg__"],
+ deps = [":jazzer_main"],
+)
+
+# On macOS, builds a binary that supports both x86_64 and arm64.
+# On all other platforms, it just symlinks the input binary.
+universal_binary(
+ name = "jazzer",
+ binary = ":jazzer_single_arch",
+ visibility = ["//visibility:public"],
+)
+
+cc_test(
+ name = "jvm_tooling_test",
+ size = "small",
+ srcs = ["jvm_tooling_test.cpp"],
+ data = [
+ "//launcher/testdata:fuzz_target_mocks_deploy.jar",
+ ],
+ env = {
+ "JAVA_OPTS": "-Djazzer.hooks=false",
+ },
+ includes = ["."],
+ deps = [
+ ":jvm_tooling_lib",
+ ":test_main",
+ "@bazel_tools//tools/cpp/runfiles",
+ "@googletest//:gtest",
+ ],
+)
+
+cc_test(
+ name = "fuzzed_data_provider_test",
+ size = "medium",
+ srcs = ["fuzzed_data_provider_test.cpp"],
+ copts = select({
+ "@platforms//os:windows": ["/std:c++17"],
+ "//conditions:default": ["-std=c++17"],
+ }),
+ data = [
+ "//launcher/testdata:fuzz_target_mocks_deploy.jar",
+ ],
+ env = {
+ "JAVA_OPTS": "-Djazzer.hooks=false",
+ },
+ includes = ["."],
+ deps = [
+ ":jvm_tooling_lib",
+ ":test_main",
+ "//src/main/native/com/code_intelligence/jazzer/driver:fuzzed_data_provider",
+ "@bazel_tools//tools/cpp/runfiles",
+ "@googletest//:gtest",
+ ],
+)
+
+cc_library(
+ name = "test_main",
+ srcs = ["test_main.cpp"],
+ linkstatic = True,
+ deps = [
+ "@fmeum_rules_jni//jni:libjvm",
+ "@googletest//:gtest",
+ ],
+)
diff --git a/launcher/android/AndroidManifest.xml b/launcher/android/AndroidManifest.xml
new file mode 100644
index 00000000..7a3c2a27
--- /dev/null
+++ b/launcher/android/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright 2023 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.code_intelligence.jazzer"
+ android:versionCode="1"
+ android:versionName="1.0" >
+</manifest>
diff --git a/launcher/android/BUILD.bazel b/launcher/android/BUILD.bazel
new file mode 100644
index 00000000..502f6124
--- /dev/null
+++ b/launcher/android/BUILD.bazel
@@ -0,0 +1,32 @@
+load("//bazel:compat.bzl", "SKIP_ON_WINDOWS")
+
+android_library(
+ name = "jazzer_android_lib",
+ data = [
+ "//launcher:jazzer_single_arch",
+ "//src/main/java/com/code_intelligence/jazzer/android:jazzer_standalone_android.apk",
+ ],
+ tags = ["manual"],
+ target_compatible_with = SKIP_ON_WINDOWS,
+)
+
+android_binary(
+ name = "jazzer_android",
+ manifest = ":android_manifest",
+ min_sdk_version = 26,
+ tags = ["manual"],
+ target_compatible_with = SKIP_ON_WINDOWS,
+ visibility = ["//visibility:public"],
+ deps = [
+ ":jazzer_android_lib",
+ ],
+)
+
+filegroup(
+ name = "android_manifest",
+ srcs = ["AndroidManifest.xml"],
+ tags = ["manual"],
+ visibility = [
+ "//visibility:public",
+ ],
+)
diff --git a/launcher/fuzzed_data_provider_test.cpp b/launcher/fuzzed_data_provider_test.cpp
new file mode 100644
index 00000000..9907b75e
--- /dev/null
+++ b/launcher/fuzzed_data_provider_test.cpp
@@ -0,0 +1,101 @@
+// 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 <cstddef>
+#include <cstdint>
+#include <random>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "launcher/jvm_tooling.h"
+#include "tools/cpp/runfiles/runfiles.h"
+
+namespace jazzer {
+
+std::pair<std::string, jint> FixUpModifiedUtf8(const uint8_t* pos,
+ jint max_bytes, jint max_length,
+ bool ascii_only,
+ bool stop_on_backslash);
+
+class FuzzedDataProviderTest : public ::testing::Test {
+ protected:
+ // After DestroyJavaVM() no new JVM instance can be created in the same
+ // process, so we set up a single JVM instance for this test binary which gets
+ // destroyed after all tests in this test suite have finished.
+ static void SetUpTestCase() {
+ using ::bazel::tools::cpp::runfiles::Runfiles;
+ std::unique_ptr<Runfiles> runfiles(Runfiles::CreateForTest());
+ FLAGS_cp = runfiles->Rlocation(
+ "jazzer/launcher/testdata/fuzz_target_mocks_deploy.jar");
+
+ jvm_ = std::make_unique<JVM>();
+ }
+
+ static void TearDownTestCase() { jvm_.reset(nullptr); }
+
+ static std::unique_ptr<JVM> jvm_;
+};
+
+std::unique_ptr<JVM> FuzzedDataProviderTest::jvm_ = nullptr;
+
+constexpr std::size_t kValidModifiedUtf8NumRuns = 1000;
+constexpr std::size_t kValidModifiedUtf8NumBytes = 100000;
+constexpr uint32_t kValidModifiedUtf8Seed = 0x12345678;
+
+TEST_F(FuzzedDataProviderTest, InvalidModifiedUtf8AfterFixup) {
+ auto& env = jvm_->GetEnv();
+ auto modified_utf8_validator = env.FindClass("test/ModifiedUtf8Encoder");
+ ASSERT_NE(nullptr, modified_utf8_validator);
+ auto string_to_modified_utf_bytes = env.GetStaticMethodID(
+ modified_utf8_validator, "encode", "(Ljava/lang/String;)[B");
+ ASSERT_NE(nullptr, string_to_modified_utf_bytes);
+ auto random_bytes = std::vector<uint8_t>(kValidModifiedUtf8NumBytes);
+ auto random = std::mt19937(kValidModifiedUtf8Seed);
+ for (bool ascii_only : {false, true}) {
+ for (bool stop_on_backslash : {false, true}) {
+ for (std::size_t i = 0; i < kValidModifiedUtf8NumRuns; ++i) {
+ std::generate(random_bytes.begin(), random_bytes.end(), random);
+ std::string fixed_string;
+ std::tie(fixed_string, std::ignore) = FixUpModifiedUtf8(
+ random_bytes.data(), random_bytes.size(),
+ std::numeric_limits<jint>::max(), ascii_only, stop_on_backslash);
+
+ jstring jni_fixed_string = env.NewStringUTF(fixed_string.c_str());
+ auto jni_roundtripped_bytes = (jbyteArray)env.CallStaticObjectMethod(
+ modified_utf8_validator, string_to_modified_utf_bytes,
+ jni_fixed_string);
+ ASSERT_FALSE(env.ExceptionCheck());
+ env.DeleteLocalRef(jni_fixed_string);
+ jint roundtripped_bytes_length =
+ env.GetArrayLength(jni_roundtripped_bytes);
+ jbyte* roundtripped_bytes =
+ env.GetByteArrayElements(jni_roundtripped_bytes, nullptr);
+ auto roundtripped_string =
+ std::string(reinterpret_cast<char*>(roundtripped_bytes),
+ roundtripped_bytes_length);
+ env.ReleaseByteArrayElements(jni_roundtripped_bytes, roundtripped_bytes,
+ JNI_ABORT);
+ env.DeleteLocalRef(jni_roundtripped_bytes);
+
+ // Verify that the bytes obtained from running our modified UTF-8 fix-up
+ // function remain unchanged when turned into a Java string and
+ // reencoded into modified UTF-8. This will only happen if the our
+ // fix-up function indeed returned valid modified UTF-8.
+ ASSERT_EQ(fixed_string, roundtripped_string);
+ }
+ }
+ }
+}
+} // namespace jazzer
diff --git a/launcher/jazzer_main.cpp b/launcher/jazzer_main.cpp
new file mode 100644
index 00000000..d2c6e294
--- /dev/null
+++ b/launcher/jazzer_main.cpp
@@ -0,0 +1,109 @@
+// 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.
+
+/*
+ * Jazzer's native main function, which starts a JVM suitably configured for
+ * fuzzing and passes control to the Java part of the driver.
+ */
+
+#include <rules_jni.h>
+
+#include <algorithm>
+#include <iostream>
+#include <memory>
+#include <vector>
+
+#include "absl/strings/str_split.h"
+#include "jvm_tooling.h"
+
+namespace {
+const std::string kJazzerClassName = "com/code_intelligence/jazzer/Jazzer";
+
+void StartLibFuzzer(std::unique_ptr<jazzer::JVM> jvm,
+ std::vector<std::string> argv) {
+ JNIEnv &env = jvm->GetEnv();
+ jclass runner = env.FindClass(kJazzerClassName.c_str());
+ if (runner == nullptr) {
+ env.ExceptionDescribe();
+ exit(1);
+ }
+ jmethodID startDriver = env.GetStaticMethodID(runner, "main", "([[B)V");
+ if (startDriver == nullptr) {
+ env.ExceptionDescribe();
+ exit(1);
+ }
+ jclass byteArrayClass = env.FindClass("[B");
+ if (byteArrayClass == nullptr) {
+ env.ExceptionDescribe();
+ exit(1);
+ }
+ jobjectArray args = env.NewObjectArray(argv.size(), byteArrayClass, nullptr);
+ if (args == nullptr) {
+ env.ExceptionDescribe();
+ exit(1);
+ }
+ for (jsize i = 0; i < argv.size(); ++i) {
+ jint len = argv[i].size();
+ jbyteArray arg = env.NewByteArray(len);
+ if (arg == nullptr) {
+ env.ExceptionDescribe();
+ exit(1);
+ }
+ // startDriver expects UTF-8 encoded strings that are not null-terminated.
+ env.SetByteArrayRegion(arg, 0, len,
+ reinterpret_cast<const jbyte *>(argv[i].data()));
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ exit(1);
+ }
+ env.SetObjectArrayElement(args, i, arg);
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ exit(1);
+ }
+ env.DeleteLocalRef(arg);
+ }
+ env.CallStaticVoidMethod(runner, startDriver, args);
+ // Should not return.
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ }
+ exit(1);
+}
+} // namespace
+
+int main(int argc, char **argv) {
+ rules_jni_init(argv[0]);
+
+ for (int i = 1; i < argc; ++i) {
+ const std::string &arg = argv[i];
+ std::vector<std::string> split =
+ absl::StrSplit(arg, absl::MaxSplits('=', 1));
+ if (split.size() < 2) {
+ continue;
+ }
+ if (split[0] == "--cp") {
+ FLAGS_cp = split[1];
+ } else if (split[0] == "--jvm_args") {
+ FLAGS_jvm_args = split[1];
+ } else if (split[0] == "--additional_jvm_args") {
+ FLAGS_additional_jvm_args = split[1];
+ } else if (split[0] == "--agent_path") {
+ FLAGS_agent_path = split[1];
+ }
+ }
+
+ StartLibFuzzer(std::unique_ptr<jazzer::JVM>(new jazzer::JVM()),
+ std::vector<std::string>(argv + 1, argv + argc));
+}
diff --git a/launcher/jvm_tooling.cpp b/launcher/jvm_tooling.cpp
new file mode 100644
index 00000000..3b731a83
--- /dev/null
+++ b/launcher/jvm_tooling.cpp
@@ -0,0 +1,314 @@
+// 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 <dlfcn.h>
+#elif defined(__APPLE__)
+#include <mach-o/dyld.h>
+#elif defined(_WIN32)
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#else // Assume Linux
+#include <unistd.h>
+#endif
+
+#include <cstdlib>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#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(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<std::string> splitEscaped(const std::string &str) {
+ // Protect \\ and \<separator> 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<std::string> 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<JniInvocationCreate_t>(
+ 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<JniInvocationInit_t>(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<JNI_CreateJavaVM_t>(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<JavaVMOption> options;
+ options.push_back(
+ JavaVMOption{.optionString = const_cast<char *>(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<std::string> 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<char *>(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<std::string> 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<char *>(arg.c_str())});
+ }
+ }
+
+ std::vector<std::string> 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<char *>(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
diff --git a/launcher/jvm_tooling.h b/launcher/jvm_tooling.h
new file mode 100644
index 00000000..d7129a13
--- /dev/null
+++ b/launcher/jvm_tooling.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <jni.h>
+
+#include <string>
+
+extern std::string FLAGS_cp;
+extern std::string FLAGS_jvm_args;
+extern std::string FLAGS_additional_jvm_args;
+extern std::string FLAGS_agent_path;
+
+namespace jazzer {
+
+void DumpJvmStackTraces();
+
+// JVM is a thin wrapper around JNI_CreateJavaVM and DestroyJavaVM. The JVM
+// instance is created inside the constructor with some default JNI options
+// + options which can be added to via command line flags.
+class JVM {
+ private:
+ JavaVM *jvm_;
+ JNIEnv *env_;
+
+ public:
+ // Creates a JVM instance with default options + options that were provided as
+ // command line flags.
+ explicit JVM();
+
+ // Destroy the running JVM instance.
+ ~JVM();
+
+ // Get the JNI environment for interaction with the running JVM instance.
+ JNIEnv &GetEnv() const;
+};
+} /* namespace jazzer */
diff --git a/launcher/jvm_tooling_test.cpp b/launcher/jvm_tooling_test.cpp
new file mode 100644
index 00000000..2a70dcb9
--- /dev/null
+++ b/launcher/jvm_tooling_test.cpp
@@ -0,0 +1,79 @@
+// 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"
+
+#include <memory>
+
+#include "gtest/gtest.h"
+#include "tools/cpp/runfiles/runfiles.h"
+
+#ifdef _WIN32
+#define ARG_SEPARATOR ";"
+#else
+#define ARG_SEPARATOR ":"
+#endif
+
+namespace jazzer {
+
+class JvmToolingTest : public ::testing::Test {
+ protected:
+ // After DestroyJavaVM() no new JVM instance can be created in the same
+ // process, so we set up a single JVM instance for this test binary which gets
+ // destroyed after all tests in this test suite have finished.
+ static void SetUpTestCase() {
+ FLAGS_jvm_args =
+ "-Denv1=va\\" ARG_SEPARATOR "l1\\\\" ARG_SEPARATOR "-Denv2=val2";
+ using ::bazel::tools::cpp::runfiles::Runfiles;
+ std::unique_ptr<Runfiles> runfiles(Runfiles::CreateForTest());
+ FLAGS_cp = runfiles->Rlocation(
+ "jazzer/launcher/testdata/fuzz_target_mocks_deploy.jar");
+
+ jvm_ = std::unique_ptr<JVM>(new JVM());
+ }
+
+ static void TearDownTestCase() { jvm_.reset(nullptr); }
+
+ static std::unique_ptr<JVM> jvm_;
+};
+
+std::unique_ptr<JVM> JvmToolingTest::jvm_ = nullptr;
+
+TEST_F(JvmToolingTest, JniProperties) {
+ auto &env = jvm_->GetEnv();
+ auto property_printer_class = env.FindClass("test/PropertyPrinter");
+ ASSERT_NE(nullptr, property_printer_class);
+ auto method_id =
+ env.GetStaticMethodID(property_printer_class, "printProperty",
+ "(Ljava/lang/String;)Ljava/lang/String;");
+ ASSERT_NE(nullptr, method_id);
+
+ for (const auto &el : std::vector<std::pair<std::string, std::string>>{
+ {"not set property", ""},
+ {"env1", "va" ARG_SEPARATOR "l1\\"},
+ {"env2", "val2"}}) {
+ jstring str = env.NewStringUTF(el.first.c_str());
+ auto ret = (jstring)env.CallStaticObjectMethod(property_printer_class,
+ method_id, str);
+ ASSERT_FALSE(env.ExceptionCheck());
+ if (el.second.empty()) {
+ ASSERT_EQ(nullptr, ret);
+ } else {
+ ASSERT_NE(nullptr, ret);
+ jboolean is_copy;
+ ASSERT_EQ(el.second, env.GetStringUTFChars(ret, &is_copy));
+ }
+ }
+}
+} // namespace jazzer
diff --git a/launcher/test_main.cpp b/launcher/test_main.cpp
new file mode 100644
index 00000000..cd7c8e81
--- /dev/null
+++ b/launcher/test_main.cpp
@@ -0,0 +1,23 @@
+// 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 <rules_jni.h>
+
+#include "gtest/gtest.h"
+
+int main(int argc, char **argv) {
+ rules_jni_init(argv[0]);
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/launcher/testdata/BUILD.bazel b/launcher/testdata/BUILD.bazel
new file mode 100644
index 00000000..c3c24431
--- /dev/null
+++ b/launcher/testdata/BUILD.bazel
@@ -0,0 +1,6 @@
+java_binary(
+ name = "fuzz_target_mocks",
+ srcs = glob(["test/*.java"]),
+ create_executable = False,
+ visibility = ["//visibility:public"],
+)
diff --git a/launcher/testdata/test/ModifiedUtf8Encoder.java b/launcher/testdata/test/ModifiedUtf8Encoder.java
new file mode 100644
index 00000000..b460c81c
--- /dev/null
+++ b/launcher/testdata/test/ModifiedUtf8Encoder.java
@@ -0,0 +1,41 @@
+// 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.
+
+package test;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+
+final class ModifiedUtf8Encoder {
+ // Encodes a string in the JVM's modified UTF-8 encoding.
+ static public byte[] encode(String value) {
+ // Modified UTF-8 is almost the same as CESU-8, the only difference being that the zero
+ // character is coded on two bytes.
+ byte[] cesuBytes = value.getBytes(Charset.forName("CESU-8"));
+ ArrayList<Byte> modifiedUtf8Bytes = new ArrayList<>();
+ for (byte cesuByte : cesuBytes) {
+ if (cesuByte != 0) {
+ modifiedUtf8Bytes.add(cesuByte);
+ } else {
+ modifiedUtf8Bytes.add((byte) 0xC0);
+ modifiedUtf8Bytes.add((byte) 0x80);
+ }
+ }
+ byte[] out = new byte[modifiedUtf8Bytes.size()];
+ for (int i = 0; i < modifiedUtf8Bytes.size(); i++) {
+ out[i] = modifiedUtf8Bytes.get(i);
+ }
+ return out;
+ }
+}
diff --git a/launcher/testdata/test/PropertyPrinter.java b/launcher/testdata/test/PropertyPrinter.java
new file mode 100644
index 00000000..97345acd
--- /dev/null
+++ b/launcher/testdata/test/PropertyPrinter.java
@@ -0,0 +1,22 @@
+// 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.
+
+package test;
+
+// Class used for testing
+class PropertyPrinter {
+ public static String printProperty(String property) {
+ return System.getProperty(property);
+ }
+}