Skip to content

Commit 249db51

Browse files
authored
[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: #102718
1 parent 257c479 commit 249db51

File tree

6 files changed

+294
-2
lines changed

6 files changed

+294
-2
lines changed

compiler-rt/lib/nsan/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ set(NSAN_SOURCES
99
nsan_malloc_linux.cpp
1010
nsan_stats.cpp
1111
nsan_suppressions.cpp
12+
nsan_thread.cpp
1213
)
1314

1415
set(NSAN_PREINIT_SOURCES

compiler-rt/lib/nsan/nsan.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "nsan_flags.h"
3535
#include "nsan_stats.h"
3636
#include "nsan_suppressions.h"
37+
#include "nsan_thread.h"
3738

3839
#include <assert.h>
3940
#include <math.h>
@@ -817,6 +818,11 @@ extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __nsan_init() {
817818
Die();
818819

819820
InitializeInterceptors();
821+
NsanTSDInit(NsanTSDDtor);
822+
823+
NsanThread *main_thread = NsanThread::Create(nullptr, nullptr);
824+
SetCurrentThread(main_thread);
825+
main_thread->Init();
820826

821827
InitializeStats();
822828
if (flags().print_stats_on_exit)

compiler-rt/lib/nsan/nsan_interceptors.cpp

+35-2
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717

1818
#include "interception/interception.h"
1919
#include "nsan.h"
20+
#include "nsan_thread.h"
2021
#include "sanitizer_common/sanitizer_common.h"
22+
#include "sanitizer_common/sanitizer_linux.h"
2123

2224
#include <wchar.h>
2325

26+
using namespace __nsan;
2427
using namespace __sanitizer;
25-
using __nsan::nsan_init_is_running;
26-
using __nsan::nsan_initialized;
2728

2829
template <typename T> T min(T a, T b) { return a < b ? a : b; }
2930

@@ -201,6 +202,36 @@ INTERCEPTOR(uptr, strxfrm, char *dst, const char *src, uptr size) {
201202
return res;
202203
}
203204

205+
extern "C" int pthread_attr_init(void *attr);
206+
extern "C" int pthread_attr_destroy(void *attr);
207+
208+
static void *NsanThreadStartFunc(void *arg) {
209+
auto *t = reinterpret_cast<NsanThread *>(arg);
210+
SetCurrentThread(t);
211+
t->Init();
212+
SetSigProcMask(&t->starting_sigset_, nullptr);
213+
return t->ThreadStart();
214+
}
215+
216+
INTERCEPTOR(int, pthread_create, void *th, void *attr,
217+
void *(*callback)(void *), void *param) {
218+
__sanitizer_pthread_attr_t myattr;
219+
if (!attr) {
220+
pthread_attr_init(&myattr);
221+
attr = &myattr;
222+
}
223+
224+
AdjustStackSize(attr);
225+
226+
NsanThread *t = NsanThread::Create(callback, param);
227+
ScopedBlockSignals block(&t->starting_sigset_);
228+
int res = REAL(pthread_create)(th, attr, NsanThreadStartFunc, t);
229+
230+
if (attr == &myattr)
231+
pthread_attr_destroy(&myattr);
232+
return res;
233+
}
234+
204235
void __nsan::InitializeInterceptors() {
205236
static bool initialized = false;
206237
CHECK(!initialized);
@@ -231,5 +262,7 @@ void __nsan::InitializeInterceptors() {
231262
INTERCEPT_FUNCTION(strsep);
232263
INTERCEPT_FUNCTION(strtok);
233264

265+
INTERCEPT_FUNCTION(pthread_create);
266+
234267
initialized = 1;
235268
}

compiler-rt/lib/nsan/nsan_thread.cpp

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//===- nsan_threads.cpp ---------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
// Thread management.
9+
//===----------------------------------------------------------------------===//
10+
11+
#include "nsan_thread.h"
12+
13+
#include <pthread.h>
14+
15+
#include "nsan.h"
16+
#include "sanitizer_common/sanitizer_tls_get_addr.h"
17+
18+
using namespace __nsan;
19+
20+
NsanThread *NsanThread::Create(thread_callback_t start_routine, void *arg) {
21+
uptr PageSize = GetPageSizeCached();
22+
uptr size = RoundUpTo(sizeof(NsanThread), PageSize);
23+
NsanThread *thread = (NsanThread *)MmapOrDie(size, __func__);
24+
thread->start_routine_ = start_routine;
25+
thread->arg_ = arg;
26+
thread->destructor_iterations_ = GetPthreadDestructorIterations();
27+
28+
return thread;
29+
}
30+
31+
void NsanThread::SetThreadStackAndTls() {
32+
uptr tls_size = 0;
33+
uptr stack_size = 0;
34+
GetThreadStackAndTls(IsMainThread(), &stack_.bottom, &stack_size, &tls_begin_,
35+
&tls_size);
36+
stack_.top = stack_.bottom + stack_size;
37+
tls_end_ = tls_begin_ + tls_size;
38+
39+
int local;
40+
CHECK(AddrIsInStack((uptr)&local));
41+
}
42+
43+
void NsanThread::ClearShadowForThreadStackAndTLS() {
44+
__nsan_set_value_unknown((const u8 *)stack_.bottom,
45+
stack_.top - stack_.bottom);
46+
if (tls_begin_ != tls_end_)
47+
__nsan_set_value_unknown((const u8 *)tls_begin_, tls_end_ - tls_begin_);
48+
DTLS *dtls = DTLS_Get();
49+
CHECK_NE(dtls, 0);
50+
ForEachDVT(dtls, [](const DTLS::DTV &dtv, int id) {
51+
__nsan_set_value_unknown((const u8 *)dtv.beg, dtv.size);
52+
});
53+
}
54+
55+
void NsanThread::Init() {
56+
SetThreadStackAndTls();
57+
ClearShadowForThreadStackAndTLS();
58+
}
59+
60+
void NsanThread::TSDDtor(void *tsd) {
61+
NsanThread *t = (NsanThread *)tsd;
62+
t->Destroy();
63+
}
64+
65+
void NsanThread::Destroy() {
66+
// We also clear the shadow on thread destruction because
67+
// some code may still be executing in later TSD destructors
68+
// and we don't want it to have any poisoned stack.
69+
ClearShadowForThreadStackAndTLS();
70+
uptr size = RoundUpTo(sizeof(NsanThread), GetPageSizeCached());
71+
UnmapOrDie(this, size);
72+
DTLS_Destroy();
73+
}
74+
75+
thread_return_t NsanThread::ThreadStart() {
76+
if (!start_routine_) {
77+
// start_routine_ == 0 if we're on the main thread or on one of the
78+
// OS X libdispatch worker threads. But nobody is supposed to call
79+
// ThreadStart() for the worker threads.
80+
return 0;
81+
}
82+
83+
return start_routine_(arg_);
84+
}
85+
86+
NsanThread::StackBounds NsanThread::GetStackBounds() const {
87+
if (!stack_switching_)
88+
return {stack_.bottom, stack_.top};
89+
const uptr cur_stack = GET_CURRENT_FRAME();
90+
// Note: need to check next stack first, because FinishSwitchFiber
91+
// may be in process of overwriting stack_.top/bottom_. But in such case
92+
// we are already on the next stack.
93+
if (cur_stack >= next_stack_.bottom && cur_stack < next_stack_.top)
94+
return {next_stack_.bottom, next_stack_.top};
95+
return {stack_.bottom, stack_.top};
96+
}
97+
98+
uptr NsanThread::stack_top() { return GetStackBounds().top; }
99+
100+
uptr NsanThread::stack_bottom() { return GetStackBounds().bottom; }
101+
102+
bool NsanThread::AddrIsInStack(uptr addr) {
103+
const auto bounds = GetStackBounds();
104+
return addr >= bounds.bottom && addr < bounds.top;
105+
}
106+
107+
void NsanThread::StartSwitchFiber(uptr bottom, uptr size) {
108+
CHECK(!stack_switching_);
109+
next_stack_.bottom = bottom;
110+
next_stack_.top = bottom + size;
111+
stack_switching_ = true;
112+
}
113+
114+
void NsanThread::FinishSwitchFiber(uptr *bottom_old, uptr *size_old) {
115+
CHECK(stack_switching_);
116+
if (bottom_old)
117+
*bottom_old = stack_.bottom;
118+
if (size_old)
119+
*size_old = stack_.top - stack_.bottom;
120+
stack_.bottom = next_stack_.bottom;
121+
stack_.top = next_stack_.top;
122+
stack_switching_ = false;
123+
next_stack_.top = 0;
124+
next_stack_.bottom = 0;
125+
}
126+
127+
static pthread_key_t tsd_key;
128+
static bool tsd_key_inited;
129+
130+
void __nsan::NsanTSDInit(void (*destructor)(void *tsd)) {
131+
CHECK(!tsd_key_inited);
132+
tsd_key_inited = true;
133+
CHECK_EQ(0, pthread_key_create(&tsd_key, destructor));
134+
}
135+
136+
static THREADLOCAL NsanThread *nsan_current_thread;
137+
138+
NsanThread *__nsan::GetCurrentThread() { return nsan_current_thread; }
139+
140+
void __nsan::SetCurrentThread(NsanThread *t) {
141+
// Make sure we do not reset the current NsanThread.
142+
CHECK_EQ(0, nsan_current_thread);
143+
nsan_current_thread = t;
144+
// Make sure that NsanTSDDtor gets called at the end.
145+
CHECK(tsd_key_inited);
146+
pthread_setspecific(tsd_key, t);
147+
}
148+
149+
void __nsan::NsanTSDDtor(void *tsd) {
150+
NsanThread *t = (NsanThread *)tsd;
151+
if (t->destructor_iterations_ > 1) {
152+
t->destructor_iterations_--;
153+
CHECK_EQ(0, pthread_setspecific(tsd_key, tsd));
154+
return;
155+
}
156+
nsan_current_thread = nullptr;
157+
// Make sure that signal handler can not see a stale current thread pointer.
158+
atomic_signal_fence(memory_order_seq_cst);
159+
NsanThread::TSDDtor(tsd);
160+
}

compiler-rt/lib/nsan/nsan_thread.h

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//===- nsan_thread.h --------------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef NSAN_THREAD_H
10+
#define NSAN_THREAD_H
11+
12+
#include "sanitizer_common/sanitizer_common.h"
13+
#include "sanitizer_common/sanitizer_posix.h"
14+
15+
namespace __nsan {
16+
17+
class NsanThread {
18+
public:
19+
static NsanThread *Create(thread_callback_t start_routine, void *arg);
20+
static void TSDDtor(void *tsd);
21+
void Destroy();
22+
23+
void Init(); // Should be called from the thread itself.
24+
thread_return_t ThreadStart();
25+
26+
uptr stack_top();
27+
uptr stack_bottom();
28+
uptr tls_begin() { return tls_begin_; }
29+
uptr tls_end() { return tls_end_; }
30+
bool IsMainThread() { return start_routine_ == nullptr; }
31+
32+
bool AddrIsInStack(uptr addr);
33+
34+
void StartSwitchFiber(uptr bottom, uptr size);
35+
void FinishSwitchFiber(uptr *bottom_old, uptr *size_old);
36+
37+
int destructor_iterations_;
38+
__sanitizer_sigset_t starting_sigset_;
39+
40+
private:
41+
void SetThreadStackAndTls();
42+
void ClearShadowForThreadStackAndTLS();
43+
struct StackBounds {
44+
uptr bottom;
45+
uptr top;
46+
};
47+
StackBounds GetStackBounds() const;
48+
49+
thread_callback_t start_routine_;
50+
void *arg_;
51+
52+
bool stack_switching_;
53+
54+
StackBounds stack_;
55+
StackBounds next_stack_;
56+
57+
uptr tls_begin_;
58+
uptr tls_end_;
59+
};
60+
61+
NsanThread *GetCurrentThread();
62+
void SetCurrentThread(NsanThread *t);
63+
void NsanTSDInit(void (*destructor)(void *tsd));
64+
void NsanTSDDtor(void *tsd);
65+
66+
} // namespace __nsan
67+
68+
#endif // NSAN_THREAD_H
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/// The static TLS block is reused among by threads. The shadow is cleared.
2+
// RUN: %clang_nsan %s -o %t
3+
// RUN: NSAN_OPTIONS=halt_on_error=1,log2_max_relative_error=19 %run %t
4+
5+
#include <pthread.h>
6+
#include <stdio.h>
7+
8+
__thread float x;
9+
10+
static void *ThreadFn(void *a) {
11+
long i = (long)a;
12+
for (long j = i * 1000; j < (i + 1) * 1000; j++)
13+
x += j;
14+
printf("%f\n", x);
15+
return 0;
16+
}
17+
18+
int main() {
19+
pthread_t t;
20+
for (long i = 0; i < 5; ++i) {
21+
pthread_create(&t, 0, ThreadFn, (void *)i);
22+
pthread_join(t, 0);
23+
}
24+
}

0 commit comments

Comments
 (0)