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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler-rt/lib/nsan/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ set(NSAN_SOURCES
nsan_malloc_linux.cpp
nsan_stats.cpp
nsan_suppressions.cpp
nsan_thread.cpp
)

set(NSAN_PREINIT_SOURCES
Expand Down
6 changes: 6 additions & 0 deletions compiler-rt/lib/nsan/nsan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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)
Expand Down
37 changes: 35 additions & 2 deletions compiler-rt/lib/nsan/nsan_interceptors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -231,5 +262,7 @@ void __nsan::InitializeInterceptors() {
INTERCEPT_FUNCTION(strsep);
INTERCEPT_FUNCTION(strtok);

INTERCEPT_FUNCTION(pthread_create);

initialized = 1;
}
160 changes: 160 additions & 0 deletions compiler-rt/lib/nsan/nsan_thread.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
68 changes: 68 additions & 0 deletions compiler-rt/lib/nsan/nsan_thread.h
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions compiler-rt/test/nsan/Posix/tls_reuse.c
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading