Skip to content

[nsan] Add NsanThread and clear static TLS shadow #102718

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

Merged

Conversation

MaskRay
Copy link
Member

@MaskRay MaskRay commented Aug 10, 2024

On thread creation, asan/hwasan/msan/tsan unpoison the thread stack and
static TLS blocks in case the blocks reuse previously freed memory that
is possibly poisoned. glibc nptl/allocatestack.c allocates thread stack
using a hidden, non-interceptable function.

nsan is similar: the shadow types for the thread stack and static TLS
blocks should be set to unknown, otherwise if the static TLS blocks
reuse previous shadow memory, and *p += x instead of *p = x is used
for the first assignment, the mismatching user and shadow memory could
lead to false positives.

NsanThread is also needed by the next patch to use the sanitizer
allocator.

Created using spr 1.3.5-bogner
@llvmbot
Copy link
Member

llvmbot commented Aug 10, 2024

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

Author: Fangrui Song (MaskRay)

Changes

On thread creation, asan/hwasan/msan/tsan unpoison the thread stack and
static TLS blocks in case the blocks reuse previously freed memory that
is possibly poisoned. nsan is similar: the shadow types for the thread
stack and static TLS blocks should be set to unknown, otherwise floating
point TLS might reuse previous shadow memory.

NsanThread is also needed by the next patch to use the sanitizer
allocator.


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

6 Files Affected:

  • (modified) compiler-rt/lib/nsan/CMakeLists.txt (+1)
  • (modified) compiler-rt/lib/nsan/nsan.cpp (+6)
  • (modified) compiler-rt/lib/nsan/nsan_interceptors.cpp (+35-2)
  • (added) compiler-rt/lib/nsan/nsan_thread.cpp (+150)
  • (added) compiler-rt/lib/nsan/nsan_thread.h (+68)
  • (added) compiler-rt/test/nsan/tls-reuse.c (+24)
