| // 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/framework/engine/runner.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/clock.h> |
| #include <zircon/sanitizer.h> |
| #include <zircon/status.h> |
| |
| #include <deque> |
| |
| #include "src/lib/fxl/macros.h" |
| |
| namespace fuzzing { |
| namespace { |
| |
| using ::fuchsia::fuzzer::MAX_PROCESS_STATS; |
| |
| // This struct can be used with |std::sort| to sort inputs according to smallest first, then most |
| // features. |
| struct InputComparator { |
| inline bool operator()(const Input& input1, const Input& input2) { |
| return (input1.size() < input2.size()) || |
| (input1.size() == input2.size() && (input1.num_features() > input2.num_features())); |
| } |
| }; |
| |
| const uintptr_t kTimeout = std::numeric_limits<uintptr_t>::max(); |
| |
| } // namespace |
| |
| RunnerImpl::RunnerImpl() { |
| timer_ = std::thread([this]() { Timer(); }); |
| seed_corpus_ = std::make_shared<Corpus>(); |
| live_corpus_ = std::make_shared<Corpus>(); |
| pool_ = std::make_shared<ModulePool>(); |
| } |
| |
| RunnerImpl::~RunnerImpl() { |
| run_deadline_ = zx::time::infinite_past(); |
| sync_completion_signal(&timer_sync_); |
| timer_.join(); |
| } |
| |
| void RunnerImpl::AddDefaults(Options* options) { |
| Corpus::AddDefaults(options); |
| Mutagen::AddDefaults(options); |
| ProcessProxyImpl::AddDefaults(options); |
| if (!options->has_runs()) { |
| options->set_runs(kDefaultRuns); |
| } |
| if (!options->has_max_total_time()) { |
| options->set_max_total_time(kDefaultMaxTotalTime); |
| } |
| if (!options->has_max_input_size()) { |
| options->set_max_input_size(kDefaultMaxInputSize); |
| } |
| if (!options->has_mutation_depth()) { |
| options->set_mutation_depth(kDefaultMutationDepth); |
| } |
| if (!options->has_detect_exits()) { |
| options->set_detect_exits(kDefaultDetectExits); |
| } |
| if (!options->has_detect_leaks()) { |
| options->set_detect_leaks(kDefaultDetectLeaks); |
| } |
| if (!options->has_run_limit()) { |
| options->set_run_limit(kDefaultRunLimit); |
| } |
| if (!options->has_pulse_interval()) { |
| options->set_pulse_interval(kDefaultPulseInterval); |
| } |
| } |
| |
| void RunnerImpl::ConfigureImpl(const std::shared_ptr<Options>& options) { |
| options_ = options; |
| seed_corpus_->Configure(options_); |
| live_corpus_->Configure(options_); |
| mutagen_.Configure(options_); |
| test_input_.Reserve(options_->max_input_size()); |
| } |
| |
| zx_status_t RunnerImpl::AddToCorpus(CorpusType corpus_type, Input input) { |
| switch (corpus_type) { |
| case CorpusType::SEED: |
| seed_corpus_->Add(std::move(input)); |
| break; |
| case CorpusType::LIVE: |
| live_corpus_->Add(std::move(input)); |
| break; |
| default: |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ZX_OK; |
| } |
| |
| Input RunnerImpl::ReadFromCorpus(CorpusType corpus_type, size_t offset) { |
| Input input; |
| switch (corpus_type) { |
| case CorpusType::SEED: |
| seed_corpus_->At(offset, &input); |
| break; |
| case CorpusType::LIVE: |
| live_corpus_->At(offset, &input); |
| break; |
| default: |
| FX_NOTREACHED(); |
| } |
| return input; |
| } |
| |
| zx_status_t RunnerImpl::ParseDictionary(const Input& input) { |
| Dictionary dict; |
| dict.Configure(options_); |
| if (!dict.Parse(input)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| mutagen_.set_dictionary(std::move(dict)); |
| return ZX_OK; |
| } |
| |
| Input RunnerImpl::GetDictionaryAsInput() const { return mutagen_.dictionary().AsInput(); } |
| |
| /////////////////////////////////////////////////////////////// |
| // Synchronous workflows. |
| |
| zx_status_t RunnerImpl::SyncExecute(const Input& input) { |
| auto scope = SyncScope(); |
| TestOne(input); |
| return ZX_OK; |
| } |
| |
| zx_status_t RunnerImpl::SyncMinimize(const Input& input) { |
| auto scope = SyncScope(); |
| TestOne(input); |
| if (result() == Result::NO_ERRORS) { |
| FX_LOGS(WARNING) << "Test input did not trigger an error."; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| auto minimized = result_input(); |
| auto saved_result = result(); |
| auto saved_corpus = live_corpus_; |
| auto saved_options = CopyOptions(*options_); |
| if (!options_->has_runs() && !options_->has_max_total_time()) { |
| FX_LOGS(INFO) << "'max_total_time' and 'runs' are both not set. Defaulting to 10 minutes."; |
| options_->set_max_total_time(zx::min(10).get()); |
| } |
| while (true) { |
| if (minimized.size() < 2) { |
| FX_LOGS(INFO) << "Input is " << minimized.size() << " byte(s); will not minimize further."; |
| break; |
| } |
| auto max_size = minimized.size() - 1; |
| auto next_input = minimized.Duplicate(); |
| next_input.Truncate(max_size); |
| options_->set_max_input_size(max_size); |
| pool_->Clear(); |
| live_corpus_ = std::make_shared<Corpus>(); |
| live_corpus_->Configure(options_); |
| auto status = live_corpus_->Add(std::move(next_input)); |
| FX_DCHECK(status == ZX_OK) << zx_status_get_string(status); |
| // Imitate libFuzzer and count from 0 so long as errors are found. |
| ClearErrors(); |
| run_ = 0; |
| FuzzLoop(); |
| if (result() == Result::NO_ERRORS) { |
| FX_LOGS(INFO) << "Did not reduce error input beyond " << minimized.size() |
| << " bytes; exiting."; |
| break; |
| } |
| // TODO(fxbug.dev/85424): This needs a more rigorous way of deduplicating crashes. |
| if (result() != saved_result) { |
| FX_LOGS(WARNING) << "Different error detected; will not minimize further."; |
| break; |
| } |
| minimized = result_input(); |
| } |
| set_result_input(minimized); |
| pool_->Clear(); |
| live_corpus_ = saved_corpus; |
| *options_ = std::move(saved_options); |
| return ZX_OK; |
| } |
| |
| zx_status_t RunnerImpl::SyncCleanse(const Input& input) { |
| auto scope = SyncScope(); |
| auto cleansed = input.Duplicate(); |
| const std::vector<uint8_t> kClean = {' ', 0xff}; |
| auto clean = kClean.begin(); |
| std::deque<size_t> offsets; |
| for (size_t i = 0; i < input.size(); ++i) { |
| // Record which offsets are replaceable. |
| if (std::find(kClean.begin(), kClean.end(), input.data()[i]) == kClean.end()) { |
| offsets.push_back(i); |
| } |
| } |
| size_t left = offsets.size(); |
| constexpr size_t kMaxCleanseAttempts = 5; |
| size_t tries = kMaxCleanseAttempts; |
| uint8_t orig = 0; |
| bool mod = false; |
| // Try various bytes at various offsets. To match existing engines (i.e. libFuzzer), this code |
| // does not distinguish between different types of errors. |
| FuzzLoopStrict( |
| /* next_input */ |
| [&cleansed, &clean, &kClean, &offsets, &left, &tries, &mod, &orig](bool first) -> Input* { |
| auto* data = cleansed.data(); |
| if (clean == kClean.end()) { |
| clean = kClean.begin(); |
| offsets.push_back(offsets.front()); |
| offsets.pop_front(); |
| --left; |
| } |
| if (!left) { |
| left = offsets.size(); |
| tries = mod ? (tries - 1) : 0; |
| mod = false; |
| } |
| if (tries == 0) { |
| return nullptr; |
| } |
| auto offset = offsets.front(); |
| orig = data[offset]; |
| data[offset] = *clean; |
| return &cleansed; |
| }, |
| /* finish_run */ |
| [this, &cleansed, &clean, &kClean, &offsets, &left, &mod, &orig](Input* ignored) { |
| auto* data = cleansed.data(); |
| if (result() != Result::NO_ERRORS) { |
| ClearErrors(); |
| clean = kClean.begin(); |
| offsets.pop_front(); |
| --left; |
| mod = true; |
| } else { |
| data[offsets.front()] = orig; |
| ++clean; |
| } |
| }, |
| /* ignore_errors */ true); |
| set_result_input(cleansed); |
| return ZX_OK; |
| } |
| |
| zx_status_t RunnerImpl::SyncFuzz() { |
| auto scope = SyncScope(); |
| pool_->Clear(); |
| // Add seed corpus to live corpus. |
| for (size_t offset = 0; offset < seed_corpus_->num_inputs(); ++offset) { |
| Input input; |
| seed_corpus_->At(offset, &input); |
| live_corpus_->Add(std::move(input)); |
| } |
| FuzzLoop(); |
| return ZX_OK; |
| } |
| |
| zx_status_t RunnerImpl::SyncMerge() { |
| auto scope = SyncScope(); |
| // Measure the coverage of the seed corpus. |
| pool_->Clear(); |
| // TODO(fxbug.dev/84364): |FuzzLoopRelaxed| is preferred here and elsewhere in this function, but |
| // using that causes some test flake. Switch to that version once the source of it is resolved. |
| size_t offset = 0; |
| Input input; |
| Input* next_input = &input; |
| FuzzLoopStrict( |
| /* next_input */ |
| [this, &offset, next_input](bool first) { |
| return seed_corpus_->At(offset++, next_input) ? next_input : nullptr; |
| }, |
| /* finish_run */ [this](const Input* last_input) { pool_->Accumulate(); }); |
| if (result() != Result::NO_ERRORS) { |
| FX_LOGS(WARNING) << "Seed corpus input triggered an error."; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // Measure the additional coverage of each input in the live corpus, and sort. |
| std::vector<Input> error_inputs; |
| std::vector<Input> inputs; |
| offset = 0; |
| FuzzLoopStrict( |
| /* next_input */ |
| [this, &offset, next_input](bool first) { |
| return live_corpus_->At(offset++, next_input) ? next_input : nullptr; |
| }, |
| /* finish_run */ |
| [this, &error_inputs, &inputs](Input* last_input) { |
| if (result() != Result::NO_ERRORS) { |
| FX_LOGS(WARNING) << "Corpus contains an input that triggers an error."; |
| error_inputs.push_back(last_input->Duplicate()); |
| ClearErrors(); |
| return; |
| } |
| auto num_features = pool_->Measure(); |
| if (last_input->size() && num_features) { |
| last_input->set_num_features(num_features); |
| inputs.push_back(std::move(*last_input)); |
| } |
| }, |
| /* ignore_errors */ true); |
| std::sort(inputs.begin(), inputs.end(), InputComparator()); |
| |
| // Keep files that add coverage. |
| live_corpus_ = std::make_shared<Corpus>(); |
| live_corpus_->Configure(options_); |
| auto iter = inputs.begin(); |
| FuzzLoopStrict( |
| /* next_input */ [&iter, &inputs]( |
| bool first) { return iter == inputs.end() ? nullptr : &(*iter++); }, |
| /* finish_run*/ |
| [this](Input* last_input) { |
| size_t unique_features = pool_->Accumulate(); |
| if (result() != Result::NO_ERRORS || unique_features) { |
| auto status = live_corpus_->Add(std::move(*last_input)); |
| FX_DCHECK(status == ZX_OK) << zx_status_get_string(status); |
| } |
| }, |
| /* ignore_errors */ true); |
| |
| // Always preserve error inputs. |
| for (auto& input : error_inputs) { |
| live_corpus_->Add(std::move(input)); |
| } |
| return ZX_OK; |
| } |
| |
| /////////////////////////////////////////////////////////////// |
| // Run-related methods. |
| |
| void RunnerImpl::TestOne(const Input& input) { |
| auto dup = input.Duplicate(); |
| FuzzLoopStrict(/* next_input */ [&dup](bool first) { return first ? &dup : nullptr; }, |
| /* finish_run */ [](Input* last_input) {}); |
| } |
| |
| void RunnerImpl::FuzzLoop() { |
| // Use two pre-allocated inputs, and swap the pointers between them each iteration, i.e. the old |
| // |next_input| becomes |prev_input|, and the old |prev_input| is recycled to a new |next_input|. |
| Input inputs[2]; |
| auto* next_input = &inputs[0]; |
| auto* prev_input = &inputs[1]; |
| next_input->Reserve(options_->max_input_size()); |
| prev_input->Reserve(options_->max_input_size()); |
| // TODO(fxbug.dev/84364): |FuzzLoopRelaxed| is preferred here, but using that causes some test |
| // flake. Switch to that version once the source of it is resolved. |
| FuzzLoopStrict(/* next_input */ |
| [this, &next_input, &prev_input](bool first) -> Input* { |
| auto runs = options_->runs(); |
| std::swap(next_input, prev_input); |
| if ((runs != 0 && run_ >= runs) || (zx::clock::get_monotonic() >= deadline_)) { |
| return nullptr; |
| } |
| // Change the input after |options_->mutation_depth()| mutations. Doing so |
| // resets the recorded sequence of mutations. |
| if (first || mutagen_.mutations().size() == options_->mutation_depth()) { |
| mutagen_.reset_mutations(); |
| live_corpus_->Pick(mutagen_.base_input()); |
| live_corpus_->Pick(mutagen_.crossover()); |
| } |
| mutagen_.Mutate(next_input); |
| return next_input; |
| }, |
| /* finish_run */ |
| [this](Input* last_input) { |
| if (pool_->Accumulate()) { |
| live_corpus_->Add(last_input->Duplicate()); |
| UpdateMonitors(UpdateReason::NEW); |
| } else if (zx::clock::get_monotonic() >= next_pulse_) { |
| UpdateMonitors(UpdateReason::PULSE); |
| next_pulse_ = zx::deadline_after(zx::sec(options_->pulse_interval())); |
| } |
| }); |
| } |
| |
| void RunnerImpl::FuzzLoopStrict(fit::function<Input*(bool)> next_input, |
| fit::function<void(Input*)> finish_run, bool ignore_errors) { |
| next_input_ = next_input(/* first */ true); |
| last_input_ = nullptr; |
| // Set initial sync state. |
| sync_completion_signal(&next_input_ready_); |
| sync_completion_reset(&next_input_taken_); |
| sync_completion_signal(&last_input_taken_); |
| sync_completion_reset(&last_input_ready_); |
| auto loop = std::thread([this, ignore_errors]() { RunLoop(ignore_errors); }); |
| while (true) { |
| sync_completion_wait(&last_input_ready_, ZX_TIME_INFINITE); |
| sync_completion_reset(&last_input_ready_); |
| if (stopped_) { |
| break; |
| } |
| // Analyze feedback from inputN |
| auto* last_input = last_input_; |
| sync_completion_signal(&last_input_taken_); |
| finish_run(last_input); |
| sync_completion_wait(&next_input_taken_, ZX_TIME_INFINITE); |
| sync_completion_reset(&next_input_taken_); |
| if (stopped_) { |
| break; |
| } |
| // Generate inputN+1 |
| next_input_ = next_input(/* first */ false); |
| sync_completion_signal(&next_input_ready_); |
| } |
| sync_completion_signal(&last_input_taken_); |
| sync_completion_signal(&next_input_ready_); |
| loop.join(); |
| } |
| |
| void RunnerImpl::FuzzLoopRelaxed(fit::function<Input*(bool)> next_input, |
| fit::function<void(Input*)> finish_run, bool ignore_errors) { |
| next_input_ = next_input(/* first */ true); |
| last_input_ = nullptr; |
| sync_completion_signal(&next_input_ready_); |
| sync_completion_reset(&next_input_taken_); |
| sync_completion_signal(&last_input_taken_); |
| sync_completion_reset(&last_input_ready_); |
| auto loop = std::thread([this, ignore_errors]() { RunLoop(ignore_errors); }); |
| while (true) { |
| sync_completion_wait(&next_input_taken_, ZX_TIME_INFINITE); |
| sync_completion_reset(&next_input_taken_); |
| if (stopped_) { |
| break; |
| } |
| // Generate inputN+1 |
| next_input_ = next_input(/* first */ false); |
| sync_completion_signal(&next_input_ready_); |
| sync_completion_wait(&last_input_ready_, ZX_TIME_INFINITE); |
| sync_completion_reset(&last_input_ready_); |
| if (stopped_) { |
| break; |
| } |
| // Analyze feedback from inputN |
| auto* last_input = last_input_; |
| sync_completion_signal(&last_input_taken_); |
| finish_run(last_input); |
| } |
| sync_completion_signal(&last_input_taken_); |
| sync_completion_signal(&next_input_ready_); |
| loop.join(); |
| } |
| |
| void RunnerImpl::RunLoop(bool ignore_errors) { |
| // Leak detection is expensive, so the strategy is as follows: |
| // 1. Try inputs once without leak detection. |
| // 2. If leak detection is requested, check if leaks are suspected (unbalanced malloc/frees). |
| // 3. If a leak if suspected, do the normal feedback analysis and then try the input again, this |
| // time with leak detection. Skip the feedback analysis on the second try. |
| // 4. Keep track of how many suspected leaks don't result in an error. After |
| // |kMaxLeakDetectionAttempts|, disable further leak detection. |
| constexpr size_t kMaxLeakDetectionAttempts = 1000; |
| bool detect_leaks = false; |
| size_t leak_detection_attempts = options_->detect_leaks() ? kMaxLeakDetectionAttempts : 0; |
| |
| Input* test_input = nullptr; |
| stopped_ = false; |
| while (!stopped_) { |
| bool has_error = false; |
| // Signal proxies that a run is about to begin. |
| { |
| std::lock_guard<std::mutex> lock(mutex_); |
| pending_proxy_signals_ = proxies_.size(); |
| ResetSyncIfNoPendingError(&process_sync_); |
| for (auto& proxy : proxies_) { |
| proxy->Start(detect_leaks); |
| } |
| } |
| // Wait for the next input to be ready. If attempting to detect a leak, use the previous input. |
| if (!detect_leaks) { |
| sync_completion_wait(&next_input_ready_, ZX_TIME_INFINITE); |
| sync_completion_reset(&next_input_ready_); |
| // Get the next input, if there is one. |
| test_input = next_input_; |
| if (test_input) { |
| sync_completion_signal(&next_input_taken_); |
| } |
| } |
| // Wait for proxies to respond. |
| while (pending_proxy_signals_ != 0) { |
| sync_completion_wait(&process_sync_, ZX_TIME_INFINITE); |
| has_error |= HasError(test_input); |
| } |
| if (has_error && !ignore_errors) { |
| // Encounting an error before this point suggests the individual fuzzer may be |
| // non-deterministic and/or non-hermetic and should be improved. |
| FX_LOGS(WARNING) << "Detected error between fuzzing runs."; |
| break; |
| } |
| // Start the fuzzing run by telling the target adapter that the test input is ready. |
| if (!test_input) { |
| break; |
| } |
| test_input_.Clear(); |
| test_input_.Write(test_input->data(), test_input->size()); |
| ++run_; |
| if (!coordinator_.is_valid()) { |
| ConnectTargetAdapter(); |
| } |
| ResetSyncIfNoPendingError(&adapter_sync_); |
| coordinator_.SignalPeer(kStart); |
| ResetTimer(); |
| // Wait for the adapter to signal the run is complete. |
| sync_completion_wait(&adapter_sync_, ZX_TIME_INFINITE); |
| has_error = HasError(test_input); |
| // Signal proxies that a run has ended. |
| { |
| std::lock_guard<std::mutex> lock(mutex_); |
| pending_proxy_signals_ = proxies_.size(); |
| ResetSyncIfNoPendingError(&process_sync_); |
| for (auto& proxy : proxies_) { |
| proxy->Finish(); |
| } |
| } |
| // Wait for proxies to respond. |
| while (pending_proxy_signals_ != 0) { |
| sync_completion_wait(&process_sync_, ZX_TIME_INFINITE); |
| has_error |= HasError(test_input); |
| } |
| if (has_error && !ignore_errors) { |
| break; |
| } |
| if (detect_leaks) { |
| // This is a second try, with leak detection. |
| --leak_detection_attempts; |
| if (leak_detection_attempts == 0) { |
| FX_LOGS(INFO) << "Disabling leak detection: No memory leaks were found in any of " |
| << kMaxLeakDetectionAttempts << " inputs suspected of leaking. " |
| << "Memory may be accumulating in some global state without leaking. " |
| << "End-of-process leak checks will still be performed."; |
| } |
| detect_leaks = false; |
| // Skip feedback analysis; this was already done on the first try. |
| continue; |
| } |
| if (leak_detection_attempts && !detect_leaks) { |
| // This is a first try, and leak detection is requested. |
| std::lock_guard<std::mutex> lock(mutex_); |
| for (auto& proxy : proxies_) { |
| detect_leaks |= proxy->leak_suspected(); |
| } |
| } |
| // Inform the worker that it can analyze the feedback from last input now. |
| sync_completion_wait(&last_input_taken_, ZX_TIME_INFINITE); |
| sync_completion_reset(&last_input_taken_); |
| last_input_ = test_input; |
| sync_completion_signal(&last_input_ready_); |
| } |
| stopped_ = true; |
| sync_completion_signal(&next_input_taken_); |
| sync_completion_signal(&last_input_ready_); |
| } |
| |
| void RunnerImpl::ClearErrors() { |
| Runner::ClearErrors(); |
| error_ = 0; |
| sync_completion_reset(&adapter_sync_); |
| sync_completion_reset(&process_sync_); |
| } |
| |
| /////////////////////////////////////////////////////////////// |
| // Signalling-related methods. |
| |
| void RunnerImpl::SetTargetAdapterHandler(fidl::InterfaceRequestHandler<TargetAdapter> handler) { |
| target_adapter_handler_ = std::move(handler); |
| coordinator_.Reset(); |
| } |
| |
| fidl::InterfaceRequestHandler<ProcessProxy> RunnerImpl::GetProcessProxyHandler( |
| const std::shared_ptr<Dispatcher>& dispatcher) { |
| return [this, dispatcher](fidl::InterfaceRequest<ProcessProxy> request) { |
| auto proxy = std::make_unique<ProcessProxyImpl>(dispatcher, pool_); |
| proxy->Bind(std::move(request)); |
| proxy->Configure(options_); |
| { |
| std::lock_guard<std::mutex> lock(mutex_); |
| proxy->SetHandlers([this]() { OnSignal(); }, |
| [this](ProcessProxyImpl* exited) { |
| auto error = reinterpret_cast<uintptr_t>(exited); |
| OnError(error); |
| }); |
| proxies_.push_back(std::move(proxy)); |
| } |
| }; |
| } |
| |
| void RunnerImpl::ConnectTargetAdapter() { |
| FX_DCHECK(target_adapter_handler_); |
| target_adapter_handler_(target_adapter_.NewRequest()); |
| auto eventpair = coordinator_.Create([this](zx_signals_t observed) { |
| sync_completion_signal(&adapter_sync_); |
| // The only signal we expected to receive from the target adapter is |kFinish| after each run. |
| return observed == kFinish; |
| }); |
| auto status = target_adapter_->Connect(std::move(eventpair), test_input_.Share()); |
| FX_DCHECK(status == ZX_OK) << zx_status_get_string(status); |
| } |
| |
| bool RunnerImpl::OnSignal() { |
| // "Normal" signals are received in response to signals sent to start or finish a run. |RunLoop| |
| // keeps track of how many of these signals are sent using |pending_proxy_signals_|. |
| auto pending = pending_proxy_signals_.fetch_sub(1); |
| FX_DCHECK(pending); |
| if (pending == 1) { |
| sync_completion_signal(&process_sync_); |
| } |
| return true; |
| } |
| |
| void RunnerImpl::OnError(uintptr_t error) { |
| // Only the first proxy to detect an error awakens the |RunLoop|. Subsequent errors are dropped. |
| uintptr_t expected = 0; |
| if (error_.compare_exchange_strong(expected, error)) { |
| sync_completion_signal(&adapter_sync_); |
| sync_completion_signal(&process_sync_); |
| } |
| } |
| |
| void RunnerImpl::ResetSyncIfNoPendingError(sync_completion_t* sync) { |
| // Avoid race by resetting then "unresetting", i.e. signalling, if there's a pending error. |
| sync_completion_reset(sync); |
| if (error_.load()) { |
| sync_completion_signal(sync); |
| } |
| } |
| |
| bool RunnerImpl::HasError(const Input* last_input) { |
| auto error = error_.load(); |
| if (!error) { |
| return false; |
| } |
| std::lock_guard<std::mutex> lock(mutex_); |
| if (error != kTimeout) { |
| // Almost every error causes the process to exit... |
| auto* exited = reinterpret_cast<ProcessProxyImpl*>(error); |
| set_result(exited->Join()); |
| } else { |
| /// .. except for timeouts. |
| set_result(Result::TIMEOUT); |
| constexpr size_t kBufSize = 1ULL << 20; |
| auto buf = std::make_unique<char[]>(kBufSize); |
| for (auto& proxy : proxies_) { |
| auto len = proxy->Dump(buf.get(), kBufSize); |
| __sanitizer_log_write(buf.get(), len); |
| } |
| } |
| // If it's an ignored exit(),just remove that one proxy and treat it like a signal. |
| if (result() == Result::EXIT && !options_->detect_exits()) { |
| auto exited = reinterpret_cast<ProcessProxyImpl*>(error); |
| proxies_.erase(std::remove_if(proxies_.begin(), proxies_.end(), |
| [exited](const std::unique_ptr<ProcessProxyImpl>& proxy) { |
| return proxy.get() == exited; |
| }), |
| proxies_.end()); |
| ClearErrors(); |
| if (pending_proxy_signals_) { |
| OnSignal(); |
| } |
| return false; |
| } |
| // Otherwise, it's really an error. Remove the target adapter and all proxies. |
| coordinator_.Reset(); |
| proxies_.clear(); |
| if (last_input) { |
| set_result_input(*last_input); |
| } |
| error_ = 0; |
| return true; |
| } |
| |
| /////////////////////////////////////////////////////////////// |
| // Timer methods. See also |SyncScope| below. |
| |
| void RunnerImpl::ResetTimer() { |
| auto run_limit = zx::duration(options_->run_limit()); |
| run_deadline_ = run_limit.get() ? zx::deadline_after(run_limit) : zx::time::infinite(); |
| sync_completion_signal(&timer_sync_); |
| } |
| |
| void RunnerImpl::Timer() { |
| while (run_deadline_ != zx::time::infinite_past()) { |
| if (run_deadline_ < zx::clock::get_monotonic()) { |
| OnError(kTimeout); |
| sync_completion_wait(&timer_sync_, ZX_TIME_INFINITE); |
| } else { |
| sync_completion_wait_deadline(&timer_sync_, run_deadline_.get()); |
| } |
| sync_completion_reset(&timer_sync_); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////// |
| // Status-related methods. |
| |
| fit::deferred_action<fit::closure> RunnerImpl::SyncScope() { |
| ClearErrors(); |
| run_ = 0; |
| auto max_time = zx::duration(options_->max_total_time()); |
| start_ = zx::clock::get_monotonic(); |
| deadline_ = max_time.get() ? zx::deadline_after(max_time) : zx::time::infinite(); |
| ResetTimer(); |
| stopped_ = false; |
| UpdateMonitors(UpdateReason::INIT); |
| return fit::defer<fit::closure>([this]() { |
| run_deadline_ = zx::time::infinite(); |
| sync_completion_signal(&timer_sync_); |
| stopped_ = true; |
| UpdateMonitors(UpdateReason::DONE); |
| }); |
| } |
| |
| Status RunnerImpl::CollectStatusLocked() { |
| Status status; |
| status.set_running(!stopped_); |
| status.set_runs(run_); |
| |
| auto elapsed = zx::clock::get_monotonic() - start_; |
| status.set_elapsed(elapsed.to_nsecs()); |
| |
| size_t covered_features; |
| auto covered_pcs = pool_->GetCoverage(&covered_features); |
| status.set_covered_pcs(covered_pcs); |
| status.set_covered_features(covered_features); |
| |
| status.set_corpus_num_inputs(seed_corpus_->num_inputs() + live_corpus_->num_inputs()); |
| status.set_corpus_total_size(seed_corpus_->total_size() + live_corpus_->total_size()); |
| |
| std::vector<ProcessStats> all_stats; |
| all_stats.reserve(std::min<size_t>(proxies_.size(), MAX_PROCESS_STATS)); |
| for (auto& proxy : proxies_) { |
| if (all_stats.size() == all_stats.capacity()) { |
| break; |
| } |
| ProcessStats stats; |
| auto status = proxy->GetStats(&stats); |
| if (status == ZX_OK) { |
| all_stats.push_back(stats); |
| } else { |
| FX_LOGS(WARNING) << "Failed to get stats for process: " << zx_status_get_string(status); |
| } |
| } |
| status.set_process_stats(std::move(all_stats)); |
| |
| return status; |
| } |
| |
| } // namespace fuzzing |