blob: 46502fd41442a1df265b7da76d0c78562dee820a [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_COMMON_RUNNER_H_
#define SRC_SYS_FUZZING_COMMON_RUNNER_H_
#include <fuchsia/fuzzer/cpp/fidl.h>
#include <lib/fidl/cpp/interface_handle.h>
#include <lib/fit/function.h>
#include <memory>
#include <vector>
#include "src/lib/fxl/macros.h"
#include "src/sys/fuzzing/common/artifact.h"
#include "src/sys/fuzzing/common/async-types.h"
#include "src/sys/fuzzing/common/input.h"
#include "src/sys/fuzzing/common/monitor-clients.h"
#include "src/sys/fuzzing/common/options.h"
#include "src/sys/fuzzing/common/result.h"
namespace fuzzing {
using ::fuchsia::fuzzer::MonitorPtr;
using ::fuchsia::fuzzer::Status;
using ::fuchsia::fuzzer::TargetAdapter;
using ::fuchsia::fuzzer::UpdateReason;
using CorpusType = ::fuchsia::fuzzer::Corpus;
// |RunnerPtr| is the preferred way to reference a |Runner| in a future or promise without needing
// to wrap it in a scope.
class Runner;
using RunnerPtr = std::shared_ptr<Runner>;
// This base class encapsulates the logic of performing a sequence of fuzzing runs. In
// particular, it defines virtual methods for performing the fuzzing workflows asynchronously, and
// invokes those methods on a dedicated worker thread to perform them without blocking the
// controller's FIDL dispatcher thread.
class Runner {
public:
// Note that the destructor cannot call |Close|, |Interrupt| or |Join|, as they are virtual.
// Instead, both this class and any derived class should have corresponding non-virtual "Impl"
// methods and call those on destruction.
virtual ~Runner() = default;
// Accessors.
const ExecutorPtr& executor() const { return executor_; }
const OptionsPtr& options() const { return options_; }
// Sets up the runner using arguments from the component manifest.
virtual ZxPromise<> Initialize(std::string pkg_dir, std::vector<std::string> args);
// Examines the options, and prepares the runner for fuzzing given their values.
virtual ZxPromise<> Configure();
// Add an input to the specified corpus. Returns ZX_ERR_INVALID_ARGS if |corpus_type| is
// unrecognized.
virtual zx_status_t AddToCorpus(CorpusType corpus_type, Input input) = 0;
// Returns a copy of all non-empty inputs in the corpus of the given |corpus_type|.
// The vector is sorted using Input's comparison operators.
virtual std::vector<Input> GetCorpus(CorpusType corpus_type) = 0;
// Parses the given |input| as an AFL-style dictionary. For format details, see
// https://lcamtuf.coredump.cx/afl/technical_details.txt. Returns ZX_ERR_INVALID_ARGS if parsing
// fails.
virtual zx_status_t ParseDictionary(const Input& input) = 0;
// Returns the current dictionary serialized into an |Input|.
virtual Input GetDictionaryAsInput() const = 0;
// Adds a subscriber for status updates.
void AddMonitor(fidl::InterfaceHandle<Monitor> monitor);
// Fuzzing workflows corresponding to methods in `fuchsia.fuzzer.Controller`.
// Implementation of `fuchsia.fuzzer.Controller/Fuzz`.
virtual ZxPromise<Artifact> Fuzz() = 0;
// Implementation of `fuchsia.fuzzer.Controller/TryOne`.
ZxPromise<Artifact> TryOne(Input input);
virtual ZxPromise<Artifact> TryEach(std::vector<Input> inputs) = 0;
// Implementation of `fuchsia.fuzzer.Controller/Minimize`. |ValidateMinimize| must be called
// before |Minimize|.
virtual ZxPromise<Artifact> ValidateMinimize(Input input) = 0;
virtual ZxPromise<Artifact> Minimize(Artifact artifact) = 0;
// Implementation of `fuchsia.fuzzer.Controller/Cleanse`.
virtual ZxPromise<Artifact> Cleanse(Input input) = 0;
// Implementation of `fuchsia.fuzzer.Controller/Merge`. |ValidateMerge| must be called
// before |Merge|.
virtual ZxPromise<> ValidateMerge() = 0;
virtual ZxPromise<Artifact> Merge() = 0;
// Creates a |Status| object representing all attached processes.
virtual Status CollectStatus() = 0;
// Cancels the current workflow.
virtual ZxPromise<> Stop() = 0;
protected:
void set_options(Options options) { *options_ = std::move(options); }
// Represents a single fuzzing workflow, e.g. |TryOne|, |Minimize|, etc. It holds a pointer to
// the object that created it, but this is safe: it cannot outlive the object it is a part of.
// It should be used in the normal way, e.g. using |wrap_with|.
//
// Derived runners should include a `Workflow` member, and use it to wrap any returned promises
// that are exclusive with a fuzzing workflow, e.g. the workflows themselves and method like
// `Configure`. Promises that are a part of a workflow can be wrapped with the workflow's
// `scope()`.
//
class Workflow final {
public:
// In most cases, a fuzzing workflow is encapsulated by a promise returned by a single method,
// e.g. `Fuzz`. In this case, the promise is wrapped by a `Workflow` that both starts and
// finishes the workflow.
//
// If a workflow spans multiple promises from multiple methods, each method may specify a `mode`
// when wrapping its promise. For example, if a workflow is made up of 3 promises from 3
// different methods:
//
// * The first may wrap its promise with `.wrap_with(workflow_, Workflow::Mode::kStart);`.
// * The second may wrap its promise with `.wrap_with(workflow_, Workflow::Mode::kNeither);`.
// * The last may wrap its promise with `.wrap_with(workflow_, Workflow::Mode::kFinish);`.
enum Mode {
kStart,
kFinish,
kBoth,
kNeither,
};
explicit Workflow(Runner* runner) : runner_(runner) {}
~Workflow() = default;
Scope& scope() { return scope_; }
// Use |wrap_with(workflow_)| on promises that implement a workflow's behavior to create scoped
// actions on set up and tear down.
template <typename Promise>
decltype(auto) wrap(Promise promise, Mode mode = Mode::kBoth) {
static_assert(std::is_same<typename Promise::error_type, zx_status_t>::value,
"Workflows must use an error type of zx_status_t.");
return Start(mode)
.and_then(std::move(promise))
.inspect([this, mode](const typename Promise::result_type& result) { Finish(mode); })
.wrap_with(scope_);
}
// Returns a promise to stop the current workflow. The promise completes after |Finish| is
// called.
ZxPromise<> Stop();
private:
ZxPromise<> Start(Mode mode);
void Finish(Mode mode);
Runner* runner_ = nullptr;
ZxCompleter<> completer_;
ZxConsumer<> consumer_;
Scope scope_;
};
explicit Runner(ExecutorPtr executor);
// These methods allow specific runners to implement actions that should be performed at the start
// or end of a workflow. They are called automatically by |Workflow|. The runners may also create
// additional tasks constrained to the workflow's |scope|.
virtual void StartWorkflow(Scope& scope) {}
virtual void FinishWorkflow() {}
// Labels the current status with the given `reason`, and sends it all attached `Monitor`s.
virtual void UpdateMonitorsWithStatus(UpdateReason reason, Status status);
// Like `UpdateMonitorsWithStatus`, but provides the status via `CollectStatus`.
void UpdateMonitors(UpdateReason reason);
private:
// Like |UpdateMonitors|, but uses UpdateReason::DONE as the reason and disconnects monitors after
// they acknowledge receipt.
void FinishMonitoring();
ExecutorPtr executor_;
OptionsPtr options_;
MonitorClients monitors_;
Scope scope_;
FXL_DISALLOW_COPY_ASSIGN_AND_MOVE(Runner);
};
} // namespace fuzzing
#endif // SRC_SYS_FUZZING_COMMON_RUNNER_H_