Bug 635961 - Allow elfhack to relocate data under the GNU_RELRO segment. r=froydnj
☠☠ backed out by 6af0ca47efab ☠ ☠
authorMike Hommey <mh+mozilla@glandium.org>
Tue, 11 Jul 2017 07:41:07 +0900
changeset 422040 ddda63d5366eca8bec018171271fbb426321eab1
parent 422039 38778100700eb9e90b829a9f045fedc97a8cff00
child 422041 c56fa9c1eda0243aef35cbb81f105957fff6dd31
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs635961
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 635961 - Allow elfhack to relocate data under the GNU_RELRO segment. r=froydnj
build/unix/elfhack/elfhack.cpp
build/unix/elfhack/elfxx.h
build/unix/elfhack/inject.c
--- a/build/unix/elfhack/elfhack.cpp
+++ b/build/unix/elfhack/elfhack.cpp
@@ -84,18 +84,18 @@ public:
         shdr.sh_size = rels.size() * shdr.sh_entsize;
     }
 private:
     std::vector<Elf_RelHack> rels;
 };
 
 class ElfRelHackCode_Section: public ElfSection {
 public:
-    ElfRelHackCode_Section(Elf_Shdr &s, Elf &e, unsigned int init)
-    : ElfSection(s, nullptr, nullptr), parent(e), init(init) {
+    ElfRelHackCode_Section(Elf_Shdr &s, Elf &e, unsigned int init, unsigned int mprotect_cb)
+    : ElfSection(s, nullptr, nullptr), parent(e), init(init), mprotect_cb(mprotect_cb) {
         std::string file(rundir);
         file += "/inject/";
         switch (parent.getMachine()) {
         case EM_386:
             file += "x86";
             break;
         case EM_X86_64:
             file += "x86_64";
@@ -120,19 +120,27 @@ public:
         for (ElfSection *section = elf->getSection(1); section != nullptr;
              section = section->getNext()) {
             if (section->getType() == SHT_SYMTAB)
                 symtab = (ElfSymtab_Section *) section;
         }
         if (symtab == nullptr)
             throw std::runtime_error("Couldn't find a symbol table for the injected code");
 
+        relro = parent.getSegmentByType(PT_GNU_RELRO);
+        align = parent.getSegmentByType(PT_LOAD)->getAlign();
+
         // Find the init symbol
         entry_point = -1;
-        Elf_SymValue *sym = symtab->lookup(init ? "init" : "init_noinit");
+        std::string symbol = "init";
+        if (!init)
+            symbol += "_noinit";
+        if (relro)
+            symbol += "_relro";
+        Elf_SymValue *sym = symtab->lookup(symbol.c_str());
         if (!sym)
             throw std::runtime_error("Couldn't find an 'init' symbol in the injected code");
 
         entry_point = sym->value.getValue();
 
         // Get all relevant sections from the injected code object.
         add_code_section(sym->value.getSection());
 
@@ -348,16 +356,24 @@ private:
                 if (strcmp(name, "relhack") == 0) {
                     addr = getNext()->getAddr();
                 } else if (strcmp(name, "elf_header") == 0) {
                     // TODO: change this ungly hack to something better
                     ElfSection *ehdr = parent.getSection(1)->getPrevious()->getPrevious();
                     addr = ehdr->getAddr();
                 } else if (strcmp(name, "original_init") == 0) {
                     addr = init;
+                } else if (relro && strcmp(name, "mprotect_cb") == 0) {
+                    addr = mprotect_cb;
+                } else if (relro && strcmp(name, "relro_start") == 0) {
+                    // Align relro segment start to the start of the page it starts in.
+                    addr = relro->getAddr() & ~(align - 1);
+                    // Align relro segment end to the start of the page it ends into.
+                } else if (relro && strcmp(name, "relro_end") == 0) {
+                    addr = (relro->getAddr() + relro->getMemSize()) & ~(align - 1);
                 } else if (strcmp(name, "_GLOBAL_OFFSET_TABLE_") == 0) {
                     // We actually don't need a GOT, but need it as a reference for
                     // GOTOFF relocations. We'll just use the start of the ELF file
                     addr = 0;
                 } else if (strcmp(name, "") == 0) {
                     // This is for R_ARM_V4BX, until we find something better
                     addr = -1;
                 } else {
@@ -398,17 +414,20 @@ private:
                 throw std::runtime_error("Unsupported relocation type");
             }
         }
     }
 
     Elf *elf, &parent;
     std::vector<ElfSection *> code;
     unsigned int init;
+    unsigned int mprotect_cb;
     int entry_point;
+    ElfSegment *relro;
+    unsigned int align;
 };
 
 unsigned int get_addend(Elf_Rel *rel, Elf *elf) {
     ElfLocation loc(rel->r_offset, elf);
     Elf_Addr addr(loc.getBuffer(), Elf_Addr::size(elf->getClass()), elf->getClass(), elf->getData());
     return addr.value;
 }
 
@@ -498,18 +517,16 @@ template <typename Rel_Type>
 int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type2, bool force, bool fill)
 {
     ElfDynamic_Section *dyn = elf->getDynSection();
     if (dyn == nullptr) {
         fprintf(stderr, "Couldn't find SHT_DYNAMIC section\n");
         return -1;
     }
 
-    ElfSegment *relro = elf->getSegmentByType(PT_GNU_RELRO);
-
     ElfRel_Section<Rel_Type> *section = (ElfRel_Section<Rel_Type> *)dyn->getSectionForType(Rel_Type::d_tag);
     assert(section->getType() == Rel_Type::sh_type);
 
     Elf32_Shdr relhack32_section =
         { 0, SHT_PROGBITS, SHF_ALLOC, 0, (Elf32_Off)-1, 0, SHN_UNDEF, 0,
           Elf_RelHack::size(elf->getClass()), Elf_RelHack::size(elf->getClass()) }; // TODO: sh_addralign should be an alignment, not size
     Elf32_Shdr relhackcode32_section =
         { 0, SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR, 0, (Elf32_Off)-1, 0,
@@ -590,19 +607,17 @@ int do_relocation_section(Elf *elf, unsi
         // 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();
-        } else if (!(loc.getSection()->getFlags() & SHF_WRITE) || (ELF32_R_TYPE(i->r_info) != rel_type) ||
-                   (relro && (i->r_offset >= relro->getAddr()) &&
-                   (i->r_offset < relro->getAddr() + relro->getMemSize()))) {
+        } 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 {
             // TODO: check that i->r_addend == *i->r_offset
             if (i->r_offset == relhack_entry.r_offset + relhack_entry.r_info * entry_sz) {
                 relhack_entry.r_info++;
             } else {
@@ -661,20 +676,82 @@ int do_relocation_section(Elf *elf, unsi
             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;
         }
     }
 
+    unsigned int mprotect_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
+    // already has one, we use that, but otherwise, we add the symbol.
+    // Then the injected code needs to be able to call the corresponding function, which
+    // means it needs access to a pointer to it. We get such a pointer by making the linker
+    // apply a relocation for the symbol at an address our code can read.
+    // The problem here is that there is not much relocated space where we can put such a
+    // pointer, so we abuse the bss section temporarily (it will be restored to a null
+    // value before any code can actually use it)
+    if (elf->getSegmentByType(PT_GNU_RELRO)) {
+        Elf_SymValue *mprotect = symtab->lookup("mprotect", STT(FUNC));
+        if (!mprotect) {
+            symtab->syms.emplace_back();
+            mprotect = &symtab->syms.back();
+            symtab->grow(symtab->syms.size() * symtab->getEntSize());
+            mprotect->name = ((ElfStrtab_Section *)symtab->getLink())->getStr("mprotect");
+            mprotect->info = ELF32_ST_INFO(STB_GLOBAL, STT_FUNC);
+            mprotect->other = STV_DEFAULT;
+            new (&mprotect->value) ElfLocation(nullptr, 0, ElfLocation::ABSOLUTE);
+            mprotect->size = 0;
+            mprotect->defined = false;
+
+            // The DT_VERSYM data (in the .gnu.version section) has the same number of
+            // entries as the symbols table. Since we added one entry there, we need to
+            // add one entry here. Zeroes in the extra data means no version for that
+            // symbol, which is the simplest thing to do.
+            ElfSection *gnu_versym = dyn->getSectionForType(DT_VERSYM);
+            if (gnu_versym) {
+               gnu_versym->grow(gnu_versym->getSize() + gnu_versym->getEntSize());
+            }
+        }
+
+        // Add a relocation for the mprotect symbol.
+        new_rels.emplace_back();
+        Rel_Type &rel = new_rels.back();
+        memset(&rel, 0, sizeof(rel));
+        rel.r_info = ELF32_R_INFO(std::distance(symtab->syms.begin(), std::vector<Elf_SymValue>::iterator(mprotect)), rel_type2);
+
+        // Find the beginning of the bss section, and use an aligned location in there
+        // for the relocation.
+        for (ElfSegment *segment = elf->getSegmentByType(PT_LOAD); segment;
+             segment = elf->getSegmentByType(PT_LOAD, segment)) {
+            if (segment->getFlags() & PF_W == 0)
+                continue;
+            size_t ptr_size = Elf_Addr::size(elf->getClass());
+            size_t aligned_mem_end = (segment->getAddr() + segment->getFileSize() + ptr_size - 1) & ~(ptr_size - 1);
+            size_t aligned_file_end = (segment->getAddr() + segment->getMemSize() + ptr_size - 1) & ~(ptr_size - 1);
+            if (aligned_mem_end - aligned_file_end >= Elf_Addr::size(elf->getClass())) {
+                mprotect_cb = rel.r_offset = aligned_file_end;
+                break;
+            }
+        }
+
+        if (mprotect_cb == 0) {
+            fprintf(stderr, "Couldn't find .bss. Skipping\n");
+            return -1;
+        }
+    }
+
     section->rels.assign(new_rels.begin(), new_rels.end());
     section->shrink(new_rels.size() * section->getEntSize());
 
-    ElfRelHackCode_Section *relhackcode = new ElfRelHackCode_Section(relhackcode_section, *elf, original_init);
+    ElfRelHackCode_Section *relhackcode = new ElfRelHackCode_Section(relhackcode_section, *elf, original_init, mprotect_cb);
     relhackcode->insertBefore(section);
     relhack->insertAfter(relhackcode);
     if (section->getOffset() + section->getSize() >= old_end) {
         fprintf(stderr, "No gain. Skipping\n");
         return -1;
     }
 
     // Adjust PT_LOAD segments
--- a/build/unix/elfhack/elfxx.h
+++ b/build/unix/elfhack/elfxx.h
@@ -322,18 +322,27 @@ public:
     unsigned int getSize() { return shdr.sh_size; }
     unsigned int getAddrAlign() { return shdr.sh_addralign; }
     unsigned int getEntSize() { return shdr.sh_entsize; }
     const char *getData() { return data; }
     ElfSection *getLink() { return link; }
     SectionInfo getInfo() { return info; }
 
     void shrink(unsigned int newsize) {
-        if (newsize < shdr.sh_size)
+        if (newsize < shdr.sh_size) {
             shdr.sh_size = newsize;
+            markDirty();
+        }
+    }
+
+    void grow(unsigned int newsize) {
+        if (newsize > shdr.sh_size) {
+            shdr.sh_size = newsize;
+            markDirty();
+        }
     }
 
     unsigned int getOffset();
     int getIndex();
     Elf_Shdr &getShdr();
 
     ElfSection *getNext() { return next; }
     ElfSection *getPrevious() { return previous; }
--- a/build/unix/elfhack/inject.c
+++ b/build/unix/elfhack/inject.c
@@ -1,13 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <stdint.h>
+#include <stdlib.h>
+#include <sys/mman.h>
 #include <elf.h>
 
 /* The Android NDK headers define those */
 #undef Elf_Ehdr
 #undef Elf_Addr
 
 #if defined(__LP64__)
 #define Elf_Ehdr Elf64_Ehdr
@@ -17,16 +19,20 @@
 #define Elf_Addr Elf32_Addr
 #endif
 
 extern __attribute__((visibility("hidden"))) void original_init(int argc, char **argv, char **env);
 
 extern __attribute__((visibility("hidden"))) Elf32_Rel relhack[];
 extern __attribute__((visibility("hidden"))) Elf_Ehdr elf_header;
 
+extern __attribute__((visibility("hidden"))) int (*mprotect_cb)(void *addr, size_t len, int prot);
+extern __attribute__((visibility("hidden"))) char relro_start[];
+extern __attribute__((visibility("hidden"))) char relro_end[];
+
 static inline __attribute__((always_inline))
 void do_relocations(void)
 {
     Elf32_Rel *rel;
     Elf_Addr *ptr, *start;
     for (rel = relhack; rel->r_offset; rel++) {
         start = (Elf_Addr *)((intptr_t)&elf_header + rel->r_offset);
         for (ptr = start; ptr < &start[rel->r_info]; ptr++)
@@ -45,8 +51,45 @@ int init_noinit(int argc, char **argv, c
 int init(int argc, char **argv, char **env)
 {
     do_relocations();
     original_init(argc, argv, env);
     // Ensure there is no tail-call optimization, avoiding the use of the
     // B.W instruction in Thumb for the call above.
     return 0;
 }
+
+static inline __attribute__((always_inline))
+void relro_pre()
+{
+    // By the time the injected code runs, the relro segment is read-only. But
+    // we want to apply relocations in it, so we set it r/w first. We'll restore
+    // it to read-only in relro_post.
+    mprotect_cb(relro_start, relro_end - relro_start, PROT_READ | PROT_WRITE);
+}
+
+static inline __attribute__((always_inline))
+void relro_post()
+{
+    mprotect_cb(relro_start, relro_end - relro_start, PROT_READ);
+    // mprotect_cb is a pointer allocated in .bss, so we need to restore it to
+    // a NULL value.
+    mprotect_cb = NULL;
+}
+
+__attribute__((section(".text._init_noinit_relro")))
+int init_noinit_relro(int argc, char **argv, char **env)
+{
+    relro_pre();
+    do_relocations();
+    relro_post();
+    return 0;
+}
+
+__attribute__((section(".text._init_relro")))
+int init_relro(int argc, char **argv, char **env)
+{
+    relro_pre();
+    do_relocations();
+    original_init(argc, argv, env);
+    relro_post();
+    return 0;
+}