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