blob: 1174ff30ad84bf4122e48fe11eb57ab912e100ea [file] [log] [blame]
// 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 <lib/fidl/cpp/binding.h>
#include <lib/sync/completion.h>
#include <memory>
#include <unordered_map>
#include <gtest/gtest.h>
#include "src/sys/fuzzing/common/options.h"
#include "src/sys/fuzzing/common/testing/dispatcher.h"
#include "src/sys/fuzzing/framework/engine/module-pool.h"
#include "src/sys/fuzzing/framework/testing/module.h"
#include "src/sys/fuzzing/framework/testing/process-proxy.h"
namespace fuzzing {
namespace {
using ::fuchsia::fuzzer::Options;
// Test fixtures.
constexpr uint32_t kNumModules = 4;
// Generates some simple |modules|. |Collect| requires at least one module, so this initializes the
// first one. This method should be called *before* instantiating a |TestProcess|.
std::vector<FakeModule> CreateModulesAndInitFirst() {
std::vector<FakeModule> modules;
for (uint32_t i = 0; i < kNumModules; ++i) {
modules.emplace_back(FakeModule(i + 1));
}
__sanitizer_cov_8bit_counters_init(modules[0].counters(), modules[0].counters_end());
__sanitizer_cov_pcs_init(modules[0].pcs(), modules[0].pcs_end());
return modules;
}
// This class simply exposes some protected methods for testing. Notably, it does NOT automatically
// connect to a |fuchsia.fuzzer.ProcessProxy| or call |Process::InstallHooks|, as
// |InstrumentedProcess| does.
class TestProcess : public Process {
public:
TestProcess() = default;
using Process::Connect;
using Process::malloc_limit;
using Process::next_purge;
using Process::options;
};
// The unit test base class.
class ProcessTest : public ::testing::Test {
protected:
void SetUp() override {
// Create and destroy a process. This will "consume" any extra modules added if the unit test
// itself is instrumented.
{ TestProcess process; }
pool_ = std::make_shared<ModulePool>();
}
// Create a fake |ProcessProxy|, bind to it, and call |Connect| on it.
std::unique_ptr<FakeProcessProxy> MakeAndBindProxy(
TestProcess& process, const std::shared_ptr<Options>& options = DefaultOptions(),
bool disable_warnings = true) {
auto proxy = std::make_unique<FakeProcessProxy>(pool_);
proxy->Configure(options);
auto ptr = proxy->Bind(dispatcher_.get(), disable_warnings);
process.Connect(std::move(ptr));
return proxy;
}
size_t MeasurePool() { return pool_->Measure(); }
private:
FakeDispatcher dispatcher_;
std::shared_ptr<ModulePool> pool_;
};
// Unit tests.
TEST_F(ProcessTest, ConnectProcess) {
auto modules = CreateModulesAndInitFirst();
TestProcess process;
auto proxy = MakeAndBindProxy(process);
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(proxy->process_koid(), info.koid);
}
TEST_F(ProcessTest, ConnectWithDefaultOptions) {
auto modules = CreateModulesAndInitFirst();
TestProcess process;
auto proxy = MakeAndBindProxy(process, DefaultOptions(), /* disable_warnings */ false);
const auto& options = process.options();
EXPECT_EQ(options.runs(), kDefaultRuns);
EXPECT_EQ(options.max_total_time(), kDefaultMaxTotalTime.get());
EXPECT_EQ(options.max_input_size(), kDefaultMaxInputSize);
EXPECT_EQ(options.detect_exits(), kDefaultDetectExits);
EXPECT_EQ(options.detect_leaks(), kDefaultDetectLeaks);
EXPECT_EQ(options.run_limit(), kDefaultRunLimit.get());
EXPECT_EQ(options.malloc_limit(), kDefaultMallocLimit);
EXPECT_EQ(options.oom_limit(), kDefaultOOMLimit);
EXPECT_EQ(options.purge_interval(), kDefaultPurgeInterval.get());
EXPECT_EQ(options.malloc_exitcode(), kDefaultMallocExitCode);
EXPECT_EQ(options.death_exitcode(), kDefaultDeathExitCode);
EXPECT_EQ(options.leak_exitcode(), kDefaultLeakExitCode);
}
TEST_F(ProcessTest, ConnectDisableLimits) {
auto modules = CreateModulesAndInitFirst();
TestProcess process;
auto options = DefaultOptions();
options->set_malloc_limit(0);
options->set_purge_interval(0);
auto proxy = MakeAndBindProxy(process, options, /* disable_warnings */ false);
EXPECT_EQ(process.malloc_limit(), std::numeric_limits<size_t>::max());
EXPECT_EQ(process.next_purge(), zx::time::infinite());
}
TEST_F(ProcessTest, ConnectAndAddModules) {
auto modules = CreateModulesAndInitFirst();
// Add some, but not all, of the modules (modules[0] was already added).
for (size_t i = 1; i < kNumModules - 1; ++i) {
__sanitizer_cov_8bit_counters_init(modules[i].counters(), modules[i].counters_end());
__sanitizer_cov_pcs_init(modules[i].pcs(), modules[i].pcs_end());
}
TestProcess process;
auto proxy = MakeAndBindProxy(process);
// The mock ProcessProxy should have received exactly the IDs added via |__sanitizer_cov_*_init|.
EXPECT_EQ(proxy->num_modules(), kNumModules - 1);
for (size_t i = 0; i < kNumModules - 1; ++i) {
EXPECT_TRUE(proxy->has_module(&modules[i]));
}
auto* module = &modules[kNumModules - 1];
EXPECT_FALSE(proxy->has_module(module));
// Late-added modules (e.g. via `dlopen`) are added automatically.
__sanitizer_cov_8bit_counters_init(module->counters(), module->counters_end());
__sanitizer_cov_pcs_init(module->pcs(), module->pcs_end());
EXPECT_TRUE(proxy->has_module(module));
}
TEST_F(ProcessTest, ConnectBadModules) {
auto modules = CreateModulesAndInitFirst();
TestProcess process;
auto proxy = MakeAndBindProxy(process);
size_t num_modules = proxy->num_modules();
// Empty-length module.
auto* module = &modules[1];
__sanitizer_cov_8bit_counters_init(module->counters(), module->counters());
__sanitizer_cov_pcs_init(module->pcs(), module->pcs());
EXPECT_EQ(proxy->num_modules(), num_modules);
// 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(proxy->num_modules(), num_modules);
// Mismatched length.
__sanitizer_cov_8bit_counters_init(module->counters(), module->counters_end() - 1);
__sanitizer_cov_pcs_init(module->pcs(), module->pcs_end());
EXPECT_EQ(proxy->num_modules(), num_modules);
}
TEST_F(ProcessTest, ConnectLateModules) {
auto modules = CreateModulesAndInitFirst();
TestProcess process;
auto proxy = MakeAndBindProxy(process);
size_t num_modules = proxy->num_modules();
// Modules with missing fields are deferred.
auto* module = &modules[1];
__sanitizer_cov_8bit_counters_init(module->counters(), module->counters_end());
EXPECT_EQ(proxy->num_modules(), num_modules);
__sanitizer_cov_pcs_init(module->pcs(), module->pcs_end());
EXPECT_EQ(proxy->num_modules(), num_modules + 1);
module = &modules[2];
__sanitizer_cov_pcs_init(module->pcs(), module->pcs_end());
EXPECT_EQ(proxy->num_modules(), num_modules + 1);
module = &modules[3];
__sanitizer_cov_pcs_init(module->pcs(), module->pcs_end());
EXPECT_EQ(proxy->num_modules(), num_modules + 1);
module = &modules[2];
__sanitizer_cov_8bit_counters_init(module->counters(), module->counters_end());
EXPECT_EQ(proxy->num_modules(), num_modules + 2);
module = &modules[3];
__sanitizer_cov_8bit_counters_init(module->counters(), module->counters_end());
EXPECT_EQ(proxy->num_modules(), num_modules + 3);
}
TEST_F(ProcessTest, ImplicitStart) {
auto modules = CreateModulesAndInitFirst();
TestProcess process;
auto proxy = MakeAndBindProxy(process);
// Processes should be implicitly |Start|ed on |Connect|ing.
EXPECT_TRUE(proxy->SignalPeer(kFinish));
EXPECT_EQ(proxy->AwaitSignal(), kFinish);
EXPECT_EQ(MeasurePool(), 0U);
}
TEST_F(ProcessTest, UpdateOnStop) {
auto modules = CreateModulesAndInitFirst();
TestProcess process;
auto proxy = MakeAndBindProxy(process);
// No new coverage.
EXPECT_TRUE(proxy->SignalPeer(kStart));
EXPECT_EQ(proxy->AwaitSignal(), kStart);
EXPECT_TRUE(proxy->SignalPeer(kFinish));
EXPECT_EQ(proxy->AwaitSignal(), kFinish);
EXPECT_EQ(MeasurePool(), 0U);
// Add some counters.
EXPECT_TRUE(proxy->SignalPeer(kStart));
EXPECT_EQ(proxy->AwaitSignal(), kStart);
auto& module = modules[0];
module[0] = 4;
module[module.num_pcs() / 2] = 16;
module[module.num_pcs() - 1] = 128;
EXPECT_TRUE(proxy->SignalPeer(kFinish));
EXPECT_EQ(proxy->AwaitSignal(), kFinish);
EXPECT_EQ(MeasurePool(), 3U);
}
TEST_F(ProcessTest, UpdateOnExit) {
auto modules = CreateModulesAndInitFirst();
TestProcess process;
auto proxy = MakeAndBindProxy(process);
// Add some counters.
EXPECT_TRUE(proxy->SignalPeer(kStart));
EXPECT_EQ(proxy->AwaitSignal(), kStart);
auto& module = modules[0];
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(MeasurePool(), 4U);
}
TEST_F(ProcessTest, StopWithoutLeaks) {
auto modules = CreateModulesAndInitFirst();
TestProcess process;
auto proxy = MakeAndBindProxy(process);
// No mallocs/frees, and no leak detection.
EXPECT_TRUE(proxy->SignalPeer(kStart));
EXPECT_EQ(proxy->AwaitSignal(), kStart);
EXPECT_TRUE(proxy->SignalPeer(kFinish));
EXPECT_EQ(proxy->AwaitSignal(), kFinish);
// Balanced mallocs/frees, and no leak detection.
// The pointers and sizes don't actually matter; just the number of calls.
EXPECT_TRUE(proxy->SignalPeer(kStart));
EXPECT_EQ(proxy->AwaitSignal(), kStart);
process.OnMalloc(nullptr, 0);
process.OnMalloc(nullptr, 0);
process.OnFree(nullptr);
process.OnMalloc(nullptr, 0);
process.OnFree(nullptr);
process.OnFree(nullptr);
EXPECT_TRUE(proxy->SignalPeer(kFinish));
EXPECT_EQ(proxy->AwaitSignal(), kFinish);
// No mallocs/frees, with leak detection.
EXPECT_TRUE(proxy->SignalPeer(kStartLeakCheck));
EXPECT_EQ(proxy->AwaitSignal(), kStart);
EXPECT_TRUE(proxy->SignalPeer(kFinish));
EXPECT_EQ(proxy->AwaitSignal(), kFinish);
// Balanced mallocs/frees, with leak detection.
EXPECT_TRUE(proxy->SignalPeer(kStartLeakCheck));
EXPECT_EQ(proxy->AwaitSignal(), kStart);
process.OnMalloc(nullptr, 0);
process.OnMalloc(nullptr, 0);
process.OnFree(nullptr);
process.OnMalloc(nullptr, 0);
process.OnFree(nullptr);
process.OnFree(nullptr);
EXPECT_TRUE(proxy->SignalPeer(kFinish));
EXPECT_TRUE(proxy->SignalPeer(kFinish));
EXPECT_EQ(proxy->AwaitSignal(), kFinish);
}
TEST_F(ProcessTest, StopWithLeaks) {
auto modules = CreateModulesAndInitFirst();
TestProcess process;
auto proxy = MakeAndBindProxy(process);
// Unbalanced mallocs/frees, and no leak detection.
// The pointers and sizes don't actually matter; just the number of calls.
EXPECT_TRUE(proxy->SignalPeer(kStart));
EXPECT_EQ(proxy->AwaitSignal(), kStart);
process.OnMalloc(nullptr, 0);
process.OnMalloc(nullptr, 0);
process.OnFree(nullptr);
EXPECT_TRUE(proxy->SignalPeer(kFinish));
EXPECT_EQ(proxy->AwaitSignal(), kFinishWithLeaks);
// Unbalanced mallocs/frees, with leak detection.
// Since these aren't real leaks, this will not abort.
EXPECT_TRUE(proxy->SignalPeer(kStartLeakCheck));
EXPECT_EQ(proxy->AwaitSignal(), kStart);
process.OnMalloc(nullptr, 0);
process.OnMalloc(nullptr, 0);
process.OnFree(nullptr);
EXPECT_TRUE(proxy->SignalPeer(kFinish));
EXPECT_EQ(proxy->AwaitSignal(), kFinishWithLeaks);
}
} // namespace
} // namespace fuzzing