blob: bce1f5b7bf3e5e6804815853939aecccf0333c6a [file] [log] [blame]
// Copyright 2022 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/common/engine.h"
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
#include "src/sys/fuzzing/common/controller-provider.h"
namespace fuzzing {
using ::fuchsia::fuzzer::FUZZ_MODE;
Engine::Engine() : Engine(ComponentContext::Create(), "/pkg") {}
Engine::Engine(ComponentContextPtr context, const std::string& pkg_dir)
: context_(std::move(context)), pkg_dir_(pkg_dir) {
FX_CHECK(context_);
}
zx_status_t Engine::Run(int argc, char** argv, RunnerPtr runner) {
FX_CHECK(runner);
fuzzing_ = false;
dictionary_ = Input();
// Skip the process name.
std::vector<std::string> args(argv + 1, argv + argc);
if (auto status = Initialize(std::move(args)); status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to initialize engine: " << zx_status_get_string(status);
return status;
}
runner_ = runner;
return fuzzing_ ? RunFuzzer() : RunTest();
}
zx_status_t Engine::Initialize(std::vector<std::string> args) {
args_ = std::move(args);
fuzzing_ = false;
std::string url;
bool skip = false;
auto is_engine_flag = [this, &url, &skip](const std::string& arg) {
if (skip) {
return false;
}
if (arg == "--") {
skip = true;
return false;
}
// Look for the fuzzing indicator. This is typically provided by `fuzz-manager`.
if (arg == FUZZ_MODE) {
fuzzing_ = true;
return true;
}
if (arg[0] != '-' && url.empty()) {
url = arg;
return true;
}
return false;
};
args_.erase(std::remove_if(args_.begin(), args_.end(), is_engine_flag), args_.end());
if (url.empty()) {
FX_LOGS(ERROR) << "Fuzzer URL not provided by component runner";
return ZX_ERR_INVALID_ARGS;
}
if (!url_.Parse(url)) {
FX_LOGS(ERROR) << "Failed to parse URL: " << url;
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
zx_status_t Engine::RunFuzzer() {
// No need to `wrap_with`. Everything remains in scope as long as context_->Run() is executing.
ControllerProviderImpl provider(runner_);
auto init = runner_->Initialize(pkg_dir_, args_).or_else([](zx_status_t& status) -> Result<> {
FX_LOGS(ERROR) << "Failed to initialize fuzzer: " << zx_status_get_string(status);
return fpromise::error();
});
auto serve = provider.Serve(url_.ToString(), context_->TakeChannel(0));
context_->ScheduleTask(init.and_then(std::move(serve)));
return context_->Run();
}
zx_status_t Engine::RunTest() {
// TODO(https://fxbug.dev/42060461): Rarely, spawned process output may be truncated. `LibFuzzerRunner`
// needs to return `ZX_ERR_IO_INVALID` in this case. By retying several times, the probability of
// the underlying flake failing a test drops to almost zero.
static constexpr const size_t kFuzzerTestRetries = 10;
// In order to make this more testable, the following task does not exit directly. Instead, it
// repeatedly calls |RunUntilIdle| until it has set an exit code. This allows this method to be
// called as part of a gTest as well as by the elf_test_runner.
zx_status_t exitcode = ZX_ERR_NEXT;
auto task = runner_->Initialize(pkg_dir_, args_)
.and_then([this, fut = ZxFuture<Artifact>(),
attempts = 0U](Context& context) mutable -> ZxResult<Artifact> {
while (attempts < kFuzzerTestRetries) {
if (!fut) {
auto corpus = runner_->GetCorpus(CorpusType::SEED);
corpus.emplace_back(Input());
FX_LOGS(INFO) << "Testing with " << corpus.size() << " input(s).";
fut = runner_->TryEach(std::move(corpus));
}
if (!fut(context)) {
return fpromise::pending();
}
if (fut.is_ok()) {
return fpromise::ok(fut.take_value());
}
if (auto status = fut.take_error(); status != ZX_ERR_IO_INVALID) {
return fpromise::error(status);
}
attempts++;
}
return fpromise::error(ZX_ERR_IO_INVALID);
})
.then([&exitcode](ZxResult<Artifact>& result) {
if (result.is_error()) {
exitcode = result.error();
return;
}
auto artifact = result.take_value();
auto fuzz_result = artifact.fuzz_result();
exitcode = (fuzz_result == FuzzResult::NO_ERRORS) ? 0 : fuzz_result;
});
context_->ScheduleTask(std::move(task));
while (exitcode == ZX_ERR_NEXT) {
if (auto status = context_->RunUntilIdle(); status != ZX_OK) {
return status;
}
}
return exitcode;
}
} // namespace fuzzing