about summary refs log tree commit diff
path: root/elf
diff options
context:
space:
mode:
Diffstat (limited to 'elf')
-rw-r--r--elf/Makefile16
-rw-r--r--elf/cache.c2
-rw-r--r--elf/dl-close.c10
-rw-r--r--elf/dl-find_object.c8
-rw-r--r--elf/dl-load.c6
-rw-r--r--elf/dl-open.c44
-rw-r--r--elf/dl-reloc.c32
-rw-r--r--elf/dl-support.c3
-rw-r--r--elf/libc_early_init.c3
-rw-r--r--elf/rtld.c107
-rw-r--r--elf/tst-dlopen-auditdup-auditmod.c104
-rw-r--r--elf/tst-dlopen-auditdup.c36
-rw-r--r--elf/tst-dlopen-auditdupmod.c48
-rw-r--r--elf/tst-rtld-no-malloc-audit.c1
-rw-r--r--elf/tst-rtld-no-malloc-preload.c1
-rw-r--r--elf/tst-rtld-no-malloc.c76
16 files changed, 396 insertions, 101 deletions
diff --git a/elf/Makefile b/elf/Makefile
index 09d77093a7..3a1cb72955 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -414,6 +414,7 @@ tests += \
   tst-dlmopen1 \
   tst-dlmopen3 \
   tst-dlmopen4 \
+  tst-dlopen-auditdup \
   tst-dlopen-self \
   tst-dlopen-tlsmodid \
   tst-dlopen-tlsreinit1 \
@@ -452,6 +453,9 @@ tests += \
   tst-recursive-tls \
   tst-relsort1 \
   tst-ro-dynamic \
+  tst-rtld-no-malloc \
+  tst-rtld-no-malloc-audit \
+  tst-rtld-no-malloc-preload \
   tst-rtld-run-static \
   tst-single_threaded \
   tst-single_threaded-pthread \
@@ -865,6 +869,8 @@ modules-names += \
   tst-dlmopen-twice-mod1 \
   tst-dlmopen-twice-mod2 \
   tst-dlmopen1mod \
+  tst-dlopen-auditdup-auditmod \
+  tst-dlopen-auditdupmod \
   tst-dlopen-tlsreinitmod1 \
   tst-dlopen-tlsreinitmod2 \
   tst-dlopen-tlsreinitmod3 \
@@ -3153,3 +3159,13 @@ $(objpfx)tst-dlopen-tlsreinit3.out: $(objpfx)tst-auditmod1.so
 tst-dlopen-tlsreinit3-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
 $(objpfx)tst-dlopen-tlsreinit4.out: $(objpfx)tst-auditmod1.so
 tst-dlopen-tlsreinit4-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
+
+tst-dlopen-auditdup-ENV = LD_AUDIT=$(objpfx)tst-dlopen-auditdup-auditmod.so
+$(objpfx)tst-dlopen-auditdup.out: \
+  $(objpfx)tst-dlopen-auditdupmod.so $(objpfx)tst-dlopen-auditdup-auditmod.so
+
+# Reuse an audit module which provides ample debug logging.
+tst-rtld-no-malloc-audit-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
+
+# Any shared object should do.
+tst-rtld-no-malloc-preload-ENV = LD_PRELOAD=$(objpfx)tst-auditmod1.so
diff --git a/elf/cache.c b/elf/cache.c
index 8a618e11fa..62d681df42 100644
--- a/elf/cache.c
+++ b/elf/cache.c
@@ -820,7 +820,7 @@ struct aux_cache_entry
   struct aux_cache_entry *next;
 };
 
