Merge "linker: avoid clobbering the .dynamic section of shared libs"
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 59b7893..e599b5b 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -93,12 +93,6 @@
 #endif
 
 
-static inline int validate_soinfo(soinfo *si)
-{
-    return (si >= sopool && si < sopool + SO_MAX) ||
-        si == &libdl_info;
-}
-
 static char ldpaths_buf[LDPATH_BUFSIZE];
 static const char *ldpaths[LDPATH_MAX + 1];
 
@@ -427,11 +421,11 @@
 }
 
 static Elf32_Sym *
-soinfo_do_lookup(soinfo *si, const char *name, Elf32_Addr *offset)
+soinfo_do_lookup(soinfo *si, const char *name, Elf32_Addr *offset,
+                 soinfo *needed[])
 {
     unsigned elf_hash = elfhash(name);
     Elf32_Sym *s;
-    unsigned *d;
     soinfo *lsi = si;
     int i;
 
@@ -457,20 +451,13 @@
             goto done;
     }
 
-    for(d = si->dynamic; *d; d += 2) {
-        if(d[0] == DT_NEEDED){
-            lsi = (soinfo *)d[1];
-            if (!validate_soinfo(lsi)) {
-                DL_ERR("bad DT_NEEDED pointer in \"%s\"", lsi->name);
-                return NULL;
-            }
-
-            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)
-                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)
+            goto done;
     }
 
 #if ALLOW_SYMBOLS_FROM_MAIN
@@ -857,11 +844,29 @@
     return si;
 }
 
