about summary refs log tree commit diff
path: root/dlfcn
diff options
context:
space:
mode:
Diffstat (limited to 'dlfcn')
-rw-r--r--dlfcn/Makefile3
-rw-r--r--dlfcn/Versions6
-rw-r--r--dlfcn/dlerror.c305
-rw-r--r--dlfcn/dlerror.h92
-rw-r--r--dlfcn/dlfreeres.c29
-rw-r--r--dlfcn/libc_dlerror_result.c39
6 files changed, 265 insertions, 209 deletions
diff --git a/dlfcn/Makefile b/dlfcn/Makefile
index d51fd08c33..994a3afee6 100644
--- a/dlfcn/Makefile
+++ b/dlfcn/Makefile
@@ -22,9 +22,10 @@ include ../Makeconfig
 headers		:= bits/dlfcn.h dlfcn.h
 extra-libs	:= libdl
 libdl-routines	:= dlopen dlclose dlsym dlvsym dlerror dladdr dladdr1 dlinfo \
-		   dlmopen dlfcn dlfreeres
+		   dlmopen dlfcn
 routines	:= $(patsubst %,s%,$(filter-out dlfcn,$(libdl-routines)))
 elide-routines.os := $(routines)
+routines += libc_dlerror_result
 
 extra-libs-others := libdl
 
