aboutsummaryrefslogtreecommitdiff
path: root/driver/jvm_tooling.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'driver/jvm_tooling.cpp')
-rw-r--r--driver/jvm_tooling.cpp400
1 files changed, 125 insertions, 275 deletions
diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp
index 178eec02..71a5f581 100644
--- a/driver/jvm_tooling.cpp
+++ b/driver/jvm_tooling.cpp
@@ -14,6 +14,7 @@
#include "jvm_tooling.h"
+#include <cstdlib>
#include <fstream>
#include <iostream>
#include <memory>
@@ -24,13 +25,8 @@
#include "absl/strings/str_join.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h"
-#include "coverage_tracker.h"
#include "gflags/gflags.h"
-#include "glog/logging.h"
-#include "libfuzzer_callbacks.h"
-#include "signal_handler.h"
#include "tools/cpp/runfiles/runfiles.h"
-#include "utils.h"
DEFINE_string(cp, ".",
"the classpath to use for fuzzing. Behaves analogously to java's "
@@ -53,23 +49,30 @@ DEFINE_string(agent_path, "", "location of the fuzzing instrumentation agent");
// combined during the initialization of the JVM.
DEFINE_string(instrumentation_includes, "",
"list of glob patterns for classes that will be instrumented for "
- "fuzzing. Separated by colon \":\"");
-DEFINE_string(instrumentation_excludes, "",
- "list of glob patterns for classes that will not be instrumented "
- "for fuzzing. Separated by colon \":\"");
+ "fuzzing (separator is ':' on Linux/macOS and ';' on Windows)");
+DEFINE_string(
+ instrumentation_excludes, "",
+ "list of glob patterns for classes that will not be instrumented "
+ "for fuzzing (separator is ':' on Linux/macOS and ';' on Windows)");
DEFINE_string(custom_hook_includes, "",
"list of glob patterns for classes that will only be "
- "instrumented using custom hooks. Separated by colon \":\"");
-DEFINE_string(custom_hook_excludes, "",
- "list of glob patterns for classes that will not be instrumented "
- "using custom hooks. Separated by colon \":\"");
+ "instrumented using custom hooks (separator is ':' on "
+ "Linux/macOS and ';' on Windows)");
+DEFINE_string(
+ custom_hook_excludes, "",
+ "list of glob patterns for classes that will not be instrumented "
+ "using custom hooks (separator is ':' on Linux/macOS and ';' on Windows)");
DEFINE_string(custom_hooks, "",
- "list of classes containing custom instrumentation hooks. "
- "Separated by colon \":\"");
+ "list of classes containing custom instrumentation hooks "
+ "(separator is ':' on Linux/macOS and ';' on Windows)");
+DEFINE_string(disabled_hooks, "",
+ "list of hook classes (custom or built-in) that should not be "
+ "loaded (separator is ':' on Linux/macOS and ';' on Windows)");
DEFINE_string(
trace, "",
- "list of instrumentation to perform separated by colon \":\". "
+ "list of instrumentation to perform separated by colon ':' on Linux/macOS "
+ "and ';' on Windows. "
"Available options are cov, cmp, div, gep, all. These options "
"correspond to the \"-fsanitize-coverage=trace-*\" flags in clang.");
DEFINE_string(
@@ -88,72 +91,57 @@ DEFINE_bool(hooks, true,
"coverage information will be processed. This can be useful for "
"running a regression test on non-instrumented bytecode.");
-#ifdef _WIN32
+DEFINE_string(
+ target_class, "",
+ "The Java class that contains the static fuzzerTestOneInput function");
+DEFINE_string(target_args, "",
+ "Arguments passed to fuzzerInitialize as a String array. "
+ "Separated by space.");
+
+DEFINE_uint32(keep_going, 0,
+ "Continue fuzzing until N distinct exception stack traces have"
+ "been encountered. Defaults to exit after the first finding "
+ "unless --autofuzz is specified.");
+DEFINE_bool(dedup, true,
+ "Emit a dedup token for every finding. Defaults to true and is "
+ "required for --keep_going and --ignore.");
+DEFINE_string(
+ ignore, "",
+ "Comma-separated list of crash dedup tokens to ignore. This is useful to "
+ "continue fuzzing before a crash is fixed.");
+
+DEFINE_string(reproducer_path, ".",
+ "Path at which fuzzing reproducers are stored. Defaults to the "
+ "current directory.");
+DEFINE_string(coverage_report, "",
+ "Path at which a coverage report is stored when the fuzzer "
+ "exits. If left empty, no report is generated (default)");
+DEFINE_string(coverage_dump, "",
+ "Path at which a coverage dump is stored when the fuzzer "
+ "exits. If left empty, no dump is generated (default)");
+
+DEFINE_string(autofuzz, "",
+ "Fully qualified reference to a method on the classpath that "
+ "should be fuzzed automatically (example: System.out::println). "
+ "Fuzzing will continue even after a finding; specify "
+ "--keep_going=N to stop after N findings.");
+DEFINE_string(autofuzz_ignore, "",
+ "Fully qualified class names of exceptions to ignore during "
+ "autofuzz. Separated by comma.");
+DEFINE_bool(fake_pcs, false,
+ "No-op flag that remains for backwards compatibility only.");
+
+#if defined(_WIN32) || defined(_WIN64)
#define ARG_SEPARATOR ";"
+constexpr auto kPathSeparator = '\\';
#else
#define ARG_SEPARATOR ":"
+constexpr auto kPathSeparator = '/';
#endif
-// Called by the agent when
-// com.code_intelligence.jazzer.instrumentor.ClassInstrumentor is initialized.
-// This only happens when FLAGS_hooks is true.
-extern "C" JNIEXPORT jint JNICALL JNI_OnLoad_jazzer_initialize(JavaVM *vm,
- void *) {
- if (!FLAGS_hooks) {
- LOG(ERROR) << "JNI_OnLoad_jazzer_initialize called with --nohooks";
- exit(1);
- }
- JNIEnv *env = nullptr;
- jint result = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_8);
- if (result != JNI_OK) {
- LOG(FATAL) << "Failed to get JNI environment";
- exit(1);
- }
- jazzer::registerFuzzerCallbacks(*env);
- jazzer::CoverageTracker::Setup(*env);
- jazzer::SignalHandler::Setup(*env);
- return JNI_VERSION_1_8;
-}
-
namespace {
constexpr auto kAgentBazelRunfilesPath = "jazzer/agent/jazzer_agent_deploy.jar";
constexpr auto kAgentFileName = "jazzer_agent_deploy.jar";
-constexpr const char kExceptionUtilsClassName[] =
- "com/code_intelligence/jazzer/runtime/ExceptionUtils";
-} // namespace
-
-namespace jazzer {
-
-void DumpJvmStackTraces() {
- JavaVM *vm;
- jsize num_vms;
- JNI_GetCreatedJavaVMs(&vm, 1, &num_vms);
- if (num_vms != 1) {
- return;
- }
- JNIEnv *env = nullptr;
- if (vm->AttachCurrentThread(reinterpret_cast<void **>(&env), nullptr) !=
- JNI_OK) {
- return;
- }
- jclass exceptionUtils = env->FindClass(kExceptionUtilsClassName);
- if (env->ExceptionCheck()) {
- env->ExceptionDescribe();
- return;
- }
- jmethodID dumpStack =
- env->GetStaticMethodID(exceptionUtils, "dumpAllStackTraces", "()V");
- if (env->ExceptionCheck()) {
- env->ExceptionDescribe();
- return;
- }
- env->CallStaticVoidMethod(exceptionUtils, dumpStack);
- if (env->ExceptionCheck()) {
- env->ExceptionDescribe();
- return;
- }
- // Do not detach as we may be the main thread (but the JVM exits anyway).
-}
std::string dirFromFullPath(const std::string &path) {
const auto pos = path.rfind(kPathSeparator);
@@ -169,8 +157,8 @@ std::string getInstrumentorAgentPath(const std::string &executable_path) {
// User provided agent location takes precedence.
if (!FLAGS_agent_path.empty()) {
if (std::ifstream(FLAGS_agent_path).good()) return FLAGS_agent_path;
- LOG(ERROR) << "Could not find " << kAgentFileName << " at \""
- << FLAGS_agent_path << "\"";
+ std::cerr << "ERROR: Could not find " << kAgentFileName << " at \""
+ << FLAGS_agent_path << "\"" << std::endl;
exit(1);
}
// First check if we are running inside the Bazel tree and use the agent
@@ -179,7 +167,7 @@ std::string getInstrumentorAgentPath(const std::string &executable_path) {
using bazel::tools::cpp::runfiles::Runfiles;
std::string error;
std::unique_ptr<Runfiles> runfiles(
- Runfiles::Create(executable_path, &error));
+ Runfiles::Create(std::string(executable_path), &error));
if (runfiles != nullptr) {
auto bazel_path = runfiles->Rlocation(kAgentBazelRunfilesPath);
if (!bazel_path.empty() && std::ifstream(bazel_path).good())
@@ -193,31 +181,43 @@ std::string getInstrumentorAgentPath(const std::string &executable_path) {
auto agent_path =
absl::StrFormat("%s%c%s", dir, kPathSeparator, kAgentFileName);
if (std::ifstream(agent_path).good()) return agent_path;
- LOG(ERROR) << "Could not find " << kAgentFileName
- << ". Please provide "
- "the pathname via the --agent_path flag.";
+ std::cerr << "ERROR: Could not find " << kAgentFileName
+ << ". Please provide the pathname via the --agent_path flag."
+ << std::endl;
exit(1);
}
-std::string agentArgsFromFlags() {
- std::vector<std::string> args;
- for (const auto &flag_pair :
- std::vector<std::pair<std::string, const std::string &>>{
- // {<agent option>, <ref to glog flag> }
- {"instrumentation_includes", FLAGS_instrumentation_includes},
- {"instrumentation_excludes", FLAGS_instrumentation_excludes},
- {"custom_hooks", FLAGS_custom_hooks},
- {"custom_hook_includes", FLAGS_custom_hook_includes},
- {"custom_hook_excludes", FLAGS_custom_hook_excludes},
- {"trace", FLAGS_trace},
- {"id_sync_file", FLAGS_id_sync_file},
- {"dump_classes_dir", FLAGS_dump_classes_dir},
- }) {
- if (!flag_pair.second.empty()) {
- args.push_back(flag_pair.first + "=" + flag_pair.second);
- }
+std::vector<std::string> optsAsDefines() {
+ std::vector<std::string> defines{
+ absl::StrFormat("-Djazzer.target_class=%s", FLAGS_target_class),
+ absl::StrFormat("-Djazzer.target_args=%s", FLAGS_target_args),
+ absl::StrFormat("-Djazzer.dedup=%s", FLAGS_dedup ? "true" : "false"),
+ absl::StrFormat("-Djazzer.ignore=%s", FLAGS_ignore),
+ absl::StrFormat("-Djazzer.reproducer_path=%s", FLAGS_reproducer_path),
+ absl::StrFormat("-Djazzer.coverage_report=%s", FLAGS_coverage_report),
+ absl::StrFormat("-Djazzer.coverage_dump=%s", FLAGS_coverage_dump),
+ absl::StrFormat("-Djazzer.autofuzz=%s", FLAGS_autofuzz),
+ absl::StrFormat("-Djazzer.autofuzz_ignore=%s", FLAGS_autofuzz_ignore),
+ absl::StrFormat("-Djazzer.hooks=%s", FLAGS_hooks ? "true" : "false"),
+ absl::StrFormat("-Djazzer.id_sync_file=%s", FLAGS_id_sync_file),
+ absl::StrFormat("-Djazzer.instrumentation_includes=%s",
+ FLAGS_instrumentation_includes),
+ absl::StrFormat("-Djazzer.instrumentation_excludes=%s",
+ FLAGS_instrumentation_excludes),
+ absl::StrFormat("-Djazzer.custom_hooks=%s", FLAGS_custom_hooks),
+ absl::StrFormat("-Djazzer.disabled_hooks=%s", FLAGS_disabled_hooks),
+ absl::StrFormat("-Djazzer.custom_hook_includes=%s",
+ FLAGS_custom_hook_includes),
+ absl::StrFormat("-Djazzer.custom_hook_excludes=%s",
+ FLAGS_custom_hook_excludes),
+ absl::StrFormat("-Djazzer.trace=%s", FLAGS_trace),
+ absl::StrFormat("-Djazzer.dump_classes_dir=%s", FLAGS_dump_classes_dir),
+ };
+ if (!gflags::GetCommandLineFlagInfoOrDie("keep_going").is_default) {
+ defines.emplace_back(
+ absl::StrFormat("-Djazzer.keep_going=%d", FLAGS_keep_going));
}
- return absl::StrJoin(args, ",");
+ return defines;
}
// Splits a string at the ARG_SEPARATOR unless it is escaped with a backslash.
@@ -247,6 +247,9 @@ std::vector<std::string> splitEscaped(const std::string &str) {
return parts;
}
+} // namespace
+
+namespace jazzer {
JVM::JVM(const std::string &executable_path) {
// combine class path from command line flags and JAVA_FUZZER_CLASSPATH env
@@ -254,11 +257,10 @@ JVM::JVM(const std::string &executable_path) {
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::StrFormat(ARG_SEPARATOR "%s", class_path_from_env);
+ class_path += absl::StrCat(ARG_SEPARATOR, class_path_from_env);
}
- class_path += absl::StrFormat(ARG_SEPARATOR "%s",
- getInstrumentorAgentPath(executable_path));
- LOG(INFO) << "got class path " << class_path;
+ class_path +=
+ absl::StrCat(ARG_SEPARATOR, getInstrumentorAgentPath(executable_path));
std::vector<JavaVMOption> options;
options.push_back(
@@ -266,13 +268,33 @@ JVM::JVM(const std::string &executable_path) {
// 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"});
- options.push_back(JavaVMOption{.optionString = (char *)"-enableassertions"});
// 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"});
+ options.push_back(
+ JavaVMOption{.optionString = (char *)"-XX:+CriticalJNINatives"});
+
+ std::vector<std::string> opt_defines = optsAsDefines();
+ for (const auto &define : opt_defines) {
+ options.push_back(
+ JavaVMOption{.optionString = const_cast<char *>(define.c_str())});
+ }
+
+ // Add additional JVM options set through JAVA_OPTS.
+ 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
std::vector<std::string> jvm_args;
@@ -292,15 +314,6 @@ JVM::JVM(const std::string &executable_path) {
JavaVMOption{.optionString = const_cast<char *>(arg.c_str())});
}
- std::string agent_jvm_arg;
- if (FLAGS_hooks) {
- agent_jvm_arg = absl::StrFormat("-javaagent:%s=%s",
- getInstrumentorAgentPath(executable_path),
- agentArgsFromFlags());
- options.push_back(JavaVMOption{
- .optionString = const_cast<char *>(agent_jvm_arg.c_str())});
- }
-
JavaVMInitArgs jvm_init_args = {.version = JNI_VERSION_1_8,
.nOptions = (int)options.size(),
.options = options.data(),
@@ -316,167 +329,4 @@ JVM::JVM(const std::string &executable_path) {
JNIEnv &JVM::GetEnv() const { return *env_; }
JVM::~JVM() { jvm_->DestroyJavaVM(); }
-
-jclass JVM::FindClass(std::string class_name) const {
- auto &env = GetEnv();
- std::replace(class_name.begin(), class_name.end(), '.', '/');
- const auto ret = env.FindClass(class_name.c_str());
- if (ret == nullptr) {
- if (env.ExceptionCheck()) {
- env.ExceptionDescribe();
- throw std::runtime_error(
- absl::StrFormat("Could not find class %s", class_name));
- } else {
- throw std::runtime_error(absl::StrFormat(
- "Java class '%s' not found without exception", class_name));
- }
- }
- return ret;
-}
-
-jmethodID JVM::GetStaticMethodID(jclass jclass, const std::string &jmethod,
- const std::string &signature,
- bool is_required) const {
- auto &env = GetEnv();
- const auto ret =
- env.GetStaticMethodID(jclass, jmethod.c_str(), signature.c_str());
- if (ret == nullptr) {
- if (is_required) {
- if (env.ExceptionCheck()) {
- env.ExceptionDescribe();
- }
- throw std::runtime_error(
- absl::StrFormat("Static method '%s' not found", jmethod));
- } else {
- LOG(INFO) << "did not find method " << jmethod << " with signature "
- << signature;
- env.ExceptionClear();
- }
- }
- return ret;
-}
-
-jmethodID JVM::GetMethodID(jclass jclass, const std::string &jmethod,
- const std::string &signature) const {
- auto &env = GetEnv();
- const auto ret = env.GetMethodID(jclass, jmethod.c_str(), signature.c_str());
- if (ret == nullptr) {
- if (env.ExceptionCheck()) {
- env.ExceptionDescribe();
- }
- throw std::runtime_error(absl::StrFormat("Method '%s' not found", jmethod));
- }
- return ret;
-}
-
-jfieldID JVM::GetStaticFieldID(jclass class_id, const std::string &field_name,
- const std::string &type) const {
- auto &env = GetEnv();
- const auto ret =
- env.GetStaticFieldID(class_id, field_name.c_str(), type.c_str());
- if (ret == nullptr) {
- if (env.ExceptionCheck()) {
- env.ExceptionDescribe();
- }
- throw std::runtime_error(
- absl::StrFormat("Field '%s' not found", field_name));
- }
- return ret;
-}
-
-ExceptionPrinter::ExceptionPrinter(JVM &jvm)
- : jvm_(jvm),
- string_writer_class_(jvm.FindClass("java/io/StringWriter")),
- string_writer_constructor_(
- jvm.GetMethodID(string_writer_class_, "<init>", "()V")),
- string_writer_to_string_method_(jvm.GetMethodID(
- string_writer_class_, "toString", "()Ljava/lang/String;")),
- print_writer_class_(jvm.FindClass("java/io/PrintWriter")),
- print_writer_constructor_(jvm.GetMethodID(print_writer_class_, "<init>",
- "(Ljava/io/Writer;)V")) {
- auto throwable_class = jvm.FindClass("java/lang/Throwable");
- print_stack_trace_method_ = jvm.GetMethodID(
- throwable_class, "printStackTrace", "(Ljava/io/PrintWriter;)V");
- if (FLAGS_hooks) {
- exception_utils_ = jvm.FindClass(kExceptionUtilsClassName);
- compute_dedup_token_method_ = jvm.GetStaticMethodID(
- exception_utils_, "computeDedupToken", "(Ljava/lang/Throwable;)J");
- preprocess_throwable_method_ =
- jvm.GetStaticMethodID(exception_utils_, "preprocessThrowable",
- "(Ljava/lang/Throwable;)Ljava/lang/Throwable;");
- }
-}
-
-// The JNI way of writing:
-// StringWriter stringWriter = new StringWriter();
-// PrintWriter printWriter = new PrintWriter(stringWriter);
-// e.printStackTrace(printWriter);
-// return stringWriter.toString();
-std::string ExceptionPrinter::getStackTrace(jthrowable exception) const {
- auto &env = jvm_.GetEnv();
- if (exception == nullptr) {
- return "";
- }
-
- auto string_writer =
- env.NewObject(string_writer_class_, string_writer_constructor_);
- if (string_writer == nullptr) {
- env.ExceptionDescribe();
- return "";
- }
- auto print_writer = env.NewObject(print_writer_class_,
- print_writer_constructor_, string_writer);
- if (print_writer == nullptr) {
- env.ExceptionDescribe();
- return "";
- }
-
- env.CallVoidMethod(exception, print_stack_trace_method_, print_writer);
- env.DeleteLocalRef(print_writer);
- if (env.ExceptionCheck()) {
- env.ExceptionDescribe();
- return "";
- }
- auto exception_string_object = reinterpret_cast<jstring>(
- env.CallObjectMethod(string_writer, string_writer_to_string_method_));
- env.DeleteLocalRef(string_writer);
- if (env.ExceptionCheck()) {
- env.ExceptionDescribe();
- return "";
- }
-
- auto char_pointer = env.GetStringUTFChars(exception_string_object, nullptr);
- std::string exception_string(char_pointer);
- env.ReleaseStringUTFChars(exception_string_object, char_pointer);
- env.DeleteLocalRef(exception_string_object);
- return exception_string;
-}
-
-jthrowable ExceptionPrinter::preprocessException(jthrowable exception) const {
- if (exception == nullptr) return nullptr;
- auto &env = jvm_.GetEnv();
- if (!FLAGS_hooks || !preprocess_throwable_method_) return exception;
- auto processed_exception = (jthrowable)(env.CallStaticObjectMethod(
- exception_utils_, preprocess_throwable_method_, exception));
- if (env.ExceptionCheck()) {
- env.ExceptionDescribe();
- return exception;
- }
- return processed_exception;
-}
-
-jlong ExceptionPrinter::computeDedupToken(jthrowable exception) const {
- auto &env = jvm_.GetEnv();
- if (!FLAGS_hooks || exception == nullptr ||
- compute_dedup_token_method_ == nullptr)
- return 0;
- const auto dedup_token = env.CallStaticLongMethod(
- exception_utils_, compute_dedup_token_method_, exception);
- if (env.ExceptionCheck()) {
- env.ExceptionDescribe();
- return 0;
- }
- return dedup_token;
-}
-
} // namespace jazzer