| // 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 <rules_jni.h> |
| |
| #include <algorithm> |
| #include <filesystem> |
| #include <fstream> |
| #include <random> |
| #include <string> |
| #include <vector> |
| |
| #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<char *> 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<short> dist(0, 'z' - 'a'); |
| std::generate_n(temp_filename_suffix.begin(), temp_filename_suffix.length(), |
| [&rng, &dist] { return static_cast<char>('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<char *> 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<char *>(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<jazzer::JVM>(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<jazzer::FuzzTargetRunner>(*jvm_); |
| } |
| |
| std::string LibfuzzerDriver::getUsageString() { |
| return R"(Test java fuzz targets using libFuzzer. Usage: |
| jazzer --cp=<java_class_path> --target_class=<fuzz_target_class> <libfuzzer_arguments...>)"; |
| } |
| |
| 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 |