aboutsummaryrefslogtreecommitdiff
path: root/elf
diff options
context:
space:
mode:
Diffstat (limited to 'elf')
-rw-r--r--elf/Makefile2
-rw-r--r--elf/dl-delayed-reloc.c247
-rw-r--r--elf/dl-delayed-reloc.h179
-rw-r--r--elf/dl-open.c6
-rw-r--r--elf/rtld.c12
5 files changed, 443 insertions, 3 deletions
diff --git a/elf/Makefile b/elf/Makefile
index 310a37cc13..bb96003d80 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -33,7 +33,7 @@ dl-routines = $(addprefix dl-,load lookup object reloc deps hwcaps \
runtime init fini debug misc \
version profile tls origin scope \
execstack open close trampoline \
- exception sort-maps)
+ exception sort-maps delayed-reloc)
ifeq (yes,$(use-ldconfig))
dl-routines += dl-cache
endif
diff --git a/elf/dl-delayed-reloc.c b/elf/dl-delayed-reloc.c
new file mode 100644
index 0000000000..39c864fc64
--- /dev/null
+++ b/elf/dl-delayed-reloc.c
@@ -0,0 +1,247 @@
+/* Delayed relocation processing.
+ Copyright (C) 2018 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/>. */
+
+#if HAVE_IFUNC
+
+# include <assert.h>
+# include <dl-delayed-reloc.h>
+# include <errno.h>
+# include <ldsodefs.h>
+# include <sys/mman.h>
+# include <unistd.h>
+
+/* Machine-specific definitions. */
+# include <dl-delayed-reloc-machine.h>
+
+/* This struct covers a whole page containing individual struct
+ dl_delayed_reloc elements, which are allocated individually by
+ allocate_reloc below. */
+struct dl_delayed_reloc_array
+{
+ struct dl_delayed_reloc_array *next;
+ struct dl_delayed_reloc data[];
+};
+
+/* Pointer to global state. We use this indirection so that we do not
+ have to add the entire struct to the BSS segment. */
+static struct dl_delayed_reloc_global *global;
+
+/* Allocate a new struct dl_delayed_reloc_array object. Update global
+ *accordingly. */
+static void
+allocate_array (void)
+{
+ size_t page_size = GLRO(dl_pagesize);
+ void *ptr = __mmap (NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (ptr == MAP_FAILED)
+ _dl_signal_error (ENOMEM, NULL, NULL,
+ "cannot allocate IFUNC resolver information");
+ struct dl_delayed_reloc_array *new_head = ptr;
+
+ if (global->array_list_tail == NULL)
+ {
+ /* First allocation. */
+ global->array_list_head = new_head;
+ global->array_list_tail = new_head;
+ global->array_limit
+ = (page_size - offsetof (struct dl_delayed_reloc_array, data))
+ / sizeof (new_head->data[0]);
+ }
+ else
+ {
+ global->array_list_tail->next = new_head;
+ global->array_list_tail = new_head;
+ global->tail_array_count = 0;
+ }
+}
+
+/* Allocate one struct dl_delayed_reloc element from the active
+ allocation array. */
+static struct dl_delayed_reloc *
+allocate_reloc (void)
+{
+ assert (global != NULL);
+
+ /* Allocate a new array if none exists or the current array is
+ full. */
+ if (global->tail_array_count == global->array_limit)
+ allocate_array ();
+ assert (global->tail_array_count < global->array_limit);
+ return &global->array_list_tail->data[global->tail_array_count++];
+}
+
+/* Deallocate the list of array allocations starting at
+ array_list. */
+static void
+free_allocations (void)
+{
+ size_t page_size = GLRO(dl_pagesize);
+ struct dl_delayed_reloc_array *p = global->array_list_head;
+ while (p != NULL)
+ {
+ struct dl_delayed_reloc_array *next = p->next;
+ __munmap (p, page_size);
+ p = next;
+ }
+ /* The caller needs to call _dl_delayed_reloc_init again to start
+ over. */
+ global = NULL;
+}
+
+/* Called in debugging mode to print details about a delayed
+ relocation. */
+static void
+report_delayed_relocation (struct link_map **current_map,
+ struct dl_delayed_reloc *dr)
+{
+ if (dr->map != *current_map)
+ {
+ *current_map = dr->map;
+
+ /* l_name is NULL for the main executable. */
+ const char *map_name;
+ if (dr->map->l_name != NULL && *dr->map->l_name != '\0')
+ map_name = dr->map->l_name;
+ else
+ map_name = "<executable>";
+
+ _dl_debug_printf ("applying delayed relocations for %s\n", map_name);
+ }
+
+ if (dr->sym != NULL)
+ {
+ const char *strtab
+ = (const char *) D_PTR (dr->sym_map, l_info[DT_STRTAB]);
+ if (dr->sym_map->l_name != NULL)
+ _dl_debug_printf ("delayed relocation of symbol %s in %s\n",
+ strtab + dr->sym->st_name, dr->sym_map->l_name);
+ else
+ _dl_debug_printf ("delayed relocation of symbol %s\n",
+ strtab + dr->sym->st_name);
+ }
+ else
+ {
+ unsigned long int where = (uintptr_t) dr->reloc_addr;
+ _dl_debug_printf ("delayed relative relocation at 0x%lx\n", where);
+ }
+}
+
+/* Process all delayed IFUNC resolutions for IFUNC_MAP alone. */
+static void
+apply_relocations (void)
+{
+ size_t array_limit = global->array_limit;
+ if (array_limit == 0)
+ /* No delayed relocations have been allocated, so there is nothing
+ to do. */
+ return;
+
+ /* Used for debugging output, to report switches from relocated
+ object to another. */
+ struct link_map *current_map = NULL;
+ unsigned long int count = 0;
+
+ for (struct dl_delayed_reloc_array *list = global->array_list_head;
+ list != NULL; list = list->next)
+ {
+ for (size_t index = 0; index < array_limit; ++index)
+ {
+ struct dl_delayed_reloc *dr = list->data + index;
+ if (dr->reloc == NULL)
+ /* An incompletely filled array marks the end of the
+ list. */
+ goto out;
+
+ if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_BINDINGS))
+ report_delayed_relocation (&current_map, dr);
+ _dl_delayed_reloc_machine (dr);
+
+ /* Mark the object as fully relocated, for subsequent dlopen
+ calls. This will clear the flag even if there are still
+ pending relocations to process, but we keep executing the
+ loop, so this is not a problem. */
+ dr->map->l_delayed_relocations = false;
+
+ ++count;
+ }
+
+ }
+
+ out:
+ if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_BINDINGS))
+ _dl_debug_printf ("%lu delayed relocations performed\n", count);
+}
+
+void
+_dl_delayed_reloc_init (struct dl_delayed_reloc_global *new_global)
+{
+ assert (global == NULL);
+ global = new_global;
+ *global = (struct dl_delayed_reloc_global) { };
+}
+
+void
+_dl_delayed_reloc_record (struct link_map *map,
+ const ElfW(Sym) *refsym,
+ const ElfW(Rela) *reloc,
+ ElfW(Addr) *reloc_addr,
+ struct link_map *sym_map,
+ const ElfW(Sym) *sym)
+{
+ /* reloc == NULL is a marker to find the end of the allocations. */
+ assert (reloc != NULL);
+
+ /* Add the delayed relocation to the global list. */
+ struct dl_delayed_reloc *dr = allocate_reloc ();
+ *dr = (struct dl_delayed_reloc)
+ {
+ .map = map,
+ .refsym = refsym,
+ .reloc = reloc,
+ .reloc_addr = reloc_addr,
+ .sym = sym,
+ .sym_map = sym_map,
+ };
+
+ /* The map containing the relocation will now need special
+ processing for future copy and relative IFUNC relocations. */
+ map->l_delayed_relocations = true;
+}
+
+void
+_dl_delayed_reloc_apply (void)
+{
+ assert (global != NULL);
+
+ apply_relocations ();
+ free_allocations ();
+}
+
+void
+_dl_delayed_reloc_clear (void)
+{
+ /* This can be called from error handling, where the initialization
+ may not yet have happened. */
+ if (global == NULL)
+ return;
+
+ free_allocations ();
+}
+
+#endif /* HAVE_IFUNC */
diff --git a/elf/dl-delayed-reloc.h b/elf/dl-delayed-reloc.h
new file mode 100644
index 0000000000..11ecb8e318
--- /dev/null
+++ b/elf/dl-delayed-reloc.h
@@ -0,0 +1,179 @@
+/* Private declarations for delayed relocation processing.
+ Copyright (C) 2018 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/>. */
+
+/* Delayed relocation processing attempts to address relocation
+ dependencies which are not reflected in DT_NEEDED dependencies
+ because objects are incompletely linked, or the relocation order
+ derived from the dependencies causes objects to refer to objects
+ relocated later due to symbol interposition. See bugs 20019,
+ 21041, 23240.
+
+ Architecture-specific changes are required to implement delayed
+ relocation processing, typically in the implementation of
+ elf_machine_rel or elf_machine_rela. There are three reasons why a
+ relocation needs to be delayed:
+
+ * The relocation is bound with the help of an IFUNC resolver, and
+ the IFUNC resolver resides in an object which has not been
+ fully relocated yet (i.e., l_relocated is false for its link
+ map, or l_delayed_relocations is true). The reason for the
+ delay is that the IFUNC resolver may depend on the
+ yet-unprocessed relocations.
+
+ * The relocation is a copy relocation and the source symbol
+ resides in an object which has not been fully relocated yet
+ (l_delayed_relocations is true). The reason for the delay is
+ that the copy relocation could otherwise copy data which has
+ not been fully initialized yet because some of the delayed
+ relocations affect it.
+
+ * The is a relative IFUNC relocation, and the link map for the
+ object containing the relocation has other delayed relocations
+ (l_delayed_relocations is true). The reason for the delay is
+ that the resolver for the relocation depends on other delayed
+ relocations for this object which have not yet been performed.
+
+ All delayed relocations are processed in the order they were
+ recorded, which reflects the initial relocation processing order
+ between objects, and the relocation order as determined by the
+ static link editor within objects.
+
+ This does not process correctly all implicit dependencies between
+ IFUNC resolvers (e.g., two IFUNC resolvers calling each other will
+ still not work), but it addresses common cases of implicit
+ dependencies, for example as the result of symbol interposition or
+ due to missing DT_NEEDED entries in objects. In particular, data
+ relocations used by the IFUNC resolvers in glibc itself will be
+ resolved before these IFUNC resolvers run, so it is safe to call
+ glibc string functions from other (non-glibc) IFUNC resolvers, or
+ use pointers to glibc functions (with IFUNC resolvers) in global
+ data initializers (inside or outside of glibc).
+
+ Note that delayed relocation processing does not address the
+ phenomenon that IFUNC resolvers run before ELF constructors and C++
+ constructors. This means that IFUNC resolvers cannot assume that
+ glibc itself has been initialized, or that global data structures
+ in other objects have the expected values, particularly if their
+ initializers are not constants. */
+
+#ifndef _DL_DELAYED_RELOC_H
+#define _DL_DELAYED_RELOC_H
+
+#include <link.h>
+
+/* All functions declared in this file must be called while the global
+ rtld lock is acquired. */
+
+/* Internal storage for delayed relocations. */
+struct dl_delayed_reloc_array;
+
+/* The full implementation is only available if IFUNCs are
+ supported. */
+#if HAVE_IFUNC
+
+struct dl_delayed_reloc_global
+{
+ /* List of allocated arrays of struct dl_delayed_reloc elements. */
+ struct dl_delayed_reloc_array *array_list_head;
+
+ /* Last element of the list. Used for allocations. */
+ struct dl_delayed_reloc_array *array_list_tail;
+
+ /* Number of entries in the current allocation array. */
+ size_t tail_array_count;
+
+ /* Maximum number of entries in each array. */
+ size_t array_limit;
+};
+
+/* Prepare for relocation processing. *PGLOBAL must remain in scope
+ until _dl_delayed_reloc_apply or _dl_delayed_reloc_clear is
+ called. */
+void _dl_delayed_reloc_init (struct dl_delayed_reloc_global *pglobal)
+ attribute_hidden;
+
+/* Apply all pending delayed relocations. Deallocate all auxiliary
+ allocations. */
+void _dl_delayed_reloc_apply (void) attribute_hidden;
+
+/* Clear all allocated delayed relocations. Deallocate all auxiliary
+ allocations. This function is intended for clearing up after a
+ dlopen failure. */
+void _dl_delayed_reloc_clear (void) attribute_hidden;
+
+/* Delayed relocation. These are stored in arrays inside struct
+ dl_delayed_reloc_array.
+
+ In case a copy or relative IFUNC relocation is encountered while
+ the link map has delayed (IFUNC) relocations, these relocations are
+ added to the list as well because the result of these relocations
+ could affect their results. */
+struct dl_delayed_reloc
+{
+ /* Information about the relocation. */
+ const ElfW(Sym) *refsym;
+ struct link_map *map;
+ const ElfW(Rela) *reloc;
+ ElfW(Addr) *reloc_addr;
+
+ /* Information about the target symbol (either an IFUNC resolver, or
+ the source of a copy relocation). The corresponding symbol map
+ is implied. NULL for relative IFUNC relocations. */
+ const ElfW(Sym) *sym;
+
+ /* The link map corresponding to sym. */
+ struct link_map *sym_map;
+};
+
+/* Record a delayed relocation for SYM at *RELOC_ADDR. The relocation
+ is associated with MAP. Can raise an error using _dl_signal_error.
+ SYM can be NULL if the relocation is a relative IFUNC
+ relocation. */
+void _dl_delayed_reloc_record (struct link_map *map,
+ const ElfW(Sym) *refsym,
+ const ElfW(Rela) *reloc,
+ ElfW(Addr) *reloc_addr,
+ struct link_map *sym_map,
+ const ElfW(Sym) *sym) attribute_hidden;
+
+#else /* !HAVE_IFUNC */
+
+/* Dummy implementations for targets without IFUNC support. */
+
+struct dl_delayed_reloc_global
+{
+};
+
+static inline void
+_dl_delayed_reloc_init (struct dl_delayed_reloc_global *global)
+{
+}
+
+static inline void
+_dl_delayed_reloc_apply (void)
+{
+}
+
+static inline void
+_dl_delayed_reloc_clear (void)
+{
+}
+
+#endif /* !HAVE_IFUNC */
+
+#endif /* _DL_DELAYED_RELOC_H */
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 90c1180a8c..4e390d9abd 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -37,6 +37,7 @@
#include <dl-dst.h>
#include <dl-prop.h>
+#include <dl-delayed-reloc.h>
/* We must be careful not to leave us in an inconsistent state. Thus we
catch any error and re-raise it after cleaning up. */
@@ -327,6 +328,9 @@ dl_open_worker (void *a)
while (l != NULL);
_dl_sort_maps (maps, nmaps, NULL, false);
+ struct dl_delayed_reloc_global delayed_relocations;
+ _dl_delayed_reloc_init (&delayed_relocations);
+
int relocation_in_progress = 0;
for (unsigned int i = nmaps; i-- > 0; )
@@ -494,6 +498,7 @@ TLS generation counter wrapped! Please report this."));
}
}
+ _dl_delayed_reloc_apply ();
_dl_relocate_apply_relro (new);
/* Notify the debugger all new objects have been relocated. */
@@ -611,6 +616,7 @@ no more namespaces available for dlmopen()"));
if ((mode & __RTLD_AUDIT) == 0)
GL(dl_tls_dtv_gaps) = true;
+ _dl_delayed_reloc_clear ();
_dl_close_worker (args.map, true);
}
diff --git a/elf/rtld.c b/elf/rtld.c
index 0b07656947..9a14e18db0 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -43,6 +43,7 @@
#include <stap-probe.h>
#include <stackinfo.h>
#include <not-cancel.h>
+#include <dl-delayed-reloc.h>
#include <assert.h>
@@ -2185,6 +2186,9 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
_rtld_main_check (main_map, _dl_argv[0]);
+ /* Used to keep track of delayed IFUNC relocations. */
+ struct dl_delayed_reloc_global delayed_relocations;
+
if (prelinked)
{
if (main_map->l_info [ADDRIDX (DT_GNU_CONFLICT)] != NULL)
@@ -2236,6 +2240,9 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
RTLD_TIMING_VAR (start);
rtld_timer_start (&start);
+
+ _dl_delayed_reloc_init (&delayed_relocations);
+
unsigned i = main_map->l_searchlist.r_nlist;
while (i-- > 0)
{
@@ -2315,10 +2322,11 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
rtld_timer_accum (&relocate_time, start);
}
- /* Activate RELRO protection. In the prelink case, this was already
- done earlier. */
+ /* Perform delayed IFUNC relocations and activate RELRO protection.
+ In the prelink case, this was already done earlier. */
if (! prelinked)
{
+ _dl_delayed_reloc_apply ();
/* Make sure that this covers the dynamic linker as well.
TODO: rtld_multiple_ref is always true because libc.so needs
the dynamic linker internally. */