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
1 change: 1 addition & 0 deletions centipede/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2053,5 +2053,6 @@ cc_static_library(
":engine_worker",
":sancov_runtime",
":weak_sancov_stubs",
"@com_google_fuzztest//fuzztest/internal:sanitizer_interface",
],
)
13 changes: 12 additions & 1 deletion fuzztest/internal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -446,11 +446,21 @@ cc_library(
name = "sanitizer_interface",
srcs = ["sanitizer_interface.cc"],
hdrs = ["sanitizer_interface.h"],
# Link statically and unconditionally into the main executable so the
# strong sanitizer error summary hook overrides the default weak definition
# in the sanitizer runtime (which is also linked statically into the
# executable).
linkstatic = True,
deps = [
"@abseil-cpp//absl/base:core_headers",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
"@abseil-cpp//absl/strings",
],
"@com_google_fuzztest//common:logging",
] + select({
"//conditions:default": [],
}),
alwayslink = True,
)

cc_test(
Expand All @@ -460,6 +470,7 @@ cc_test(
":sanitizer_interface",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
"@abseil-cpp//absl/strings:string_view",
"@googletest//:gtest_main",
],
)
Expand Down
3 changes: 3 additions & 0 deletions fuzztest/internal/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ fuzztest_cc_library(
SRCS
"sanitizer_interface.cc"
DEPS
fuzztest::common_logging
absl::core_headers
absl::status
absl::statusor
absl::strings
Expand All @@ -439,6 +441,7 @@ fuzztest_cc_test(
fuzztest::sanitizer_interface
absl::status
absl::statusor
absl::string_view
GTest::gmock_main
)

Expand Down
22 changes: 6 additions & 16 deletions fuzztest/internal/runtime.cc
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,14 @@
#include "./fuzztest/internal/io.h"
#include "./fuzztest/internal/logging.h"
#include "./fuzztest/internal/printer.h"
#include "./fuzztest/internal/sanitizer_interface.h"
#include "./fuzztest/internal/serialization.h"
#include "./fuzztest/internal/status.h"

#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(THREAD_SANITIZER)
#define FUZZTEST_HAS_SANITIZER
#include <sanitizer/common_interface_defs.h>

#include "./fuzztest/internal/sanitizer_interface.h"
#endif

#ifndef TRAP_PERF
Expand Down Expand Up @@ -164,20 +163,6 @@ absl::string_view GetSeparator() {
"\n";
}

#if defined(FUZZTEST_HAS_SANITIZER)
// clang-format off
extern "C" void __attribute__((visibility("default")))
__sanitizer_report_error_summary(const char* error_summary) {
// clang-format on
absl::StatusOr<std::string> crash_type =
ParseCrashTypeFromSanitizerSummary(error_summary);
FUZZTEST_LOG_IF(ERROR, !crash_type.ok())
<< "Failed to extract sanitizer crash type: " << crash_type.status();
Runtime::instance().SetCrashTypeIfUnset(
std::move(crash_type).value_or("Sanitizer crash"));
}
#endif

} // namespace

