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