diff options
Diffstat (limited to 'driver/libfuzzer_driver.cpp')
-rw-r--r-- | driver/libfuzzer_driver.cpp | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/driver/libfuzzer_driver.cpp b/driver/libfuzzer_driver.cpp new file mode 100644 index 00000000..57beef58 --- /dev/null +++ b/driver/libfuzzer_driver.cpp @@ -0,0 +1,187 @@ +// 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 |