-soinfo *find_library(const char *name)
+static soinfo *find_loaded_library(const char *name)
 {
     soinfo *si;
     const char *bname;
 
+    // TODO: don't use basename only for determining libraries
+    // http://code.google.com/p/android/issues/detail?id=6670
+
+    bname = strrchr(name, '/');
+    bname = bname ? bname + 1 : name;
+
+    for(si = solist; si != NULL; si = si->next){
+        if(!strcmp(bname, si->name)) {
+            return si;
+        }
+    }
+    return NULL;
+}
+
+soinfo *find_library(const char *name)
+{
+    soinfo *si;
+
 #if ALLOW_SYMBOLS_FROM_MAIN
     if (name == NULL)
         return somain;
@@ -870,19 +875,15 @@
         return NULL;
 #endif
 
-    bname = strrchr(name, '/');
-    bname = bname ? bname + 1 : name;
-
-    for(si = solist; si != 0; si = si->next){
-        if(!strcmp(bname, si->name)) {
-            if(si->flags & FLAG_ERROR) {
-                DL_ERR("\"%s\" failed to load previously", bname);
-                return NULL;
-            }
-            if(si->flags & FLAG_LINKED) return si;
-            DL_ERR("OOPS: recursive link to \"%s\"", si->name);
+    si = find_loaded_library(name);
+    if (si != NULL) {
+        if(si->flags & FLAG_ERROR) {
+            DL_ERR("\"%s\" failed to load previously", name);
             return NULL;
         }
+        if(si->flags & FLAG_LINKED) return si;
+        DL_ERR("OOPS: recursive link to \"%s\"", si->name);
+        return NULL;
     }
 
     TRACE("[ %5d '%s' has not been loaded yet.  Locating...]\n", pid, name);
@@ -903,27 +904,11 @@
         TRACE("%5d unloading '%s'\n", pid, si->name);
         call_destructors(si);
 
-        /*
-         * Make sure that we undo the PT_GNU_RELRO protections we added
-         * in soinfo_link_image. This is needed to undo the DT_NEEDED hack below.
-         */
-        if (phdr_table_unprotect_gnu_relro(si->phdr, si->phnum,
-                                           si->load_bias) < 0) {
-            DL_ERR("%s: could not undo GNU_RELRO protections. "
-                    "Expect a crash soon. errno=%d (%s)",
-                    si->name, errno, strerror(errno));
-        }
-
         for(d = si->dynamic; *d; d += 2) {
             if(d[0] == DT_NEEDED){
-                soinfo *lsi = (soinfo *)d[1];
+                soinfo *lsi = find_loaded_library(si->strtab + d[1]);
 
-                // The next line will segfault if the we don't undo the
-                // PT_GNU_RELRO protections (see comments above and in
-                // soinfo_link_image().
-                d[1] = 0;
-
-                if (validate_soinfo(lsi)) {
+                if (lsi) {
                     TRACE("%5d %s needs to unload %s\n", pid,
                           si->name, lsi->name);
                     soinfo_unload(lsi);
@@ -951,7 +936,8 @@
  * ideal. They should probably be either uint32_t, Elf32_Addr, or unsigned
  * long.
  */
-static int soinfo_relocate(soinfo *si, Elf32_Rel *rel, unsigned count)
+static int soinfo_relocate(soinfo *si, Elf32_Rel *rel, unsigned count,
+                           soinfo *needed[])
 {
     Elf32_Sym *symtab = si->symtab;
     const char *strtab = si->strtab;
@@ -973,7 +959,7 @@
         }
         if(sym != 0) {
             sym_name = (char *)(strtab + symtab[sym].st_name);
-            s = soinfo_do_lookup(si, sym_name, &offset);
+            s = soinfo_do_lookup(si, sym_name, &offset, needed);
             if(s == NULL) {
                 /* We only allow an undefined symbol if this is a weak
                    reference..   */
@@ -1171,7 +1157,7 @@
 }
 
 #ifdef ANDROID_MIPS_LINKER
-int mips_relocate_got(struct soinfo *si)
+int mips_relocate_got(struct soinfo *si, soinfo *needed[])
 {
     unsigned *got;
     unsigned local_gotno, gotsym, symtabno;
@@ -1216,7 +1202,7 @@
 
         /* This is an undefined reference... try to locate it */
         sym_name = si->strtab + sym->st_name;
-        s = soinfo_do_lookup(si, sym_name, &base);
+        s = soinfo_do_lookup(si, sym_name, &base, needed);
         if (s == NULL) {
             /* We only allow an undefined symbol if this is a weak
                reference..   */
@@ -1312,9 +1298,10 @@
         unsigned *d;
         for(d = si->dynamic; *d; d += 2) {
             if(d[0] == DT_NEEDED){
-                soinfo* lsi = (soinfo *)d[1];
-                if (!validate_soinfo(lsi)) {
-                    DL_ERR("bad DT_NEEDED pointer in \"%s\"", si->name);
+                soinfo* lsi = find_loaded_library(si->strtab + d[1]);
+                if (!lsi) {
+                    DL_ERR("\"%s\": could not initialize dependent library",
+                           si->name);
                 } else {
                     soinfo_call_constructors(lsi);
                 }
@@ -1425,6 +1412,8 @@
     const Elf32_Phdr *phdr = si->phdr;
     int phnum = si->phnum;
     int relocating_linker = (si->flags & FLAG_LINKER) != 0;
+    soinfo **needed, **pneeded;
+    size_t dynamic_count;
 
     /* We can't debug anything until the linker is relocated */
     if (!relocating_linker) {
@@ -1434,7 +1423,8 @@
     }
 
     /* Extract dynamic section */
-    si->dynamic = phdr_table_get_dynamic_section(phdr, phnum, base);
+    phdr_table_get_dynamic_section(phdr, phnum, base, &si->dynamic,
+                                   &dynamic_count);
     if (si->dynamic == NULL) {
         if (!relocating_linker) {
             DL_ERR("missing PT_DYNAMIC?!");
@@ -1607,6 +1597,9 @@
         }
     }
 
+    /* dynamic_count is an upper bound for the number of needed libs */
+    pneeded = needed = (soinfo**) alloca((1 + dynamic_count) * sizeof(soinfo*));
+
     for(d = si->dynamic; *d; d += 2) {
         if(d[0] == DT_NEEDED){
             DEBUG("%5d %s needs %s\n", pid, si->name, si->strtab + d[1]);
@@ -1617,17 +1610,11 @@
                        si->strtab + d[1], si->name, tmp_err_buf);
                 goto fail;
             }
-            /* Save the soinfo of the loaded DT_NEEDED library in the payload
-               of the DT_NEEDED entry itself, so that we can retrieve the
-               soinfo directly later from the dynamic segment.  This is a hack,
-               but it allows us to map from DT_NEEDED to soinfo efficiently
-               later on when we resolve relocations, trying to look up a symbol
-               with dlsym().
-            */
-            d[1] = (unsigned)lsi;
+            *pneeded++ = lsi;
             lsi->refcount++;
         }
     }
+    *pneeded = NULL;
 
     if (si->has_text_relocations) {
         /* Unprotect the segments, i.e. make them writable, to allow
@@ -1644,17 +1631,17 @@
 
     if(si->plt_rel) {
         DEBUG("[ %5d relocating %s plt ]\n", pid, si->name );
-        if(soinfo_relocate(si, si->plt_rel, si->plt_rel_count))
+        if(soinfo_relocate(si, si->plt_rel, si->plt_rel_count, needed))
             goto fail;
     }
     if(si->rel) {
         DEBUG("[ %5d relocating %s ]\n", pid, si->name );
-        if(soinfo_relocate(si, si->rel, si->rel_count))
+        if(soinfo_relocate(si, si->rel, si->rel_count, needed))
             goto fail;
     }
 
 #ifdef ANDROID_MIPS_LINKER
-    if(mips_relocate_got(si)) {
+    if(mips_relocate_got(si, needed)) {
         goto fail;
     }
 #endif
@@ -1849,8 +1836,8 @@
     Elf32_Ehdr *elf_hdr = (Elf32_Ehdr *) linker_base;
     Elf32_Phdr *phdr =
         (Elf32_Phdr *)((unsigned char *) linker_base + elf_hdr->e_phoff);
-    linker_soinfo.dynamic =
-        phdr_table_get_dynamic_section(phdr, elf_hdr->e_phnum, linker_base);
+    phdr_table_get_dynamic_section(phdr, elf_hdr->e_phnum, linker_base,
+                                   &linker_soinfo.dynamic, NULL);
     insert_soinfo_into_debug_map(&linker_soinfo);
 
         /* extract information passed from the kernel */
diff --git a/linker/linker_phdr.c b/linker/linker_phdr.c
index beb756f..19ed7ba 100644
--- a/linker/linker_phdr.c
+++ b/linker/linker_phdr.c
@@ -468,11 +468,9 @@
  * specified by one or more PT_GNU_RELRO segments. This must be always
  * performed after relocations.
  *
- * NOTE: One must call phdr_table_unprotect_gnu_relro() before calling
- *        the library's destructors, in order to ensure that the .dynamic
- *        section is writable (as well as the .data.relro section that
- *        might contain the content of static constant C++ objects that
- *        needs to be destroyed).
+ * The areas typically covered are .got and .data.rel.ro, these are
+ * read-only from the program's POV, but contain absolute addresses
+ * that need to be relocated before use.
  *
  * Input:
  *   phdr_table  -> program header table
@@ -492,27 +490,6 @@
                                           PROT_READ);
 }
 
-/* Un-apply GNU relro protection if specified by the program header.
- * See comment for phdr_table_protect_gnu_relro.
- *
- * Input:
- *   phdr_table  -> program header table
- *   phdr_count  -> number of entires in tables
- *   load_bias   -> load bias
- * Return:
- *   0 on error, -1 on failure (error code in errno).
- */
-int
-phdr_table_unprotect_gnu_relro(const Elf32_Phdr* phdr_table,
-                               int               phdr_count,
-                               Elf32_Addr        load_bias)
-{
-    return _phdr_table_set_gnu_relro_prot(phdr_table,
-                                          phdr_count,
-                                          load_bias,
-                                          PROT_READ|PROT_WRITE);
-}
-
 #ifdef ANDROID_ARM_LINKER
 
 #  ifndef PT_ARM_EXIDX
@@ -556,30 +533,44 @@
 }
 #endif /* ANDROID_ARM_LINKER */
 
-/* Return the address of the ELF file's .dynamic section in memory,
+/* Return the address and size of the ELF file's .dynamic section in memory,
  * or NULL if missing.
  *
  * Input:
  *   phdr_table  -> program header table
  *   phdr_count  -> number of entires in tables
  *   load_bias   -> load bias
+ * Output:
+ *   dynamic       -> address of table in memory (NULL on failure).
+ *   dynamic_count -> number of items in table (0 on failure).
  * Return:
- *   0 on error, -1 on failure (_no_ error code in errno)
+ *   void
  */
-Elf32_Addr*
+void
 phdr_table_get_dynamic_section(const Elf32_Phdr* phdr_table,
                                int               phdr_count,
-                               Elf32_Addr        load_bias)
+                               Elf32_Addr        load_bias,
+                               Elf32_Addr**      dynamic,
+                               size_t*           dynamic_count)
 {
     const Elf32_Phdr* phdr = phdr_table;
     const Elf32_Phdr* phdr_limit = phdr + phdr_count;
 
     for (phdr = phdr_table; phdr < phdr_limit; phdr++) {
-        if (phdr->p_type == PT_DYNAMIC) {
-            return (Elf32_Addr*)(load_bias + phdr->p_vaddr);
+        if (phdr->p_type != PT_DYNAMIC) {
+            continue;
         }
+
+        *dynamic = (Elf32_Addr*)(load_bias + phdr->p_vaddr);
+        if (dynamic_count) {
+            *dynamic_count = (unsigned)(phdr->p_memsz / 8);
+        }
+        return;
     }
-    return NULL;
+    *dynamic = NULL;
+    if (dynamic_count) {
+        *dynamic_count = 0;
+    }
 }
 
 /* Return the address of the program header table as it appears in the loaded
diff --git a/linker/linker_phdr.h b/linker/linker_phdr.h
index 753c7e7..a759262 100644
--- a/linker/linker_phdr.h
+++ b/linker/linker_phdr.h
@@ -86,11 +86,6 @@
                              int               phdr_count,
                              Elf32_Addr        load_bias);
 
-int
-phdr_table_unprotect_gnu_relro(const Elf32_Phdr* phdr_table,
-                               int               phdr_count,
-                               Elf32_Addr        load_bias);
-
 const Elf32_Phdr*
 phdr_table_get_loaded_phdr(const Elf32_Phdr*   phdr_table,
                            int                 phdr_count,
@@ -105,10 +100,12 @@
                          unsigned*         arm_exidix_count);
 #endif
 
-Elf32_Addr*
+void
 phdr_table_get_dynamic_section(const Elf32_Phdr* phdr_table,
                                int               phdr_count,
-                               Elf32_Addr        load_bias);
+                               Elf32_Addr        load_bias,
+                               Elf32_Addr**      dynamic,
+                               size_t*           dynamic_count);
 
 #ifdef __cplusplus
 };