forked from mirrors/gecko-dev
Bug 635961 - Allow elfhack to relocate data under the GNU_RELRO segment. r=froydnj
--HG-- extra : rebase_source : 873898d5929414b754bf592ab4d60574700b646a
This commit is contained in:
parent
3229d39dba
commit
43b0a30fd0
3 changed files with 141 additions and 10 deletions
|
|
@ -89,8 +89,8 @@ private:
|
||||||
|
|
||||||
class ElfRelHackCode_Section: public ElfSection {
|
class ElfRelHackCode_Section: public ElfSection {
|
||||||
public:
|
public:
|
||||||
ElfRelHackCode_Section(Elf_Shdr &s, Elf &e, unsigned int init)
|
ElfRelHackCode_Section(Elf_Shdr &s, Elf &e, unsigned int init, unsigned int mprotect_cb)
|
||||||
: ElfSection(s, nullptr, nullptr), parent(e), init(init) {
|
: ElfSection(s, nullptr, nullptr), parent(e), init(init), mprotect_cb(mprotect_cb) {
|
||||||
std::string file(rundir);
|
std::string file(rundir);
|
||||||
file += "/inject/";
|
file += "/inject/";
|
||||||
switch (parent.getMachine()) {
|
switch (parent.getMachine()) {
|
||||||
|
|
@ -125,9 +125,17 @@ public:
|
||||||
if (symtab == nullptr)
|
if (symtab == nullptr)
|
||||||
throw std::runtime_error("Couldn't find a symbol table for the injected code");
|
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
|
// Find the init symbol
|
||||||
entry_point = -1;
|
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)
|
if (!sym)
|
||||||
throw std::runtime_error("Couldn't find an 'init' symbol in the injected code");
|
throw std::runtime_error("Couldn't find an 'init' symbol in the injected code");
|
||||||
|
|
||||||
|
|
@ -353,6 +361,14 @@ private:
|
||||||
addr = ehdr->getAddr();
|
addr = ehdr->getAddr();
|
||||||
} else if (strcmp(name, "original_init") == 0) {
|
} else if (strcmp(name, "original_init") == 0) {
|
||||||
addr = init;
|
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) {
|
} else if (strcmp(name, "_GLOBAL_OFFSET_TABLE_") == 0) {
|
||||||
// We actually don't need a GOT, but need it as a reference for
|
// 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
|
// GOTOFF relocations. We'll just use the start of the ELF file
|
||||||
|
|
@ -403,7 +419,10 @@ private:
|
||||||
Elf *elf, &parent;
|
Elf *elf, &parent;
|
||||||
std::vector<ElfSection *> code;
|
std::vector<ElfSection *> code;
|
||||||
unsigned int init;
|
unsigned int init;
|
||||||
|
unsigned int mprotect_cb;
|
||||||
int entry_point;
|
int entry_point;
|
||||||
|
ElfSegment *relro;
|
||||||
|
unsigned int align;
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned int get_addend(Elf_Rel *rel, Elf *elf) {
|
unsigned int get_addend(Elf_Rel *rel, Elf *elf) {
|
||||||
|
|
@ -503,8 +522,6 @@ int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ElfSegment *relro = elf->getSegmentByType(PT_GNU_RELRO);
|
|
||||||
|
|
||||||
ElfRel_Section<Rel_Type> *section = (ElfRel_Section<Rel_Type> *)dyn->getSectionForType(Rel_Type::d_tag);
|
ElfRel_Section<Rel_Type> *section = (ElfRel_Section<Rel_Type> *)dyn->getSectionForType(Rel_Type::d_tag);
|
||||||
if (section == nullptr) {
|
if (section == nullptr) {
|
||||||
fprintf(stderr, "No relocations\n");
|
fprintf(stderr, "No relocations\n");
|
||||||
|
|
@ -599,9 +616,7 @@ int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type
|
||||||
}
|
}
|
||||||
new_rels.push_back(*i);
|
new_rels.push_back(*i);
|
||||||
init_array_reloc = new_rels.size();
|
init_array_reloc = new_rels.size();
|
||||||
} else if (!(loc.getSection()->getFlags() & SHF_WRITE) || (ELF32_R_TYPE(i->r_info) != rel_type) ||
|
} 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()))) {
|
|
||||||
// Don't pack relocations happening in non writable sections.
|
// Don't pack relocations happening in non writable sections.
|
||||||
// Our injected code is likely not to be allowed to write there.
|
// Our injected code is likely not to be allowed to write there.
|
||||||
new_rels.push_back(*i);
|
new_rels.push_back(*i);
|
||||||
|
|
@ -670,10 +685,72 @@ int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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->getMemSize() + ptr_size - 1) & ~(ptr_size - 1);
|
||||||
|
size_t aligned_file_end = (segment->getAddr() + segment->getFileSize() + 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->rels.assign(new_rels.begin(), new_rels.end());
|
||||||
section->shrink(new_rels.size() * section->getEntSize());
|
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);
|
relhackcode->insertBefore(section);
|
||||||
relhack->insertAfter(relhackcode);
|
relhack->insertAfter(relhackcode);
|
||||||
if (section->getOffset() + section->getSize() >= old_end) {
|
if (section->getOffset() + section->getSize() >= old_end) {
|
||||||
|
|
|
||||||
|
|
@ -327,8 +327,19 @@ public:
|
||||||
SectionInfo getInfo() { return info; }
|
SectionInfo getInfo() { return info; }
|
||||||
|
|
||||||
void shrink(unsigned int newsize) {
|
void shrink(unsigned int newsize) {
|
||||||
if (newsize < shdr.sh_size)
|
if (newsize < shdr.sh_size) {
|
||||||
shdr.sh_size = newsize;
|
shdr.sh_size = newsize;
|
||||||
|
markDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void grow(unsigned int newsize) {
|
||||||
|
if (newsize > shdr.sh_size) {
|
||||||
|
data = static_cast<char*>(realloc(data, newsize));
|
||||||
|
memset(data + shdr.sh_size, 0, newsize - shdr.sh_size);
|
||||||
|
shdr.sh_size = newsize;
|
||||||
|
markDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int getOffset();
|
unsigned int getOffset();
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
#include <elf.h>
|
#include <elf.h>
|
||||||
|
|
||||||
/* The Android NDK headers define those */
|
/* The Android NDK headers define those */
|
||||||
|
|
@ -22,6 +24,10 @@ extern __attribute__((visibility("hidden"))) void original_init(int argc, char *
|
||||||
extern __attribute__((visibility("hidden"))) Elf32_Rel relhack[];
|
extern __attribute__((visibility("hidden"))) Elf32_Rel relhack[];
|
||||||
extern __attribute__((visibility("hidden"))) Elf_Ehdr elf_header;
|
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))
|
static inline __attribute__((always_inline))
|
||||||
void do_relocations(void)
|
void do_relocations(void)
|
||||||
{
|
{
|
||||||
|
|
@ -50,3 +56,40 @@ int init(int argc, char **argv, char **env)
|
||||||
// B.W instruction in Thumb for the call above.
|
// B.W instruction in Thumb for the call above.
|
||||||
return 0;
|
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();
|
||||||
|
relro_post();
|
||||||
|
original_init(argc, argv, env);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue