| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/sys/fuzzing/libfuzzer/runner.h" |
| |
| #include <fcntl.h> |
| #include <lib/fdio/spawn.h> |
| #include <lib/fit/defer.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <zircon/status.h> |
| #include <zircon/time.h> |
| |
| #include <filesystem> |
| #include <iostream> |
| #include <sstream> |
| |
| #include <openssl/sha.h> |
| #include <re2/re2.h> |
| |
| #include "src/lib/files/eintr_wrapper.h" |
| #include "src/lib/files/file.h" |
| #include "src/lib/files/path.h" |
| #include "src/sys/fuzzing/common/status.h" |
| |
| namespace fuzzing { |
| namespace { |
| |
| using fuchsia::fuzzer::ProcessStats; |
| |
| const char* kTestInputPath = "/tmp/test_input"; |
| const char* kLiveCorpusPath = "/tmp/live_corpus"; |
| const char* kSeedCorpusPath = "/tmp/seed_corpus"; |
| const char* kTempCorpusPath = "/tmp/temp_corpus"; |
| const char* kDictionaryPath = "/tmp/dictionary"; |
| const char* kResultInputPath = "/tmp/result_input"; |
| |
| constexpr zx_duration_t kOneSecond = ZX_SEC(1); |
| constexpr size_t kOneKb = 1ULL << 10; |
| constexpr size_t kOneMb = 1ULL << 20; |
| |
| // Returns |one| if |original| is non-zero and less than |one|, otherwise returns |original|. |
| template <typename T> |
| T Clamp(T original, const T& one, const char* type, const char* unit, const char* flag) { |
| if (!original) { |
| return 0; |
| } |
| if (original < one) { |
| FX_LOGS(WARNING) << "libFuzzer does not support " << type << "s of less than 1 " << unit |
| << " for '" << flag << "'."; |
| return one; |
| } |
| return original; |
| } |
| |
| // Converts a flag into a libFuzzer command line argument. |
| template <typename T> |
| std::string MakeArg(const std::string& flag, T value) { |
| std::ostringstream oss; |
| oss << "-" << flag << "=" << value; |
| return oss.str(); |
| } |
| |
| // Reads a byte sequence from a file. |
| Input ReadInputFromFile(const std::string& pathname) { |
| std::vector<uint8_t> data; |
| if (!files::ReadFileToVector(pathname, &data)) { |
| FX_LOGS(FATAL) << "Failed to read input from '" << pathname << "': " << strerror(errno); |
| } |
| return Input(data); |
| } |
| |
| // Writes a byte sequence to a file. |
| void WriteInputToFile(const Input& input, const std::string& pathname) { |
| FX_DCHECK(input.size() < std::numeric_limits<ssize_t>::max()); |
| const auto* data = reinterpret_cast<const char*>(input.data()); |
| auto size = static_cast<ssize_t>(input.size()); |
| if (!files::WriteFile(pathname, data, size)) { |
| FX_LOGS(FATAL) << "Failed to write input to '" << pathname << "': " << strerror(errno); |
| } |
| } |
| |
| } // namespace |
| |
| RunnerPtr LibFuzzerRunner::MakePtr(ExecutorPtr executor) { |
| return RunnerPtr(new LibFuzzerRunner(std::move(executor))); |
| } |
| |
| LibFuzzerRunner::LibFuzzerRunner(ExecutorPtr executor) |
| : Runner(executor), |
| close_([this]() { CloseImpl(); }), |
| interrupt_([this]() { InterruptImpl(); }), |
| join_([this]() { JoinImpl(); }) { |
| FX_CHECK(std::filesystem::create_directory(kSeedCorpusPath)); |
| FX_CHECK(std::filesystem::create_directory(kLiveCorpusPath)); |
| } |
| |
| LibFuzzerRunner::~LibFuzzerRunner() { |
| close_.Run(); |
| interrupt_.Run(); |
| join_.Run(); |
| } |
| |
| void LibFuzzerRunner::AddDefaults(Options* options) { |
| if (!options->has_detect_exits()) { |
| options->set_detect_exits(true); |
| } |
| } |
| |
| void LibFuzzerRunner::ConfigureImpl(const OptionsPtr& options) { options_ = options; } |
| |
| /////////////////////////////////////////////////////////////// |
| // Corpus-related methods. |
| |
| zx_status_t LibFuzzerRunner::AddToCorpus(CorpusType corpus_type, Input input) { |
| uint8_t digest[SHA_DIGEST_LENGTH]; |
| SHA1(input.data(), input.size(), digest); |
| std::ostringstream filename; |
| filename << std::hex; |
| for (size_t i = 0; i < SHA_DIGEST_LENGTH; ++i) { |
| filename << std::setw(2) << std::setfill('0') << size_t(digest[i]); |
| } |
| std::ostringstream pathname; |
| switch (corpus_type) { |
| case CorpusType::SEED: |
| pathname << kSeedCorpusPath << "/" << filename.str(); |
| seed_corpus_.push_back(filename.str()); |
| break; |
| case CorpusType::LIVE: |
| pathname << kLiveCorpusPath << "/" << filename.str(); |
| live_corpus_.push_back(filename.str()); |
| break; |
| default: |
| return ZX_ERR_INVALID_ARGS; |
| } |
| WriteInputToFile(std::move(input), pathname.str()); |
| return ZX_OK; |
| } |
| |
| Input LibFuzzerRunner::ReadFromCorpus(CorpusType corpus_type, size_t offset) { |
| switch (corpus_type) { |
| case CorpusType::SEED: |
| if (offset < seed_corpus_.size()) { |
| return ReadInputFromFile(files::JoinPath(kSeedCorpusPath, seed_corpus_[offset])); |
| } |
| break; |
| case CorpusType::LIVE: |
| if (offset < live_corpus_.size()) { |
| return ReadInputFromFile(files::JoinPath(kLiveCorpusPath, live_corpus_[offset])); |
| } |
| break; |
| default: |
| FX_NOTIMPLEMENTED(); |
| } |
| return Input(); |
| } |
| |
| void LibFuzzerRunner::ReloadLiveCorpus() { |
| live_corpus_.clear(); |
| std::vector<std::string> dups; |
| for (const auto& dir_entry : std::filesystem::directory_iterator(kLiveCorpusPath)) { |
| auto filename = dir_entry.path().filename().string(); |
| if (files::IsFile(files::JoinPath(kSeedCorpusPath, filename))) { |
| dups.push_back(files::JoinPath(kLiveCorpusPath, filename)); |
| } else { |
| live_corpus_.push_back(std::move(filename)); |
| } |
| } |
| for (const auto& dup_path : dups) { |
| std::filesystem::remove(dup_path); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////// |
| // Dictionary-related methods. |
| |
| zx_status_t LibFuzzerRunner::ParseDictionary(const Input& input) { |
| WriteInputToFile(input, kDictionaryPath); |
| has_dictionary_ = true; |
| return ZX_OK; |
| } |
| |
| Input LibFuzzerRunner::GetDictionaryAsInput() const { |
| return has_dictionary_ ? ReadInputFromFile(kDictionaryPath) : Input(); |
| } |
| |
| /////////////////////////////////////////////////////////////// |
| // Synchronous workflows. |
| |
| zx_status_t LibFuzzerRunner::SyncExecute(const Input& input) { |
| set_result_input(input); |
| auto args = MakeArgs(); |
| auto test_input = kTestInputPath; |
| WriteInputToFile(input, test_input); |
| args.push_back(test_input); |
| return Spawn(args); |
| } |
| |
| zx_status_t LibFuzzerRunner::SyncMinimize(const Input& input) { |
| set_result_input(input); |
| auto args = MakeArgs(); |
| WriteInputToFile(input, kTestInputPath); |
| args.push_back("-minimize_crash=1"); |
| args.push_back(kTestInputPath); |
| minimized_ = false; |
| return Spawn(args); |
| } |
| |
| zx_status_t LibFuzzerRunner::SyncCleanse(const Input& input) { |
| set_result_input(input); |
| auto args = MakeArgs(); |
| WriteInputToFile(input, kTestInputPath); |
| args.push_back("-cleanse_crash=1"); |
| args.push_back(kTestInputPath); |
| return Spawn(args); |
| } |
| |
| zx_status_t LibFuzzerRunner::SyncFuzz() { |
| auto reload = fit::defer([this]() { ReloadLiveCorpus(); }); |
| auto args = MakeArgs(); |
| args.push_back(kLiveCorpusPath); |
| args.push_back(kSeedCorpusPath); |
| return Spawn(args); |
| } |
| |
| zx_status_t LibFuzzerRunner::SyncMerge() { |
| std::filesystem::create_directory(kTempCorpusPath); |
| auto args = MakeArgs(); |
| args.push_back("-merge=1"); |
| args.push_back(kTempCorpusPath); |
| args.push_back(kSeedCorpusPath); |
| args.push_back(kLiveCorpusPath); |
| auto status = Spawn(args); |
| if (status != ZX_OK) { |
| std::filesystem::remove_all(kTempCorpusPath); |
| return status; |
| } |
| std::filesystem::remove_all(kLiveCorpusPath); |
| std::filesystem::rename(kTempCorpusPath, kLiveCorpusPath); |
| ReloadLiveCorpus(); |
| return ZX_OK; |
| } |
| |
| /////////////////////////////////////////////////////////////// |
| // Spawn-related methods. |
| |
| std::vector<std::string> LibFuzzerRunner::MakeArgs() { |
| std::vector<std::string> args; |
| auto cmdline_iter = cmdline_.begin(); |
| while (cmdline_iter != cmdline_.end() && *cmdline_iter != "--") { |
| args.push_back(*cmdline_iter++); |
| } |
| if (options_->has_runs()) { |
| args.push_back(MakeArg("runs", options_->runs())); |
| } |
| if (options_->has_max_total_time()) { |
| auto max_total_time = options_->max_total_time(); |
| max_total_time = Clamp(max_total_time, kOneSecond, "duration", "second", "max_total_time"); |
| options_->set_max_total_time(max_total_time); |
| args.push_back(MakeArg("max_total_time", max_total_time / kOneSecond)); |
| } |
| if (options_->has_seed()) { |
| args.push_back(MakeArg("seed", options_->seed())); |
| } |
| if (options_->has_max_input_size()) { |
| args.push_back(MakeArg("max_len", options_->max_input_size())); |
| } |
| if (options_->has_mutation_depth()) { |
| args.push_back(MakeArg("mutate_depth", options_->mutation_depth())); |
| } |
| if (options_->has_dictionary_level()) { |
| FX_LOGS(WARNING) << "libFuzzer does not support setting the dictionary level."; |
| } |
| if (options_->has_detect_exits() && !options_->detect_exits()) { |
| FX_LOGS(WARNING) << "libFuzzer does not support ignoring process exits."; |
| } |
| if (options_->has_detect_leaks()) { |
| args.push_back(MakeArg("detect_leaks", options_->detect_leaks() ? "1" : "0")); |
| } |
| if (options_->has_run_limit()) { |
| auto run_limit = options_->run_limit(); |
| run_limit = Clamp(run_limit, kOneSecond, "duration", "second", "run_limit"); |
| options_->set_run_limit(run_limit); |
| args.push_back(MakeArg("timeout", run_limit / kOneSecond)); |
| } |
| if (options_->has_malloc_limit()) { |
| auto malloc_limit = options_->malloc_limit(); |
| malloc_limit = Clamp(malloc_limit, kOneMb, "memory amount", "MB", "malloc_limit"); |
| options_->set_malloc_limit(malloc_limit); |
| args.push_back(MakeArg("malloc_limit_mb", malloc_limit / kOneMb)); |
| } |
| if (options_->has_oom_limit()) { |
| auto oom_limit = options_->oom_limit(); |
| oom_limit = Clamp(oom_limit, kOneMb, "memory amount", "MB", "oom_limit"); |
| options_->set_oom_limit(oom_limit); |
| args.push_back(MakeArg("rss_limit_mb", oom_limit / kOneMb)); |
| } |
| if (options_->has_purge_interval()) { |
| auto purge_interval = options_->purge_interval(); |
| purge_interval = Clamp(purge_interval, kOneSecond, "duration", "second", "purge_interval"); |
| options_->set_purge_interval(purge_interval); |
| args.push_back(MakeArg("purge_allocator_interval", purge_interval / kOneSecond)); |
| } |
| if (options_->has_malloc_exitcode()) { |
| FX_LOGS(WARNING) << "libFuzzer does not support setting the 'malloc_exitcode'."; |
| } |
| if (options_->has_death_exitcode()) { |
| FX_LOGS(WARNING) << "libFuzzer does not support setting the 'death_exitcode'."; |
| } |
| if (options_->has_leak_exitcode()) { |
| FX_LOGS(WARNING) << "libFuzzer does not support setting the 'leak_exitcode'."; |
| } |
| if (options_->has_oom_exitcode()) { |
| FX_LOGS(WARNING) << "libFuzzer does not support setting the 'oom_exitcode'."; |
| } |
| if (options_->has_pulse_interval()) { |
| FX_LOGS(WARNING) << "libFuzzer does not support setting the 'pulse_interval'."; |
| } |
| if (has_dictionary_) { |
| args.push_back(MakeArg("dict", kDictionaryPath)); |
| } |
| std::filesystem::remove(kResultInputPath); |
| args.push_back(MakeArg("exact_artifact_path", kResultInputPath)); |
| while (cmdline_iter != cmdline_.end()) { |
| args.push_back(*cmdline_iter++); |
| } |
| return args; |
| } |
| |
| std::vector<fdio_spawn_action_t> LibFuzzerRunner::MakeSpawnActions() { |
| int fds[2]; |
| std::vector<fdio_spawn_action_t> spawn_actions; |
| |
| // Standard input. |
| if (pipe(fds) != 0) { |
| FX_LOGS(FATAL) << "Failed to pipe stdin to libfuzzer " << strerror(errno); |
| } |
| piped_stdin_ = fds[1]; |
| spawn_actions.push_back({ |
| .action = FDIO_SPAWN_ACTION_TRANSFER_FD, |
| .fd = |
| { |
| .local_fd = fds[0], |
| .target_fd = STDIN_FILENO, |
| }, |
| }); |
| |
| // Standard output. |
| spawn_actions.push_back({ |
| .action = FDIO_SPAWN_ACTION_CLONE_FD, |
| .fd = |
| { |
| .local_fd = STDOUT_FILENO, |
| .target_fd = STDOUT_FILENO, |
| }, |
| }); |
| |
| // Standard error. |
| if (pipe(fds) != 0) { |
| FX_LOGS(FATAL) << "Failed to pipe stderr from libfuzzer " << strerror(errno); |
| } |
| piped_stderr_ = fds[0]; |
| spawn_actions.push_back({ |
| .action = FDIO_SPAWN_ACTION_TRANSFER_FD, |
| .fd = |
| { |
| .local_fd = fds[1], |
| .target_fd = STDERR_FILENO, |
| }, |
| }); |
| |
| return spawn_actions; |
| } |
| |
| zx_status_t LibFuzzerRunner::Spawn(const std::vector<std::string>& args) { |
| std::vector<const char*> argv; |
| for (const auto& arg : args) { |
| argv.push_back(arg.c_str()); |
| } |
| argv.push_back(nullptr); |
| auto spawn_actions = MakeSpawnActions(); |
| char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; |
| if (verbose_) { |
| for (const auto& arg : args) { |
| std::cerr << arg << " "; |
| } |
| std::cerr << std::endl; |
| } |
| zx_status_t status; |
| { |
| std::lock_guard<std::mutex> lock(mutex_); |
| status = stopping_ ? ZX_ERR_BAD_STATE |
| : fdio_spawn_etc( |
| ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL & (~FDIO_SPAWN_CLONE_STDIO), |
| argv[0], &argv[0], nullptr, spawn_actions.size(), spawn_actions.data(), |
| subprocess_.reset_and_get_address(), err_msg); |
| } |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to spawn libfuzzer: " << err_msg; |
| return status; |
| } |
| ParseStderr(); |
| Waiter waiter = [this](zx::time deadline) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| auto status = subprocess_.wait_one(ZX_TASK_TERMINATED, deadline, nullptr); |
| subprocess_.reset(); |
| return status; |
| }; |
| status = WaitFor("process to terminate", &waiter); |
| UpdateMonitors(UpdateReason::DONE); |
| FX_DCHECK(status == ZX_OK) << zx_status_get_string(status); |
| close(piped_stdin_); |
| piped_stdin_ = -1; |
| close(piped_stderr_); |
| piped_stderr_ = -1; |
| // Collect results from the filesystem. |
| if (files::IsFile(kResultInputPath)) { |
| set_result_input(ReadInputFromFile(kResultInputPath)); |
| } |
| return error_; |
| } |
| |
| void LibFuzzerRunner::ClearErrors() { error_ = ZX_OK; } |
| |
| /////////////////////////////////////////////////////////////// |
| // Status-related methods. |
| |
| void LibFuzzerRunner::ParseStderr() { |
| char buf[kOneKb]; |
| auto first = std::begin(buf); |
| auto end = first; |
| size_t bytes_read = 0; |
| Waiter waiter = [this, &buf, &end, &bytes_read](zx::time deadline) { |
| auto now = zx::clock::get_monotonic(); |
| if (now >= deadline) { |
| return ZX_ERR_TIMED_OUT; |
| } |
| auto duration = deadline - now; |
| auto seconds = duration.to_secs(); |
| auto useconds = (duration % ZX_SEC(1)).to_usecs(); |
| struct timeval timeout { |
| .tv_sec = static_cast<time_t>(std::min(seconds, std::numeric_limits<time_t>::max())), |
| .tv_usec = static_cast<suseconds_t>(useconds), |
| }; |
| fd_set fds; |
| FD_ZERO(&fds); |
| FD_SET(piped_stderr_, &fds); |
| auto num_fds = HANDLE_EINTR(select(piped_stderr_ + 1, &fds, nullptr, nullptr, &timeout)); |
| if (num_fds < 0) { |
| FX_LOGS(ERROR) << "select: " << strerror(errno); |
| return ZX_ERR_IO; |
| } |
| if (num_fds == 0) { |
| return ZX_ERR_TIMED_OUT; |
| } |
| bytes_read = HANDLE_EINTR(read(piped_stderr_, &*end, std::end(buf) - end)); |
| return ZX_OK; |
| }; |
| while (WaitFor("stderr from spawned process", &waiter) == ZX_OK) { |
| if (bytes_read < 0) { |
| FX_LOGS(ERROR) << "Failed to read stderr from libfuzzer: " << strerror(errno); |
| } |
| if (bytes_read <= 0) { |
| break; |
| } |
| end += bytes_read; |
| for (auto last = first; (last = std::find(first, end, '\n')) != end; first = last + 1) { |
| *last = '\0'; |
| auto line = std::string_view(first, last - first); |
| if (!ParseStatus(line)) { |
| ParseMessage(line); |
| } |
| if (verbose_ && !line.empty()) { |
| std::cerr << line << std::endl; |
| } |
| } |
| if (first != std::begin(buf)) { |
| memmove(buf, &*first, end - first); |
| end -= first - std::begin(buf); |
| first = std::begin(buf); |
| } else if (end == std::end(buf)) { |
| FX_LOGS(WARNING) << "a single log line exceeds " << sizeof(buf) << " characters; skipping..."; |
| end = first; |
| } |
| } |
| } |
| |
| bool LibFuzzerRunner::ParseStatus(const std::string_view& line) { |
| // The patterns in this function should match libFuzzer's |Fuzzer::PrintStats|. |
| re2::StringPiece input(line); |
| |
| // Parse runs. |
| uint32_t runs; |
| if (!re2::RE2::Consume(&input, "#(\\d+)", &runs)) { |
| return false; |
| } |
| status_.set_runs(runs); |
| |
| // Parse reason. |
| std::string reason_str; |
| if (!re2::RE2::Consume(&input, "\\t(\\S+)", &reason_str)) { |
| return false; |
| } |
| auto reason = UpdateReason::PULSE; // By default, assume it's just a status update. |
| if (reason_str == "INITED") { |
| status_.set_running(true); |
| start_ = zx::clock::get_monotonic(); |
| reason = UpdateReason::INIT; |
| } else if (reason_str == "NEW") { |
| reason = UpdateReason::NEW; |
| } else if (reason_str == "REDUCE") { |
| reason = UpdateReason::REDUCE; |
| } else if (reason_str == "DONE") { |
| set_result(FuzzResult::NO_ERRORS); |
| status_.set_running(false); |
| reason = UpdateReason::DONE; |
| } |
| |
| // Parse covered PCs. |
| size_t covered_pcs; |
| if (re2::RE2::FindAndConsume(&input, "cov: (\\d+)", &covered_pcs)) { |
| status_.set_covered_pcs(covered_pcs); |
| } |
| |
| // Parse covered features. |
| size_t covered_features; |
| if (re2::RE2::FindAndConsume(&input, "ft: (\\d+)", &covered_features)) { |
| status_.set_covered_features(covered_features); |
| } |
| |
| // Parse corpus stats. |
| size_t corpus_num_inputs; |
| if (re2::RE2::FindAndConsume(&input, "corp: (\\d+)", &corpus_num_inputs)) { |
| size_t corpus_total_size; |
| status_.set_corpus_num_inputs(corpus_num_inputs); |
| if (re2::RE2::Consume(&input, "/(\\d+)b", &corpus_total_size)) { |
| status_.set_corpus_total_size(corpus_total_size); |
| } else if (re2::RE2::Consume(&input, "/(\\d+)Kb", &corpus_total_size)) { |
| status_.set_corpus_total_size(corpus_total_size * kOneKb); |
| } else if (re2::RE2::Consume(&input, "/(\\d+)Mb", &corpus_total_size)) { |
| status_.set_corpus_total_size(corpus_total_size * kOneMb); |
| } |
| } |
| |
| // Add other stats. |
| auto elapsed = zx::clock::get_monotonic() - start_; |
| status_.set_elapsed(elapsed.to_nsecs()); |
| |
| std::vector<ProcessStats> process_stats; |
| ProcessStats stats; |
| { |
| std::lock_guard<std::mutex> lock(mutex_); |
| if (subprocess_ && GetStatsForProcess(subprocess_, &stats) == ZX_OK) { |
| process_stats.push_back(std::move(stats)); |
| } |
| } |
| status_.set_process_stats(std::move(process_stats)); |
| |
| UpdateMonitors(reason); |
| return true; |
| } |
| |
| void LibFuzzerRunner::ParseMessage(const std::string_view& line) { |
| re2::StringPiece input(line); |
| |
| // Parse error messages. |
| if (re2::RE2::Consume(&input, "==\\d+== ERROR: libFuzzer: ")) { |
| if (re2::RE2::PartialMatch(input, "fuzz target exited")) { |
| // See libFuzzer's |Fuzzer::ExitCallback|. |
| set_result(FuzzResult::EXIT); |
| } else if (re2::RE2::PartialMatch(input, "deadly signal")) { |
| // See libFuzzer's |Fuzzer::CrashCallback|. |
| set_result(FuzzResult::CRASH); |
| } else if (re2::RE2::PartialMatch(input, "timeout after \\d+ seconds")) { |
| // See libFuzzer's |Fuzzer::AlarmCallback|. |
| set_result(FuzzResult::TIMEOUT); |
| } else if (re2::RE2::PartialMatch(input, "out-of-memory \\(malloc\\(-?\\d+\\)\\)")) { |
| // See libFuzzer's |Fuzzer::HandleMalloc|. |
| set_result(FuzzResult::BAD_MALLOC); |
| } else if (re2::RE2::PartialMatch(input, "out-of-memory \\(used: \\d+Mb; limit: \\d+Mb\\)")) { |
| // See libFuzzer's |Fuzzer::RssLimitCallback|. |
| set_result(FuzzResult::OOM); |
| } else { |
| // See libFuzzer's |Fuzzer::DeathCallback|. |
| set_result(FuzzResult::DEATH); |
| } |
| return; |
| } |
| |
| // See libFuzzer's |Fuzzer::TryDetectingAMemoryLeak|. |
| // This match is ugly, but it's the only message in current libFuzzer we can rely on for a leak. |
| if (line == "INFO: to ignore leaks on libFuzzer side use -detect_leaks=0.") { |
| set_result(FuzzResult::LEAK); |
| return; |
| } |
| |
| // See libFuzzer's |Fuzzer::MinimizeCrashInput|. |
| // Annoyingly, libFuzzer prints the same "error" message for an invalid input and a minimize |
| // loop that no longer triggers an error (see below). Use this diagnostic to distinguish. |
| if (re2::RE2::PartialMatch( |
| input, |
| "CRASH_MIN: '\\S+' \\(\\d+ bytes\\) caused a crash. Will try to minimize it further")) { |
| minimized_ = true; |
| return; |
| } |
| |
| // See libFuzzer's |Fuzzer::MinimizeCrashInput|. |
| if (re2::RE2::PartialMatch(input, "ERROR: the input \\S+ did not crash")) { |
| if (!minimized_) { |
| FX_LOGS(WARNING) << "Test input did not trigger an error."; |
| error_ = ZX_ERR_INVALID_ARGS; |
| } |
| return; |
| } |
| } |
| |
| Status LibFuzzerRunner::CollectStatus() { |
| // For libFuzzer, we return the most recently parsed status rather than point-in-time status. |
| return CopyStatus(status_); |
| } |
| |
| /////////////////////////////////////////////////////////////// |
| // Stop-related methods. |
| |
| void LibFuzzerRunner::CloseImpl() { |
| Runner::Close(); |
| std::lock_guard<std::mutex> lock(mutex_); |
| stopping_ = true; |
| } |
| |
| void LibFuzzerRunner::InterruptImpl() { |
| Runner::Interrupt(); |
| std::lock_guard<std::mutex> lock(mutex_); |
| // TODO(fxbug.dev/87155): If libFuzzer-for-Fuchsia watches for something sent to stdin in order to |
| // call its |Fuzzer::StaticInterruptCallback|, we could ask libFuzzer to shut itself down. This |
| // would guarantee we get all of its output. |
| if (subprocess_) { |
| subprocess_.kill(); |
| } |
| } |
| |
| void LibFuzzerRunner::JoinImpl() { Runner::Join(); } |
| |
| } // namespace fuzzing |