ReproducerOutputLocation GetReproducerOutputLocation() {
Expand Down Expand Up @@ -246,6 +231,11 @@ void PrintReproducerIfRequested(RawSink out, const FuzzTest& test,
void (*crash_handler_hook)();

Runtime::Runtime() {
FuzzTestSetSanitizerErrorSummaryCallback(
[](const char* crash_type_data, size_t crash_type_size) {
Runtime::instance().SetCrashTypeIfUnset(
std::string(crash_type_data, crash_type_size));
});
if (const char* crash_metadata_path =
std::getenv("FUZZTEST_CRASH_METADATA_PATH");
crash_metadata_path != nullptr) {
Expand Down
43 changes: 38 additions & 5 deletions fuzztest/internal/sanitizer_interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,26 @@

#include "./fuzztest/internal/sanitizer_interface.h"

#include <atomic>
#include <cstddef>
#include <optional>
#include <string>

#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "./common/logging.h"

namespace fuzztest::internal {

std::atomic<FuzzTestSanitizerErrorSummaryCallback>
sanitizer_error_summary_callback{nullptr};

namespace {

std::optional<std::string> MaybeExtractTsanCrashType(
std::optional<absl::string_view> MaybeExtractTsanCrashType(
absl::string_view sanitizer_name, absl::string_view error_summary) {
if (sanitizer_name != "ThreadSanitizer") return std::nullopt;

Expand Down Expand Up @@ -75,7 +80,7 @@ std::optional<std::string> MaybeExtractTsanCrashType(

} // namespace

absl::StatusOr<std::string> ParseCrashTypeFromSanitizerSummary(
absl::StatusOr<absl::string_view> ParseCrashTypeFromSanitizerSummary(
absl::string_view error_summary) {
if (!absl::ConsumePrefix(&error_summary, "SUMMARY: ")) {
return absl::InvalidArgumentError(absl::StrCat(
Expand All @@ -94,13 +99,41 @@ absl::StatusOr<std::string> ParseCrashTypeFromSanitizerSummary(
if (error_summary.find("byte(s) leaked") != error_summary.npos) {
return "memory-leak";
}
if (auto tsan_crash_type =
if (std::optional<absl::string_view> tsan_crash_type =
MaybeExtractTsanCrashType(sanitizer_name, error_summary);
tsan_crash_type.has_value()) {
return *tsan_crash_type;
}
const size_t space_pos = error_summary.find(' ');
return std::string(error_summary.substr(0, space_pos));
return error_summary.substr(0, space_pos);
}

} // namespace fuzztest::internal

// clang-format off
extern "C" void __attribute__((visibility("default"), used))
__sanitizer_report_error_summary(const char* error_summary) {
const FuzzTestSanitizerErrorSummaryCallback callback =
fuzztest::internal::sanitizer_error_summary_callback.load(
std::memory_order_relaxed);
if (callback == nullptr) return;
absl::StatusOr<absl::string_view> crash_type =
fuzztest::internal::ParseCrashTypeFromSanitizerSummary(
absl::NullSafeStringView(error_summary));
FUZZTEST_LOG_IF(ERROR, !crash_type.ok())
<< "Failed to extract sanitizer crash type: " << crash_type.status();
const absl::string_view resolved_crash_type =
crash_type.value_or("Sanitizer crash");
callback(resolved_crash_type.data(), resolved_crash_type.size());
}
// clang-format on

extern "C" void FuzzTestSetSanitizerErrorSummaryCallback(
FuzzTestSanitizerErrorSummaryCallback callback) {
// Ensure the sanitizer error summary hook is retained by the linker (e.g.,
// under -Wl,--gc-sections) whenever a callback is registered.
void (*volatile hook)(const char*) = &__sanitizer_report_error_summary;
(void)hook;
fuzztest::internal::sanitizer_error_summary_callback.store(
callback, std::memory_order_relaxed);
}
29 changes: 26 additions & 3 deletions fuzztest/internal/sanitizer_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,41 @@
#ifndef FUZZTEST_FUZZTEST_INTERNAL_SANITIZER_INTERFACE_H_
#define FUZZTEST_FUZZTEST_INTERNAL_SANITIZER_INTERFACE_H_

#include <string>
#include <cstddef>

#include "absl/base/attributes.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"

extern "C" {

using FuzzTestSanitizerErrorSummaryCallback =
void (*)(const char* crash_type_data, size_t crash_type_size);

// Registers a callback to be invoked with the parsed crash type whenever the
// sanitizer runtime reports an error summary.
//
// The `(crash_type_data, crash_type_size)` slice passed to `callback` is
// non-null and non-empty (`crash_type_size > 0`), points either to a static
// string literal or into the `error_summary` buffer passed by the sanitizer
// runtime, and is valid for reads for the duration of the callback invocation
// (or longer if the input `error_summary` outlives the call).
void FuzzTestSetSanitizerErrorSummaryCallback(
FuzzTestSanitizerErrorSummaryCallback callback);

} // extern "C"

namespace fuzztest::internal {

// Parses the crash type from the sanitizer error summary.
// The summary is expected to be in the format:
// "SUMMARY: SomeSanitizer: some-crash-type ..."
absl::StatusOr<std::string> ParseCrashTypeFromSanitizerSummary(
absl::string_view error_summary);
//
// The returned `absl::string_view` points either to a static string literal or
// into `error_summary`, and remains valid for as long as `error_summary` is
// valid.
absl::StatusOr<absl::string_view> ParseCrashTypeFromSanitizerSummary(
absl::string_view error_summary ABSL_ATTRIBUTE_LIFETIME_BOUND);

} // namespace fuzztest::internal

Expand Down
Loading
Loading