reprocess all libc/ldso symbolic relocations in dynamic linking stage 3
authorRich Felker <dalias@aerifal.cx>
Tue, 26 May 2015 03:33:59 +0000 (23:33 -0400)
committerRich Felker <dalias@aerifal.cx>
Tue, 26 May 2015 03:33:59 +0000 (23:33 -0400)
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

index 3842aeba1e7b494bed9c624fecad3c5bd74ed284..42930adfb44a6a46772addcedd0ad147402c9fdf 100644 (file)
@@ -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