aboutsummaryrefslogtreecommitdiff
path: root/driver/fuzz_target_runner.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'driver/fuzz_target_runner.cpp')
-rw-r--r--driver/fuzz_target_runner.cpp398
1 files changed, 0 insertions, 398 deletions
diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp
deleted file mode 100644
index 934e27e1..00000000
--- a/driver/fuzz_target_runner.cpp
+++ /dev/null
@@ -1,398 +0,0 @@
-// 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