text: // 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
  