about summary refs log tree commit diff
path: root/elf
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2020-02-08 19:58:43 +0100
committerFlorian Weimer <fweimer@redhat.com>2020-02-15 11:01:23 +0100
commit3a0ecccb599a6b1ad4b149dc569c0080e92d057b (patch)
treee1c4c0e5f2e80221054d6bb6260b4038e27567b4 /elf
parent2efa52c880d46ee89523c8ed8102ceeb02043926 (diff)
downloadglibc-3a0ecccb599a6b1ad4b149dc569c0080e92d057b.tar.gz
glibc-3a0ecccb599a6b1ad4b149dc569c0080e92d057b.tar.xz
glibc-3a0ecccb599a6b1ad4b149dc569c0080e92d057b.zip
ld.so: Do not export free/calloc/malloc/realloc functions [BZ #25486]
Exporting functions and relying on symbol interposition from libc.so
makes the choice of implementation dependent on DT_NEEDED order, which
is not what some compiler drivers expect.

This commit replaces one magic mechanism (symbol interposition) with
another one (preprocessor-/compiler-based redirection).  This makes
the hand-over from the minimal malloc to the full malloc more
explicit.

Removing the ABI symbols is backwards-compatible because libc.so is
always in scope, and the dynamic loader will find the malloc-related
symbols there since commit f0b2132b35248c1f4a80f62a2c38cddcc802aa8c
("ld.so: Support moving versioned symbols between sonames
[BZ #24741]").

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Diffstat (limited to 'elf')
-rw-r--r--elf/Makefile6
-rw-r--r--elf/Versions3
-rw-r--r--elf/dl-lookup.c4
-rw-r--r--elf/dl-minimal.c97
-rw-r--r--elf/rtld.c12
5 files changed, 103 insertions, 19 deletions
diff --git a/elf/Makefile b/elf/Makefile
index ffc34a67d4..a137143db7 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -488,7 +488,11 @@ $(objpfx)dl-allobjs.os: $(all-rtld-routines:%=$(objpfx)%.os)
 # their implementation is provided differently in rtld, and the symbol
 # discovery mechanism is not compatible with the libc implementation
 # when compiled for libc.
-rtld-stubbed-symbols =
+rtld-stubbed-symbols = \
+  calloc \
+  free \
+  malloc \
+  realloc \
 
 # The GCC arguments that implement $(rtld-stubbed-symbols).
 rtld-stubbed-symbols-args = \
diff --git a/elf/Versions b/elf/Versions
index 3b09901f6c..705489fc51 100644
--- a/elf/Versions
+++ b/elf/Versions
@@ -35,9 +35,6 @@ libc {
 
 ld {
   GLIBC_2.0 {
-    # Functions which are interposed from libc.so.
-    calloc; free; malloc; realloc;
-
     _r_debug;
   }
   GLIBC_2.1 {
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
index 378f28fa7d..12a229f06c 100644
--- a/elf/dl-lookup.c
+++ b/elf/dl-lookup.c
@@ -291,7 +291,7 @@ do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
 	  tab->size = newsize;
 	  size = newsize;
 	  entries = tab->entries = newentries;
-	  tab->free = free;
+	  tab->free = __rtld_free;
 	}
     }
   else
@@ -322,7 +322,7 @@ do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
 
       tab->entries = entries;
       tab->size = size;
-      tab->free = free;
+      tab->free = __rtld_free;
     }
 
   if ((type_class & ELF_RTYPE_CLASS_COPY) != 0)
diff --git a/elf/dl-minimal.c b/elf/dl-minimal.c
index 42192f8a7b..c79ce23be4 100644
--- a/elf/dl-minimal.c
+++ b/elf/dl-minimal.c
@@ -26,11 +26,87 @@
 #include <sys/param.h>
 #include <sys/types.h>
 #include <ldsodefs.h>
+#include <dl-irel.h>
+#include <dl-hash.h>
+#include <dl-sym-post.h>
 #include <_itoa.h>
 #include <malloc/malloc-internal.h>
 
 #include <assert.h>
 
+/* The rtld startup code calls __rtld_malloc_init_stubs after the
+  first self-relocation to adjust the pointers to the minimal
+  implementation below.  Before the final relocation,
+  __rtld_malloc_init_real is called to replace the pointers with the
+  real implementation.  */
+__typeof (calloc) *__rtld_calloc;
+__typeof (free) *__rtld_free;
+__typeof (malloc) *__rtld_malloc;
+__typeof (realloc) *__rtld_realloc;
+
+/* Defined below.  */
+static __typeof (calloc) rtld_calloc attribute_relro;
+static __typeof (free) rtld_free attribute_relro;
+static __typeof (malloc) rtld_malloc attribute_relro;
+static __typeof (realloc) rtld_realloc attribute_relro;
+
+void
+__rtld_malloc_init_stubs (void)
+{
+  __rtld_calloc = &rtld_calloc;
+  __rtld_free = &rtld_free;
+  __rtld_malloc = &rtld_malloc;
+  __rtld_realloc = &rtld_realloc;
+}
+
+/* Lookup NAME at VERSION in the scope of MATCH.  */
+static void *
+lookup_malloc_symbol (struct link_map *main_map, const char *name,
+		      struct r_found_version *version)
+{
+
+  const ElfW(Sym) *ref = NULL;
+  lookup_t result = _dl_lookup_symbol_x (name, main_map, &ref,
+					 main_map->l_scope,
+					 version, 0, 0, NULL);
+
+  assert (ELFW(ST_TYPE) (ref->st_info) != STT_TLS);
+  void *value = DL_SYMBOL_ADDRESS (result, ref);
+
+  return _dl_sym_post (result, ref, value, 0, main_map);
+}
+
+void
+__rtld_malloc_init_real (struct link_map *main_map)
+{
+  /* We cannot use relocations and initializers for this because the
+     changes made by __rtld_malloc_init_stubs break REL-style
+     (non-RELA) relocations that depend on the previous pointer
+     contents.  Also avoid direct relocation depedencies for the
+     malloc symbols so this function can be called before the final
+     rtld relocation (which enables RELRO, after which the pointer
+     variables cannot be written to).  */
+
+  struct r_found_version version;
+  version.name = symbol_version_string (libc, GLIBC_2_0);
+  version.hidden = 0;
+  version.hash = _dl_elf_hash (version.name);
+  version.filename = NULL;
+
+  void *new_calloc = lookup_malloc_symbol (main_map, "calloc", &version);
+  void *new_free = lookup_malloc_symbol (main_map, "free", &version);
+  void *new_malloc = lookup_malloc_symbol (main_map, "malloc", &version);
+  void *new_realloc = lookup_malloc_symbol (main_map, "realloc", &version);
+
+  /* Update the pointers in one go, so that any internal allocations
+     performed by lookup_malloc_symbol see a consistent
+     implementation.  */
+  __rtld_calloc = new_calloc;
+  __rtld_free = new_free;
+  __rtld_malloc = new_malloc;
+  __rtld_realloc = new_realloc;
+}
+
 /* Minimal malloc allocator for used during initial link.  After the
    initial link, a full malloc implementation is interposed, either
    the one in libc, or a different one supplied by the user through
@@ -38,14 +114,9 @@
 
 static void *alloc_ptr, *alloc_end, *alloc_last_block;
 
-/* Declarations of global functions.  */
-extern void weak_function free (void *ptr);
-extern void * weak_function realloc (void *ptr, size_t n);
-
-
 /* Allocate an aligned memory block.  */
-void * weak_function
-malloc (size_t n)
+static void *
+rtld_malloc (size_t n)
 {
   if (alloc_end == 0)
     {
@@ -87,8 +158,8 @@ malloc (size_t n)
 /* We use this function occasionally since the real implementation may
    be optimized when it can assume the memory it returns already is
    set to NUL.  */
-void * weak_function
-calloc (size_t nmemb, size_t size)
+static void *
+rtld_calloc (size_t nmemb, size_t size)
 {
   /* New memory from the trivial malloc above is always already cleared.
      (We make sure that's true in the rare occasion it might not be,
@@ -104,8 +175,8 @@ calloc (size_t nmemb, size_t size)
 }
 
 /* This will rarely be called.  */
-void weak_function
-free (void *ptr)
+void
+rtld_free (void *ptr)
 {
   /* We can free only the last block allocated.  */
   if (ptr == alloc_last_block)
@@ -118,8 +189,8 @@ free (void *ptr)
 }
 
 /* This is only called with the most recent block returned by malloc.  */
-void * weak_function
-realloc (void *ptr, size_t n)
+void *
+rtld_realloc (void *ptr, size_t n)
 {
   if (ptr == NULL)
     return malloc (n);
diff --git a/elf/rtld.c b/elf/rtld.c
index 553cfbd1b7..51dfaf966a 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -534,6 +534,9 @@ _dl_start (void *arg)
      header table in core.  Put the rest of _dl_start into a separate
      function, that way the compiler cannot put accesses to the GOT
      before ELF_DYNAMIC_RELOCATE.  */
+
+  __rtld_malloc_init_stubs ();
+
   {
 #ifdef DONT_USE_BOOTSTRAP_MAP
     ElfW(Addr) entry = _dl_start_final (arg);
@@ -2210,6 +2213,10 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
 	  rtld_timer_stop (&relocate_time, start);
 	}
 
+      /* The library defining malloc has already been relocated due to
+	 prelinking.  Resolve the malloc symbols for the dynamic
+	 loader.  */
+      __rtld_malloc_init_real (main_map);
 
       /* Mark all the objects so we know they have been already relocated.  */
       for (struct link_map *l = main_map; l != NULL; l = l->l_next)
@@ -2310,6 +2317,11 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
 	 re-relocation, we might call a user-supplied function
 	 (e.g. calloc from _dl_relocate_object) that uses TLS data.  */
 
+      /* 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);