diff options
Diffstat (limited to 'launcher')
-rw-r--r-- | launcher/BUILD.bazel | 99 | ||||
-rw-r--r-- | launcher/android/AndroidManifest.xml | 21 | ||||
-rw-r--r-- | launcher/android/BUILD.bazel | 32 | ||||
-rw-r--r-- | launcher/fuzzed_data_provider_test.cpp | 101 | ||||
-rw-r--r-- | launcher/jazzer_main.cpp | 109 | ||||
-rw-r--r-- | launcher/jvm_tooling.cpp | 314 | ||||
-rw-r--r-- | launcher/jvm_tooling.h | 51 | ||||
-rw-r--r-- | launcher/jvm_tooling_test.cpp | 79 | ||||
-rw-r--r-- | launcher/test_main.cpp | 23 | ||||
-rw-r--r-- | launcher/testdata/BUILD.bazel | 6 | ||||
-rw-r--r-- | launcher/testdata/test/ModifiedUtf8Encoder.java | 41 | ||||
-rw-r--r-- | launcher/testdata/test/PropertyPrinter.java | 22 |
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); + } +} |