blob: 92551a9eb4d6b2555c40d3182108eba312685d4b [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/target/process.h"
#include <lib/backtrace-request/backtrace-request.h>
#include <lib/syslog/cpp/macros.h>
#include <stddef.h>
#include <stdlib.h>
#include "src/sys/fuzzing/common/module.h"
#include "src/sys/fuzzing/common/options.h"
#include "src/sys/fuzzing/framework/target/weak-symbols.h"
namespace fuzzing {
namespace {
// Maximum number of LLVM modules per process. This limit matches libFuzzer.
constexpr size_t kMaxModules = 4096;
// Memory profile parameters; see compiler-rt/lib/asan/asan_memory_profile.cpp.
constexpr size_t kTopPercentChunks = 95;
constexpr size_t kMaxUniqueContexts = 8;
// Static context; used to store module info until the process singleton is created and to find the
// singleton from the static hook functions. This structure is NOT thread-safe, and should only be
// accessed from the main thread. More precisely, do not load multiple shared libraries concurrently
// from different threads.
struct {
struct {
uint8_t* counters;
size_t counters_len;
const uintptr_t* pcs;
size_t pcs_len;
} modules[kMaxModules];
size_t num_counters = 0;
size_t num_pcs = 0;
Process* process = nullptr;
} gContext;
// Hook functions that simply forward to the singleton.
void MallocHook(const volatile void* ptr, size_t size) { gContext.process->OnMalloc(ptr, size); }
void FreeHook(const volatile void* ptr) { gContext.process->OnFree(ptr); }
void DeathHook() { gContext.process->OnDeath(); }
void ExitHook() { gContext.process->OnExit(); }
} // namespace
} // namespace fuzzing
extern "C" {
// NOLINTNEXTLINE(readability-non-const-parameter)
void __sanitizer_cov_8bit_counters_init(uint8_t* start, uint8_t* stop) {
if (start < stop && fuzzing::gContext.num_counters < fuzzing::kMaxModules) {
auto& info = fuzzing::gContext.modules[fuzzing::gContext.num_counters++];
info.counters = start;
info.counters_len = stop - start;
}
if (fuzzing::gContext.process) {
fuzzing::gContext.process->AddModules();
}
}
void __sanitizer_cov_pcs_init(const uintptr_t* start, const uintptr_t* stop) {
using fuzzing::gContext;
if (start < stop && gContext.num_pcs < fuzzing::kMaxModules) {
auto& info = gContext.modules[gContext.num_pcs++];
info.pcs = start;
info.pcs_len = stop - start;
}
if (gContext.process) {
gContext.process->AddModules();
}
}
} // extern "C"
namespace fuzzing {
Process::Process() : next_purge_(zx::time::infinite()) {
FX_CHECK(!gContext.process);
gContext.process = this;
AddDefaults(&options_);
}
Process::~Process() { memset(&gContext, 0, sizeof(gContext)); }
void Process::AddDefaults(Options* options) {
if (!options->has_detect_leaks()) {
options->set_detect_leaks(kDefaultDetectLeaks);
}
if (!options->has_malloc_limit()) {
options->set_malloc_limit(kDefaultMallocLimit);
}
if (!options->has_oom_limit()) {
options->set_oom_limit(kDefaultOomLimit);
}
if (!options->has_purge_interval()) {
options->set_purge_interval(kDefaultPurgeInterval);
}
if (!options->has_malloc_exitcode()) {
options->set_malloc_exitcode(kDefaultMallocExitcode);
}
if (!options->has_death_exitcode()) {
options->set_death_exitcode(kDefaultDeathExitcode);
}
if (!options->has_leak_exitcode()) {
options->set_leak_exitcode(kDefaultLeakExitcode);
}
if (!options->has_oom_exitcode()) {
options->set_oom_exitcode(kDefaultOomExitcode);
}
}
void Process::InstallHooks() {
// This method can only be called once.
static bool first = true;
FX_CHECK(!first) << "InstallHooks called more than once!";
first = false;
// Warn about missing symbols.
if (!__sanitizer_acquire_crash_state) {
FX_LOGS(WARNING) << "Missing '__sanitizer_acquire_crash_state'.";
}
if (!__sanitizer_set_death_callback) {
FX_LOGS(WARNING) << "Missing '__sanitizer_set_death_callback'.";
}
// Install hooks.
if (__sanitizer_set_death_callback) {
__sanitizer_set_death_callback(DeathHook);
}
if (__sanitizer_install_malloc_and_free_hooks) {
__sanitizer_install_malloc_and_free_hooks(MallocHook, FreeHook);
}
std::atexit([]() { ExitHook(); });
}
void Process::Connect(ProcessProxySyncPtr&& proxy) {
std::lock_guard<std::mutex> lock(mutex_);
// This method can only be called once.
if (connected_) {
return;
}
proxy_ = std::move(proxy);
connected_ = true;
// Create the eventpair.
auto eventpair =
coordinator_.Create([this](zx_signals_t observed) { return OnSignal(observed); });
// Duplicate a handle to ourselves.
zx::process process;
auto self = zx::process::self();
self->duplicate(ZX_RIGHT_SAME_RIGHTS, &process);
// Connect to the engine.
proxy_->Connect(std::move(eventpair), std::move(process), &options_);
AddDefaults(&options_);
// Configure allocator purging.
// TODO(fxbug.dev/85284): Add integration tests that produce these and following logs.
auto purge_interval = options_.purge_interval();
if (purge_interval && !__sanitizer_purge_allocator) {
FX_LOGS(WARNING) << "Missing '__sanitizer_purge_allocator'.";
FX_LOGS(WARNING) << "Allocator purging disabled.";
purge_interval = 0;
}
next_purge_ =
purge_interval ? zx::deadline_after(zx::duration(purge_interval)) : zx::time::infinite();
// Check if leak detection is possible.
if (options_.detect_leaks()) {
can_detect_leaks_ = false;
if (!__lsan_enable) {
FX_LOGS(WARNING) << "Missing '__lsan_enable'.";
} else if (!__lsan_disable) {
FX_LOGS(WARNING) << "Missing '__lsan_disable'.";
} else if (!__lsan_do_recoverable_leak_check) {
FX_LOGS(WARNING) << "Missing '__lsan_do_recoverable_leak_check'.";
} else if (!__sanitizer_install_malloc_and_free_hooks) {
FX_LOGS(WARNING) << "Missing '__sanitizer_install_malloc_and_free_hooks'.";
} else {
can_detect_leaks_ = true;
}
if (!can_detect_leaks_) {
FX_LOGS(WARNING) << "Leak detection disabled.";
}
}
// Check if bad malloc detection is possible.
auto malloc_limit = options_.malloc_limit();
if (malloc_limit && !__sanitizer_install_malloc_and_free_hooks) {
FX_LOGS(WARNING) << "Missing '__sanitizer_install_malloc_and_free_hooks'.";
FX_LOGS(WARNING) << "Large allocation detection disabled.";
}
malloc_limit_ = malloc_limit ? malloc_limit : std::numeric_limits<size_t>::max();
// Send the early modules to the engine.
AddModulesLocked();
if (modules_.empty()) {
FX_LOGS(FATAL) << "No modules found; is the code instrumented for fuzzing?";
}
// Processes connect when started, as a result of processing a test input during a fuzzing run.
// This is after the engine would have sent a |kStart| signal, so match that state in |OnSignal|.
num_mallocs_ = 0;
num_frees_ = 0;
detecting_leaks_ = false;
}
void Process::AddModules() {
std::lock_guard<std::mutex> lock(mutex_);
AddModulesLocked();
}
void Process::AddModulesLocked() {
FX_DCHECK(proxy_.is_bound());
for (size_t i = modules_.size(); i < gContext.num_counters && i < gContext.num_pcs; ++i) {
auto& info = gContext.modules[i];
FX_DCHECK(info.counters);
FX_DCHECK(info.counters_len);
FX_DCHECK(info.pcs);
FX_DCHECK(info.pcs_len);
if (info.counters_len == info.pcs_len * sizeof(uintptr_t) / sizeof(ModulePC)) {
Module module(info.counters, info.pcs, info.counters_len);
Feedback feedback;
feedback.set_id(module.id());
feedback.set_inline_8bit_counters(module.Share());
proxy_->AddFeedback(std::move(feedback));
modules_.push_back(std::move(module));
} else {
FX_LOGS(WARNING) << "Length mismatch: counters=" << info.counters_len
<< ", pcs=" << info.pcs_len << "; module will be skipped.";
}
memset(&info, 0, sizeof(info));
}
}
void Process::OnMalloc(const volatile void* ptr, size_t size) {
++num_mallocs_;
if (size > malloc_limit_ && AcquireCrashState()) {
backtrace_request();
_Exit(options_.malloc_exitcode());
}
}
void Process::OnFree(const volatile void* ptr) { ++num_frees_; }
void Process::OnDeath() { _Exit(options_.death_exitcode()); }
void Process::OnExit() {
// Exits may not be fatal, e.g. if detect_exits=false. May sure the process publishes all its
// coverage before it ends as the framework will keep fuzzing.
Update();
}
bool Process::OnSignal(zx_signals_t observed) {
if (observed & ZX_EVENTPAIR_PEER_CLOSED) {
return false;
}
switch (observed) {
case kStart:
case kStartLeakCheck: {
{
std::lock_guard<std::mutex> lock(mutex_);
for (auto& module : modules_) {
module.Clear();
}
}
// See |DetectLeaks| below.
num_mallocs_ = 0;
num_frees_ = 0;
if (can_detect_leaks_ && observed == kStartLeakCheck && !detecting_leaks_) {
detecting_leaks_ = true;
__lsan_disable();
}
return coordinator_.SignalPeer(kStart);
}
case kFinish: {
Update();
// See |DetectLeaks| below.
bool has_leak = DetectLeak();
if (next_purge_ < zx::clock::get_monotonic()) {
__sanitizer_purge_allocator();
next_purge_ = zx::deadline_after(zx::duration(options_.purge_interval()));
}
// TODO(fxbug.dev/84368): The check for OOM is missing!
return coordinator_.SignalPeer(has_leak ? kFinishWithLeaks : kFinish);
}
default:
FX_LOGS(FATAL) << "unexpected signal: 0x" << std::hex << observed << std::dec;
return false;
}
}
bool Process::DetectLeak() {
// As described in the header, full leak detection is expensive. This framework imitates libFuzzer
// and performs a two-pass process:
// 1a. Upon starting a fuzzing iteration,i.e. |OnSignal(kStart)|, it tracks |num_mallocs| and
// |num_frees|.
// 1b. Upon finishing an iteration, i.e. |OnSignal(kFinish)|, it checks if |num_mallocs| equals
// |num_frees| and returns |kFinish| or |kFinishWithLeaks|, as appropriate.
// 2a. Returning |kFinishWithLeaks| will cause the framework to repeat the input with leak
// detection, i.e. |OnSignal(kStartLeakCheck)|. It will disable LSan for this run to avoid
// eventually reporting the same error twice.
// 2b. Upon finishing the second iteration, i.e. |OnSignal(kFinish)| again, it re-enables LSan.
// If |num_mallocs| still does not match |num_frees|, it performs the (expensive) leak
// check. If a true leak, it will report it using info from the first iteration and exit.
bool has_leak = num_mallocs_.exchange(0) != num_frees_.exchange(0);
if (!can_detect_leaks_ || !detecting_leaks_) {
return has_leak;
}
__lsan_enable();
detecting_leaks_ = false;
if (has_leak && __lsan_do_recoverable_leak_check() && AcquireCrashState()) {
if (__sanitizer_print_memory_profile) {
__sanitizer_print_memory_profile(kTopPercentChunks, kMaxUniqueContexts);
}
_Exit(options_.leak_exitcode());
}
return has_leak;
}
void Process::Update() {
std::lock_guard<std::mutex> lock(mutex_);
for (auto& module : modules_) {
module.Update();
}
}
bool Process::AcquireCrashState() {
return __sanitizer_acquire_crash_state && __sanitizer_acquire_crash_state();
}
} // namespace fuzzing