Implement _dl_catch_error, _dl_signal_error in libc.so [BZ #16628]

Message ID 7650651f-f268-6405-c04c-61aa18d166f9@redhat.com
State Superseded
Headers

Commit Message

Florian Weimer Nov. 14, 2016, 3:07 p.m. UTC
  The attached patch depends on the elf/dl-load.c clean I posted today, 
and the addition of the __sigsetjmp hidden definitions on architectures 
which lack them.

The removal of the two function pointers from libc.so (the one in 
rtld_global, and the receiver pointer internal to elf/dl-error.c) is 
desirable from a security perspective, too.

Thanks,
Florian
  

Comments

Carlos O'Donell Nov. 30, 2016, 1:49 p.m. UTC | #1
On 11/14/2016 10:07 AM, Florian Weimer wrote:
> The attached patch depends on the elf/dl-load.c clean I posted today,
> and the addition of the __sigsetjmp hidden definitions on
> architectures which lack them.
> 
> The removal of the two function pointers from libc.so (the one in
> rtld_global, and the receiver pointer internal to elf/dl-error.c) is
> desirable from a security perspective, too.

At a high level I think the design is much cleaner than the existing
confusion of function pointers that are manipulated during the startup
sequence.

I like that the libc.so versions are used once symbol interposition is
enabled, though in the long run I think we need to do something to more
clearly mark exactly when that transition happens and how to cleanup
before it. The dl-minimal malloc transition isn't always that clear and
has caused some problems in the past. I see you're using l_relocated != 0
to detect if it's been malloc'd, and that's the kind of thing we might
eventually turn into some easier to use API.

Regarding the test case, tst-latepthreadmod, it seems to me that it tests
only the interposed libc.so versions of the try/catch framework? A test
that verifies the ld.so try/catch minimal hooks would require a failure
_before_ relocation, like say loading an LD_AUDIT object early and having
it fail.

While I think it's great coverage to test the try/catch hooks, I think we
probably need just one more test case marked XFAIL that loads a broken
LD_AUDIT object to verify the ld.so side of the hooks before transition
to the libc.so versions?
  
Florian Weimer Nov. 30, 2016, 1:59 p.m. UTC | #2
On 11/30/2016 02:49 PM, Carlos O'Donell wrote:

> Regarding the test case, tst-latepthreadmod, it seems to me that it tests
> only the interposed libc.so versions of the try/catch framework? A test
> that verifies the ld.so try/catch minimal hooks would require a failure
> _before_ relocation, like say loading an LD_AUDIT object early and having
> it fail.

We already have an audit module that exercises the exception handling 
mechanism, tst-auditmod11.c, because unlike all the other audit modules, 
it does not define all audit function.  The equivalent of dlsym fails, 
and this raises an exception.  To my knowledge, this is the only use of 
the catch/receive mechanism in ld.so, and is the reason why we cannot 
have a smaller implementation of the exception handling framework in ld.so.

I think this level of test coverage is completely sufficient, even 
though it was added rather by accident, as part of a completely 
different dynamic linker change.

Thanks,
Florian
  
Carlos O'Donell Nov. 30, 2016, 2:01 p.m. UTC | #3
On 11/30/2016 08:59 AM, Florian Weimer wrote:
> On 11/30/2016 02:49 PM, Carlos O'Donell wrote:
> 
>> Regarding the test case, tst-latepthreadmod, it seems to me that it
>> tests only the interposed libc.so versions of the try/catch
>> framework? A test that verifies the ld.so try/catch minimal hooks
>> would require a failure _before_ relocation, like say loading an
>> LD_AUDIT object early and having it fail.
> 
> We already have an audit module that exercises the exception handling
> mechanism, tst-auditmod11.c, because unlike all the other audit
> modules, it does not define all audit function.  The equivalent of
> dlsym fails, and this raises an exception.  To my knowledge, this is
> the only use of the catch/receive mechanism in ld.so, and is the
> reason why we cannot have a smaller implementation of the exception
> handling framework in ld.so.
> 
> I think this level of test coverage is completely sufficient, even
> though it was added rather by accident, as part of a completely
> different dynamic linker change.

Perfect. Please checkin your changes then.
  
Joseph Myers Nov. 30, 2016, 9:39 p.m. UTC | #4
I'm seeing a large number of failures from build-many-glibcs.py that look 
related to this change.

alpha fails to build with:

/scratch/jmyers/glibc-bot/build/compilers/alpha-linux-gnu/glibc/alpha-linux-gnu/libc_pic.os: In function `__GI__dl_catch_error':
/scratch/jmyers/glibc-bot/build/compilers/alpha-linux-gnu/glibc-src/alpha-linux-gnu/elf/dl-error-skeleton.c:196: undefined reference to `__GI___sigsetjmp'

Likewise hppa, mips n32, sh, sparc64, tilegx, tilepro.  (Quite possibly 
other ABIs for mips / sparc / tilegx as well - once a first glibc has 
failed to build when building the compilers, it doesn't attempt building 
the others for that compiler, and this build run was one including 
building the compilers, rather than building glibcs with previously built 
compilers.)
  

Patch

Implement _dl_catch_error, _dl_signal_error in libc.so [BZ #16628]

This change moves the main implementation of _dl_catch_error,
_dl_signal_error to libc.so, where TLS variables can be used
 directly.  This removes a writable function pointer from the
rtld_global variable.

For use during initial relocation, minimal implementations of these
functions are provided in ld.so.  These are eventually interposed
by the libc.so implementations.  This is implemented by compiling
elf/dl-error-skeleton.c twice, via elf/dl-error.c and
elf/dl-error-minimal.c.

As a side effect of this change, the static version of dl-error.c
no longer includes support for the
 _dl_signal_cerror/_dl_receive_error mechanism because it is only
used in ld.so.

2016-11-11  Florian Weimer  <fweimer@redhat.com>

	[BZ #16628]
	Implement _dl_catch_error, _dl_signal_error in libc.so.
	* elf/dl-error-skeleton.c: Rename from elf/dl-error.c.
	(catch_hook): Define as thread-local or regular variable,
	depending on DL_ERROR_BOOTSTRAP.
	(CATCH_HOOK): Remove.
	(dl_signal_error, _dl_catch_error): Use
	catch_hook.  Add hidden definition for libc.
	(_dl_receive_error, _dl_signal_cerror): Use catch_hook.  Compile
	for DL_ERROR_BOOTSTRAP only.
	* elf/dl-error.c, elf/dl-error-minimal.c: New files.
	* elf/Makefile (routines): Add dl-error.
	(dl-routines): Remove dl-error.
	(rtld-routines): Add dl-error-minimal.
	* elf/Versions [libc] (GLIBC_PRIVATE): Add _dl_signal_error,
	_dl_catch_error.
	[ld] (GLIBC_PRIVATE): Likewise.
	* elf/dl-close.c (_dl_cose): Call _dl_signal_error directly.
	* elf/dl-libc.c (dlerror_run): Call _dl_catch_error directly.
	* elf/dl-sym.c (do_sym): Call _dl_signal_error, _dl_catch_error
	directly.
	* elf/dl-tsd.c: Remove file.
	* elf/rtld.c (_rtld_global_ro): Remove initializers for
	_dl_catch_error, _dl_signal_error.
	(_dl_initial_error_catch_tsd): Remove definition.
	(do_preload): Remove initialization of dl_error_catch_tsd.
	* dlfcn/dlerror.c (_dlerror_run): Call _dl_catch_error directly.
	* dlfcn/dlinfo.c (dlinfo_doit): Call _dl_signal_error directly.
	* dlfcn/dlmopen.c (dlmopen_doit): Likewise.
	* dlfcn/dlopen.c (dlopen_doit): Likewise.
	* nptl/nptl-init.c (__pthread_initialize_minimal_internal): Do not
	set dl_error_catch_tsd.
	* sysdeps/generic/ldsodefs.h (struct rtld_global): Remove
	_dl_error_catch_tsd member.
	(struct rtld_global_ro): Remove _dl_catch_error, _dl_signal_error
	members.
	(_dl_initial_error_catch_tsd): Remove declaration.
	(_dl_dprintf): Provide definition for use outside of ld.so.
	[!rtld] (_dl_signal_cerror): Redirect to _dl_signal_error.
	(_dl_signal_error, _dl_catch_error): Make public.  Add hidden
	prototype for libc.
	* sysdeps/generic/localplt.data (ld.so): Add _dl_signal_error,
	_dl_catch_error.
	* sysdeps/unix/sysv/linux/aarch64/localplt.data (ld.so): Likewise.
	* sysdeps/unix/sysv/linux/alpha/localplt.data (ld.so): Likewise.
	* sysdeps/unix/sysv/linux/arm/localplt.data (ld.so): Likewise.
	* sysdeps/unix/sysv/linux/hppa/localplt.data (ld.so): Likewise.
	* sysdeps/unix/sysv/linux/i386/localplt.data (ld.so): Likewise.
	* sysdeps/unix/sysv/linux/ia64/localplt.data (ld.so): Likewise.
	* sysdeps/unix/sysv/linux/m68k/localplt.data (ld.so): Likewise.
	* sysdeps/unix/sysv/linux/microblaze/localplt.data (ld.so):
	Likewise.
	* sysdeps/unix/sysv/linux/nios2/localplt.data (ld.so): Likewise.
	* sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/localplt.data
	(ld.so): Likewise.
	* sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/localplt.data
	(ld.so): Likewise.
	* sysdeps/unix/sysv/linux/powerpc/powerpc64/localplt.data (ld.so):
	Likewise.
	* sysdeps/unix/sysv/linux/s390/localplt.data (ld.so): Likewise.
	* sysdeps/unix/sysv/linux/sh/localplt.data (ld.so): Likewise.
	* sysdeps/unix/sysv/linux/sparc/sparc32/localplt.data (ld.so):
	Likewise.
	* sysdeps/unix/sysv/linux/sparc/sparc64/localplt.data (ld.so):
	Likewise.
	* sysdeps/x86_64/localplt.data (ld.so): Likewise.

diff --git a/dlfcn/dlerror.c b/dlfcn/dlerror.c
index 41b2bd6..e0e5648 100644
--- a/dlfcn/dlerror.c
+++ b/dlfcn/dlerror.c
@@ -160,8 +160,8 @@  _dlerror_run (void (*operate) (void *), void *args)
       result->errstring = NULL;
     }
 
-  result->errcode = GLRO(dl_catch_error) (&result->objname, &result->errstring,
-					  &result->malloced, operate, args);
+  result->errcode = _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;
diff --git a/dlfcn/dlinfo.c b/dlfcn/dlinfo.c
index eeba8da..794e354 100644
--- a/dlfcn/dlinfo.c
+++ b/dlfcn/dlinfo.c
@@ -60,7 +60,7 @@  dlinfo_doit (void *argsblock)
 	    break;
 
       if (l == NULL)
-	GLRO(dl_signal_error) (0, NULL, NULL, N_("\
+	_dl_signal_error (0, NULL, NULL, N_("\
 RTLD_SELF used in code not dynamically loaded"));
     }
 # endif
@@ -69,7 +69,7 @@  RTLD_SELF used in code not dynamically loaded"));
     {
     case RTLD_DI_CONFIGADDR:
     default:
-      GLRO(dl_signal_error) (0, NULL, NULL, N_("unsupported dlinfo request"));
+      _dl_signal_error (0, NULL, NULL, N_("unsupported dlinfo request"));
       break;
 
     case RTLD_DI_LMID:
diff --git a/dlfcn/dlmopen.c b/dlfcn/dlmopen.c
index 6da0d0d..4eed513 100644
--- a/dlfcn/dlmopen.c
+++ b/dlfcn/dlmopen.c
@@ -60,12 +60,12 @@  dlmopen_doit (void *a)
 	 must be the main one.  */
       if (args->file == NULL)
 # endif
-	GLRO(dl_signal_error) (EINVAL, NULL, NULL, N_("invalid namespace"));
+	_dl_signal_error (EINVAL, NULL, NULL, N_("invalid namespace"));
 
       /* It makes no sense to use RTLD_GLOBAL when loading a DSO into
 	 a namespace other than the base namespace.  */
       if (__glibc_unlikely (args->mode & RTLD_GLOBAL))
-	GLRO(dl_signal_error) (EINVAL, NULL, NULL, N_("invalid mode"));
+	_dl_signal_error (EINVAL, NULL, NULL, N_("invalid mode"));
     }
 
   args->new = GLRO(dl_open) (args->file ?: "", args->mode | __RTLD_DLOPEN,
diff --git a/dlfcn/dlopen.c b/dlfcn/dlopen.c
index 453efbd..5263fed 100644
--- a/dlfcn/dlopen.c
+++ b/dlfcn/dlopen.c
@@ -61,7 +61,7 @@  dlopen_doit (void *a)
   if (args->mode & ~(RTLD_BINDING_MASK | RTLD_NOLOAD | RTLD_DEEPBIND
 		     | RTLD_GLOBAL | RTLD_LOCAL | RTLD_NODELETE
 		     | __RTLD_SPROF))
-    GLRO(dl_signal_error) (0, NULL, NULL, _("invalid mode parameter"));
+    _dl_signal_error (0, NULL, NULL, _("invalid mode parameter"));
 
   args->new = GLRO(dl_open) (args->file ?: "", args->mode | __RTLD_DLOPEN,
 			     args->caller,
diff --git a/elf/Makefile b/elf/Makefile
index 82c7e05..99942e3 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -24,12 +24,12 @@  include ../Makeconfig
 headers		= elf.h bits/elfclass.h link.h bits/link.h
 routines	= $(all-dl-routines) dl-support dl-iteratephdr \
 		  dl-addr dl-addr-obj enbl-secure dl-profstub \
-		  dl-origin dl-libc dl-sym dl-tsd dl-sysdep
+		  dl-origin dl-libc dl-sym dl-sysdep dl-error
 
 # The core dynamic linking functions are in libc for the static and
 # profiled libraries.
 dl-routines	= $(addprefix dl-,load lookup object reloc deps hwcaps \
-				  runtime error init fini debug misc \
+				  runtime init fini debug misc \
 				  version profile conflict tls origin scope \
 				  execstack caller open close trampoline)
 ifeq (yes,$(use-ldconfig))
@@ -43,7 +43,8 @@  shared-only-routines += dl-caller
 
 # ld.so uses those routines, plus some special stuff for being the program
 # interpreter and operating independent of libc.
-rtld-routines	= rtld $(all-dl-routines) dl-sysdep dl-environ dl-minimal
+rtld-routines	= rtld $(all-dl-routines) dl-sysdep dl-environ dl-minimal \
+  dl-error-minimal
 all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines)
 
 CFLAGS-dl-runtime.c = -fexceptions -fasynchronous-unwind-tables
diff --git a/elf/Versions b/elf/Versions
index 23deda9..05e5449 100644
--- a/elf/Versions
+++ b/elf/Versions
@@ -26,6 +26,9 @@  libc {
     _dl_open_hook;
     _dl_sym; _dl_vsym;
     __libc_dlclose; __libc_dlopen_mode; __libc_dlsym;
+
+    # Internal error handling support.  Interposes the functions in ld.so.
+    _dl_signal_error; _dl_catch_error;
   }
 }
 
@@ -64,5 +67,8 @@  ld {
 
     # Pointer protection.
     __pointer_chk_guard;
+
+    # Internal error handling support.  Interposed by libc.so.
+    _dl_signal_error; _dl_catch_error;
   }
 }
diff --git a/elf/dl-close.c b/elf/dl-close.c
index 687d7de..6489703 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -814,7 +814,7 @@  _dl_close (void *_map)
     }
 
   if (__builtin_expect (map->l_direct_opencount, 1) == 0)
-    GLRO(dl_signal_error) (0, map->l_name, NULL, N_("shared object not open"));
+    _dl_signal_error (0, map->l_name, NULL, N_("shared object not open"));
 
   /* Acquire the lock.  */
   __rtld_lock_lock_recursive (GL(dl_load_lock));
diff --git a/elf/dl-error-minimal.c b/elf/dl-error-minimal.c
new file mode 100644
index 0000000..d535d65
--- /dev/null
+++ b/elf/dl-error-minimal.c
@@ -0,0 +1,23 @@ 
+/* Error handling for runtime dynamic linker, minimal version.
+   Copyright (C) 1995-2016 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/>.  */
+
+/* This version does lives in ld.so, does not use thread-local data
+   and supports _dl_signal_cerror and _dl_receive_error.  */
+
+#define DL_ERROR_BOOTSTRAP 1
+#include "dl-error-skeleton.c"
diff --git a/elf/dl-error-skeleton.c b/elf/dl-error-skeleton.c
new file mode 100644
index 0000000..beb31ae
--- /dev/null
+++ b/elf/dl-error-skeleton.c
@@ -0,0 +1,230 @@ 
+/* Template for error handling for runtime dynamic linker.
+   Copyright (C) 1995-2016 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/>.  */
+
+/* The following macro needs to be defined before including this
+   skeleton file:
+
+   DL_ERROR_BOOTSTRAP
+
+     If 1, do not use TLS and implement _dl_signal_cerror and
+     _dl_receive_error.  If 0, TLS is used, and the variants with
+     error callbacks are not provided.  */
+
+
+#include <libintl.h>
+#include <setjmp.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ldsodefs.h>
+#include <stdio.h>
+
+/* This structure communicates state between _dl_catch_error and
+   _dl_signal_error.  */
+struct catch
+  {
+    const char **objname;	/* Object/File name.  */
+    const char **errstring;	/* Error detail filled in here.  */
+    bool *malloced;		/* Nonzero if the string is malloced
+				   by the libc malloc.  */
+    volatile int *errcode;	/* Return value of _dl_signal_error.  */
+    jmp_buf env;		/* longjmp here on error.  */
+  };
+
+/* Multiple threads at once can use the `_dl_catch_error' function.  The
+   calls can come from `_dl_map_object_deps', `_dlerror_run', or from
+   any of the libc functionality which loads dynamic objects (NSS, iconv).
+   Therefore we have to be prepared to save the state in thread-local
+   memory.  */
+#if !DL_ERROR_BOOTSTRAP
+static __thread struct catch *catch_hook attribute_tls_model_ie;
+#else
+/* The version of this code in ld.so cannot use thread-local variables
+   and is used during bootstrap only.  */
+static struct catch *catch_hook;
+#endif
+
+/* This message we return as a last resort.  We define the string in a
+   variable since we have to avoid freeing it and so have to enable
+   a pointer comparison.  See below and in dlfcn/dlerror.c.  */
+static const char _dl_out_of_memory[] = "out of memory";
+
+#if DL_ERROR_BOOTSTRAP
+/* This points to a function which is called when an continuable error is
+   received.  Unlike the handling of `catch' this function may return.
+   The arguments will be the `errstring' and `objname'.
+
+   Since this functionality is not used in normal programs (only in ld.so)
+   we do not care about multi-threaded programs here.  We keep this as a
+   global variable.  */
+static receiver_fct receiver;
+#endif /* DL_ERROR_BOOTSTRAP */
+
+void
+internal_function
+_dl_signal_error (int errcode, const char *objname, const char *occation,
+		  const char *errstring)
+{
+  struct catch *lcatch = catch_hook;
+
+  if (! errstring)
+    errstring = N_("DYNAMIC LINKER BUG!!!");
+
+  if (objname == NULL)
+    objname = "";
+  if (lcatch != NULL)
+    {
+      /* We are inside _dl_catch_error.  Return to it.  We have to
+	 duplicate the error string since it might be allocated on the
+	 stack.  The object name is always a string constant.  */
+      size_t len_objname = strlen (objname) + 1;
+      size_t len_errstring = strlen (errstring) + 1;
+
+      char *errstring_copy = malloc (len_objname + len_errstring);
+      if (errstring_copy != NULL)
+	{
+	  /* Make a copy of the object file name and the error string.  */
+	  *lcatch->objname = memcpy (__mempcpy (errstring_copy,
+						errstring, len_errstring),
+				     objname, len_objname);
+	  *lcatch->errstring = errstring_copy;
+
+	  /* If the main executable is relocated it means the libc's malloc
+	     is used.  */
+          bool malloced = true;
+#ifdef SHARED
+	  malloced = (GL(dl_ns)[LM_ID_BASE]._ns_loaded != NULL
+                      && (GL(dl_ns)[LM_ID_BASE]._ns_loaded->l_relocated != 0));
+#endif
+	  *lcatch->malloced = malloced;
+	}
+      else
+	{
+	  /* This is better than nothing.  */
+	  *lcatch->objname = "";
+	  *lcatch->errstring = _dl_out_of_memory;
+	  *lcatch->malloced = false;
+	}
+
+      *lcatch->errcode = errcode;
+
+      /* We do not restore the signal mask because none was saved.  */
+      __longjmp (lcatch->env[0].__jmpbuf, 1);
+    }
+  else
+    {
+      /* Lossage while resolving the program's own symbols is always fatal.  */
+      char buffer[1024];
+      _dl_fatal_printf ("%s: %s: %s%s%s%s%s\n",
+			RTLD_PROGNAME,
+			occation ?: N_("error while loading shared libraries"),
+			objname, *objname ? ": " : "",
+			errstring, errcode ? ": " : "",
+			(errcode
+			 ? __strerror_r (errcode, buffer, sizeof buffer)
+			 : ""));
+    }
+}
+libc_hidden_def (_dl_signal_error)
+
+
+#if DL_ERROR_BOOTSTRAP
+void
+internal_function
+_dl_signal_cerror (int errcode, const char *objname, const char *occation,
+		   const char *errstring)
+{
+  if (__builtin_expect (GLRO(dl_debug_mask)
+			& ~(DL_DEBUG_STATISTICS|DL_DEBUG_PRELINK), 0))
+    _dl_debug_printf ("%s: error: %s: %s (%s)\n", objname, occation,
+		      errstring, receiver ? "continued" : "fatal");
+
+  if (receiver)
+    {
+      /* We are inside _dl_receive_error.  Call the user supplied
+	 handler and resume the work.  The receiver will still be
+	 installed.  */
+      (*receiver) (errcode, objname, errstring);
+    }
+  else
+    _dl_signal_error (errcode, objname, occation, errstring);
+}
+#endif /* DL_ERROR_BOOTSTRAP */
+
+
+int
+internal_function
+_dl_catch_error (const char **objname, const char **errstring,
+		 bool *mallocedp, void (*operate) (void *), void *args)
+{
+  /* We need not handle `receiver' since setting a `catch' is handled
+     before it.  */
+
+  /* Only this needs to be marked volatile, because it is the only local
+     variable that gets changed between the setjmp invocation and the
+     longjmp call.  All others are just set here (before setjmp) and read
+     in _dl_signal_error (before longjmp).  */
+  volatile int errcode;
+
+  struct catch c;
+  /* Don't use an initializer since we don't need to clear C.env.  */
+  c.objname = objname;
+  c.errstring = errstring;
+  c.malloced = mallocedp;
+  c.errcode = &errcode;
+
+  struct catch *const old = catch_hook;
+  catch_hook = &c;
+
+  /* Do not save the signal mask.  */
+  if (__builtin_expect (__sigsetjmp (c.env, 0), 0) == 0)
+    {
+      (*operate) (args);
+      catch_hook = old;
+      *objname = NULL;
+      *errstring = NULL;
+      *mallocedp = false;
+      return 0;
+    }
+
+  /* We get here only if we longjmp'd out of OPERATE.  _dl_signal_error has
+     already stored values into *OBJNAME, *ERRSTRING, and *MALLOCEDP.  */
+  catch_hook = old;
+  return errcode;
+}
+libc_hidden_def (_dl_catch_error)
+
+#if DL_ERROR_BOOTSTRAP
+void
+internal_function
+_dl_receive_error (receiver_fct fct, void (*operate) (void *), void *args)
+{
+  struct catch *old_catch = catch_hook;
+  receiver_fct old_receiver = receiver;
+
+  /* Set the new values.  */
+  catch_hook = NULL;
+  receiver = fct;
+
+  (*operate) (args);
+
+  catch_hook = old_catch;
+  receiver = old_receiver;
+}
+#endif /* DL_ERROR_BOOTSTRAP */
diff --git a/elf/dl-error.c b/elf/dl-error.c
index bd22ec6..7fe36b4 100644
--- a/elf/dl-error.c
+++ b/elf/dl-error.c
@@ -1,4 +1,4 @@ 
-/* Error handling for runtime dynamic linker.
+/* Error handling for runtime dynamic linker, full version.
    Copyright (C) 1995-2016 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
@@ -16,206 +16,12 @@ 
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
-#include <libintl.h>
-#include <setjmp.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <ldsodefs.h>
+/* This implementation lives in libc.so because it uses thread-local
+   data, which is not available in ld.so.  It interposes the version
+   in dl-error-minimal.c after ld.so bootstrap.
 
-/* This structure communicates state between _dl_catch_error and
-   _dl_signal_error.  */
-struct catch
-  {
-    const char **objname;	/* Object/File name.  */
-    const char **errstring;	/* Error detail filled in here.  */
-    bool *malloced;		/* Nonzero if the string is malloced
-				   by the libc malloc.  */
-    volatile int *errcode;	/* Return value of _dl_signal_error.  */
-    jmp_buf env;		/* longjmp here on error.  */
-  };
+   The signal/catch mechanism is used by the audit framework, which
+   means that even in ld.so, not all errors are fatal.  */
 
-/* Multiple threads at once can use the `_dl_catch_error' function.  The
-   calls can come from `_dl_map_object_deps', `_dlerror_run', or from
-   any of the libc functionality which loads dynamic objects (NSS, iconv).
-   Therefore we have to be prepared to save the state in thread-local
-   memory.  The _dl_error_catch_tsd function pointer is reset by the thread
-   library so that it returns the address of a thread-local variable.  */
-
-
-/* This message we return as a last resort.  We define the string in a
-   variable since we have to avoid freeing it and so have to enable
-   a pointer comparison.  See below and in dlfcn/dlerror.c.  */
-static const char _dl_out_of_memory[] = "out of memory";
-
-
-/* This points to a function which is called when an continuable error is
-   received.  Unlike the handling of `catch' this function may return.
-   The arguments will be the `errstring' and `objname'.
-
-   Since this functionality is not used in normal programs (only in ld.so)
-   we do not care about multi-threaded programs here.  We keep this as a
-   global variable.  */
-static receiver_fct receiver;
-
-#ifdef _LIBC_REENTRANT
-# define CATCH_HOOK	(*(struct catch **) (*GL(dl_error_catch_tsd)) ())
-#else
-static struct catch *catch_hook;
-# define CATCH_HOOK	catch_hook
-#endif
-
-void
-internal_function
-_dl_signal_error (int errcode, const char *objname, const char *occation,
-		  const char *errstring)
-{
-  struct catch *lcatch;
-
-  if (! errstring)
-    errstring = N_("DYNAMIC LINKER BUG!!!");
-
-  lcatch = CATCH_HOOK;
-  if (objname == NULL)
-    objname = "";
-  if (lcatch != NULL)
-    {
-      /* We are inside _dl_catch_error.  Return to it.  We have to
-	 duplicate the error string since it might be allocated on the
-	 stack.  The object name is always a string constant.  */
-      size_t len_objname = strlen (objname) + 1;
-      size_t len_errstring = strlen (errstring) + 1;
-
-      char *errstring_copy = malloc (len_objname + len_errstring);
-      if (errstring_copy != NULL)
-	{
-	  /* Make a copy of the object file name and the error string.  */
-	  *lcatch->objname = memcpy (__mempcpy (errstring_copy,
-						errstring, len_errstring),
-				     objname, len_objname);
-	  *lcatch->errstring = errstring_copy;
-
-	  /* If the main executable is relocated it means the libc's malloc
-	     is used.  */
-          bool malloced = true;
-#ifdef SHARED
-	  malloced = (GL(dl_ns)[LM_ID_BASE]._ns_loaded != NULL
-                      && (GL(dl_ns)[LM_ID_BASE]._ns_loaded->l_relocated != 0));
-#endif
-	  *lcatch->malloced = malloced;
-	}
-      else
-	{
-	  /* This is better than nothing.  */
-	  *lcatch->objname = "";
-	  *lcatch->errstring = _dl_out_of_memory;
-	  *lcatch->malloced = false;
-	}
-
-      *lcatch->errcode = errcode;
-
-      /* We do not restore the signal mask because none was saved.  */
-      __longjmp (lcatch->env[0].__jmpbuf, 1);
-    }
-  else
-    {
-      /* Lossage while resolving the program's own symbols is always fatal.  */
-      char buffer[1024];
-      _dl_fatal_printf ("%s: %s: %s%s%s%s%s\n",
-			RTLD_PROGNAME,
-			occation ?: N_("error while loading shared libraries"),
-			objname, *objname ? ": " : "",
-			errstring, errcode ? ": " : "",
-			(errcode
-			 ? __strerror_r (errcode, buffer, sizeof buffer)
-			 : ""));
-    }
-}
-
-
-void
-internal_function
-_dl_signal_cerror (int errcode, const char *objname, const char *occation,
-		   const char *errstring)
-{
-  if (__builtin_expect (GLRO(dl_debug_mask)
-			& ~(DL_DEBUG_STATISTICS|DL_DEBUG_PRELINK), 0))
-    _dl_debug_printf ("%s: error: %s: %s (%s)\n", objname, occation,
-		      errstring, receiver ? "continued" : "fatal");
-
-  if (receiver)
-    {
-      /* We are inside _dl_receive_error.  Call the user supplied
-	 handler and resume the work.  The receiver will still be
-	 installed.  */
-      (*receiver) (errcode, objname, errstring);
-    }
-  else
-    _dl_signal_error (errcode, objname, occation, errstring);
-}
-
-
-int
-internal_function
-_dl_catch_error (const char **objname, const char **errstring,
-		 bool *mallocedp, void (*operate) (void *), void *args)
-{
-  /* We need not handle `receiver' since setting a `catch' is handled
-     before it.  */
-
-  /* Only this needs to be marked volatile, because it is the only local
-     variable that gets changed between the setjmp invocation and the
-     longjmp call.  All others are just set here (before setjmp) and read
-     in _dl_signal_error (before longjmp).  */
-  volatile int errcode;
-
-  struct catch c;
-  /* Don't use an initializer since we don't need to clear C.env.  */
-  c.objname = objname;
-  c.errstring = errstring;
-  c.malloced = mallocedp;
-  c.errcode = &errcode;
-
-  struct catch **const catchp = &CATCH_HOOK;
-  struct catch *const old = *catchp;
-  *catchp = &c;
-
-  /* Do not save the signal mask.  */
-  if (__builtin_expect (__sigsetjmp (c.env, 0), 0) == 0)
-    {
-      (*operate) (args);
-      *catchp = old;
-      *objname = NULL;
-      *errstring = NULL;
-      *mallocedp = false;
-      return 0;
-    }
-
-  /* We get here only if we longjmp'd out of OPERATE.  _dl_signal_error has
-     already stored values into *OBJNAME, *ERRSTRING, and *MALLOCEDP.  */
-  *catchp = old;
-  return errcode;
-}
-
-
-void
-internal_function
-_dl_receive_error (receiver_fct fct, void (*operate) (void *), void *args)
-{
-  struct catch **const catchp = &CATCH_HOOK;
-  struct catch *old_catch;
-  receiver_fct old_receiver;
-
-  old_catch = *catchp;
-  old_receiver = receiver;
-
-  /* Set the new values.  */
-  *catchp = NULL;
-  receiver = fct;
-
-  (*operate) (args);
-
-  *catchp = old_catch;
-  receiver = old_receiver;
-}
+#define DL_ERROR_BOOTSTRAP 0
+#include "dl-error-skeleton.c"
diff --git a/elf/dl-libc.c b/elf/dl-libc.c
index d56de1a..dde44c8 100644
--- a/elf/dl-libc.c
+++ b/elf/dl-libc.c
@@ -43,8 +43,8 @@  dlerror_run (void (*operate) (void *), void *args)
   const char *last_errstring = NULL;
   bool malloced;
 
-  int result = (GLRO(dl_catch_error) (&objname, &last_errstring, &malloced,
-				      operate, args)
+  int result = (_dl_catch_error (&objname, &last_errstring, &malloced,
+				operate, args)
 		?: last_errstring != NULL);
 
   if (result && malloced)
diff --git a/elf/dl-sym.c b/elf/dl-sym.c
index 6431c22..e00b286 100644
--- a/elf/dl-sym.c
+++ b/elf/dl-sym.c
@@ -123,8 +123,8 @@  do_sym (void *handle, const char *name, void *who,
 	  const char *objname;
 	  const char *errstring = NULL;
 	  bool malloced;
-	  int err = GLRO(dl_catch_error) (&objname, &errstring, &malloced,
-					  call_dl_lookup, &args);
+	  int err = _dl_catch_error (&objname, &errstring, &malloced,
+				     call_dl_lookup, &args);
 
 	  THREAD_GSCOPE_RESET_FLAG ();
 
@@ -136,7 +136,7 @@  do_sym (void *handle, const char *name, void *who,
 	      if (malloced)
 		free ((char *) errstring);
 
-	      GLRO(dl_signal_error) (err, objname_dup, NULL, errstring_dup);
+	      _dl_signal_error (err, objname_dup, NULL, errstring_dup);
 	      /* NOTREACHED */
 	    }
 
@@ -150,7 +150,7 @@  do_sym (void *handle, const char *name, void *who,
 	  if (match == NULL
 	      || caller < match->l_map_start
 	      || caller >= match->l_map_end)
-	    GLRO(dl_signal_error) (0, NULL, NULL, N_("\
+	    _dl_signal_error (0, NULL, NULL, N_("\
 RTLD_NEXT used in code not dynamically loaded"));
 	}
 
diff --git a/elf/dl-tsd.c b/elf/dl-tsd.c
deleted file mode 100644
index 7181e1c..0000000
--- a/elf/dl-tsd.c
+++ /dev/null
@@ -1,53 +0,0 @@ 
-/* Thread-local data used by error handling for runtime dynamic linker.
-   Copyright (C) 2002-2016 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/>.  */
-
-#ifdef _LIBC_REENTRANT
-
-# include <ldsodefs.h>
-# include <tls.h>
-
-# ifndef SHARED
-
-/* _dl_error_catch_tsd points to this for the single-threaded case.
-   It's reset by the thread library for multithreaded programs
-   if we're not using __thread.  */
-void ** __attribute__ ((const))
-_dl_initial_error_catch_tsd (void)
-{
-  static __thread void *data;
-  return &data;
-}
-void **(*_dl_error_catch_tsd) (void) __attribute__ ((const))
-     = &_dl_initial_error_catch_tsd;
-
-# else
-
-/* libpthread sets _dl_error_catch_tsd to point to this function.
-   We define it here instead of in libpthread so that it doesn't
-   need to have a TLS segment of its own just for this one pointer.  */
-
-void ** __attribute__ ((const))
-__libc_dl_error_tsd (void)
-{
-  static __thread void *data attribute_tls_model_ie;
-  return &data;
-}
-
-# endif	/* SHARED */
-
-#endif /* _LIBC_REENTRANT */
diff --git a/elf/rtld.c b/elf/rtld.c
index 647661c..4ec25d7 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -167,8 +167,6 @@  struct rtld_global_ro _rtld_global_ro attribute_relro =
 
     /* Function pointers.  */
     ._dl_debug_printf = _dl_debug_printf,
-    ._dl_catch_error = _dl_catch_error,
-    ._dl_signal_error = _dl_signal_error,
     ._dl_mcount = _dl_mcount,
     ._dl_lookup_symbol_x = _dl_lookup_symbol_x,
     ._dl_check_caller = _dl_check_caller,
@@ -637,18 +635,6 @@  cannot allocate TLS data structures for initial thread");
   return tcbp;
 }
 
-#ifdef _LIBC_REENTRANT
-/* _dl_error_catch_tsd points to this for the single-threaded case.
-   It's reset by the thread library for multithreaded programs.  */
-void ** __attribute__ ((const))
-_dl_initial_error_catch_tsd (void)
-{
-  static void *data;
-  return &data;
-}
-#endif
-
-
 static unsigned int
 do_preload (const char *fname, struct link_map *main_map, const char *where)
 {
@@ -752,11 +738,6 @@  dl_main (const ElfW(Phdr) *phdr,
 #endif
   void *tcbp = NULL;
 
-#ifdef _LIBC_REENTRANT
-  /* Explicit initialization since the reloc would just be more work.  */
-  GL(dl_error_catch_tsd) = &_dl_initial_error_catch_tsd;
-#endif
-
   GL(dl_init_static_tls) = &_dl_nothread_init_static_tls;
 
 #if defined SHARED && defined _LIBC_REENTRANT \
diff --git a/nptl/nptl-init.c b/nptl/nptl-init.c
index bdbdfed..839e3fe 100644
--- a/nptl/nptl-init.c
+++ b/nptl/nptl-init.c
@@ -467,10 +467,6 @@  __pthread_initialize_minimal_internal (void)
   lll_unlock (__default_pthread_attr_lock, LLL_PRIVATE);
 
 #ifdef SHARED
-  /* Transfer the old value from the dynamic linker's internal location.  */
-  *__libc_dl_error_tsd () = *(*GL(dl_error_catch_tsd)) ();
-  GL(dl_error_catch_tsd) = &__libc_dl_error_tsd;
-
   /* Make __rtld_lock_{,un}lock_recursive use pthread_mutex_{,un}lock,
      keep the lock count from the ld.so implementation.  */
   GL(dl_rtld_lock_recursive) = (void *) __pthread_mutex_lock;
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index f68fdf4..288f5fe 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -357,10 +357,6 @@  struct rtld_global
   /* List of search directories.  */
   EXTERN struct r_search_path_elem *_dl_all_dirs;
 
-#ifdef _LIBC_REENTRANT
-  EXTERN void **(*_dl_error_catch_tsd) (void) __attribute__ ((const));
-#endif
-
   /* Structure describing the dynamic linker itself.  We need to
      reserve memory for the data the audit libraries need.  */
   EXTERN struct link_map _dl_rtld_map;
@@ -583,10 +579,6 @@  struct rtld_global_ro
      PLT relocations in libc.so.  */
   void (*_dl_debug_printf) (const char *, ...)
        __attribute__ ((__format__ (__printf__, 1, 2)));
-  int (internal_function *_dl_catch_error) (const char **, const char **,
-					    bool *, void (*) (void *), void *);
-  void (internal_function *_dl_signal_error) (int, const char *, const char *,
-					      const char *);
   void (*_dl_mcount) (ElfW(Addr) frompc, ElfW(Addr) selfpc);
   lookup_t (internal_function *_dl_lookup_symbol_x) (const char *,
 						     struct link_map *,
@@ -632,13 +624,6 @@  extern const ElfW(Phdr) *_dl_phdr;
 extern size_t _dl_phnum;
 #endif
 
-#if IS_IN (rtld)
-/* This is the initial value of GL(dl_error_catch_tsd).
-   A non-TLS libpthread will change it.  */
-extern void **_dl_initial_error_catch_tsd (void) __attribute__ ((const))
-     attribute_hidden;
-#endif
-
 /* This is the initial value of GL(dl_make_stack_executable_hook).
    A threads library can change it.  */
 extern int _dl_make_stack_executable (void **stack_endp) internal_function;
@@ -705,9 +690,20 @@  extern void _dl_debug_printf_c (const char *fmt, ...)
 
 /* Write a message on the specified descriptor FD.  The parameters are
    interpreted as for a `printf' call.  */
+#if IS_IN (rtld) || !defined (SHARED)
 extern void _dl_dprintf (int fd, const char *fmt, ...)
      __attribute__ ((__format__ (__printf__, 2, 3)))
      attribute_hidden;
+#else
+__attribute__ ((always_inline, __format__ (__printf__, 2, 3)))
+static inline void
+_dl_dprintf (int fd, const char *fmt, ...)
+{
+  /* Use local declaration to avoid includign <stdio.h>.  */
+  extern int __dprintf(int fd, const char *format, ...) attribute_hidden;
+  __dprintf (fd, fmt, __builtin_va_arg_pack ());
+}
+#endif
 
 /* Write a message on the specified descriptor standard output.  The
    parameters are interpreted as for a `printf' call.  */
@@ -737,13 +733,26 @@  extern void _dl_dprintf (int fd, const char *fmt, ...)
    problem.  */
 extern void _dl_signal_error (int errcode, const char *object,
 			      const char *occurred, const char *errstring)
-     internal_function __attribute__ ((__noreturn__)) attribute_hidden;
+     internal_function __attribute__ ((__noreturn__));
+libc_hidden_proto (_dl_signal_error)
 
 /* Like _dl_signal_error, but may return when called in the context of
-   _dl_receive_error.  */
+   _dl_receive_error.  This is only used during ld.so bootstrap.  In
+   static and profiled builds, this is equivalent to
+   _dl_signal_error.  */
+#if IS_IN (rtld)
 extern void _dl_signal_cerror (int errcode, const char *object,
 			       const char *occation, const char *errstring)
      internal_function attribute_hidden;
+#else
+__attribute__ ((always_inline))
+static inline void
+_dl_signal_cerror (int errcode, const char *object,
+			       const char *occation, const char *errstring)
+{
+  _dl_signal_error (errcode, object, occation, errstring);
+}
+#endif
 
 /* Call OPERATE, receiving errors from `dl_signal_cerror'.  Unlike
    `_dl_catch_error' the operation is resumed after the OPERATE
@@ -764,7 +773,8 @@  extern void _dl_receive_error (receiver_fct fct, void (*operate) (void *),
 extern int _dl_catch_error (const char **objname, const char **errstring,
 			    bool *mallocedp, void (*operate) (void *),
 			    void *args)
-     internal_function attribute_hidden;
+     internal_function;
+libc_hidden_proto (_dl_catch_error)
 
 /* Open the shared object NAME and map in its segments.
    LOADER's DT_RPATH is used in searching for NAME.
diff --git a/sysdeps/generic/localplt.data b/sysdeps/generic/localplt.data
index 1a40cf9..5cf53a4 100644
--- a/sysdeps/generic/localplt.data
+++ b/sysdeps/generic/localplt.data
@@ -15,3 +15,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/aarch64/localplt.data b/sysdeps/unix/sysv/linux/aarch64/localplt.data
index d7d6734..e431f36 100644
--- a/sysdeps/unix/sysv/linux/aarch64/localplt.data
+++ b/sysdeps/unix/sysv/linux/aarch64/localplt.data
@@ -16,3 +16,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/alpha/localplt.data b/sysdeps/unix/sysv/linux/alpha/localplt.data
index 351596c..2984393 100644
--- a/sysdeps/unix/sysv/linux/alpha/localplt.data
+++ b/sysdeps/unix/sysv/linux/alpha/localplt.data
@@ -33,3 +33,6 @@  ld.so: malloc + RELA R_ALPHA_GLOB_DAT
 ld.so: calloc + RELA R_ALPHA_GLOB_DAT
 ld.so: realloc + RELA R_ALPHA_GLOB_DAT
 ld.so: free + RELA R_ALPHA_GLOB_DAT
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error + RELA R_ALPHA_GLOB_DAT
+ld.so: _dl_catch_error + RELA R_ALPHA_GLOB_DAT
diff --git a/sysdeps/unix/sysv/linux/arm/localplt.data b/sysdeps/unix/sysv/linux/arm/localplt.data
index 4301703..a5ccd7f 100644
--- a/sysdeps/unix/sysv/linux/arm/localplt.data
+++ b/sysdeps/unix/sysv/linux/arm/localplt.data
@@ -16,3 +16,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/hppa/localplt.data b/sysdeps/unix/sysv/linux/hppa/localplt.data
index 2a25ebc..fea8c9c 100644
--- a/sysdeps/unix/sysv/linux/hppa/localplt.data
+++ b/sysdeps/unix/sysv/linux/hppa/localplt.data
@@ -21,3 +21,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/i386/localplt.data b/sysdeps/unix/sysv/linux/i386/localplt.data
index 4ce8447..48bcc42 100644
--- a/sysdeps/unix/sysv/linux/i386/localplt.data
+++ b/sysdeps/unix/sysv/linux/i386/localplt.data
@@ -15,3 +15,6 @@  ld.so: malloc + REL R_386_GLOB_DAT
 ld.so: calloc + REL R_386_GLOB_DAT
 ld.so: realloc + REL R_386_GLOB_DAT
 ld.so: free + REL R_386_GLOB_DAT
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error + REL R_386_GLOB_DAT
+ld.so: _dl_catch_error + REL R_386_GLOB_DAT
diff --git a/sysdeps/unix/sysv/linux/ia64/localplt.data b/sysdeps/unix/sysv/linux/ia64/localplt.data
index b628ca4..df63530 100644
--- a/sysdeps/unix/sysv/linux/ia64/localplt.data
+++ b/sysdeps/unix/sysv/linux/ia64/localplt.data
@@ -14,3 +14,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/m68k/localplt.data b/sysdeps/unix/sysv/linux/m68k/localplt.data
index 88124c4..abfbd34 100644
--- a/sysdeps/unix/sysv/linux/m68k/localplt.data
+++ b/sysdeps/unix/sysv/linux/m68k/localplt.data
@@ -14,3 +14,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/microblaze/localplt.data b/sysdeps/unix/sysv/linux/microblaze/localplt.data
index 176d618..697fdd0 100644
--- a/sysdeps/unix/sysv/linux/microblaze/localplt.data
+++ b/sysdeps/unix/sysv/linux/microblaze/localplt.data
@@ -15,3 +15,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/nios2/localplt.data b/sysdeps/unix/sysv/linux/nios2/localplt.data
index 6cf93cc..a7d774d 100644
--- a/sysdeps/unix/sysv/linux/nios2/localplt.data
+++ b/sysdeps/unix/sysv/linux/nios2/localplt.data
@@ -35,3 +35,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/localplt.data b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/localplt.data
index b25abf8..4ef5bf4 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/localplt.data
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/localplt.data
@@ -13,3 +13,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/localplt.data b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/localplt.data
index 8c4e65d..c919426 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/localplt.data
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/localplt.data
@@ -43,3 +43,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/localplt.data b/sysdeps/unix/sysv/linux/powerpc/powerpc64/localplt.data
index 49d5de6..2d43472 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/localplt.data
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/localplt.data
@@ -12,3 +12,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/s390/localplt.data b/sysdeps/unix/sysv/linux/s390/localplt.data
index 1226413..bd1addd 100644
--- a/sysdeps/unix/sysv/linux/s390/localplt.data
+++ b/sysdeps/unix/sysv/linux/s390/localplt.data
@@ -20,3 +20,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/sh/localplt.data b/sysdeps/unix/sysv/linux/sh/localplt.data
index 57f31c5..e6fb930 100644
--- a/sysdeps/unix/sysv/linux/sh/localplt.data
+++ b/sysdeps/unix/sysv/linux/sh/localplt.data
@@ -18,3 +18,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/localplt.data b/sysdeps/unix/sysv/linux/sparc/sparc32/localplt.data
index a208adf..d5b5895 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc32/localplt.data
+++ b/sysdeps/unix/sysv/linux/sparc/sparc32/localplt.data
@@ -24,3 +24,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc64/localplt.data b/sysdeps/unix/sysv/linux/sparc/sparc64/localplt.data
index 2323551..edceab5 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc64/localplt.data
+++ b/sysdeps/unix/sysv/linux/sparc/sparc64/localplt.data
@@ -26,3 +26,6 @@  ld.so: malloc
 ld.so: calloc
 ld.so: realloc
 ld.so: free
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error
+ld.so: _dl_catch_error
diff --git a/sysdeps/x86_64/localplt.data b/sysdeps/x86_64/localplt.data
index f168b14..28096f8 100644
--- a/sysdeps/x86_64/localplt.data
+++ b/sysdeps/x86_64/localplt.data
@@ -17,3 +17,6 @@  ld.so: malloc + RELA R_X86_64_GLOB_DAT
 ld.so: calloc + RELA R_X86_64_GLOB_DAT
 ld.so: realloc + RELA R_X86_64_GLOB_DAT
 ld.so: free + RELA R_X86_64_GLOB_DAT
+# The TLS-enabled version of these functions is interposed from libc.so.
+ld.so: _dl_signal_error + RELA R_X86_64_GLOB_DAT
+ld.so: _dl_catch_error + RELA R_X86_64_GLOB_DAT