Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rtsan] Support basic call stack suppressions #111608

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

cjappl
Copy link
Contributor

@cjappl cjappl commented Oct 9, 2024

This adds basic support for suppressions, which is a first class feature of most (all?) of the other sanitizers. Example docs from tsan.

What

This introduces an "anywhere in the call stack" suppression, which is the most basic (and most expensive) kind. For a similar approach, see asan:

bool IsStackTraceSuppressed(const StackTrace *stack) {
if (!HaveStackTraceBasedSuppressions())
return false;
CHECK(suppression_ctx);
Symbolizer *symbolizer = Symbolizer::GetOrInit();
Suppression *s;
for (uptr i = 0; i < stack->size && stack->trace[i]; i++) {
uptr addr = stack->trace[i];
if (suppression_ctx->HasSuppressionType(kInterceptorViaLibrary)) {
// Match "interceptor_via_lib" suppressions.
if (const char *module_name = symbolizer->GetModuleNameForPc(addr))
if (suppression_ctx->Match(module_name, kInterceptorViaLibrary, &s))
return true;
}
if (suppression_ctx->HasSuppressionType(kInterceptorViaFunction)) {
SymbolizedStackHolder symbolized_stack(symbolizer->SymbolizePC(addr));
const SymbolizedStack *frames = symbolized_stack.get();
CHECK(frames);
for (const SymbolizedStack *cur = frames; cur; cur = cur->next) {
const char *function_name = cur->info.function;
if (!function_name) {
continue;
}
// Match "interceptor_via_fun" suppressions.
if (suppression_ctx->Match(function_name, kInterceptorViaFunction,
&s)) {
return true;
}
}
}
}
return false;
}

Extra note for @davidtrevelyan - this just introduces the first version (and least controversial) of the three suppressions we chatted about. I figured we can break this into little chunks where we can discuss the finer points of the other types in more detail. This review introduces a ton of the boring boilerplate to just get it off the ground.

@llvmbot
Copy link
Collaborator

llvmbot commented Oct 9, 2024

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: Chris Apple (cjappl)

Changes

This adds basic support for suppressions, which is a first class feature of most (all?) of the other sanitizers. Example docs from tsan.

What

This introduces an "anywhere in the call stack" suppression, which is the most basic (and most expensive) kind. For a similar approach, see asan:

bool IsStackTraceSuppressed(const StackTrace *stack) {
if (!HaveStackTraceBasedSuppressions())
return false;
CHECK(suppression_ctx);
Symbolizer *symbolizer = Symbolizer::GetOrInit();
Suppression *s;
for (uptr i = 0; i < stack->size && stack->trace[i]; i++) {
uptr addr = stack->trace[i];
if (suppression_ctx->HasSuppressionType(kInterceptorViaLibrary)) {
// Match "interceptor_via_lib" suppressions.
if (const char *module_name = symbolizer->GetModuleNameForPc(addr))
if (suppression_ctx->Match(module_name, kInterceptorViaLibrary, &s))
return true;
}
if (suppression_ctx->HasSuppressionType(kInterceptorViaFunction)) {
SymbolizedStackHolder symbolized_stack(symbolizer->SymbolizePC(addr));
const SymbolizedStack *frames = symbolized_stack.get();
CHECK(frames);
for (const SymbolizedStack *cur = frames; cur; cur = cur->next) {
const char *function_name = cur->info.function;
if (!function_name) {
continue;
}
// Match "interceptor_via_fun" suppressions.
if (suppression_ctx->Match(function_name, kInterceptorViaFunction,
&s)) {
return true;
}
}
}
}
return false;
}

Extra note for @davidtrevelyan - this just introduces the first version (and least controversial) of the three suppressions we chatted about. I figured we can break this into little chunks where we can discuss the finer points of the other types in more detail. This review introduces a ton of the boring boilerplate to just get it off the ground.


Full diff: https://github.com/llvm/llvm-project/pull/111608.diff

