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