diff options
Diffstat (limited to 'driver/fuzz_target_runner.cpp')
-rw-r--r-- | driver/fuzz_target_runner.cpp | 398 |
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 |