Skip to content

Commit 1aa746d

Browse files
authored
[tsan] Fix nested signal handling (#138599)
This PR fixes the bug reported in #134358. In the current implementation of the tsan posix interceptors, the signal set does not get restored to the correct original set, if a signal handler gets called, while already inside of a signal handler. This leads to the wrong signal set being set for the thread in which the signal handler was called. To fix this I introduced a stack of `__sanitizer_sigset_t` to keep all the correct old signal sets and restore them in the correct order. There was also already an existing test that tested nested / recursive signal handlers, but it was disabled. I therefore reenabled it, made it more robust by waiting for the second thread to have been properly started and added checks for the signal sets. This test then failed before the introduction of the interceptor fix and didn't fail with the fix. @dvyukov What are your thoughts?
1 parent 111ac69 commit 1aa746d

File tree

3 files changed

+81
-29
lines changed

3 files changed

+81
-29
lines changed

compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
// sanitizer_common/sanitizer_common_interceptors.inc
1313
//===----------------------------------------------------------------------===//
1414

15+
#include <stdarg.h>
16+
17+
#include "interception/interception.h"
1518
#include "sanitizer_common/sanitizer_allocator_dlsym.h"
1619
#include "sanitizer_common/sanitizer_atomic.h"
1720
#include "sanitizer_common/sanitizer_errno.h"
@@ -24,16 +27,14 @@
2427
#include "sanitizer_common/sanitizer_posix.h"
2528
#include "sanitizer_common/sanitizer_stacktrace.h"
2629
#include "sanitizer_common/sanitizer_tls_get_addr.h"
27-
#include "interception/interception.h"
30+
#include "sanitizer_common/sanitizer_vector.h"
31+
#include "tsan_fd.h"
2832
#include "tsan_interceptors.h"
2933
#include "tsan_interface.h"
34+
#include "tsan_mman.h"
3035
#include "tsan_platform.h"
31-
#include "tsan_suppressions.h"
3236
#include "tsan_rtl.h"
33-
#include "tsan_mman.h"
34-
#include "tsan_fd.h"
35-
36-
#include <stdarg.h>
37+
#include "tsan_suppressions.h"
3738

3839
using namespace __tsan;
3940

@@ -177,7 +178,7 @@ struct ThreadSignalContext {
177178
SignalDesc pending_signals[kSigCount];
178179
// emptyset and oldset are too big for stack.
179180
__sanitizer_sigset_t emptyset;
180-
__sanitizer_sigset_t oldset;
181+
__sanitizer::Vector<__sanitizer_sigset_t> oldset;
181182
};
182183

183184
void EnterBlockingFunc(ThreadState *thr) {
@@ -558,6 +559,7 @@ static void SetJmp(ThreadState *thr, uptr sp) {
558559
buf->shadow_stack_pos = thr->shadow_stack_pos;
559560
ThreadSignalContext *sctx = SigCtx(thr);
560561
buf->int_signal_send = sctx ? sctx->int_signal_send : 0;
562+
buf->oldset_stack_size = sctx ? sctx->oldset.Size() : 0;
561563
buf->in_blocking_func = atomic_load(&thr->in_blocking_func, memory_order_relaxed);
562564
buf->in_signal_handler = atomic_load(&thr->in_signal_handler,
563565
memory_order_relaxed);
@@ -574,8 +576,11 @@ static void LongJmp(ThreadState *thr, uptr *env) {
574576
while (thr->shadow_stack_pos > buf->shadow_stack_pos)
575577
FuncExit(thr);
576578
ThreadSignalContext *sctx = SigCtx(thr);
577-
if (sctx)
579+
if (sctx) {
578580
sctx->int_signal_send = buf->int_signal_send;
581+
while (sctx->oldset.Size() > buf->oldset_stack_size)
582+
sctx->oldset.PopBack();
583+
}
579584
atomic_store(&thr->in_blocking_func, buf->in_blocking_func,
580585
memory_order_relaxed);
581586
atomic_store(&thr->in_signal_handler, buf->in_signal_handler,
@@ -980,6 +985,7 @@ void PlatformCleanUpThreadState(ThreadState *thr) {
980985
&thr->signal_ctx, memory_order_relaxed);
981986
if (sctx) {
982987
atomic_store(&thr->signal_ctx, 0, memory_order_relaxed);
988+
sctx->oldset.Reset();
983989
UnmapOrDie(sctx, sizeof(*sctx));
984990
}
985991
}
@@ -2176,7 +2182,8 @@ void ProcessPendingSignalsImpl(ThreadState *thr) {
21762182
return;
21772183
atomic_fetch_add(&thr->in_signal_handler, 1, memory_order_relaxed);
21782184
internal_sigfillset(&sctx->emptyset);
2179-
int res = REAL(pthread_sigmask)(SIG_SETMASK, &sctx->emptyset, &sctx->oldset);
2185+
__sanitizer_sigset_t *oldset = sctx->oldset.PushBack();
2186+
int res = REAL(pthread_sigmask)(SIG_SETMASK, &sctx->emptyset, oldset);
21802187
CHECK_EQ(res, 0);
21812188
for (int sig = 0; sig < kSigCount; sig++) {
21822189
SignalDesc *signal = &sctx->pending_signals[sig];
@@ -2186,8 +2193,9 @@ void ProcessPendingSignalsImpl(ThreadState *thr) {
21862193
&signal->ctx);
21872194
}
21882195
}
2189-
res = REAL(pthread_sigmask)(SIG_SETMASK, &sctx->oldset, 0);
2196+
res = REAL(pthread_sigmask)(SIG_SETMASK, oldset, 0);
21902197
CHECK_EQ(res, 0);
2198+
sctx->oldset.PopBack();
21912199
atomic_fetch_add(&thr->in_signal_handler, -1, memory_order_relaxed);
21922200
}
21932201

compiler-rt/lib/tsan/rtl/tsan_rtl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ struct JmpBuf {
9898
uptr sp;
9999
int int_signal_send;
100100
bool in_blocking_func;
101+
uptr oldset_stack_size;
101102
uptr in_signal_handler;
102103
uptr *shadow_stack_pos;
103104
};

compiler-rt/test/tsan/signal_recursive.cpp

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
// Test case for recursive signal handlers, adopted from:
44
// https://github.com/google/sanitizers/issues/478
55

6-
// REQUIRES: disabled
7-
86
#include "test.h"
97
#include <semaphore.h>
108
#include <signal.h>
@@ -15,8 +13,6 @@ static const int kSigRestart = SIGUSR2;
1513

1614
static sem_t g_thread_suspend_ack_sem;
1715

18-
static bool g_busy_thread_received_restart;
19-
2016
static volatile bool g_busy_thread_garbage_collected;
2117

2218
static void SaveRegistersInStack() {
@@ -30,22 +26,51 @@ static void fail(const char *what) {
3026
exit(1);
3127
}
3228

29+
static void CheckSigBlocked(const sigset_t &oldset, const sigset_t &newset,
30+
int sig) {
31+
const int is_old_member = sigismember(&oldset, sig);
32+
const int is_new_member = sigismember(&newset, sig);
33+
34+
if (is_old_member == -1 || is_new_member == -1)
35+
fail("sigismember failed");
36+
37+
if (is_old_member != is_new_member)
38+
fail("restoring signals failed");
39+
}
40+
41+
sigset_t GetCurrentSigSet() {
42+
sigset_t set;
43+
if (sigemptyset(&set) != 0)
44+
fail("sigemptyset failed");
45+
46+
if (pthread_sigmask(SIG_BLOCK, NULL, &set) != 0)
47+
fail("pthread_sigmask failed");
48+
49+
return set;
50+
}
51+
3352
static void SuspendHandler(int sig) {
3453
int old_errno = errno;
3554
SaveRegistersInStack();
3655

3756
// Enable kSigRestart handling, tsan disables signals around signal handlers.
38-
sigset_t sigset;
39-
sigemptyset(&sigset);
40-
pthread_sigmask(SIG_SETMASK, &sigset, 0);
57+
const auto oldset = GetCurrentSigSet();
4158

4259
// Acknowledge that thread is saved and suspended
4360
if (sem_post(&g_thread_suspend_ack_sem) != 0)
4461
fail("sem_post failed");
4562

4663
// Wait for wakeup signal.
47-
while (!g_busy_thread_received_restart)
48-
usleep(100); // wait for kSigRestart signal
64+
sigset_t sigset;
65+
sigemptyset(&sigset);
66+
if (sigsuspend(&sigset) != 0 && errno != EINTR)
67+
fail("sigsuspend failed");
68+
69+
const auto newset = GetCurrentSigSet();
70+
71+
// Check that the same signals are blocked as before
72+
CheckSigBlocked(oldset, newset, kSigSuspend);
73+
CheckSigBlocked(oldset, newset, kSigRestart);
4974

5075
// Acknowledge that thread restarted
5176
if (sem_post(&g_thread_suspend_ack_sem) != 0)
@@ -56,31 +81,33 @@ static void SuspendHandler(int sig) {
5681
errno = old_errno;
5782
}
5883

59-
static void RestartHandler(int sig) {
60-
g_busy_thread_received_restart = true;
84+
static void RestartHandler(int sig) {}
85+
86+
static void WaitSem() {
87+
while (sem_wait(&g_thread_suspend_ack_sem) != 0) {
88+
if (errno != EINTR)
89+
fail("sem_wait failed");
90+
}
6191
}
6292

6393
static void StopWorld(pthread_t thread) {
6494
if (pthread_kill(thread, kSigSuspend) != 0)
6595
fail("pthread_kill failed");
6696

67-
while (sem_wait(&g_thread_suspend_ack_sem) != 0) {
68-
if (errno != EINTR)
69-
fail("sem_wait failed");
70-
}
97+
WaitSem();
7198
}
7299

73100
static void StartWorld(pthread_t thread) {
74101
if (pthread_kill(thread, kSigRestart) != 0)
75102
fail("pthread_kill failed");
76103

77-
while (sem_wait(&g_thread_suspend_ack_sem) != 0) {
78-
if (errno != EINTR)
79-
fail("sem_wait failed");
80-
}
104+
WaitSem();
81105
}
82106

83107
static void CollectGarbage(pthread_t thread) {
108+
// Wait for the thread to start
109+
WaitSem();
110+
84111
StopWorld(thread);
85112
// Walk stacks
86113
StartWorld(thread);
@@ -102,21 +129,37 @@ static void Init() {
102129

103130
void* BusyThread(void *arg) {
104131
(void)arg;
132+
const auto oldset = GetCurrentSigSet();
133+
134+
if (sem_post(&g_thread_suspend_ack_sem) != 0)
135+
fail("sem_post failed");
136+
105137
while (!g_busy_thread_garbage_collected) {
106138
usleep(100); // Tsan deadlocks without these sleeps
107139
}
140+
141+
const auto newset = GetCurrentSigSet();
142+
143+
// Check that we have the same signals blocked as before
144+
CheckSigBlocked(oldset, newset, kSigSuspend);
145+
CheckSigBlocked(oldset, newset, kSigRestart);
146+
108147
return NULL;
109148
}
110149

111150
int main(int argc, const char *argv[]) {
112151
Init();
152+
113153
pthread_t busy_thread;
114154
if (pthread_create(&busy_thread, NULL, &BusyThread, NULL) != 0)
115155
fail("pthread_create failed");
156+
116157
CollectGarbage(busy_thread);
117158
if (pthread_join(busy_thread, 0) != 0)
118159
fail("pthread_join failed");
160+
119161
fprintf(stderr, "DONE\n");
162+
120163
return 0;
121164
}
122165

0 commit comments

Comments
 (0)