diff mbox

[committed] Cherry-pick upstream asan fix for upcoming glibc (PR sanitizer/71160)

Message ID 20160517092323.GH28550@tucnak.redhat.com
State New
Headers show

Commit Message

Jakub Jelinek May 17, 2016, 9:23 a.m. UTC
Hi!

This is a cherry-pick of upstream fix, so that dlsym can call not just
calloc, but also malloc or realloc, even before asan is initialized.

Tested on x86_64-linux, committed so far to trunk.

IMHO even better would be to make sure that in the common case (recent
glibc) we don't have failed dlsym calls (still, this hack is useful just in
case) - the __isoc99_*printf* interceptors make no sense, glibc has never
exported those.  Thus, if we bump ABI of libasan again for GCC 7, IMHO those
bogus interceptors should be ifdefed out for glibc or removed completely.
Or, if we don't want to break ABI, at least changed so that they actuall
dlsym the corresponding *printf* (not __isoc99_ prefixed) functions
instead of the bogus ones.

2016-05-17  Jakub Jelinek  <jakub@redhat.com>

	PR sanitizer/71160
	* asan/asan_malloc_linux.cc: Cherry pick upstream r254395
	and r269633.


	Jakub

Comments

Maxim Ostapenko May 17, 2016, 2:38 p.m. UTC | #1
Hi Jakub,

thanks for backporting this! Do you have any plans to apply this patch 
to GCC 5 and 6 branches? AFAIK people hit on this ASan + newer Glibc bug 
by using GCC 5.3.1 on Fedora 23.

On 17/05/16 12:23, Jakub Jelinek wrote:
> Hi!
>
> This is a cherry-pick of upstream fix, so that dlsym can call not just
> calloc, but also malloc or realloc, even before asan is initialized.
>
> Tested on x86_64-linux, committed so far to trunk.
>
> IMHO even better would be to make sure that in the common case (recent
> glibc) we don't have failed dlsym calls (still, this hack is useful just in
> case) - the __isoc99_*printf* interceptors make no sense, glibc has never
> exported those.  Thus, if we bump ABI of libasan again for GCC 7, IMHO those
> bogus interceptors should be ifdefed out for glibc or removed completely.
> Or, if we don't want to break ABI, at least changed so that they actuall
> dlsym the corresponding *printf* (not __isoc99_ prefixed) functions
> instead of the bogus ones.

We should definitely bump libasan version on next libsanitizer merge, 
because it would contain ABI breaking changes in ASan. Perhaps we could 
ifdef these __isoc99 interceptors as a local GCC patch then?

