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