| // 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. |
| |
| #include "src/sys/fuzzing/libfuzzer/runner.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| #include <zircon/status.h> |
| |
| #include <test/fuzzer/cpp/fidl.h> |
| |
| #include "src/lib/files/directory.h" |
| #include "src/lib/files/path.h" |
| #include "src/sys/fuzzing/common/async-eventpair.h" |
| #include "src/sys/fuzzing/common/runner-unittest.h" |
| #include "src/sys/fuzzing/common/testing/component-context.h" |
| #include "src/sys/fuzzing/libfuzzer/testing/feedback.h" |
| |
| namespace fuzzing { |
| |
| using ::test::fuzzer::Relay; |
| using ::test::fuzzer::RelayPtr; |
| using ::test::fuzzer::SignaledBuffer; |
| |
| // Test fixtures. |
| |
| // libFuzzer's output is normally suppressed when testing; but can be enabled using this flag when |
| // debugging failed tests. |
| #define LIBFUZZER_SHOW_OUTPUT 0 |
| |
| // libFuzzer normally attaches to itself as a debugger to catch crashes; but can be prevented from |
| // doing so when another debugger like zxdb is needed to investigate failed tests. |
| #define LIBFUZZER_ALLOW_DEBUG 0 |
| |
| // Specializes the generic |RunnerTest| for |LibFuzzerRunner|. |
| class LibFuzzerRunnerTest : public RunnerTest { |
| protected: |
| ////////////////////////////////////// |
| // Test fixtures. |
| |
| const RunnerPtr& runner() const override { return runner_; } |
| |
| void SetUp() override { |
| RunnerTest::SetUp(); |
| runner_ = LibFuzzerRunner::MakePtr(executor()); |
| context_ = ComponentContextForTest::Create(executor()); |
| |
| // LibFuzzer's "entropic energy" feature allows it to focus on inputs that provide more useful |
| // coverage; but is tricky to fake in unit testing. |
| cmdline_ = std::vector<std::string>({"bin/libfuzzer_unittest_fuzzer", "-entropic=0"}); |
| |
| eventpair_ = std::make_unique<AsyncEventPair>(executor()); |
| ASSERT_EQ(test_input_vmo_.Reserve(kDefaultMaxInputSize), ZX_OK); |
| ASSERT_EQ(feedback_vmo_.Mirror(&feedback_, sizeof(feedback_)), ZX_OK); |
| // Convince libFuzzer that the code is instrumented. |
| // See |Fuzzer::ReadAndExecuteSeedCorpora|. |
| SetCoverage(Input("\n"), {{255, 255}}); |
| |
| FUZZING_EXPECT_OK(runner_->Initialize("ignored", GetParameters())); |
| RunUntilIdle(); |
| } |
| |
| std::vector<std::string> GetParameters() const override { |
| return std::vector<std::string>(cmdline_); |
| } |
| |
| void SetParameters(std::vector<std::string> parameters) override { |
| cmdline_.resize(2); |
| std::copy(parameters.begin(), parameters.end(), std::back_inserter(cmdline_)); |
| } |
| |
| void Configure(const OptionsPtr& options) override { |
| // See notes on LIBFUZZER_ALLOW_DEBUG above. |
| options->set_detect_exits(true); |
| options->set_debug(LIBFUZZER_ALLOW_DEBUG); |
| RunnerTest::Configure(options); |
| |
| // See notes on LIBFUZZER_SHOW_OUTPUT above. |
| auto libfuzzer_runner = std::static_pointer_cast<LibFuzzerRunner>(runner_); |
| libfuzzer_runner->set_verbose(LIBFUZZER_SHOW_OUTPUT); |
| } |
| |
| ZxPromise<Input> GetTestInput() override { |
| // Some workflows, notably |Cleanse|, may run multiple successful instances of the libFuzzer |
| // process without error. This poses a challenge to this method, as it will be unclear whether |
| // it is connecting to a running fuzzer or one that is in the process of exiting without error. |
| // The easiest way to detect this is to simply wait for a fuzzing run to start without checking |
| // if the fuzzer is connected. If it is not, or if it is exiting, then the wait will fail and |
| // the test can connect to a new fuzzer instance via the relay. If it is, it is inexpensive to |
| // simply wait again on the already active signal. |
| return eventpair_->WaitFor(kStart) |
| .and_then([](const zx_signals_t& observed) -> ZxResult<> { return fpromise::ok(); }) |
| .or_else([this, relay = RelayPtr(), connect = ZxFuture<>()]( |
| Context& context, const zx_status_t& status) mutable -> ZxResult<> { |
| if (!connect) { |
| // Connect to the fuzzer via the relay. |
| auto handler = context_->MakeRequestHandler<Relay>(); |
| handler(relay.NewRequest(executor()->dispatcher())); |
| |
| // Exchange shared objects with the fuzzer via the relay. |
| SignaledBuffer data; |
| data.eventpair = eventpair_->Create(); |
| if (auto status = test_input_vmo_.Share(&data.test_input); status != ZX_OK) { |
| return fpromise::error(status); |
| } |
| if (auto status = feedback_vmo_.Share(&data.feedback); status != ZX_OK) { |
| return fpromise::error(status); |
| } |
| Bridge<> bridge; |
| relay->SetTestData(std::move(data), bridge.completer.bind()); |
| connect = ConsumeBridge(bridge); |
| } |
| if (!connect(context)) { |
| return fpromise::pending(); |
| } |
| if (connect.is_error()) { |
| return fpromise::error(connect.error()); |
| } |
| // At this point, the test should be connected to the fuzzer. Wait for a run to start. |
| return fpromise::ok(); |
| }) |
| .and_then(eventpair_->WaitFor(kStart)) |
| .and_then([this](const zx_signals_t& observed) -> ZxResult<Input> { |
| if (auto status = eventpair_->SignalSelf(kStart, 0); status != ZX_OK) { |
| return fpromise::error(status); |
| } |
| auto input = Input(test_input_vmo_); |
| return fpromise::ok(std::move(input)); |
| }) |
| .wrap_with(scope_); |
| } |
| |
| ZxPromise<> SetFeedback(Coverage coverage, FuzzResult fuzz_result, bool leak) override { |
| return fpromise::make_promise([this, coverage = std::move(coverage), fuzz_result, leak]() { |
| feedback_.result = fuzz_result; |
| feedback_.leak_suspected = leak; |
| feedback_.num_counters = coverage.size(); |
| FX_DCHECK(feedback_.num_counters <= kMaxNumFeedbackCounters) << feedback_.num_counters; |
| size_t i = 0; |
| for (const auto& [offset, value] : coverage) { |
| feedback_.counters[i].offset = static_cast<uint16_t>(offset); |
| feedback_.counters[i].value = static_cast<uint8_t>(value); |
| ++i; |
| } |
| feedback_vmo_.Update(); |
| return AsZxResult(eventpair_->SignalPeer(0, kStart)); |
| }) |
| .and_then(eventpair_->WaitFor(kFinish)) |
| .and_then([this](const zx_signals_t& observed) -> ZxResult<> { |
| return AsZxResult(eventpair_->SignalSelf(kFinish, 0)); |
| }) |
| .or_else([](const zx_status_t& status) -> ZxResult<> { |
| if (status == ZX_ERR_PEER_CLOSED) { |
| // LibFuzzer oftens runs multiple fuzzers in child processes; don't treat exits as |
| // failures. |
| return fpromise::ok(); |
| } |
| return fpromise::error(status); |
| }) |
| .wrap_with(scope_); |
| } |
| |
| private: |
| RunnerPtr runner_; |
| std::unique_ptr<ComponentContext> context_; |
| std::vector<std::string> cmdline_; |
| std::unique_ptr<AsyncEventPair> eventpair_; |
| SharedMemory test_input_vmo_; |
| SharedMemory feedback_vmo_; |
| RelayedFeedback feedback_; |
| Scope scope_; |
| }; |
| |
| #undef LIBFUZZER_SHOW_OUTPUT |
| #undef LIBFUZZER_ALLOW_DEBUG |
| |
| #define RUNNER_TYPE LibFuzzerRunner |
| #define RUNNER_TEST LibFuzzerRunnerTest |
| #include "src/sys/fuzzing/common/runner-fatal-unittest.inc" |
| #include "src/sys/fuzzing/common/runner-unittest.inc" |
| #undef RUNNER_TYPE |
| #undef RUNNER_TEST |
| |
| // TODO(https://fxbug.dev/42077933): Re-enable (and return to runner-unittest.inc) once flake is fixed. |
| TEST_F(LibFuzzerRunnerTest, DISABLED_Stop) { Stop(); } |
| TEST_F(LibFuzzerRunnerTest, Merge) { Merge(/* keep_errors= */ false); } |
| |
| } // namespace fuzzing |