linker: handle R_ARM_COPY relocations in a proper way

If an executable contain copy relocations, other references
to the symbol it points to should be preempted and made to
point to the copy instead.

Also, the linker should make sure the target area has
sufficient space to contain the copy. It also checks
whether the library that supplies the symbol is built
with -Bsymbolic, and errors out if this is the case.

Change-Id: If135c83590092741cfd8f82f54816f363a4a4a3b
Signed-off-by: Ard Biesheuvel <ard.biesheuvel@gmail.com>
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 4c52f3d..9e4138e 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -51,7 +51,6 @@
 #include "linker_format.h"
 #include "linker_phdr.h"
 
-#define ALLOW_SYMBOLS_FROM_MAIN 1
 #define SO_MAX 128
 
 /* Assume average path length of 64 and max 8 paths */
@@ -89,9 +88,7 @@
 static soinfo *freelist = NULL;
 static soinfo *solist = &libdl_info;
 static soinfo *sonext = &libdl_info;
-#if ALLOW_SYMBOLS_FROM_MAIN
 static soinfo *somain; /* main process, always the one after libdl_info */
-#endif
 
 static char ldpaths_buf[LDPATH_BUFSIZE];
 static const char *ldpaths[LDPATH_MAX + 1];
@@ -421,15 +418,31 @@
 }
 
 static Elf32_Sym *
-soinfo_do_lookup(soinfo *si, const char *name, Elf32_Addr *offset,
-                 soinfo *needed[], bool ignore_local)
+soinfo_do_lookup(soinfo *si, const char *name, soinfo **lsi,
+                 soinfo *needed[])
 {
     unsigned elf_hash = elfhash(name);
     Elf32_Sym *s = NULL;
-    soinfo *lsi = si;
     int i;
 
-    if (!ignore_local) {
+    if (si != NULL) {
+
+        /*
+         * If this object was built with symbolic relocations disabled, the
+         * first place to look to resolve external references is the main
+         * executable.
+         */
+
+        if (!si->has_DT_SYMBOLIC) {
+            DEBUG("%5d %s: looking up %s in executable %s\n",
+                  pid, si->name, name, somain->name);
+            s = soinfo_elf_lookup(somain, elf_hash, name);
+            if (s != NULL) {
+                *lsi = somain;
+                goto done;
+            }
+        }
+
         /* Look for symbols in the local scope (the object who is
          * searching). This happens with C++ templates on i386 for some
          * reason.
@@ -441,47 +454,37 @@
          * Here we return the first definition found for simplicity.  */
 
         s = soinfo_elf_lookup(si, elf_hash, name);
-        if(s != NULL)
+        if (s != NULL) {
+            *lsi = si;
             goto done;
+        }
     }
 
     /* Next, look for it in the preloads list */
     for(i = 0; preloads[i] != NULL; i++) {
-        lsi = preloads[i];
-        s = soinfo_elf_lookup(lsi, elf_hash, name);
-        if(s != NULL)
+        s = soinfo_elf_lookup(preloads[i], elf_hash, name);
+        if(s != NULL) {
+            *lsi = preloads[i];
             goto done;
+        }
     }
 
     for(i = 0; needed[i] != NULL; i++) {
-        lsi = needed[i];
         DEBUG("%5d %s: looking up %s in %s\n",
-              pid, si->name, name, lsi->name);
-        s = soinfo_elf_lookup(lsi, elf_hash, name);
-        if (s != NULL)
+              pid, si->name, name, needed[i]->name);
+        s = soinfo_elf_lookup(needed[i], elf_hash, name);
+        if (s != NULL) {
+            *lsi = needed[i];
             goto done;
+        }
     }
 
-#if ALLOW_SYMBOLS_FROM_MAIN
-    /* If we are resolving relocations while dlopen()ing a library, it's OK for
-     * the library to resolve a symbol that's defined in the executable itself,
-     * although this is rare and is generally a bad idea.
-     */
-    if (somain) {
-        lsi = somain;
-        DEBUG("%5d %s: looking up %s in executable %s\n",
-              pid, si->name, name, lsi->name);
-        s = soinfo_elf_lookup(lsi, elf_hash, name);
-    }
-#endif
-
 done:
     if(s != NULL) {
         TRACE_TYPE(LOOKUP, "%5d si %s sym %s s->st_value = 0x%08x, "
                    "found in %s, base = 0x%08x, load bias = 0x%08x\n",
                    pid, si->name, name, s->st_value,
-                   lsi->name, lsi->base, lsi->load_bias);
-        *offset = lsi->load_bias;
+                   (*lsi)->name, (*lsi)->base, (*lsi)->load_bias);
         return s;
     }
 
