Compare commits

...
Author SHA1 Message Date
Florian Weimer defaba407d WIP delayed IFUNC relocation 2017-01-24 18:32:30 +01:00
Florian Weimer feb6d6b394 ld.so: Defer applying RELRO protection
This is a prerequisite of a future change which applies additional
cross-object relocations after _dl_relocate_object has been called.
2017-01-24 13:06:05 +01:00
11 changed files with 435 additions and 26 deletions
+10
View File
@@ -1,3 +1,13 @@
2017-01-24 Florian Weimer <fweimer@redhat.com>
* sysdeps/generic/ldsodefs.h (_dl_relocate_apply_relro): Declare.
* elf/rtld.c (relocate_doit): Add comment.
(dl_main): Call _dl_relocate_apply_relro.
* elf/dl-open.c (dl_open_worker): Likewise.
* elf/dl-reloc.c (_dl_relocate_object): Do not call
_dl_protect_relro.
(_dl_relocate_apply_relro): New function.
2017-01-20 DJ Delorie <dj@redhat.com>
* elf/dl-tunables.c (tunable_set_val_if_valid_range): Split into ...
+1 -1
View File
@@ -29,7 +29,7 @@ routines = $(all-dl-routines) dl-support dl-iteratephdr \
# 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 init fini debug misc \
runtime init fini debug misc ifunc \
version profile conflict tls origin scope \
execstack caller open close trampoline)
ifeq (yes,$(use-ldconfig))
+192
View File
@@ -0,0 +1,192 @@
/* Delayed IFUNC processing.
Copyright (C) 2017 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 HAVE_IFUNC
# include <dl-ifunc.h>
# include <errno.h>
# include <ldsodefs.h>
# include <sys/mman.h>
# include <unistd.h>
/* Machine-specific definitions. */
# include <dl-ifunc-reloc.h>
/* This struct covers a whole page containing individual struct
dl_ifunc_relocation elements, which are allocated individually by
allocate_relocation below. */
struct dl_ifunc_relocation_array
{
struct dl_ifunc_relocation_array *next;
struct dl_ifunc_relocation data[];
};
/* Global list of allocated arrays of struct dl_ifunc_relocation
elements. */
static struct dl_ifunc_relocation_array *array_list;
/* Position of the next allocation in the current array used as the
source of struct dl_ifunc_relocation allocations. */
static struct dl_ifunc_relocation *next_alloc;
static unsigned int remaining_alloc;
/* Allocate a new struct dl_ifunc_relocation_array object. Return the
first data object therein. Update array_list, next_alloc,
remaining_alloc. This function assumes taht remaining_alloc is
zero. */
static struct dl_ifunc_relocation *
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_ifunc_relocation_array *new_head = ptr;
new_head->next = array_list;
array_list = new_head;
next_alloc = &new_head->data[1];
/* The function returns the first element of the new array. */
remaining_alloc
= (page_size - offsetof (struct dl_ifunc_relocation_array, data))
/ sizeof (new_head->data[0]) - 1;
return &new_head->data[0];
}
/* Allocate whone struct dl_ifunc_relocation element from the active
array. Updated next_alloc, remaining_alloc, and potentially
array_list. */
static struct dl_ifunc_relocation *
allocate_ifunc (void)
{
if (remaining_alloc == 0)
return allocate_array ();
--remaining_alloc;
return next_alloc++;
}
/* Deallocate the list of array allocations starting at
array_list. */
static void
free_allocations (void)
{
size_t page_size = GLRO(dl_pagesize);
struct dl_ifunc_relocation_array *p = array_list;
while (p != NULL)
{
struct dl_ifunc_relocation_array *next = p->next;
munmap (p, page_size);
p = next;
}
/* Restart from scratch if _dl_ifunc_record_reloc is called
again. */
array_list = NULL;
next_alloc = NULL;
remaining_alloc = 0;
}
/* Process all delayed IFUNC resolutions for IFUNC_MAP alone. */
static void
apply_relocations (struct link_map *ifunc_map)
{
if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_BINDINGS)
&& ifunc_map->l_name != NULL)
_dl_debug_printf ("applying delayed IFUNC relocations against %s\n",
ifunc_map->l_name);
struct dl_ifunc_relocation *p = ifunc_map->l_ifunc_relocations;
unsigned int count = 0;
while (p != NULL)
{
if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_BINDINGS)
&& ifunc_map->l_name != NULL)
{
if (p->ifunc_sym == NULL)
_dl_debug_printf ("processing relative IFUNC relocation for %s\n",
ifunc_map->l_name);
else
{
const char *strtab = (const char *) D_PTR (ifunc_map, l_info[DT_STRTAB]);
_dl_debug_printf ("binding IFUNC symbol %s in %s against %s\n",
strtab + p->ifunc_sym->st_name,
p->reloc_map->l_name, ifunc_map->l_name);
}
}
_dl_ifunc_process_relocation (p, ifunc_map);
p = p->next;
++count;
}
if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_BINDINGS)
&& ifunc_map->l_name != NULL)
_dl_debug_printf ("%u delayed IFUNC relocations performed against %s\n",
count, ifunc_map->l_name);
/* No deallocation takes place here. All allocated objects are
freed in bulk by a later call to free_allocations. */
ifunc_map->l_ifunc_relocations = NULL;
}
void internal_function
_dl_ifunc_record_reloc (struct link_map *reloc_map,
const ElfW(Rela) *reloc,
ElfW(Addr) *reloc_addr,
struct link_map *ifunc_map,
const ElfW(Sym) *ifunc_sym)
{
/* Add the delayed IFUNC relocation to the list for ifunc_map. */
struct dl_ifunc_relocation *ifunc = allocate_ifunc ();
*ifunc = (struct dl_ifunc_relocation)
{
.reloc_map = reloc_map,
.reloc = reloc,
.reloc_addr = reloc_addr,
.ifunc_sym = ifunc_sym,
.next = ifunc_map->l_ifunc_relocations,
};
ifunc_map->l_ifunc_relocations = ifunc;
}
void internal_function
_dl_ifunc_apply_relocations (struct link_map *new)
{
/* Traverse the search list in reverse order, in case an IFUNC
resolver calls into one its dependencies, and those dependency
needs IFUNC resolution as well. */
struct link_map **r_list = new->l_searchlist.r_list;
for (unsigned int i = new->l_searchlist.r_nlist; i-- > 0; )
{
struct link_map *l = r_list[i];
if (l->l_ifunc_relocations != NULL)
apply_relocations (l);
}
free_allocations ();
}
void internal_function
_dl_ifunc_clear_relocations (struct link_map *map)
{
Lmid_t nsid = map->l_ns;
struct link_namespaces *ns = &GL(dl_ns)[nsid];
for (struct link_map *l = ns->_ns_loaded; l != NULL; l = l->l_next)
l->l_ifunc_relocations = NULL;
free_allocations ();
}
#endif /* HAVE_IFUNC */
+88
View File
@@ -0,0 +1,88 @@
/* Private declarations for delayed IFUNC processing.
Copyright (C) 2017 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/>. */
#ifndef DL_IFUNC_H
#define DL_IFUNC_H
#include <link.h>
/* All functions declared in this file must be called while the global
rtld lock is acquired. */
/* Apply all pending IFUNC relocations in the search scope of the new
link map. Deallocate all auxiliary allocations. */
void _dl_ifunc_apply_relocations (struct link_map *new)
attribute_hidden internal_function;
/* Clear all allocated delayed IFUNC relocations in the namespace of
the link map. Deallocate all auxiliary allocations. This function
is intended for clearing up after a dlopen failure. */
void _dl_ifunc_clear_relocations (struct link_map *map)
attribute_hidden internal_function;
/* The no-op implementatoin is only available if IFUNCs are
supported. */
#ifdef HAVE_IFUNC
/* Delayed IFUNC relocation. For each link map with a referenced
IFUNC symbol, a separate single-linked list of delayed IFUNC
relocations exists while the dynamic linker is running. */
struct dl_ifunc_relocation
{
/* Information about the relocation. */
struct link_map *reloc_map;
const ElfW(Rela) *reloc;
ElfW(Addr) *reloc_addr;
/* Information about the IFUNC resolver. The corresponding symbol
map is implied. */
const ElfW(Sym) *ifunc_sym;
/* Pointer to the next element in the list of IFUNC relocations for
the symbol map. */
struct dl_ifunc_relocation *next;
};
/* Record a delayed IFUNC relocation for IFUNC_SYM at *RELOC_ADDR.
The relocation is associated with IFUNC_MAP. Can raise an error
using _dl_signal_error. IFUNC_SYM can be NULL if the relocation is
a relative IFUNC relocation. */
void _dl_ifunc_record_reloc (struct link_map *reloc_map,
const ElfW(Rela) *reloc,
ElfW(Addr) *reloc_addr,
struct link_map *ifunc_map,
const ElfW(Sym) *ifunc_sym)
attribute_hidden internal_function;
#else /* !HAVE_IFUNC */
/* Dummy implementations for targets without IFUNC support. */
static inline void
_dl_ifunc_apply_relocations (struct link_map *new)
{
}
static inline void
_dl_ifunc_clear_relocations (struct link_map *map)
{
}
#endif /* !HAVE_IFUNC */
#endif /* DL_IFUNC_H */
+5 -1
View File
@@ -35,7 +35,7 @@
#include <atomic.h>
#include <dl-dst.h>
#include <dl-ifunc.h>
extern int __libc_multiple_libcs; /* Defined in init-first.c. */
@@ -552,6 +552,9 @@ TLS generation counter wrapped! Please report this."));
}
}
_dl_ifunc_apply_relocations (new);
_dl_relocate_apply_relro (new);
/* Notify the debugger all new objects have been relocated. */
if (relocation_in_progress)
LIBC_PROBE (reloc_complete, 3, args->nsid, r, new);
@@ -671,6 +674,7 @@ no more namespaces available for dlmopen()"));
if ((mode & __RTLD_AUDIT) == 0)
GL(dl_tls_dtv_gaps) = true;
_dl_ifunc_clear_relocations (args.map);
_dl_close_worker (args.map, true);
}
+14 -6
View File
@@ -306,14 +306,8 @@ _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 internal_function
_dl_protect_relro (struct link_map *l)
{
@@ -333,6 +327,20 @@ cannot apply additional memory protection after relocation");
}
}
void internal_function
_dl_relocate_apply_relro (struct link_map *new)
{
struct link_map **lp = new->l_searchlist.r_list;
struct link_map **end = lp + new->l_searchlist.r_nlist;
for (; lp < end; ++lp)
{
struct link_map *l = *lp;
if (l->l_relro_size)
_dl_protect_relro (l);
}
}
void
internal_function __attribute_noinline__
_dl_reloc_bad_type (struct link_map *map, unsigned int type, int plt)
+14
View File
@@ -41,6 +41,7 @@
#include <tls.h>
#include <stap-probe.h>
#include <stackinfo.h>
#include <dl-ifunc.h>
#include <assert.h>
@@ -465,6 +466,7 @@ struct version_check_args
int dotrace;
};
/* Callback function used during tracing. */
static void
relocate_doit (void *a)
{
@@ -2113,6 +2115,18 @@ ERROR: ld.so: object '%s' cannot be loaded as audit interface: %s; ignored.\n",
HP_TIMING_ACCUM_NT (relocate_time, add);
}
/* Perform delayed IFUNC relocations and activate RELRO protection.
In the prelink case, this was already done earlier. */
if (! prelinked)
{
_dl_ifunc_apply_relocations (main_map);
/* 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. */
assert (rtld_multiple_ref);
_dl_relocate_apply_relro (main_map);
}
/* Do any necessary cleanups for the startup OS interface code.
We do these now so that no calls are made after rtld re-relocation
which might be resolved to different functions than we expect.
+6
View File
@@ -60,6 +60,7 @@ struct r_search_path_elem;
/* Forward declaration. */
struct link_map;
struct dl_ifunc_relocation;
/* Structure to describe a single list of scope elements. The lookup
functions get passed an array of pointers to such structures. */
@@ -318,6 +319,11 @@ struct link_map
ElfW(Addr) l_relro_addr;
size_t l_relro_size;
#ifdef HAVE_IFUNC
/* Deferred IFUNC relocations. Only used during relocation. */
struct dl_ifunc_relocation *l_ifunc_relocations;
#endif
unsigned long long int l_serial;
/* Audit information. This array apparent must be the last in the
+6
View File
@@ -863,6 +863,12 @@ extern void _dl_relocate_object (struct link_map *map,
int reloc_mode, int consider_profiling)
attribute_hidden;
/* Apply RELRO protection for all objects on the search path of NEW.
This is the final step of relocation processing for freshly loaded
objects. */
void _dl_relocate_apply_relro (struct link_map *new)
internal_function attribute_hidden;
/* Protect PT_GNU_RELRO area. */
extern void _dl_protect_relro (struct link_map *map)
internal_function attribute_hidden;
+91
View File
@@ -0,0 +1,91 @@
/* IFUNC relocation processing, x86-64 version.
Copyright (C) 2001-2017 Free Software Foundation, Inc.
This file is part of the GNU C Library.
Contributed by Andreas Jaeger <aj@suse.de>.
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/>. */
#ifndef DL_IFUNC_RELOC_H
#define DL_IFUNC_RELOC_H
static inline void
_dl_ifunc_process_relocation (const struct dl_ifunc_relocation *ifunc,
struct link_map *sym_map)
{
const ElfW(Rela) *reloc = ifunc->reloc;
const unsigned long int r_type = ELFW(R_TYPE) (reloc->r_info);
ElfW(Addr) *const reloc_addr = ifunc->reloc_addr;
/* Special case: A relative IFUNC relocation does not have an
associated symbol. */
if (r_type == R_X86_64_IRELATIVE)
{
ElfW(Addr) value = sym_map->l_addr + reloc->r_addend;
value = ((ElfW(Addr) (*) (void)) value) ();
*reloc_addr = value;
return;
}
ElfW(Addr) value = (ElfW(Addr)) sym_map->l_addr + ifunc->ifunc_sym->st_value;
value = ((ElfW(Addr) (*) (void)) value) ();
/* This switch statement needs to be kept in sync with the switch
statement in elf_machine_rela. */
switch (r_type)
{
case R_X86_64_GLOB_DAT:
case R_X86_64_JUMP_SLOT:
*reloc_addr = value + reloc->r_addend;
break;
case R_X86_64_64:
/* value + r_addend may be > 0xffffffff and R_X86_64_64
relocation updates the whole 64-bit entry. */
*(Elf64_Addr *) reloc_addr = (Elf64_Addr) value + reloc->r_addend;
break;
case R_X86_64_32:
value += reloc->r_addend;
*(unsigned int *) reloc_addr = value;
const char *fmt;
if (__glibc_unlikely (value > UINT_MAX))
{
const char *strtab;
fmt = "\
%s: Symbol `%s' causes overflow in R_X86_64_32 relocation\n";
print_err:
strtab = (const char *) D_PTR (sym_map, l_info[DT_STRTAB]);
_dl_error_printf (fmt, RTLD_PROGNAME,
strtab + ifunc->ifunc_sym->st_name);
}
break;
case R_X86_64_PC32:
value += reloc->r_addend - (ElfW(Addr)) reloc_addr;
*(unsigned int *) reloc_addr = value;
if (__glibc_unlikely (value != (int) value))
{
fmt = "\
%s: Symbol `%s' causes overflow in R_X86_64_PC32 relocation\n";
goto print_err;
}
break;
default:
_dl_reloc_bad_type (ifunc->reloc_map, r_type, 0);
}
}
#endif /* DL_IFUNC_RELOC_H */
+8 -18
View File
@@ -27,6 +27,7 @@
#include <tls.h>
#include <dl-tlsdesc.h>
#include <cpu-features.c>
#include <dl-ifunc.h>
/* Return nonzero iff ELF header is compatible with the running host. */
static inline int __attribute__ ((unused))
@@ -326,29 +327,20 @@ elf_machine_rela (struct link_map *map, const ElfW(Rela) *reloc,
ElfW(Addr) value = (sym == NULL ? 0
: (ElfW(Addr)) sym_map->l_addr + sym->st_value);
# ifndef RTLD_BOOTSTRAP
if (sym != NULL
&& __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC,
0)
&& __builtin_expect (sym->st_shndx != SHN_UNDEF, 1)
&& __builtin_expect (!skip_ifunc, 1))
{
# ifndef RTLD_BOOTSTRAP
if (sym_map != map
&& sym_map->l_type != lt_executable
&& !sym_map->l_relocated)
{
const char *strtab
= (const char *) D_PTR (map, l_info[DT_STRTAB]);
_dl_fatal_printf ("\
%s: Relink `%s' with `%s' for IFUNC symbol `%s'\n",
RTLD_PROGNAME, map->l_name,
sym_map->l_name,
strtab + refsym->st_name);
}
# endif
value = ((ElfW(Addr) (*) (void)) value) ();
_dl_ifunc_record_reloc (map, reloc, reloc_addr, sym_map, sym);
return;
}
# endif /* !RTLD_BOOTSTRAP */
/* This switch statement needs to be kept in sync with the
switch statement in _dl_ifunc_process_relocation. */
switch (r_type)
{
# ifndef RTLD_BOOTSTRAP
@@ -528,9 +520,7 @@ elf_machine_rela (struct link_map *map, const ElfW(Rela) *reloc,
break;
# endif
case R_X86_64_IRELATIVE:
value = map->l_addr + reloc->r_addend;
value = ((ElfW(Addr) (*) (void)) value) ();
*reloc_addr = value;
_dl_ifunc_record_reloc (map, reloc, reloc_addr, map, NULL);
break;
default:
_dl_reloc_bad_type (map, r_type, 0);