[compiler-rt] 249db51 - [nsan] Add NsanThread and clear static TLS shadow

via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 11 10:53:21 PDT 2024


Author: Fangrui Song
Date: 2024-08-11T10:53:18-07:00
New Revision: 249db518e324f8658bf5b2c0e3c0a1ee789d784b

URL: https://github.com/llvm/llvm-project/commit/249db518e324f8658bf5b2c0e3c0a1ee789d784b
DIFF: https://github.com/llvm/llvm-project/commit/249db518e324f8658bf5b2c0e3c0a1ee789d784b.diff

LOG: [nsan] Add NsanThread and clear static TLS shadow

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.

Pull Request: https://github.com/llvm/llvm-project/pull/102718

Added: 
    compiler-rt/lib/nsan/nsan_thread.cpp
    compiler-rt/lib/nsan/nsan_thread.h
    compiler-rt/test/nsan/Posix/tls_reuse.c

Modified: 
    compiler-rt/lib/nsan/CMakeLists.txt
    compiler-rt/lib/nsan/nsan.cpp
    compiler-rt/lib/nsan/nsan_interceptors.cpp

Removed: 
    


################################################################################
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..d607c8d6a636b8 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) {
+  auto *t = reinterpret_cast<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..273c46831cf381
--- /dev/null
+++ b/compiler-rt/lib/nsan/nsan_thread.cpp
@@ -0,0 +1,160 @@
+//===- nsan_threads.cpp ---------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+// Thread management.
+//===----------------------------------------------------------------------===//
+
+#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;
+
+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/Posix/tls_reuse.c b/compiler-rt/test/nsan/Posix/tls_reuse.c
new file mode 100644
index 00000000000000..e323260d15e385
--- /dev/null
+++ b/compiler-rt/test/nsan/Posix/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 %run %t
+
+#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);
+  }
+}


        


More information about the llvm-commits mailing list