188 lines
6.6 KiB
C++
188 lines
6.6 KiB
C++
// 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
|