[lsan] Avoid segfaults during threads destruction under high load

This patch addresses two issues:

	* It turned out that suspended thread may have dtls->dtv_size == kDestroyedThread (-1)
	and LSan wrongly assumes that DTV is available. This leads to SEGV when LSan tries to
	iterate through DTV that is invalid.
	* In some rare cases GetRegistersAndSP can fail with errno 3 (ESRCH). In this case LSan
	assumes that the whole stack of a given thread is available. This is wrong because ESRCH
	can indicate that suspended thread was destroyed and its stack was unmapped. This patch
	properly handles ESRCH from GetRegistersAndSP in order to avoid invalid accesses to already
	unpapped threads stack.

Differential Revision: https://reviews.llvm.org/D30818

llvm-svn: 299630
This commit is contained in:
Maxim Ostapenko 2017-04-06 07:42:27 +00:00
parent 895171e6ee
commit fe863a6510
5 changed files with 37 additions and 12 deletions

View File

@ -201,11 +201,13 @@ static void ProcessThreads(SuspendedThreadsList const &suspended_threads,
continue;
}
uptr sp;
bool have_registers =
(suspended_threads.GetRegistersAndSP(i, registers.data(), &sp) == 0);
if (!have_registers) {
Report("Unable to get registers from thread %d.\n");
// If unable to get SP, consider the entire stack to be reachable.
PtraceRegistersStatus have_registers =
suspended_threads.GetRegistersAndSP(i, registers.data(), &sp);
if (have_registers != REGISTERS_AVAILABLE) {
Report("Unable to get registers from thread %d.\n", os_id);
// If unable to get SP, consider the entire stack to be reachable unless
// GetRegistersAndSP failed with ESRCH.
if (have_registers == REGISTERS_UNAVAILABLE_FATAL) continue;
sp = stack_begin;
}
@ -253,7 +255,7 @@ static void ProcessThreads(SuspendedThreadsList const &suspended_threads,
if (tls_end > cache_end)
ScanRangeForPointers(cache_end, tls_end, frontier, "TLS", kReachable);
}
if (dtls) {
if (dtls && !DTLSInDestruction(dtls)) {
for (uptr j = 0; j < dtls->dtv_size; ++j) {
uptr dtls_beg = dtls->dtv[j].beg;
uptr dtls_end = dtls_beg + dtls->dtv[j].size;
@ -263,6 +265,10 @@ static void ProcessThreads(SuspendedThreadsList const &suspended_threads,
kReachable);
}
}
} else {
// We are handling a thread with DTLS under destruction. Log about
// this and continue.
LOG_THREADS("Thread %d has DTLS under destruction.\n", os_id);
}
}
}

View File

@ -20,6 +20,12 @@
namespace __sanitizer {
typedef int SuspendedThreadID;
enum PtraceRegistersStatus {
REGISTERS_UNAVAILABLE_FATAL = -1,
REGISTERS_UNAVAILABLE = 0,
REGISTERS_AVAILABLE = 1
};
// Holds the list of suspended threads and provides an interface to dump their
// register contexts.
class SuspendedThreadsList {
@ -30,7 +36,8 @@ class SuspendedThreadsList {
CHECK_LT(index, thread_ids_.size());
return thread_ids_[index];
}
int GetRegistersAndSP(uptr index, uptr *buffer, uptr *sp) const;
PtraceRegistersStatus GetRegistersAndSP(uptr index, uptr *buffer,
uptr *sp) const;
// The buffer in GetRegistersAndSP should be at least this big.
static uptr RegisterCount();
uptr thread_count() const { return thread_ids_.size(); }

View File

@ -493,9 +493,9 @@ typedef _user_regs_struct regs_struct;
#error "Unsupported architecture"
#endif // SANITIZER_ANDROID && defined(__arm__)
int SuspendedThreadsList::GetRegistersAndSP(uptr index,
uptr *buffer,
uptr *sp) const {
PtraceRegistersStatus SuspendedThreadsList::GetRegistersAndSP(uptr index,
uptr *buffer,
uptr *sp) const {
pid_t tid = GetThreadID(index);
regs_struct regs;
int pterrno;
@ -513,12 +513,16 @@ int SuspendedThreadsList::GetRegistersAndSP(uptr index,
if (isErr) {
VReport(1, "Could not get registers from thread %d (errno %d).\n", tid,
pterrno);
return -1;
// ESRCH means that the given thread is not suspended or already dead.
// Therefore it's unsafe to inspect its data (e.g. walk through stack) and
// we should notify caller about this.
return pterrno == ESRCH ? REGISTERS_UNAVAILABLE_FATAL
: REGISTERS_UNAVAILABLE;
}
*sp = regs.REG_SP;
internal_memcpy(buffer, &regs, sizeof(regs));
return 0;
return REGISTERS_AVAILABLE;
}
uptr SuspendedThreadsList::RegisterCount() {

View File

@ -136,11 +136,17 @@ void DTLS_on_libc_memalign(void *ptr, uptr size) {
DTLS *DTLS_Get() { return &dtls; }
bool DTLSInDestruction(DTLS *dtls) {
return dtls->dtv_size == kDestroyedThread;
}
#else
void DTLS_on_libc_memalign(void *ptr, uptr size) {}
DTLS::DTV *DTLS_on_tls_get_addr(void *arg, void *res) { return 0; }
DTLS *DTLS_Get() { return 0; }
void DTLS_Destroy() {}
bool DTLSInDestruction(DTLS *dtls) { UNREACHABLE(); }
#endif // SANITIZER_INTERCEPT_TLS_GET_ADDR
} // namespace __sanitizer

View File

@ -55,6 +55,8 @@ DTLS::DTV *DTLS_on_tls_get_addr(void *arg, void *res, uptr static_tls_begin,
void DTLS_on_libc_memalign(void *ptr, uptr size);
DTLS *DTLS_Get();
void DTLS_Destroy(); // Make sure to call this before the thread is destroyed.
// Returns true if DTLS of suspended thread is in destruction process.
bool DTLSInDestruction(DTLS *dtls);
} // namespace __sanitizer