@@ -863,13 +866,8 @@
 {
     soinfo *si;
 
-#if ALLOW_SYMBOLS_FROM_MAIN
     if (name == NULL)
         return somain;
-#else
-    if (name == NULL)
-        return NULL;
-#endif
 
     si = find_loaded_library(name);
     if (si != NULL) {
@@ -933,8 +931,8 @@
     Elf32_Sym *symtab = si->symtab;
     const char *strtab = si->strtab;
     Elf32_Sym *s;
-    Elf32_Addr offset;
     Elf32_Rel *start = rel;
+    soinfo *lsi;
 
     for (size_t idx = 0; idx < count; ++idx, ++rel) {
         unsigned type = ELF32_R_TYPE(rel->r_info);
@@ -950,11 +948,7 @@
         }
         if(sym != 0) {
             sym_name = (char *)(strtab + symtab[sym].st_name);
-            bool ignore_local = false;
-#if defined(ANDROID_ARM_LINKER)
-            ignore_local = (type == R_ARM_COPY);
-#endif
-            s = soinfo_do_lookup(si, sym_name, &offset, needed, ignore_local);
+            s = soinfo_do_lookup(si, sym_name, &lsi, needed);
             if(s == NULL) {
                 /* We only allow an undefined symbol if this is a weak
                    reference..   */
@@ -1020,7 +1014,7 @@
                     return -1;
                 }
 #endif
-                sym_addr = (unsigned)(s->st_value + offset);
+                sym_addr = (unsigned)(s->st_value + lsi->load_bias);
             }
             count_relocation(kRelocSymbol);
         } else {
@@ -1154,10 +1148,27 @@
             TRACE_TYPE(RELO, "%5d RELO %08x <- %d @ %08x %s\n", pid,
                        reloc, s->st_size, sym_addr, sym_name);
             if (reloc == sym_addr) {
-                DL_ERR("Internal linker error detected. reloc == symaddr");
+                Elf32_Sym *src = soinfo_do_lookup(NULL, sym_name, &lsi, needed);
+
+                if (src == NULL) {
+                    DL_ERR("%s R_ARM_COPY relocation source cannot be resolved", si->name);
+                    return -1;
+                }
+                if (lsi->has_DT_SYMBOLIC) {
+                    DL_ERR("%s invalid R_ARM_COPY relocation against DT_SYMBOLIC shared "
+                           "library %s (built with -Bsymbolic?)", si->name, lsi->name);
+                    return -1;
+                }
+                if (s->st_size < src->st_size) {
+                    DL_ERR("%s R_ARM_COPY relocation size mismatch (%d < %d)",
+                           si->name, s->st_size, src->st_size);
+                    return -1;
+                }
+                memcpy((void*)reloc, (void*)(src->st_value + lsi->load_bias), src->st_size);
+            } else {
+                DL_ERR("%s R_ARM_COPY relocation target cannot be resolved", si->name);
                 return -1;
             }
-            memcpy((void*)reloc, (void*)sym_addr, s->st_size);
             break;
 #endif /* ANDROID_ARM_LINKER */
 
@@ -1210,12 +1221,12 @@
     got = si->plt_got + local_gotno;
     for (g = gotsym; g < symtabno; g++, sym++, got++) {
         const char *sym_name;
-        unsigned base;
         Elf32_Sym *s;
+        soinfo *lsi;
 
         /* This is an undefined reference... try to locate it */
         sym_name = si->strtab + sym->st_name;
-        s = soinfo_do_lookup(si, sym_name, &base, needed, false);
+        s = soinfo_do_lookup(si, sym_name, &lsi, needed);
         if (s == NULL) {
             /* We only allow an undefined symbol if this is a weak
                reference..   */
@@ -1231,7 +1242,7 @@
              * For reference see NetBSD link loader
              * http://cvsweb.netbsd.org/bsdweb.cgi/src/libexec/ld.elf_so/arch/mips/mips_reloc.c?rev=1.53&content-type=text/x-cvsweb-markup
              */
-             *got = base + s->st_value;
+             *got = lsi->load_bias + s->st_value;
         }
     }
     return 0;
@@ -1537,6 +1548,19 @@
         case DT_TEXTREL:
             si->has_text_relocations = true;
             break;
+        case DT_SYMBOLIC:
+            si->has_DT_SYMBOLIC = true;
+            break;
+#if defined(DT_FLAGS)
+        case DT_FLAGS:
+            if (*d & DF_TEXTREL) {
+                si->has_text_relocations = true;
+            }
+            if (*d & DF_SYMBOLIC) {
+                si->has_DT_SYMBOLIC = true;
+            }
+            break;
+#endif
 #if defined(ANDROID_MIPS_LINKER)
         case DT_NEEDED:
         case DT_STRSZ:
@@ -1868,6 +1892,8 @@
     parse_LD_LIBRARY_PATH(ldpath_env);
     parse_LD_PRELOAD(ldpreload_env);
 
+    somain = si;
+
     if(soinfo_link_image(si)) {
         char errmsg[] = "CANNOT LINK EXECUTABLE\n";
         write(2, __linker_dl_err_buf, strlen(__linker_dl_err_buf));
@@ -1889,14 +1915,6 @@
     map->l_addr = si->base;
     soinfo_call_constructors(si);
 
-#if ALLOW_SYMBOLS_FROM_MAIN
-    /* Set somain after we've loaded all the libraries in order to prevent
-     * linking of symbols back to the main image, which is not set up at that
-     * point yet.
-     */
-    somain = si;
-#endif
-
 #if TIMING
     gettimeofday(&t1,NULL);
     PRINT("LINKER TIME: %s: %d microseconds\n", argv[0], (int) (