blob: 8ba845ca5df9194b91003b20c01a0d1a4188bf48 [file] [log] [blame]
// Copyright 2018 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 <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <fbl/auto_call.h>
#include <fbl/macros.h>
#include <fbl/string.h>
#include <fbl/string_buffer.h>
#include <fbl/string_piece.h>
#include <fbl/string_printf.h>
#include <fuzz-utils/fuzzer.h>
#include <fuzz-utils/path.h>
#include <fuzz-utils/string-list.h>
#include <lib/fdio/spawn.h>
#include <lib/zx/process.h>
#include <lib/zx/time.h>
#include <task-utils/walker.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/syscalls/object.h>
#include <zircon/types.h>
namespace fuzzing {
namespace {
// List of supported subcommands
enum Command : uint32_t {
kNone,
kHelp,
kList,
kSeeds,
kStart,
kCheck,
kRepro,
kMerge,
};
// Usage information for specific tool subcommands.
const struct {
Command cmd;
const char* name;
const char* args;
const char* desc;
} kCommands[] = {
{kHelp, "help", "", "Print this message and exit."},
{kList, "list", "[name]", "Lists fuzzers matching 'name' if provided, or all fuzzers."},
{kSeeds, "seeds", "name", "Lists the seed corpus location(s) for the fuzzer."},
{kStart, "start", "name [...]",
"Starts the named fuzzer. Additional arguments are passed to the fuzzer."},
{kCheck, "check", "name",
"Reports information about the named fuzzer, such as execution status, corpus size, and "
"number of artifacts."},
{kRepro, "repro", "name [...]",
"Runs the named fuzzer on specific inputs. If no additional inputs are provided, uses "
"previously found artifacts."},
{kMerge, "merge", "name [...]",
"Merges the corpus for the named fuzzer. If no additional inputs are provided, minimizes the "
"current corpus."},
};
// |kArtifactPrefixes| should matches the prefixes in libFuzzer passed to |Fuzzer::DumpCurrentUnit|
// or |Fuzzer::WriteUnitToFileWithPrefix|.
constexpr const char* kArtifactPrefixes[] = {
"crash", "leak", "mismatch", "oom", "slow-unit", "timeout",
};
constexpr size_t kArtifactPrefixesLen = sizeof(kArtifactPrefixes) / sizeof(kArtifactPrefixes[0]);
} // namespace
// Public methods
Fuzzer::~Fuzzer() {}
zx_status_t Fuzzer::Main(int argc, char** argv) {
Fuzzer fuzzer;
StringList args(argv + 1, argc - 1);
return fuzzer.Run(&args);
}
// Protected methods
Fuzzer::Fuzzer() : cmd_(kNone), out_(stdout), err_(stderr) {}
void Fuzzer::Reset() {
cmd_ = kNone;
name_.clear();
target_.clear();
root_.clear();
resource_path_.Reset();
data_path_.Reset();
inputs_.clear();
options_.clear();
process_.reset();
out_ = stdout;
err_ = stderr;
}
zx_status_t Fuzzer::Run(StringList* args) {
ZX_DEBUG_ASSERT(args);
zx_status_t rc;
if ((rc = SetCommand(args->first())) != ZX_OK || (rc = SetFuzzer(args->next())) != ZX_OK ||
(rc = LoadOptions()) != ZX_OK) {
return rc;
}
const char* arg;
while ((arg = args->next())) {
if (*arg != '-') {
inputs_.push_back(arg);
} else if ((rc = SetOption(arg + 1)) != ZX_OK) {
return rc;
}
}
switch (cmd_) {
case kHelp:
return Help();
case kList:
return List();
case kSeeds:
return Seeds();
case kStart:
return Start();
case kCheck:
return Check();
case kRepro:
return Repro();
case kMerge:
return Merge();
default:
// Shouldn't get here.
ZX_DEBUG_ASSERT(false);
return ZX_ERR_INTERNAL;
}
}
zx_status_t Fuzzer::SetOption(const char* option) {
ZX_DEBUG_ASSERT(option);
const char* ptr = option;
while (*ptr && *ptr != '#' && (*ptr == '-' || isspace(*ptr))) {
++ptr;
}
const char* mark = ptr;
while (*ptr && *ptr != '#' && *ptr != '=' && !isspace(*ptr)) {
++ptr;
}
fbl::String key(mark, ptr - mark);
while (*ptr && *ptr != '#' && (*ptr == '=' || isspace(*ptr))) {
++ptr;
}
mark = ptr;
while (*ptr && *ptr != '#' && !isspace(*ptr)) {
++ptr;
}
fbl::String val(mark, ptr - mark);
return SetOption(key.c_str(), val.c_str());
}
zx_status_t Fuzzer::SetOption(const char* key, const char* value) {
ZX_DEBUG_ASSERT(key);
ZX_DEBUG_ASSERT(value);
// Ignore blank options
if (*key == '\0' && *value == '\0') {
return ZX_OK;
}
// Must have both key and value
if (*key == '\0' || *value == '\0') {
fprintf(err_, "Empty key or value: '%s'='%s'\n", key, value);
return ZX_ERR_INVALID_ARGS;
}
// Save the option
options_.set(key, value);
return ZX_OK;
}
zx_status_t Fuzzer::RebasePath(const char* path, Path* out) {
zx_status_t rc;
out->Reset();
if (!root_.empty() && (rc = out->Push(root_.c_str())) != ZX_OK) {
fprintf(err_, "failed to move to '%s': %s\n", root_.c_str(), zx_status_get_string(rc));
return rc;
}
if ((rc = out->Push(path)) != ZX_OK) {
return rc;
}
return ZX_OK;
}
zx_status_t Fuzzer::GetPackagePath(const char* package, Path* out) {
zx_status_t rc;
if ((rc = RebasePath("pkgfs/packages", out)) != ZX_OK) {
return rc;
}
auto pop_prefix = fbl::MakeAutoCall([&out]() { out->Pop(); });
if ((rc = out->Push(package)) != ZX_OK) {
fprintf(err_, "failed to move to '%s': %s\n", package, zx_status_get_string(rc));
return rc;
}
auto pop_package = fbl::MakeAutoCall([&out]() { out->Pop(); });
auto versions = out->List();
long int max = -1;
const char* max_version = nullptr;
for (const char* version = versions->first(); version; version = versions->next()) {
if (version[0] == '\0') {
continue;
}
char* endptr = nullptr;
long int val = strtol(version, &endptr, 10);
if (endptr[0] != '\0') {
continue;
}
if (val > max) {
max = val;
max_version = version;
}
}
if (!max_version) {
fprintf(err_, "No versions available for package: %s\n", package);
return ZX_ERR_NOT_FOUND;
}
if ((rc = out->Push(max_version)) != ZX_OK) {
fprintf(err_, "failed to move to '%s': %s\n", max_version, zx_status_get_string(rc));
return rc;
}
pop_package.cancel();
pop_prefix.cancel();
return ZX_OK;
}
void Fuzzer::FindZirconFuzzers(const char* zircon_path, const char* target, StringMap* out) {
Path path;
if (RebasePath(zircon_path, &path) != ZX_OK) {
return;
}
auto targets = path.List();
for (const char* t = targets->first(); t; t = targets->next()) {
}
targets->keep_if(target);
for (const char* t = targets->first(); t; t = targets->next()) {
out->set(fbl::StringPrintf("zircon_fuzzers/%s", t).c_str(), path.Join(t).c_str());
}
}
void Fuzzer::FindFuchsiaFuzzers(const char* package, const char* target, StringMap* out) {
Path path;
if (RebasePath("pkgfs/packages", &path) != ZX_OK) {
return;
}
auto packages = path.List();
packages->keep_if("_fuzzers");
packages->keep_if(package);
for (const char* p = packages->first(); p; p = packages->next()) {
if (GetPackagePath(p, &path) != ZX_OK || path.Push("meta") != ZX_OK) {
continue;
}
auto targets = path.List();
targets->keep_if(target);
targets->keep_if(".cmx");
fbl::String abspath;
for (const char* t = targets->first(); t; t = targets->next()) {
fbl::String t1(t, strlen(t) - strlen(".cmx"));
out->set(fbl::StringPrintf("%s/%s", p, t1.c_str()).c_str(),
fbl::StringPrintf("fuchsia-pkg://fuchsia.com/%s#meta/%s", p, t).c_str());
}
}
}
void Fuzzer::FindFuzzers(const char* package, const char* target, StringMap* out) {
if (strstr("zircon_fuzzers", package) != nullptr) {
FindZirconFuzzers("boot/test/fuzz", target, out);
FindZirconFuzzers("system/test/fuzz", target, out);
}
FindFuchsiaFuzzers(package, target, out);
}
static zx_status_t ParseName(const char* name, fbl::String* out_package, fbl::String* out_target) {
const char* sep = name ? strchr(name, '/') : nullptr;
if (!sep) {
return ZX_ERR_NOT_FOUND;
}
out_package->Set(name, sep - name);
out_target->Set(sep + 1);
return ZX_OK;
}
void Fuzzer::FindFuzzers(const char* name, StringMap* out) {
ZX_DEBUG_ASSERT(out);
// Scan the system for available fuzzers
out->clear();
fbl::String package, target;
if (ParseName(name, &package, &target) == ZX_OK) {
FindFuzzers(package.c_str(), target.c_str(), out);
} else if (name) {
FindFuzzers(name, "", out);
FindFuzzers("", name, out);
} else {
FindFuzzers("", "", out);
}
}
void Fuzzer::GetArgs(StringList* out) {
out->clear();
if (strstr(target_.c_str(), "fuchsia-pkg://fuchsia.com/") == target_.c_str()) {
out->push_back("/system/bin/run");
}
out->push_back(target_.c_str());
const char* key;
const char* val;
options_.begin();
while (options_.next(&key, &val)) {
out->push_back(fbl::StringPrintf("-%s=%s", key, val).c_str());
}
for (const char* input = inputs_.first(); input; input = inputs_.next()) {
out->push_back(input);
}
}
zx_status_t Fuzzer::Execute(bool wait_for_completion) {
zx_status_t rc;
StringList args;
GetArgs(&args);
size_t argc = args.length();
const char* argv[argc + 1];
argv[0] = args.first();
fprintf(out_, "+ %s", argv[0]);
for (size_t i = 1; i < argc; ++i) {
argv[i] = args.next();
fprintf(out_, " %s", argv[i]);
}
argv[argc] = nullptr;
fprintf(out_, "\n");
if ((rc = fdio_spawn(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, argv[0], argv,
process_.reset_and_get_address())) != ZX_OK) {
fprintf(err_, "Failed to spawn '%s': %s\n", argv[0], zx_status_get_string(rc));
return rc;
}
if (!wait_for_completion) {
return ZX_OK;
}
if ((rc = process_.wait_one(ZX_TASK_TERMINATED, zx::time::infinite(), nullptr)) != ZX_OK) {
fprintf(err_, "Failed while waiting for process to end: %s\n", zx_status_get_string(rc));
return rc;
}
zx_info_process_t proc_info;
if ((rc = process_.get_info(ZX_INFO_PROCESS, &proc_info, sizeof(proc_info), nullptr,
nullptr)) != ZX_OK) {
fprintf(err_, "Failed to get exit code for process: %s\n", zx_status_get_string(rc));
return rc;
}
if (proc_info.return_code != ZX_OK) {
fprintf(out_, "Fuzzer returned non-zero exit code: %" PRId64 "\n", proc_info.return_code);
}
return ZX_OK;
}
// |fuzzing::Walked| is a |TaskEnumerator| used to find and print status information about a given
// fuzzer |executable|.
class Walker final : public TaskEnumerator {
public:
explicit Walker(const Fuzzer* fuzzer) : fuzzer_(fuzzer) {}
~Walker() {}
zx_status_t OnProcess(int depth, zx_handle_t task, zx_koid_t koid, zx_koid_t pkoid) override {
return fuzzer_->CheckProcess(task) ? ZX_ERR_STOP : ZX_OK;
}
protected:
bool has_on_process() const override { return true; }
private:
const Fuzzer* fuzzer_;
};
bool Fuzzer::CheckProcess(zx_handle_t process) const {
char name[ZX_MAX_NAME_LEN];
zx_info_process_t info;
if (zx_object_get_property(process, ZX_PROP_NAME, name, sizeof(name)) != ZX_OK) {
return false;
}
const char* target = target_.c_str();
const char* meta = strstr(target, "#meta/");
if (meta) {
target = meta + strlen("#meta/");
}
if (strcmp(name, target) != 0 || zx_object_get_info(process, ZX_INFO_PROCESS, &info,
sizeof(info), nullptr, nullptr) != ZX_OK) {
return false;
}
if (!info.started) {
fprintf(out_, "%s: NOT STARTED\n", name_.c_str());
} else if (!info.exited) {
fprintf(out_, "%s: RUNNING\n", name_.c_str());
} else {
fprintf(out_, "%s: EXITED (return code = %" PRId64 ")\n", name_.c_str(), info.return_code);
}
return true;
}
// Private methods
zx_status_t Fuzzer::SetCommand(const char* command) {
cmd_ = kNone;
options_.clear();
inputs_.clear();
if (!command) {
fprintf(err_, "Missing command. Try 'help'.\n");
return ZX_ERR_INVALID_ARGS;
}
for (size_t i = 0; i < sizeof(kCommands) / sizeof(kCommands[0]); ++i) {
if (strcmp(command, kCommands[i].name) == 0) {
cmd_ = kCommands[i].cmd;
break;
}
}
if (cmd_ == kNone) {
fprintf(err_, "Unknown command '%s'. Try 'help'.\n", command);
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
zx_status_t Fuzzer::SetFuzzer(const char* name) {
zx_status_t rc;
// Early exit for commands that don't need a single, selected fuzzer
switch (cmd_) {
case kHelp:
case kList:
if (name) {
name_.Set(name);
}
return ZX_OK;
default:
break;
}
if (!name) {
fprintf(err_, "Missing fuzzer name.\n");
return ZX_ERR_INVALID_ARGS;
}
name_.Set(name);
// Determine the fuzzer
StringMap fuzzers;
FindFuzzers(name, &fuzzers);
switch (fuzzers.size()) {
case 0:
fprintf(err_, "No matching fuzzers for '%s'.\n", name);
return ZX_ERR_NOT_FOUND;
case 1:
break;
default:
fprintf(err_, "Multiple matching fuzzers for '%s':\n", name);
List();
return ZX_ERR_INVALID_ARGS;
}
const char* executable;
fuzzers.begin();
fuzzers.next(&name, &executable);
name_.Set(name);
target_.Set(executable);
fbl::String package, target;
if ((rc = ParseName(name_.c_str(), &package, &target)) != ZX_OK) {
return rc;
}
// Determine the directory that holds the fuzzing resources. It may not be present if fuzzing
// Zircon standalone.
if ((rc = GetPackagePath(package.c_str(), &resource_path_)) != ZX_OK ||
(rc = resource_path_.Push("data")) != ZX_OK ||
(rc = resource_path_.Push(target.c_str())) != ZX_OK) {
// No-op: The directory may not be present when fuzzing standalone Zircon.
resource_path_.Reset();
}
// Ensure the directory that will hold the fuzzing artifacts is present.
if ((rc = RebasePath("data", &data_path_)) != ZX_OK ||
(rc = data_path_.Ensure("fuzzing")) != ZX_OK ||
(rc = data_path_.Push("fuzzing")) != ZX_OK ||
(rc = data_path_.Ensure(package.c_str())) != ZX_OK ||
(rc = data_path_.Push(package.c_str())) != ZX_OK ||
(rc = data_path_.Ensure(target.c_str())) != ZX_OK ||
(rc = data_path_.Push(target.c_str())) != ZX_OK) {
fprintf(err_, "Failed to establish data path for '%s/%s': %s\n", package.c_str(),
target.c_str(), zx_status_get_string(rc));
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t Fuzzer::LoadOptions() {
zx_status_t rc;
switch (cmd_) {
case kHelp:
case kList:
case kSeeds:
// No options needed
return ZX_OK;
case kMerge:
if ((rc = SetOption("merge", "1")) != ZX_OK) {
return rc;
}
break;
default:
break;
}
// Artifacts go in the data directory
if ((rc = SetOption("artifact_prefix", data_path_.c_str())) != ZX_OK) {
return rc;
}
// Early exit if no resources
if (strlen(resource_path_.c_str()) <= 1) {
return ZX_OK;
}
// Record the (optional) dictionary
size_t dict_size;
if ((rc = resource_path_.GetSize("dictionary", &dict_size)) == ZX_OK && dict_size != 0 &&
(rc = SetOption("dict", resource_path_.Join("dictionary").c_str())) != ZX_OK) {
fprintf(err_, "failed to set dictionary option: %s\n", zx_status_get_string(rc));
return rc;
}
// Read the (optional) options file
fbl::String options = resource_path_.Join("options");
FILE* f = fopen(options.c_str(), "r");
if (f) {
auto close_f = fbl::MakeAutoCall([&f]() { fclose(f); });
char buffer[PATH_MAX];
while (fgets(buffer, sizeof(buffer), f)) {
if ((rc = SetOption(buffer)) != ZX_OK) {
fprintf(err_, "Failed to set option: %s", zx_status_get_string(rc));
return rc;
}
}
}
return ZX_OK;
}
// Specific subcommands
zx_status_t Fuzzer::Help() {
fprintf(out_, "usage: fuzz <command> [args]\n\n");
fprintf(out_, "Supported commands are:\n");
for (size_t i = 0; i < sizeof(kCommands) / sizeof(kCommands[0]); ++i) {
fprintf(out_, " %s %s\n", kCommands[i].name, kCommands[i].args);
fprintf(out_, " %s\n\n", kCommands[i].desc);
}
return ZX_OK;
}
zx_status_t Fuzzer::List() {
StringMap fuzzers;
FindFuzzers(name_.c_str(), &fuzzers);
if (fuzzers.is_empty()) {
fprintf(out_, "No matching fuzzers.\n");
return ZX_OK;
}
fprintf(out_, "Found %zu matching fuzzers:\n", fuzzers.size());
const char* name;
fuzzers.begin();
while (fuzzers.next(&name, nullptr)) {
fprintf(out_, " %s\n", name);
}
return ZX_OK;
}
zx_status_t Fuzzer::Seeds() {
if (strlen(resource_path_.c_str()) <= 1) {
fprintf(out_, "No seed corpora found for %s.\n", name_.c_str());
return ZX_OK;
}
fbl::String corpora = resource_path_.Join("corpora");
FILE* f = fopen(corpora.c_str(), "r");
if (!f) {
fprintf(out_, "No seed corpora found for %s.\n", name_.c_str());
return ZX_OK;
}
auto close_f = fbl::MakeAutoCall([&f]() { fclose(f); });
char buffer[PATH_MAX];
while (fgets(buffer, sizeof(buffer), f)) {
fprintf(out_, "%s\n", buffer);
}
return ZX_OK;
}
zx_status_t Fuzzer::Start() {
zx_status_t rc;
// If no inputs, use the default corpus
if (inputs_.is_empty()) {
if ((rc = data_path_.Ensure("corpus")) != ZX_OK) {
fprintf(err_, "Failed to make empty corpus: %s\n", zx_status_get_string(rc));
return rc;
}
inputs_.push_front(data_path_.Join("corpus").c_str());
}
return Execute(false /* !wait_for_completion */);
}
zx_status_t Fuzzer::Check() {
zx_status_t rc;
// Report fuzzer execution status
Walker walker(this);
if (walker.WalkRootJobTree() != ZX_ERR_STOP) {
fprintf(out_, "%s: STOPPED\n", name_.c_str());
}
// Fuzzer details
fprintf(out_, " Target info: %s\n", target_.c_str());
fprintf(out_, " Output path: %s\n", data_path_.c_str());
// Report corpus details, if present
if ((rc = data_path_.Push("corpus")) != ZX_OK) {
fprintf(out_, " Corpus size: 0 inputs / 0 bytes\n");
} else {
auto corpus = data_path_.List();
size_t corpus_size = 0;
for (const char* input = corpus->first(); input; input = corpus->next()) {
size_t input_size;
if ((rc = data_path_.GetSize(input, &input_size)) != ZX_OK) {
return rc;
}
corpus_size += input_size;
}
fprintf(out_, " Corpus size: %zu inputs / %zu bytes\n", corpus->length(), corpus_size);
data_path_.Pop();
}
// Report number of artifacts.
auto artifacts = data_path_.List();
StringList prefixes(kArtifactPrefixes,
sizeof(kArtifactPrefixes) / sizeof(kArtifactPrefixes[0]));
artifacts->keep_if_any(&prefixes);
size_t num_artifacts = artifacts->length();
if (num_artifacts == 0) {
fprintf(out_, " Artifacts: None\n");
} else {
const char* artifact = artifacts->first();
fprintf(out_, " Artifacts: %s\n", artifact);
while ((artifact = artifacts->next())) {
fprintf(out_, " %s\n", artifact);
}
}
return ZX_OK;
}
zx_status_t Fuzzer::Repro() {
zx_status_t rc;
// If no patterns, match all artifacts
if (inputs_.is_empty()) {
inputs_.push_back("");
}
// Filter data for just artifacts that match one or more supplied patterns
auto artifacts = data_path_.List();
StringList prefixes(kArtifactPrefixes, kArtifactPrefixesLen);
artifacts->keep_if_any(&prefixes);
artifacts->keep_if_any(&inputs_);
// Get full paths of artifacts
inputs_.clear();
for (const char* artifact = artifacts->first(); artifact; artifact = artifacts->next()) {
inputs_.push_back(data_path_.Join(artifact).c_str());
}
// Nothing to repro
if (inputs_.is_empty()) {
fprintf(err_, "No matching artifacts found.\n");
return ZX_ERR_NOT_FOUND;
}
if ((rc = Execute(true /* wait_for_completion */)) != ZX_OK) {
fprintf(err_, "Failed to execute: %s\n", zx_status_get_string(rc));
return rc;
}
return ZX_OK;
}
zx_status_t Fuzzer::Merge() {
zx_status_t rc;
// If no inputs, minimize the previous corpus (and there must be an existing corpus!)
if (inputs_.is_empty()) {
if ((rc = data_path_.Remove("corpus.prev")) != ZX_OK ||
(rc = data_path_.Rename("corpus", "corpus.prev")) != ZX_OK) {
fprintf(err_, "Failed to move 'corpus' for minimization: %s\n",
zx_status_get_string(rc));
return rc;
}
inputs_.push_back(data_path_.Join("corpus.prev").c_str());
}
// Make sure the corpus directory exists, and make sure the output corpus is the first argument
if ((rc = data_path_.Ensure("corpus")) != ZX_OK) {
fprintf(err_, "Failed to ensure 'corpus': %s\n", zx_status_get_string(rc));
return rc;
}
inputs_.erase_if(data_path_.Join("corpus").c_str());
inputs_.push_front(data_path_.Join("corpus").c_str());
if ((rc = Execute(false /* !wait_for_completion */)) != ZX_OK) {
fprintf(err_, "Failed to execute: %s\n", zx_status_get_string(rc));
return rc;
}
return ZX_OK;
}
} // namespace fuzzing