| // 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. |
| |
| // These tests only cover the basic configuration and operation of the Process class. Testing |
| // functionality that leads to the process exiting is tricky. It can require specific build |
| // configurations (i.e. link against ASan or LSan) and more complex process lifecycle management. As |
| // a result, this functionality is tested using integration rather than unit tests. |
| |
| #include "src/sys/fuzzing/framework/target/process.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <zircon/status.h> |
| |
| #include <memory> |
| #include <string> |
| #include <unordered_map> |
| #include <unordered_set> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/sys/fuzzing/common/async-eventpair.h" |
| #include "src/sys/fuzzing/common/options.h" |
| #include "src/sys/fuzzing/common/testing/async-test.h" |
| #include "src/sys/fuzzing/framework/engine/coverage-data.h" |
| #include "src/sys/fuzzing/framework/engine/module-pool.h" |
| #include "src/sys/fuzzing/framework/testing/coverage.h" |
| #include "src/sys/fuzzing/framework/testing/module.h" |
| |
| namespace fuzzing { |
| |
| using ::fuchsia::fuzzer::CoverageDataProviderPtr; |
| using ::fuchsia::fuzzer::Options; |
| |
| // Test fixtures. |
| |
| class ProcessTest : public AsyncTest { |
| protected: |
| void SetUp() override { |
| AsyncTest::SetUp(); |
| coverage_ = std::make_unique<FakeCoverage>(executor()); |
| eventpair_ = std::make_shared<AsyncEventPair>(executor()); |
| pool_ = ModulePool::MakePtr(); |
| |
| auto provider_handler = coverage_->GetProviderHandler(); |
| provider_handler(provider_.NewRequest(executor()->dispatcher())); |
| Configure(DefaultOptions()); |
| } |
| |
| // Accessors. |
| ModulePoolPtr pool() const { return pool_; } |
| uint64_t target_id() const { return target_id_; } |
| size_t num_added() const { return added_.size(); } |
| std::shared_ptr<AsyncEventPair> eventpair() const { return eventpair_; } |
| |
| // Returns options that limit the number of spurious warnings during tests. |
| static OptionsPtr DefaultOptions(bool disable_warnings = true) { |
| auto options = MakeOptions(); |
| if (disable_warnings) { |
| options->set_malloc_limit(0); |
| options->set_purge_interval(0); |
| } |
| Process::AddDefaults(options.get()); |
| return options; |
| } |
| |
| // Copies the given |options| to the watcher, to be given to new processes. |
| void Configure(OptionsPtr options) { |
| provider_->SetOptions(CopyOptions(*options)); |
| RunOnce(); |
| } |
| |
| // Returns a promises to connect the given process to the fake "engine" provided by the test. |
| // Tests typically need to call |WatchForProcess| and |WatchForModule| for this promise to |
| // complete. |
| ZxPromise<> Connect(Process* process) { |
| fidl::InterfaceHandle<CoverageDataCollector> collector; |
| auto collector_handler = coverage_->GetCollectorHandler(); |
| collector_handler(collector.NewRequest()); |
| auto eventpair = std::make_shared<AsyncEventPair>(executor()); |
| auto task = process->Connect(std::move(collector), eventpair->Create()).wrap_with(scope_); |
| executor()->schedule_task(std::move(task)); |
| return fpromise::make_promise([eventpair, wait = ZxFuture<zx_signals_t>()]( |
| Context& context) mutable -> ZxResult<> { |
| if (!wait) { |
| wait = eventpair->WaitFor(kSync); |
| } |
| if (!wait(context)) { |
| return fpromise::pending(); |
| } |
| if (wait.is_error()) { |
| return fpromise::error(wait.error()); |
| } |
| return fpromise::ok(); |
| }) |
| .wrap_with(scope_); |
| } |
| |
| // Creates a fake module for the current process, but defers adding its coverage. Returns the |
| // unique module ID. |
| std::string CreateModule() { |
| FakeFrameworkModule module(static_cast<uint32_t>(modules_.size() + 1)); |
| auto id = module.id(); |
| auto result = modules_.emplace(id, std::move(module)); |
| FX_CHECK(result.second); |
| return id; |
| } |
| |
| // Creates a fake module for the current process and adds its coverage. Returns the unique module |
| // ID. |
| std::string AddModule() { |
| auto id = CreateModule(); |
| auto* module = GetModule(id); |
| __sanitizer_cov_8bit_counters_init(module->counters(), module->counters_end()); |
| __sanitizer_cov_pcs_init(module->pcs(), module->pcs_end()); |
| return id; |
| } |
| |
| // The returned pointer may be invalidated by calls to |AddModule|. |
| FakeFrameworkModule* GetModule(const std::string& id) { |
| auto i = modules_.find(id); |
| return i == modules_.end() ? nullptr : &i->second; |
| } |
| |
| // Returns a promise to handle an expected coverage event from a new process. Completes |
| // with an error if the next coverage event is for an LLVM module. |
| Promise<> WatchForProcess() { |
| Bridge<CoverageData> bridge; |
| provider_->GetCoverageData(bridge.completer.bind()); |
| return bridge.consumer.promise_or(fpromise::error()) |
| .and_then([this](CoverageData& coverage_data) -> Result<> { |
| if (!coverage_data.is_instrumented()) { |
| return fpromise::error(); |
| } |
| auto& instrumented = coverage_data.instrumented(); |
| target_id_ = GetTargetId(instrumented.process); |
| eventpair_->Pair(std::move(instrumented.eventpair)); |
| return fpromise::ok(); |
| }) |
| .wrap_with(scope_); |
| } |
| |
| // Returns a promise to handle an expected coverage event from a new module. Completes |
| // with an error if the next coverage event is for an instrumented process. |
| Promise<> WatchForModule() { |
| Bridge<CoverageData> bridge; |
| provider_->GetCoverageData(bridge.completer.bind()); |
| return bridge.consumer.promise_or(fpromise::error()) |
| .and_then([this](CoverageData& coverage_data) -> Result<> { |
| if (!coverage_data.is_inline_8bit_counters()) { |
| return fpromise::error(); |
| } |
| auto& inline_8bit_counters = coverage_data.inline_8bit_counters(); |
| auto module_id = GetModuleId(inline_8bit_counters); |
| SharedMemory counters; |
| if (auto status = counters.Link(std::move(inline_8bit_counters)); status != ZX_OK) { |
| return fpromise::error(); |
| } |
| auto* module = pool_->Get(module_id, counters.size()); |
| module->Add(counters.data(), counters.size()); |
| added_.push_back(std::move(counters)); |
| return fpromise::ok(); |
| }) |
| .wrap_with(scope_); |
| } |
| |
| private: |
| std::unique_ptr<FakeCoverage> coverage_; |
| std::shared_ptr<AsyncEventPair> eventpair_; |
| ModulePoolPtr pool_; |
| CoverageDataProviderPtr provider_; |
| uint64_t target_id_ = kInvalidTargetId; |
| std::unordered_map<std::string, FakeFrameworkModule> modules_; |
| std::vector<SharedMemory> added_; |
| Completer<zx_signals_t> completer_; |
| Scope scope_; |
| }; |
| |
| // Unit tests. |
| |
| TEST_F(ProcessTest, AddDefaults) { |
| Options options; |
| Process::AddDefaults(&options); |
| EXPECT_EQ(options.detect_leaks(), kDefaultDetectLeaks); |
| EXPECT_EQ(options.malloc_limit(), kDefaultMallocLimit); |
| EXPECT_EQ(options.oom_limit(), kDefaultOomLimit); |
| EXPECT_EQ(options.purge_interval(), kDefaultPurgeInterval); |
| EXPECT_EQ(options.malloc_exitcode(), kDefaultMallocExitcode); |
| EXPECT_EQ(options.death_exitcode(), kDefaultDeathExitcode); |
| EXPECT_EQ(options.leak_exitcode(), kDefaultLeakExitcode); |
| EXPECT_EQ(options.oom_exitcode(), kDefaultOomExitcode); |
| } |
| |
| TEST_F(ProcessTest, ConnectProcess) { |
| Process process(executor()); |
| FUZZING_EXPECT_OK(Connect(&process)); |
| FUZZING_EXPECT_OK(WatchForProcess()); |
| RunUntilIdle(); |
| |
| auto self = zx::process::self(); |
| zx_info_handle_basic_t info; |
| EXPECT_EQ(self->get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr), ZX_OK); |
| EXPECT_EQ(target_id(), info.koid); |
| } |
| |
| TEST_F(ProcessTest, ConnectWithDefaultOptions) { |
| Configure(DefaultOptions(/* disable_warnings */ false)); |
| |
| Process process(executor()); |
| FUZZING_EXPECT_OK(Connect(&process)); |
| FUZZING_EXPECT_OK(WatchForProcess()); |
| RunUntilIdle(); |
| |
| const auto& options = process.options(); |
| EXPECT_EQ(options.detect_leaks(), kDefaultDetectLeaks); |
| EXPECT_EQ(options.malloc_limit(), kDefaultMallocLimit); |
| EXPECT_EQ(options.oom_limit(), kDefaultOomLimit); |
| EXPECT_EQ(options.purge_interval(), kDefaultPurgeInterval); |
| EXPECT_EQ(options.malloc_exitcode(), kDefaultMallocExitcode); |
| EXPECT_EQ(options.death_exitcode(), kDefaultDeathExitcode); |
| EXPECT_EQ(options.leak_exitcode(), kDefaultLeakExitcode); |
| EXPECT_EQ(options.oom_exitcode(), kDefaultOomExitcode); |
| } |
| |
| TEST_F(ProcessTest, ConnectDisableLimits) { |
| auto options = DefaultOptions(); |
| options->set_malloc_limit(0); |
| options->set_purge_interval(0); |
| Configure(options); |
| |
| Process process(executor()); |
| FUZZING_EXPECT_OK(Connect(&process)); |
| FUZZING_EXPECT_OK(WatchForProcess()); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(process.malloc_limit(), std::numeric_limits<size_t>::max()); |
| EXPECT_EQ(process.next_purge(), zx::time::infinite()); |
| } |
| |
| TEST_F(ProcessTest, ConnectAndAddModules) { |
| // Modules can be added "early", i.e. before the |Process| constructor... |
| auto id1 = AddModule(); |
| auto id2 = AddModule(); |
| Process process(executor()); |
| FUZZING_EXPECT_OK(Connect(&process)); |
| |
| // Add ALL the modules. This may include extras if the test itself is instrumented. The promise |
| // will be dropped when the test completes and the scope object is destroyed. |
| Scope scope; |
| auto task = WatchForProcess() |
| .and_then([this, watch = Future<>()](Context& context) mutable -> Result<> { |
| while (true) { |
| if (!watch) { |
| watch = WatchForModule(); |
| } |
| if (!watch(context)) { |
| return fpromise::pending(); |
| } |
| if (watch.is_error()) { |
| return fpromise::error(); |
| } |
| watch = nullptr; |
| } |
| }) |
| .wrap_with(scope); |
| executor()->schedule_task(std::move(task)); |
| |
| // ...or late, i.e. via `dlopen`. |
| auto id3 = AddModule(); |
| auto id4 = AddModule(); |
| RunUntilIdle(); |
| |
| EXPECT_NE(GetModule(id1), nullptr); |
| EXPECT_NE(GetModule(id2), nullptr); |
| EXPECT_NE(GetModule(id3), nullptr); |
| EXPECT_NE(GetModule(id4), nullptr); |
| } |
| |
| TEST_F(ProcessTest, ConnectBadModules) { |
| Process process(executor()); |
| FUZZING_EXPECT_OK(Connect(&process)); |
| FUZZING_EXPECT_OK(WatchForProcess()); |
| RunUntilIdle(); |
| |
| // |initial| may be non-zero when the test is instrumented. |
| size_t initial = num_added(); |
| |
| // Empty-length module. |
| auto* module = GetModule(CreateModule()); |
| __sanitizer_cov_8bit_counters_init(module->counters(), module->counters()); |
| __sanitizer_cov_pcs_init(module->pcs(), module->pcs()); |
| EXPECT_EQ(num_added(), initial); |
| |
| // Module ends before it begins. |
| __sanitizer_cov_8bit_counters_init(module->counters() + 1, module->counters()); |
| __sanitizer_cov_pcs_init(module->pcs() + 2, module->pcs()); |
| EXPECT_EQ(num_added(), initial); |
| |
| // Mismatched length. |
| __sanitizer_cov_8bit_counters_init(module->counters(), module->counters_end() - 1); |
| __sanitizer_cov_pcs_init(module->pcs(), module->pcs_end()); |
| EXPECT_EQ(num_added(), initial); |
| } |
| |
| TEST_F(ProcessTest, ConnectLateModules) { |
| Process process(executor()); |
| FUZZING_EXPECT_OK(Connect(&process)); |
| FUZZING_EXPECT_OK(WatchForProcess()); |
| RunUntilIdle(); |
| |
| // |initial| may be non-zero when the test is instrumented. |
| size_t initial = num_added(); |
| |
| // Modules with missing fields are deferred. |
| FUZZING_EXPECT_OK(WatchForModule()); |
| auto id1 = CreateModule(); |
| auto* module = GetModule(id1); |
| __sanitizer_cov_8bit_counters_init(module->counters(), module->counters_end()); |
| RunOnce(); |
| EXPECT_EQ(num_added(), initial); |
| |
| __sanitizer_cov_pcs_init(module->pcs(), module->pcs_end()); |
| RunUntilIdle(); |
| EXPECT_EQ(num_added(), initial + 1); |
| |
| FUZZING_EXPECT_OK(WatchForModule()); |
| auto id2 = CreateModule(); |
| module = GetModule(id2); |
| __sanitizer_cov_pcs_init(module->pcs(), module->pcs_end()); |
| RunOnce(); |
| EXPECT_EQ(num_added(), initial + 1); |
| |
| FUZZING_EXPECT_OK(WatchForModule()); |
| auto id3 = CreateModule(); |
| module = GetModule(id3); |
| __sanitizer_cov_pcs_init(module->pcs(), module->pcs_end()); |
| RunOnce(); |
| EXPECT_EQ(num_added(), initial + 1); |
| |
| module = GetModule(id2); |
| __sanitizer_cov_8bit_counters_init(module->counters(), module->counters_end()); |
| RunOnce(); |
| EXPECT_EQ(num_added(), initial + 2); |
| |
| module = GetModule(id3); |
| __sanitizer_cov_8bit_counters_init(module->counters(), module->counters_end()); |
| RunUntilIdle(); |
| EXPECT_EQ(num_added(), initial + 3); |
| } |
| |
| TEST_F(ProcessTest, ImplicitStart) { |
| Process process(executor()); |
| FUZZING_EXPECT_OK(Connect(&process)); |
| FUZZING_EXPECT_OK(WatchForProcess()); |
| RunUntilIdle(); |
| |
| // Processes should be implicitly |Start|ed on |Connect|ing. |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kFinish)); |
| EXPECT_EQ(eventpair()->SignalPeer(0, kFinish), ZX_OK); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(pool()->Measure(), 0U); |
| } |
| |
| TEST_F(ProcessTest, UpdateOnFinish) { |
| Process process(executor()); |
| FUZZING_EXPECT_OK(Connect(&process)); |
| FUZZING_EXPECT_OK(WatchForProcess()); |
| RunUntilIdle(); |
| |
| auto* module = GetModule(AddModule()); |
| FUZZING_EXPECT_OK(WatchForModule()); |
| RunUntilIdle(); |
| |
| // No new coverage. |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kFinish)); |
| EXPECT_EQ(eventpair()->SignalPeer(0, kFinish), ZX_OK); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(pool()->Measure(), 0U); |
| |
| // Add some counters. |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kStart)); |
| EXPECT_EQ(eventpair()->SignalPeer(kFinish, kStart), ZX_OK); |
| RunUntilIdle(); |
| |
| (*module)[0] = 4; |
| (*module)[module->num_pcs() / 2] = 16; |
| (*module)[module->num_pcs() - 1] = 128; |
| |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kFinish)); |
| EXPECT_EQ(eventpair()->SignalPeer(kStart, kFinish), ZX_OK); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(pool()->Measure(), 3U); |
| } |
| |
| TEST_F(ProcessTest, UpdateOnExit) { |
| Process process(executor()); |
| FUZZING_EXPECT_OK(Connect(&process)); |
| FUZZING_EXPECT_OK(WatchForProcess()); |
| RunUntilIdle(); |
| |
| auto* module = GetModule(AddModule()); |
| FUZZING_EXPECT_OK(WatchForModule()); |
| RunUntilIdle(); |
| |
| // Add some counters. |
| (*module)[module->num_pcs() - 4] = 64; |
| (*module)[module->num_pcs() - 3] = 32; |
| (*module)[module->num_pcs() - 2] = 16; |
| (*module)[module->num_pcs() - 1] = 8; |
| |
| // Fake a call to |exit|. |
| process.OnExit(); |
| EXPECT_EQ(pool()->Measure(), 4U); |
| } |
| |
| TEST_F(ProcessTest, FinishWithoutLeaks) { |
| Process process(executor()); |
| FUZZING_EXPECT_OK(Connect(&process)); |
| FUZZING_EXPECT_OK(WatchForProcess()); |
| RunUntilIdle(); |
| |
| // No mallocs/frees, and no leak detection. |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kFinish)); |
| EXPECT_EQ(eventpair()->SignalPeer(0, kFinish), ZX_OK); |
| RunUntilIdle(); |
| |
| // Balanced mallocs/frees, and no leak detection. |
| // The pointers and sizes don't actually matter; just the number of calls. |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kStart)); |
| EXPECT_EQ(eventpair()->SignalPeer(0, kStart), ZX_OK); |
| RunUntilIdle(); |
| |
| process.OnMalloc(nullptr, 0); |
| process.OnMalloc(nullptr, 0); |
| process.OnFree(nullptr); |
| process.OnMalloc(nullptr, 0); |
| process.OnFree(nullptr); |
| process.OnFree(nullptr); |
| |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kFinish)); |
| EXPECT_EQ(eventpair()->SignalPeer(0, kFinish), ZX_OK); |
| RunUntilIdle(); |
| |
| // No mallocs/frees, with leak detection. |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kStart)); |
| EXPECT_EQ(eventpair()->SignalPeer(0, kStartLeakCheck), ZX_OK); |
| RunUntilIdle(); |
| |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kFinish)); |
| EXPECT_EQ(eventpair()->SignalPeer(0, kFinish), ZX_OK); |
| RunUntilIdle(); |
| |
| // Balanced mallocs/frees, with leak detection. |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kStart)); |
| EXPECT_EQ(eventpair()->SignalPeer(0, kStartLeakCheck), ZX_OK); |
| RunUntilIdle(); |
| |
| process.OnMalloc(nullptr, 0); |
| process.OnMalloc(nullptr, 0); |
| process.OnFree(nullptr); |
| process.OnMalloc(nullptr, 0); |
| process.OnFree(nullptr); |
| process.OnFree(nullptr); |
| |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kFinish)); |
| EXPECT_EQ(eventpair()->SignalPeer(0, kFinish), ZX_OK); |
| RunUntilIdle(); |
| } |
| |
| TEST_F(ProcessTest, FinishWithLeaks) { |
| Process process(executor()); |
| FUZZING_EXPECT_OK(Connect(&process)); |
| FUZZING_EXPECT_OK(WatchForProcess()); |
| RunUntilIdle(); |
| |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kFinish)); |
| EXPECT_EQ(eventpair()->SignalPeer(0, kFinish), ZX_OK); |
| RunUntilIdle(); |
| |
| // Unbalanced mallocs/frees, and no leak detection. |
| // The pointers and sizes don't actually matter; just the number of calls. |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kStart)); |
| EXPECT_EQ(eventpair()->SignalPeer(kFinish, kStart), ZX_OK); |
| RunUntilIdle(); |
| |
| process.OnMalloc(nullptr, 0); |
| process.OnMalloc(nullptr, 0); |
| process.OnFree(nullptr); |
| |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kFinishWithLeaks)); |
| EXPECT_EQ(eventpair()->SignalPeer(kStart, kFinish), ZX_OK); |
| RunUntilIdle(); |
| |
| // Unbalanced mallocs/frees, with leak detection. |
| // Since these aren't real leaks, this will not abort. |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kStart)); |
| EXPECT_EQ(eventpair()->SignalPeer(kFinish, kStartLeakCheck), ZX_OK); |
| RunUntilIdle(); |
| |
| process.OnMalloc(nullptr, 0); |
| process.OnMalloc(nullptr, 0); |
| process.OnFree(nullptr); |
| |
| FUZZING_EXPECT_OK(eventpair()->WaitFor(kFinishWithLeaks)); |
| EXPECT_EQ(eventpair()->SignalPeer(kStartLeakCheck, kFinish), ZX_OK); |
| RunUntilIdle(); |
| } |
| |
| } // namespace fuzzing |