diff --git a/dlfcn/Versions b/dlfcn/Versions
index 1df6925a92..f07cb929aa 100644
--- a/dlfcn/Versions
+++ b/dlfcn/Versions
@@ -1,3 +1,8 @@
+libc {
+  GLIBC_PRIVATE {
+    __libc_dlerror_result;
+  }
+}
 libdl {
   GLIBC_2.0 {
     dladdr; dlclose; dlerror; dlopen; dlsym;
@@ -13,6 +18,5 @@ libdl {
   }
   GLIBC_PRIVATE {
     _dlfcn_hook;
-    __libdl_freeres;
   }
 }
diff --git a/dlfcn/dlerror.c b/dlfcn/dlerror.c
index 947b7c10c6..7db70a26d0 100644
--- a/dlfcn/dlerror.c
+++ b/dlfcn/dlerror.c
@@ -25,6 +25,8 @@
 #include <libc-lock.h>
 #include <ldsodefs.h>
 #include <libc-symbols.h>
+#include <assert.h>
+#include <dlerror.h>
 
 #if !defined SHARED && IS_IN (libdl)
 
@@ -36,92 +38,75 @@ dlerror (void)
 
 #else
 
-/* Type for storing results of dynamic loading actions.  */
-struct dl_action_result
-  {
-    int errcode;
-    int returned;
-    bool malloced;
-    const char *objname;
-    const char *errstring;
-  };
-static struct dl_action_result last_result;
-static struct dl_action_result *static_buf;
-
-/* This is the key for the thread specific memory.  */
-static __libc_key_t key;
-__libc_once_define (static, once);
-
-/* Destructor for the thread-specific data.  */
-static void init (void);
-static void free_key_mem (void *mem);
-
-
 char *
 __dlerror (void)
 {
-  char *buf = NULL;
-  struct dl_action_result *result;
-
 # ifdef SHARED
   if (!rtld_active ())
     return _dlfcn_hook->dlerror ();
 # endif
 
-  /* If we have not yet initialized the buffer do it now.  */
-  __libc_once (once, init);
+  struct dl_action_result *result = __libc_dlerror_result;
 
-  /* Get error string.  */
-  if (static_buf != NULL)
-    result = static_buf;
-  else
+  /* No libdl function has been called.  No error is possible.  */
+  if (result == NULL)
+    return NULL;
+
+  /* For an early malloc failure, clear the error flag and return the
+     error message.  This marks the error as delivered.  */
+  if (result == dl_action_result_malloc_failed)
     {
-      /* init () has been run and we don't use the static buffer.
-	 So we have a valid key.  */
-      result = (struct dl_action_result *) __libc_getspecific (key);
-      if (result == NULL)
-	result = &last_result;
+      __libc_dlerror_result = NULL;
+      return (char *) "out of memory";
     }
 
-  /* Test whether we already returned the string.  */
-  if (result->returned != 0)
+  /* Placeholder object.  This can be observed in a recursive call,
+     e.g. from an ELF constructor.  */
+  if (result->errstring == NULL)
+    return NULL;
+
+  /* If we have already reported the error, we can free the result and
+     return NULL.  See __libc_dlerror_result_free.  */
+  if (result->returned)
     {
-      /* We can now free the string.  */
-      if (result->errstring != NULL)
-	{
-	  if (strcmp (result->errstring, "out of memory") != 0)
-	    free ((char *) result->errstring);
-	  result->errstring = NULL;
-	}
+      __libc_dlerror_result = NULL;
+      dl_action_result_errstring_free (result);
+      free (result);
+      return NULL;
     }
-  else if (result->errstring != NULL)
-    {
-      buf = (char *) result->errstring;
-      int n;
-      if (result->errcode == 0)
-	n = __asprintf (&buf, "%s%s%s",
-			result->objname,
-			result->objname[0] == '\0' ? "" : ": ",
-			_(result->errstring));
-      else
-	n = __asprintf (&buf, "%s%s%s: %s",
-			result->objname,
-			result->objname[0] == '\0' ? "" : ": ",
-			_(result->errstring),
-			strerror (result->errcode));
-      if (n != -1)
-	{
-	  /* We don't need the error string anymore.  */
-	  if (strcmp (result->errstring, "out of memory") != 0)
-	    free ((char *) result->errstring);
-	  result->errstring = buf;
-	}
 
-      /* Mark the error as returned.  */
-      result->returned = 1;
-    }
+  assert (result->errstring != NULL);
+
+  /* Create the combined error message.  */
+  char *buf;
+  int n;
+  if (result->errcode == 0)
+    n = __asprintf (&buf, "%s%s%s",
+		    result->objname,
+		    result->objname[0] == '\0' ? "" : ": ",
+		    _(result->errstring));
+  else
+    n = __asprintf (&buf, "%s%s%s: %s",
+		    result->objname,
+		    result->objname[0] == '\0' ? "" : ": ",
+		    _(result->errstring),
+		    strerror (result->errcode));
 
-  return buf;
+  /* Mark the error as delivered.  */
+  result->returned = true;
+
+  if (n >= 0)
+    {
+      /* Replace the error string with the newly allocated one.  */
+      dl_action_result_errstring_free (result);
+      result->errstring = buf;
+      result->errstring_source = dl_action_result_errstring_local;
+      return buf;
+    }
+  else
+    /* We could not create the combined error message, so use the
+       existing string as a fallback.  */
+    return result->errstring;
 }
 # ifdef SHARED
 strong_alias (__dlerror, dlerror)
@@ -130,130 +115,94 @@ strong_alias (__dlerror, dlerror)
 int
 _dlerror_run (void (*operate) (void *), void *args)
 {
-  struct dl_action_result *result;
-
-  /* If we have not yet initialized the buffer do it now.  */
-  __libc_once (once, init);
-
-  /* Get error string and number.  */
-  if (static_buf != NULL)
-    result = static_buf;
-  else
+  struct dl_action_result *result = __libc_dlerror_result;
+  if (result != NULL)
     {
-      /* We don't use the static buffer and so we have a key.  Use it
-	 to get the thread-specific buffer.  */
-      result = __libc_getspecific (key);
-      if (result == NULL)
+      if (result == dl_action_result_malloc_failed)
 	{
-	  result = (struct dl_action_result *) calloc (1, sizeof (*result));
-	  if (result == NULL)
-	    /* We are out of memory.  Since this is no really critical
-	       situation we carry on by using the global variable.
-	       This might lead to conflicts between the threads but
-	       they soon all will have memory problems.  */
-	    result = &last_result;
-	  else
-	    /* Set the tsd.  */
-	    __libc_setspecific (key, result);
+	  /* Clear the previous error.  */
+	  __libc_dlerror_result = NULL;
+	  result = NULL;
+	}
+      else
+	{
+	  /* There is an existing object.  Free its error string, but
+	     keep the object.  */
+	  dl_action_result_errstring_free (result);
+	  /* Mark the object as not containing an error.  This ensures
+	     that call to dlerror from, for example, an ELF
+	     constructor will not notice this result object.  */
+	  result->errstring = NULL;
 	}
     }
 
-  if (result->errstring != NULL)
-    {
-      /* Free the error string from the last failed command.  This can
-	 happen if `dlerror' was not run after an error was found.  */
-      if (result->malloced)
-	free ((char *) result->errstring);
-      result->errstring = NULL;
-    }
-
-  result->errcode = GLRO (dl_catch_error) (&result->objname,
-					   &result->errstring,
-					   &result->malloced,
-					   operate, args);
-
-  /* If no error we mark that no error string is available.  */
-  result->returned = result->errstring == NULL;
+  const char *objname;
+  const char *errstring;
+  bool malloced;
+  int errcode = GLRO (dl_catch_error) (&objname, &errstring, &malloced,
+				       operate, args);
 
-  return result->errstring != NULL;
-}
+  /* ELF constructors or destructors may have indirectly altered the
+     value of __libc_dlerror_result, therefore reload it.  */
+  result = __libc_dlerror_result;
 
-
-/* Initialize buffers for results.  */
-static void
-init (void)
-{
-  if (__libc_key_create (&key, free_key_mem))
-    /* Creating the key failed.  This means something really went
-       wrong.  In any case use a static buffer which is better than
-       nothing.  */
-    static_buf = &last_result;
-}
-
-
-static void
-check_free (struct dl_action_result *rec)
-{
-  if (rec->errstring != NULL
-      && strcmp (rec->errstring, "out of memory") != 0)
+  if (errstring == NULL)
     {
-      /* We can free the string only if the allocation happened in the
-	 C library used by the dynamic linker.  This means, it is
-	 always the C library in the base namespace.  When we're statically
-         linked, the dynamic linker is part of the program and so always
-	 uses the same C library we use here.  */
-#ifdef SHARED
-      struct link_map *map = NULL;
-      Dl_info info;
-      if (_dl_addr (check_free, &info, &map, NULL) != 0 && map->l_ns == 0)
-#endif
+      /* There is no error.  We no longer need the result object if it
+	 does not contain an error.  However, a recursive call may
+	 have added an error even if this call did not cause it.  Keep
+	 the other error.  */
+      if (result != NULL && result->errstring == NULL)
 	{
-	  free ((char *) rec->errstring);
-	  rec->errstring = NULL;
+	  __libc_dlerror_result = NULL;
+	  free (result);
 	}
+      return 0;
     }
-}
-
-
-static void
-__attribute__ ((destructor))
-fini (void)
-{
-  check_free (&last_result);
-}
-
-
-/* Free the thread specific data, this is done if a thread terminates.  */
-static void
-free_key_mem (void *mem)
-{
-  check_free ((struct dl_action_result *) mem);
+  else
+    {
+      /* A new error occurred.  Check if a result object has to be
+	 allocated.  */
+      if (result == NULL || result == dl_action_result_malloc_failed)
+	{
+	  /* Allocating storage for the error message after the fact
+	     is not ideal.  But this avoids an infinite recursion in
+	     case malloc itself calls libdl functions (without
+	     triggering errors).  */
+	  result = malloc (sizeof (*result));
+	  if (result == NULL)
+	    {
+	      /* Assume that the dlfcn failure was due to a malloc
+		 failure, too.  */
+	      if (malloced)
+		dl_error_free ((char *) errstring);
+	      __libc_dlerror_result = dl_action_result_malloc_failed;
+	      return 1;
+	    }
+	  __libc_dlerror_result = result;
+	}
+      else
+	/* Deallocate the existing error message from a recursive
+	   call, but reuse the result object.  */
+	dl_action_result_errstring_free (result);
+
+      result->errcode = errcode;
+      result->objname = objname;
+      result->errstring = (char *) errstring;
+      result->returned = false;
+      /* In case of an error, the malloced flag indicates whether the
+	 error string is constant or not.  */
+      if (malloced)
+	result->errstring_source = dl_action_result_errstring_rtld;
+      else
+	result->errstring_source = dl_action_result_errstring_constant;
 
-  free (mem);
-  __libc_setspecific (key, NULL);
+      return 1;
+    }
 }
 
 # ifdef SHARED
 
-/* Free the dlerror-related resources.  */
-void
-__dlerror_main_freeres (void)
-{
-  /* Free the global memory if used.  */
-  check_free (&last_result);
-
-  if (__libc_once_get (once) && static_buf == NULL)
-    {
-      /* init () has been run and we don't use the static buffer.
-	 So we have a valid key.  */
-      void *mem;
-      /* Free the TSD memory if used.  */
-      mem = __libc_getspecific (key);
-      if (mem != NULL)
-	free_key_mem (mem);
-    }
-}
-
 struct dlfcn_hook *_dlfcn_hook __attribute__((nocommon));
 libdl_hidden_data_def (_dlfcn_hook)
 
diff --git a/dlfcn/dlerror.h b/dlfcn/dlerror.h
new file mode 100644
index 0000000000..cb9a9cea4c
--- /dev/null
+++ b/dlfcn/dlerror.h
@@ -0,0 +1,92 @@
+/* Memory management for dlerror messages.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _DLERROR_H
+#define _DLERROR_H
+
+#include <dlfcn.h>
+#include <ldsodefs.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/* Source of the errstring member in struct dl_action_result, for
+   finding the right deallocation routine.  */
+enum dl_action_result_errstring_source
+  {
+   dl_action_result_errstring_constant, /* String literal, no deallocation.  */
+   dl_action_result_errstring_rtld, /* libc in the primary namespace.  */
+   dl_action_result_errstring_local, /* libc in the current namespace.  */
+  };
+
+struct dl_action_result
+{
+  int errcode;
+  char errstring_source;
+  bool returned;
+  const char *objname;
+  char *errstring;
+};
+
+/* Used to free the errstring member of struct dl_action_result in the
+   dl_action_result_errstring_rtld case.  */
+static inline void
+dl_error_free (void *ptr)
+{
+#ifdef SHARED
+  /* In the shared case, ld.so may use a different malloc than this
+     namespace.  */
+  GLRO (dl_error_free (ptr));
+#else
+  /* Call the implementation directly.  It still has to check for
+     pointers which cannot be freed, so do not call free directly
+     here.  */
+  _dl_error_free (ptr);
+#endif
+}
+
+/* Deallocate RESULT->errstring, leaving *RESULT itself allocated.  */
+static inline void
+dl_action_result_errstring_free (struct dl_action_result *result)
+{
+  switch (result->errstring_source)
+    {
+    case dl_action_result_errstring_constant:
+      break;
+    case dl_action_result_errstring_rtld:
+      dl_error_free (result->errstring);
+      break;
+    case dl_action_result_errstring_local:
+      free (result->errstring);
+      break;
+    }
+}
+
+/* Stand-in for an error result object whose allocation failed.  No
+   precise message can be reported for this, but an error must still
+   be signaled.  */
+static struct dl_action_result *const dl_action_result_malloc_failed
+  __attribute__ ((unused)) = (struct dl_action_result *) (intptr_t) -1;
+
+/* Thread-local variable for storing dlfcn failures for subsequent
+   reporting via dlerror.  */
+extern __thread struct dl_action_result *__libc_dlerror_result
+  attribute_tls_model_ie;
+void __libc_dlerror_result_free (void) attribute_hidden;
+
+#endif /* _DLERROR_H */
diff --git a/dlfcn/dlfreeres.c b/dlfcn/dlfreeres.c
deleted file mode 100644
index 856b76416d..0000000000
--- a/dlfcn/dlfreeres.c
+++ /dev/null
@@ -1,29 +0,0 @@
-/* Clean up allocated libdl memory on demand.
-   Copyright (C) 2018-2021 Free Software Foundation, Inc.
-   This file is part of the GNU C Library.
-
-   The GNU C Library is free software; you can redistribute it and/or
-   modify it under the terms of the GNU Lesser General Public
-   License as published by the Free Software Foundation; either
-   version 2.1 of the License, or (at your option) any later version.
-
-   The GNU C Library is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-   Lesser General Public License for more details.
-
-   You should have received a copy of the GNU Lesser General Public
-   License along with the GNU C Library; if not, see
-   <https://www.gnu.org/licenses/>.  */
-
-#include <set-hooks.h>
-#include <libc-symbols.h>
-#include <dlfcn.h>
-
-/* Free libdl.so resources.
-   Note: Caller ensures we are called only once.  */
-void
-__libdl_freeres (void)
-{
-  call_function_static_weak (__dlerror_main_freeres);
-}
diff --git a/dlfcn/libc_dlerror_result.c b/dlfcn/libc_dlerror_result.c
new file mode 100644
index 0000000000..99747186b9
--- /dev/null
+++ b/dlfcn/libc_dlerror_result.c
@@ -0,0 +1,39 @@
+/* Thread-local variable holding the dlerror result.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <dlerror.h>
+
+/* This pointer is either NULL, dl_action_result_malloc_failed (), or
+   has been allocated using malloc by the namespace that also contains
+   this instance of the thread-local variable.  */
+__thread struct dl_action_result *__libc_dlerror_result attribute_tls_model_ie;
+
+/* Called during thread shutdown to free resources.  */
+void
+__libc_dlerror_result_free (void)
+{
+  if (__libc_dlerror_result != NULL)
+    {
+      if (__libc_dlerror_result != dl_action_result_malloc_failed)
+        {
+          dl_action_result_errstring_free (__libc_dlerror_result);
+          free (__libc_dlerror_result);
+        }
+      __libc_dlerror_result = NULL;
+    }
+}