#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
diff --git a/centipede/BUILD b/centipede/BUILD
index 9beecfb..76d1955 100644
--- a/centipede/BUILD
+++ b/centipede/BUILD
@@ -1013,10 +1013,10 @@
 
 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 5fd7e44..0232050 100644
--- a/centipede/dso_example/BUILD
+++ b/centipede/dso_example/BUILD
@@ -61,3 +61,19 @@
         "@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 0000000..59c4c79
--- /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 4e1ab3f..bdebe73 100644
--- a/centipede/runner.cc
+++ b/centipede/runner.cc
@@ -69,7 +69,10 @@
 
 // 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<const uint8_t *>(s1);
   const auto *p2 = static_cast<const uint8_t *>(s2);
   static constexpr size_t kMaxLen = 63;
@@ -89,7 +92,14 @@
   ~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::OnThreadStart() {
-  termination_detector.EnsureAlive();
+  thread_termination_detector.EnsureAlive();
   tls.lowest_sp = tls.top_frame_sp =
       reinterpret_cast<uintptr_t>(__builtin_frame_address(0));
   tls.stack_region_low = GetCurrentThreadStackRegionLow();
@@ -1000,7 +1010,7 @@
   }
 }
 
-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 73b74d7..6fdb49a 100644
--- a/centipede/runner.h
+++ b/centipede/runner.h
@@ -131,7 +131,8 @@
   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 78b33b1..22e19e9 100644
--- a/centipede/runner_fork_server.cc
+++ b/centipede/runner_fork_server.cc
@@ -122,7 +122,8 @@
 // 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 0c564c8..15e611d 100644
--- a/centipede/runner_sancov.cc
+++ b/centipede/runner_sancov.cc
@@ -188,6 +188,7 @@
 // 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 ec3878f..e97c83d 100644
--- a/centipede/testing/build_defs.bzl
+++ b/centipede/testing/build_defs.bzl
@@ -23,13 +23,6 @@
 # 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 @@
         ],
         "//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 @@
     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",
     ],
 )