Bug 1423822 - Handle more cases of pointer reuse in DT_INIT_ARRAY. r=froydnj draft
authorMike Hommey <mh+mozilla@glandium.org>
Tue, 31 Jul 2018 16:13:35 +0900
changeset 825728 c7493f77f0a55ad63434c9fb7c41e4f0bf660b6a
parent 825727 ab27c1e9ef9cab5987706df24b8dc96af3a72668
child 825729 9ca3ce5300f0923a6e14bb1661cd834079ba4c85
push id118154
push userbmo:mh+mozilla@glandium.org
push dateThu, 02 Aug 2018 05:40:11 +0000
reviewersfroydnj
bugs1423822
milestone63.0a1
Bug 1423822 - Handle more cases of pointer reuse in DT_INIT_ARRAY. r=froydnj
build/unix/elfhack/elfhack.cpp
--- a/build/unix/elfhack/elfhack.cpp
+++ b/build/unix/elfhack/elfhack.cpp
@@ -570,17 +570,18 @@ int do_relocation_section(Elf *elf, unsi
     ElfRelHack_Section *relhack = new ElfRelHack_Section(relhack_section);
 
     ElfSymtab_Section *symtab = (ElfSymtab_Section *) section->getLink();
     Elf_SymValue *sym = symtab->lookup("__cxa_pure_virtual");
 
     std::vector<Rel_Type> new_rels;
     Elf_RelHack relhack_entry;
     relhack_entry.r_offset = relhack_entry.r_info = 0;
-    size_t init_array_reloc = 0;
+    std::vector<Rel_Type> init_array_relocs;
+    size_t init_array_insert = 0;
     for (typename std::vector<Rel_Type>::iterator i = section->rels.begin();
          i != section->rels.end(); ++i) {
         // We don't need to keep R_*_NONE relocations
         if (!ELF32_R_TYPE(i->r_info))
             continue;
         ElfLocation loc(i->r_offset, elf);
         // __cxa_pure_virtual is a function used in vtables to point at pure
         // virtual methods. The __cxa_pure_virtual function usually abort()s.
@@ -607,24 +608,21 @@ int do_relocation_section(Elf *elf, unsi
                 // have absolute relocations (rel_type2) for it.
                 if ((ELF32_R_TYPE(i->r_info) == rel_type2) &&
                     (sym == &symtab->syms[ELF32_R_SYM(i->r_info)])) {
                     memset((char *)loc.getBuffer(), 0, entry_sz);
                     continue;
                 }
             }
         }
-        // Keep track of the relocation associated with the first init_array entry.
-        if (init_array && i->r_offset == init_array->getAddr()) {
-            if (init_array_reloc) {
-                fprintf(stderr, "Found multiple relocations for the first init_array entry. Skipping\n");
-                return -1;
-            }
-            new_rels.push_back(*i);
-            init_array_reloc = new_rels.size();
+        // Keep track of the relocations associated with the init_array section.
+        if (init_array && i->r_offset >= init_array->getAddr() &&
+            i->r_offset < init_array->getAddr() + init_array->getSize()) {
+            init_array_relocs.push_back(*i);
+            init_array_insert = new_rels.size();
         } else if (!(loc.getSection()->getFlags() & SHF_WRITE) || (ELF32_R_TYPE(i->r_info) != rel_type)) {
             // Don't pack relocations happening in non writable sections.
             // Our injected code is likely not to be allowed to write there.
             new_rels.push_back(*i);
         } else {
             // With Elf_Rel, the value pointed by the relocation offset is the addend.
             // With Elf_Rela, the addend is in the relocation entry, but the elfhacked
             // relocation info doesn't contain it. Elfhack relies on the value pointed
@@ -651,59 +649,85 @@ int do_relocation_section(Elf *elf, unsi
         }
     }
     if (relhack_entry.r_offset)
         relhack->push_back(relhack_entry);
     // Last entry must be nullptr
     relhack_entry.r_offset = relhack_entry.r_info = 0;
     relhack->push_back(relhack_entry);
 
-    if (init_array && !init_array_reloc) {
+    if (init_array) {
         // Some linkers create a DT_INIT_ARRAY section that, for all purposes,
         // is empty: it only contains 0x0 or 0xffffffff pointers with no relocations.
+        // In some other cases, there can be null pointers with no relocations in
+        // the middle of the section. Example: crtend_so.o in the Android NDK contains
+        // a sized .init_array with a null pointer and no relocation, which ends up
+        // in all Android libraries, and in some cases it ends up in the middle of
+        // the final .init_array section.
+        // If we have such a reusable slot at the beginning of .init_array, we just
+        // use it. It we have one in the middle of .init_array, we slide its content
+        // to move the "hole" at the beginning and use it there (we need our injected
+        // code to run before any other).
+        // Otherwise, replace the first entry and keep the original pointer.
+        std::sort(init_array_relocs.begin(), init_array_relocs.end(),
+                  [](Rel_Type& a, Rel_Type& b) { return a.r_offset < b.r_offset; });
+        size_t expected = init_array->getAddr();
         const size_t zero = 0;
         const size_t all = SIZE_MAX;
         const char *data = init_array->getData();
         size_t length = Elf_Addr::size(elf->getClass());
-        bool empty = true;
-        for (size_t off = 0; off < init_array->getSize(); off += length) {
-            if (memcmp(data + off, &zero, length) &&
-                memcmp(data + off, &all, length)) {
-                empty = false;
+	size_t off = 0;
+	for (; off < init_array_relocs.size(); off++) {
+            auto& r = init_array_relocs[off];
+            if (r.r_offset >= expected + length &&
+                (memcmp(data + off * length, &zero, length) == 0 ||
+                 memcmp(data + off * length, &all, length) == 0)) {
+                // We found a hole, move the preceding entries.
+                while (off) {
+                    auto& p = init_array_relocs[--off];
+                    if (ELF32_R_TYPE(p.r_info) == rel_type) {
+                        unsigned int addend = get_addend(&p, elf);
+                        p.r_offset += length;
+                        set_relative_reloc(&p, elf, addend);
+                    } else {
+                        fprintf(stderr, "Unsupported relocation type in DT_INIT_ARRAY. Skipping\n");
+                        return -1;
+                    }
+                }
                 break;
             }
+            expected = r.r_offset + length;
+        }
+
+        if (off == 0) {
+            // We either found a hole above, and can now use the first entry,
+            // or the init_array section is effectively empty (see further above)
+            // and we also can use the first entry.
+            // Either way, code further below will take care of actually setting
+            // the right r_info and r_added for the relocation.
+            Rel_Type rel;
+            rel.r_offset = init_array->getAddr();
+            init_array_relocs.insert(init_array_relocs.begin(), rel);
+        } else {
+            // Use relocated value of DT_INIT_ARRAY's first entry for the
+            // function to be called by the injected code.
+            auto& rel = init_array_relocs[0];
+            unsigned int addend = get_addend(&rel, elf);
+            if (ELF32_R_TYPE(rel.r_info) == rel_type) {
+                original_init = addend;
+            } else if (ELF32_R_TYPE(rel.r_info) == rel_type2) {
+                ElfSymtab_Section *symtab = (ElfSymtab_Section *)section->getLink();
+                original_init = symtab->syms[ELF32_R_SYM(rel.r_info)].value.getValue() + addend;
+            } else {
+                fprintf(stderr, "Unsupported relocation type for DT_INIT_ARRAY's first entry. Skipping\n");
+                return -1;
+            }
         }
-	// If we encounter such an empty DT_INIT_ARRAY section, we add a
-	// relocation for its first entry to point to our init. Code further
-	// below will take care of actually setting the right r_info and
-	// r_addend for the relocation, as if we had a normal DT_INIT_ARRAY
-	// section.
-        if (empty) {
-            new_rels.emplace_back();
-            init_array_reloc = new_rels.size();
-            Rel_Type *rel = &new_rels[init_array_reloc - 1];
-            rel->r_offset = init_array->getAddr();
-        } else {
-            fprintf(stderr, "Didn't find relocation for DT_INIT_ARRAY's first entry. Skipping\n");
-            return -1;
-        }
-    } else if (init_array) {
-        Rel_Type *rel = &new_rels[init_array_reloc - 1];
-        unsigned int addend = get_addend(rel, elf);
-        // Use relocated value of DT_INIT_ARRAY's first entry for the
-        // function to be called by the injected code.
-        if (ELF32_R_TYPE(rel->r_info) == rel_type) {
-            original_init = addend;
-        } else if (ELF32_R_TYPE(rel->r_info) == rel_type2) {
-            ElfSymtab_Section *symtab = (ElfSymtab_Section *)section->getLink();
-            original_init = symtab->syms[ELF32_R_SYM(rel->r_info)].value.getValue() + addend;
-        } else {
-            fprintf(stderr, "Unsupported relocation type for DT_INIT_ARRAY's first entry. Skipping\n");
-            return -1;
-        }
+
+        new_rels.insert(std::next(new_rels.begin(), init_array_insert), init_array_relocs.begin(), init_array_relocs.end());
     }
 
     unsigned int mprotect_cb = 0;
     unsigned int sysconf_cb = 0;
     // If there is a relro segment, our injected code will run after the linker sets the
     // corresponding pages read-only. We need to make our code change that to read-write
     // before applying relocations, which means it needs to call mprotect.
     // To do that, we need to find a reference to the mprotect symbol. In case the library
@@ -821,19 +845,19 @@ int do_relocation_section(Elf *elf, unsi
 
     // Ensure Elf sections will be at their final location.
     elf->normalize();
     ElfLocation *init = new ElfLocation(relhackcode, relhackcode->getEntryPoint());
     if (init_array) {
         // Adjust the first DT_INIT_ARRAY entry to point at the injected code
         // by transforming its relocation into a relative one pointing to the
         // address of the injected code.
-        Rel_Type *rel = &section->rels[init_array_reloc - 1];
+        Rel_Type *rel = &section->rels[init_array_insert];
         rel->r_info = ELF32_R_INFO(0, rel_type); // Set as a relative relocation
-        set_relative_reloc(&section->rels[init_array_reloc - 1], elf, init->getValue());
+        set_relative_reloc(rel, elf, init->getValue());
     } else if (!dyn->setValueForType(DT_INIT, init)) {
         fprintf(stderr, "Can't grow .dynamic section to set DT_INIT. Skipping\n");
         return -1;
     }
     // TODO: adjust the value according to the remaining number of relative relocations
     if (dyn->getValueForType(Rel_Type::d_tag_count))
         dyn->setValueForType(Rel_Type::d_tag_count, new ElfPlainValue(0));