blob: 57beef5807f16962ef72a4b49df0a526730cf2d6 [file] [log] [blame]
// 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