9 Files Affected:

  • (modified) compiler-rt/lib/rtsan/CMakeLists.txt (+3)
  • (modified) compiler-rt/lib/rtsan/rtsan.cpp (+3-1)
  • (modified) compiler-rt/lib/rtsan/rtsan_assertions.h (+4)
  • (added) compiler-rt/lib/rtsan/rtsan_checks.inc (+19)
  • (modified) compiler-rt/lib/rtsan/rtsan_flags.inc (+1)
  • (added) compiler-rt/lib/rtsan/rtsan_suppressions.cpp (+94)
  • (added) compiler-rt/lib/rtsan/rtsan_suppressions.h (+22)
  • (added) compiler-rt/test/rtsan/stack_suppressions.cpp (+53)
  • (added) compiler-rt/test/rtsan/stack_suppressions.cpp.supp (+4)
diff --git a/compiler-rt/lib/rtsan/CMakeLists.txt b/compiler-rt/lib/rtsan/CMakeLists.txt
index af34fb63cf53cc..f8dd4d735bc2a3 100644
--- a/compiler-rt/lib/rtsan/CMakeLists.txt
+++ b/compiler-rt/lib/rtsan/CMakeLists.txt
@@ -7,6 +7,7 @@ set(RTSAN_CXX_SOURCES
   rtsan_flags.cpp
   rtsan_interceptors.cpp
   rtsan_stats.cpp
+  rtsan_suppressions.cpp
   )
 
 set(RTSAN_PREINIT_SOURCES
@@ -14,12 +15,14 @@ set(RTSAN_PREINIT_SOURCES
 
 set(RTSAN_HEADERS
   rtsan.h
+  rtsan_checks.inc
   rtsan_assertions.h
   rtsan_context.h
   rtsan_diagnostics.h
   rtsan_flags.h
   rtsan_flags.inc
   rtsan_stats.h
+  rtsan_suppressions.h
   )
 
 set(RTSAN_DEPS)
diff --git a/compiler-rt/lib/rtsan/rtsan.cpp b/compiler-rt/lib/rtsan/rtsan.cpp
index 7691815fd5c1dc..e9f42d3760aa82 100644
--- a/compiler-rt/lib/rtsan/rtsan.cpp
+++ b/compiler-rt/lib/rtsan/rtsan.cpp
@@ -14,12 +14,12 @@
 #include "rtsan/rtsan_flags.h"
 #include "rtsan/rtsan_interceptors.h"
 #include "rtsan/rtsan_stats.h"
+#include "rtsan/rtsan_suppressions.h"
 
 #include "sanitizer_common/sanitizer_atomic.h"
 #include "sanitizer_common/sanitizer_common.h"
 #include "sanitizer_common/sanitizer_mutex.h"
 #include "sanitizer_common/sanitizer_stackdepot.h"
-#include "sanitizer_common/sanitizer_stacktrace.h"
 
 using namespace __rtsan;
 using namespace __sanitizer;
@@ -85,6 +85,8 @@ SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_init() {
   InitializeFlags();
   InitializeInterceptors();
 
+  InitializeSuppressions();
+
   if (flags().print_stats_on_exit)
     Atexit(PrintStatisticsSummary);
 
diff --git a/compiler-rt/lib/rtsan/rtsan_assertions.h b/compiler-rt/lib/rtsan/rtsan_assertions.h
index 745cbea0eb3a29..8183a8202478ff 100644
--- a/compiler-rt/lib/rtsan/rtsan_assertions.h
+++ b/compiler-rt/lib/rtsan/rtsan_assertions.h
@@ -15,6 +15,7 @@
 #include "rtsan/rtsan.h"
 #include "rtsan/rtsan_context.h"
 #include "rtsan/rtsan_diagnostics.h"
+#include "rtsan/rtsan_suppressions.h"
 
 #include "sanitizer_common/sanitizer_stacktrace.h"
 
@@ -34,6 +35,9 @@ void ExpectNotRealtime(Context &context, const DiagnosticsInfo &info,
     stack.Unwind(info.pc, info.bp, nullptr,
                  __sanitizer::common_flags()->fast_unwind_on_fatal);
 
+    if (IsStackTraceSuppressed(stack))
+      return;
+
     OnViolation(stack, info);
   }
 }
diff --git a/compiler-rt/lib/rtsan/rtsan_checks.inc b/compiler-rt/lib/rtsan/rtsan_checks.inc
new file mode 100644
index 00000000000000..d0a720c4575d27
--- /dev/null
+++ b/compiler-rt/lib/rtsan/rtsan_checks.inc
@@ -0,0 +1,19 @@
+//===-- rtsan_checks.inc ----------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// List of suppression checks handled by RTSan runtime.
+//
+//===----------------------------------------------------------------------===//
+#ifndef RTSAN_CHECK
+#error "Define RTSAN_CHECK prior to including this file!"
+#endif
+
+// RTSAN_CHECK(Name, SummaryKind)
+// SummaryKind should be a string literal.
+
+RTSAN_CHECK(InCallStack, "in-call-stack")
diff --git a/compiler-rt/lib/rtsan/rtsan_flags.inc b/compiler-rt/lib/rtsan/rtsan_flags.inc
index 1df71127d19d37..5c3eb3f53a5eb4 100644
--- a/compiler-rt/lib/rtsan/rtsan_flags.inc
+++ b/compiler-rt/lib/rtsan/rtsan_flags.inc
@@ -18,3 +18,4 @@
 
 RTSAN_FLAG(bool, halt_on_error, true, "Exit after first reported error.")
 RTSAN_FLAG(bool, print_stats_on_exit, false, "Print stats on exit.")
+RTSAN_FLAG(const char *, suppressions, "", "Suppressions file name.")
diff --git a/compiler-rt/lib/rtsan/rtsan_suppressions.cpp b/compiler-rt/lib/rtsan/rtsan_suppressions.cpp
new file mode 100644
index 00000000000000..a1f9c316f98d6e
--- /dev/null
+++ b/compiler-rt/lib/rtsan/rtsan_suppressions.cpp
@@ -0,0 +1,94 @@
+//===--- rtsan_suppressions.cpp - Realtime Sanitizer ------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of the RTSan runtime, providing support for suppressions
+//
+//===----------------------------------------------------------------------===//
+
+#include "rtsan/rtsan_suppressions.h"
+
+#include "rtsan/rtsan_flags.h"
+
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+#include "sanitizer_common/sanitizer_suppressions.h"
+#include "sanitizer_common/sanitizer_symbolizer.h"
+
+#include <new>
+
+using namespace __sanitizer;
+using namespace __rtsan;
+
+namespace {
+enum class ErrorType {
+#define RTSAN_CHECK(Name, FSanitizeFlagName) Name,
+#include "rtsan_checks.inc"
+#undef RTSAN_CHECK
+};
+} // namespace
+
+alignas(64) static char suppression_placeholder[sizeof(SuppressionContext)];
+static SuppressionContext *suppression_ctx = nullptr;
+
+static const char *kSuppressionTypes[] = {
+#define RTSAN_CHECK(Name, FSanitizeFlagName) FSanitizeFlagName,
+#include "rtsan_checks.inc"
+#undef RTSAN_CHECK
+};
+
+static const char *ConvertTypeToFlagName(ErrorType Type) {
+  switch (Type) {
+#define RTSAN_CHECK(Name, FSanitizeFlagName)                                   \
+  case ErrorType::Name:                                                        \
+    return FSanitizeFlagName;
+#include "rtsan_checks.inc"
+#undef RTSAN_CHECK
+  }
+  UNREACHABLE("unknown ErrorType!");
+}
+
+void __rtsan::InitializeSuppressions() {
+  CHECK_EQ(nullptr, suppression_ctx);
+
+  // We will use suppression_ctx == nullptr as an early out
+  if (flags().suppressions[0] == 0)
+    return;
+
+  suppression_ctx = new (suppression_placeholder)
+      SuppressionContext(kSuppressionTypes, ARRAY_SIZE(kSuppressionTypes));
+  suppression_ctx->ParseFromFile(flags().suppressions);
+}
+
+bool __rtsan::IsStackTraceSuppressed(const StackTrace &stack) {
+  if (suppression_ctx == nullptr)
+    return false;
+
+  const char *call_stack_flag = ConvertTypeToFlagName(ErrorType::InCallStack);
+  if (!suppression_ctx->HasSuppressionType(call_stack_flag))
+    return false;
+
+  Symbolizer *symbolizer = Symbolizer::GetOrInit();
+  Suppression *s;
+
+  for (uptr i = 0; i < stack.size && stack.trace[i]; i++) {
+    const uptr addr = stack.trace[i];
+
+    SymbolizedStackHolder symbolized_stack(symbolizer->SymbolizePC(addr));
+    const SymbolizedStack *frames = symbolized_stack.get();
+    CHECK(frames);
+    for (const SymbolizedStack *cur = frames; cur; cur = cur->next) {
+      const char *function_name = cur->info.function;
+      if (!function_name)
+        continue;
+
+      if (suppression_ctx->Match(function_name, call_stack_flag, &s))
+        return true;
+    }
+  }
+  return false;
+}
diff --git a/compiler-rt/lib/rtsan/rtsan_suppressions.h b/compiler-rt/lib/rtsan/rtsan_suppressions.h
new file mode 100644
index 00000000000000..45545f8c0e0b65
--- /dev/null
+++ b/compiler-rt/lib/rtsan/rtsan_suppressions.h
@@ -0,0 +1,22 @@
+//===--- rtsan_suppressions.h - Realtime Sanitizer --------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of the RTSan runtime, providing support for suppressions
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "sanitizer_common/sanitizer_stacktrace.h"
+
+namespace __rtsan {
+
+void InitializeSuppressions();
+bool IsStackTraceSuppressed(const __sanitizer::StackTrace &stack);
+
+} // namespace __rtsan
diff --git a/compiler-rt/test/rtsan/stack_suppressions.cpp b/compiler-rt/test/rtsan/stack_suppressions.cpp
new file mode 100644
index 00000000000000..2aceedbb313b11
--- /dev/null
+++ b/compiler-rt/test/rtsan/stack_suppressions.cpp
@@ -0,0 +1,53 @@
+// RUN: %clangxx -fsanitize=realtime %s -o %t
+// RUN: %env_rtsan_opts=suppressions='%s.supp' not %run %t 2>&1 | FileCheck %s
+// UNSUPPORTED: ios
+
+// Intent: Ensure that suppressions work as intended
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <vector>
+
+void *MallocViolation() { return malloc(10); }
+
+void VectorViolations() {
+  // All of these should be suppressed by *vector*
+  std::vector<int> v(10);
+  v.resize(20);
+  v.clear();
+  v.resize(0);
+  v.push_back(1);
+  v.reserve(10);
+}
+
+void BlockFunc() [[clang::blocking]] { usleep(1); }
+
+void *process() [[clang::nonblocking]] {
+  void *ptr = MallocViolation();
+  VectorViolations();
+  BlockFunc();
+  free(ptr);
+
+  // This is the one that should abort the program
+  // Everything else is suppressed
+  usleep(1);
+
+  return ptr;
+}
+
+int main() {
+  process();
+  return 0;
+}
+
+// CHECK-NOT: failed to open suppressions file
+// CHECK: Intercepted call to real-time unsafe function
+// CHECK-SAME: usleep
+
+// CHECK-NOT: Intercepted call to real-time unsafe function
+// CHECK-NOT: malloc
+// CHECK-NOT: vector
+// CHECK-NOT: free
+// CHECK-NOT: BlockFunc
diff --git a/compiler-rt/test/rtsan/stack_suppressions.cpp.supp b/compiler-rt/test/rtsan/stack_suppressions.cpp.supp
new file mode 100644
index 00000000000000..73e1a935c741d8
--- /dev/null
+++ b/compiler-rt/test/rtsan/stack_suppressions.cpp.supp
@@ -0,0 +1,4 @@
+in-call-stack:MallocViolation
+in-call-stack:std::*vector
+in-call-stack:free
+in-call-stack:BlockFunc

// RTSAN_CHECK(Name, SummaryKind)
// SummaryKind should be a string literal.

RTSAN_CHECK(InCallStack, "in-call-stack")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any feedback on this name? See it in use in this .supp below


#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_mutex.h"
#include "sanitizer_common/sanitizer_stackdepot.h"
#include "sanitizer_common/sanitizer_stacktrace.h"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was cruft from the last PR, I can separate this out if we want but figured it was minor enough to roll in here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants