dlfcn: dlerror needs to call free from the base namespace [BZ #24773]
diff mbox series

Message ID 87o924x1ok.fsf@oldenburg2.str.redhat.com
State New
Headers show
Series
  • dlfcn: dlerror needs to call free from the base namespace [BZ #24773]
Related show

Commit Message

Florian Weimer July 8, 2019, 10:26 a.m. UTC
(This depends on the earlier patch for restoring GLRO(dl_catch_error).)

Calling free directly may end up freeing a pointer allocated by the
dynamic loader using malloc from libc.so in the base namespace using
the allocator from libc.so in a secondary namespace, which results in
crashes.

This commit redirects the free call through GLRO and the dynamic
linker, to reach the correct namespace.  It also cleans up the dlerror
handling along the way, so that pthread_setspecific is no longer
needed (which avoids triggering bug 24774).

2019-07-06  Florian Weimer  <fweimer@redhat.com>

	[BZ #24772]
	* dlfcn/Makefile (libdl-routines): Remove dlfreeres.
	(routines): Add libc_dlerror_result.
	* dlfcn/Versions (libc): Export __libc_dlerror_result under
	GLIBC_PRIVATE.
	(libdl): Remove __libdl_freeres.
	* dlfcn/dlerror.c (struct dl_action result): Moved to
	dlfcn/dlerror.h.
	(last_result, static_buf, key, once, init, free_key_mem): Remove.
	(__dlerror): Use new functions from dlfcn.h.
	(_dlerror_run): Likewise.
	(check_free, fini, __dlerror_main_freeres): Remove.
	* dlfcn/dlerror.h: New file.
	* dlfcn/dlfreeres.c: Remove file.
	* dlfcn/libc_dlerror_result.c: New file.
	* elf/dl-exception.c (_dl_error_free): New function.
	* elf/rtld.c (_rtld_global_ro): Initialization _dl_error_free
	member.
	* elf/tst-dlmopen-dlerror-mod.c (call_dlsym, call_dlopen): Add
	dlerror checks.
	* elf/tst-dlmopen-dlerror.c (do_test): Call dlsym, dlopen with
	various name lengths.
	* include/dlfcn.h (__dlerror_main_freeres): Remove declaration.
	* malloc/set-freeres.c (__libdl_freeres): Likewise.
	(__libc_freeres): Call __libc_dlerror_result_free.
	* malloc/thread-freeres.c (__libc_thread_freeres): Likewise.
	* sysdeps/generic/ldsodefs.h (struct rtld_global_ro): Add
	_dl_error_free member.
	(_dl_error_free): Declare.

Patch
diff mbox series

diff --git a/dlfcn/Makefile b/dlfcn/Makefile
index c49fd6d4c5..b34bcc694f 100644
--- a/dlfcn/Makefile
+++ b/dlfcn/Makefile
@@ -22,9 +22,10 @@  include ../Makeconfig
 headers		:= bits/dlfcn.h dlfcn.h
 extra-libs	:= libdl
 libdl-routines	:= dlopen dlclose dlsym dlvsym dlerror dladdr dladdr1 dlinfo \
-		   dlmopen dlfcn dlfreeres
+		   dlmopen dlfcn
 routines	:= $(patsubst %,s%,$(filter-out dlfcn,$(libdl-routines)))
 elide-routines.os := $(routines)
+routines += libc_dlerror_result
 
 extra-libs-others := libdl
 
diff --git a/dlfcn/Versions b/dlfcn/Versions
index 1df6925a92..f07cb929aa 100644
--- a/dlfcn/Versions
+++ b/dlfcn/Versions
@@ -1,3 +1,8 @@ 
+libc {
+  GLIBC_PRIVATE {
+    __libc_dlerror_result;
+  }
+}
 libdl {
   GLIBC_2.0 {
     dladdr; dlclose; dlerror; dlopen; dlsym;
@@ -13,6 +18,5 @@  libdl {
   }
   GLIBC_PRIVATE {
     _dlfcn_hook;
-    __libdl_freeres;
   }
 }
diff --git a/dlfcn/dlerror.c b/dlfcn/dlerror.c
index c2b08c7fc4..61891f48fd 100644
--- a/dlfcn/dlerror.c
+++ b/dlfcn/dlerror.c
@@ -25,6 +25,8 @@ 
 #include <libc-lock.h>
 #include <ldsodefs.h>
 #include <libc-symbols.h>
+#include <assert.h>
+#include <dlerror.h>
 
 #if !defined SHARED && IS_IN (libdl)
 
@@ -36,89 +38,70 @@  dlerror (void)
 
 #else
 
-/* Type for storing results of dynamic loading actions.  */
-struct dl_action_result
-  {
-    int errcode;
-    int returned;
-    bool malloced;
-    const char *objname;
-    const char *errstring;
-  };
-static struct dl_action_result last_result;
-static struct dl_action_result *static_buf;
-
-/* This is the key for the thread specific memory.  */
-static __libc_key_t key;
-__libc_once_define (static, once);
-
-/* Destructor for the thread-specific data.  */
-static void init (void);
-static void free_key_mem (void *mem);
-
-
 char *
 __dlerror (void)
 {
-  char *buf = NULL;
-  struct dl_action_result *result;
-
 # ifdef SHARED
   if (!rtld_active ())
     return _dlfcn_hook->dlerror ();
 # endif
 
-  /* If we have not yet initialized the buffer do it now.  */
-  __libc_once (once, init);
+  struct dl_action_result *result = __libc_dlerror_result;
 
-  /* Get error string.  */
-  if (static_buf != NULL)
-    result = static_buf;
+  /* No libdl function has been called.  No error is possible.  */
+  if (result == NULL)
+    return NULL;
+
+  /* For an early malloc failure, clear the error flag and return the
+     error message.  This marks the error as delivered.  */
+  if (result == dl_action_result_malloc_failed ())
+    {
+      __libc_dlerror_result = NULL;
+      return (char *) "out of memory";
+    }
+
+  /* Placeholder object.  This can be observed in a recursive call,
+     e.g. from an ELF constructor.  */
+  if (result->errstring == NULL)
+    return NULL;
+
+  /* If we have already reported the error, we can free the result
+     and return NULL.  */
+  if (result->returned)
+    {
+      __libc_dlerror_result = NULL;
+      dl_action_result_errstring_free (result);
+      free (result);
+      return NULL;
+    }
+
+  assert (result->errstring != NULL);
+
+  /* Create he combined error message.  */
+  char *buf;
+  int n;
+  if (result->errcode == 0)
+    n = __asprintf (&buf, "%s%s%s",
+		    result->objname,
+		    result->objname[0] == '\0' ? "" : ": ",
+		    _(result->errstring));
   else
-    {
-      /* init () has been run and we don't use the static buffer.
-	 So we have a valid key.  */
-      result = (struct dl_action_result *) __libc_getspecific (key);
-      if (result == NULL)
-	result = &last_result;
-    }
+    n = __asprintf (&buf, "%s%s%s: %s",
+		    result->objname,
+		    result->objname[0] == '\0' ? "" : ": ",
+		    _(result->errstring),
+		    strerror (result->errcode));
 
-  /* Test whether we already returned the string.  */
-  if (result->returned != 0)
-    {
-      /* We can now free the string.  */
-      if (result->errstring != NULL)
-	{
-	  if (strcmp (result->errstring, "out of memory") != 0)
-	    free ((char *) result->errstring);
-	  result->errstring = NULL;
-	}
-    }
-  else if (result->errstring != NULL)
-    {
-      buf = (char *) result->errstring;
-      int n;
-      if (result->errcode == 0)
-	n = __asprintf (&buf, "%s%s%s",
-			result->objname,
-			result->objname[0] == '\0' ? "" : ": ",
-			_(result->errstring));
-      else
-	n = __asprintf (&buf, "%s%s%s: %s",
-			result->objname,
-			result->objname[0] == '\0' ? "" : ": ",
-			_(result->errstring),
-			strerror (result->errcode));
-      if (n != -1)
-	{
-	  /* We don't need the error string anymore.  */
-	  if (strcmp (result->errstring, "out of memory") != 0)
-	    free ((char *) result->errstring);
-	  result->errstring = buf;
-	}
+  /* Mark the error as delivered.  */
+  result->returned = true;
 
-      /* Mark the error as returned.  */
-      result->returned = 1;
+  if (n >= 0)
+    {
+      /* Replace the error string with the newly allocated one.  */
+      dl_action_result_errstring_free (result);
+      result->errstring = buf;
+      result->errstring_source = dl_action_result_errstring_local;
+      return buf;
     }
 
   return buf;
@@ -130,130 +113,94 @@  strong_alias (__dlerror, dlerror)
 int
 _dlerror_run (void (*operate) (void *), void *args)
 {
-  struct dl_action_result *result;
+  struct dl_action_result *result = __libc_dlerror_result;
+  if (result != NULL)
+    {
+      if (result == dl_action_result_malloc_failed ())
+	{
+	  /* Clear the previous error.  */
+	  __libc_dlerror_result = NULL;
+	  result = NULL;
+	}
+      else
+	{
+	  /* There is an existing object.  Free its error string, but
+	     keep the object.  */
+	  dl_action_result_errstring_free (result);
+	  /* Mark the object as not containing an error.  This ensures
+	     that call to dlerror from, for example, an ELF
+	     constructor will not notice this result object.  */
+	  result->errstring = NULL;
+	}
+    }
 
-  /* If we have not yet initialized the buffer do it now.  */
-  __libc_once (once, init);
+  const char *objname;
+  const char *errstring;
+  bool malloced;
+  int errcode = GLRO (dl_catch_error) (&objname, &errstring, &malloced,
+				       operate, args);
 
-  /* Get error string and number.  */
-  if (static_buf != NULL)
-    result = static_buf;
+  /* ELF constructors or destructors may have indirectly altered the
+     value of __libc_dlerror_result, therefore reload it.  */
+  result = __libc_dlerror_result;
+
+  if (errstring == NULL)
+    {
+      /* There is no error.  We no longer need the result object if it
+	 does not contain an error.  However, a recursive call may
+	 have added an error even if this call did not cause it.  Keep
+	 the other error.  */
+      if (result != NULL && result->errstring == NULL)
+	{
+	  __libc_dlerror_result = NULL;
+	  free (result);
+	}
+      return 0;
+    }
   else
     {
-      /* We don't use the static buffer and so we have a key.  Use it
-	 to get the thread-specific buffer.  */
-      result = __libc_getspecific (key);
-      if (result == NULL)
+      /* A new error occurred.  Check if a result object has to be
+	 allocated.  */
+      if (result == NULL || result == dl_action_result_malloc_failed ())
 	{
-	  result = (struct dl_action_result *) calloc (1, sizeof (*result));
+	  /* Allocating storage for the error message after the fact
+	     is not ideal.  But this avoids an infinite recursion in
+	     case malloc itself calls libdl functions (without
+	     triggering errors).  */
+	  result = malloc (sizeof (*result));
 	  if (result == NULL)
-	    /* We are out of memory.  Since this is no really critical
-	       situation we carry on by using the global variable.
-	       This might lead to conflicts between the threads but
-	       they soon all will have memory problems.  */
-	    result = &last_result;
-	  else
-	    /* Set the tsd.  */
-	    __libc_setspecific (key, result);
+	    {
+	      /* Assume that the dlfcn failure was due to a malloc
+		 failure, too.  */
+	      if (malloced)
+		dl_error_free ((char *) errstring);
+	      __libc_dlerror_result = dl_action_result_malloc_failed ();
+	      return 1;
+	    }
+	  __libc_dlerror_result = result;
 	}
+      else
+	/* Deallocate the existing error message from a recursive
+	   call, but reuse the result object.  */
+	dl_action_result_errstring_free (result);
+
+      result->errcode = errcode;
+      result->objname = objname;
+      result->errstring = (char *) errstring;
+      result->returned = false;
+      /* In case of an error, the malloced flag indicates whether the
+	 error string is constant or not.  */
+      if (malloced)
+	result->errstring_source = dl_action_result_errstring_rtld;
+      else
+	result->errstring_source = dl_action_result_errstring_constant;
+
+      return 1;
     }
-
-  if (result->errstring != NULL)
-    {
-      /* Free the error string from the last failed command.  This can
-	 happen if `dlerror' was not run after an error was found.  */
-      if (result->malloced)
-	free ((char *) result->errstring);
-      result->errstring = NULL;
-    }
-
-  result->errcode = GLRO (dl_catch_error) (&result->objname,
-					   &result->errstring,
-					   &result->malloced,
-					   operate, args);
-
-  /* If no error we mark that no error string is available.  */
-  result->returned = result->errstring == NULL;
-
-  return result->errstring != NULL;
-}
-
-
-/* Initialize buffers for results.  */
-static void
-init (void)
-{
-  if (__libc_key_create (&key, free_key_mem))
-    /* Creating the key failed.  This means something really went
-       wrong.  In any case use a static buffer which is better than
-       nothing.  */
-    static_buf = &last_result;
-}
-
-
-static void
-check_free (struct dl_action_result *rec)
-{
-  if (rec->errstring != NULL
-      && strcmp (rec->errstring, "out of memory") != 0)
-    {
-      /* We can free the string only if the allocation happened in the
-	 C library used by the dynamic linker.  This means, it is
-	 always the C library in the base namespace.  When we're statically
-         linked, the dynamic linker is part of the program and so always
-	 uses the same C library we use here.  */
-#ifdef SHARED
-      struct link_map *map = NULL;
-      Dl_info info;
-      if (_dl_addr (check_free, &info, &map, NULL) != 0 && map->l_ns == 0)
-#endif
-	{
-	  free ((char *) rec->errstring);
-	  rec->errstring = NULL;
-	}
-    }
-}
-
-
-static void
-__attribute__ ((destructor))
-fini (void)
-{
-  check_free (&last_result);
-}
-
-
-/* Free the thread specific data, this is done if a thread terminates.  */
-static void
-free_key_mem (void *mem)
-{
-  check_free ((struct dl_action_result *) mem);
-
-  free (mem);
-  __libc_setspecific (key, NULL);
 }
 
 # ifdef SHARED
 
-/* Free the dlerror-related resources.  */
-void
-__dlerror_main_freeres (void)
-{
-  /* Free the global memory if used.  */
-  check_free (&last_result);
-
-  if (__libc_once_get (once) && static_buf == NULL)
-    {
-      /* init () has been run and we don't use the static buffer.
-	 So we have a valid key.  */
-      void *mem;
-      /* Free the TSD memory if used.  */
-      mem = __libc_getspecific (key);
-      if (mem != NULL)
-	free_key_mem (mem);
-    }
-}
-
 struct dlfcn_hook *_dlfcn_hook __attribute__((nocommon));
 libdl_hidden_data_def (_dlfcn_hook)
 
diff --git a/dlfcn/dlerror.h b/dlfcn/dlerror.h
new file mode 100644
index 0000000000..ada7a54671
--- /dev/null
+++ b/dlfcn/dlerror.h
@@ -0,0 +1,74 @@ 
+#ifndef _DLERROR_H
+#define _DLERROR_H
+
+#include <dlfcn.h>
+#include <ldsodefs.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/* Source of the errstring member in struct dl_action_result, for
+   finding the right deallocation routine.  */
+enum dl_action_result_errstring_source
+  {
+   dl_action_result_errstring_constant,
+   dl_action_result_errstring_rtld,
+   dl_action_result_errstring_local
+  };
+
+struct dl_action_result
+{
+  int errcode;
+  char errstring_source;
+  bool returned;
+  const char *objname;
+  char *errstring;
+};
+
+/* Used to free the errstring member of struct dl_action_result in the
+   dl_action_result_errstring_rtld case.  */
+static inline void
+dl_error_free (void *ptr)
+{
+#ifdef SHARED
+  /* In the shared case, ld.so may use a different malloc than this
+     namespace.  */
+  GLRO (dl_error_free (ptr));
+#else
+  /* Call the implementation directly.  It still has to check for
+     pointers which cannot be freed, so do not call free directly
+     here.  */
+  _dl_error_free (ptr);
+#endif
+}
+
+/* Deallocate RESULT->errstring, leaving *RESULT itself allocated.  */
+static inline void
+dl_action_result_errstring_free (struct dl_action_result *result)
+{
+  switch (result->errstring_source)
+    {
+    case dl_action_result_errstring_constant:
+      break;
+    case dl_action_result_errstring_rtld:
+      dl_error_free (result->errstring);
+      break;
+    case dl_action_result_errstring_local:
+      free (result->errstring);
+      break;
+    }
+}
+
+static inline struct dl_action_result *
+dl_action_result_malloc_failed (void)
+{
+  return (struct dl_action_result *) (intptr_t) -1;
+}
+
+/* Thread-local variable for storing dlfcn failures for subsequent
+   reporting via dlerror.  */
+extern __thread struct dl_action_result *__libc_dlerror_result
+  attribute_tls_model_ie;
+void __libc_dlerror_result_free (void) attribute_hidden;
+
+#endif /* _DLERROR_H */
diff --git a/dlfcn/dlfreeres.c b/dlfcn/dlfreeres.c
deleted file mode 100644
index ac0a86cad2..0000000000
--- a/dlfcn/dlfreeres.c
+++ /dev/null
@@ -1,29 +0,0 @@ 
-/* Clean up allocated libdl memory on demand.
-   Copyright (C) 2018-2019 Free Software Foundation, Inc.
-   This file is part of the GNU C Library.
-
-   The GNU C Library is free software; you can redistribute it and/or
-   modify it under the terms of the GNU Lesser General Public
-   License as published by the Free Software Foundation; either
-   version 2.1 of the License, or (at your option) any later version.
-
-   The GNU C Library is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-   Lesser General Public License for more details.
-
-   You should have received a copy of the GNU Lesser General Public
-   License along with the GNU C Library; if not, see
-   <http://www.gnu.org/licenses/>.  */
-
-#include <set-hooks.h>
-#include <libc-symbols.h>
-#include <dlfcn.h>
-
-/* Free libdl.so resources.
-   Note: Caller ensures we are called only once.  */
-void
-__libdl_freeres (void)
-{
-  call_function_static_weak (__dlerror_main_freeres);
-}
diff --git a/dlfcn/libc_dlerror_result.c b/dlfcn/libc_dlerror_result.c
new file mode 100644
index 0000000000..11468937a2
--- /dev/null
+++ b/dlfcn/libc_dlerror_result.c
@@ -0,0 +1,39 @@ 
+/* Thread-local variable holding the dlerror result.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <dlerror.h>
+
+/* This pointer is either NULL, dl_action_result_malloc_failed (), or
+   has been allocated using malloc by the namespace that also contains
+   this instance of the thread-local variable.  */
+__thread struct dl_action_result *__libc_dlerror_result attribute_tls_model_ie;
+
+/* Called during thread shutdown to free resources.  */
+void
+__libc_dlerror_result_free (void)
+{
+  if (__libc_dlerror_result != NULL)
+    {
+      if (__libc_dlerror_result != dl_action_result_malloc_failed ())
+        {
+          dl_action_result_errstring_free (__libc_dlerror_result);
+          free (__libc_dlerror_result);
+        }
+      __libc_dlerror_result = NULL;
+    }
+}
diff --git a/elf/dl-exception.c b/elf/dl-exception.c
index 7ee19a7fc8..703415768f 100644
--- a/elf/dl-exception.c
+++ b/elf/dl-exception.c
@@ -30,6 +30,17 @@ 
    a pointer comparison.  See below and in dlfcn/dlerror.c.  */
 static const char _dl_out_of_memory[] = "out of memory";
 
+/* Call free in the main libc.so.  This allows other namespaces to
+   free pointers on the main libc heap, via GLRO (dl_error_free).  It
+   also avoids calling free on the special, pre-allocated
+   out-of-memory error message.  */
+void
+_dl_error_free (void *ptr)
+{
+  if (ptr != _dl_out_of_memory)
+    free (ptr);
+}
+
 /* Dummy allocation object used if allocating the message buffer
    fails.  */
 static void
diff --git a/elf/rtld.c b/elf/rtld.c
index ba2323baa1..402831b2cc 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -326,6 +326,7 @@  struct rtld_global_ro _rtld_global_ro attribute_relro =
     ._dl_open = _dl_open,
     ._dl_close = _dl_close,
     ._dl_catch_error = _rtld_catch_error,
+    ._dl_error_free = _dl_error_free,
     ._dl_tls_get_addr_soft = _dl_tls_get_addr_soft,
 #ifdef HAVE_DL_DISCOVER_OSVERSION
     ._dl_discover_osversion = _dl_discover_osversion
diff --git a/elf/tst-dlmopen-dlerror-mod.c b/elf/tst-dlmopen-dlerror-mod.c
index dcb94320b4..25188750fb 100644
--- a/elf/tst-dlmopen-dlerror-mod.c
+++ b/elf/tst-dlmopen-dlerror-mod.c
@@ -18,6 +18,8 @@ 
 
 #include <dlfcn.h>
 #include <stddef.h>
+#include <stdio.h>
+#include <string.h>
 #include <support/check.h>
 
 /* Note: This object is not linked into the main program, so we cannot
@@ -25,17 +27,32 @@ 
    to use FAIL_EXIT1 (or something else that calls exit).  */
 
 void
-call_dlsym (void)
+call_dlsym (const char *name)
 {
-  void *ptr = dlsym (NULL, "does not exist");
+  void *ptr = dlsym (NULL, name);
   if (ptr != NULL)
-    FAIL_EXIT1 ("dlsym did not fail as expected");
+    FAIL_EXIT1 ("dlsym did not fail as expected for: %s", name);
+  const char *message = dlerror ();
+  if (strstr (message, ": undefined symbol: does not exist X") == NULL)
+    FAIL_EXIT1 ("invalid dlsym error message for [[%s]]: %s", name, message);
+  message = dlerror ();
+  if (message != NULL)
+    FAIL_EXIT1 ("second dlsym for [[%s]]: %s", name, message);
 }
 
 void
-call_dlopen (void)
+call_dlopen (const char *name)
 {
-  void *handle = dlopen ("tst-dlmopen-dlerror does not exist", RTLD_NOW);
+  void *handle = dlopen (name, RTLD_NOW);
   if (handle != NULL)
-    FAIL_EXIT1 ("dlopen did not fail as expected");
+    FAIL_EXIT1 ("dlopen did not fail as expected for: %s", name);
+  const char *message = dlerror ();
+  if (strstr (message, "X: cannot open shared object file:"
+              " No such file or directory") == NULL
+      && strstr (message, "X: cannot open shared object file:"
+                 " File name too long") == NULL)
+    FAIL_EXIT1 ("invalid dlopen error message for [[%s]]: %s", name, message);
+  message = dlerror ();
+  if (message != NULL)
+    FAIL_EXIT1 ("second dlopen for [[%s]]: %s", name, message);
 }
diff --git a/elf/tst-dlmopen-dlerror.c b/elf/tst-dlmopen-dlerror.c
index 65638f7f38..d02993847d 100644
--- a/elf/tst-dlmopen-dlerror.c
+++ b/elf/tst-dlmopen-dlerror.c
@@ -17,6 +17,7 @@ 
    <http://www.gnu.org/licenses/>.  */
 
 #include <stddef.h>
+#include <string.h>
 #include <support/check.h>
 #include <support/xdlfcn.h>
 
@@ -25,11 +26,22 @@  do_test (void)
 {
   void *handle = xdlmopen (LM_ID_NEWLM, "tst-dlmopen-dlerror-mod.so",
                            RTLD_NOW);
-  void (*call_dlsym) (void) = xdlsym (handle, "call_dlsym");
-  void (*call_dlopen) (void) = xdlsym (handle, "call_dlopen");
+  void (*call_dlsym) (const char *name) = xdlsym (handle, "call_dlsym");
+  void (*call_dlopen) (const char *name) = xdlsym (handle, "call_dlopen");
 
-  call_dlsym ();
-  call_dlopen ();
+  /* Iterate over various name lengths.  This changes the size of
+     error messages allocated by ld.so and has been shown to trigger
+     detectable heap corruption if malloc/free calls in different
+     namespaces are mixed.  */
+  char buffer[2048];
+  char *buffer_end = &buffer[sizeof (buffer) - 2];
+  for (char *p = stpcpy (buffer, "does not exist "); p < buffer_end; ++p)
+    {
+      p[0] = 'X';
+      p[1] = '\0';
+      call_dlsym (buffer);
+      call_dlopen (buffer);
+    }
 
   return 0;
 }
diff --git a/include/dlfcn.h b/include/dlfcn.h
index 0dc57dbe22..109586a1d9 100644
--- a/include/dlfcn.h
+++ b/include/dlfcn.h
@@ -156,7 +156,5 @@  extern void __libc_register_dlfcn_hook (struct link_map *map)
      attribute_hidden;
 #endif
 
-extern void __dlerror_main_freeres (void) attribute_hidden;
-
 #endif
 #endif
diff --git a/malloc/set-freeres.c b/malloc/set-freeres.c
index c9387888e1..9f23f6cb3a 100644
--- a/malloc/set-freeres.c
+++ b/malloc/set-freeres.c
@@ -19,6 +19,7 @@ 
 #include <stdlib.h>
 #include <set-hooks.h>
 #include <libc-internal.h>
+#include <dlfcn/dlerror.h>
 
 #include "../libio/libioP.h"
 
@@ -26,8 +27,6 @@  DEFINE_HOOK (__libc_subfreeres, (void));
 
 symbol_set_define (__libc_freeres_ptrs);
 
-extern __attribute__ ((weak)) void __libdl_freeres (void);
-
 extern __attribute__ ((weak)) void __libpthread_freeres (void);
 
 void __libc_freeres_fn_section
@@ -46,16 +45,13 @@  __libc_freeres (void)
       /* We run the resource freeing after IO cleanup.  */
       RUN_HOOK (__libc_subfreeres, ());
 
-      /* Call the libdl list of cleanup functions
-	 (weak-ref-and-check).  */
-      if (&__libdl_freeres != NULL)
-	__libdl_freeres ();
-
       /* Call the libpthread list of cleanup functions
 	 (weak-ref-and-check).  */
       if (&__libpthread_freeres != NULL)
 	__libpthread_freeres ();
 
+      call_function_static_weak (__libc_dlerror_result_free);
+
       for (p = symbol_set_first_element (__libc_freeres_ptrs);
            !symbol_set_end_p (__libc_freeres_ptrs, p); ++p)
         free (*p);
diff --git a/malloc/thread-freeres.c b/malloc/thread-freeres.c
index ad91755a10..9ebfa5cc4e 100644
--- a/malloc/thread-freeres.c
+++ b/malloc/thread-freeres.c
@@ -16,6 +16,7 @@ 
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#include <dlfcn/dlerror.h>
 #include <libc-internal.h>
 #include <malloc-internal.h>
 #include <resolv/resolv-internal.h>
@@ -32,6 +33,7 @@  __libc_thread_freeres (void)
   call_function_static_weak (__rpc_thread_destroy);
   call_function_static_weak (__res_thread_freeres);
   call_function_static_weak (__strerror_thread_freeres);
+  call_function_static_weak (__libc_dlerror_result_free);
 
   /* This should come last because it shuts down malloc for this
      thread and the other shutdown functions might well call free.  */
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index c8894e9518..7c0f9a6d38 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -610,6 +610,9 @@  struct rtld_global_ro
   int (*_dl_catch_error) (const char **objname, const char **errstring,
 			  bool *mallocedp, void (*operate) (void *),
 			  void *args);
+  /* libdl in a secondary namespace must use free from the base
+     namespace.  */
+  void (*_dl_error_free) (void *);
   void *(*_dl_tls_get_addr_soft) (struct link_map *);
 #ifdef HAVE_DL_DISCOVER_OSVERSION
   int (*_dl_discover_osversion) (void);
@@ -764,6 +767,10 @@  void _dl_exception_create (struct dl_exception *, const char *object,
   __attribute__ ((nonnull (1, 3)));
 rtld_hidden_proto (_dl_exception_create)
 
+/* Used internally to implement dlerror message freeing.  See
+   include/dlfcn.h and dlfcn/dlerror.c.  */
+void _dl_error_free (void *ptr) attribute_hidden;
+
 /* Like _dl_exception_create, but create errstring from a format
    string FMT.  Currently, only "%s" and "%%" are supported as format
    directives.  */