Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions centipede/runner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -216,20 +216,21 @@ static void CheckWatchdogLimits() {
}
}

__attribute__((noinline)) void CheckStackLimit(uintptr_t sp) {
__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 && tls.stack_region_low &&
tls.top_frame_sp - sp > stack_limit) {
if (stack_limit > 0 && stack_usage > stack_limit) {
const bool test_not_running = state->input_start_time == 0;
if (test_not_running) return;
if (test_not_running && is_current_stack) return;
if (stack_limit_exceeded.test_and_set()) return;
fprintf(stderr,
"========= Stack limit exceeded: %" PRIuPTR
"========= Stack limit exceeded: %zu"
" > %zu"
" (byte); aborting\n",
tls.top_frame_sp - sp, stack_limit);
" (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();
Expand Down
4 changes: 4 additions & 0 deletions centipede/runner_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@

#include "absl/base/nullability.h"

// Use this attribute for functions that must not be instrumented even if
// the library is built with sanitizers (asan, etc).
#define FUZZTEST_NO_SANITIZE __attribute__((no_sanitize("all")))

namespace fuzztest::internal {

// If `condition` prints `error` and calls exit(1).
Expand Down
84 changes: 53 additions & 31 deletions centipede/sancov_callbacks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "./centipede/pc_info.h"
#include "./centipede/reverse_pc_table.h"
#include "./centipede/runner_dl_info.h"
#include "./centipede/runner_utils.h"
#include "./centipede/sancov_state.h"

namespace fuzztest::internal {
Expand Down Expand Up @@ -61,10 +62,6 @@ using fuzztest::internal::tls;
// https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html.
#define ENFORCE_INLINE __attribute__((always_inline)) inline

// Use this attribute for functions that must not be instrumented even if
// the runner is built with sanitizers (asan, etc).
#define NO_SANITIZE __attribute__((no_sanitize("all")))

// NOTE: Enforce inlining so that `__builtin_return_address` works.
ENFORCE_INLINE static void TraceLoad(void *addr) {
if (ABSL_PREDICT_FALSE(!tls.traced) ||
Expand Down Expand Up @@ -127,55 +124,65 @@ ENFORCE_INLINE void TraceCmp(T a, T b, uintptr_t pc) {
//------------------------------------------------------------------------------

extern "C" {
NO_SANITIZE void __sanitizer_cov_load1(uint8_t *addr) { TraceLoad(addr); }
NO_SANITIZE void __sanitizer_cov_load2(uint16_t *addr) { TraceLoad(addr); }
NO_SANITIZE void __sanitizer_cov_load4(uint32_t *addr) { TraceLoad(addr); }
NO_SANITIZE void __sanitizer_cov_load8(uint64_t *addr) { TraceLoad(addr); }
NO_SANITIZE void __sanitizer_cov_load16(__uint128_t *addr) { TraceLoad(addr); }
FUZZTEST_NO_SANITIZE void __sanitizer_cov_load1(uint8_t* addr) {
TraceLoad(addr);
}
FUZZTEST_NO_SANITIZE void __sanitizer_cov_load2(uint16_t* addr) {
TraceLoad(addr);
}
FUZZTEST_NO_SANITIZE void __sanitizer_cov_load4(uint32_t* addr) {
TraceLoad(addr);
}
FUZZTEST_NO_SANITIZE void __sanitizer_cov_load8(uint64_t* addr) {
TraceLoad(addr);
}
FUZZTEST_NO_SANITIZE void __sanitizer_cov_load16(__uint128_t* addr) {
TraceLoad(addr);
}

NO_SANITIZE
FUZZTEST_NO_SANITIZE
void __sanitizer_cov_trace_const_cmp1(uint8_t Arg1, uint8_t Arg2) {
if (ABSL_PREDICT_FALSE(!tls.traced)) return;
TraceCmp(Arg1, Arg2,
reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
}
NO_SANITIZE
FUZZTEST_NO_SANITIZE
void __sanitizer_cov_trace_const_cmp2(uint16_t Arg1, uint16_t Arg2) {
if (ABSL_PREDICT_FALSE(!tls.traced)) return;
TraceCmp(Arg1, Arg2,
reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
}
NO_SANITIZE
FUZZTEST_NO_SANITIZE
void __sanitizer_cov_trace_const_cmp4(uint32_t Arg1, uint32_t Arg2) {
if (ABSL_PREDICT_FALSE(!tls.traced)) return;
TraceCmp(Arg1, Arg2,
reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
}
NO_SANITIZE
FUZZTEST_NO_SANITIZE
void __sanitizer_cov_trace_const_cmp8(uint64_t Arg1, uint64_t Arg2) {
if (ABSL_PREDICT_FALSE(!tls.traced)) return;
TraceCmp(Arg1, Arg2,
reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
}
NO_SANITIZE
FUZZTEST_NO_SANITIZE
void __sanitizer_cov_trace_cmp1(uint8_t Arg1, uint8_t Arg2) {
if (ABSL_PREDICT_FALSE(!tls.traced)) return;
TraceCmp(Arg1, Arg2,
reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
}
NO_SANITIZE
FUZZTEST_NO_SANITIZE
void __sanitizer_cov_trace_cmp2(uint16_t Arg1, uint16_t Arg2) {
if (ABSL_PREDICT_FALSE(!tls.traced)) return;
TraceCmp(Arg1, Arg2,
reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
}
NO_SANITIZE
FUZZTEST_NO_SANITIZE
void __sanitizer_cov_trace_cmp4(uint32_t Arg1, uint32_t Arg2) {
if (ABSL_PREDICT_FALSE(!tls.traced)) return;
TraceCmp(Arg1, Arg2,
reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
}
NO_SANITIZE
FUZZTEST_NO_SANITIZE
void __sanitizer_cov_trace_cmp8(uint64_t Arg1, uint64_t Arg2) {
if (ABSL_PREDICT_FALSE(!tls.traced)) return;
TraceCmp(Arg1, Arg2,
Expand All @@ -188,7 +195,7 @@ void __sanitizer_cov_trace_cmp8(uint64_t Arg1, uint64_t Arg2) {
// LLVM/libFuzzer implementation).
//
// Source: https://clang.llvm.org/docs/SanitizerCoverage.html
NO_SANITIZE
FUZZTEST_NO_SANITIZE
void __sanitizer_cov_trace_switch(uint64_t val, uint64_t* cases) {
if (ABSL_PREDICT_FALSE(!tls.traced)) return;
const auto num_cases = cases[0];
Expand Down Expand Up @@ -262,6 +269,23 @@ __attribute__((noinline)) static void HandlePath(uintptr_t normalized_pc) {
sancov_state->path_feature_set.set(hash);
}

// Updates the lowest stack using the current stack pointer `sp` and checks
// against the stack limit if needed.
static ENFORCE_INLINE void UpdateLowestStackAndCheckLimit(uintptr_t sp) {
// It should be rare for the stack pointer to be valid and exceed the previous
// record.
if (ABSL_PREDICT_FALSE(sp < tls.lowest_sp && sp <= tls.top_frame_sp &&
sp >= tls.stack_region_low &&
tls.stack_region_low > 0)) {
tls.lowest_sp = sp;
if (fuzztest::internal::CheckStackLimit == nullptr) {
return;
}
fuzztest::internal::CheckStackLimit(tls.top_frame_sp - sp,
/*is_current_stack=*/true);
}
}

// Handles one observed PC.
// `normalized_pc` is an integer representation of PC that is stable between
// the executions.
Expand All @@ -278,18 +302,7 @@ static ENFORCE_INLINE void HandleOnePc(PCGuard pc_guard) {

if (pc_guard.is_function_entry) {
uintptr_t sp = reinterpret_cast<uintptr_t>(__builtin_frame_address(0));
// It should be rare for the stack depth to exceed the previous record.
if (__builtin_expect(
sp < tls.lowest_sp &&
// And ignore the stack pointer when it is not in the known
// region (e.g. for signal handling with an alternative stack).
(tls.stack_region_low == 0 || sp >= tls.stack_region_low),
0)) {
tls.lowest_sp = sp;
if (fuzztest::internal::CheckStackLimit != nullptr) {
fuzztest::internal::CheckStackLimit(sp);
}
}
UpdateLowestStackAndCheckLimit(sp);
if (sancov_state->flags.callstack_level != 0) {
tls.call_stack.OnFunctionEntry(pc_guard.pc_index, sp);
sancov_state->callstack_set.set(tls.call_stack.Hash());
Expand Down Expand Up @@ -361,6 +374,7 @@ __attribute__((noinline)) static void MainObjectLazyInit() {
// This instrumentation is redundant if other instrumentation
// (e.g. trace-pc-guard) is available, but GCC as of 2022-04 only supports
// this variant.
FUZZTEST_NO_SANITIZE
void __sanitizer_cov_trace_pc() {
if (ABSL_PREDICT_FALSE(!tls.traced)) return;
uintptr_t pc = reinterpret_cast<uintptr_t>(__builtin_return_address(0));
Expand All @@ -386,7 +400,7 @@ void __sanitizer_cov_trace_pc_guard_init(PCGuard *absl_nonnull start,
}

// This function is called on every instrumented edge.
NO_SANITIZE
FUZZTEST_NO_SANITIZE
void __sanitizer_cov_trace_pc_guard(PCGuard *absl_nonnull guard) {
if (ABSL_PREDICT_FALSE(!tls.traced)) return;
// This function may be called very early during the DSO initialization,
Expand All @@ -397,4 +411,12 @@ void __sanitizer_cov_trace_pc_guard(PCGuard *absl_nonnull guard) {
HandleOnePc(*guard);
}

// This callback is called by the compiler on every function entry when enabled.
// https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-stack-depth
FUZZTEST_NO_SANITIZE void __sanitizer_cov_stack_depth() {
if (ABSL_PREDICT_FALSE(!tls.traced)) return;
UpdateLowestStackAndCheckLimit(
reinterpret_cast<uintptr_t>(__builtin_frame_address(0)));
}

} // extern "C"
24 changes: 13 additions & 11 deletions centipede/sancov_interceptors.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include "absl/base/nullability.h"
#include "absl/base/optimization.h"
#include "./centipede/runner_utils.h"
#include "./centipede/sancov_state.h"

using fuzztest::internal::tls;
Expand All @@ -30,7 +31,6 @@ using fuzztest::internal::tls;
// before or during the sanitizer initialization. Instead, we check if the
// current thread is marked as started by the runner as the proxy of sanitizier
// initialization. If not, we skip the interception logic.
#define NO_SANITIZE __attribute__((no_sanitize("all")))

namespace {

Expand Down Expand Up @@ -131,8 +131,8 @@ DECLARE_CENTIPEDE_ORIG_FUNC(int, pthread_create,

// Fallback for the case *cmp_orig is null.
// Will be executed several times at process startup, if at all.
static NO_SANITIZE int memcmp_fallback(const void *s1, const void *s2,
size_t n) {
static FUZZTEST_NO_SANITIZE int memcmp_fallback(const void* s1, const void* s2,
size_t n) {
const auto *p1 = static_cast<const uint8_t *>(s1);
const auto *p2 = static_cast<const uint8_t *>(s2);
for (size_t i = 0; i < n; ++i) {
Expand All @@ -143,8 +143,8 @@ static NO_SANITIZE int memcmp_fallback(const void *s1, const void *s2,
}

// Fallback for case insensitive comparison.
static NO_SANITIZE int memcasecmp_fallback(const void* s1, const void* s2,
size_t n) {
static FUZZTEST_NO_SANITIZE int memcasecmp_fallback(const void* s1,
const void* s2, size_t n) {
static uint8_t to_lower[256];
[[maybe_unused]] static bool initialize_to_lower = [&] {
for (size_t i = 0; i < sizeof(to_lower); ++i) {
Expand All @@ -166,7 +166,8 @@ static NO_SANITIZE int memcasecmp_fallback(const void* s1, const void* s2,

// memcmp interceptor.
// Calls the real memcmp() and possibly modifies state.cmp_feature_set.
extern "C" NO_SANITIZE int memcmp(const void *s1, const void *s2, size_t n) {
extern "C" FUZZTEST_NO_SANITIZE int memcmp(const void* s1, const void* s2,
size_t n) {
const int result =
memcmp_orig ? memcmp_orig(s1, s2, n) : memcmp_fallback(s1, s2, n);
if (ABSL_PREDICT_FALSE(!tls.traced)) {
Expand All @@ -183,7 +184,7 @@ extern "C" NO_SANITIZE int memcmp(const void *s1, const void *s2, size_t n) {

// strcmp interceptor.
// Calls the real strcmp() and possibly modifies state.cmp_feature_set.
extern "C" NO_SANITIZE int strcmp(const char *s1, const char *s2) {
extern "C" FUZZTEST_NO_SANITIZE int strcmp(const char* s1, const char* s2) {
// Find the length of the shorter string, as this determines the actual number
// of bytes that are compared. Note that this is needed even if we call
// `strcmp_orig` because we're passing it to `TraceMemCmp()`.
Expand All @@ -205,7 +206,8 @@ extern "C" NO_SANITIZE int strcmp(const char *s1, const char *s2) {

// strncmp interceptor.
// Calls the real strncmp() and possibly modifies state.cmp_feature_set.
extern "C" NO_SANITIZE int strncmp(const char *s1, const char *s2, size_t n) {
extern "C" FUZZTEST_NO_SANITIZE int strncmp(const char* s1, const char* s2,
size_t n) {
// Find the length of the shorter string, as this determines the actual number
// of bytes that are compared. Note that this is needed even if we call
// `strncmp_orig` because we're passing it to `TraceMemCmp()`.
Expand All @@ -228,7 +230,7 @@ extern "C" NO_SANITIZE int strncmp(const char *s1, const char *s2, size_t n) {

// strcasecmp interceptor.
// Calls the real strcasecmp() and possibly modifies state.cmp_feature_set.
extern "C" NO_SANITIZE int strcasecmp(const char* s1, const char* s2) {
extern "C" FUZZTEST_NO_SANITIZE int strcasecmp(const char* s1, const char* s2) {
// Find the length of the shorter string, as this determines the actual number
// of bytes that are compared. Note that this is needed even if we call
// `strcasecmp_orig` because we're passing it to `TraceMemCmp()`.
Expand All @@ -251,8 +253,8 @@ extern "C" NO_SANITIZE int strcasecmp(const char* s1, const char* s2) {

// strncasecmp interceptor.
// Calls the real strncasecmp() and possibly modifies state.cmp_feature_set.
extern "C" NO_SANITIZE int strncasecmp(const char* s1, const char* s2,
size_t n) {
extern "C" FUZZTEST_NO_SANITIZE int strncasecmp(const char* s1, const char* s2,
size_t n) {
// Find the length of the shorter string, as this determines the actual number
// of bytes that are compared. Note that this is needed even if we call
// `strncasecmp_orig` because we're passing it to `TraceMemCmp()`.
Expand Down
Loading
Loading