// 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 "libfuzzer_driver.h" #include #include #include #include #include #include #include #include "absl/strings/match.h" #include "absl/strings/str_format.h" #include "fuzz_target_runner.h" #include "gflags/gflags.h" #include "glog/logging.h" #include "jvm_tooling.h" using namespace std::string_literals; // Defined by glog DECLARE_bool(log_prefix); // Defined in libfuzzer_callbacks.cpp DECLARE_bool(fake_pcs); // Defined in jvm_tooling.cpp DECLARE_string(id_sync_file); // Defined in fuzz_target_runner.cpp DECLARE_string(coverage_report); // This symbol is defined by sanitizers if linked into Jazzer or in // sanitizer_symbols.cpp if no sanitizer is used. extern "C" void __sanitizer_set_death_callback(void (*)()); // We apply a patch to libFuzzer to make it call this function instead of // __sanitizer_set_death_callback to pass us the death callback. extern "C" [[maybe_unused]] void __jazzer_set_death_callback( void (*callback)()) { jazzer::AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_ = callback; __sanitizer_set_death_callback(callback); } namespace { char *additional_arg; std::vector modified_argv; std::string GetNewTempFilePath() { auto temp_dir = std::filesystem::temp_directory_path(); std::string temp_filename_suffix(32, '\0'); std::random_device rng; std::uniform_int_distribution dist(0, 'z' - 'a'); std::generate_n(temp_filename_suffix.begin(), temp_filename_suffix.length(), [&rng, &dist] { return static_cast('a' + dist(rng)); }); auto temp_path = temp_dir / ("jazzer-" + temp_filename_suffix); if (std::filesystem::exists(temp_path)) throw std::runtime_error("Random temp file path exists: " + temp_path.string()); return temp_path.string(); } } // namespace namespace jazzer { // A libFuzzer-registered callback that outputs the crashing input, but does // not include a stack trace. void (*AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_)() = nullptr; AbstractLibfuzzerDriver::AbstractLibfuzzerDriver( int *argc, char ***argv, const std::string &usage_string) { gflags::SetUsageMessage(usage_string); // Disable glog log prefixes to mimic libFuzzer output. FLAGS_log_prefix = false; google::InitGoogleLogging((*argv)[0]); rules_jni_init((*argv)[0]); auto argv_start = *argv; auto argv_end = *argv + *argc; if (std::find(argv_start, argv_end, "-use_value_profile=1"s) != argv_end) { FLAGS_fake_pcs = true; } // All libFuzzer flags start with a single dash, our arguments all start with // a double dash. We can thus filter out the arguments meant for gflags by // taking only those with a leading double dash. std::vector our_args = {*argv_start}; std::copy_if( argv_start, argv_end, std::back_inserter(our_args), [](const auto arg) { return absl::StartsWith(std::string(arg), "--"); }); int our_argc = our_args.size(); char **our_argv = our_args.data(); // Let gflags consume its flags, but keep them in the argument list in case // libFuzzer forwards the command line (e.g. with -jobs or -minimize_crash). gflags::ParseCommandLineFlags(&our_argc, &our_argv, false); if (std::any_of(argv_start, argv_end, [](const std::string_view &arg) { return absl::StartsWith(arg, "-fork=") || absl::StartsWith(arg, "-jobs=") || absl::StartsWith(arg, "-merge="); })) { if (!FLAGS_coverage_report.empty()) { LOG(WARNING) << "WARN: --coverage_report does not support parallel " "fuzzing and has been disabled"; FLAGS_coverage_report = ""; } if (FLAGS_id_sync_file.empty()) { // Create an empty temporary file used for coverage ID synchronization and // pass its path to the agent in every child process. This requires adding // the argument to argv for it to be picked up by libFuzzer, which then // forwards it to child processes. FLAGS_id_sync_file = GetNewTempFilePath(); std::string new_arg = absl::StrFormat("--id_sync_file=%s", FLAGS_id_sync_file); // This argument can be accessed by libFuzzer at any (later) time and thus // cannot be safely freed by us. additional_arg = strdup(new_arg.c_str()); modified_argv = std::vector(argv_start, argv_end); modified_argv.push_back(additional_arg); // Terminate modified_argv. modified_argv.push_back(nullptr); // Modify argv and argc for libFuzzer. modified_argv must not be changed // after this point. *argc += 1; *argv = modified_argv.data(); argv_start = *argv; argv_end = *argv + *argc; } // Creates the file, truncating it if it exists. std::ofstream touch_file(FLAGS_id_sync_file, std::ios_base::trunc); auto cleanup_fn = [] { try { std::filesystem::remove(std::filesystem::path(FLAGS_id_sync_file)); } catch (...) { // We should not throw exceptions during shutdown. } }; std::atexit(cleanup_fn); } initJvm(*argv_start); } void AbstractLibfuzzerDriver::initJvm(const std::string &executable_path) { jvm_ = std::make_unique(executable_path); } LibfuzzerDriver::LibfuzzerDriver(int *argc, char ***argv) : AbstractLibfuzzerDriver(argc, argv, getUsageString()) { // the FuzzTargetRunner can only be initialized after the fuzzer callbacks // have been registered otherwise link errors would occur runner_ = std::make_unique(*jvm_); } std::string LibfuzzerDriver::getUsageString() { return R"(Test java fuzz targets using libFuzzer. Usage: jazzer --cp= --target_class= )"; } RunResult LibfuzzerDriver::TestOneInput(const uint8_t *data, const std::size_t size) { // pass the fuzzer input to the java fuzz target return runner_->Run(data, size); } void LibfuzzerDriver::DumpReproducer(const uint8_t *data, std::size_t size) { return runner_->DumpReproducer(data, size); } } // namespace jazzer