Skip to content

Commit

Permalink
Send SIGABRT to the runtime thread in the legacy FuzzTest runtime.
Browse files Browse the repository at this point in the history
I also made it so that we no longer spawn a new watchdog thread that we never
terminate whenever we run a test. The watchdog threads are now properly cleaned
up at the end of each test.

PiperOrigin-RevId: 726626429
  • Loading branch information
fniksic authored and copybara-github committed Feb 13, 2025
1 parent b35469f commit 84bcab2
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 32 deletions.
6 changes: 6 additions & 0 deletions fuzztest/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,11 @@ cc_library(
name = "runtime",
srcs = ["internal/runtime.cc"],
hdrs = ["internal/runtime.h"],
linkopts = select({
"@platforms//os:linux": ["-lpthread"],
"@platforms//os:macos": ["-lpthread"],
"//conditions:default": [],
}),
deps = [
":configuration",
":corpus_database",
Expand All @@ -618,6 +623,7 @@ cc_library(
":seed_seq",
":serialization",
":status",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/functional:any_invocable",
"@com_google_absl//absl/functional:bind_front",
"@com_google_absl//absl/functional:function_ref",
Expand Down
3 changes: 3 additions & 0 deletions fuzztest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ fuzztest_cc_library(
fuzztest::seed_seq
fuzztest::serialization
fuzztest::status
absl::core_headers
absl::any_invocable
absl::bind_front
absl::function_ref
Expand All @@ -591,6 +592,8 @@ fuzztest_cc_library(
absl::str_format
absl::time
absl::span
LINKOPTS
pthread
)

fuzztest_cc_test(
Expand Down
108 changes: 87 additions & 21 deletions fuzztest/internal/runtime.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include <sys/resource.h>
#endif

#include <signal.h>

#include <algorithm>
#include <atomic>
#include <cerrno>
Expand Down Expand Up @@ -346,25 +348,62 @@ void Runtime::PrintReport(RawSink out) const {
absl::Format(out, "%s", GetSeparator());
}

void Runtime::StartWatchdog() {
// Centipede runner has its own watchdog.
#ifndef FUZZTEST_USE_CENTIPEDE
auto watchdog_thread = std::thread(std::bind(&Runtime::Watchdog, this));
while (!watchdog_thread_started) std::this_thread::yield();
watchdog_thread.detach();
#endif
}

void Runtime::Watchdog() {
watchdog_thread_started = true;
while (true) {
while (watchdog_spinlock_.test_and_set()) std::this_thread::yield();
if (test_iteration_started_) CheckWatchdogLimits();
watchdog_spinlock_.clear();
absl::SleepFor(absl::Seconds(1));
class Runtime::Watchdog {
public:
explicit Watchdog(Runtime& runtime) : runtime_{runtime} {
watchdog_thread_ =
std::thread(absl::bind_front(&Runtime::Watchdog::WatchdogLoop, this));
while (!watchdog_thread_started_) std::this_thread::yield();
}

~Watchdog() {
stop_requested_ = true;
watchdog_thread_.join();
}

private:
void WatchdogLoop() {
watchdog_thread_started_ = true;
while (!stop_requested_) {
runtime_.CheckWatchdogLimits();
runtime_.watchdog_spinlock_.Lock();
if (runtime_.watchdog_limit_exceeded_) {
runtime_.watchdog_spinlock_.Unlock();
break;
}
runtime_.watchdog_spinlock_.Unlock();
absl::SleepFor(absl::Seconds(1));
}
}

std::atomic<bool> watchdog_thread_started_ = false;
std::atomic<bool> stop_requested_ = false;
std::thread watchdog_thread_;
Runtime& runtime_;
};

#else // FUZZTEST_USE_CENTIPEDE

// Centipede runner has its own watchdog.
class Runtime::Watchdog {
public:
explicit Watchdog(Runtime&) {}
};

#endif // FUZZTEST_USE_CENTIPEDE

Runtime::Watchdog Runtime::CreateWatchdog() { return Watchdog{*this}; }

void Runtime::Spinlock::Lock() {
while (locked_.test_and_set(std::memory_order_acq_rel)) {
std::this_thread::yield();
}
}

void Runtime::Spinlock::Unlock() { locked_.clear(std::memory_order_release); }

static size_t GetPeakRSSBytes() {
#ifndef FUZZTEST_HAS_RUSAGE
return 0;
Expand All @@ -379,7 +418,12 @@ static size_t GetPeakRSSBytes() {
void Runtime::CheckWatchdogLimits() {
// Centipede runner has its own watchdog.
#ifndef FUZZTEST_USE_CENTIPEDE
if (current_configuration_ == nullptr) return;
watchdog_spinlock_.Lock();
if (current_configuration_ == nullptr || !test_iteration_started_ ||
watchdog_limit_exceeded_) {
watchdog_spinlock_.Unlock();
return;
}
const absl::Duration run_duration =
clock_fn_() - current_iteration_start_time_;
if (current_configuration_->time_limit_per_input > absl::ZeroDuration() &&
Expand All @@ -388,17 +432,32 @@ void Runtime::CheckWatchdogLimits() {
GetStderr(), "[!] Per-input timeout exceeded: %s > %s - aborting\n",
absl::FormatDuration(run_duration),
absl::FormatDuration(current_configuration_->time_limit_per_input));
watchdog_limit_exceeded_ = true;
watchdog_spinlock_.Unlock();
#if defined(__linux__) || defined(__APPLE__)
pthread_kill(reporting_thread_, SIGABRT);
return;
#else
std::abort();
#endif
}
const size_t rss_usage = GetPeakRSSBytes();
if (current_configuration_->rss_limit > 0 &&
rss_usage > current_configuration_->rss_limit) {
absl::FPrintF(GetStderr(),
"[!] RSS limit exceeded: %zu > %zu (bytes) - aborting\n",
rss_usage, current_configuration_->rss_limit);
watchdog_limit_exceeded_ = true;
watchdog_spinlock_.Unlock();
#if defined(__linux__) || defined(__APPLE__)
pthread_kill(reporting_thread_, SIGABRT);
return;
#else
std::abort();
}
#endif
}
watchdog_spinlock_.Unlock();
#endif // FUZZTEST_USE_CENTIPEDE
}

void Runtime::SetCurrentTest(const FuzzTest* test,
Expand All @@ -422,11 +481,18 @@ void Runtime::SetCurrentTest(const FuzzTest* test,
++test_counter_;
}

void Runtime::OnTestIterationStart(const absl::Time& start_time) {
watchdog_spinlock_.Lock();
current_iteration_start_time_ = start_time;
test_iteration_started_ = true;
watchdog_spinlock_.Unlock();
}

void Runtime::OnTestIterationEnd() {
test_iteration_started_ = false;
while (watchdog_spinlock_.test_and_set()) std::this_thread::yield();
CheckWatchdogLimits();
watchdog_spinlock_.clear();
watchdog_spinlock_.Lock();
test_iteration_started_ = false;
watchdog_spinlock_.Unlock();
}

#if defined(__linux__) || defined(__APPLE__)
Expand Down Expand Up @@ -971,7 +1037,7 @@ bool FuzzTestFuzzerImpl::RunInUnitTestMode(const Configuration& configuration) {
test_.full_name());
return;
}
runtime_.StartWatchdog();
[[maybe_unused]] auto watchdog = runtime_.CreateWatchdog();
PopulateLimits(configuration, execution_coverage_);

// TODO(sbenzaquen): Currently, some infrastructure code assumes that replay
Expand Down Expand Up @@ -1143,7 +1209,7 @@ bool FuzzTestFuzzerImpl::RunInFuzzingMode(int* /*argc*/, char*** /*argv*/,
test_.full_name());
return true;
}
runtime_.StartWatchdog();
[[maybe_unused]] auto watchdog = runtime_.CreateWatchdog();
PopulateLimits(configuration, execution_coverage_);
runtime_.SetRunMode(RunMode::kFuzz);

Expand Down
56 changes: 45 additions & 11 deletions fuzztest/internal/runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
#ifndef FUZZTEST_FUZZTEST_INTERNAL_RUNTIME_H_
#define FUZZTEST_FUZZTEST_INTERNAL_RUNTIME_H_

#if defined(__linux__) || defined(__APPLE__)
#include <pthread.h>
#endif

#include <atomic>
#include <cstddef>
#include <cstdio>
Expand All @@ -28,6 +32,7 @@
#include <utility>
#include <vector>

#include "absl/base/thread_annotations.h"
#include "absl/functional/any_invocable.h"
#include "absl/functional/function_ref.h"
#include "absl/random/bit_gen_ref.h"
Expand All @@ -36,6 +41,7 @@
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "./fuzztest/internal/configuration.h"
Expand Down Expand Up @@ -156,8 +162,6 @@ class Runtime {
return termination_requested_.load(std::memory_order_relaxed);
}

void StartWatchdog();

void SetRunMode(RunMode run_mode) { run_mode_ = run_mode; }
RunMode run_mode() const { return run_mode_; }

Expand All @@ -167,6 +171,11 @@ class Runtime {
reporter_enabled_ = true;
stats_ = stats;
clock_fn_ = clock_fn;
#if defined(__linux__) || defined(__APPLE__)
// Set this just in case that the function is called in a thread other than
// the one that created the runtime.
reporting_thread_ = pthread_self();
#endif
// In case we have not installed them yet, do so now.
InstallSignalHandlers(GetStderr());
ResetCrashType();
Expand All @@ -183,10 +192,7 @@ class Runtime {
// calling `DisableReporter()`.
void SetCurrentTest(const FuzzTest* test, const Configuration* configuration);

void OnTestIterationStart(const absl::Time& start_time) {
current_iteration_start_time_ = start_time;
test_iteration_started_ = true;
}
void OnTestIterationStart(const absl::Time& start_time);
void OnTestIterationEnd();

void SetCurrentArgs(Args* args) { current_args_ = args; }
Expand All @@ -209,11 +215,18 @@ class Runtime {
}
void ResetCrashType() { crash_type_ = std::nullopt; }

class Watchdog;
// Returns a watchdog that periodically checks the time and memory limits in a
// separate thread. The watchdog handles the logic of starting and joining the
// thread. The runtime must outlive the watchdog.
Watchdog CreateWatchdog();

private:
Runtime();

// Checks time and memory limits. If any limit is exceeded, sends SIGABRT to
// the runtime thread.
void CheckWatchdogLimits();
void Watchdog();

// Returns the file path of the reproducer.
// Returns empty string if writing the file failed.
Expand Down Expand Up @@ -243,7 +256,6 @@ class Runtime {
std::atomic<bool> termination_requested_ = false;

RunMode run_mode_ = RunMode::kUnitTest;
std::atomic<bool> watchdog_thread_started = false;

absl::Time creation_time_ = absl::Now();
size_t test_counter_ = 0;
Expand All @@ -252,12 +264,34 @@ class Runtime {
Args* current_args_ = nullptr;
const FuzzTest* current_test_ = nullptr;
const Configuration* current_configuration_;
absl::Time current_iteration_start_time_;
std::atomic<bool> test_iteration_started_ = false;
std::atomic_flag watchdog_spinlock_ = ATOMIC_FLAG_INIT;
const RuntimeStats* stats_ = nullptr;
absl::Time (*clock_fn_)() = nullptr;

// We use a simple custom spinlock instead of absl::Mutex to reduce
// dependencies and avoid potential issues with code instrumentation.
class ABSL_LOCKABLE Spinlock {
public:
Spinlock() = default;
Spinlock(const Spinlock&) = delete;
Spinlock& operator=(const Spinlock&) = delete;

void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION();
void Unlock() ABSL_UNLOCK_FUNCTION();

private:
std::atomic_flag locked_ = ATOMIC_FLAG_INIT;
};

Spinlock watchdog_spinlock_;
absl::Time current_iteration_start_time_ ABSL_GUARDED_BY(watchdog_spinlock_);
bool test_iteration_started_ ABSL_GUARDED_BY(watchdog_spinlock_) = false;
bool watchdog_limit_exceeded_ ABSL_GUARDED_BY(watchdog_spinlock_) = false;

#if defined(__linux__) || defined(__APPLE__)
// The thread to which the watchdog sends SIGABRT.
pthread_t reporting_thread_ = pthread_self();
#endif

// A registry of crash metadata listeners.
std::vector<CrashMetadataListener> crash_metadata_listeners_;
// In case of a crash, contains the crash type.
Expand Down

0 comments on commit 84bcab2

Please sign in to comment.