> 2016-05-17  Jakub Jelinek  <jakub@redhat.com>
>
> 	PR sanitizer/71160
> 	* asan/asan_malloc_linux.cc: Cherry pick upstream r254395
> 	and r269633.
>
> --- libsanitizer/asan/asan_malloc_linux.cc.jj	2014-09-24 11:08:01.772039066 +0200
> +++ libsanitizer/asan/asan_malloc_linux.cc	2016-05-17 11:02:37.859379380 +0200
> @@ -24,39 +24,62 @@
>   // ---------------------- Replacement functions ---------------- {{{1
>   using namespace __asan;  // NOLINT
>   
> +static uptr allocated_for_dlsym;
> +static const uptr kDlsymAllocPoolSize = 1024;
> +static uptr alloc_memory_for_dlsym[kDlsymAllocPoolSize];
> +
> +static bool IsInDlsymAllocPool(const void *ptr) {
> +  uptr off = (uptr)ptr - (uptr)alloc_memory_for_dlsym;
> +  return off < sizeof(alloc_memory_for_dlsym);
> +}
> +
> +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];
> +  allocated_for_dlsym += size_in_words;
> +  CHECK_LT(allocated_for_dlsym, kDlsymAllocPoolSize);
> +  return mem;
> +}
> +
>   INTERCEPTOR(void, free, void *ptr) {
>     GET_STACK_TRACE_FREE;
> +  if (UNLIKELY(IsInDlsymAllocPool(ptr)))
> +    return;
>     asan_free(ptr, &stack, FROM_MALLOC);
>   }
>   
>   INTERCEPTOR(void, cfree, void *ptr) {
>     GET_STACK_TRACE_FREE;
> +  if (UNLIKELY(IsInDlsymAllocPool(ptr)))
> +    return;
>     asan_free(ptr, &stack, FROM_MALLOC);
>   }
>   
>   INTERCEPTOR(void*, malloc, uptr size) {
> +  if (UNLIKELY(!asan_inited))
> +    // Hack: dlsym calls malloc before REAL(malloc) is retrieved from dlsym.
> +    return AllocateFromLocalPool(size);
>     GET_STACK_TRACE_MALLOC;
>     return asan_malloc(size, &stack);
>   }
>   
>   INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) {
> -  if (UNLIKELY(!asan_inited)) {
> +  if (UNLIKELY(!asan_inited))
>       // Hack: dlsym calls calloc before REAL(calloc) is retrieved from dlsym.
> -    const uptr kCallocPoolSize = 1024;
> -    static uptr calloc_memory_for_dlsym[kCallocPoolSize];
> -    static uptr allocated;
> -    uptr size_in_words = ((nmemb * size) + kWordSize - 1) / kWordSize;
> -    void *mem = (void*)&calloc_memory_for_dlsym[allocated];
> -    allocated += size_in_words;
> -    CHECK(allocated < kCallocPoolSize);
> -    return mem;
> -  }
> +    return AllocateFromLocalPool(nmemb * size);
>     GET_STACK_TRACE_MALLOC;
>     return asan_calloc(nmemb, size, &stack);
>   }
>   
>   INTERCEPTOR(void*, realloc, void *ptr, uptr size) {
>     GET_STACK_TRACE_MALLOC;
> +  if (UNLIKELY(IsInDlsymAllocPool(ptr))) {
> +    uptr offset = (uptr)ptr - (uptr)alloc_memory_for_dlsym;
> +    uptr copy_size = Min(size, kDlsymAllocPoolSize - offset);
> +    void *new_ptr = asan_malloc(size, &stack);
> +    internal_memcpy(new_ptr, ptr, copy_size);
> +    return new_ptr;
> +  }
>     return asan_realloc(ptr, size, &stack);
>   }
>   
>
> 	Jakub
>
>
Jakub Jelinek May 17, 2016, 2:46 p.m. UTC | #2
On Tue, May 17, 2016 at 05:38:27PM +0300, Maxim Ostapenko wrote:
> Hi Jakub,
> 
> thanks for backporting this! Do you have any plans to apply this patch to
> GCC 5 and 6 branches? AFAIK people hit on this ASan + newer Glibc bug by
> using GCC 5.3.1 on Fedora 23.

I don't have the newer glibc on my box, therefore I'm waiting until somebody
confirms the trunk change fixed it before backporting.

> >IMHO even better would be to make sure that in the common case (recent
> >glibc) we don't have failed dlsym calls (still, this hack is useful just in
> >case) - the __isoc99_*printf* interceptors make no sense, glibc has never
> >exported those.  Thus, if we bump ABI of libasan again for GCC 7, IMHO those
> >bogus interceptors should be ifdefed out for glibc or removed completely.
> >Or, if we don't want to break ABI, at least changed so that they actuall
> >dlsym the corresponding *printf* (not __isoc99_ prefixed) functions
> >instead of the bogus ones.
> 
> We should definitely bump libasan version on next libsanitizer merge,
> because it would contain ABI breaking changes in ASan. Perhaps we could
> ifdef these __isoc99 interceptors as a local GCC patch then?

Well, with the patch it is nothing urgent, but IMNSHO the bogus interceptors
aren't needed upstream either.

	Jakub
Florian Weimer May 18, 2016, 11:44 a.m. UTC | #3
On 05/17/2016 04:46 PM, Jakub Jelinek wrote:
> On Tue, May 17, 2016 at 05:38:27PM +0300, Maxim Ostapenko wrote:
>> Hi Jakub,
>>
>> thanks for backporting this! Do you have any plans to apply this patch to
>> GCC 5 and 6 branches? AFAIK people hit on this ASan + newer Glibc bug by
>> using GCC 5.3.1 on Fedora 23.
>
> I don't have the newer glibc on my box, therefore I'm waiting until somebody
> confirms the trunk change fixed it before backporting.

I compiled GCC trunk (r236371) and today's glibc master (around commit 
0014680d6a5bdeb4fe17682450105ebed19f35da), and both work together, in 
the sense that this test program, when compiled with ASAN, reports a 
memory leak:

