[compiler-rt] [nsan] Add NsanThread and clear static TLS shadow (PR #102718)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 10 10:14:55 PDT 2024


https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/102718

>From f000981885e3866c9a13cf433c9cb9de1999eb2c Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Fri, 9 Aug 2024 21:53:04 -0700
Subject: [PATCH] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20initia?=
 =?UTF-8?q?l=20version?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Created using spr 1.3.5-bogner
---
 compiler-rt/lib/nsan/CMakeLists.txt        |   1 +
 compiler-rt/lib/nsan/nsan.cpp              |   6 +
 compiler-rt/lib/nsan/nsan_interceptors.cpp |  37 ++++-
 compiler-rt/lib/nsan/nsan_thread.cpp       | 150 +++++++++++++++++++++
 compiler-rt/lib/nsan/nsan_thread.h         |  68 ++++++++++
 compiler-rt/test/nsan/tls-reuse.c          |  24 ++++
 6 files changed, 284 insertions(+), 2 deletions(-)
 create mode 100644 compiler-rt/lib/nsan/nsan_thread.cpp
 create mode 100644 compiler-rt/lib/nsan/nsan_thread.h
 create mode 100644 compiler-rt/test/nsan/tls-reuse.c

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);
+  }
+}



More information about the llvm-commits mailing list