-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
[sanitizer] Fix asserts in asan and tsan in pthread interceptors. #75394
[sanitizer] Fix asserts in asan and tsan in pthread interceptors. #75394
Conversation
@llvm/pr-subscribers-compiler-rt-sanitizer Author: None (goussepi) ChangesCalling one of pthread join/detach interceptor on an already joined/detached thread causes asserts such as: AddressSanitizer: CHECK failed: sanitizer_thread_arg_retval.cpp:56 "((t)) != (0)" (0x0, 0x0) (tid=1236094) The assert are replaced by error codes. Full diff: https://github.com/llvm/llvm-project/pull/75394.diff 4 Files Affected:
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_thread_arg_retval.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_thread_arg_retval.cpp
index bddb2852140854..5edf6f1764f966 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_thread_arg_retval.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_thread_arg_retval.cpp
@@ -53,7 +53,8 @@ void ThreadArgRetval::Finish(uptr thread, void* retval) {
u32 ThreadArgRetval::BeforeJoin(uptr thread) const {
__sanitizer::Lock lock(&mtx_);
auto t = data_.find(thread);
- CHECK(t);
+ if (!t)
+ return 0;
CHECK(!t->second.detached);
return t->second.gen;
}
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cpp
index 741e0731c41559..12d36277589626 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cpp
@@ -345,7 +345,8 @@ u32 ThreadRegistry::ConsumeThreadUserId(uptr user_id) {
ThreadRegistryLock l(this);
u32 tid;
auto *t = live_.find(user_id);
- CHECK(t);
+ if (!t)
+ return kInvalidTid;
tid = t->second;
live_.erase(t);
auto *tctx = threads_[tid];
diff --git a/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp b/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
index 80f86ca98ed9cd..bef0aa373119fa 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
@@ -1118,7 +1118,7 @@ TSAN_INTERCEPTOR(int, pthread_tryjoin_np, void *th, void **ret) {
ThreadIgnoreEnd(thr);
if (res == 0)
ThreadJoin(thr, pc, tid);
- else
+ else if (tid != kInvalidTid)
ThreadNotJoined(thr, pc, tid, (uptr)th);
return res;
}
@@ -1132,7 +1132,7 @@ TSAN_INTERCEPTOR(int, pthread_timedjoin_np, void *th, void **ret,
ThreadIgnoreEnd(thr);
if (res == 0)
ThreadJoin(thr, pc, tid);
- else
+ else if (tid != kInvalidTid)
ThreadNotJoined(thr, pc, tid, (uptr)th);
return res;
}
diff --git a/compiler-rt/test/sanitizer_common/TestCases/Linux/pthread_join.cpp b/compiler-rt/test/sanitizer_common/TestCases/Linux/pthread_join.cpp
index 212a28dd3985bb..dd70f12fbbef6c 100644
--- a/compiler-rt/test/sanitizer_common/TestCases/Linux/pthread_join.cpp
+++ b/compiler-rt/test/sanitizer_common/TestCases/Linux/pthread_join.cpp
@@ -4,6 +4,10 @@
// FIXME: Crashes on some bots in pthread_exit.
// RUN: %run %t %if tsan %{ 0 %} %else %{ 1 %}
+// Check interceptors' return codes are the same without sanitizers.
+// RUN: %clangxx -pthread -fno-sanitize=all %s -o %t
+// RUN: %run %t 0
+
// REQUIRES: glibc
#include <assert.h>
@@ -12,6 +16,7 @@
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
+#include <errno.h>
bool use_exit;
static void *fn(void *args) {
@@ -36,12 +41,14 @@ int main(int argc, char **argv) {
assert(!pthread_attr_destroy(&attr));
assert(!pthread_detach(thread[0]));
+ assert(pthread_detach(thread[0]) == EINVAL);
{
void *res;
while (pthread_tryjoin_np(thread[1], &res))
sleep(1);
assert(~(uintptr_t)res == 1001);
+ assert(pthread_tryjoin_np(thread[1], &res) == EBUSY);
}
{
@@ -50,12 +57,14 @@ int main(int argc, char **argv) {
while (pthread_timedjoin_np(thread[2], &res, &tm))
sleep(1);
assert(~(uintptr_t)res == 1002);
+ assert(pthread_timedjoin_np(thread[2], &res, &tm) == ESRCH);
}
{
void *res;
assert(!pthread_join(thread[3], &res));
assert(~(uintptr_t)res == 1003);
+ assert(pthread_join(thread[3], &res) == ESRCH);
}
return 0;
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
@dvyukov had strong opinion on that. From pthread_detach manual
There is no promise to return particular error on the second detach. User code should not do that. However sanitizers can get into inconsistent state, which will be hard to debug. |
Yes, I wonder what's the pattern in user code that triggers this and is not a bug. |
Thanks @vitalybuka @dvyukov for the comments. |
@@ -4,10 +4,15 @@ | |||
// FIXME: Crashes on some bots in pthread_exit. | |||
// RUN: %run %t %if tsan %{ 0 %} %else %{ 1 %} | |||
|
|||
// Check interceptors' return codes are the same without sanitizers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see value in this test
-fno-sanitize=all removes runtime, we don't have interceptors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant it as a sanity check to double check the interceptors returns the same error code as the intercepted functions.
Not strictly required so happy to remove.
@@ -53,7 +53,8 @@ void ThreadArgRetval::Finish(uptr thread, void* retval) { | |||
u32 ThreadArgRetval::BeforeJoin(uptr thread) const { | |||
__sanitizer::Lock lock(&mtx_); | |||
auto t = data_.find(thread); | |||
CHECK(t); | |||
if (!t) | |||
return 0; | |||
CHECK(!t->second.detached); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we still have the case when joined thread is reused by another 'detached' thread and we will hit the CHECK below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A yes of course, will fix thanks!
It's not critical for this component to mismatch join However, it could be nice to see example why keeping the bug in the program is needed? Maybe rather introduce |
The error report I got was for pthread_join being called unconditionally inside a destructor. Many thanks! Pierre |
Yes, we need |
ping! |
There is "re-request" review, otherwise it does not show up in searches like this https://github.com/llvm/llvm-project/pulls/review-requested/@me |
CHECK(t); | ||
CHECK(!t->second.detached); | ||
return t->second.gen; | ||
bool detect_invalid_join = common_flags()->detect_invalid_join; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (t && !t->second.detached) {
return t->second.gen;
}
if (!common_flags()->detect_invalid_join)
return kInvalidGen;
const char* const reason = "unknown";
if (!t) {
reason = "already joined";
} else if (t->second.detached) {
reason = "detached";
}
Report("ERROR: %s: Joining %s thread, aborting.\n",
SanitizerToolName, reason);
Die();
} | ||
|
||
void ThreadArgRetval::AfterJoin(uptr thread, u32 gen) { | ||
__sanitizer::Lock lock(&mtx_); | ||
auto t = data_.find(thread); | ||
if (!t || gen != t->second.gen) { | ||
// Thread was reused and erased by any other event. | ||
if (!t || gen != t->second.gen || gen == kInvalidGen) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should not have t->second.gen = kInvalidGen,
to make sure skip invalid here .gen = gen_++;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Thank you!
@@ -23,6 +23,9 @@ void ThreadArgRetval::CreateLocked(uptr thread, bool detached, | |||
Data& t = data_[thread]; | |||
t = {}; | |||
t.gen = gen_++; | |||
static_assert(sizeof(gen_) == sizeof(u32) && kInvalidGen == UINT32_MAX); | |||
if (gen_ == kInvalidGen) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I missed if (gen_ == kInvalidGen) = 0
,
so we don't need the one introduced with b96deb8
I believe "member" can merge? |
Calling one of pthread join/detach interceptor on an already joined/detached thread causes asserts such as: AddressSanitizer: CHECK failed: sanitizer_thread_arg_retval.cpp:56 "((t)) != (0)" (0x0, 0x0) (tid=1236094) #0 0x555555634f8b in __asan::CheckUnwind() compiler-rt/lib/asan/asan_rtl.cpp:69:3 llvm#1 0x55555564e06e in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) compiler-rt/lib/sanitizer_common/sanitizer_termination.cpp:86:24 llvm#2 0x5555556491df in __sanitizer::ThreadArgRetval::BeforeJoin(unsigned long) const compiler-rt/lib/sanitizer_common/sanitizer_thread_arg_retval.cpp:56:3 llvm#3 0x5555556198ed in Join<___interceptor_pthread_tryjoin_np(void*, void**)::<lambda()> > compiler-rt/lib/asan/../sanitizer_common/sanitizer_thread_arg_retval.h:74:26 llvm#4 0x5555556198ed in pthread_tryjoin_np compiler-rt/lib/asan/asan_interceptors.cpp:311:29 This change does 2 things: - Introduce a detect_invalid_join common flags - Replace CHECK assert by Report() and Die()
5f3b82b
to
4e031a9
Compare
…vm#75394) Calling one of pthread join/detach interceptor on an already joined/detached thread causes asserts such as: AddressSanitizer: CHECK failed: sanitizer_thread_arg_retval.cpp:56 "((t)) != (0)" (0x0, 0x0) (tid=1236094) #0 0x555555634f8b in __asan::CheckUnwind() compiler-rt/lib/asan/asan_rtl.cpp:69:3 llvm#1 0x55555564e06e in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) compiler-rt/lib/sanitizer_common/sanitizer_termination.cpp:86:24 llvm#2 0x5555556491df in __sanitizer::ThreadArgRetval::BeforeJoin(unsigned long) const compiler-rt/lib/sanitizer_common/sanitizer_thread_arg_retval.cpp:56:3 llvm#3 0x5555556198ed in Join<___interceptor_pthread_tryjoin_np(void*, void**)::<lambda()> > compiler-rt/lib/asan/../sanitizer_common/sanitizer_thread_arg_retval.h:74:26 llvm#4 0x5555556198ed in pthread_tryjoin_np compiler-rt/lib/asan/asan_interceptors.cpp:311:29 The assert are replaced by error codes.
Calling one of pthread join/detach interceptor on an already joined/detached thread causes asserts such as:
AddressSanitizer: CHECK failed: sanitizer_thread_arg_retval.cpp:56 "((t)) != (0)" (0x0, 0x0) (tid=1236094)
#0 0x555555634f8b in __asan::CheckUnwind() compiler-rt/lib/asan/asan_rtl.cpp:69:3
#1 0x55555564e06e in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) compiler-rt/lib/sanitizer_common/sanitizer_termination.cpp:86:24
#2 0x5555556491df in __sanitizer::ThreadArgRetval::BeforeJoin(unsigned long) const compiler-rt/lib/sanitizer_common/sanitizer_thread_arg_retval.cpp:56:3
#3 0x5555556198ed in Join<___interceptor_pthread_tryjoin_np(void*, void**)::<lambda()> > compiler-rt/lib/asan/../sanitizer_common/sanitizer_thread_arg_retval.h:74:26
#4 0x5555556198ed in pthread_tryjoin_np compiler-rt/lib/asan/asan_interceptors.cpp:311:29
The assert are replaced by error codes.