blob: 18ed503b18fb171d8c2eb728eb23a875aad60bcf [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/framework/engine/corpus.h"
#include <lib/async/dispatcher.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/syscalls.h>
#include <algorithm>
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
namespace fuzzing {
// Public methods
Corpus::Corpus() { inputs_.emplace_back(); }
Corpus& Corpus::operator=(Corpus&& other) noexcept {
options_ = other.options_;
other.options_ = nullptr;
prng_ = other.prng_;
inputs_ = std::move(other.inputs_);
total_size_ = other.total_size_;
other.total_size_ = 0;
return *this;
}
void Corpus::AddDefaults(Options* options) {
if (!options->has_seed()) {
options->set_seed(kDefaultSeed);
}
if (!options->has_max_input_size()) {
options->set_max_input_size(kDefaultMaxInputSize);
}
}
void Corpus::Configure(const OptionsPtr& options) {
options_ = options;
prng_.seed(options_->seed());
}
zx_status_t Corpus::LoadAt(const std::string& root, const std::vector<std::string>& dirs) {
for (const auto& dirname : dirs) {
auto status = ReadDir(files::JoinPath(root, dirname));
if (status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t Corpus::Load(const std::vector<std::string>& dirs) { return LoadAt("/pkg", dirs); }
zx_status_t Corpus::ReadDir(const std::string& dirname) {
std::vector<std::string> contents;
if (!files::ReadDirContents(dirname, &contents)) {
FX_LOGS(ERROR) << "No such corpus directory: " << dirname << " (errno=" << errno << ")";
return ZX_ERR_NOT_FOUND;
}
for (const auto& dir_entry : contents) {
if (dir_entry == ".") {
continue;
}
auto pathname = files::SimplifyPath(files::JoinPath(dirname, dir_entry));
zx_status_t status = ZX_OK;
if (files::IsFile(pathname)) {
status = ReadFile(pathname);
} else if (files::IsDirectory(pathname)) {
status = ReadDir(pathname);
}
if (status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t Corpus::ReadFile(const std::string& filename) {
std::vector<uint8_t> input;
if (!files::ReadFileToVector(filename, &input)) {
FX_LOGS(ERROR) << "Failed to read " << filename;
return ZX_ERR_IO;
}
return Add(Input(input));
}
zx_status_t Corpus::Add(Input input) {
FX_DCHECK(options_);
if (input.size() > options_->max_input_size()) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
// Keep the inputs sorted and deduplicated.
auto iter = std::lower_bound(inputs_.begin(), inputs_.end(), input);
if (iter == inputs_.end() || *iter != input) {
total_size_ += input.size();
inputs_.insert(iter, std::move(input));
}
return ZX_OK;
}
zx_status_t Corpus::Add(CorpusPtr corpus) {
if (!corpus) {
return ZX_ERR_INVALID_ARGS;
}
for (auto& input : corpus->inputs_) {
if (auto status = Add(input.Duplicate()); status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
bool Corpus::At(size_t offset, Input* out) {
out->Clear();
if (offset >= inputs_.size()) {
return false;
}
out->Duplicate(inputs_[offset]);
return true;
}
void Corpus::Pick(Input* out) {
// Use rejection sampling to get uniform distribution.
// NOLINTNEXTLINE(google-runtime-int)
static_assert(sizeof(unsigned long long) * 8 == 64);
uint64_t size = inputs_.size();
FX_DCHECK(size > 0);
auto shift = 64 - __builtin_clzll(size);
FX_DCHECK(size < 64);
auto mod = 1ULL << shift;
size_t offset;
do {
offset = prng_() % mod;
} while (offset >= size);
out->Duplicate(inputs_[offset]);
}
} // namespace fuzzing