[ASAN] fix startup crash in dlsym for long paths since glibc 2.27
authorPeter Wu <peter@lekensteyn.nl>
Thu, 14 Jun 2018 10:42:54 +0000 (10:42 +0000)
committerPeter Wu <peter@lekensteyn.nl>
Thu, 14 Jun 2018 10:42:54 +0000 (10:42 +0000)
Summary:
Error messages for dlsym used to be stored on the stack, but since
commit 2449ae7b ("ld.so: Introduce struct dl_exception") in glibc 2.27
these are now stored on the heap (and thus use the dlsym alloc pool).

Messages look like "undefined symbol: __isoc99_printf\0/path/to/a.out".
With many missing library functions and long object paths, the pool is
quickly exhausted. Implement a simple mechanism to return freed memory
to the pool (clear it in case it is used for calloc).

Fixes https://github.com/google/sanitizers/issues/957

Reviewed By: vitalybuka

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

llvm-svn: 334703

compiler-rt/lib/asan/asan_malloc_linux.cc
compiler-rt/test/asan/TestCases/long-object-path.cc [new file with mode: 0644]

index 726cc1f..7152fe0 100644 (file)
@@ -31,6 +31,7 @@
 using namespace __asan;  // NOLINT
 
 static uptr allocated_for_dlsym;
+static uptr last_dlsym_alloc_size_in_words;
 static const uptr kDlsymAllocPoolSize = SANITIZER_RTEMS ? 4096 : 1024;
 static uptr alloc_memory_for_dlsym[kDlsymAllocPoolSize];
 
@@ -42,11 +43,25 @@ static INLINE bool IsInDlsymAllocPool(const void *ptr) {
 static void *AllocateFromLocalPool(uptr size_in_bytes) {
   uptr size_in_words = RoundUpTo(size_in_bytes, kWordSize) / kWordSize;
   void *mem = (void*)&alloc_memory_for_dlsym[allocated_for_dlsym];
+  last_dlsym_alloc_size_in_words = size_in_words;
   allocated_for_dlsym += size_in_words;
   CHECK_LT(allocated_for_dlsym, kDlsymAllocPoolSize);
   return mem;
 }
 
+static void DeallocateFromLocalPool(const void *ptr) {
+  // Hack: since glibc 2.27, dlsym longer use stack-allocated memory to store
+  // error messages and instead use malloc followed by free. To avoid pool
+  // exhaustion due to long object filenames, handle that special case here.
+  uptr prev_offset = allocated_for_dlsym - last_dlsym_alloc_size_in_words;
+  void *prev_mem = (void*)&alloc_memory_for_dlsym[prev_offset];
+  if (prev_mem == ptr) {
+    REAL(memset)(prev_mem, 0, last_dlsym_alloc_size_in_words * kWordSize);
+    allocated_for_dlsym = prev_offset;
+    last_dlsym_alloc_size_in_words = 0;
+  }
+}
+
 static int PosixMemalignFromLocalPool(void **memptr, uptr alignment,
                                       uptr size_in_bytes) {
   if (UNLIKELY(!CheckPosixMemalignAlignment(alignment)))
@@ -107,8 +122,10 @@ static void *ReallocFromLocalPool(void *ptr, uptr size) {
 
 INTERCEPTOR(void, free, void *ptr) {
   GET_STACK_TRACE_FREE;
-  if (UNLIKELY(IsInDlsymAllocPool(ptr)))
+  if (UNLIKELY(IsInDlsymAllocPool(ptr))) {
+    DeallocateFromLocalPool(ptr);
     return;
+  }
   asan_free(ptr, &stack, FROM_MALLOC);
 }
 
diff --git a/compiler-rt/test/asan/TestCases/long-object-path.cc b/compiler-rt/test/asan/TestCases/long-object-path.cc
new file mode 100644 (file)
index 0000000..592b0ab
--- /dev/null
@@ -0,0 +1,7 @@
+// RUN: mkdir -p %T/a-long-directory-name-to-test-allocations-for-exceptions-in-_dl_lookup_symbol_x-since-glibc-2.27
+// RUN: %clangxx_asan -g %s -o %T/long-object-path
+// RUN: %run %T/a-*/../a-*/../a-*/../a-*/../a-*/../a-*/../a-*/../a-*/../long-object-path
+
+int main(void) {
+    return 0;
+}