From d5a8a0f059c29db05f6ec9b75e025d85c229b6ad Mon Sep 17 00:00:00 2001 From: Xinhao Yuan Date: Mon, 30 Oct 2023 10:49:28 -0700 Subject: [PATCH] #Centipede Support memory sanitizer and add new tests. MSAN tests are not enabled in OSS since it requires the standard libraries to be instrumented too. Specially, no_sanitize("memory") is applied on a few new functions to avoid false positives. Also renamed the dtor of the global runner state since MSAN would poison the state memory and crash on further access from the watchdog. PiperOrigin-RevId: 577888953 --- centipede/BUILD | 4 +- centipede/dso_example/BUILD | 16 +++++++ .../dso_example_sanitizer_symbolize_test.sh | 44 +++++++++++++++++++ centipede/runner.cc | 18 ++++++-- centipede/runner.h | 3 +- centipede/runner_fork_server.cc | 3 +- centipede/runner_sancov.cc | 1 + centipede/testing/build_defs.bzl | 12 ----- 8 files changed, 81 insertions(+), 20 deletions(-) create mode 100755 centipede/dso_example/dso_example_sanitizer_symbolize_test.sh diff --git a/centipede/BUILD b/centipede/BUILD index 9beecfba..76d1955b 100644 --- a/centipede/BUILD +++ b/centipede/BUILD @@ -1013,10 +1013,10 @@ RUNNER_SOURCES_NO_MAIN = [ RUNNER_SOURCES_WITH_MAIN = RUNNER_SOURCES_NO_MAIN + ["runner_main.cc"] -# Disable sancov and sanitizer instrumentation +# Disable sancov and sanitizer instrumentation, except for memory sanitizer, which would produce false positive if skipped here. RUNNER_COPTS = [ "-fsanitize-coverage=0", - "-fno-sanitize=address,hwaddress,memory,thread,undefined", + "-fno-sanitize=address,hwaddress,thread,undefined", ] RUNNER_LINKOPTS = [ diff --git a/centipede/dso_example/BUILD b/centipede/dso_example/BUILD index 5fd7e448..02320508 100644 --- a/centipede/dso_example/BUILD +++ b/centipede/dso_example/BUILD @@ -61,3 +61,19 @@ sh_test( "@com_google_fuzztest//centipede:test_util_sh", ], ) + +sh_test( + name = "dso_example_sanitizer_symbolize_test", + srcs = ["dso_example_sanitizer_symbolize_test.sh"], + data = [ + ":main", + ], + tags = [ + "external", + "manual", + "notap", + ], + deps = [ + "@com_google_fuzztest//centipede:test_util_sh", + ], +) diff --git a/centipede/dso_example/dso_example_sanitizer_symbolize_test.sh b/centipede/dso_example/dso_example_sanitizer_symbolize_test.sh new file mode 100755 index 00000000..59c4c79c --- /dev/null +++ b/centipede/dso_example/dso_example_sanitizer_symbolize_test.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Copyright 2023 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. + +# Tests the stack symbolization of the main binary when running with sanitizers. + +set -eu + +source "$(dirname "$0")/../test_util.sh" + +CENTIPEDE_TEST_SRCDIR="$(centipede::get_centipede_test_srcdir)" + +centipede::maybe_set_var_to_executable_path \ + TARGET_BINARY "${CENTIPEDE_TEST_SRCDIR}/dso_example/main" + +INPUT="${TEST_TMPDIR}/input" +LOG="${TEST_TMPDIR}/log" + +echo -n 'FUZ' > "${INPUT}" +unset TEST_WARNINGS_OUTPUT_FILE +export ASAN_OPTIONS='handle_abort=2' +export MSAN_OPTIONS='handle_abort=2' +export TSAN_OPTIONS='handle_abort=2' +export CENTIPEDE_RUNNER_FLAGS=":use_cmp_features:" +"${TARGET_BINARY}" "${INPUT}" |& tee "${LOG}" + +# Check that sanitizer reports the crash +centipede::assert_regex_in_file "ERROR: .*Sanitizer:" "${LOG}" +# Check that the intended stack location is symbolized and printed +centipede::assert_regex_in_file "#0 .* in FuzzMe" "${LOG}" + +echo "PASS" diff --git a/centipede/runner.cc b/centipede/runner.cc index 4e1ab3fe..bdebe73f 100644 --- a/centipede/runner.cc +++ b/centipede/runner.cc @@ -69,7 +69,10 @@ namespace { // Returns the length of the common prefix of `s1` and `s2`, but not more // than 63. I.e. the returned value is in [0, 64). -size_t LengthOfCommonPrefix(const void *s1, const void *s2, size_t n) { +// +// Needed to skip memory sanitizer to avoid false positives in error reporting. +__attribute__((no_sanitize("memory"))) size_t LengthOfCommonPrefix( + const void *s1, const void *s2, size_t n) { const auto *p1 = static_cast(s1); const auto *p2 = static_cast(s2); static constexpr size_t kMaxLen = 63; @@ -89,7 +92,14 @@ class ThreadTerminationDetector { ~ThreadTerminationDetector() { tls.OnThreadStop(); } }; -thread_local ThreadTerminationDetector termination_detector; +thread_local ThreadTerminationDetector thread_termination_detector; + +class ProcessTerminationDetector { + public: + ~ProcessTerminationDetector() { state.OnProcessExit(); } +}; + +static ProcessTerminationDetector process_termination_detector; } // namespace @@ -142,7 +152,7 @@ void ThreadLocalRunnerState::TraceMemCmp(uintptr_t caller_pc, const uint8_t *s1, } void ThreadLocalRunnerState::OnThreadStart() { - termination_detector.EnsureAlive(); + thread_termination_detector.EnsureAlive(); tls.lowest_sp = tls.top_frame_sp = reinterpret_cast(__builtin_frame_address(0)); tls.stack_region_low = GetCurrentThreadStackRegionLow(); @@ -1000,7 +1010,7 @@ GlobalRunnerState::GlobalRunnerState() { } } -GlobalRunnerState::~GlobalRunnerState() { +void GlobalRunnerState::OnProcessExit() { // 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. diff --git a/centipede/runner.h b/centipede/runner.h index 73b74d74..6fdb49a4 100644 --- a/centipede/runner.h +++ b/centipede/runner.h @@ -131,7 +131,8 @@ struct GlobalRunnerState { Knobs knobs; GlobalRunnerState(); - ~GlobalRunnerState(); + + void OnProcessExit(); // Runner reads flags from CentipedeGetRunnerFlags(). We don't use flags // passed via argv so that argv flags can be passed directly to diff --git a/centipede/runner_fork_server.cc b/centipede/runner_fork_server.cc index 78b33b12..22e19e92 100644 --- a/centipede/runner_fork_server.cc +++ b/centipede/runner_fork_server.cc @@ -122,7 +122,8 @@ const char *GetOneEnv(const char *key) { // without explicitly specified priority run after all constructors with // explicitly specified priority, thus we still run before most // "normal" constructors. -__attribute__((constructor(150))) void ForkServerCallMeVeryEarly() { +__attribute__((constructor(150), no_sanitize("memory"))) void +ForkServerCallMeVeryEarly() { // Guard against calling twice. static bool called_already = false; if (called_already) return; diff --git a/centipede/runner_sancov.cc b/centipede/runner_sancov.cc index 0c564c84..15e611d0 100644 --- a/centipede/runner_sancov.cc +++ b/centipede/runner_sancov.cc @@ -188,6 +188,7 @@ __attribute__((noinline)) static void HandlePath(uintptr_t normalized_pc) { // With __sanitizer_cov_trace_pc_guard this is an index of PC in the PC table. // With __sanitizer_cov_trace_pc this is PC itself, normalized by subtracting // the DSO's dynamic start address. +NO_SANITIZE static inline void HandleOnePc(PCGuard pc_guard) { if (!state.run_time_flags.use_pc_features) return; state.pc_counter_set.SaturatedIncrement(pc_guard.pc_index); diff --git a/centipede/testing/build_defs.bzl b/centipede/testing/build_defs.bzl index ec3878f0..e97c83d4 100644 --- a/centipede/testing/build_defs.bzl +++ b/centipede/testing/build_defs.bzl @@ -23,13 +23,6 @@ affect all its transitive dependencies as well. # Change the flags from the default ones to sancov: # https://clang.llvm.org/docs/SanitizerCoverage.html. def _sancov_transition_impl(settings, attr): - features_to_strip = ["tsan", "msan"] - filtered_features = [ - x - for x in settings["//command_line_option:features"] - if x not in features_to_strip - ] - # some of the valid sancov flag combinations: # trace-pc-guard,pc-table # trace-pc-guard,pc-table,trace-cmp @@ -50,8 +43,6 @@ def _sancov_transition_impl(settings, attr): ], "//command_line_option:compilation_mode": "opt", "//command_line_option:strip": "never", # preserve debug info. - "//command_line_option:features": filtered_features, - "//command_line_option:compiler": None, "//command_line_option:dynamic_mode": "off", } @@ -59,15 +50,12 @@ sancov_transition = transition( implementation = _sancov_transition_impl, inputs = [ "//command_line_option:copt", - "//command_line_option:features", ], outputs = [ "//command_line_option:collect_code_coverage", "//command_line_option:copt", "//command_line_option:compilation_mode", "//command_line_option:strip", - "//command_line_option:features", - "//command_line_option:compiler", "//command_line_option:dynamic_mode", ], )