diff options
| author | Florian Weimer <fweimer@redhat.com> | 2018-06-06 16:02:02 +0200 |
|---|---|---|
| committer | Florian Weimer <fweimer@redhat.com> | 2019-04-15 17:39:45 +0200 |
| commit | 59c45eeb109a3e4567f2bebe3feb5330d99a392a (patch) | |
| tree | 098d597006bdc88499f913eba751b586b1ba26a4 /sysdeps | |
| parent | ed8938f4f66e87cd805e0b1b94bab868e8ae0ea3 (diff) | |
| download | glibc-fw/bug21242.tar.xz glibc-fw/bug21242.zip | |
ld.so: Introduce delayed relocation processingfw/bug21242
This makes it possible to use IFUNC resolvers which depend
on relocations themselves, as long as these reloctions do
not depend on IFUNCs.
So far, delayed relocation processing is only implemented for
x86-64.
Diffstat (limited to 'sysdeps')
| -rw-r--r-- | sysdeps/generic/dl-delayed-reloc-machine.h | 30 | ||||
| -rw-r--r-- | sysdeps/x86_64/dl-delayed-reloc-machine.h | 97 | ||||
| -rw-r--r-- | sysdeps/x86_64/dl-machine.h | 46 |
3 files changed, 158 insertions, 15 deletions
diff --git a/sysdeps/generic/dl-delayed-reloc-machine.h b/sysdeps/generic/dl-delayed-reloc-machine.h new file mode 100644 index 0000000000..9cd078d043 --- /dev/null +++ b/sysdeps/generic/dl-delayed-reloc-machine.h @@ -0,0 +1,30 @@ +/* Delayed relocation processing. Generic version. + 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/>. */ + +#ifndef _DL_DELAYED_RELOC_MACHINE_H +#define _DL_DELAYED_RELOC_MACHINE_H + +/* Process a delayed relocation. In the default implementation, there + are no delayed relocations, so this implementation does + nothing. */ +static inline void +_dl_delayed_reloc_machine (const struct dl_delayed_reloc *reloc) +{ +} + +#endif /* _DL_DELAYED_RELOC_MACHINE_H */ diff --git a/sysdeps/x86_64/dl-delayed-reloc-machine.h b/sysdeps/x86_64/dl-delayed-reloc-machine.h new file mode 100644 index 0000000000..d49e9ba27e --- /dev/null +++ b/sysdeps/x86_64/dl-delayed-reloc-machine.h @@ -0,0 +1,97 @@ +/* Delayed relocation processing. x86-64 version. + Copyright (C) 2001-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/>. */ + +#ifndef _DL_DELAYED_RELOC_MACHINE_H +#define _DL_DELAYED_RELOC_MACHINE_H + +/* This needs to be kept in sync with elf_machine_rela in + dl-machine.h. */ +static inline void +_dl_delayed_reloc_machine (const struct dl_delayed_reloc *dr) +{ + const ElfW(Rela) *reloc = dr->reloc; + const unsigned long int r_type = ELFW(R_TYPE) (reloc->r_info); + ElfW(Addr) *const reloc_addr = dr->reloc_addr; + const ElfW(Sym) *refsym = dr->refsym; + const ElfW(Sym) *sym = dr->sym; + ElfW(Addr) value = SYMBOL_ADDRESS (dr->sym_map, sym, true); + + if (r_type == R_X86_64_IRELATIVE) + { + /* Special case: IRELATIVE relocations do not have an associated + symbol. */ + value = dr->map->l_addr + reloc->r_addend; + value = ((ElfW(Addr) (*) (void)) value) (); + *reloc_addr = value; + return; + } + + if (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC + && sym->st_shndx != SHN_UNDEF) + 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 (dr->map, l_info[DT_STRTAB]); + + _dl_error_printf (fmt, RTLD_PROGNAME, strtab + refsym->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; + } + case R_X86_64_COPY: + memcpy (reloc_addr, (void *) value, + MIN (sym->st_size, refsym->st_size)); + break; + default: + _dl_reloc_bad_type (dr->map, r_type, 0); + } +} + +#endif /* _DL_DELAYED_RELOC_MACHINE_H */ diff --git a/sysdeps/x86_64/dl-machine.h b/sysdeps/x86_64/dl-machine.h index 95a13b35b5..a1788e6998 100644 --- a/sysdeps/x86_64/dl-machine.h +++ b/sysdeps/x86_64/dl-machine.h @@ -27,6 +27,7 @@ #include <tls.h> #include <dl-tlsdesc.h> #include <cpu-features.c> +#include <dl-delayed-reloc.h> /* Return nonzero iff ELF header is compatible with the running host. */ static inline int __attribute__ ((unused)) @@ -314,22 +315,21 @@ elf_machine_rela (struct link_map *map, const ElfW(Rela) *reloc, && __glibc_likely (!skip_ifunc)) { # ifndef RTLD_BOOTSTRAP - if (sym_map != map - && sym_map->l_type != lt_executable - && !sym_map->l_relocated) + if (!sym_map->l_relocated || sym_map->l_delayed_relocations) { - const char *strtab - = (const char *) D_PTR (map, l_info[DT_STRTAB]); - _dl_error_printf ("\ -%s: Relink `%s' with `%s' for IFUNC symbol `%s'\n", - RTLD_PROGNAME, map->l_name, - sym_map->l_name, - strtab + refsym->st_name); + /* If the target map has not yet been fully relocated, + delay the processing of the relocation until + later. */ + _dl_delayed_reloc_record + (map, refsym, reloc, reloc_addr, sym_map, sym); + return; } # endif value = ((ElfW(Addr) (*) (void)) value) (); } + /* This switch statement needs to be kept in sync with the + switch statement in _dl_delayed_reloc_machine. */ switch (r_type) { # ifndef RTLD_BOOTSTRAP @@ -498,8 +498,14 @@ elf_machine_rela (struct link_map *map, const ElfW(Rela) *reloc, /* This can happen in trace mode if an object could not be found. */ break; - memcpy (reloc_addr_arg, (void *) value, - MIN (sym->st_size, refsym->st_size)); + if (sym_map->l_delayed_relocations) + /* The relocation result could depend on earlier delayed + relocations. */ + _dl_delayed_reloc_record + (map, refsym, reloc, reloc_addr, sym_map, sym); + else + memcpy (reloc_addr_arg, (void *) value, + MIN (sym->st_size, refsym->st_size)); if (__glibc_unlikely (sym->st_size > refsym->st_size) || (__glibc_unlikely (sym->st_size < refsym->st_size) && GLRO(dl_verbose))) @@ -511,9 +517,19 @@ 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; + if (map->l_delayed_relocations) + /* We need to delay these IFUNC relocation because the + IFUNC resolver may pick up the address of a symbol + which in turn is determined by a (delayed) IFUNC + relocation. */ + _dl_delayed_reloc_record + (map, refsym, reloc, reloc_addr, map, NULL); + else + { + value = map->l_addr + reloc->r_addend; + value = ((ElfW(Addr) (*) (void)) value) (); + *reloc_addr = value; + } break; default: _dl_reloc_bad_type (map, r_type, 0); |
