blob: 26d7dbf5cc6901951384eb4b0ab0ea0df3817dfb [file] [log] [blame] [edit]
// 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;
namespace {
// Removes the string at |offset| from |argv|, and updates |argc| and |argv| by decrementing by 1
// and shifting other elements, respectively. Returns the removed string.
std::string ConsumeArg(int* pargc, char*** pargv, char* arg) {
int argc = *pargc;
char** argv = *pargv;
auto i = 0;
while (i < argc && argv[i] != arg) {
++i;
}
for (; i != 0; --i) {
argv[i] = argv[i - 1];
}
*pargv = argc > 1 ? &argv[1] : nullptr;
*pargc = argc > 0 ? (argc - 1) : 0;
return std::string(arg);
}
} // namespace
Engine::Engine() : Engine("/pkg") {}
Engine::Engine(const std::string& pkg_dir) : pkg_dir_(pkg_dir) {}
zx_status_t Engine::Initialize(int* pargc, char*** pargv) {
url_.reset();
fuzzing_ = false;
corpus_.clear();
dictionary_ = Input();
int argc = *pargc;
char** argv = *pargv;
for (int i = 1; i < argc; ++i) {
char* arg = argv[i];
// First, look for the fuzzing indicator. This is typically provided by `fuzz-manager`.
if (strcmp(arg, FUZZ_MODE) == 0) {
fuzzing_ = true;
ConsumeArg(pargc, pargv, arg);
continue;
}
// Escape hatch.
if (strcmp(arg, "--") == 0) {
ConsumeArg(pargc, pargv, arg);
break;
}
// Skip any remaining flags.
if (arg[0] == '-') {
continue;
}
// First positional argument is the fuzzer URL.
if (!url_) {
url_ = std::make_unique<component::FuchsiaPkgUrl>();
auto url = ConsumeArg(pargc, pargv, arg);
if (!url_->Parse(url)) {
FX_LOGS(WARNING) << "Failed to parse URL: " << url;
return ZX_ERR_INVALID_ARGS;
}
continue;
}
// Ignore remaining arguments except data files that need to be imported.
if (strncmp(arg, "data", 4) != 0) {
continue;
}
auto pathname = files::JoinPath(pkg_dir_, ConsumeArg(pargc, pargv, arg));
// A file argument is a dictionary.
if (files::IsFile(pathname)) {
if (dictionary_.size() != 0) {
FX_LOGS(WARNING) << "Multiple dictionaries found: " << arg;
return ZX_ERR_INVALID_ARGS;
}
std::vector<uint8_t> data;
if (!files::ReadFileToVector(pathname, &data)) {
FX_LOGS(WARNING) << "Failed to read dictionary '" << pathname << "': " << strerror(errno);
return ZX_ERR_IO;
}
dictionary_ = Input(data);
continue;
}
// Directory arguments are seed corpora.
if (files::IsDirectory(pathname)) {
std::vector<std::string> filenames;
if (!files::ReadDirContents(pathname, &filenames)) {
FX_LOGS(WARNING) << "Failed to read seed corpus '" << pathname << "': " << strerror(errno);
return ZX_ERR_IO;
}
for (const auto& filename : filenames) {
auto input_file = files::JoinPath(pathname, filename);
if (!files::IsFile(input_file)) {
continue;
}
std::vector<uint8_t> data;
if (!files::ReadFileToVector(input_file, &data)) {
FX_LOGS(WARNING) << "Failed to read input '" << input_file << "': " << strerror(errno);
return ZX_ERR_IO;
}
corpus_.push_back(Input(data));
}
continue;
}
// No other positional arguments are supported.
FX_LOGS(WARNING) << "Invalid package path: " << pathname;
return ZX_ERR_NOT_FOUND;
}
if (!url_) {
FX_LOGS(WARNING) << "Missing required URL.";
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
zx_status_t Engine::Run(ComponentContextPtr context, RunnerPtr runner) {
if (!url_) {
FX_LOGS(WARNING) << "Not initialized.";
return ZX_ERR_BAD_STATE;
}
auto url = url_->ToString();
url_.reset();
if (dictionary_.size() != 0) {
if (auto status = runner->ParseDictionary(dictionary_); status != ZX_OK) {
return status;
}
}
if (fuzzing_) {
return RunFuzzer(std::move(context), std::move(runner), url);
} else {
return RunTest(std::move(context), std::move(runner));
}
}
zx_status_t Engine::RunFuzzer(ComponentContextPtr context, RunnerPtr runner,
const std::string& url) {
for (auto& input : corpus_) {
if (auto status = runner->AddToCorpus(CorpusType::SEED, std::move(input)); status != ZX_OK) {
return status;
}
}
ControllerProviderImpl provider(context->executor());
provider.SetRunner(std::move(runner));
auto task = provider.Serve(url, context->TakeChannel(0));
context->ScheduleTask(std::move(task));
return context->Run();
}
zx_status_t Engine::RunTest(ComponentContextPtr context, RunnerPtr runner) {
corpus_.emplace_back(Input());
FX_LOGS(INFO) << "Testing with " << corpus_.size() << " inputs.";
auto options = MakeOptions();
runner->OverrideDefaults(options.get());
// TODO(fxbug.dev/109100): 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->Configure(options)
.and_then([runner, corpus = std::move(corpus_), execute = ZxFuture<FuzzResult>(),
attempts = 0U](Context& context) mutable -> ZxResult<FuzzResult> {
while (attempts < kFuzzerTestRetries) {
if (!execute) {
execute = runner->Execute(std::move(corpus));
}
if (!execute(context)) {
return fpromise::pending();
}
if (execute.is_ok()) {
return fpromise::ok(execute.take_value());
}
if (auto status = execute.take_error(); status != ZX_ERR_IO_INVALID) {
return fpromise::error(status);
}
attempts++;
}
return fpromise::error(ZX_ERR_IO_INVALID);
})
.then([&exitcode](ZxResult<FuzzResult>& result) {
if (result.is_error()) {
exitcode = result.error();
return;
}
auto fuzz_result = result.take_value();
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