blob: e4d83844d3c44d021d02df643b88dfb2b3d2b9de [file]
// Copyright 2022 The Centipede Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Fuzz target runner (engine) for Centipede.
// Reads the input files and feeds their contents to
// the fuzz target (RunnerCallbacks::Execute), then dumps the coverage data.
// If the input path is "/path/to/foo",
// the coverage features are dumped to "/path/to/foo-features"
//
// WARNING: please avoid any C++ libraries here, such as Absl and (most of) STL,
// in order to avoid creating new coverage edges in the binary.
#include "./centipede/runner.h"
#include <fcntl.h>
#include <pthread.h> // NOLINT: use pthread to avoid extra dependencies.
#include <sched.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <unistd.h>
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <cinttypes>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "absl/base/nullability.h"
#include "absl/base/optimization.h"
#include "absl/types/span.h"
#include "./centipede/byte_array_mutator.h"
#include "./centipede/engine_abi.h"
#include "./centipede/engine_worker_abi.h"
#include "./centipede/execution_metadata.h"
#include "./centipede/feature.h"
#include "./centipede/mutation_data.h"
#include "./centipede/runner_interface.h"
#include "./centipede/runner_request.h"
#include "./centipede/runner_result.h"
#include "./centipede/runner_utils.h"
#include "./centipede/sancov_runtime.h"
#include "./centipede/sancov_state.h"
#include "./centipede/shared_memory_blob_sequence.h"
#include "./common/defs.h"
namespace fuzztest::internal {
ExplicitLifetime<GlobalRunnerState> state;
namespace {
struct GlobalRunnerStateManager {
GlobalRunnerStateManager() { state.Construct(); }
~GlobalRunnerStateManager() { state->OnTermination(); }
};
GlobalRunnerStateManager state_manager __attribute__((init_priority(200)));
class SpinlockGuard {
public:
SpinlockGuard(const SpinlockGuard&) = delete;
SpinlockGuard& operator=(const SpinlockGuard&) = delete;
SpinlockGuard(SpinlockGuard&& other) : lock_(other.lock_) {
other.lock_ = nullptr;
}
SpinlockGuard& operator=(SpinlockGuard&& other) = delete;
static SpinlockGuard Acquire(std::atomic<bool>& lock) {
while (lock.exchange(true, std::memory_order_acquire)) {
sched_yield();
}
return SpinlockGuard{&lock};
}
static SpinlockGuard TryAcquire(std::atomic<bool>& lock) {
if (lock.exchange(true, std::memory_order_acquire)) {
return SpinlockGuard{nullptr};
}
return SpinlockGuard{&lock};
}
operator bool() const { return lock_ != nullptr; }
~SpinlockGuard() {
if (lock_ != nullptr) {
lock_->store(false, std::memory_order_release);
}
}
private:
explicit SpinlockGuard(std::atomic<bool>* lock) : lock_(lock) {}
std::atomic<bool>* lock_;
};
} // namespace
static size_t GetPeakRSSMb() {
struct rusage usage = {};
if (getrusage(RUSAGE_SELF, &usage) != 0) return 0;
#ifdef __APPLE__
// On MacOS, the unit seems to be byte according to experiment, while some
// documents mentioned KiB. This could depend on OS variants.
return usage.ru_maxrss >> 20;
#else // __APPLE__
// On Linux, ru_maxrss is in KiB
return usage.ru_maxrss >> 10;
#endif // __APPLE__
}
// Returns the current time in microseconds.
static uint64_t TimeInUsec() {
struct timeval tv = {};
constexpr size_t kUsecInSec = 1000000;
gettimeofday(&tv, nullptr);
return tv.tv_sec * kUsecInSec + tv.tv_usec;
}
// Atomic flags to make sure that (a) watchdog failure is reported only for
// the current input, and (b) only one thread is handling watchdog failures.
// True if the watchdog thread is detecting failures, false otherwise.
static std::atomic<bool> watchdog_thread_busy = false;
// True if a watchdog failure is found, false otherwise.
static std::atomic<bool> watchdog_failure_found = false;
static void WaitWatchdogThreadIdle() {
while (ABSL_PREDICT_FALSE(watchdog_thread_busy.load())) {
if (ABSL_PREDICT_FALSE(watchdog_failure_found.load())) {
// A failure is found - wait for the process to terminate.
sleep(1); // NOLINT
} else {
// Busy-wait for the detection.
sleep(0); // NOLINT
}
}
}
static void CheckWatchdogLimits() {
const uint64_t curr_time = time(nullptr);
struct Resource {
const char* what;
const char* units;
uint64_t value;
uint64_t limit;
bool ignore_report;
const char* failure;
};
const uint64_t input_start_time = state->input_start_time;
if (input_start_time == 0) return;
const Resource resources[] = {
{Resource{
/*what=*/"Per-input timeout",
/*units=*/"sec",
/*value=*/curr_time - input_start_time,
/*limit=*/state->run_time_flags.timeout_per_input,
/*ignore_report=*/
state->run_time_flags.ignore_timeout_reports != 0,
/*failure=*/kExecutionFailurePerInputTimeout.data(),
}},
{Resource{
/*what=*/"RSS limit",
/*units=*/"MB",
/*value=*/GetPeakRSSMb(),
/*limit=*/state->run_time_flags.rss_limit_mb,
/*ignore_report=*/false,
/*failure=*/kExecutionFailureRssLimitExceeded.data(),
}},
};
for (const auto& resource : resources) {
if (resource.limit != 0 && resource.value > resource.limit) {
if (!watchdog_failure_found.exchange(true)) {
if (resource.ignore_report) {
fprintf(stderr,
"========= %s exceeded: %" PRIu64 " > %" PRIu64
" (%s); exiting without reporting as an error\n",
resource.what, resource.value, resource.limit,
resource.units);
std::_Exit(0);
// should not return here.
}
fprintf(stderr,
"========= %s exceeded: %" PRIu64 " > %" PRIu64
" (%s); exiting\n",
resource.what, resource.value, resource.limit, resource.units);
fprintf(
stderr,
"=============================================================="
"===\n"
"=== BUG FOUND!\n The %s is set to %" PRIu64
" (%s), but it exceeded %" PRIu64
".\n"
"Find out how to adjust the resource limits at "
"https://github.com/google/fuzztest/tree/main/doc/flags-reference.md"
"\n",
resource.what, resource.limit, resource.units, resource.value);
CentipedeSetFailureDescription(resource.failure);
std::abort();
}
}
}
}
// Watchdog thread. Periodically checks if it's time to abort due to a
// timeout/OOM.
[[noreturn]] static void* WatchdogThread(void* unused) {
// Since the watchdog is internal and does not execute user code, disable
// SanCov tracing and TLS traversal.
tls.traced = false;
tls.ignore = true;
state->watchdog_thread_started = true;
while (true) {
sleep(1);
// No calls to ResetInputTimer() yet: input execution hasn't started.
if (state->input_start_time == 0) continue;
watchdog_thread_busy = true;
CheckWatchdogLimits();
watchdog_thread_busy = false;
}
}
__attribute__((noinline)) void CheckStackLimit(size_t stack_usage,
bool is_current_stack) {
static std::atomic_flag stack_limit_exceeded = ATOMIC_FLAG_INIT;
const size_t stack_limit = state->run_time_flags.stack_limit_kb.load() << 10;
// Check for the stack limit only if sp is inside the stack region.
if (stack_limit > 0 && stack_usage > stack_limit) {
const bool test_not_running = state->input_start_time == 0;
if (test_not_running && is_current_stack) return;
if (stack_limit_exceeded.test_and_set()) return;
fprintf(stderr,
"========= Stack limit exceeded: %zu"
" > %zu"
" (byte) in %s; aborting\n",
stack_usage, stack_limit,
is_current_stack ? "the current stack" : "a previous stack");
CentipedeSetFailureDescription(
fuzztest::internal::kExecutionFailureStackLimitExceeded.data());
std::abort();
}
}
void GlobalRunnerState::StartWatchdogThread() {
fprintf(stderr,
"Starting watchdog thread: timeout_per_input: %" PRIu64
" sec; rss_limit_mb: %" PRIu64 " MB; stack_limit_kb: %" PRIu64
" KB\n",
run_time_flags.timeout_per_input.load(),
run_time_flags.rss_limit_mb.load(),
state->run_time_flags.stack_limit_kb.load());
pthread_t watchdog_thread;
pthread_create(&watchdog_thread, nullptr, WatchdogThread, nullptr);
pthread_detach(watchdog_thread);
// Wait until the watchdog actually starts and initializes itself.
while (!state->watchdog_thread_started) {
sleep(0);
}
}
void GlobalRunnerState::ResetTimers() {
const auto curr_time = time(nullptr);
state->input_start_time = curr_time;
}
// Returns a random seed. No need for a more sophisticated seed.
static unsigned GetRandomSeed() { return time(nullptr); }
static void EnsureBuiltinMutator() {
if (state->byte_array_mutator == nullptr) {
static auto* mutator = new ByteArrayMutator(state->knobs, GetRandomSeed());
state->byte_array_mutator = mutator;
}
}
// Byte array mutation fallback for a custom mutator, as defined here:
// https://github.com/google/fuzzing/blob/master/docs/structure-aware-fuzzing.md
extern "C" __attribute__((weak)) size_t
CentipedeLLVMFuzzerMutateCallback(uint8_t* data, size_t size, size_t max_size) {
// TODO(kcc): [as-needed] fix the interface mismatch.
// LLVMFuzzerMutate is an array-based interface (for compatibility reasons)
// while ByteArray has a vector-based interface.
// This incompatibility causes us to do extra allocate/copy per mutation.
// It may not cause big problems in practice though.
if (max_size == 0) return 0; // just in case, not expected to happen.
if (size == 0) {
// Don't mutate empty data, just return a 1-byte result.
data[0] = 0;
return 1;
}
ByteArray array(data, data + size);
EnsureBuiltinMutator();
state->byte_array_mutator->set_max_len(max_size);
state->byte_array_mutator->Mutate(array);
if (array.size() > max_size) {
array.resize(max_size);
}
memcpy(data, array.data(), array.size());
return array.size();
}
extern "C" size_t LLVMFuzzerMutate(uint8_t* data, size_t size,
size_t max_size) {
return CentipedeLLVMFuzzerMutateCallback(data, size, max_size);
}
// An arbitrary large size for input data.
static const size_t kMaxDataSize = 1 << 20;
static void WriteFeaturesToFile(FILE* file, const feature_t* features,
size_t size) {
if (!size) return;
auto bytes_written = fwrite(features, 1, sizeof(features[0]) * size, file);
PrintErrorAndExitIf(bytes_written != size * sizeof(features[0]),
"wrong number of bytes written for coverage");
}
// Clears all coverage data.
// We still need to clear all the thread-local data updated during execution.
// If `full_clear==true` clear all coverage anyway - useful to remove the
// coverage accumulated during startup.
__attribute__((noinline)) // so that we see it in profile.
static void PrepareCoverage(bool full_clear) {
CleanUpSancovTls();
{
fuzztest::internal::LockGuard lock(state->execution_result_override_mu);
if (state->execution_result_override != nullptr) {
state->execution_result_override->ClearAndResize(0);
}
}
PrepareSancov(full_clear);
}
void RunnerCallbacks::GetPresetSeedInputs(
const std::function<void(void*)>& seed_callback) {}
void* RunnerCallbacks::GetRandomSeedInput() { return DeserializeInput({0}); }
std::string RunnerCallbacks::GetSerializedTargetConfig() { return ""; }
void* RunnerCallbacks::Mutate(void* origin,
const ExecutionMetadata& origin_metadata) {
RunnerCheck(HasCustomMutator(),
"RunnerCallbacks::Mutate called despite HasCustomMutator() "
"returning false. This is a runner bug!");
RunnerCheck(!HasCustomMutator(),
"Class deriving from RunnerCallbacks must implement Mutate() if "
"HasCustomMutator() returns true.");
// Should be unreachable here, but still return something safe.
return DeserializeInput({0});
}
void* RunnerCallbacks::CrossOver(void* origin,
const ExecutionMetadata& origin_metadata,
void* other,
const ExecutionMetadata& other_metadata) {
return Mutate(origin, origin_metadata);
}
class LegacyRunnerCallbacks : public RunnerCallbacks {
public:
LegacyRunnerCallbacks(FuzzerTestOneInputCallback test_one_input_cb,
FuzzerCustomMutatorCallback custom_mutator_cb,
FuzzerCustomCrossOverCallback custom_crossover_cb)
: test_one_input_cb_(test_one_input_cb),
custom_mutator_cb_(custom_mutator_cb),
custom_crossover_cb_(custom_crossover_cb) {}
bool Execute(void* input) override {
PrintErrorAndExitIf(test_one_input_cb_ == nullptr,
"missing test_on_input_cb");
const auto* input_ba = reinterpret_cast<const ByteArray*>(input);
const int retval = test_one_input_cb_(input_ba->data(), input_ba->size());
PrintErrorAndExitIf(
retval != -1 && retval != 0,
"test_on_input_cb returns invalid value other than -1 and 0");
return retval == 0;
}
bool HasCustomMutator() const override {
return custom_mutator_cb_ != nullptr;
}
void SerializeInput(void* input,
const std::function<void(ByteSpan)>& bytes_sink) override;
void* DeserializeInput(ByteSpan input_bytes) override;
void FreeInput(void* input) override;
void* Mutate(void* origin, const ExecutionMetadata& origin_metadata) override;
void* CrossOver(void* origin, const ExecutionMetadata& origin_metadata,
void* other,
const ExecutionMetadata& other_metadata) override;
private:
FuzzerTestOneInputCallback test_one_input_cb_;
FuzzerCustomMutatorCallback custom_mutator_cb_;
FuzzerCustomCrossOverCallback custom_crossover_cb_;
};
std::unique_ptr<RunnerCallbacks> CreateLegacyRunnerCallbacks(
FuzzerTestOneInputCallback test_one_input_cb,
FuzzerCustomMutatorCallback custom_mutator_cb,
FuzzerCustomCrossOverCallback custom_crossover_cb) {
return std::make_unique<LegacyRunnerCallbacks>(
test_one_input_cb, custom_mutator_cb, custom_crossover_cb);
}
static void RunOneInput(void* input, RunnerCallbacks& callbacks) {
state->stats = {};
size_t last_time_usec = 0;
auto UsecSinceLast = [&last_time_usec]() {
uint64_t t = TimeInUsec();
uint64_t ret_val = t - last_time_usec;
last_time_usec = t;
return ret_val;
};
UsecSinceLast();
PrepareCoverage(/*full_clear=*/false);
state->stats.prep_time_usec = UsecSinceLast();
state->ResetTimers();
int target_return_value = callbacks.Execute(input) ? 0 : -1;
state->stats.exec_time_usec = UsecSinceLast();
CheckWatchdogLimits();
if (fuzztest::internal::state->input_start_time.exchange(0) != 0) {
PostProcessSancov(target_return_value == -1);
}
WaitWatchdogThreadIdle();
state->stats.post_time_usec = UsecSinceLast();
state->stats.peak_rss_mb = GetPeakRSSMb();
}
// Runs one input provided in file `input_path`.
// Produces coverage data in file `input_path`-features.
__attribute__((noinline)) // so that we see it in profile.
static void ReadOneInputExecuteItAndDumpCoverage(const char* input_path,
RunnerCallbacks& callbacks) {
// Read the input.
auto data = ReadBytesFromFilePath<uint8_t>(input_path);
void* input = callbacks.DeserializeInput(data);
RunOneInput(input, callbacks);
callbacks.FreeInput(input);
// Dump features to a file.
char features_file_path[PATH_MAX];
snprintf(features_file_path, sizeof(features_file_path), "%s-features",
input_path);
FILE* features_file = fopen(features_file_path, "w");
PrintErrorAndExitIf(features_file == nullptr, "can't open coverage file");
const SanCovRuntimeRawFeatureParts sancov_features =
SanCovRuntimeGetFeatures();
WriteFeaturesToFile(features_file, sancov_features.features,
sancov_features.num_features);
fclose(features_file);
}
// Starts sending the outputs (coverage, etc.) to `outputs_blobseq`.
// Returns true on success.
static bool StartSendingOutputsToEngine(BlobSequence& outputs_blobseq) {
return BatchResult::WriteInputBegin(outputs_blobseq);
}
// Copy all the sancov features to `data` with given `capacity` in bytes.
// Returns the byte size of sancov features.
static size_t CopyFeatures(uint8_t* data, size_t capacity) {
const SanCovRuntimeRawFeatureParts sancov_features =
SanCovRuntimeGetFeatures();
const size_t features_len_in_bytes =
sancov_features.num_features * sizeof(feature_t);
if (features_len_in_bytes > capacity) return 0;
memcpy(data, sancov_features.features, features_len_in_bytes);
return features_len_in_bytes;
}
// Finishes sending the outputs (coverage, etc.) to `outputs_blobseq`.
// Returns true on success.
static bool FinishSendingOutputsToEngine(BlobSequence& outputs_blobseq) {
{
LockGuard lock(state->execution_result_override_mu);
bool has_overridden_execution_result = false;
if (state->execution_result_override != nullptr) {
RunnerCheck(state->execution_result_override->results().size() <= 1,
"unexpected number of overridden execution results");
has_overridden_execution_result =
state->execution_result_override->results().size() == 1;
}
if (has_overridden_execution_result) {
const auto& result = state->execution_result_override->results()[0];
return BatchResult::WriteOneFeatureVec(result.features().data(),
result.features().size(),
outputs_blobseq) &&
BatchResult::WriteMetadata(result.metadata(), outputs_blobseq) &&
BatchResult::WriteStats(result.stats(), outputs_blobseq) &&
BatchResult::WriteInputEnd(outputs_blobseq);
}
}
const SanCovRuntimeRawFeatureParts sancov_features =
SanCovRuntimeGetFeatures();
// Copy features to shared memory.
if (!BatchResult::WriteOneFeatureVec(sancov_features.features,
sancov_features.num_features,
outputs_blobseq)) {
return false;
}
if (!BatchResult::WriteMetadata(SanCovRuntimeGetExecutionMetadata(),
outputs_blobseq)) {
return false;
}
// Write the stats.
if (!BatchResult::WriteStats(state->stats, outputs_blobseq)) return false;
// We are done with this input.
if (!BatchResult::WriteInputEnd(outputs_blobseq)) return false;
return true;
}
// Dumps serialized target config to `output_file_path`. Also see
// `GetSerializedTargetConfigViaExternalBinary()`.
static void DumpSerializedTargetConfigToFile(RunnerCallbacks& callbacks,
const char* output_file_path) {
const std::string config = callbacks.GetSerializedTargetConfig();
FILE* output_file = fopen(output_file_path, "w");
const size_t num_bytes_written =
fwrite(config.data(), 1, config.size(), output_file);
PrintErrorAndExitIf(
num_bytes_written != config.size(),
"wrong number of bytes written for serialized target configuration");
fclose(output_file);
}
void LegacyRunnerCallbacks::SerializeInput(
void* input, const std::function<void(ByteSpan)>& bytes_sink) {
const auto* input_object = reinterpret_cast<const ByteArray*>(input);
bytes_sink(*input_object);
}
void* LegacyRunnerCallbacks::DeserializeInput(ByteSpan input_bytes) {
return reinterpret_cast<void*>(
new ByteArray{input_bytes.begin(), input_bytes.end()});
}
void LegacyRunnerCallbacks::FreeInput(void* input) {
delete reinterpret_cast<const ByteArray*>(input);
}
void* LegacyRunnerCallbacks::Mutate(void* origin,
const ExecutionMetadata& origin_metadata) {
const auto* origin_ba = reinterpret_cast<const ByteArray*>(origin);
EnsureBuiltinMutator();
state->byte_array_mutator->SetMetadata(origin_metadata);
const size_t max_mutant_size = state->run_time_flags.max_len;
const size_t size = std::min(max_mutant_size, origin_ba->size());
auto* mutant = new ByteArray();
mutant->resize(max_mutant_size);
std::copy(origin_ba->begin(), origin_ba->begin() + size, mutant->begin());
static unsigned int seed = GetRandomSeed();
size_t new_size =
custom_mutator_cb_(mutant->data(), size, max_mutant_size, rand_r(&seed));
if (new_size == 0) {
new_size = 1;
mutant->assign({0});
} else {
if (new_size >= max_mutant_size) {
new_size = max_mutant_size;
}
mutant->resize(new_size);
}
return reinterpret_cast<void*>(mutant);
}
void* LegacyRunnerCallbacks::CrossOver(
void* origin, const ExecutionMetadata& origin_metadata, void* other,
const ExecutionMetadata& other_metadata) {
if (custom_crossover_cb_ == nullptr) {
return Mutate(origin, origin_metadata);
}
const auto* origin_ba = reinterpret_cast<const ByteArray*>(origin);
const auto* other_ba = reinterpret_cast<const ByteArray*>(other);
static unsigned int seed = GetRandomSeed();
EnsureBuiltinMutator();
state->byte_array_mutator->SetMetadata((rand_r(&seed) & 1) ? origin_metadata
: other_metadata);
const size_t max_mutant_size = state->run_time_flags.max_len;
const size_t size = std::min(max_mutant_size, origin_ba->size());
auto* mutant = new ByteArray();
mutant->resize(max_mutant_size);
std::copy(origin_ba->begin(), origin_ba->begin() + size, mutant->begin());
size_t new_size = custom_crossover_cb_(
origin_ba->data(), origin_ba->size(), other_ba->data(), other_ba->size(),
mutant->data(), max_mutant_size, rand_r(&seed));
if (new_size == 0) {
new_size = 1;
mutant->assign({0});
} else {
if (new_size >= max_mutant_size) {
new_size = max_mutant_size;
}
mutant->resize(new_size);
}
return reinterpret_cast<void*>(mutant);
}
// Returns the current process VmSize, in bytes.
static size_t GetVmSizeInBytes() {
FILE* f = fopen("/proc/self/statm", "r"); // man proc
if (!f) return 0;
size_t vm_size = 0;
// NOTE: Ignore any (unlikely) failures to suppress a compiler warning.
(void)fscanf(f, "%zd", &vm_size);
fclose(f);
return vm_size * getpagesize(); // proc gives VmSize in pages.
}
// Sets RLIMIT_CORE, RLIMIT_AS
static void SetLimits() {
// Disable core dumping.
struct rlimit core_limits;
getrlimit(RLIMIT_CORE, &core_limits);
core_limits.rlim_cur = 0;
core_limits.rlim_max = 0;
setrlimit(RLIMIT_CORE, &core_limits);
// ASAN/TSAN/MSAN can not be used with RLIMIT_AS.
// We get the current VmSize, if it is greater than 1Tb, we assume we
// are running under one of ASAN/TSAN/MSAN and thus cannot use RLIMIT_AS.
constexpr size_t one_tb = 1ULL << 40;
size_t vm_size_in_bytes = GetVmSizeInBytes();
// Set the address-space limit (RLIMIT_AS).
// No-op under ASAN/TSAN/MSAN - those may still rely on rss_limit_mb.
if (vm_size_in_bytes < one_tb) {
size_t address_space_limit_mb =
state->flag_helper.GetIntFlag("address_space_limit_mb=", 0);
if (address_space_limit_mb > 0) {
size_t limit_in_bytes = address_space_limit_mb << 20;
struct rlimit rlimit_as = {limit_in_bytes, limit_in_bytes};
setrlimit(RLIMIT_AS, &rlimit_as);
}
} else {
fprintf(stderr,
"Not using RLIMIT_AS; "
"VmSize is %zdGb, suspecting ASAN/MSAN/TSAN\n",
vm_size_in_bytes >> 30);
}
}
GlobalRunnerState::GlobalRunnerState() {
FuzzTestWorkerInitEarly();
SancovRuntimeInitialize();
// TODO(kcc): move some code from CentipedeRunnerMain() here so that it works
// even if CentipedeRunnerMain() is not called.
state->StartWatchdogThread();
SetLimits();
}
void GlobalRunnerState::OnTermination() {
// The process is winding down, but CentipedeRunnerMain did not run.
// This means, the binary is standalone with its own main(), and we need to
// report the coverage now.
if (!state->centipede_runner_main_executed &&
state->run_time_flags.shmem_size_mb != 0) {
PostProcessSancov(); // TODO(xinhaoyuan): do we know our exit status?
SharedMemoryBlobSequence outputs_blobseq(
sancov_state->arg2, state->run_time_flags.shmem_size_mb << 20);
StartSendingOutputsToEngine(outputs_blobseq);
FinishSendingOutputsToEngine(outputs_blobseq);
}
{
LockGuard lock(state->execution_result_override_mu);
if (state->execution_result_override != nullptr) {
delete state->execution_result_override;
state->execution_result_override = nullptr;
}
}
}
namespace {
struct Input {
void* content;
ExecutionMetadata metadata;
};
struct RunnerAdapterCtx {
bool need_full_cleanup;
RunnerCallbacks& callbacks;
};
void SetUpCoverageDomains(FuzzTestAdapterCtx* ctx,
const FuzzTestCoverageDomainRegistry* registry) {
SanCovRuntimeSetUpCoverageDomains(registry);
}
void GetPresetSeedInputs(FuzzTestAdapterCtx* ctx,
const FuzzTestInputSink* sink) {
auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx);
runner_ctx->callbacks.GetPresetSeedInputs([&](void* input) {
sink->Emit(sink->ctx, reinterpret_cast<FuzzTestInputHandle>(
new Input{/*content=*/input,
/*metadata=*/{}}));
});
}
void GetRandomSeedInput(FuzzTestAdapterCtx* ctx,
const FuzzTestInputSink* sink) {
auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx);
sink->Emit(sink->ctx,
reinterpret_cast<FuzzTestInputHandle>(new Input{
/*content=*/runner_ctx->callbacks.GetRandomSeedInput(),
/*metadata=*/{}}));
}
void NotifyControllerAndExitIfNoCustomMutator(RunnerCallbacks& callbacks) {
if (!callbacks.HasCustomMutator()) {
// This is a ugly hack to let Centipede use the builtin mutator
// needed by the LLVM fuzzer runners (used by some engine tests). We
// cannot remove it until the LLVM fuzzers can be migrated to use
// the FuzzTest LLVM fuzzer wrapper.
SharedMemoryBlobSequence outputs_blobseq(
sancov_state->arg2,
sancov_state->flag_helper.GetIntFlag("shmem_size_mb=", 0) << 20);
RunnerCheck(MutationResult::WriteHasCustomMutator(false, outputs_blobseq),
"Failed to write the indicator for no custom mutator");
std::_Exit(0);
}
}
void Mutate(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle origin_handle,
int shrink, const FuzzTestInputSink* sink) {
auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx);
runner_ctx->need_full_cleanup = true;
NotifyControllerAndExitIfNoCustomMutator(runner_ctx->callbacks);
const auto* origin = reinterpret_cast<Input*>(origin_handle);
auto mutant = new Input{
/*content=*/runner_ctx->callbacks.Mutate(origin->content,
origin->metadata),
/*metadata=*/{},
};
sink->Emit(sink->ctx, reinterpret_cast<FuzzTestInputHandle>(mutant));
}
void CrossOver(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle origin_handle,
FuzzTestInputHandle other_handle,
const FuzzTestInputSink* sink) {
auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx);
runner_ctx->need_full_cleanup = true;
NotifyControllerAndExitIfNoCustomMutator(runner_ctx->callbacks);
const auto* origin = reinterpret_cast<Input*>(origin_handle);
const auto* other = reinterpret_cast<Input*>(other_handle);
auto mutant = new Input{
/*content=*/runner_ctx->callbacks.CrossOver(
origin->content, origin->metadata, other->content, other->metadata),
/*metadata=*/{},
};
sink->Emit(sink->ctx, reinterpret_cast<FuzzTestInputHandle>(mutant));
}
void Execute(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle handle,
const FuzzTestFeedbackSink* sink) {
auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx);
auto* input = reinterpret_cast<Input*>(handle);
SanCovRuntimeClearCoverage(runner_ctx->need_full_cleanup);
if (runner_ctx->need_full_cleanup) {
runner_ctx->need_full_cleanup = false;
}
const int old_traced = CentipedeSetCurrentThreadTraced(/*traced=*/1);
RunOneInput(input->content, runner_ctx->callbacks);
CentipedeSetCurrentThreadTraced(old_traced);
{
LockGuard lock(state->execution_result_override_mu);
bool has_overridden_execution_result = false;
if (state->execution_result_override != nullptr) {
RunnerCheck(state->execution_result_override->results().size() <= 1,
"unexpected number of overridden execution results");
has_overridden_execution_result =
state->execution_result_override->results().size() == 1;
}
if (has_overridden_execution_result) {
auto& result = state->execution_result_override->results()[0];
SanCovRuntimeConvertToEngineFeatures(result.mutable_features().data(),
result.mutable_features().size());
const FuzzTestUint64sView features = {
result.features().data(),
result.features().size(),
};
sink->EmitCoverageFeatures(sink->ctx, &features);
input->metadata = result.metadata();
return;
}
}
SanCovRuntimeEmitFeatures(sink);
input->metadata = SanCovRuntimeGetExecutionMetadata();
}
void DeserializeInputContent(FuzzTestAdapterCtx* ctx,
const FuzzTestBytesView* view,
const FuzzTestInputSink* sink) {
auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx);
auto* input = new Input{
/*content=*/runner_ctx->callbacks.DeserializeInput(
{view->data, view->size}),
/*metadata=*/{},
};
sink->Emit(sink->ctx, reinterpret_cast<FuzzTestInputHandle>(input));
}
void UpdateInputMetadata(FuzzTestAdapterCtx* ctx, const FuzzTestBytesView* view,
FuzzTestInputHandle handle) {
auto* input = reinterpret_cast<Input*>(handle);
input->metadata.cmp_data = {view->data, view->data + view->size};
}
void SerializeInputContent(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle handle,
const FuzzTestBytesSink* sink) {
auto* input = reinterpret_cast<Input*>(handle);
auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx);
runner_ctx->callbacks.SerializeInput(input->content, [&](ByteSpan bytes) {
const auto input_bytes = FuzzTestBytesView{
reinterpret_cast<const uint8_t*>(bytes.data()),
bytes.size(),
};
sink->Emit(sink->ctx, &input_bytes);
});
}
void SerializeInputMetadata(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle handle,
const FuzzTestBytesSink* sink) {
auto* input = reinterpret_cast<Input*>(handle);
const auto input_bytes = FuzzTestBytesView{
reinterpret_cast<const uint8_t*>(input->metadata.cmp_data.data()),
input->metadata.cmp_data.size(),
};
sink->Emit(sink->ctx, &input_bytes);
}
void FreeInput(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle handle) {
auto* input = reinterpret_cast<Input*>(handle);
auto* runner_ctx = reinterpret_cast<RunnerAdapterCtx*>(ctx);
runner_ctx->callbacks.FreeInput(input->content);
delete input;
}
void FreeCtx(FuzzTestAdapterCtx* ctx) {
delete reinterpret_cast<RunnerAdapterCtx*>(ctx);
auto guard = SpinlockGuard::Acquire(state->diagnostic_sink_spinlock);
state->diagnostic_sink = nullptr;
}
void GetTestName(FuzzTestAdapterManagerCtx* ctx,
const FuzzTestBytesSink* sink) {
// Provide the test name exactly specified from the flag. This is hacky
// but should work as the user of runner should call RunnerMain at most
// once.
static const char* test_name = state->flag_helper.GetStringFlag("test=");
if (test_name == nullptr) return;
static size_t len = strlen(test_name);
const auto bytes = FuzzTestBytesView{
reinterpret_cast<const uint8_t*>(test_name),
len,
};
sink->Emit(sink->ctx, &bytes);
}
void ConstructAdapter(FuzzTestAdapterManagerCtx* ctx,
const FuzzTestDiagnosticSink* diagnostic_sink,
FuzzTestAdapter* adapter_out) {
{
auto guard = SpinlockGuard::Acquire(state->diagnostic_sink_spinlock);
state->diagnostic_sink = diagnostic_sink;
}
adapter_out->ctx = reinterpret_cast<FuzzTestAdapterCtx*>(new RunnerAdapterCtx{
/*need_full_cleanup=*/true, *reinterpret_cast<RunnerCallbacks*>(ctx)});
adapter_out->SetUpCoverageDomains = SetUpCoverageDomains;
adapter_out->GetPresetSeedInputs = GetPresetSeedInputs;
adapter_out->GetRandomSeedInput = GetRandomSeedInput;
adapter_out->Mutate = Mutate;
adapter_out->CrossOver = CrossOver;
adapter_out->Execute = Execute;
adapter_out->DeserializeInputContent = DeserializeInputContent;
adapter_out->UpdateInputMetadata = UpdateInputMetadata;
adapter_out->SerializeInputContent = SerializeInputContent;
adapter_out->SerializeInputMetadata = SerializeInputMetadata;
adapter_out->FreeInput = FreeInput;
adapter_out->FreeCtx = FreeCtx;
}
} // namespace
// Constructs a FuzzTestAdapterManager and try to run as an engine worker.
//
// If engine worker is not required, calls
// ReadOneInputExecuteItAndDumpCoverage() for all input files from argc/argv.
int RunnerMain(int argc, char** argv, RunnerCallbacks& callbacks) {
state->centipede_runner_main_executed = true;
fprintf(stderr, "Centipede fuzz target runner; argv[0]: %s flags: %s\n",
argv[0],
CentipedeGetRunnerFlags() ? CentipedeGetRunnerFlags() : "(unset)");
if (state->flag_helper.HasSwitchFlag("dump_configuration")) {
DumpSerializedTargetConfigToFile(callbacks,
/*output_file_path=*/sancov_state->arg1);
return EXIT_SUCCESS;
}
FuzzTestAdapterManager manager = {
/*ctx=*/reinterpret_cast<FuzzTestAdapterManagerCtx*>(&callbacks),
/*GetBinaryId=*/nullptr,
/*GetTestName=*/GetTestName,
/*ConstructAdapter=*/ConstructAdapter,
};
const int old_traced = CentipedeSetCurrentThreadTraced(/*traced=*/0);
const auto s = FuzzTestWorkerMaybeRun(&manager);
CentipedeSetCurrentThreadTraced(old_traced);
if (s == kFuzzTestWorkerNotRequired) {
for (int i = 1; i < argc; i++) {
ReadOneInputExecuteItAndDumpCoverage(argv[i], callbacks);
}
return EXIT_SUCCESS;
}
return s == kFuzzTestWorkerSuccess ? EXIT_SUCCESS : EXIT_FAILURE;
}
} // namespace fuzztest::internal
extern "C" int LLVMFuzzerRunDriver(
int* absl_nonnull argc, char*** absl_nonnull argv,
FuzzerTestOneInputCallback test_one_input_cb) {
if (LLVMFuzzerInitialize) LLVMFuzzerInitialize(argc, argv);
return RunnerMain(*argc, *argv,
*fuzztest::internal::CreateLegacyRunnerCallbacks(
test_one_input_cb, LLVMFuzzerCustomMutator,
LLVMFuzzerCustomCrossOver));
}
extern "C" void CentipedeSetRssLimit(size_t rss_limit_mb) {
fprintf(stderr, "CentipedeSetRssLimit: changing rss_limit_mb to %zu\n",
rss_limit_mb);
fuzztest::internal::state->run_time_flags.rss_limit_mb = rss_limit_mb;
}
extern "C" void CentipedeSetStackLimit(size_t stack_limit_kb) {
fprintf(stderr, "CentipedeSetStackLimit: changing stack_limit_kb to %zu\n",
stack_limit_kb);
fuzztest::internal::state->run_time_flags.stack_limit_kb = stack_limit_kb;
}
extern "C" void CentipedeSetTimeoutPerInput(uint64_t timeout_per_input) {
fprintf(stderr,
"CentipedeSetTimeoutPerInput: changing timeout_per_input to %" PRIu64
"\n",
timeout_per_input);
fuzztest::internal::state->run_time_flags.timeout_per_input =
timeout_per_input;
}
extern "C" __attribute__((weak)) const char* absl_nullable
CentipedeGetRunnerFlags() {
static const char* flags = []() -> const char* {
if (const char* runner_flags_env = getenv("CENTIPEDE_RUNNER_FLAGS")) {
return strdup(runner_flags_env);
}
return nullptr;
}();
return flags;
}
// TODO: xinhaoyuan - write test for this.
extern "C" const char* absl_nullable GetSancovFlags() {
return CentipedeGetRunnerFlags();
}
static std::atomic<bool> in_execution_batch = false;
extern "C" void CentipedeBeginExecutionBatch() {
if (in_execution_batch) {
fprintf(stderr,
"CentipedeBeginExecutionBatch called twice without calling "
"CentipedeEndExecutionBatch in between\n");
_exit(EXIT_FAILURE);
}
in_execution_batch = true;
fuzztest::internal::PrepareCoverage(/*full_clear=*/true);
}
extern "C" void CentipedeEndExecutionBatch() {
if (!in_execution_batch) {
fprintf(stderr,
"CentipedeEndExecutionBatch called without calling "
"CentipedeBeginExecutionBatch before\n");
_exit(EXIT_FAILURE);
}
in_execution_batch = false;
fuzztest::internal::state->input_start_time = 0;
}
extern "C" void CentipedePrepareProcessing() {
fuzztest::internal::PrepareCoverage(/*full_clear=*/!in_execution_batch);
fuzztest::internal::state->ResetTimers();
}
extern "C" void CentipedeFinalizeProcessing() {
fuzztest::internal::CheckWatchdogLimits();
if (fuzztest::internal::state->input_start_time.exchange(0) != 0) {
fuzztest::internal::PostProcessSancov();
}
}
extern "C" int CentipedeSetCurrentThreadTraced(int traced) {
const int old_traced = fuzztest::internal::tls.traced;
fuzztest::internal::tls.traced = traced;
return old_traced;
}
extern "C" size_t CentipedeGetExecutionResult(uint8_t* data, size_t capacity) {
fuzztest::internal::BlobSequence outputs_blobseq(data, capacity);
if (!fuzztest::internal::StartSendingOutputsToEngine(outputs_blobseq))
return 0;
if (!fuzztest::internal::FinishSendingOutputsToEngine(outputs_blobseq))
return 0;
return outputs_blobseq.offset();
}
extern "C" size_t CentipedeGetCoverageData(uint8_t* data, size_t capacity) {
return fuzztest::internal::CopyFeatures(data, capacity);
}
extern "C" void CentipedeSetExecutionResult(const uint8_t* data, size_t size) {
using fuzztest::internal::state;
fuzztest::internal::LockGuard lock(state->execution_result_override_mu);
if (!state->execution_result_override)
state->execution_result_override = new fuzztest::internal::BatchResult();
state->execution_result_override->ClearAndResize(1);
if (data == nullptr) return;
// Removing const here should be fine as we don't write to `blobseq`.
fuzztest::internal::BlobSequence blobseq(const_cast<uint8_t*>(data), size);
state->execution_result_override->Read(blobseq);
fuzztest::internal::RunnerCheck(
state->execution_result_override->num_outputs_read() == 1,
"Failed to set execution result from CentipedeSetExecutionResult");
}
extern "C" void CentipedeSetFailureDescription(const char* description) {
if (description == nullptr) {
static constexpr std::string_view kMsg =
"CentipedeSetFailureDescription called with null description\n";
write(STDERR_FILENO, kMsg.data(), kMsg.size());
std::_Exit(EXIT_FAILURE);
}
std::string_view desc_sv = description;
static constexpr std::string_view kSetupFailurePrefix = "SETUP FAILURE:";
using ::fuzztest::internal::SpinlockGuard;
using ::fuzztest::internal::state;
auto sink_guard = SpinlockGuard::TryAcquire(state->diagnostic_sink_spinlock);
if (!sink_guard) {
static constexpr std::string_view kMsg =
"Diagnostic sink is busy while setting failure:\n";
write(STDERR_FILENO, kMsg.data(), kMsg.size());
write(STDERR_FILENO, description, std::strlen(description));
std::_Exit(EXIT_FAILURE);
}
const auto* diagnostic_sink = state->diagnostic_sink;
if (diagnostic_sink == nullptr) {
static constexpr std::string_view kMsg =
"Diagnostic sink is missing while setting failure:\n";
write(STDERR_FILENO, kMsg.data(), kMsg.size());
write(STDERR_FILENO, description, std::strlen(description));
std::_Exit(EXIT_FAILURE);
}
if (desc_sv.substr(0, kSetupFailurePrefix.size()) == kSetupFailurePrefix) {
size_t prefix_size = kSetupFailurePrefix.size();
while (prefix_size < desc_sv.size() && desc_sv[prefix_size] == ' ') {
++prefix_size;
}
const FuzzTestBytesView error = {
reinterpret_cast<const uint8_t*>(desc_sv.data() + prefix_size),
desc_sv.size() - prefix_size,
};
diagnostic_sink->EmitError(diagnostic_sink->ctx, &error);
return;
}
const FuzzTestBytesView description_view = {
reinterpret_cast<const uint8_t*>(desc_sv.data()),
desc_sv.size(),
};
diagnostic_sink->EmitFinding(diagnostic_sink->ctx, &description_view,
// For now, use the description as the signature.
&description_view);
}