blob: b96c86657bbad98644d8d57619baee0d74faa63c [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.
/*
* Jazzer's native main function, which:
* 1. defines default settings for ASan and UBSan;
* 2. preprocesses the command-line arguments passed to libFuzzer;
* 3. starts a JVM;
* 4. passes control to the fuzz target runner.
*/
#include <rules_jni.h>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <random>
#include <string>
#include <vector>
#include "absl/strings/match.h"
#include "absl/strings/str_format.h"
#include "absl/strings/strip.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 jvm_tooling.cpp
DECLARE_string(id_sync_file);
// Defined in fuzz_target_runner.cpp
DECLARE_string(coverage_report);
// Defined in fuzz_target_runner.cpp
DECLARE_string(coverage_dump);
namespace {
bool is_asan_active = false;
}
extern "C" {
[[maybe_unused]] const char *__asan_default_options() {
is_asan_active = true;
// LeakSanitizer is not yet supported as it reports too many false positives
// due to how the JVM GC works.
// We use a distinguished exit code to recognize ASan crashes in tests.
// Also specify abort_on_error=0 explicitly since ASan aborts rather than
// exits on macOS by default, which would cause our exit code to be ignored.
return "abort_on_error=0,detect_leaks=0,exitcode=76";
}
[[maybe_unused]] const char *__ubsan_default_options() {
// We use a distinguished exit code to recognize UBSan crashes in tests.
// Also specify abort_on_error=0 explicitly since UBSan aborts rather than
// exits on macOS by default, which would cause our exit code to be ignored.
return "abort_on_error=0,exitcode=76";
}
}
namespace {
const std::string kUsageMessage =
R"(Test java fuzz targets using libFuzzer. Usage:
jazzer --cp=<java_class_path> --target_class=<fuzz_target_class> <libfuzzer_arguments...>)";
const std::string kFuzzTargetRunnerClassName =
"com/code_intelligence/jazzer/driver/FuzzTargetRunner";
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();
}
int StartLibFuzzer(std::unique_ptr<jazzer::JVM> jvm,
std::vector<std::string> argv) {
JNIEnv &env = jvm->GetEnv();
jclass runner = env.FindClass(kFuzzTargetRunnerClassName.c_str());
if (runner == nullptr) {
env.ExceptionDescribe();
return 1;
}
jmethodID startFuzzer =
env.GetStaticMethodID(runner, "startLibFuzzer", "([[B)I");
if (startFuzzer == nullptr) {
env.ExceptionDescribe();
return 1;
}
jclass byteArrayClass = env.FindClass("[B");
if (byteArrayClass == nullptr) {
env.ExceptionDescribe();
return 1;
}
jobjectArray args = env.NewObjectArray(argv.size(), byteArrayClass, nullptr);
if (args == nullptr) {
env.ExceptionDescribe();
return 1;
}
for (jsize i = 0; i < argv.size(); ++i) {
jint len = argv[i].size();
jbyteArray arg = env.NewByteArray(len);
if (arg == nullptr) {
env.ExceptionDescribe();
return 1;
}
// startFuzzer expects UTF-8 encoded strings that are not null-terminated.
env.SetByteArrayRegion(arg, 0, len,
reinterpret_cast<const jbyte *>(argv[i].data()));
if (env.ExceptionCheck()) {
env.ExceptionDescribe();
return 1;
}
env.SetObjectArrayElement(args, i, arg);
if (env.ExceptionCheck()) {
env.ExceptionDescribe();
return 1;
}
env.DeleteLocalRef(arg);
}
int res = env.CallStaticIntMethod(runner, startFuzzer, args);
if (env.ExceptionCheck()) {
env.ExceptionDescribe();
return 1;
}
env.DeleteLocalRef(args);
return res;
}
} // namespace
int main(int argc, char **argv) {
gflags::SetUsageMessage(kUsageMessage);
// Disable glog log prefixes to mimic libFuzzer output.
FLAGS_log_prefix = false;
google::InitGoogleLogging(argv[0]);
rules_jni_init(argv[0]);
const auto argv_end = argv + argc;
{
// 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};
std::copy_if(argv, 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);
}
// The potentially modified command line arguments passed to libFuzzer at the
// end of this function.
std::vector<std::string> modified_argv =
std::vector<std::string>(argv, argv_end);
bool spawns_subprocesses = false;
if (std::any_of(argv, argv_end, [](std::string_view arg) {
return absl::StartsWith(arg, "-fork=") ||
absl::StartsWith(arg, "-jobs=") ||
absl::StartsWith(arg, "-merge=");
})) {
spawns_subprocesses = true;
if (!FLAGS_coverage_report.empty()) {
LOG(WARNING) << "WARN: --coverage_report does not support parallel "
"fuzzing and has been disabled";
FLAGS_coverage_report = "";
}
if (!FLAGS_coverage_dump.empty()) {
LOG(WARNING) << "WARN: --coverage_dump does not support parallel "
"fuzzing and has been disabled";
FLAGS_coverage_dump = "";
}
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();
modified_argv.emplace_back(
absl::StrFormat("--id_sync_file=%s", FLAGS_id_sync_file));
}
// 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);
}
std::string seed;
// Search for the last occurence of a "-seed" argument as that is the one that
// is used by libFuzzer.
auto seed_pos = std::find_if(
std::reverse_iterator(argv_end), std::reverse_iterator(argv),
[](std::string_view arg) { return absl::StartsWith(arg, "-seed="); });
if (seed_pos != std::reverse_iterator(argv)) {
// An explicit seed has been provided on the command-line, record its value
// so that it can be forwarded to the agent.
seed = absl::StripPrefix(*seed_pos, "-seed=");
} else {
// No explicit seed has been set. Since Jazzer hooks might still want to use
// a seed and we have to ensure that a fuzzing run can be reproduced by
// setting the seed printed by libFuzzer, we generate a seed for it here so
// that the two stay in sync.
unsigned int random_seed = std::random_device()();
seed = std::to_string(random_seed);
// Only add the -seed argument to the command line if not running in a mode
// that spawns subprocesses. These would inherit the same seed, which might
// make them less effective.
if (!spawns_subprocesses) {
modified_argv.emplace_back("-seed=" + seed);
}
}
if (is_asan_active) {
std::cerr << "WARN: Jazzer is not compatible with LeakSanitizer yet. Leaks "
"are not reported."
<< std::endl;
}
return StartLibFuzzer(std::make_unique<jazzer::JVM>(argv[0], seed),
modified_argv);
}