Bug 24476 - __libc_freeres triggers bad free in libdl if dlerror was not used
Summary: __libc_freeres triggers bad free in libdl if dlerror was not used
Status: RESOLVED FIXED
Alias: None
Product: glibc
Classification: Unclassified
Component: dynamic-link (show other bugs)
Version: 2.28
: P2 normal
Target Milestone: 2.30
Assignee: Mark Wielaard
URL:
Keywords: glibc_2.28, glibc_2.29
Depends on:
Blocks:
 
Reported: 2019-04-22 20:17 UTC by David Benjamin
Modified: 2019-06-06 16:11 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed: 2019-05-09 00:00:00
fweimer: security-


Attachments
sample reproducer (1.33 KB, text/x-csrc)
2019-04-22 20:17 UTC, David Benjamin
Details

Note You need to log in before you can comment on or make changes to this bug.
Description David Benjamin 2019-04-22 20:17:42 UTC
Created attachment 11750 [details]
sample reproducer

glibc 2.28 includes [0], which causes __libc_freeres to free some memory from libdl. One of the functions it calls is __dlerror_main_freeres[1], which frees the current thread's dlerror state. This does not work right if init[2] here was never run. In that case, __dlerror_main_freeres ends up freeing whatever is at thread local key zero, which may have been allocated by some other application and may not be cleaned up by free().

I've attached a sample program which demonstrates the problem when run in valgrind. See the comment at the top for instructions and notes.

[0] https://sourceware.org/git/?p=glibc.git;a=commit;h=2827ab990aefbb0e53374199b875d98f116d6390
[1] https://sourceware.org/git/?p=glibc.git;a=blob;f=dlfcn/dlerror.c;h=33574faab65628ff85c358677e567837390efe7b;hb=3c03baca37fdcb52c3881e653ca392bba7a99c2b#l226
[2] https://sourceware.org/git/?p=glibc.git;a=blob;f=dlfcn/dlerror.c;h=33574faab65628ff85c358677e567837390efe7b;hb=3c03baca37fdcb52c3881e653ca392bba7a99c2b#l173
Comment 1 Carlos O'Donell 2019-04-26 01:58:22 UTC
I'm looking into this to see what went wrong and how I might fix it. Thanks for the report.
Comment 2 Mark Wielaard 2019-05-09 14:37:46 UTC
Proposed fix. Only free the memory if __libc_once (once, init) has been called.

diff --git a/dlfcn/dlerror.c b/dlfcn/dlerror.c
index 2737658..41a41ee 100644
--- a/dlfcn/dlerror.c
+++ b/dlfcn/dlerror.c
@@ -230,13 +230,16 @@ free_key_mem (void *mem)
 void
 __dlerror_main_freeres (void)
 {
-  void *mem;
-  /* Free the global memory if used.  */
-  check_free (&last_result);
-  /* Free the TSD memory if used.  */
-  mem = __libc_getspecific (key);
-  if (mem != NULL)
-    free_key_mem (mem);
+  if (__libc_once_get (once))
+    {
+      void *mem;
+      /* Free the global memory if used.  */
+      check_free (&last_result);
+      /* Free the TSD memory if used.  */
+      mem = __libc_getspecific (key);
+      if (mem != NULL)
+       free_key_mem (mem);
+    }
 }
 
 struct dlfcn_hook *_dlfcn_hook __attribute__((nocommon));
Comment 3 Carlos O'Donell 2019-05-09 18:35:08 UTC
(In reply to Mark Wielaard from comment #2)
> Proposed fix. Only free the memory if __libc_once (once, init) has been
> called.
> 
> diff --git a/dlfcn/dlerror.c b/dlfcn/dlerror.c
> index 2737658..41a41ee 100644
> --- a/dlfcn/dlerror.c
> +++ b/dlfcn/dlerror.c
> @@ -230,13 +230,16 @@ free_key_mem (void *mem)
>  void
>  __dlerror_main_freeres (void)
>  {
> -  void *mem;
> -  /* Free the global memory if used.  */
> -  check_free (&last_result);
> -  /* Free the TSD memory if used.  */
> -  mem = __libc_getspecific (key);
> -  if (mem != NULL)
> -    free_key_mem (mem);
> +  if (__libc_once_get (once))
> +    {
> +      void *mem;
> +      /* Free the global memory if used.  */
> +      check_free (&last_result);
> +      /* Free the TSD memory if used.  */
> +      mem = __libc_getspecific (key);
> +      if (mem != NULL)
> +       free_key_mem (mem);
> +    }
>  }
>  
>  struct dlfcn_hook *_dlfcn_hook __attribute__((nocommon));

I reviewed this on libc-alpha.

It isn't quite right, and I noticed a similar failure if the key fails to be created.

I have suggested some changes for Mark to look at.

I think we're almost done though.
Comment 4 Mark Wielaard 2019-05-15 19:51:28 UTC
Updated patch: https://sourceware.org/ml/libc-alpha/2019-05/msg00320.html
Comment 5 Mark Wielaard 2019-05-15 21:50:50 UTC
commit 11b451c8868d8a2b0edc5dfd44fc58d9ee538be0
Author: Mark Wielaard <mark@klomp.org>
Date:   Wed May 15 17:14:01 2019 +0200

    dlfcn: Guard __dlerror_main_freeres with __libc_once_get (once) [BZ# 24476]
    
    dlerror.c (__dlerror_main_freeres) will try to free resources which only
    have been initialized when init () has been called. That function is
    called when resources are needed using __libc_once (once, init) where
    once is a __libc_once_define (static, once) in the dlerror.c file.
    Trying to free those resources if init () hasn't been called will
    produce errors under valgrind memcheck. So guard the freeing of those
    resources using __libc_once_get (once) and make sure we have a valid
    key. Also add a similar guard to __dlerror ().
    
        * dlfcn/dlerror.c (__dlerror_main_freeres): Guard using
        __libc_once_get (once) and static_bug == NULL.
        (__dlerror): Check we have a valid key, set result to static_buf
        otherwise.
    
    Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Comment 6 Mark Wielaard 2019-05-16 13:34:06 UTC
Also backported/cherry-picked to release/2.28/master and release/2.29/master.