aboutsummaryrefslogtreecommitdiff
path: root/driver/fuzz_target_runner.cpp
diff options
context:
space:
mode:
authorMuhammad Haseeb Ahmad <mhahmad@google.com>2021-12-30 18:23:53 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-12-30 18:23:53 +0000
commit268d7f757f82e6e706cd4f5dfcb854fc2342b053 (patch)
treec5880647e8b29782d15be0c99a60e56fed6f8a02 /driver/fuzz_target_runner.cpp
parentb997679abe998d84ad4b9c3e6589342794d3bfcb (diff)
parent0f73d9c5add52fa24500a9ddb691528db216e096 (diff)
downloadjazzer-api-268d7f757f82e6e706cd4f5dfcb854fc2342b053.tar.gz
Merge remote-tracking branch 'aosp/upstream-main' into master am: 5c6f411699 am: 844d7aba71 am: 0f73d9c5ad
Original change: https://android-review.googlesource.com/c/platform/external/jazzer-api/+/1935188 Change-Id: I0c6c57f25d7b033e469b5f869e4de16f0ec62839
Diffstat (limited to 'driver/fuzz_target_runner.cpp')
-rw-r--r--driver/fuzz_target_runner.cpp398
1 files changed, 398 insertions, 0 deletions
diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp
new file mode 100644
index 00000000..934e27e1
--- /dev/null
+++ b/driver/fuzz_target_runner.cpp
@@ -0,0 +1,398 @@
+// 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 "fuzz_target_runner.h"
+
+#include <jni.h>
+
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/str_replace.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/substitute.h"
+#include "coverage_tracker.h"
+#include "fuzzed_data_provider.h"
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+#include "java_reproducer.h"
+#include "java_reproducer_templates.h"
+#include "utils.h"
+
+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(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.");
+
+DECLARE_bool(hooks);
+
+constexpr auto kManifestUtilsClass =
+ "com/code_intelligence/jazzer/runtime/ManifestUtils";
+constexpr auto kJazzerClass =
+ "com/code_intelligence/jazzer/runtime/JazzerInternal";
+constexpr auto kAutofuzzFuzzTargetClass =
+ "com/code_intelligence/jazzer/autofuzz/FuzzTarget";
+
+namespace jazzer {
+// split a string on unescaped spaces
+std::vector<std::string> splitOnSpace(const std::string &s) {
+ if (s.empty()) {
+ return {};
+ }
+
+ std::vector<std::string> tokens;
+ std::size_t token_begin = 0;
+ for (std::size_t i = 1; i < s.size() - 1; i++) {
+ // only split if the space is not escaped by a backslash "\"
+ if (s[i] == ' ' && s[i - 1] != '\\') {
+ // don't split on multiple spaces
+ if (i > token_begin + 1)
+ tokens.push_back(s.substr(token_begin, i - token_begin));
+ token_begin = i + 1;
+ }
+ }
+ tokens.push_back(s.substr(token_begin));
+ return tokens;
+}
+
+FuzzTargetRunner::FuzzTargetRunner(
+ JVM &jvm, const std::vector<std::string> &additional_target_args)
+ : ExceptionPrinter(jvm), jvm_(jvm), ignore_tokens_() {
+ auto &env = jvm.GetEnv();
+ if (!FLAGS_target_class.empty() && !FLAGS_autofuzz.empty()) {
+ std::cerr << "--target_class and --autofuzz cannot be specified together"
+ << std::endl;
+ exit(1);
+ }
+ if (!FLAGS_target_args.empty() && !FLAGS_autofuzz.empty()) {
+ std::cerr << "--target_args and --autofuzz cannot be specified together"
+ << std::endl;
+ exit(1);
+ }
+ if (FLAGS_autofuzz.empty() && !FLAGS_autofuzz_ignore.empty()) {
+ std::cerr << "--autofuzz_ignore requires --autofuzz" << std::endl;
+ exit(1);
+ }
+ if (FLAGS_target_class.empty() && FLAGS_autofuzz.empty()) {
+ FLAGS_target_class = DetectFuzzTargetClass();
+ }
+ // If automatically detecting the fuzz target class failed, we expect it as
+ // the value of the --target_class argument.
+ if (FLAGS_target_class.empty() && FLAGS_autofuzz.empty()) {
+ std::cerr << "Missing argument --target_class=<fuzz_target_class>"
+ << std::endl;
+ exit(1);
+ }
+ if (!FLAGS_autofuzz.empty()) {
+ FLAGS_target_class = kAutofuzzFuzzTargetClass;
+ if (FLAGS_keep_going == 0) {
+ FLAGS_keep_going = std::numeric_limits<gflags::uint32>::max();
+ }
+ // Pass the method reference string as the first argument to the generic
+ // autofuzz fuzz target. Subseqeuent arguments are interpreted as exception
+ // class names that should be ignored.
+ FLAGS_target_args = FLAGS_autofuzz;
+ if (!FLAGS_autofuzz_ignore.empty()) {
+ FLAGS_target_args = absl::StrCat(
+ FLAGS_target_args, " ",
+ absl::StrReplaceAll(FLAGS_autofuzz_ignore, {{",", " "}}));
+ }
+ }
+ // Set --keep_going to its real default.
+ if (FLAGS_keep_going == 0) {
+ FLAGS_keep_going = 1;
+ }
+ if ((!FLAGS_ignore.empty() || FLAGS_keep_going > 1) && !FLAGS_dedup) {
+ std::cerr << "--nodedup is not supported with --ignore or --keep_going"
+ << std::endl;
+ exit(1);
+ }
+ jazzer_ = jvm.FindClass(kJazzerClass);
+ last_finding_ =
+ env.GetStaticFieldID(jazzer_, "lastFinding", "Ljava/lang/Throwable;");
+
+ jclass_ = jvm.FindClass(FLAGS_target_class);
+ // one of the following functions is required:
+ // public static void fuzzerTestOneInput(byte[] input)
+ // public static void fuzzerTestOneInput(FuzzedDataProvider data)
+ fuzzer_test_one_input_bytes_ =
+ jvm.GetStaticMethodID(jclass_, "fuzzerTestOneInput", "([B)V", false);
+ fuzzer_test_one_input_data_ = jvm.GetStaticMethodID(
+ jclass_, "fuzzerTestOneInput",
+ "(Lcom/code_intelligence/jazzer/api/FuzzedDataProvider;)V", false);
+ bool using_bytes = fuzzer_test_one_input_bytes_ != nullptr;
+ bool using_data = fuzzer_test_one_input_data_ != nullptr;
+ // Fail if none ore both of the two possible fuzzerTestOneInput versions is
+ // defined in the class.
+ if (using_bytes == using_data) {
+ LOG(ERROR) << FLAGS_target_class
+ << " must define exactly one of the following two functions:";
+ LOG(ERROR) << "public static void fuzzerTestOneInput(byte[] ...)";
+ LOG(ERROR)
+ << "public static void fuzzerTestOneInput(FuzzedDataProvider ...)";
+ LOG(ERROR) << "Note: Fuzz targets returning boolean are no longer "
+ "supported; exceptions should be thrown instead of "
+ "returning true.";
+ exit(1);
+ }
+
+ // check existence of optional methods for initialization and destruction
+ fuzzer_initialize_ =
+ jvm.GetStaticMethodID(jclass_, "fuzzerInitialize", "()V", false);
+ fuzzer_tear_down_ =
+ jvm.GetStaticMethodID(jclass_, "fuzzerTearDown", "()V", false);
+ fuzzer_initialize_with_args_ = jvm.GetStaticMethodID(
+ jclass_, "fuzzerInitialize", "([Ljava/lang/String;)V", false);
+
+ auto fuzz_target_args_tokens = splitOnSpace(FLAGS_target_args);
+ fuzz_target_args_tokens.insert(fuzz_target_args_tokens.end(),
+ additional_target_args.begin(),
+ additional_target_args.end());
+
+ if (fuzzer_initialize_with_args_) {
+ // fuzzerInitialize with arguments gets priority
+ jclass string_class = jvm.FindClass("java/lang/String");
+ jobjectArray arg_array = jvm.GetEnv().NewObjectArray(
+ fuzz_target_args_tokens.size(), string_class, nullptr);
+ for (jint i = 0; i < fuzz_target_args_tokens.size(); i++) {
+ jstring str = env.NewStringUTF(fuzz_target_args_tokens[i].c_str());
+ env.SetObjectArrayElement(arg_array, i, str);
+ }
+ env.CallStaticObjectMethod(jclass_, fuzzer_initialize_with_args_,
+ arg_array);
+ } else if (fuzzer_initialize_) {
+ env.CallStaticVoidMethod(jclass_, fuzzer_initialize_);
+ } else {
+ LOG(INFO) << "did not call any fuzz target initialize functions";
+ }
+
+ if (jthrowable exception = env.ExceptionOccurred()) {
+ LOG(ERROR) << "== Java Exception in fuzzerInitialize: ";
+ LOG(ERROR) << getStackTrace(exception);
+ std::exit(1);
+ }
+
+ if (FLAGS_hooks) {
+ CoverageTracker::RecordInitialCoverage(env);
+ }
+ SetUpFuzzedDataProvider(jvm_.GetEnv());
+
+ // Parse a comma-separated list of hex dedup tokens.
+ std::vector<std::string> str_ignore_tokens =
+ absl::StrSplit(FLAGS_ignore, ',');
+ for (const std::string &str_token : str_ignore_tokens) {
+ if (str_token.empty()) continue;
+ try {
+ ignore_tokens_.push_back(std::stoull(str_token, nullptr, 16));
+ } catch (...) {
+ LOG(ERROR) << "Invalid dedup token (expected up to 16 hex digits): '"
+ << str_token << "'";
+ // Don't let libFuzzer print a crash stack trace.
+ _Exit(1);
+ }
+ }
+}
+
+FuzzTargetRunner::~FuzzTargetRunner() {
+ if (FLAGS_hooks && !FLAGS_coverage_report.empty()) {
+ std::string report = CoverageTracker::ComputeCoverage(jvm_.GetEnv());
+ std::ofstream report_file(FLAGS_coverage_report);
+ if (report_file) {
+ report_file << report << std::flush;
+ } else {
+ LOG(ERROR) << "Failed to write coverage report to "
+ << FLAGS_coverage_report;
+ }
+ }
+ if (fuzzer_tear_down_ != nullptr) {
+ std::cerr << "calling fuzzer teardown function" << std::endl;
+ jvm_.GetEnv().CallStaticVoidMethod(jclass_, fuzzer_tear_down_);
+ if (jthrowable exception = jvm_.GetEnv().ExceptionOccurred())
+ std::cerr << getStackTrace(exception) << std::endl;
+ }
+}
+
+RunResult FuzzTargetRunner::Run(const uint8_t *data, const std::size_t size) {
+ auto &env = jvm_.GetEnv();
+ static std::size_t run_count = 0;
+ if (run_count < 2) {
+ run_count++;
+ // For the first two runs only, replay the coverage recorded from static
+ // initializers. libFuzzer cleared the coverage map after they ran and could
+ // fail to see any coverage, triggering an early exit, if we don't replay it
+ // here.
+ // https://github.com/llvm/llvm-project/blob/957a5e987444d3193575d6ad8afe6c75da00d794/compiler-rt/lib/fuzzer/FuzzerLoop.cpp#L804-L809
+ CoverageTracker::ReplayInitialCoverage(env);
+ }
+ if (fuzzer_test_one_input_data_ != nullptr) {
+ FeedFuzzedDataProvider(data, size);
+ env.CallStaticVoidMethod(jclass_, fuzzer_test_one_input_data_,
+ GetFuzzedDataProviderJavaObject(jvm_));
+ } else {
+ jbyteArray byte_array = env.NewByteArray(size);
+ if (byte_array == nullptr) {
+ env.ExceptionDescribe();
+ throw std::runtime_error(std::string("Cannot create byte array"));
+ }
+ env.SetByteArrayRegion(byte_array, 0, size,
+ reinterpret_cast<const jbyte *>(data));
+ env.CallStaticVoidMethod(jclass_, fuzzer_test_one_input_bytes_, byte_array);
+ env.DeleteLocalRef(byte_array);
+ }
+
+ const auto finding = GetFinding();
+ if (finding != nullptr) {
+ jlong dedup_token = computeDedupToken(finding);
+ // Check whether this stack trace has been encountered before if
+ // `--keep_going` has been supplied.
+ if (dedup_token != 0 && FLAGS_keep_going > 1 &&
+ std::find(ignore_tokens_.cbegin(), ignore_tokens_.cend(),
+ dedup_token) != ignore_tokens_.end()) {
+ env.DeleteLocalRef(finding);
+ return RunResult::kOk;
+ } else {
+ ignore_tokens_.push_back(dedup_token);
+ std::cout << std::endl;
+ std::cerr << "== Java Exception: " << getStackTrace(finding);
+ env.DeleteLocalRef(finding);
+ if (FLAGS_dedup) {
+ std::cout << "DEDUP_TOKEN: " << std::hex << std::setfill('0')
+ << std::setw(16) << dedup_token << std::endl;
+ }
+ if (ignore_tokens_.size() < static_cast<std::size_t>(FLAGS_keep_going)) {
+ return RunResult::kDumpAndContinue;
+ } else {
+ return RunResult::kException;
+ }
+ }
+ }
+ return RunResult::kOk;
+}
+
+// Returns a fuzzer finding as a Throwable (or nullptr if there is none),
+// clearing any JVM exceptions in the process.
+jthrowable FuzzTargetRunner::GetFinding() const {
+ auto &env = jvm_.GetEnv();
+ jthrowable unprocessed_finding = nullptr;
+ if (env.ExceptionCheck()) {
+ unprocessed_finding = env.ExceptionOccurred();
+ env.ExceptionClear();
+ }
+ // Explicitly reported findings take precedence over uncaught exceptions.
+ if (auto reported_finding =
+ (jthrowable)env.GetStaticObjectField(jazzer_, last_finding_);
+ reported_finding != nullptr) {
+ env.DeleteLocalRef(unprocessed_finding);
+ unprocessed_finding = reported_finding;
+ }
+ jthrowable processed_finding = preprocessException(unprocessed_finding);
+ env.DeleteLocalRef(unprocessed_finding);
+ return processed_finding;
+}
+
+void FuzzTargetRunner::DumpReproducer(const uint8_t *data, std::size_t size) {
+ auto &env = jvm_.GetEnv();
+ std::string base64_data;
+ if (fuzzer_test_one_input_data_) {
+ // Record the data retrieved from the FuzzedDataProvider and supply it to a
+ // Java-only CannedFuzzedDataProvider in the reproducer.
+ FeedFuzzedDataProvider(data, size);
+ jobject recorder = GetRecordingFuzzedDataProviderJavaObject(jvm_);
+ env.CallStaticVoidMethod(jclass_, fuzzer_test_one_input_data_, recorder);
+ const auto finding = GetFinding();
+ if (finding == nullptr) {
+ LOG(ERROR) << "Failed to reproduce crash when rerunning with recorder";
+ return;
+ }
+ base64_data = SerializeRecordingFuzzedDataProvider(jvm_, recorder);
+ } else {
+ absl::string_view data_str(reinterpret_cast<const char *>(data), size);
+ absl::Base64Escape(data_str, &base64_data);
+ }
+ const char *fuzz_target_call = fuzzer_test_one_input_data_
+ ? kTestOneInputWithData
+ : kTestOneInputWithBytes;
+ std::string data_sha1 = jazzer::Sha1Hash(data, size);
+ std::string reproducer =
+ absl::Substitute(kBaseReproducer, data_sha1, base64_data,
+ FLAGS_target_class, fuzz_target_call);
+ std::string reproducer_filename = absl::StrFormat("Crash_%s.java", data_sha1);
+ std::string reproducer_full_path = absl::StrFormat(
+ "%s%c%s", FLAGS_reproducer_path, kPathSeparator, reproducer_filename);
+ std::ofstream reproducer_out(reproducer_full_path);
+ reproducer_out << reproducer;
+ std::cout << absl::StrFormat(
+ "reproducer_path='%s'; Java reproducer written to %s",
+ FLAGS_reproducer_path, reproducer_full_path)
+ << std::endl;
+}
+
+std::string FuzzTargetRunner::DetectFuzzTargetClass() const {
+ jclass manifest_utils = jvm_.FindClass(kManifestUtilsClass);
+ jmethodID detect_fuzz_target_class = jvm_.GetStaticMethodID(
+ manifest_utils, "detectFuzzTargetClass", "()Ljava/lang/String;", true);
+ auto &env = jvm_.GetEnv();
+ auto jni_fuzz_target_class = (jstring)(env.CallStaticObjectMethod(
+ manifest_utils, detect_fuzz_target_class));
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ exit(1);
+ }
+ if (jni_fuzz_target_class == nullptr) return "";
+
+ const char *fuzz_target_class_cstr =
+ env.GetStringUTFChars(jni_fuzz_target_class, nullptr);
+ std::string fuzz_target_class = std::string(fuzz_target_class_cstr);
+ env.ReleaseStringUTFChars(jni_fuzz_target_class, fuzz_target_class_cstr);
+ env.DeleteLocalRef(jni_fuzz_target_class);
+
+ return fuzz_target_class;
+}
+} // namespace jazzer