| // 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 |