#include <stdlib.h>
int main () { malloc(35); return 0; }

=================================================================
==30982==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 35 byte(s) in 1 object(s) allocated from:
     #0 0x7fa00ea15928 in __interceptor_malloc 
../../../../trunk/libsanitizer/asan/asan_malloc_linux.cc:62
     #1 0x400763  (/home/fweimer/src/gnu/glibc/build/elf/ld.so+0x400763)
     #2 0x7fa00e5d625f in __libc_start_main ../csu/libc-start.c:289

SUMMARY: AddressSanitizer: 35 byte(s) leaked in 1 allocation(s).

Before, when compiled with GCC 5.3.1 (in Fedora 22), it would report an 
internal error:

==30945==AddressSanitizer CHECK failed: 
../../../../libsanitizer/asan/asan_rtl.cc:556 "((!asan_init_is_running 
&& "ASan init calls itself!")) != (0)" (0x0, 0x0)
     <empty stack>


Thanks,
Florian
diff mbox

Patch

--- libsanitizer/asan/asan_malloc_linux.cc.jj	2014-09-24 11:08:01.772039066 +0200
+++ libsanitizer/asan/asan_malloc_linux.cc	2016-05-17 11:02:37.859379380 +0200
@@ -24,39 +24,62 @@ 
 // ---------------------- Replacement functions ---------------- {{{1
 using namespace __asan;  // NOLINT
 
+static uptr allocated_for_dlsym;
+static const uptr kDlsymAllocPoolSize = 1024;
+static uptr alloc_memory_for_dlsym[kDlsymAllocPoolSize];
+
+static bool IsInDlsymAllocPool(const void *ptr) {
+  uptr off = (uptr)ptr - (uptr)alloc_memory_for_dlsym;
+  return off < sizeof(alloc_memory_for_dlsym);
+}
+
+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];
+  allocated_for_dlsym += size_in_words;
+  CHECK_LT(allocated_for_dlsym, kDlsymAllocPoolSize);
+  return mem;
+}
+
 INTERCEPTOR(void, free, void *ptr) {
   GET_STACK_TRACE_FREE;
+  if (UNLIKELY(IsInDlsymAllocPool(ptr)))
+    return;
   asan_free(ptr, &stack, FROM_MALLOC);
 }
 
 INTERCEPTOR(void, cfree, void *ptr) {
   GET_STACK_TRACE_FREE;
+  if (UNLIKELY(IsInDlsymAllocPool(ptr)))
+    return;
   asan_free(ptr, &stack, FROM_MALLOC);
 }
 
 INTERCEPTOR(void*, malloc, uptr size) {
+  if (UNLIKELY(!asan_inited))
+    // Hack: dlsym calls malloc before REAL(malloc) is retrieved from dlsym.
+    return AllocateFromLocalPool(size);
   GET_STACK_TRACE_MALLOC;
   return asan_malloc(size, &stack);
 }
 
 INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) {
-  if (UNLIKELY(!asan_inited)) {
+  if (UNLIKELY(!asan_inited))
     // Hack: dlsym calls calloc before REAL(calloc) is retrieved from dlsym.
-    const uptr kCallocPoolSize = 1024;
-    static uptr calloc_memory_for_dlsym[kCallocPoolSize];
-    static uptr allocated;
-    uptr size_in_words = ((nmemb * size) + kWordSize - 1) / kWordSize;
-    void *mem = (void*)&calloc_memory_for_dlsym[allocated];
-    allocated += size_in_words;
-    CHECK(allocated < kCallocPoolSize);
-    return mem;
-  }
+    return AllocateFromLocalPool(nmemb * size);
   GET_STACK_TRACE_MALLOC;
   return asan_calloc(nmemb, size, &stack);
 }
 
 INTERCEPTOR(void*, realloc, void *ptr, uptr size) {
   GET_STACK_TRACE_MALLOC;
+  if (UNLIKELY(IsInDlsymAllocPool(ptr))) {
+    uptr offset = (uptr)ptr - (uptr)alloc_memory_for_dlsym;
+    uptr copy_size = Min(size, kDlsymAllocPoolSize - offset);
+    void *new_ptr = asan_malloc(size, &stack);
+    internal_memcpy(new_ptr, ptr, copy_size);
+    return new_ptr;
+  }
   return asan_realloc(ptr, size, &stack);
 }