blob: aff4989bd3a8e5223e8be6e30c57dc0a778167b0 [file] [log] [blame]
// 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.
#ifndef SRC_SYS_FUZZING_FRAMEWORK_ENGINE_RUNNER_H_
#define SRC_SYS_FUZZING_FRAMEWORK_ENGINE_RUNNER_H_
#include <fuchsia/fuzzer/cpp/fidl.h>
#include <lib/fidl/cpp/interface_request.h>
#include <lib/fit/defer.h>
#include <lib/fit/function.h>
#include <lib/sync/completion.h>
#include <lib/zx/eventpair.h>
#include <lib/zx/time.h>
#include <stddef.h>
#include <atomic>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
#include "src/lib/fxl/macros.h"
#include "src/lib/fxl/synchronization/thread_annotations.h"
#include "src/sys/fuzzing/common/dispatcher.h"
#include "src/sys/fuzzing/common/input.h"
#include "src/sys/fuzzing/common/runner.h"
#include "src/sys/fuzzing/common/shared-memory.h"
#include "src/sys/fuzzing/common/signal-coordinator.h"
#include "src/sys/fuzzing/framework/engine/corpus.h"
#include "src/sys/fuzzing/framework/engine/module-pool.h"
#include "src/sys/fuzzing/framework/engine/mutagen.h"
#include "src/sys/fuzzing/framework/engine/process-proxy.h"
namespace fuzzing {
using ::fuchsia::fuzzer::MonitorPtr;
using ::fuchsia::fuzzer::Result;
using ::fuchsia::fuzzer::Status;
using ::fuchsia::fuzzer::TargetAdapter;
using ::fuchsia::fuzzer::TargetAdapterSyncPtr;
using ::fuchsia::fuzzer::UpdateReason;
using CorpusType = ::fuchsia::fuzzer::Corpus;
// The concrete implementation of |Runner|.
class RunnerImpl final : public Runner {
public:
RunnerImpl();
~RunnerImpl() override;
// |Runner| method implementations.
void AddDefaults(Options* options) override;
zx_status_t AddToCorpus(CorpusType corpus_type, Input input) override;
Input ReadFromCorpus(CorpusType corpus_type, size_t offset) override;
zx_status_t ParseDictionary(const Input& input) override;
Input GetDictionaryAsInput() const override;
// Configure where to send |TargetAdapter| requests.
void SetTargetAdapterHandler(fidl::InterfaceRequestHandler<TargetAdapter> handler);
// Handle incoming |ProcessProxy| requests.
fidl::InterfaceRequestHandler<ProcessProxy> GetProcessProxyHandler(
const std::shared_ptr<Dispatcher>& dispatcher);
// Callback for signals received from the target adapter and process proxies that are used to
// notify the runner that they have started or finished.
bool OnSignal();
// Callback for signals received from the target adapter and process proxies that are used to
// notify the runner that they have encountered an error. Error values are interpreted as:
// * 0: - no error.
// * UINTPTR_MAX: timeout
// * other: pointer to process proxy with error.
void OnError(uintptr_t error);
protected:
// Fuzzing workflow implementations.
void ConfigureImpl(const std::shared_ptr<Options>& options) override;
zx_status_t SyncExecute(const Input& input) override;
zx_status_t SyncMinimize(const Input& input) override;
zx_status_t SyncCleanse(const Input& input) override;
zx_status_t SyncFuzz() override;
zx_status_t SyncMerge() override;
Status CollectStatusLocked() override FXL_REQUIRE(mutex_);
void ClearErrors() override;
private:
// Creates and returns a scope object for a synchronous workflow. This will reset errors,
// deadlines, and run counts, and update monitors with an INIT update. When the object falls out
// of scope, it will ensure the fuzzer is stopped, disable timers, and send a DONE update. Each
// fuzzing workflow should begin with a call to this function.
fit::deferred_action<fit::closure> SyncScope();
// Resets the timer thread's alarm, setting a new run deadline and delaying how long until the
// TIMEOUT error is triggered.
void ResetTimer();
// The timer thread body.
void Timer();
// Sends the test input to the target adapter.
void TestOne(const Input& input);
// The core loop, implemented two ways. In both, the loop will repeatedly generate the
// |next_input|, send it to the target adapter, and perform additional actions using |finish_run|.
// The "strict" version will always analyze the feedback from the Nth input before generating the
// N+1th input. The "relaxed" version will generate the N+1th input *before* analyzing the
// feedback of the Nth input. If the next input doesn't directly depend on the previous input's
// feedback, this can improve performance by allowing the engine to generate inputs while waiting
// for a run to progress.
//
// Parameters:
// next_input: function that takes a boolean indicating if this is the first run, and returns
// the next input to send to target adapter, or null if fuzzing should stop.
// finish_run: function that takes the input that was just exercised by the target adapter.
// ignore_errors: bool that may be set to true if a workflow expects to encounter errors and
// then continue, e.g. Cleanse and Merge.
//
void FuzzLoopStrict(fit::function<Input*(bool /* first*/)> next_input,
fit::function<void(Input* /* last_input */)> finish_run,
bool ignore_errors = false);
void FuzzLoopRelaxed(fit::function<Input*(bool /* first*/)> next_input,
fit::function<void(Input* /* last_input */)> finish_run,
bool ignore_errors = false);
// A loop that handles signalling the target adapter and proxies. This is started on a dedicated
// thread by one of |FuzzLoop*|s above, allowing it to generate inputs and analyze feedback while
// waiting for other processes to respond.
void RunLoop(bool ignore_errors);
// Wraps |FuzzLoopStrict| to perform "normal" fuzzing, i.e. mutates an input from the live corpus,
// accumulates feedback from each input, and exits on error, max runs, or max time.
//
// 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.
void FuzzLoop();
// Uses the target adapter request handler to connect to the target adapter.
void ConnectTargetAdapter();
// Resets |sync|, but only if there is no pending error, allowing |RunLoop| to avoid blocking
// in the error case.
void ResetSyncIfNoPendingError(sync_completion_t* sync);
// Returns false if no error is pending. Otherwise, if the error is recoverable (e.g. a process
// exit when not detecting exits), it recovers and returns false. Otherwise, it records the
// |last_input|, determines its result, and returns true.
bool HasError(const Input* last_input);
// General configuration.
std::shared_ptr<Options> options_;
uint32_t run_ = 0;
zx::time start_ = zx::time::infinite_past();
zx::time deadline_ = zx::time::infinite();
zx::time next_pulse_ = zx::time::infinite();
// Variables to synchronize between the worker and run-loop.
std::atomic<bool> stopped_ = true;
Input* next_input_ = nullptr;
Input* last_input_ = nullptr;
sync_completion_t next_input_ready_;
sync_completion_t next_input_taken_;
sync_completion_t last_input_ready_;
sync_completion_t last_input_taken_;
sync_completion_t run_finished_;
// Timer variables
std::thread timer_;
sync_completion_t timer_sync_;
zx::time run_deadline_ = zx::time::infinite();
// Input generation and management variables.
std::shared_ptr<Corpus> seed_corpus_;
std::shared_ptr<Corpus> live_corpus_;
Mutagen mutagen_;
// Variables used to send data to and coordinate with the target adapter.
fidl::InterfaceRequestHandler<TargetAdapter> target_adapter_handler_;
TargetAdapterSyncPtr target_adapter_;
SignalCoordinator coordinator_;
SharedMemory test_input_;
sync_completion_t adapter_sync_;
// Feedback collection and analysis variables.
std::shared_ptr<ModulePool> pool_;
std::mutex mutex_;
std::vector<std::unique_ptr<ProcessProxyImpl>> proxies_ FXL_GUARDED_BY(mutex_);
std::atomic<size_t> pending_proxy_signals_ = 0;
sync_completion_t process_sync_;
// Represents the different types of fuzzing-ending errors. If an input results in multiple errors
// from different processes, only the first assignment is handled as the primary error.
std::atomic<uintptr_t> error_ = 0;
FXL_DISALLOW_COPY_ASSIGN_AND_MOVE(RunnerImpl);
};
} // namespace fuzzing
#endif // SRC_SYS_FUZZING_FRAMEWORK_ENGINE_RUNNER_H_