[compiler-rt] r312939 - [scudo] Fix improper TSD init after TLS destructors are called

Kostya Kortchinsky via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 11 12:59:41 PDT 2017


Author: cryptoad
Date: Mon Sep 11 12:59:40 2017
New Revision: 312939

URL: http://llvm.org/viewvc/llvm-project?rev=312939&view=rev
Log:
[scudo] Fix improper TSD init after TLS destructors are called

Summary:
Some of glibc's own thread local data is destroyed after a user's thread local
destructors are called, via __libc_thread_freeres. This might involve calling
free, as is the case for strerror_thread_freeres.
If there is no prior heap operation in the thread, this free would end up
initializing some thread specific data that would never be destroyed properly
(as user's pthread destructors have already been called), while still being
deallocated when the TLS goes away. As a result, a program could SEGV, usually
in __sanitizer::AllocatorGlobalStats::Unregister, where one of the doubly linked
list links would refer to a now unmapped memory area.

To prevent this from happening, we will not do a full initialization from the
deallocation path. This means that the fallback cache & quarantine will be used
if no other heap operation has been called, and we effectively prevent the TSD
being initialized and never destroyed. The TSD will be fully initialized for all
other paths.

In the event of a thread doing only frees and nothing else, a TSD would never
be initialized for that thread, but this situation is unlikely and we can live
with that.

Reviewers: alekseyshl

Reviewed By: alekseyshl

Subscribers: llvm-commits

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

Added:
    compiler-rt/trunk/test/scudo/tsd_destruction.cpp
Modified:
    compiler-rt/trunk/lib/scudo/scudo_allocator.cpp
    compiler-rt/trunk/lib/scudo/scudo_tls.h
    compiler-rt/trunk/lib/scudo/scudo_tls_android.cpp
    compiler-rt/trunk/lib/scudo/scudo_tls_android.inc
    compiler-rt/trunk/lib/scudo/scudo_tls_linux.cpp
    compiler-rt/trunk/lib/scudo/scudo_tls_linux.inc

Modified: compiler-rt/trunk/lib/scudo/scudo_allocator.cpp
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/scudo/scudo_allocator.cpp?rev=312939&r1=312938&r2=312939&view=diff
==============================================================================
--- compiler-rt/trunk/lib/scudo/scudo_allocator.cpp (original)
+++ compiler-rt/trunk/lib/scudo/scudo_allocator.cpp Mon Sep 11 12:59:40 2017
@@ -495,7 +495,13 @@ struct ScudoAllocator {
   // Deallocates a Chunk, which means adding it to the delayed free list (or
   // Quarantine).
   void deallocate(void *UserPtr, uptr DeleteSize, AllocType Type) {
-    initThreadMaybe();
+    // For a deallocation, we only ensure minimal initialization, meaning thread
+    // local data will be left uninitialized for now (when using ELF TLS). The
+    // fallback cache will be used instead. This is a workaround for a situation
+    // where the only heap operation performed in a thread would be a free past
+    // the TLS destructors, ending up in initialized thread specific data never
+    // being destroyed properly. Any other heap operation will do a full init.
+    initThreadMaybe(/*MinimalInit=*/true);
     // if (&__sanitizer_free_hook) __sanitizer_free_hook(UserPtr);
     if (UNLIKELY(!UserPtr))
       return;

Modified: compiler-rt/trunk/lib/scudo/scudo_tls.h
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/scudo/scudo_tls.h?rev=312939&r1=312938&r2=312939&view=diff
==============================================================================
--- compiler-rt/trunk/lib/scudo/scudo_tls.h (original)
+++ compiler-rt/trunk/lib/scudo/scudo_tls.h Mon Sep 11 12:59:40 2017
@@ -36,7 +36,7 @@ struct ALIGNED(64) ScudoThreadContext :
   void commitBack();
 };
 
-void initThread();
+void initThread(bool MinimalInit);
 
 // Platform specific dastpath functions definitions.
 #include "scudo_tls_android.inc"

Modified: compiler-rt/trunk/lib/scudo/scudo_tls_android.cpp
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/scudo/scudo_tls_android.cpp?rev=312939&r1=312938&r2=312939&view=diff
==============================================================================
--- compiler-rt/trunk/lib/scudo/scudo_tls_android.cpp (original)
+++ compiler-rt/trunk/lib/scudo/scudo_tls_android.cpp Mon Sep 11 12:59:40 2017
@@ -49,7 +49,7 @@ static void initOnce() {
     ThreadContexts[i].init();
 }
 
-void initThread() {
+void initThread(bool MinimalInit) {
   pthread_once(&GlobalInitialized, initOnce);
   // Initial context assignment is done in a plain round-robin fashion.
   u32 Index = atomic_fetch_add(&ThreadContextCurrentIndex, 1,

Modified: compiler-rt/trunk/lib/scudo/scudo_tls_android.inc
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/scudo/scudo_tls_android.inc?rev=312939&r1=312938&r2=312939&view=diff
==============================================================================
--- compiler-rt/trunk/lib/scudo/scudo_tls_android.inc (original)
+++ compiler-rt/trunk/lib/scudo/scudo_tls_android.inc Mon Sep 11 12:59:40 2017
@@ -20,10 +20,10 @@
 
 #if SANITIZER_LINUX && SANITIZER_ANDROID
 
-ALWAYS_INLINE void initThreadMaybe() {
+ALWAYS_INLINE void initThreadMaybe(bool MinimalInit = false) {
   if (LIKELY(*get_android_tls_ptr()))
     return;
-  initThread();
+  initThread(MinimalInit);
 }
 
 ScudoThreadContext *getThreadContextAndLockSlow();

Modified: compiler-rt/trunk/lib/scudo/scudo_tls_linux.cpp
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/scudo/scudo_tls_linux.cpp?rev=312939&r1=312938&r2=312939&view=diff
==============================================================================
--- compiler-rt/trunk/lib/scudo/scudo_tls_linux.cpp (original)
+++ compiler-rt/trunk/lib/scudo/scudo_tls_linux.cpp Mon Sep 11 12:59:40 2017
@@ -53,8 +53,10 @@ static void initOnce() {
   initScudo();
 }
 
-void initThread() {
+void initThread(bool MinimalInit) {
   CHECK_EQ(pthread_once(&GlobalInitialized, initOnce), 0);
+  if (UNLIKELY(MinimalInit))
+    return;
   CHECK_EQ(pthread_setspecific(PThreadKey, reinterpret_cast<void *>(
       GetPthreadDestructorIterations())), 0);
   ThreadLocalContext.init();

Modified: compiler-rt/trunk/lib/scudo/scudo_tls_linux.inc
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/scudo/scudo_tls_linux.inc?rev=312939&r1=312938&r2=312939&view=diff
==============================================================================
--- compiler-rt/trunk/lib/scudo/scudo_tls_linux.inc (original)
+++ compiler-rt/trunk/lib/scudo/scudo_tls_linux.inc Mon Sep 11 12:59:40 2017
@@ -31,14 +31,14 @@ extern THREADLOCAL ThreadState ScudoThre
 __attribute__((tls_model("initial-exec")))
 extern THREADLOCAL ScudoThreadContext ThreadLocalContext;
 
-ALWAYS_INLINE void initThreadMaybe() {
+ALWAYS_INLINE void initThreadMaybe(bool MinimalInit = false) {
   if (LIKELY(ScudoThreadState != ThreadNotInitialized))
     return;
-  initThread();
+  initThread(MinimalInit);
 }
 
 ALWAYS_INLINE ScudoThreadContext *getThreadContextAndLock() {
-  if (UNLIKELY(ScudoThreadState == ThreadTornDown))
+  if (UNLIKELY(ScudoThreadState != ThreadInitialized))
     return nullptr;
   return &ThreadLocalContext;
 }

Added: compiler-rt/trunk/test/scudo/tsd_destruction.cpp
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/test/scudo/tsd_destruction.cpp?rev=312939&view=auto
==============================================================================
--- compiler-rt/trunk/test/scudo/tsd_destruction.cpp (added)
+++ compiler-rt/trunk/test/scudo/tsd_destruction.cpp Mon Sep 11 12:59:40 2017
@@ -0,0 +1,42 @@
+// RUN: %clang_scudo %s -o %t
+// RUN: %run %t 2>&1
+
+#include <locale.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+// Some of glibc's own thread local data is destroyed after a user's thread
+// local destructors are called, via __libc_thread_freeres. This might involve
+// calling free, as is the case for strerror_thread_freeres.
+// If there is no prior heap operation in the thread, this free would end up 
+// initializing some thread specific data that would never be destroyed
+// properly, while still being deallocated when the TLS goes away. As a result,
+// a program could SEGV, usually in
+// __sanitizer::AllocatorGlobalStats::Unregister, where one of the doubly
+// linked list links would refer to a now unmapped memory area.
+
+// This test reproduces those circumstances. Success means executing without
+// a segmentation fault.
+
+const int kNumThreads = 16;
+pthread_t tid[kNumThreads];
+
+void *thread_func(void *arg) {
+  uintptr_t i = (uintptr_t)arg;
+  if ((i & 1) == 0) free(malloc(16));
+  // Calling strerror_l allows for strerror_thread_freeres to be called.
+  strerror_l(0, LC_GLOBAL_LOCALE);
+  return 0;
+}
+
+int main(int argc, char** argv) {
+  for (uintptr_t j = 0; j < 8; j++) {
+    for (uintptr_t i = 0; i < kNumThreads; i++)
+      pthread_create(&tid[i], 0, thread_func, (void *)i);
+    for (uintptr_t i = 0; i < kNumThreads; i++)
+      pthread_join(tid[i], 0);
+  }
+  return 0;
+}




More information about the llvm-commits mailing list