--- 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/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 allocator 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;
+}