[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