diff --git a/compiler-rt/lib/nsan/CMakeLists.txt b/compiler-rt/lib/nsan/CMakeLists.txt
index acadb09c3332bf..fa9f02abdf0801 100644
--- a/compiler-rt/lib/nsan/CMakeLists.txt
+++ b/compiler-rt/lib/nsan/CMakeLists.txt
@@ -9,6 +9,7 @@ set(NSAN_SOURCES
   nsan_malloc_linux.cpp
   nsan_stats.cpp
   nsan_suppressions.cpp
+  nsan_thread.cpp
 )
 
 set(NSAN_PREINIT_SOURCES
diff --git a/compiler-rt/lib/nsan/nsan.cpp b/compiler-rt/lib/nsan/nsan.cpp
index 499e823ae22599..7d10681a1bc917 100644
--- a/compiler-rt/lib/nsan/nsan.cpp
+++ b/compiler-rt/lib/nsan/nsan.cpp
@@ -34,6 +34,7 @@
 #include "nsan_flags.h"
 #include "nsan_stats.h"
 #include "nsan_suppressions.h"
+#include "nsan_thread.h"
 
 #include <assert.h>
 #include <math.h>
@@ -817,6 +818,11 @@ extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __nsan_init() {
     Die();
 
   InitializeInterceptors();
+  NsanTSDInit(NsanTSDDtor);
+
+  NsanThread *main_thread = NsanThread::Create(nullptr, nullptr);
+  SetCurrentThread(main_thread);
+  main_thread->Init();
 
   InitializeStats();
   if (flags().print_stats_on_exit)
diff --git a/compiler-rt/lib/nsan/nsan_interceptors.cpp b/compiler-rt/lib/nsan/nsan_interceptors.cpp
index 95b411bed2600f..c8a389384f2850 100644
--- a/compiler-rt/lib/nsan/nsan_interceptors.cpp
+++ b/compiler-rt/lib/nsan/nsan_interceptors.cpp
@@ -17,13 +17,14 @@
 
 #include "interception/interception.h"
 #include "nsan.h"
+#include "nsan_thread.h"
 #include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_linux.h"
 
 #include <wchar.h>
 
+using namespace __nsan;
 using namespace __sanitizer;
-using __nsan::nsan_init_is_running;
-using __nsan::nsan_initialized;
 
 template <typename T> T min(T a, T b) { return a < b ? a : b; }
 
@@ -201,6 +202,36 @@ INTERCEPTOR(uptr, strxfrm, char *dst, const char *src, uptr size) {
   return res;
 }
 
+extern "C" int pthread_attr_init(void *attr);
+extern "C" int pthread_attr_destroy(void *attr);
+
+static void *NsanThreadStartFunc(void *arg) {
+  NsanThread *t = (NsanThread *)arg;
+  SetCurrentThread(t);
+  t->Init();
+  SetSigProcMask(&t->starting_sigset_, nullptr);
+  return t->ThreadStart();
+}
+
+INTERCEPTOR(int, pthread_create, void *th, void *attr,
+            void *(*callback)(void *), void *param) {
+  __sanitizer_pthread_attr_t myattr;
+  if (!attr) {
+    pthread_attr_init(&myattr);
+    attr = &myattr;
+  }
+
+  AdjustStackSize(attr);
+
+  NsanThread *t = NsanThread::Create(callback, param);
+  ScopedBlockSignals block(&t->starting_sigset_);
+  int res = REAL(pthread_create)(th, attr, NsanThreadStartFunc, t);
+
+  if (attr == &myattr)
+    pthread_attr_destroy(&myattr);
+  return res;
+}
+
 void __nsan::InitializeInterceptors() {
   static bool initialized = false;
   CHECK(!initialized);
@@ -231,5 +262,7 @@ void __nsan::InitializeInterceptors() {
   INTERCEPT_FUNCTION(strsep);
   INTERCEPT_FUNCTION(strtok);
 
+  INTERCEPT_FUNCTION(pthread_create);
+
   initialized = 1;
 }
diff --git a/compiler-rt/lib/nsan/nsan_thread.cpp b/compiler-rt/lib/nsan/nsan_thread.cpp
new file mode 100644
index 00000000000000..211c87afa9d5cc
--- /dev/null
+++ b/compiler-rt/lib/nsan/nsan_thread.cpp
@@ -0,0 +1,150 @@
+#include "nsan_thread.h"
+
+#include <pthread.h>
+
+#include "nsan.h"
+#include "sanitizer_common/sanitizer_tls_get_addr.h"
+
+using namespace __nsan;
+
+NsanThread *NsanThread::Create(thread_callback_t start_routine, void *arg) {
+  uptr PageSize = GetPageSizeCached();
+  uptr size = RoundUpTo(sizeof(NsanThread), PageSize);
+  NsanThread *thread = (NsanThread *)MmapOrDie(size, __func__);
+  thread->start_routine_ = start_routine;
+  thread->arg_ = arg;
+  thread->destructor_iterations_ = GetPthreadDestructorIterations();
+
+  return thread;
+}
+
+void NsanThread::SetThreadStackAndTls() {
+  uptr tls_size = 0;
+  uptr stack_size = 0;
+  GetThreadStackAndTls(IsMainThread(), &stack_.bottom, &stack_size, &tls_begin_,
+                       &tls_size);
+  stack_.top = stack_.bottom + stack_size;
+  tls_end_ = tls_begin_ + tls_size;
+
+  int local;
+  CHECK(AddrIsInStack((uptr)&local));
+}
+
+void NsanThread::ClearShadowForThreadStackAndTLS() {
+  __nsan_set_value_unknown((const u8 *)stack_.bottom,
+                           stack_.top - stack_.bottom);
+  if (tls_begin_ != tls_end_)
+    __nsan_set_value_unknown((const u8 *)tls_begin_, tls_end_ - tls_begin_);
+  DTLS *dtls = DTLS_Get();
+  CHECK_NE(dtls, 0);
+  ForEachDVT(dtls, [](const DTLS::DTV &dtv, int id) {
+    __nsan_set_value_unknown((const u8 *)dtv.beg, dtv.size);
+  });
+}
+
+void NsanThread::Init() {
+  SetThreadStackAndTls();
+  ClearShadowForThreadStackAndTLS();
+}
+
+void NsanThread::TSDDtor(void *tsd) {
+  NsanThread *t = (NsanThread *)tsd;
+  t->Destroy();
+}
+
+void NsanThread::Destroy() {
+  // We also clear the shadow on thread destruction because
+  // some code may still be executing in later TSD destructors
+  // and we don't want it to have any poisoned stack.
+  ClearShadowForThreadStackAndTLS();
+  uptr size = RoundUpTo(sizeof(NsanThread), GetPageSizeCached());
+  UnmapOrDie(this, size);
+  DTLS_Destroy();
+}
+
+thread_return_t NsanThread::ThreadStart() {
+  if (!start_routine_) {
+    // start_routine_ == 0 if we're on the main thread or on one of the
+    // OS X libdispatch worker threads. But nobody is supposed to call
+    // ThreadStart() for the worker threads.
+    return 0;
+  }
+
+  return start_routine_(arg_);
+}
+
+NsanThread::StackBounds NsanThread::GetStackBounds() const {
+  if (!stack_switching_)
+    return {stack_.bottom, stack_.top};
+  const uptr cur_stack = GET_CURRENT_FRAME();
+  // Note: need to check next stack first, because FinishSwitchFiber
+  // may be in process of overwriting stack_.top/bottom_. But in such case
+  // we are already on the next stack.
+  if (cur_stack >= next_stack_.bottom && cur_stack < next_stack_.top)
+    return {next_stack_.bottom, next_stack_.top};
+  return {stack_.bottom, stack_.top};
+}
+
+uptr NsanThread::stack_top() { return GetStackBounds().top; }
+
+uptr NsanThread::stack_bottom() { return GetStackBounds().bottom; }
+
+bool NsanThread::AddrIsInStack(uptr addr) {
+  const auto bounds = GetStackBounds();
+  return addr >= bounds.bottom && addr < bounds.top;
+}
+
+void NsanThread::StartSwitchFiber(uptr bottom, uptr size) {
+  CHECK(!stack_switching_);
+  next_stack_.bottom = bottom;
+  next_stack_.top = bottom + size;
+  stack_switching_ = true;
+}
+
+void NsanThread::FinishSwitchFiber(uptr *bottom_old, uptr *size_old) {
+  CHECK(stack_switching_);
+  if (bottom_old)
+    *bottom_old = stack_.bottom;
+  if (size_old)
+    *size_old = stack_.top - stack_.bottom;
+  stack_.bottom = next_stack_.bottom;
+  stack_.top = next_stack_.top;
+  stack_switching_ = false;
+  next_stack_.top = 0;
+  next_stack_.bottom = 0;
+}
+
+static pthread_key_t tsd_key;
+static bool tsd_key_inited = false;
+
+void __nsan::NsanTSDInit(void (*destructor)(void *tsd)) {
+  CHECK(!tsd_key_inited);
+  tsd_key_inited = true;
+  CHECK_EQ(0, pthread_key_create(&tsd_key, destructor));
+}
+
+static THREADLOCAL NsanThread *nsan_current_thread;
+
+NsanThread *__nsan::GetCurrentThread() { return nsan_current_thread; }
+
+void __nsan::SetCurrentThread(NsanThread *t) {
+  // Make sure we do not reset the current NsanThread.
+  CHECK_EQ(0, nsan_current_thread);
+  nsan_current_thread = t;
+  // Make sure that NsanTSDDtor gets called at the end.
+  CHECK(tsd_key_inited);
+  pthread_setspecific(tsd_key, t);
+}
+
+void __nsan::NsanTSDDtor(void *tsd) {
+  NsanThread *t = (NsanThread *)tsd;
+  if (t->destructor_iterations_ > 1) {
+    t->destructor_iterations_--;
+    CHECK_EQ(0, pthread_setspecific(tsd_key, tsd));
+    return;
+  }
+  nsan_current_thread = nullptr;
+  // Make sure that signal handler can not see a stale current thread pointer.
+  atomic_signal_fence(memory_order_seq_cst);
+  NsanThread::TSDDtor(tsd);
+}
diff --git a/compiler-rt/lib/nsan/nsan_thread.h b/compiler-rt/lib/nsan/nsan_thread.h
new file mode 100644
index 00000000000000..18f24fd6f1d78a
--- /dev/null
+++ b/compiler-rt/lib/nsan/nsan_thread.h
@@ -0,0 +1,68 @@
+//===- nsan_thread.h --------------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef NSAN_THREAD_H
+#define NSAN_THREAD_H
+
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_posix.h"
+
+namespace __nsan {
+
+class NsanThread {
+public:
+  static NsanThread *Create(thread_callback_t start_routine, void *arg);
+  static void TSDDtor(void *tsd);
+  void Destroy();
+
+  void Init(); // Should be called from the thread itself.
+  thread_return_t ThreadStart();
+
+  uptr stack_top();
+  uptr stack_bottom();
+  uptr tls_begin() { return tls_begin_; }
+  uptr tls_end() { return tls_end_; }
+  bool IsMainThread() { return start_routine_ == nullptr; }
+
+  bool AddrIsInStack(uptr addr);
+
+  void StartSwitchFiber(uptr bottom, uptr size);
+  void FinishSwitchFiber(uptr *bottom_old, uptr *size_old);
+
+  int destructor_iterations_;
+  __sanitizer_sigset_t starting_sigset_;
+
+private:
+  void SetThreadStackAndTls();
+  void ClearShadowForThreadStackAndTLS();
+  struct StackBounds {
+    uptr bottom;
+    uptr top;
+  };
+  StackBounds GetStackBounds() const;
+
+  thread_callback_t start_routine_;
+  void *arg_;
+
+  bool stack_switching_;
+
+  StackBounds stack_;
+  StackBounds next_stack_;
+
+  uptr tls_begin_;
+  uptr tls_end_;
+};
+
+NsanThread *GetCurrentThread();
+void SetCurrentThread(NsanThread *t);
+void NsanTSDInit(void (*destructor)(void *tsd));
+void NsanTSDDtor(void *tsd);
+
+} // namespace __nsan
+
+#endif // NSAN_THREAD_H
diff --git a/compiler-rt/test/nsan/tls-reuse.c b/compiler-rt/test/nsan/tls-reuse.c
new file mode 100644
index 00000000000000..5cf5f3eb711d45
--- /dev/null
+++ b/compiler-rt/test/nsan/tls-reuse.c
@@ -0,0 +1,24 @@
+/// The static TLS block is reused among by threads. The shadow is cleared.
+// RUN: %clang_nsan %s -o %t
+// RUN: NSAN_OPTIONS=halt_on_error=1,log2_max_relative_error=19 not %run %t 2>&1 | FileCheck %s
+
+#include <pthread.h>
+#include <stdio.h>
+
+__thread float x;
+
+static void *ThreadFn(void *a) {
+  long i = (long)a;
+  for (long j = i * 1000; j < (i+1) * 1000; j++)
+    x += j;
+  printf("%f\n", x);
+  return 0;
+}
+
+int main() {
+  pthread_t t;
+  for (long i = 0; i < 5; ++i) {
+    pthread_create(&t, 0, ThreadFn, (void *)i);
+    pthread_join(t, 0);
+  }
+}

Copy link

github-actions bot commented Aug 10, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

Created using spr 1.3.5-bogner
@alexander-shaposhnikov
Copy link
Collaborator

LG

Created using spr 1.3.5-bogner
Created using spr 1.3.5-bogner
@MaskRay MaskRay merged commit 249db51 into main Aug 11, 2024
5 of 7 checks passed
@MaskRay MaskRay deleted the users/MaskRay/spr/nsan-add-nsanthread-and-clear-static-tls-shadow branch August 11, 2024 17:53
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.

3 participants