-#define AUX_CACHEMAGIC		"glibc-ld.so.auxcache-1.0"
+#define AUX_CACHEMAGIC		"glibc-ld.so.auxcache-2.0"
 
 struct aux_cache_file_entry
 {
diff --git a/elf/dl-close.c b/elf/dl-close.c
index 88226245eb..b6f4daac79 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -723,6 +723,11 @@ _dl_close_worker (struct link_map *map, bool force)
   /* TLS is cleaned up for the unloaded modules.  */
   __rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
 
+  /* Notify the debugger those objects are finalized and gone.  */
+  r->r_state = RT_CONSISTENT;
+  _dl_debug_state ();
+  LIBC_PROBE (unmap_complete, 2, nsid, r);
+
 #ifdef SHARED
   /* Auditing checkpoint: we have deleted all objects.  Also, do not notify
      auditors of the cleanup of a failed audit module loading attempt.  */
@@ -735,11 +740,6 @@ _dl_close_worker (struct link_map *map, bool force)
       --GL(dl_nns);
     while (GL(dl_ns)[GL(dl_nns) - 1]._ns_loaded == NULL);
 
-  /* Notify the debugger those objects are finalized and gone.  */
-  r->r_state = RT_CONSISTENT;
-  _dl_debug_state ();
-  LIBC_PROBE (unmap_complete, 2, nsid, r);
-
   /* Recheck if we need to retry, release the lock.  */
  out:
   if (dl_close_state == rerun)
diff --git a/elf/dl-find_object.c b/elf/dl-find_object.c
index 449302eda3..ae18b438d3 100644
--- a/elf/dl-find_object.c
+++ b/elf/dl-find_object.c
@@ -662,6 +662,14 @@ _dl_find_object_update_1 (struct link_map **loaded, size_t count)
     = _dlfo_loaded_mappings[!active_idx];
   size_t remaining_to_add = current_used + count;
 
+  /* remaining_to_add can be 0 if (current_used + count) wraps, but in practice
+     this is not possible as it represent counts of link maps.  Link maps have
+     sizes larger than 1 byte, so the sum of any two link map counts will
+     always fit within a size_t without wrapping around.  This check ensures
+     that target_seg is not erroneously considered potentially NULL by GCC. */
+  if (remaining_to_add == 0)
+    __builtin_unreachable ();
+
   /* Ensure that the new segment chain has enough space.  */
   {
     size_t new_allocated
diff --git a/elf/dl-load.c b/elf/dl-load.c
index ac8e217a7f..335b34c56d 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -1931,6 +1931,9 @@ _dl_map_object (struct link_map *loader, const char *name,
 		      : "\nfile=%s [%lu];  dynamically loaded by %s [%lu]\n",
 		      name, nsid, DSO_FILENAME (loader->l_name), loader->l_ns);
 
+  /* Will be true if we found a DSO which is of the other ELF class.  */
+  bool found_other_class = false;
+
 #ifdef SHARED
   /* Give the auditing libraries a chance to change the name before we
      try anything.  */
@@ -1948,9 +1951,6 @@ _dl_map_object (struct link_map *loader, const char *name,
     }
 #endif
 
-  /* Will be true if we found a DSO which is of the other ELF class.  */
-  bool found_other_class = false;
-
   if (strchr (name, '/') == NULL)
     {
       /* Search for NAME in several places.  */
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 8b4704c09d..ba3c266e6a 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -565,6 +565,14 @@ dl_open_worker_begin (void *a)
 	_dl_debug_printf ("opening file=%s [%lu]; direct_opencount=%u\n\n",
 			  new->l_name, new->l_ns, new->l_direct_opencount);
 
+#ifdef SHARED
+      /* No relocation processing on this execution path.  But
+	 relocation has not been performed for static
+	 position-dependent executables, so disable the assert for
+	 static linking.  */
+      assert (new->l_relocated);
+#endif
+
       /* If the user requested the object to be in the global
 	 namespace but it is not so far, prepare to add it now.  This
 	 can raise an exception to do a malloc failure.  */
@@ -586,10 +594,6 @@ dl_open_worker_begin (void *a)
       if ((mode & RTLD_GLOBAL) && new->l_global == 0)
 	add_to_global_update (new);
 
-      const int r_state __attribute__ ((unused))
-        = _dl_debug_update (args->nsid)->r_state;
-      assert (r_state == RT_CONSISTENT);
-
       return;
     }
 
@@ -620,17 +624,6 @@ dl_open_worker_begin (void *a)
 #endif
       }
 
-#ifdef SHARED
-  /* Auditing checkpoint: we have added all objects.  */
-  _dl_audit_activity_nsid (new->l_ns, LA_ACT_CONSISTENT);
-#endif
-
-  /* Notify the debugger all new objects are now ready to go.  */
-  struct r_debug *r = _dl_debug_update (args->nsid);
-  r->r_state = RT_CONSISTENT;
-  _dl_debug_state ();
-  LIBC_PROBE (map_complete, 3, args->nsid, r, new);
-
   _dl_open_check (new);
 
   /* Print scope information.  */
@@ -677,6 +670,7 @@ dl_open_worker_begin (void *a)
      created dlmopen namespaces.  Do not do this for static dlopen
      because libc has relocations against ld.so, which may not have
      been relocated at this point.  */
+  struct r_debug *r = _dl_debug_update (args->nsid);
 #ifdef SHARED
   if (GL(dl_ns)[args->nsid].libc_map != NULL)
     _dl_open_relocate_one_object (args, r, GL(dl_ns)[args->nsid].libc_map,
@@ -768,6 +762,26 @@ dl_open_worker (void *a)
 
     __rtld_lock_unlock_recursive (GL(dl_load_tls_lock));
 
+    /* Auditing checkpoint and debugger signalling.  Do this even on
+       error, so that dlopen exists with consistent state.  */
+    if (args->nsid >= 0 || args->map != NULL)
+      {
+	Lmid_t nsid = args->map != NULL ? args->map->l_ns : args->nsid;
+	struct r_debug *r = _dl_debug_update (nsid);
+#ifdef SHARED
+	bool was_not_consistent  = r->r_state != RT_CONSISTENT;
+#endif
+	r->r_state = RT_CONSISTENT;
+	_dl_debug_state ();
+	LIBC_PROBE (map_complete, 3, nsid, r, args->map);
+
+#ifdef SHARED
+	if (was_not_consistent)
+	  /* Avoid redudant/recursive signalling.  */
+	  _dl_audit_activity_nsid (nsid, LA_ACT_CONSISTENT);
+#endif
+      }
+
     if (__glibc_unlikely (ex.errstring != NULL))
       /* Reraise the error.  */
       _dl_signal_exception (err, &ex, NULL);
diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c
index 4bf7aec88b..76d14830dd 100644
--- a/elf/dl-reloc.c
+++ b/elf/dl-reloc.c
@@ -202,12 +202,9 @@ resolve_map (lookup_t l, struct r_scope_elem *scope[], const ElfW(Sym) **ref,
 #include "dynamic-link.h"
 
 void
-_dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
-		     int reloc_mode, int consider_profiling)
+_dl_relocate_object_no_relro (struct link_map *l, struct r_scope_elem *scope[],
+			      int reloc_mode, int consider_profiling)
 {
-  if (l->l_relocated)
-    return;
-
   struct textrels
   {
     caddr_t start;
@@ -220,8 +217,8 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
   int lazy = reloc_mode & RTLD_LAZY;
   int skip_ifunc = reloc_mode & __RTLD_NOIFUNC;
 
-#ifdef SHARED
   bool consider_symbind = false;
+#ifdef SHARED
   /* If we are auditing, install the same handlers we need for profiling.  */
   if ((reloc_mode & __RTLD_AUDIT) == 0)
     {
@@ -240,9 +237,7 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
     }
 #elif defined PROF
   /* Never use dynamic linker profiling for gprof profiling code.  */
-# define consider_profiling 0
-#else
-# define consider_symbind 0
+  consider_profiling = 0;
 #endif
 
   /* If DT_BIND_NOW is set relocate all references in this object.  We
@@ -300,7 +295,6 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
 
     ELF_DYNAMIC_RELOCATE (l, scope, lazy, consider_profiling, skip_ifunc);
 
-#ifndef PROF
     if ((consider_profiling || consider_symbind)
 	&& l->l_info[DT_PLTRELSZ] != NULL)
       {
@@ -321,7 +315,6 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
 	    _dl_fatal_printf (errstring, RTLD_PROGNAME, l->l_name);
 	  }
       }
-#endif
   }
 
   /* Mark the object so we know this work has been done.  */
@@ -342,17 +335,24 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
 
       textrels = textrels->next;
     }
-
-  /* In case we can protect the data now that the relocations are
-     done, do it.  */
-  if (l->l_relro_size != 0)
-    _dl_protect_relro (l);
 }
 
+void
+_dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
+		     int reloc_mode, int consider_profiling)
+{
+  if (l->l_relocated)
+    return;
+  _dl_relocate_object_no_relro (l, scope, reloc_mode, consider_profiling);
+  _dl_protect_relro (l);
+}
 
 void
 _dl_protect_relro (struct link_map *l)
 {
+  if (l->l_relro_size == 0)
+    return;
+
   ElfW(Addr) start = ALIGN_DOWN((l->l_addr
 				 + l->l_relro_addr),
 				GLRO(dl_pagesize));
diff --git a/elf/dl-support.c b/elf/dl-support.c
index 451932dd03..ee590edf93 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -338,8 +338,7 @@ _dl_non_dynamic_init (void)
   call_function_static_weak (_dl_find_object_init);
 
   /* Setup relro on the binary itself.  */
-  if (_dl_main_map.l_relro_size != 0)
-    _dl_protect_relro (&_dl_main_map);
+  _dl_protect_relro (&_dl_main_map);
 }
 
 #ifdef DL_SYSINFO_IMPLEMENTATION
diff --git a/elf/libc_early_init.c b/elf/libc_early_init.c
index 575b837f8f..20c71fd48b 100644
--- a/elf/libc_early_init.c
+++ b/elf/libc_early_init.c
@@ -23,6 +23,7 @@
 #include <lowlevellock.h>
 #include <pthread_early_init.h>
 #include <sys/single_threaded.h>
+#include <getrandom-internal.h>
 
 #ifdef SHARED
 _Bool __libc_initial;
@@ -43,6 +44,8 @@ __libc_early_init (_Bool initial)
 
   __pthread_early_init ();
 
+  __getrandom_early_init (initial);
+
 #if ENABLE_ELISION_SUPPORT
   __lll_elision_init ();
 #endif
diff --git a/elf/rtld.c b/elf/rtld.c
index cb6b61d570..b8cc3f605f 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1963,43 +1963,37 @@ dl_main (const ElfW(Phdr) *phdr,
     if (main_map->l_searchlist.r_list[i] == &GL(dl_rtld_map))
       break;
 
-  bool rtld_multiple_ref = false;
-  if (__glibc_likely (i < main_map->l_searchlist.r_nlist))
-    {
-      /* Some DT_NEEDED entry referred to the interpreter object itself, so
-	 put it back in the list of visible objects.  We insert it into the
-	 chain in symbol search order because gdb uses the chain's order as
-	 its symbol search order.  */
-      rtld_multiple_ref = true;
+  /* Insert the link map for the dynamic loader into the chain in
+     symbol search order because gdb uses the chain's order as its
+     symbol search order.  */
 
-      GL(dl_rtld_map).l_prev = main_map->l_searchlist.r_list[i - 1];
-      if (__glibc_likely (state.mode == rtld_mode_normal))
-	{
-	  GL(dl_rtld_map).l_next = (i + 1 < main_map->l_searchlist.r_nlist
-				    ? main_map->l_searchlist.r_list[i + 1]
-				    : NULL);
+  GL(dl_rtld_map).l_prev = main_map->l_searchlist.r_list[i - 1];
+  if (__glibc_likely (state.mode == rtld_mode_normal))
+    {
+      GL(dl_rtld_map).l_next = (i + 1 < main_map->l_searchlist.r_nlist
+				? main_map->l_searchlist.r_list[i + 1]
+				: NULL);
 #ifdef NEED_DL_SYSINFO_DSO
-	  if (GLRO(dl_sysinfo_map) != NULL
-	      && GL(dl_rtld_map).l_prev->l_next == GLRO(dl_sysinfo_map)
-	      && GL(dl_rtld_map).l_next != GLRO(dl_sysinfo_map))
-	    GL(dl_rtld_map).l_prev = GLRO(dl_sysinfo_map);
+      if (GLRO(dl_sysinfo_map) != NULL
+	  && GL(dl_rtld_map).l_prev->l_next == GLRO(dl_sysinfo_map)
+	  && GL(dl_rtld_map).l_next != GLRO(dl_sysinfo_map))
+	GL(dl_rtld_map).l_prev = GLRO(dl_sysinfo_map);
 #endif
-	}
-      else
-	/* In trace mode there might be an invisible object (which we
-	   could not find) after the previous one in the search list.
-	   In this case it doesn't matter much where we put the
-	   interpreter object, so we just initialize the list pointer so
-	   that the assertion below holds.  */
-	GL(dl_rtld_map).l_next = GL(dl_rtld_map).l_prev->l_next;
-
-      assert (GL(dl_rtld_map).l_prev->l_next == GL(dl_rtld_map).l_next);
-      GL(dl_rtld_map).l_prev->l_next = &GL(dl_rtld_map);
-      if (GL(dl_rtld_map).l_next != NULL)
-	{
-	  assert (GL(dl_rtld_map).l_next->l_prev == GL(dl_rtld_map).l_prev);
-	  GL(dl_rtld_map).l_next->l_prev = &GL(dl_rtld_map);
-	}
+    }
+  else
+    /* In trace mode there might be an invisible object (which we
+       could not find) after the previous one in the search list.
+       In this case it doesn't matter much where we put the
+       interpreter object, so we just initialize the list pointer so
+       that the assertion below holds.  */
+    GL(dl_rtld_map).l_next = GL(dl_rtld_map).l_prev->l_next;
+
+  assert (GL(dl_rtld_map).l_prev->l_next == GL(dl_rtld_map).l_next);
+  GL(dl_rtld_map).l_prev->l_next = &GL(dl_rtld_map);
+  if (GL(dl_rtld_map).l_next != NULL)
+    {
+      assert (GL(dl_rtld_map).l_next->l_prev == GL(dl_rtld_map).l_prev);
+      GL(dl_rtld_map).l_next->l_prev = &GL(dl_rtld_map);
     }
 
   /* Now let us see whether all libraries are available in the
@@ -2327,35 +2321,30 @@ dl_main (const ElfW(Phdr) *phdr,
   /* Make sure no new search directories have been added.  */
   assert (GLRO(dl_init_all_dirs) == GL(dl_all_dirs));
 
-  if (rtld_multiple_ref)
-    {
-      /* There was an explicit ref to the dynamic linker as a shared lib.
-	 Re-relocate ourselves with user-controlled symbol definitions.
+  /* Set up the object lookup structures.  */
+  _dl_find_object_init ();
 
-	 We must do this after TLS initialization in case after this
-	 re-relocation, we might call a user-supplied function
-	 (e.g. calloc from _dl_relocate_object) that uses TLS data.  */
+  /* Likewise for the locking implementation.  */
+  __rtld_mutex_init ();
 
-      /* Set up the object lookup structures.  */
-      _dl_find_object_init ();
+  /* Re-relocate ourselves with user-controlled symbol definitions.  */
 
-      /* The malloc implementation has been relocated, so resolving
-	 its symbols (and potentially calling IFUNC resolvers) is safe
-	 at this point.  */
-      __rtld_malloc_init_real (main_map);
+  {
+    RTLD_TIMING_VAR (start);
+    rtld_timer_start (&start);
 
-      /* Likewise for the locking implementation.  */
-      __rtld_mutex_init ();
+    _dl_relocate_object_no_relro (&GL(dl_rtld_map), main_map->l_scope, 0, 0);
 
-      RTLD_TIMING_VAR (start);
-      rtld_timer_start (&start);
+    /* The malloc implementation has been relocated, so resolving
+       its symbols (and potentially calling IFUNC resolvers) is safe
+       at this point.  */
+    __rtld_malloc_init_real (main_map);
 
-      /* Mark the link map as not yet relocated again.  */
-      GL(dl_rtld_map).l_relocated = 0;
-      _dl_relocate_object (&GL(dl_rtld_map), main_map->l_scope, 0, 0);
+    if (GL(dl_rtld_map).l_relro_size != 0)
+      _dl_protect_relro (&GL(dl_rtld_map));
 
-      rtld_timer_accum (&relocate_time, start);
-    }
+    rtld_timer_accum (&relocate_time, start);
+  }
 
   /* Relocation is complete.  Perform early libc initialization.  This
      is the initial libc, even if audit modules have been loaded with
@@ -2369,9 +2358,6 @@ dl_main (const ElfW(Phdr) *phdr,
      _dl_relocate_object might need to call `mprotect' for DT_TEXTREL.  */
   _dl_sysdep_start_cleanup ();
 
-  /* Auditing checkpoint: we have added all objects.  */
-  _dl_audit_activity_nsid (LM_ID_BASE, LA_ACT_CONSISTENT);
-
   /* Notify the debugger all new objects are now ready to go.  We must re-get
      the address since by now the variable might be in another object.  */
   r = _dl_debug_update (LM_ID_BASE);
@@ -2379,6 +2365,9 @@ dl_main (const ElfW(Phdr) *phdr,
   _dl_debug_state ();
   LIBC_PROBE (init_complete, 2, LM_ID_BASE, r);
 
+  /* Auditing checkpoint: we have added all objects.  */
+  _dl_audit_activity_nsid (LM_ID_BASE, LA_ACT_CONSISTENT);
+
 #if defined USE_LDCONFIG && !defined MAP_COPY
   /* We must munmap() the cache file.  */
   _dl_unload_cache ();
diff --git a/elf/tst-dlopen-auditdup-auditmod.c b/elf/tst-dlopen-auditdup-auditmod.c
new file mode 100644
index 0000000000..270a595ec4
--- /dev/null
+++ b/elf/tst-dlopen-auditdup-auditmod.c
@@ -0,0 +1,104 @@
+/* Auditor that opens again an object that just has been opened.
+   Copyright (C) 2024 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 <dlfcn.h>
+#include <link.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+unsigned int
+la_version (unsigned int v)
+{
+  return LAV_CURRENT;
+}
+
+static bool trigger_on_la_activity;
+
+unsigned int
+la_objopen (struct link_map *map, Lmid_t lmid, uintptr_t *cookie)
+{
+  printf ("info: la_objopen: \"%s\"\n", map->l_name);
+  if (strstr (map->l_name, "/tst-dlopen-auditdupmod.so") != NULL)
+    trigger_on_la_activity = true;
+  return 0;
+}
+
+void
+la_activity (uintptr_t *cookie, unsigned int flag)
+{
+  static unsigned int calls;
+  ++calls;
+  printf ("info: la_activity: call %u (flag %u)\n", calls, flag);
+  fflush (stdout);
+  if (trigger_on_la_activity)
+    {
+      /* Avoid triggering on the dlmopen call below.  */
+      static bool recursion;
+      if (recursion)
+        return;
+      recursion = true;
+
+      puts ("info: about to dlmopen tst-dlopen-auditdupmod.so");
+      fflush (stdout);
+      void *handle = dlmopen (LM_ID_BASE, "tst-dlopen-auditdupmod.so",
+                              RTLD_NOW);
+      if (handle == NULL)
+        {
+          printf ("error: dlmopen: %s\n", dlerror ());
+          fflush (stdout);
+          _exit (1);
+        }
+
+      /* Check that the constructor has not run.  Running the
+         constructor would require constructing its dependencies, but
+         the constructor call that triggered this auditing activity
+         has not completed, and constructors among the dependencies
+         may not be able to deal with that.  */
+      int *status = dlsym (handle, "auditdupmod_status");
+      if (status == NULL)
+        {
+          printf ("error: dlsym: %s\n", dlerror ());
+          fflush (stdout);
+          _exit (1);
+        }
+      printf ("info: auditdupmod_status == %d\n", *status);
+      if (*status != 0)
+        {
+          puts ("error: auditdupmod_status == 0 expected");
+          fflush (stdout);
+          _exit (1);
+        }
+      /* Checked in the destructor and the main program.  */
+      ++*status;
+      printf ("info: auditdupmod_status == %d\n", *status);
+
+      /* Check that the module has been relocated.  */
+      int **status_address = dlsym (handle, "auditdupmod_status_address");
+      if (status_address == NULL || *status_address != status)
+        {
+          puts ("error: invalid auditdupmod_status address in"
+                " tst-dlopen-auditdupmod.so");
+          fflush (stdout);
+          _exit (1);
+        }
+
+      fflush (stdout);
+    }
+}
diff --git a/elf/tst-dlopen-auditdup.c b/elf/tst-dlopen-auditdup.c
new file mode 100644
index 0000000000..d022c58ae3
--- /dev/null
+++ b/elf/tst-dlopen-auditdup.c
@@ -0,0 +1,36 @@
+/* Test that recursive dlopen from auditor works (bug 31986).
+   Copyright (C) 2024 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 <stdio.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+
+static int
+do_test (void)
+{
+  puts ("info: about to dlopen tst-dlopen-auditdupmod.so");
+  fflush (stdout);
+  void *handle = xdlopen ("tst-dlopen-auditdupmod.so", RTLD_NOW);
+  int *status = xdlsym (handle, "auditdupmod_status");
+  printf ("info: auditdupmod_status == %d (from main)\n", *status);
+  TEST_COMPARE (*status, 2);
+  xdlclose (handle);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-dlopen-auditdupmod.c b/elf/tst-dlopen-auditdupmod.c
new file mode 100644
index 0000000000..59b7e21daa
--- /dev/null
+++ b/elf/tst-dlopen-auditdupmod.c
@@ -0,0 +1,48 @@
+/* Directly opened test module that gets reopened from the auditor.
+   Copyright (C) 2024 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 <stdio.h>
+#include <stdlib.h>
+#include <support/xdlfcn.h>
+
+int auditdupmod_status;
+
+/* Used to check for successful relocation processing.  */
+int *auditdupmod_status_address = &auditdupmod_status;
+
+static void __attribute__ ((constructor))
+init (void)
+{
+  ++auditdupmod_status;
+  printf ("info: tst-dlopen-auditdupmod.so constructor called (status %d)\n",
+          auditdupmod_status);
+}
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+  /* The tst-dlopen-auditdup-auditmod.so auditor incremented
+     auditdupmod_status.  */
+  printf ("info: tst-dlopen-auditdupmod.so destructor called (status %d)\n",
+          auditdupmod_status);
+  if (auditdupmod_status != 2)
+    {
+      puts ("error: auditdupmod_status == 2 expected");
+      exit (1);
+    }
+}
diff --git a/elf/tst-rtld-no-malloc-audit.c b/elf/tst-rtld-no-malloc-audit.c
new file mode 100644
index 0000000000..a028377ad1
--- /dev/null
+++ b/elf/tst-rtld-no-malloc-audit.c
@@ -0,0 +1 @@
+#include "tst-rtld-no-malloc.c"
diff --git a/elf/tst-rtld-no-malloc-preload.c b/elf/tst-rtld-no-malloc-preload.c
new file mode 100644
index 0000000000..a028377ad1
--- /dev/null
+++ b/elf/tst-rtld-no-malloc-preload.c
@@ -0,0 +1 @@
+#include "tst-rtld-no-malloc.c"
diff --git a/elf/tst-rtld-no-malloc.c b/elf/tst-rtld-no-malloc.c
new file mode 100644
index 0000000000..5f24d4bd72
--- /dev/null
+++ b/elf/tst-rtld-no-malloc.c
@@ -0,0 +1,76 @@
+/* Test that program loading does not call malloc.
+   Copyright (C) 2024 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 <string.h>
+#include <unistd.h>
+
+static void
+print (const char *s)
+{
+  const char *end = s + strlen (s);
+  while (s < end)
+    {
+      ssize_t ret = write (STDOUT_FILENO, s, end - s);
+      if (ret <= 0)
+        _exit (2);
+      s += ret;
+    }
+}
+
+static void __attribute__ ((noreturn))
+unexpected_call (const char *function)
+{
+  print ("error: unexpected call to ");
+  print (function);
+  print ("\n");
+  _exit (1);
+}
+
+/* These are the malloc functions implement in elf/dl-minimal.c.  */
+
+void
+free (void *ignored)
+{
+  unexpected_call ("free");
+}
+
+void *
+calloc (size_t ignored1, size_t ignored2)
+{
+  unexpected_call ("calloc");
+}
+
+void *
+malloc (size_t ignored)
+{
+  unexpected_call ("malloc");
+}
+
+void *
+realloc (void *ignored1, size_t ignored2)
+{
+  unexpected_call ("realloc");
+}
+
+int
+main (void)
+{
+  /* Do not use the test wrapper, to avoid spurious malloc calls from it.  */
+  return 0;
+}