blob: 2644e6e0e2ad236ac3e55c09d5cbcfa0edab5f6d [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.
#include "src/sys/fuzzing/common/runner-unittest.h"
#include <zircon/status.h>
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
#include "src/sys/fuzzing/common/testing/monitor.h"
#include "src/sys/fuzzing/common/testing/runner.h"
namespace fuzzing {
// |Cleanse| tries to replace bytes with 0x20 or 0xff.
static constexpr size_t kNumReplacements = 2;
// |Merge| uses real OOMs.
static constexpr size_t kSmallOomLimit = 1ULL << 26; // 64 MB
// Test fixtures.
void RunnerTest::SetUp() {
AsyncTest::SetUp();
handler_ = [](const Input& input) { return FuzzResult::NO_ERRORS; };
}
void RunnerTest::Configure(const OptionsPtr& overrides) {
auto runner = this->runner();
options_ = runner->options();
SetOptions(options_.get(), *overrides);
options_->set_seed(1);
FUZZING_EXPECT_OK(runner->Configure());
RunUntilIdle();
}
void RunnerTest::SetCoverage(const Input& input, Coverage coverage) {
coverage_[input.ToHex()] = std::move(coverage);
}
void RunnerTest::SetFuzzResultHandler(FuzzResultHandler handler) { handler_ = std::move(handler); }
void RunnerTest::SetLeak(bool has_leak) { has_leak_ = has_leak; }
Promise<Input> RunnerTest::RunOne() {
return RunOne([this](const Input& input) {
return SetFeedback(Coverage(coverage_[input.ToHex()]), handler_(input), has_leak_);
});
}
Promise<Input> RunnerTest::RunOne(Coverage coverage) {
return RunOne([this, coverage = std::move(coverage)](const Input& input) {
return SetFeedback(std::move(coverage), handler_(input), has_leak_);
});
}
Promise<Input> RunnerTest::RunOne(FuzzResult fuzz_result) {
return RunOne([this, fuzz_result](const Input& input) {
return SetFeedback(Coverage(coverage_[input.ToHex()]), fuzz_result, has_leak_);
});
}
Promise<Input> RunnerTest::RunOne(bool has_leak) {
return RunOne([this, has_leak](const Input& input) {
return SetFeedback(Coverage(coverage_[input.ToHex()]), handler_(input), has_leak);
});
}
Promise<Input> RunnerTest::RunOne(fit::function<ZxPromise<>(const Input&)> set_feedback) {
Bridge<> bridge;
auto task = fpromise::make_promise([]() -> ZxResult<> { return fpromise::ok(); })
.and_then(GetTestInput())
.and_then([set_feedback = std::move(set_feedback)](Input& input) mutable {
return set_feedback(input).and_then([input = std::move(input)]() mutable {
return fpromise::ok(std::move(input));
});
})
.or_else([](const zx_status_t& status) {
// Target may close before returning test input.
EXPECT_EQ(status, ZX_ERR_PEER_CLOSED) << zx_status_get_string(status);
return fpromise::error();
})
.then([completer = std::move(bridge.completer)](Result<Input>& result) mutable {
if (result.is_ok()) {
completer.complete_ok();
} else {
completer.complete_error();
}
return std::move(result);
});
auto consumer = std::move(previous_run_);
previous_run_ = std::move(bridge.consumer);
return consumer ? consumer.promise().and_then(std::move(task)).box() : task.box();
}
ZxResult<Artifact> RunnerTest::RunUntil(ZxPromise<Artifact> promise) {
ZxResult<Artifact> out;
RunUntil(promise.then([&out](ZxResult<Artifact>& result) { out = std::move(result); }));
return out;
}
ZxResult<> RunnerTest::RunUntil(ZxPromise<> promise) {
ZxResult<> out;
RunUntil(promise.then([&out](ZxResult<>& result) { out = std::move(result); }));
return out;
}
void RunnerTest::RunUntil(Promise<> promise) {
Barrier barrier;
Schedule(promise.wrap_with(barrier));
auto task =
fpromise::make_promise([this, run = Future<Input>(), until = Future<>(barrier.sync())](
Context& context) mutable -> Result<> {
// while (result.is_ok() && !until(context)) {
while (!until(context)) {
if (!run) {
run = RunOne();
}
if (!run(context)) {
return fpromise::pending();
}
if (run.is_error()) {
// Ignore errors; they were checked in |RunOne|.
return fpromise::ok();
}
run = Future<Input>();
}
// A call to |RunOne| was dropped, and the |previous_run_|'s completer will not be called.
previous_run_ = Consumer<>();
return fpromise::ok();
}).wrap_with(scope_);
FUZZING_EXPECT_OK(std::move(task));
RunUntilIdle();
}
void RunnerTest::TearDown() {
// Clear temporary files.
std::vector<std::string> paths;
if (files::ReadDirContents("/tmp", &paths)) {
for (const auto& path : paths) {
files::DeletePath(files::JoinPath("/tmp", path), /* recursive */ true);
}
}
AsyncTest::TearDown();
}
// Unit tests.
void RunnerTest::InitializeCorpus() {
constexpr const char* kPkgDir = "/tmp/initialize-corpus-test";
auto runner = this->runner();
// Only "data/..." directories are considered corpora.
auto pkg_path = files::JoinPath(kPkgDir, "non-data/corpus");
ASSERT_TRUE(files::CreateDirectory(pkg_path));
SetParameters({"non-data/corpus"});
FUZZING_EXPECT_ERROR(runner->Initialize(kPkgDir, GetParameters()), ZX_ERR_INVALID_ARGS);
RunUntilIdle();
// Directory must exist.
SetParameters({"data/invalid-corpus"});
FUZZING_EXPECT_ERROR(runner->Initialize(kPkgDir, GetParameters()), ZX_ERR_NOT_FOUND);
RunUntilIdle();
// Directory contents are added.
pkg_path = files::JoinPath(kPkgDir, "data/corpus1");
std::vector<Input> expected;
ASSERT_NO_FATAL_FAILURE(MakeCorpus(pkg_path, {"foo", "bar"}, &expected));
SetParameters({"data/corpus1"});
FUZZING_EXPECT_OK(runner->Initialize(kPkgDir, GetParameters()));
RunUntilIdle();
auto actual = runner->GetCorpus(CorpusType::SEED);
std::sort(actual.begin(), actual.end());
EXPECT_EQ(actual, expected);
// Multiple corpora can be added.
pkg_path = files::JoinPath(kPkgDir, "data/corpus2");
ASSERT_NO_FATAL_FAILURE(MakeCorpus(pkg_path, {"baz", "qux", "quux"}, &expected));
SetParameters({"data/corpus1", "data/corpus2"});
FUZZING_EXPECT_OK(runner->Initialize(kPkgDir, GetParameters()));
RunUntilIdle();
actual = runner->GetCorpus(CorpusType::SEED);
std::sort(actual.begin(), actual.end());
EXPECT_EQ(actual, expected);
}
void RunnerTest::InitializeDictionary() {
constexpr const char* kPkgDir = "/tmp/initialize-dictionary-test";
auto runner = this->runner();
// Only "data/..." files are considered dictionaries.
auto pkg_path = files::JoinPath(kPkgDir, "non-data/some-file");
ASSERT_NO_FATAL_FAILURE(WriteInput(pkg_path, FakeRunner::valid_dictionary()));
SetParameters({"non-data/some-file"});
FUZZING_EXPECT_ERROR(runner->Initialize(kPkgDir, GetParameters()), ZX_ERR_INVALID_ARGS);
RunUntilIdle();
// File must exist.
SetParameters({"data/invalid-dictionary"});
FUZZING_EXPECT_ERROR(runner->Initialize(kPkgDir, GetParameters()), ZX_ERR_NOT_FOUND);
RunUntilIdle();
// Dictionary must have a valid format.
pkg_path = files::JoinPath(kPkgDir, "data/invalid-dictionary");
ASSERT_NO_FATAL_FAILURE(WriteInput(pkg_path, FakeRunner::invalid_dictionary()));
FUZZING_EXPECT_ERROR(runner->Initialize(kPkgDir, GetParameters()), ZX_ERR_INVALID_ARGS);
RunUntilIdle();
// Valid.
pkg_path = files::JoinPath(kPkgDir, "data/dictionary1");
ASSERT_NO_FATAL_FAILURE(WriteInput(pkg_path, FakeRunner::valid_dictionary()));
SetParameters({"data/dictionary1"});
FUZZING_EXPECT_OK(runner->Initialize(kPkgDir, GetParameters()));
RunUntilIdle();
EXPECT_EQ(runner->GetDictionaryAsInput(), FakeRunner::valid_dictionary());
// At most one dictionary is supported.
pkg_path = files::JoinPath(kPkgDir, "data/dictionary2");
ASSERT_NO_FATAL_FAILURE(WriteInput(pkg_path, FakeRunner::valid_dictionary()));
SetParameters({"data/dictionary1", "data/dictionary2"});
FUZZING_EXPECT_ERROR(runner->Initialize(kPkgDir, GetParameters()), ZX_ERR_INVALID_ARGS);
RunUntilIdle();
}
void RunnerTest::FuzzUntilError() {
auto runner = this->runner();
auto options = MakeOptions();
options->set_detect_exits(true);
options->set_mutation_depth(1);
Configure(options);
Artifact artifact;
FUZZING_EXPECT_OK(runner->Fuzz(), &artifact);
// Add some corpus elements.
std::vector<Input> inputs;
inputs.emplace_back("foo");
inputs.emplace_back("bar");
inputs.emplace_back("baz");
inputs.emplace_back("qux");
auto last = CorpusType::LIVE;
auto next = CorpusType::SEED;
for (const auto& input : inputs) {
EXPECT_EQ(runner->AddToCorpus(next, input.Duplicate()), ZX_OK);
std::swap(next, last);
}
// Set some coverage for the inputs above. Some runners (e.g. libFuzzer) won't mutate an input
// that lacks any coverage. According to the AFL bucketing scheme used by libFuzzer and others,
// the counter must be at least 2 to map to a coverage "feature".
for (size_t i = 0; i < inputs.size(); ++i) {
SetCoverage(inputs[i], {{i + 1, i + 1}});
}
std::vector<Input> actual;
for (size_t i = 0; i < 100; ++i) {
FUZZING_EXPECT_OK(RunOne().then([&](Result<Input>& result) {
if (result.is_ok()) {
actual.push_back(result.take_value());
}
}));
}
FUZZING_EXPECT_OK(RunOne(FuzzResult::EXIT));
RunUntilIdle();
// Helper lambda to check if the sequence of bytes given by |needle| appears in order, but not
// necessarily contiguously, in the sequence of bytes given by |haystack|.
auto contains = [](const Input& haystack, const Input& needle) -> bool {
const auto* needle_data = needle.data();
const auto* haystack_data = haystack.data();
size_t i = 0;
for (size_t j = 0; i < needle.size() && j < haystack.size(); ++j) {
if (needle_data[i] == haystack_data[j]) {
++i;
}
}
return i == needle.size();
};
// Verify that each corpus element is 1) used as-is, and 2) used as the basis for mutations.
for (auto& orig : inputs) {
bool exact_match_found = false;
bool near_match_found = false;
for (auto& input : actual) {
if (orig == input) {
exact_match_found = true;
} else if (contains(input, orig)) {
near_match_found = true;
}
if (exact_match_found && near_match_found) {
break;
}
}
EXPECT_TRUE(exact_match_found) << "input: " << orig.ToHex();
EXPECT_TRUE(near_match_found) << "input: " << orig.ToHex();
}
auto fuzz_result = artifact.fuzz_result();
EXPECT_TRUE(fuzz_result == FuzzResult::EXIT || fuzz_result == FuzzResult::CRASH);
}
void RunnerTest::FuzzUntilRuns() {
auto runner = this->runner();
auto options = MakeOptions();
const size_t kNumRuns = 10;
options->set_runs(kNumRuns);
Configure(options);
std::vector<std::string> expected({""});
// Subscribe to status updates.
FakeMonitor monitor(executor());
runner->AddMonitor(monitor.NewBinding());
// Fuzz for exactly |kNumRuns|.
Artifact artifact;
FUZZING_EXPECT_OK(runner->Fuzz(), &artifact);
for (size_t i = 0; i < kNumRuns; ++i) {
FUZZING_EXPECT_OK(RunOne({{i, i}}));
}
// Check that we get the expected status updates.
FUZZING_EXPECT_OK(monitor.AwaitUpdate());
RunUntilIdle();
EXPECT_EQ(monitor.reason(), UpdateReason::INIT);
auto status = monitor.take_status();
ASSERT_TRUE(status.has_running());
EXPECT_TRUE(status.running());
ASSERT_TRUE(status.has_runs());
auto runs = status.runs();
ASSERT_TRUE(status.has_elapsed());
EXPECT_GT(status.elapsed(), 0U);
auto elapsed = status.elapsed();
ASSERT_TRUE(status.has_covered_pcs());
EXPECT_GE(status.covered_pcs(), 0U);
auto covered_pcs = status.covered_pcs();
monitor.pop_front();
FUZZING_EXPECT_OK(monitor.AwaitUpdate());
RunUntilIdle();
EXPECT_EQ(size_t(monitor.reason()), size_t(UpdateReason::NEW));
status = monitor.take_status();
ASSERT_TRUE(status.has_running());
EXPECT_TRUE(status.running());
ASSERT_TRUE(status.has_runs());
EXPECT_GT(status.runs(), runs);
runs = status.runs();
ASSERT_TRUE(status.has_elapsed());
EXPECT_GT(status.elapsed(), elapsed);
elapsed = status.elapsed();
ASSERT_TRUE(status.has_covered_pcs());
EXPECT_GT(status.covered_pcs(), covered_pcs);
covered_pcs = status.covered_pcs();
// Skip others up to DONE.
while (monitor.reason() != UpdateReason::DONE) {
monitor.pop_front();
FUZZING_EXPECT_OK(monitor.AwaitUpdate());
RunUntilIdle();
}
status = monitor.take_status();
ASSERT_TRUE(status.has_running());
EXPECT_FALSE(status.running());
ASSERT_TRUE(status.has_runs());
EXPECT_GE(status.runs(), runs);
ASSERT_TRUE(status.has_elapsed());
EXPECT_GT(status.elapsed(), elapsed);
ASSERT_TRUE(status.has_covered_pcs());
EXPECT_GE(status.covered_pcs(), covered_pcs);
// All done.
EXPECT_EQ(artifact.fuzz_result(), FuzzResult::NO_ERRORS);
}
void RunnerTest::FuzzUntilTime() {
auto runner = this->runner();
// Time is always tricky to test. As a result, this test verifies the bare minimum, namely that
// the runner exits at least 100 ms after it started. All other verification is performed in more
// controllable tests, such as |FuzzUntilRuns| above.
auto options = MakeOptions();
options->set_max_total_time(zx::msec(100).get());
Configure(options);
auto start = zx::clock::get_monotonic();
auto result = RunUntil(runner->Fuzz());
ASSERT_TRUE(result.is_ok());
auto artifact = result.take_value();
EXPECT_EQ(artifact.fuzz_result(), FuzzResult::NO_ERRORS);
auto elapsed = zx::clock::get_monotonic() - start;
EXPECT_GE(elapsed, zx::msec(100));
}
void RunnerTest::TryOneNoError() {
Configure(MakeOptions());
Input input({0x01});
Artifact artifact;
FUZZING_EXPECT_OK(runner()->TryOne(input.Duplicate()), &artifact);
FUZZING_EXPECT_OK(RunOne(), std::move(input));
RunUntilIdle();
EXPECT_EQ(artifact.fuzz_result(), FuzzResult::NO_ERRORS);
EXPECT_FALSE(artifact.has_input());
}
void RunnerTest::TryOneWithError() {
Configure(MakeOptions());
Input input({0x02});
Artifact artifact;
FUZZING_EXPECT_OK(runner()->TryOne(input.Duplicate()), &artifact);
FUZZING_EXPECT_OK(RunOne(FuzzResult::BAD_MALLOC), std::move(input));
RunUntilIdle();
EXPECT_TRUE(artifact.fuzz_result() == FuzzResult::BAD_MALLOC ||
artifact.fuzz_result() == FuzzResult::OOM);
}
void RunnerTest::TryOneWithLeak() {
auto options = MakeOptions();
options->set_detect_leaks(true);
Configure(options);
Input input({0x03});
Artifact artifact;
// Simulate a suspected leak, followed by an LSan exit. The leak detection heuristics only run
// full leak detection when a leak is suspected based on mismatched allocations.
SetLeak(true);
FUZZING_EXPECT_OK(runner()->TryOne(input.Duplicate()), &artifact);
FUZZING_EXPECT_OK(RunOne(), input.Duplicate());
FUZZING_EXPECT_OK(RunOne(FuzzResult::LEAK), std::move(input));
RunUntilIdle();
EXPECT_EQ(artifact.fuzz_result(), FuzzResult::LEAK);
}
// Simulate no error on the original input.
void RunnerTest::MinimizeNoError() {
Configure(MakeOptions());
Input input({0x04});
FUZZING_EXPECT_ERROR(runner()->ValidateMinimize(input.Duplicate()), ZX_ERR_INVALID_ARGS);
FUZZING_EXPECT_OK(RunOne(), std::move(input));
RunUntilIdle();
}
// Empty input should exit immediately.
void RunnerTest::MinimizeEmpty() {
Configure(MakeOptions());
Input input;
SetFuzzResultHandler([](const Input& input) { return FuzzResult::CRASH; });
Artifact validated;
FUZZING_EXPECT_OK(runner()->ValidateMinimize(input.Duplicate()), &validated);
FUZZING_EXPECT_OK(RunOne(), input.Duplicate());
RunUntilIdle();
EXPECT_EQ(validated.fuzz_result(), FuzzResult::CRASH);
ASSERT_TRUE(validated.has_input());
EXPECT_EQ(validated.input(), input);
auto result = RunUntil(runner()->Minimize(std::move(validated)));
ASSERT_TRUE(result.is_ok());
auto artifact = result.take_value();
EXPECT_EQ(artifact.fuzz_result(), FuzzResult::MINIMIZED);
ASSERT_TRUE(artifact.has_input());
EXPECT_EQ(artifact.input(), input);
}
// 1-byte input should exit immediately.
void RunnerTest::MinimizeOneByte() {
Configure(MakeOptions());
Input input({0x44});
SetFuzzResultHandler([](const Input& input) { return FuzzResult::CRASH; });
Artifact validated;
FUZZING_EXPECT_OK(runner()->ValidateMinimize(input.Duplicate()), &validated);
FUZZING_EXPECT_OK(RunOne(), input.Duplicate());
RunUntilIdle();
EXPECT_EQ(validated.fuzz_result(), FuzzResult::CRASH);
ASSERT_TRUE(validated.has_input());
EXPECT_EQ(validated.input(), input);
auto result = RunUntil(runner()->Minimize(std::move(validated)));
ASSERT_TRUE(result.is_ok());
auto minimized = result.take_value();
EXPECT_EQ(minimized.fuzz_result(), FuzzResult::MINIMIZED);
ASSERT_TRUE(minimized.has_input());
EXPECT_EQ(minimized.input(), input);
}
void RunnerTest::MinimizeReduceByTwo() {
auto options = MakeOptions();
constexpr size_t kRuns = 0x40;
options->set_runs(kRuns);
Configure(options);
Input input({0x51, 0x52, 0x53, 0x54, 0x55, 0x56});
Artifact validated;
FUZZING_EXPECT_OK(runner()->ValidateMinimize(input.Duplicate()), &validated);
FUZZING_EXPECT_OK(RunOne(FuzzResult::CRASH), input.Duplicate());
RunUntilIdle();
EXPECT_EQ(validated.fuzz_result(), FuzzResult::CRASH);
ASSERT_TRUE(validated.has_input());
EXPECT_EQ(validated.input(), input);
// Crash until inputs are smaller than 4 bytes.
SetFuzzResultHandler([](const Input& input) {
return input.size() > 3 ? FuzzResult::CRASH : FuzzResult::NO_ERRORS;
});
auto result = RunUntil(runner()->Minimize(std::move(validated)));
ASSERT_TRUE(result.is_ok());
auto minimized = result.take_value();
EXPECT_EQ(minimized.fuzz_result(), FuzzResult::MINIMIZED);
ASSERT_TRUE(minimized.has_input());
EXPECT_EQ(minimized.input().size(), 4U);
}
void RunnerTest::MinimizeNewError() {
auto options = MakeOptions();
options->set_run_limit(zx::msec(500).get());
Configure(options);
Input input({0x05, 0x15, 0x25, 0x35});
Artifact validated;
FUZZING_EXPECT_OK(runner()->ValidateMinimize(input.Duplicate()), &validated);
FUZZING_EXPECT_OK(RunOne(FuzzResult::CRASH), input.Duplicate());
RunUntilIdle();
EXPECT_EQ(validated.fuzz_result(), FuzzResult::CRASH);
ASSERT_TRUE(validated.has_input());
EXPECT_EQ(validated.input(), input);
// Simulate a crash on the original input, and a timeout on any smaller input.
SetFuzzResultHandler([](const Input& input) {
return input.size() > 3 ? FuzzResult::CRASH : FuzzResult::TIMEOUT;
});
auto result = RunUntil(runner()->Minimize(std::move(validated)));
ASSERT_TRUE(result.is_ok());
auto minimized = result.take_value();
EXPECT_EQ(minimized.fuzz_result(), FuzzResult::MINIMIZED);
ASSERT_TRUE(minimized.has_input());
EXPECT_EQ(minimized.input(), input);
}
void RunnerTest::CleanseNoReplacement() {
Configure(MakeOptions());
Input input({0x07, 0x17, 0x27});
Artifact artifact;
FUZZING_EXPECT_OK(runner()->Cleanse(input.Duplicate()), &artifact);
// Simulate no error after cleansing any byte.
for (size_t i = 0; i < input.size(); ++i) {
for (size_t j = 0; j < kNumReplacements; ++j) {
FUZZING_EXPECT_OK(RunOne(FuzzResult::NO_ERRORS));
}
}
RunUntilIdle();
EXPECT_EQ(artifact.fuzz_result(), FuzzResult::CLEANSED);
EXPECT_EQ(artifact.input(), input);
}
void RunnerTest::CleanseAlreadyClean() {
Configure(MakeOptions());
Input input({' ', 0xff});
Artifact artifact;
FUZZING_EXPECT_OK(runner()->Cleanse(input.Duplicate()), &artifact);
// All bytes match replacements, so this should be done.
RunUntilIdle();
EXPECT_EQ(artifact.fuzz_result(), FuzzResult::CLEANSED);
EXPECT_EQ(artifact.input(), input);
}
void RunnerTest::CleanseTwoBytes() {
Configure(MakeOptions());
Input input({0x08, 0x18, 0x28});
Input inputs[] = {
{0x20, 0x18, 0x28}, // 1st try.
{0xff, 0x18, 0x28}, //
{0x08, 0x20, 0x28}, //
{0x08, 0xff, 0x28}, //
{0x08, 0x18, 0x20}, //
{0x08, 0x18, 0xff}, // Error on 2nd replacement of 3rd byte.
{0x20, 0x18, 0xff}, // 2nd try. Error on 1st replacement of 1st byte.
{0x20, 0x20, 0xff}, //
{0x20, 0xff, 0xff}, //
{0x20, 0x20, 0xff}, // 3rd try. No errors.
{0x20, 0xff, 0xff}, //
};
SetFuzzResultHandler([](const Input& input) {
auto hex = input.ToHex();
return (hex == "081828" || hex == "0818ff" || hex == "2018ff") ? FuzzResult::DEATH
: FuzzResult::NO_ERRORS;
});
Artifact artifact;
FUZZING_EXPECT_OK(runner()->Cleanse(std::move(input)), &artifact);
for (auto& input : inputs) {
FUZZING_EXPECT_OK(RunOne(), std::move(input));
}
RunUntilIdle();
EXPECT_EQ(artifact.fuzz_result(), FuzzResult::CLEANSED);
EXPECT_EQ(artifact.input(), Input({0x20, 0x18, 0xff}));
}
void RunnerTest::MergeSeedError() {
auto runner = this->runner();
auto options = MakeOptions();
options->set_oom_limit(kSmallOomLimit);
Configure(options);
Input input1({0x0a});
runner->AddToCorpus(CorpusType::SEED, input1.Duplicate());
// Triggers error.
Input input2({0x0b});
SetFuzzResultHandler([](const Input& input) {
return input.ToHex() == "0b" ? FuzzResult::OOM : FuzzResult::NO_ERRORS;
});
runner->AddToCorpus(CorpusType::SEED, input2.Duplicate());
Input input3({0x0c, 0x0c});
runner->AddToCorpus(CorpusType::SEED, input3.Duplicate());
auto result = RunUntil(runner->ValidateMerge());
ASSERT_TRUE(result.is_error());
EXPECT_EQ(result.take_error(), ZX_ERR_INVALID_ARGS);
}
void RunnerTest::Merge(bool keeps_errors) {
auto runner = this->runner();
auto options = MakeOptions();
options->set_oom_limit(kSmallOomLimit);
Configure(options);
std::vector<std::string> expected_seed;
std::vector<std::string> expected_live;
// Seed input => kept.
Input input1({0x0a});
SetCoverage(input1, {{0, 1}, {1, 2}, {2, 3}});
runner->AddToCorpus(CorpusType::SEED, input1.Duplicate());
expected_seed.push_back(input1.ToHex());
// Triggers error => maybe kept.
Input input2({0x0b});
SetFuzzResultHandler([](const Input& input) {
return input.ToHex() == "0b" ? FuzzResult::OOM : FuzzResult::NO_ERRORS;
});
runner->AddToCorpus(CorpusType::LIVE, input2.Duplicate());
if (keeps_errors) {
expected_live.push_back(input2.ToHex());
}
// Second-smallest and 2 non-seed features => kept.
Input input3({0x0c, 0x0c});
SetCoverage(input3, {{0, 2}, {2, 2}});
runner->AddToCorpus(CorpusType::LIVE, input3.Duplicate());
expected_live.push_back(input3.ToHex());
// Larger and 1 feature not in any smaller inputs => kept.
Input input4({0x0d, 0x0d, 0x0d});
SetCoverage(input4, {{0, 2}, {1, 1}});
runner->AddToCorpus(CorpusType::LIVE, input4.Duplicate());
expected_live.push_back(input4.ToHex());
// Second-smallest but only 1 non-seed feature above => skipped.
Input input5({0x0e, 0x0e});
SetCoverage(input5, {{0, 2}, {2, 3}});
runner->AddToCorpus(CorpusType::LIVE, input5.Duplicate());
// Smallest but features are subset of seed corpus => skipped.
Input input6({0x0f});
SetCoverage(input6, {{0, 1}, {2, 3}});
runner->AddToCorpus(CorpusType::LIVE, input6.Duplicate());
// Largest with all 3 of the new features => skipped.
Input input7({0x10, 0x10, 0x10, 0x10});
SetCoverage(input7, {{0, 2}, {1, 1}, {2, 2}});
runner->AddToCorpus(CorpusType::LIVE, input7.Duplicate());
auto result1 = RunUntil(runner->ValidateMerge());
ASSERT_TRUE(result1.is_ok());
auto result2 = RunUntil(runner->Merge());
ASSERT_TRUE(result2.is_ok());
auto merged = result2.take_value();
EXPECT_EQ(merged.fuzz_result(), FuzzResult::MERGED);
EXPECT_FALSE(merged.has_input());
auto seed_corpus = runner->GetCorpus(CorpusType::SEED);
std::vector<std::string> actual_seed;
actual_seed.reserve(seed_corpus.size());
for (const auto& input : seed_corpus) {
actual_seed.emplace_back(input.ToHex());
}
std::sort(expected_seed.begin(), expected_seed.end());
std::sort(actual_seed.begin(), actual_seed.end());
EXPECT_EQ(expected_seed, actual_seed);
auto live_corpus = runner->GetCorpus(CorpusType::LIVE);
std::vector<std::string> actual_live;
actual_live.reserve(live_corpus.size());
for (const auto& input : live_corpus) {
actual_live.emplace_back(input.ToHex());
}
std::sort(expected_live.begin(), expected_live.end());
std::sort(actual_live.begin(), actual_live.end());
EXPECT_EQ(expected_live, actual_live);
}
void RunnerTest::Stop() {
auto runner = this->runner();
Configure(MakeOptions());
Artifact artifact;
Barrier barrier;
auto task = runner->Fuzz()
.and_then([&artifact](Artifact& result) {
artifact = std::move(result);
return fpromise::ok();
})
.or_else([](zx_status_t& status) -> ZxResult<> {
// TODO(https://fxbug.dev/42060461): fdio sometimes truncates the fuzzer output when
// stopping, and this leads to errors being returned. For the sake of this test,
// ignore those errors.
if (status != ZX_ERR_IO) {
return fpromise::error(status);
}
return fpromise::ok();
})
.wrap_with(barrier);
FUZZING_EXPECT_OK(std::move(task));
FUZZING_EXPECT_OK(executor()
->MakeDelayedPromise(zx::msec(100))
.then([runner](const Result<>& result) { return runner->Stop(); })
.then([runner](const ZxResult<>& result) {
// Should be idempotent.
return runner->Stop();
}));
RunUntil(barrier.sync());
EXPECT_EQ(artifact.fuzz_result(), FuzzResult::NO_ERRORS);
}
} // namespace fuzzing