From 9bbddf730f7837cf87f4c789fbb41a312e295d6c Mon Sep 17 00:00:00 2001 From: Rich Felker Date: Mon, 25 May 2015 23:33:59 -0400 Subject: [PATCH] reprocess all libc/ldso symbolic relocations in dynamic linking stage 3 commit f3ddd173806fd5c60b3f034528ca24542aecc5b9 introduced early relocations and subsequent reprocessing as part of the dynamic linker bootstrap overhaul, to allow use of arbitrary libc functions before the main application and libraries are loaded, but only reprocessed GOT/PLT relocation types. commit c093e2e8201524db0d638920e76bcb6b1d925f3a added reprocessing of non-GOT/PLT relocations to fix an actual regression that was observed on powerpc, but only for RELA format tables with out-of-line addends. REL table (inline addends at the relocation address) reprocessing is trickier because the first relocation pass clobbers the addends. this patch extends symbolic relocation reprocessing for libc/ldso to support all relocation types, whether REL or RELA format tables are used. it is believed not to alter behavior on any existing archs for the current dynamic linker and libc code. the motivations for this change are consistency and future-proofing. it ensures that behavior does not differ depending on whether REL or RELA tables are used, which could lead to undetected arch-specific bugs. it also ensures that, if in the future code depending on additional relocation types is added to libc.so, either at the source level or as part of the compiler runtime that gets pulled in (for example, soft-float with TLS for fenv), the new code will work properly. the implementation concept is simple: stage 2 of the dynamic linker counts the number of symbolic relocations in the libc/ldso REL table and allocates a VLA to save their addends into; stage 3 then uses the saved addends in place of the inline ones which were clobbered. for stack safety, a hard limit (currently 4k) is imposed on the number of such addends; this should be a couple orders of magnitude larger than the actual need. this number is not a runtime variable that could break fail-safety; it is constant for a given libc.so build. --- src/ldso/dynlink.c | 54 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/src/ldso/dynlink.c b/src/ldso/dynlink.c index 3842aeba..42930adf 100644 --- a/src/ldso/dynlink.c +++ b/src/ldso/dynlink.c @@ -74,7 +74,6 @@ struct dso { volatile int new_dtv_idx, new_tls_idx; struct td_index *td_index; struct dso *fini_next; - int rel_early_relative, rel_update_got; char *shortname; char buf[]; }; @@ -96,6 +95,9 @@ static struct builtin_tls { } builtin_tls[1]; #define MIN_TLS_ALIGN offsetof(struct builtin_tls, pt) +#define ADDEND_LIMIT 4096 +static size_t *saved_addends, *apply_addends_to; + static struct dso ldso; static struct dso *head, *tail, *fini_head; static char *env_path, *sys_path; @@ -256,9 +258,17 @@ static void do_relocs(struct dso *dso, size_t *rel, size_t rel_size, size_t stri size_t sym_val; size_t tls_val; size_t addend; + int skip_relative = 0, reuse_addends = 0, save_slot = 0; + + if (dso == &ldso) { + /* Only ldso's REL table needs addend saving/reuse. */ + if (rel == apply_addends_to) + reuse_addends = 1; + skip_relative = 1; + } for (; rel_size; rel+=stride, rel_size-=stride*sizeof(size_t)) { - if (dso->rel_early_relative && IS_RELATIVE(rel[1])) continue; + if (skip_relative && IS_RELATIVE(rel[1])) continue; type = R_TYPE(rel[1]); sym_index = R_SYM(rel[1]); reloc_addr = (void *)(base + rel[0]); @@ -280,12 +290,20 @@ static void do_relocs(struct dso *dso, size_t *rel, size_t rel_size, size_t stri def.dso = dso; } - int gotplt = (type == REL_GOT || type == REL_PLT); - if (dso->rel_update_got && !gotplt && stride==2) continue; - - addend = stride>2 ? rel[2] - : gotplt || type==REL_COPY ? 0 - : *reloc_addr; + if (stride > 2) { + addend = rel[2]; + } else if (type==REL_GOT || type==REL_PLT|| type==REL_COPY) { + addend = 0; + } else if (reuse_addends) { + /* Save original addend in stage 2 where the dso + * chain consists of just ldso; otherwise read back + * saved addend since the inline one was clobbered. */ + if (head==&ldso) + saved_addends[save_slot] = *reloc_addr; + addend = saved_addends[save_slot++]; + } else { + addend = *reloc_addr; + } sym_val = def.sym ? (size_t)def.dso->base+def.sym->st_value : 0; tls_val = def.sym ? def.sym->st_value : 0; @@ -879,7 +897,7 @@ static void do_mips_relocs(struct dso *p, size_t *got) size_t i, j, rel[2]; unsigned char *base = p->base; i=0; search_vec(p->dynv, &i, DT_MIPS_LOCAL_GOTNO); - if (p->rel_early_relative) { + if (p==&ldso) { got += i; } else { while (i--) *got++ += (size_t)base; @@ -1125,15 +1143,29 @@ void __dls2(unsigned char *base, size_t *sp) ldso.phnum = ehdr->e_phnum; ldso.phdr = (void *)(base + ehdr->e_phoff); ldso.phentsize = ehdr->e_phentsize; - ldso.rel_early_relative = 1; kernel_mapped_dso(&ldso); decode_dyn(&ldso); + /* Prepare storage for to save clobbered REL addends so they + * can be reused in stage 3. There should be very few. If + * something goes wrong and there are a huge number, abort + * instead of risking stack overflow. */ + size_t dyn[DYN_CNT]; + decode_vec(ldso.dynv, dyn, DYN_CNT); + size_t *rel = (void *)(base+dyn[DT_REL]); + size_t rel_size = dyn[DT_RELSZ]; + size_t symbolic_rel_cnt = 0; + apply_addends_to = rel; + for (; rel_size; rel+=2, rel_size-=2*sizeof(size_t)) + if (!IS_RELATIVE(rel[1])) symbolic_rel_cnt++; + if (symbolic_rel_cnt >= ADDEND_LIMIT) a_crash(); + size_t addends[symbolic_rel_cnt+1]; + saved_addends = addends; + head = &ldso; reloc_all(&ldso); ldso.relocated = 0; - ldso.rel_update_got = 1; /* Call dynamic linker stage-3, __dls3, looking it up * symbolically as a barrier against moving the address -- 2.25.1