diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /build/unix/elfhack | |
parent | Initial commit. (diff) | |
download | firefox-e51783d008170d9ab27d25da98ca3a38b0a41b67.tar.xz firefox-e51783d008170d9ab27d25da98ca3a38b0a41b67.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'build/unix/elfhack')
-rw-r--r-- | build/unix/elfhack/Makefile.in | 46 | ||||
-rw-r--r-- | build/unix/elfhack/README | 28 | ||||
-rw-r--r-- | build/unix/elfhack/dummy.c | 7 | ||||
-rw-r--r-- | build/unix/elfhack/elf.cpp | 935 | ||||
-rw-r--r-- | build/unix/elfhack/elfhack.cpp | 1458 | ||||
-rw-r--r-- | build/unix/elfhack/elfxx.h | 700 | ||||
-rw-r--r-- | build/unix/elfhack/inject.c | 225 | ||||
-rw-r--r-- | build/unix/elfhack/inject/copy_source.py | 10 | ||||
-rw-r--r-- | build/unix/elfhack/inject/moz.build | 56 | ||||
-rw-r--r-- | build/unix/elfhack/moz.build | 46 | ||||
-rw-r--r-- | build/unix/elfhack/relrhack.cpp | 567 | ||||
-rw-r--r-- | build/unix/elfhack/relrhack.h | 25 | ||||
-rw-r--r-- | build/unix/elfhack/test-array.c | 8 | ||||
-rw-r--r-- | build/unix/elfhack/test-ctors.c | 16 | ||||
-rw-r--r-- | build/unix/elfhack/test.c | 221 |
15 files changed, 4348 insertions, 0 deletions
diff --git a/build/unix/elfhack/Makefile.in b/build/unix/elfhack/Makefile.in new file mode 100644 index 0000000000..d5e58efa67 --- /dev/null +++ b/build/unix/elfhack/Makefile.in @@ -0,0 +1,46 @@ +# +# 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 $(topsrcdir)/config/rules.mk + +ifdef COMPILE_ENVIRONMENT +ifndef RELRHACK +test-array$(DLL_SUFFIX) test-ctors$(DLL_SUFFIX): %$(DLL_SUFFIX): %.$(OBJ_SUFFIX) elfhack + $(MKSHLIB) $(LDFLAGS) $< -nostartfiles + @echo === + @echo === If you get failures below, please file a bug describing the error + @echo === and your environment \(compiler and linker versions\), and + @echo === provide the pre-elfhacked library as an attachment. + @echo === Use --disable-elf-hack until this is fixed. + @echo === + # Fail if the library doesn't have $(DT_TYPE) .dynamic info + $(READELF) -d $@ | grep '\b$(DT_TYPE)\b' + @rm -f $@.bak + $(CURDIR)/elfhack -b -f $@ + # Fail if the backup file doesn't exist + [ -f '$@.bak' ] + # Fail if the new library doesn't contain less relocations + [ $$($(READELF) -r $@.bak | wc -l) -gt $$($(READELF) -r $@ | wc -l) ] + +test-array$(DLL_SUFFIX) test-ctors$(DLL_SUFFIX): DSO_SONAME=$@ +test-array$(DLL_SUFFIX): DT_TYPE=INIT_ARRAY +test-ctors$(DLL_SUFFIX): DT_TYPE=INIT + +libs:: test-array$(DLL_SUFFIX) test-ctors$(DLL_SUFFIX) + +.PRECIOUS: test-array$(DLL_SUFFIX) test-ctors$(DLL_SUFFIX) + +ifndef CROSS_COMPILE +dummy: dummy.$(OBJ_SUFFIX) + $(CC) -o $@ $^ $(LDFLAGS) + +libs:: dummy + # Will either crash or return exit code 1 if elfhack is broken + LD_PRELOAD=$(CURDIR)/test-array$(DLL_SUFFIX) $(CURDIR)/dummy + LD_PRELOAD=$(CURDIR)/test-ctors$(DLL_SUFFIX) $(CURDIR)/dummy + +endif +endif +endif diff --git a/build/unix/elfhack/README b/build/unix/elfhack/README new file mode 100644 index 0000000000..8c68031e33 --- /dev/null +++ b/build/unix/elfhack/README @@ -0,0 +1,28 @@ +Elfhack is a program to optimize ELF binaries for size and cold startup +speed. + +Presently, it is quite experimental, though it works well for the target +it was created for: Firefox's libxul.so. + +Elfhack currently only does one thing: packing dynamic relocations ; +which ends up being a quite complex task, that can be summarized this +way: +- Remove RELATIVE relocations from the .rel.dyn/.rela.dyn section. +- Inject a small code able to apply relative relocations "by hand" + after the .rel.dyn/.rela.dyn section. +- Inject a section containing relocative relocations in a different + and more packed format, after the small code. +- Register the small code as DT_INIT function. Make the small code call + what was initially the DT_INIT function, if there was one. +- Remove the hole between the new section containing relative + relocations and the following sections, adjusting offsets and base + addresses accordingly. +- Adjust PT_LOAD entries to fit new offsets, and add an additional + PT_LOAD entry when that is necessary to handle the discrepancy between + offsets and base addresses, meaning the section offsets may yet again + need adjustments. +- Adjust various DT_* dynamic tags to fit the new ELF layout. +- Adjust section headers. +- Adjust ELF headers. + +See http://glandium.org/blog/?p=1177#relocations for some figures. diff --git a/build/unix/elfhack/dummy.c b/build/unix/elfhack/dummy.c new file mode 100644 index 0000000000..2cde16102e --- /dev/null +++ b/build/unix/elfhack/dummy.c @@ -0,0 +1,7 @@ +/* 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/. */ + +extern __attribute__((visibility("default"), weak)) int print_status(); + +int main() { return print_status(); } diff --git a/build/unix/elfhack/elf.cpp b/build/unix/elfhack/elf.cpp new file mode 100644 index 0000000000..b2a21e4901 --- /dev/null +++ b/build/unix/elfhack/elf.cpp @@ -0,0 +1,935 @@ +/* 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/. */ + +#undef NDEBUG +#include <cstring> +#include <assert.h> +#include "elfxx.h" + +template <class endian, typename R, typename T> +void Elf_Ehdr_Traits::swap(T& t, R& r) { + memcpy(r.e_ident, t.e_ident, sizeof(r.e_ident)); + r.e_type = endian::swap(t.e_type); + r.e_machine = endian::swap(t.e_machine); + r.e_version = endian::swap(t.e_version); + r.e_entry = endian::swap(t.e_entry); + r.e_phoff = endian::swap(t.e_phoff); + r.e_shoff = endian::swap(t.e_shoff); + r.e_flags = endian::swap(t.e_flags); + r.e_ehsize = endian::swap(t.e_ehsize); + r.e_phentsize = endian::swap(t.e_phentsize); + r.e_phnum = endian::swap(t.e_phnum); + r.e_shentsize = endian::swap(t.e_shentsize); + r.e_shnum = endian::swap(t.e_shnum); + r.e_shstrndx = endian::swap(t.e_shstrndx); +} + +template <class endian, typename R, typename T> +void Elf_Phdr_Traits::swap(T& t, R& r) { + r.p_type = endian::swap(t.p_type); + r.p_offset = endian::swap(t.p_offset); + r.p_vaddr = endian::swap(t.p_vaddr); + r.p_paddr = endian::swap(t.p_paddr); + r.p_filesz = endian::swap(t.p_filesz); + r.p_memsz = endian::swap(t.p_memsz); + r.p_flags = endian::swap(t.p_flags); + r.p_align = endian::swap(t.p_align); +} + +template <class endian, typename R, typename T> +void Elf_Shdr_Traits::swap(T& t, R& r) { + r.sh_name = endian::swap(t.sh_name); + r.sh_type = endian::swap(t.sh_type); + r.sh_flags = endian::swap(t.sh_flags); + r.sh_addr = endian::swap(t.sh_addr); + r.sh_offset = endian::swap(t.sh_offset); + r.sh_size = endian::swap(t.sh_size); + r.sh_link = endian::swap(t.sh_link); + r.sh_info = endian::swap(t.sh_info); + r.sh_addralign = endian::swap(t.sh_addralign); + r.sh_entsize = endian::swap(t.sh_entsize); +} + +template <class endian, typename R, typename T> +void Elf_Dyn_Traits::swap(T& t, R& r) { + r.d_tag = endian::swap(t.d_tag); + r.d_un.d_val = endian::swap(t.d_un.d_val); +} + +template <class endian, typename R, typename T> +void Elf_Sym_Traits::swap(T& t, R& r) { + r.st_name = endian::swap(t.st_name); + r.st_value = endian::swap(t.st_value); + r.st_size = endian::swap(t.st_size); + r.st_info = t.st_info; + r.st_other = t.st_other; + r.st_shndx = endian::swap(t.st_shndx); +} + +template <class endian> +struct _Rel_info { + static inline void swap(Elf32_Word& t, Elf32_Word& r) { r = endian::swap(t); } + static inline void swap(Elf64_Xword& t, Elf64_Xword& r) { + r = endian::swap(t); + } + static inline void swap(Elf64_Xword& t, Elf32_Word& r) { + r = endian::swap(ELF32_R_INFO(ELF64_R_SYM(t), ELF64_R_TYPE(t))); + } + static inline void swap(Elf32_Word& t, Elf64_Xword& r) { + r = endian::swap(ELF64_R_INFO(ELF32_R_SYM(t), ELF32_R_TYPE(t))); + } +}; + +template <class endian, typename R, typename T> +void Elf_Rel_Traits::swap(T& t, R& r) { + r.r_offset = endian::swap(t.r_offset); + _Rel_info<endian>::swap(t.r_info, r.r_info); +} + +template <class endian, typename R, typename T> +void Elf_Rela_Traits::swap(T& t, R& r) { + r.r_offset = endian::swap(t.r_offset); + _Rel_info<endian>::swap(t.r_info, r.r_info); + r.r_addend = endian::swap(t.r_addend); +} + +static const Elf64_Shdr null64_section = {0, SHT_NULL, 0, 0, 0, + 0, SHN_UNDEF, 0, 0, 0}; + +Elf_Shdr null_section(null64_section); + +Elf_Ehdr::Elf_Ehdr(std::ifstream& file, unsigned char ei_class, + unsigned char ei_data) + : serializable<Elf_Ehdr_Traits>(file, ei_class, ei_data), + ElfSection(null_section, nullptr, nullptr) { + shdr.sh_size = Elf_Ehdr::size(ei_class); +} + +Elf::Elf(std::ifstream& file) { + if (!file.is_open()) throw std::runtime_error("Error opening file"); + + file.exceptions(std::ifstream::eofbit | std::ifstream::failbit | + std::ifstream::badbit); + // Read ELF magic number and identification information + unsigned char e_ident[EI_VERSION]; + file.seekg(0); + file.read((char*)e_ident, sizeof(e_ident)); + file.seekg(0); + ehdr = new Elf_Ehdr(file, e_ident[EI_CLASS], e_ident[EI_DATA]); + + // ELFOSABI_LINUX is kept unsupported because I haven't looked whether + // STB_GNU_UNIQUE or STT_GNU_IFUNC would need special casing. + if ((ehdr->e_ident[EI_OSABI] != ELFOSABI_NONE) && + (ehdr->e_ident[EI_ABIVERSION] != 0)) + throw std::runtime_error("unsupported ELF ABI"); + + if (ehdr->e_version != 1) throw std::runtime_error("unsupported ELF version"); + + // Sanity checks + if (ehdr->e_shnum == 0) + throw std::runtime_error("sstripped ELF files aren't supported"); + + if (ehdr->e_ehsize != Elf_Ehdr::size(e_ident[EI_CLASS])) + throw std::runtime_error( + "unsupported ELF inconsistency: ehdr.e_ehsize != sizeof(ehdr)"); + + if (ehdr->e_shentsize != Elf_Shdr::size(e_ident[EI_CLASS])) + throw std::runtime_error( + "unsupported ELF inconsistency: ehdr.e_shentsize != sizeof(shdr)"); + + if (ehdr->e_phnum == 0) { + if (ehdr->e_phoff != 0) + throw std::runtime_error( + "unsupported ELF inconsistency: e_phnum == 0 && e_phoff != 0"); + if (ehdr->e_phentsize != 0) + throw std::runtime_error( + "unsupported ELF inconsistency: e_phnum == 0 && e_phentsize != 0"); + } else if (ehdr->e_phoff != ehdr->e_ehsize) + throw std::runtime_error( + "unsupported ELF inconsistency: ehdr->e_phoff != ehdr->e_ehsize"); + else if (ehdr->e_phentsize != Elf_Phdr::size(e_ident[EI_CLASS])) + throw std::runtime_error( + "unsupported ELF inconsistency: ehdr->e_phentsize != sizeof(phdr)"); + + // Read section headers + Elf_Shdr** shdr = new Elf_Shdr*[ehdr->e_shnum]; + file.seekg(ehdr->e_shoff); + for (int i = 0; i < ehdr->e_shnum; i++) + shdr[i] = new Elf_Shdr(file, e_ident[EI_CLASS], e_ident[EI_DATA]); + + // Sanity check in section header for index 0 + if ((shdr[0]->sh_name != 0) || (shdr[0]->sh_type != SHT_NULL) || + (shdr[0]->sh_flags != 0) || (shdr[0]->sh_addr != 0) || + (shdr[0]->sh_offset != 0) || (shdr[0]->sh_size != 0) || + (shdr[0]->sh_link != SHN_UNDEF) || (shdr[0]->sh_info != 0) || + (shdr[0]->sh_addralign != 0) || (shdr[0]->sh_entsize != 0)) + throw std::runtime_error( + "Section header for index 0 contains unsupported values"); + + if ((shdr[ehdr->e_shstrndx]->sh_link != 0) || + (shdr[ehdr->e_shstrndx]->sh_info != 0)) + throw std::runtime_error( + "unsupported ELF content: string table with sh_link != 0 || sh_info != " + "0"); + + // Store these temporarily + tmp_shdr = shdr; + tmp_file = &file; + + // Fill sections list + sections = new ElfSection*[ehdr->e_shnum]; + for (int i = 0; i < ehdr->e_shnum; i++) sections[i] = nullptr; + for (int i = 1; i < ehdr->e_shnum; i++) { + // The .dynamic section is going to have references to other sections, + // so it's better to start with that one and recursively initialize those + // other sections first, to avoid possible infinite recursion (bug 1606739). + if (tmp_shdr[i]->sh_type == SHT_DYNAMIC) { + getSection(i); + } + } + for (int i = 1; i < ehdr->e_shnum; i++) { + if (sections[i] != nullptr) continue; + getSection(i); + } + Elf_Shdr s; + s.sh_name = 0; + s.sh_type = SHT_NULL; + s.sh_flags = 0; + s.sh_addr = 0; + s.sh_offset = ehdr->e_shoff; + s.sh_entsize = Elf_Shdr::size(e_ident[EI_CLASS]); + s.sh_size = s.sh_entsize * ehdr->e_shnum; + s.sh_link = 0; + s.sh_info = 0; + s.sh_addralign = (e_ident[EI_CLASS] == ELFCLASS32) ? 4 : 8; + shdr_section = new ElfSection(s, nullptr, nullptr); + + // Fake section for program headers + s.sh_offset = ehdr->e_phoff; + s.sh_addr = ehdr->e_phoff; + s.sh_entsize = Elf_Phdr::size(e_ident[EI_CLASS]); + s.sh_size = s.sh_entsize * ehdr->e_phnum; + phdr_section = new ElfSection(s, nullptr, nullptr); + + phdr_section->insertAfter(ehdr, false); + + sections[1]->insertAfter(phdr_section, false); + for (int i = 2; i < ehdr->e_shnum; i++) { + // TODO: this should be done in a better way + if ((shdr_section->getPrevious() == nullptr) && + (shdr[i]->sh_offset > ehdr->e_shoff)) { + shdr_section->insertAfter(sections[i - 1], false); + sections[i]->insertAfter(shdr_section, false); + } else + sections[i]->insertAfter(sections[i - 1], false); + } + if (shdr_section->getPrevious() == nullptr) + shdr_section->insertAfter(sections[ehdr->e_shnum - 1], false); + + tmp_file = nullptr; + tmp_shdr = nullptr; + for (int i = 0; i < ehdr->e_shnum; i++) delete shdr[i]; + delete[] shdr; + + eh_shstrndx = (ElfStrtab_Section*)sections[ehdr->e_shstrndx]; + + // Skip reading program headers if there aren't any + if (ehdr->e_phnum == 0) return; + + bool adjusted_phdr_section = false; + // Read program headers + file.seekg(ehdr->e_phoff); + for (int i = 0; i < ehdr->e_phnum; i++) { + Elf_Phdr phdr(file, e_ident[EI_CLASS], e_ident[EI_DATA]); + if (phdr.p_type == PT_LOAD) { + // Default alignment for PT_LOAD on x86-64 prevents elfhack from + // doing anything useful. However, the system doesn't actually + // require such a big alignment, so in order for elfhack to work + // efficiently, reduce alignment when it's originally the default + // one. + if ((ehdr->e_machine == EM_X86_64) && (phdr.p_align == 0x200000)) + phdr.p_align = 0x1000; + } + ElfSegment* segment = new ElfSegment(&phdr); + // Some segments aren't entirely filled (if at all) by sections + // For those, we use fake sections + if ((phdr.p_type == PT_LOAD) && (phdr.p_offset == 0)) { + // Use a fake section for ehdr and phdr + ehdr->getShdr().sh_addr = phdr.p_vaddr; + if (!adjusted_phdr_section) { + phdr_section->getShdr().sh_addr += phdr.p_vaddr; + adjusted_phdr_section = true; + } + segment->addSection(ehdr); + segment->addSection(phdr_section); + } + if (phdr.p_type == PT_PHDR) { + if (!adjusted_phdr_section) { + phdr_section->getShdr().sh_addr = phdr.p_vaddr; + adjusted_phdr_section = true; + } + segment->addSection(phdr_section); + } + for (int j = 1; j < ehdr->e_shnum; j++) + if (phdr.contains(sections[j])) segment->addSection(sections[j]); + // Make sure that our view of segments corresponds to the original + // ELF file. + // GNU gold likes to start some segments before the first section + // they contain. https://sourceware.org/bugzilla/show_bug.cgi?id=19392 + unsigned int gold_adjustment = segment->getAddr() - phdr.p_vaddr; + assert(segment->getFileSize() == phdr.p_filesz - gold_adjustment); + // gold makes TLS segments end on an aligned virtual address, even + // when the underlying section ends before that, while bfd ld + // doesn't. It's fine if we don't keep that alignment. + unsigned int memsize = segment->getMemSize(); + if (phdr.p_type == PT_TLS && memsize != phdr.p_memsz) { + unsigned int align = segment->getAlign(); + memsize = (memsize + align - 1) & ~(align - 1); + } + assert(memsize == phdr.p_memsz - gold_adjustment); + segments.push_back(segment); + } + + new (&eh_entry) ElfLocation(ehdr->e_entry, this); +} + +Elf::~Elf() { + for (std::vector<ElfSegment*>::iterator seg = segments.begin(); + seg != segments.end(); seg++) + delete *seg; + delete[] sections; + ElfSection* section = ehdr; + while (section != nullptr) { + ElfSection* next = section->getNext(); + delete section; + section = next; + } +} + +// TODO: This shouldn't fail after inserting sections +ElfSection* Elf::getSection(int index) { + if ((index < -1) || (index >= ehdr->e_shnum)) + throw std::runtime_error("Section index out of bounds"); + if (index == -1) + index = ehdr->e_shstrndx; // TODO: should be fixed to use the actual + // current number + // Special case: the section at index 0 is void + if (index == 0) return nullptr; + // Infinite recursion guard + if (sections[index] == (ElfSection*)this) return nullptr; + if (sections[index] == nullptr) { + sections[index] = (ElfSection*)this; + switch (tmp_shdr[index]->sh_type) { + case SHT_DYNAMIC: + sections[index] = + new ElfDynamic_Section(*tmp_shdr[index], tmp_file, this); + break; + case SHT_REL: + sections[index] = + new ElfRel_Section<Elf_Rel>(*tmp_shdr[index], tmp_file, this); + break; + case SHT_RELA: + sections[index] = + new ElfRel_Section<Elf_Rela>(*tmp_shdr[index], tmp_file, this); + break; + case SHT_DYNSYM: + case SHT_SYMTAB: + sections[index] = + new ElfSymtab_Section(*tmp_shdr[index], tmp_file, this); + break; + case SHT_STRTAB: + sections[index] = + new ElfStrtab_Section(*tmp_shdr[index], tmp_file, this); + break; + default: + sections[index] = new ElfSection(*tmp_shdr[index], tmp_file, this); + } + } + return sections[index]; +} + +ElfSection* Elf::getSectionAt(Elf64_Off offset) { + for (int i = 1; i < ehdr->e_shnum; i++) { + ElfSection* section = getSection(i); + if ((section != nullptr) && (section->getFlags() & SHF_ALLOC) && + !(section->getFlags() & SHF_TLS) && (offset >= section->getAddr()) && + (offset < section->getAddr() + section->getSize())) + return section; + } + return nullptr; +} + +ElfSegment* Elf::getSegmentByType(unsigned int type, ElfSegment* last) { + std::vector<ElfSegment*>::iterator seg; + if (last) { + seg = std::find(segments.begin(), segments.end(), last); + ++seg; + } else + seg = segments.begin(); + for (; seg != segments.end(); seg++) + if ((*seg)->getType() == type) return *seg; + return nullptr; +} + +void Elf::removeSegment(ElfSegment* segment) { + if (!segment) return; + std::vector<ElfSegment*>::iterator seg; + seg = std::find(segments.begin(), segments.end(), segment); + if (seg == segments.end()) return; + segment->clear(); + segments.erase(seg); +} + +ElfDynamic_Section* Elf::getDynSection() { + for (std::vector<ElfSegment*>::iterator seg = segments.begin(); + seg != segments.end(); seg++) + if (((*seg)->getType() == PT_DYNAMIC) && + ((*seg)->getFirstSection() != nullptr) && + (*seg)->getFirstSection()->getType() == SHT_DYNAMIC) + return (ElfDynamic_Section*)(*seg)->getFirstSection(); + + return nullptr; +} + +void Elf::normalize() { + // fixup section headers sh_name; TODO: that should be done by sections + // themselves + for (ElfSection* section = ehdr; section != nullptr; + section = section->getNext()) { + if (section->getIndex() == 0) + continue; + else + ehdr->e_shnum = section->getIndex() + 1; + section->getShdr().sh_name = eh_shstrndx->getStrIndex(section->getName()); + } + ehdr->markDirty(); + // Check segments consistency + int i = 0; + for (std::vector<ElfSegment*>::iterator seg = segments.begin(); + seg != segments.end(); seg++, i++) { + std::list<ElfSection*>::iterator it = (*seg)->begin(); + for (ElfSection* last = *(it++); it != (*seg)->end(); last = *(it++)) { + if (((*it)->getType() != SHT_NOBITS) && + ((*it)->getAddr() - last->getAddr()) != + ((*it)->getOffset() - last->getOffset())) { + throw std::runtime_error("Segments inconsistency"); + } + } + } + + ElfSegment* prevLoad = nullptr; + for (auto& it : segments) { + if (it->getType() == PT_LOAD) { + if (prevLoad) { + size_t alignedPrevEnd = (prevLoad->getAddr() + prevLoad->getMemSize() + + prevLoad->getAlign() - 1) & + ~(prevLoad->getAlign() - 1); + size_t alignedStart = it->getAddr() & ~(it->getAlign() - 1); + if (alignedPrevEnd > alignedStart) { + throw std::runtime_error("Segments overlap"); + } + } + prevLoad = it; + } + } + + // fixup ehdr before writing + if (ehdr->e_phnum != segments.size()) { + ehdr->e_phnum = segments.size(); + phdr_section->getShdr().sh_size = + segments.size() * Elf_Phdr::size(ehdr->e_ident[EI_CLASS]); + phdr_section->getNext()->markDirty(); + } + // fixup shdr before writing + if (ehdr->e_shnum != shdr_section->getSize() / shdr_section->getEntSize()) + shdr_section->getShdr().sh_size = + ehdr->e_shnum * Elf_Shdr::size(ehdr->e_ident[EI_CLASS]); + ehdr->e_shoff = shdr_section->getOffset(); + ehdr->e_entry = eh_entry.getValue(); + ehdr->e_shstrndx = eh_shstrndx->getIndex(); + + // Check sections consistency + unsigned int minOffset = 0; + for (ElfSection* section = ehdr; section != nullptr; + section = section->getNext()) { + unsigned int offset = section->getOffset(); + if (offset < minOffset) { + throw std::runtime_error("Sections overlap"); + } + if (section->getType() != SHT_NOBITS) { + minOffset = offset + section->getSize(); + } + } +} + +void Elf::write(std::ofstream& file) { + normalize(); + for (ElfSection* section = ehdr; section != nullptr; + section = section->getNext()) { + file.seekp(section->getOffset()); + if (section == phdr_section) { + for (std::vector<ElfSegment*>::iterator seg = segments.begin(); + seg != segments.end(); seg++) { + Elf_Phdr phdr; + phdr.p_type = (*seg)->getType(); + phdr.p_flags = (*seg)->getFlags(); + phdr.p_offset = (*seg)->getOffset(); + phdr.p_vaddr = (*seg)->getAddr(); + phdr.p_paddr = phdr.p_vaddr + (*seg)->getVPDiff(); + phdr.p_filesz = (*seg)->getFileSize(); + phdr.p_memsz = (*seg)->getMemSize(); + phdr.p_align = (*seg)->getAlign(); + phdr.serialize(file, ehdr->e_ident[EI_CLASS], ehdr->e_ident[EI_DATA]); + } + } else if (section == shdr_section) { + null_section.serialize(file, ehdr->e_ident[EI_CLASS], + ehdr->e_ident[EI_DATA]); + for (ElfSection* sec = ehdr; sec != nullptr; sec = sec->getNext()) { + if (sec->getType() != SHT_NULL) + sec->getShdr().serialize(file, ehdr->e_ident[EI_CLASS], + ehdr->e_ident[EI_DATA]); + } + } else + section->serialize(file, ehdr->e_ident[EI_CLASS], ehdr->e_ident[EI_DATA]); + } +} + +ElfSection::ElfSection(Elf_Shdr& s, std::ifstream* file, Elf* parent) + : shdr(s), + link(shdr.sh_link == SHN_UNDEF ? nullptr + : parent->getSection(shdr.sh_link)), + next(nullptr), + previous(nullptr), + index(-1) { + if ((file == nullptr) || (shdr.sh_type == SHT_NULL) || + (shdr.sh_type == SHT_NOBITS)) + data = nullptr; + else { + data = static_cast<char*>(malloc(shdr.sh_size)); + if (!data) { + throw std::runtime_error("Could not malloc ElfSection data"); + } + auto pos = file->tellg(); + file->seekg(shdr.sh_offset); + file->read(data, shdr.sh_size); + file->seekg(pos); + } + if (shdr.sh_name == 0) + name = nullptr; + else { + ElfStrtab_Section* strtab = (ElfStrtab_Section*)parent->getSection(-1); + // Special case (see elfgeneric.cpp): if strtab is nullptr, the + // section being created is the strtab. + if (strtab == nullptr) + name = &data[shdr.sh_name]; + else + name = strtab->getStr(shdr.sh_name); + } + // Only SHT_REL/SHT_RELA sections use sh_info to store a section + // number. + if ((shdr.sh_type == SHT_REL) || (shdr.sh_type == SHT_RELA)) + info.section = shdr.sh_info ? parent->getSection(shdr.sh_info) : nullptr; + else + info.index = shdr.sh_info; +} + +Elf64_Addr ElfSection::getAddr() { + if (shdr.sh_addr != (Elf64_Addr)-1) return shdr.sh_addr; + + // It should be safe to adjust sh_addr for all allocated sections that + // are neither SHT_NOBITS nor SHT_PROGBITS + if ((previous != nullptr) && isRelocatable()) { + unsigned int addr = previous->getAddr(); + if (previous->getType() != SHT_NOBITS) addr += previous->getSize(); + + if (addr & (getAddrAlign() - 1)) addr = (addr | (getAddrAlign() - 1)) + 1; + + return (shdr.sh_addr = addr); + } + return shdr.sh_addr; +} + +Elf64_Off ElfSection::getOffset() { + if (shdr.sh_offset != (Elf64_Off)-1) return shdr.sh_offset; + + if (previous == nullptr) return (shdr.sh_offset = 0); + + Elf64_Off offset = previous->getOffset(); + + ElfSegment* ptload = getSegmentByType(PT_LOAD); + ElfSegment* prev_ptload = previous->getSegmentByType(PT_LOAD); + + if (ptload && (ptload == prev_ptload)) { + offset += getAddr() - previous->getAddr(); + return (shdr.sh_offset = offset); + } + + if (previous->getType() != SHT_NOBITS) offset += previous->getSize(); + + Elf32_Word align = 0x1000; + for (std::vector<ElfSegment*>::iterator seg = segments.begin(); + seg != segments.end(); seg++) + align = std::max(align, (*seg)->getAlign()); + + Elf32_Word mask = align - 1; + // SHF_TLS is used for .tbss which is some kind of special case. + if (((getType() != SHT_NOBITS) || (getFlags() & SHF_TLS)) && + (getFlags() & SHF_ALLOC)) { + if ((getAddr() & mask) < (offset & mask)) + offset = (offset | mask) + (getAddr() & mask) + 1; + else + offset = (offset & ~mask) + (getAddr() & mask); + } + if ((getType() != SHT_NOBITS) && (offset & (getAddrAlign() - 1))) + offset = (offset | (getAddrAlign() - 1)) + 1; + + return (shdr.sh_offset = offset); +} + +int ElfSection::getIndex() { + if (index != -1) return index; + if (getType() == SHT_NULL) return (index = 0); + ElfSection* reference; + for (reference = previous; + (reference != nullptr) && (reference->getType() == SHT_NULL); + reference = reference->getPrevious()) + ; + if (reference == nullptr) return (index = 1); + return (index = reference->getIndex() + 1); +} + +Elf_Shdr& ElfSection::getShdr() { + getOffset(); + if (shdr.sh_link == (Elf64_Word)-1) + shdr.sh_link = getLink() ? getLink()->getIndex() : 0; + if (shdr.sh_info == (Elf64_Word)-1) + shdr.sh_info = ((getType() == SHT_REL) || (getType() == SHT_RELA)) + ? (getInfo().section ? getInfo().section->getIndex() : 0) + : getInfo().index; + + return shdr; +} + +ElfSegment::ElfSegment(Elf_Phdr* phdr) + : type(phdr->p_type), + v_p_diff(phdr->p_paddr - phdr->p_vaddr), + flags(phdr->p_flags), + align(phdr->p_align), + vaddr(phdr->p_vaddr), + filesz(phdr->p_filesz), + memsz(phdr->p_memsz) {} + +void ElfSegment::addSection(ElfSection* section) { + // Make sure all sections in PT_GNU_RELRO won't be moved by elfhack + assert(!((type == PT_GNU_RELRO) && (section->isRelocatable()))); + + // TODO: Check overlapping sections + std::list<ElfSection*>::iterator i; + for (i = sections.begin(); i != sections.end(); ++i) + if ((*i)->getAddr() > section->getAddr()) break; + sections.insert(i, section); + section->addToSegment(this); +} + +void ElfSegment::removeSection(ElfSection* section) { + sections.remove(section); + section->removeFromSegment(this); +} + +unsigned int ElfSegment::getFileSize() { + if (type == PT_GNU_RELRO) return filesz; + + if (sections.empty()) return 0; + // Search the last section that is not SHT_NOBITS + std::list<ElfSection*>::reverse_iterator i; + for (i = sections.rbegin(); + (i != sections.rend()) && ((*i)->getType() == SHT_NOBITS); ++i) + ; + // All sections are SHT_NOBITS + if (i == sections.rend()) return 0; + + unsigned int end = (*i)->getAddr() + (*i)->getSize(); + + return end - sections.front()->getAddr(); +} + +unsigned int ElfSegment::getMemSize() { + if (type == PT_GNU_RELRO) return memsz; + + if (sections.empty()) return 0; + + unsigned int end = sections.back()->getAddr() + sections.back()->getSize(); + + return end - sections.front()->getAddr(); +} + +unsigned int ElfSegment::getOffset() { + if ((type == PT_GNU_RELRO) && !sections.empty() && + (sections.front()->getAddr() != vaddr)) + throw std::runtime_error( + "PT_GNU_RELRO segment doesn't start on a section start"); + + return sections.empty() ? 0 : sections.front()->getOffset(); +} + +unsigned int ElfSegment::getAddr() { + if ((type == PT_GNU_RELRO) && !sections.empty() && + (sections.front()->getAddr() != vaddr)) + throw std::runtime_error( + "PT_GNU_RELRO segment doesn't start on a section start"); + + return sections.empty() ? 0 : sections.front()->getAddr(); +} + +void ElfSegment::clear() { + for (std::list<ElfSection*>::iterator i = sections.begin(); + i != sections.end(); ++i) + (*i)->removeFromSegment(this); + sections.clear(); +} + +ElfValue* ElfDynamic_Section::getValueForType(unsigned int tag) { + for (unsigned int i = 0; i < shdr.sh_size / shdr.sh_entsize; i++) + if (dyns[i].tag == tag) return dyns[i].value; + + return nullptr; +} + +ElfSection* ElfDynamic_Section::getSectionForType(unsigned int tag) { + ElfValue* value = getValueForType(tag); + return value ? value->getSection() : nullptr; +} + +bool ElfDynamic_Section::setValueForType(unsigned int tag, ElfValue* val) { + unsigned int i; + unsigned int shnum = shdr.sh_size / shdr.sh_entsize; + for (i = 0; (i < shnum) && (dyns[i].tag != DT_NULL); i++) + if (dyns[i].tag == tag) { + delete dyns[i].value; + dyns[i].value = val; + return true; + } + // If we get here, this means we didn't match for the given tag + // Most of the time, there are a few DT_NULL entries, that we can + // use to add our value, but if we are on the last entry, we can't. + if (i >= shnum - 1) return false; + + dyns[i].tag = tag; + dyns[i].value = val; + return true; +} + +ElfDynamic_Section::ElfDynamic_Section(Elf_Shdr& s, std::ifstream* file, + Elf* parent) + : ElfSection(s, file, parent) { + auto pos = file->tellg(); + dyns.resize(s.sh_size / s.sh_entsize); + file->seekg(shdr.sh_offset); + // Here we assume tags refer to only one section (e.g. DT_RELSZ accounts + // for .rel.dyn size) + for (unsigned int i = 0; i < s.sh_size / s.sh_entsize; i++) { + Elf_Dyn dyn(*file, parent->getClass(), parent->getData()); + dyns[i].tag = dyn.d_tag; + switch (dyn.d_tag) { + case DT_NULL: + case DT_SYMBOLIC: + case DT_TEXTREL: + case DT_BIND_NOW: + dyns[i].value = new ElfValue(); + break; + case DT_NEEDED: + case DT_SONAME: + case DT_RPATH: + case DT_PLTREL: + case DT_RUNPATH: + case DT_FLAGS: + case DT_RELACOUNT: + case DT_RELCOUNT: + case DT_VERDEFNUM: + case DT_VERNEEDNUM: + dyns[i].value = new ElfPlainValue(dyn.d_un.d_val); + break; + case DT_PLTGOT: + case DT_HASH: + case DT_STRTAB: + case DT_SYMTAB: + case DT_RELA: + case DT_INIT: + case DT_FINI: + case DT_REL: + case DT_JMPREL: + case DT_INIT_ARRAY: + case DT_FINI_ARRAY: + case DT_GNU_HASH: + case DT_VERSYM: + case DT_VERNEED: + case DT_VERDEF: + dyns[i].value = new ElfLocation(dyn.d_un.d_ptr, parent); + break; + default: + dyns[i].value = nullptr; + } + } + // Another loop to get the section sizes + for (unsigned int i = 0; i < s.sh_size / s.sh_entsize; i++) + switch (dyns[i].tag) { + case DT_PLTRELSZ: + dyns[i].value = new ElfSize(getSectionForType(DT_JMPREL)); + break; + case DT_RELASZ: + dyns[i].value = new ElfSize(getSectionForType(DT_RELA)); + break; + case DT_STRSZ: + dyns[i].value = new ElfSize(getSectionForType(DT_STRTAB)); + break; + case DT_RELSZ: + dyns[i].value = new ElfSize(getSectionForType(DT_REL)); + break; + case DT_INIT_ARRAYSZ: + dyns[i].value = new ElfSize(getSectionForType(DT_INIT_ARRAY)); + break; + case DT_FINI_ARRAYSZ: + dyns[i].value = new ElfSize(getSectionForType(DT_FINI_ARRAY)); + break; + case DT_RELAENT: + dyns[i].value = new ElfEntSize(getSectionForType(DT_RELA)); + break; + case DT_SYMENT: + dyns[i].value = new ElfEntSize(getSectionForType(DT_SYMTAB)); + break; + case DT_RELENT: + dyns[i].value = new ElfEntSize(getSectionForType(DT_REL)); + break; + } + + file->seekg(pos); +} + +ElfDynamic_Section::~ElfDynamic_Section() { + for (unsigned int i = 0; i < shdr.sh_size / shdr.sh_entsize; i++) + delete dyns[i].value; +} + +void ElfDynamic_Section::serialize(std::ofstream& file, unsigned char ei_class, + unsigned char ei_data) { + for (unsigned int i = 0; i < shdr.sh_size / shdr.sh_entsize; i++) { + Elf_Dyn dyn; + dyn.d_tag = dyns[i].tag; + dyn.d_un.d_val = (dyns[i].value != nullptr) ? dyns[i].value->getValue() : 0; + dyn.serialize(file, ei_class, ei_data); + } +} + +ElfSymtab_Section::ElfSymtab_Section(Elf_Shdr& s, std::ifstream* file, + Elf* parent) + : ElfSection(s, file, parent) { + auto pos = file->tellg(); + syms.resize(s.sh_size / s.sh_entsize); + ElfStrtab_Section* strtab = (ElfStrtab_Section*)getLink(); + file->seekg(shdr.sh_offset); + for (unsigned int i = 0; i < shdr.sh_size / shdr.sh_entsize; i++) { + Elf_Sym sym(*file, parent->getClass(), parent->getData()); + syms[i].name = strtab->getStr(sym.st_name); + syms[i].info = sym.st_info; + syms[i].other = sym.st_other; + ElfSection* section = + (sym.st_shndx == SHN_ABS) ? nullptr : parent->getSection(sym.st_shndx); + new (&syms[i].value) + ElfLocation(section, sym.st_value, ElfLocation::ABSOLUTE); + syms[i].size = sym.st_size; + syms[i].defined = (sym.st_shndx != SHN_UNDEF); + } + file->seekg(pos); +} + +void ElfSymtab_Section::serialize(std::ofstream& file, unsigned char ei_class, + unsigned char ei_data) { + ElfStrtab_Section* strtab = (ElfStrtab_Section*)getLink(); + for (unsigned int i = 0; i < shdr.sh_size / shdr.sh_entsize; i++) { + Elf_Sym sym; + sym.st_name = strtab->getStrIndex(syms[i].name); + sym.st_info = syms[i].info; + sym.st_other = syms[i].other; + sym.st_value = syms[i].value.getValue(); + ElfSection* section = syms[i].value.getSection(); + if (syms[i].defined) + sym.st_shndx = section ? section->getIndex() : SHN_ABS; + else + sym.st_shndx = SHN_UNDEF; + sym.st_size = syms[i].size; + sym.serialize(file, ei_class, ei_data); + } +} + +Elf_SymValue* ElfSymtab_Section::lookup(const char* name, + unsigned int type_filter) { + for (std::vector<Elf_SymValue>::iterator sym = syms.begin(); + sym != syms.end(); sym++) { + if ((type_filter & (1 << ELF32_ST_TYPE(sym->info))) && + (strcmp(sym->name, name) == 0)) { + return &*sym; + } + } + return nullptr; +} + +const char* ElfStrtab_Section::getStr(unsigned int index) { + for (std::vector<table_storage>::iterator t = table.begin(); t != table.end(); + t++) { + if (index < t->used) return t->buf + index; + index -= t->used; + } + assert(1 == 0); + return nullptr; +} + +const char* ElfStrtab_Section::getStr(const char* string) { + if (string == nullptr) return nullptr; + + // If the given string is within the section, return it + for (std::vector<table_storage>::iterator t = table.begin(); t != table.end(); + t++) + if ((string >= t->buf) && (string < t->buf + t->used)) return string; + + // TODO: should scan in the section to find an existing string + + // If not, we need to allocate the string in the section + size_t len = strlen(string) + 1; + + if (table.back().size - table.back().used < len) + table.resize(table.size() + 1); + + char* alloc_str = table.back().buf + table.back().used; + memcpy(alloc_str, string, len); + table.back().used += len; + + shdr.sh_size += len; + markDirty(); + + return alloc_str; +} + +unsigned int ElfStrtab_Section::getStrIndex(const char* string) { + if (string == nullptr) return 0; + + unsigned int index = 0; + string = getStr(string); + for (std::vector<table_storage>::iterator t = table.begin(); t != table.end(); + t++) { + if ((string >= t->buf) && (string < t->buf + t->used)) + return index + (string - t->buf); + index += t->used; + } + + assert(1 == 0); + return 0; +} + +void ElfStrtab_Section::serialize(std::ofstream& file, unsigned char ei_class, + unsigned char ei_data) { + file.seekp(getOffset()); + for (std::vector<table_storage>::iterator t = table.begin(); t != table.end(); + t++) + file.write(t->buf, t->used); +} diff --git a/build/unix/elfhack/elfhack.cpp b/build/unix/elfhack/elfhack.cpp new file mode 100644 index 0000000000..719d4ac8f5 --- /dev/null +++ b/build/unix/elfhack/elfhack.cpp @@ -0,0 +1,1458 @@ +/* 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/. */ + +#undef NDEBUG +#include <assert.h> +#include <cstring> +#include <cstdlib> +#include <cstdio> +#include <memory> + +#include "elfxx.h" +#include "mozilla/CheckedInt.h" + +#define ver "1" +#define elfhack_data ".elfhack.data.v" ver +#define elfhack_text ".elfhack.text.v" ver + +#ifndef R_ARM_V4BX +# define R_ARM_V4BX 0x28 +#endif +#ifndef R_ARM_CALL +# define R_ARM_CALL 0x1c +#endif +#ifndef R_ARM_JUMP24 +# define R_ARM_JUMP24 0x1d +#endif +#ifndef R_ARM_THM_JUMP24 +# define R_ARM_THM_JUMP24 0x1e +#endif + +char* rundir = nullptr; + +template <typename T> +struct wrapped { + T value; +}; + +class Elf_Addr_Traits { + public: + typedef wrapped<Elf32_Addr> Type32; + typedef wrapped<Elf64_Addr> Type64; + + template <class endian, typename R, typename T> + static inline void swap(T& t, R& r) { + r.value = endian::swap(t.value); + } +}; + +typedef serializable<Elf_Addr_Traits> Elf_Addr; + +class ElfRelHack_Section : public ElfSection { + public: + ElfRelHack_Section(Elf_Shdr& s) + : ElfSection(s, nullptr, nullptr), + block_size((8 * s.sh_entsize - 1) * s.sh_entsize) { + name = elfhack_data; + }; + + void serialize(std::ofstream& file, unsigned char ei_class, + unsigned char ei_data) { + if (bitmap) { + relr.push_back((bitmap << 1) | 1); + } + for (std::vector<Elf64_Addr>::iterator i = relr.begin(); i != relr.end(); + ++i) { + Elf_Addr out; + out.value = *i; + out.serialize(file, ei_class, ei_data); + } + } + + bool isRelocatable() { return true; } + + void push_back(Elf64_Addr offset) { + // The format used for the packed relocations is SHT_RELR, described in + // https://groups.google.com/g/generic-abi/c/bX460iggiKg/m/Jnz1lgLJAgAJ + // The gist of it is that an address is recorded, and the following words, + // if their LSB is 1, represent a bitmap of word-size-spaced relocations + // at the addresses that follow. There can be multiple such bitmaps, such + // that very long streaks of (possibly spaced) relocations can be recorded + // in a very compact way. + for (;;) { + // [block_start; block_start + block_size] represents the range of offsets + // the current bitmap can record. If the offset doesn't fall in that + // range, or if doesn't align properly to be recorded, we record the + // bitmap, and slide the block corresponding to a new bitmap. If the + // offset doesn't fall in the range for the new bitmap, or if there wasn't + // an active bitmap in the first place, we record the offset and start a + // new bitmap for the block that follows it. + if (!block_start || offset < block_start || + offset >= block_start + block_size || + (offset - block_start) % shdr.sh_entsize) { + if (bitmap) { + relr.push_back((bitmap << 1) | 1); + block_start += block_size; + bitmap = 0; + continue; + } + relr.push_back(offset); + block_start = offset + shdr.sh_entsize; + break; + } + bitmap |= 1ULL << ((offset - block_start) / shdr.sh_entsize); + break; + } + shdr.sh_size = (relr.size() + (bitmap ? 1 : 0)) * shdr.sh_entsize; + } + + private: + std::vector<Elf64_Addr> relr; + size_t block_size; + Elf64_Addr block_start = 0; + Elf64_Addr bitmap = 0; +}; + +class ElfRelHackCode_Section : public ElfSection { + public: + ElfRelHackCode_Section(Elf_Shdr& s, Elf& e, + ElfRelHack_Section& relhack_section, unsigned int init, + unsigned int mprotect_cb, unsigned int sysconf_cb) + : ElfSection(s, nullptr, nullptr), + parent(e), + relhack_section(relhack_section), + init(init), + init_trampoline(nullptr), + mprotect_cb(mprotect_cb), + sysconf_cb(sysconf_cb) { + std::string file(rundir); + file += "/inject/"; + switch (parent.getMachine()) { + case EM_386: + file += "x86"; + break; + case EM_X86_64: + file += "x86_64"; + break; + case EM_ARM: + file += "arm"; + break; + case EM_AARCH64: + file += "aarch64"; + break; + default: + throw std::runtime_error("unsupported architecture"); + } + file += ".o"; + std::ifstream inject(file.c_str(), std::ios::in | std::ios::binary); + elf = new Elf(inject); + if (elf->getType() != ET_REL) + throw std::runtime_error("object for injected code is not ET_REL"); + if (elf->getMachine() != parent.getMachine()) + throw std::runtime_error( + "architecture of object for injected code doesn't match"); + + ElfSymtab_Section* symtab = nullptr; + + // Find the symbol table. + 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); + + // Find the init symbol + entry_point = -1; + 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()); + + // If the original init function is located too far away, we're going to + // need to use a trampoline. See comment in inject.c. + // Theoretically, we should check for (init - instr) > boundary, where + // boundary is the platform-dependent limit, and instr is the virtual + // address of the instruction that calls the original init, but we don't + // have it at this point, so punt to just init. + if ((init > 0xffffff && parent.getMachine() == EM_ARM) || + (init > 0x07ffffff && parent.getMachine() == EM_AARCH64)) { + Elf_SymValue* trampoline = symtab->lookup("init_trampoline"); + if (!trampoline) { + throw std::runtime_error( + "Couldn't find an 'init_trampoline' symbol in the injected code"); + } + + init_trampoline = trampoline->value.getSection(); + add_code_section(init_trampoline); + } + + // Adjust code sections offsets according to their size + std::vector<ElfSection*>::iterator c = code.begin(); + (*c)->getShdr().sh_addr = 0; + for (ElfSection* last = *(c++); c != code.end(); ++c) { + unsigned int addr = last->getShdr().sh_addr + last->getSize(); + if (addr & ((*c)->getAddrAlign() - 1)) + addr = (addr | ((*c)->getAddrAlign() - 1)) + 1; + (*c)->getShdr().sh_addr = addr; + // We need to align this section depending on the greater + // alignment required by code sections. + if (shdr.sh_addralign < (*c)->getAddrAlign()) + shdr.sh_addralign = (*c)->getAddrAlign(); + last = *c; + } + shdr.sh_size = code.back()->getAddr() + code.back()->getSize(); + data = static_cast<char*>(malloc(shdr.sh_size)); + if (!data) { + throw std::runtime_error("Could not malloc ElfSection data"); + } + char* buf = data; + for (c = code.begin(); c != code.end(); ++c) { + memcpy(buf, (*c)->getData(), (*c)->getSize()); + buf += (*c)->getSize(); + } + name = elfhack_text; + } + + ~ElfRelHackCode_Section() { delete elf; } + + void serialize(std::ofstream& file, unsigned char ei_class, + unsigned char ei_data) override { + // Readjust code offsets + for (std::vector<ElfSection*>::iterator c = code.begin(); c != code.end(); + ++c) + (*c)->getShdr().sh_addr += getAddr(); + + // Apply relocations + for (std::vector<ElfSection*>::iterator c = code.begin(); c != code.end(); + ++c) { + for (ElfSection* rel = elf->getSection(1); rel != nullptr; + rel = rel->getNext()) + if (((rel->getType() == SHT_REL) || (rel->getType() == SHT_RELA)) && + (rel->getInfo().section == *c)) { + if (rel->getType() == SHT_REL) + apply_relocations((ElfRel_Section<Elf_Rel>*)rel, *c); + else + apply_relocations((ElfRel_Section<Elf_Rela>*)rel, *c); + } + } + + ElfSection::serialize(file, ei_class, ei_data); + } + + bool isRelocatable() override { return false; } + + unsigned int getEntryPoint() { return entry_point; } + + void insertBefore(ElfSection* section, bool dirty = true) override { + // Adjust the address so that this section is adjacent to the one it's + // being inserted before. This avoids creating holes which subsequently + // might lead the PHDR-adjusting code to create unnecessary additional + // PT_LOADs. + shdr.sh_addr = + (section->getAddr() - shdr.sh_size) & ~(shdr.sh_addralign - 1); + ElfSection::insertBefore(section, dirty); + } + + private: + void add_code_section(ElfSection* section) { + if (section) { + /* Don't add section if it's already been added in the past */ + for (auto s = code.begin(); s != code.end(); ++s) { + if (section == *s) return; + } + code.push_back(section); + find_code(section); + } + } + + /* Look at the relocations associated to the given section to find other + * sections that it requires */ + void find_code(ElfSection* section) { + for (ElfSection* s = elf->getSection(1); s != nullptr; s = s->getNext()) { + if (((s->getType() == SHT_REL) || (s->getType() == SHT_RELA)) && + (s->getInfo().section == section)) { + if (s->getType() == SHT_REL) + scan_relocs_for_code((ElfRel_Section<Elf_Rel>*)s); + else + scan_relocs_for_code((ElfRel_Section<Elf_Rela>*)s); + } + } + } + + template <typename Rel_Type> + void scan_relocs_for_code(ElfRel_Section<Rel_Type>* rel) { + ElfSymtab_Section* symtab = (ElfSymtab_Section*)rel->getLink(); + for (auto r = rel->rels.begin(); r != rel->rels.end(); ++r) { + ElfSection* section = + symtab->syms[ELF64_R_SYM(r->r_info)].value.getSection(); + add_code_section(section); + } + } + + // TODO: sort out which non-aarch64 relocation types should be using + // `value` (even though in practice it's either 0 or the same as addend) + class pc32_relocation { + public: + Elf32_Addr operator()(unsigned int base_addr, Elf64_Off offset, + Elf64_Sxword addend, unsigned int addr, + Elf64_Word value) { + return addr + addend - offset - base_addr; + } + }; + + class arm_plt32_relocation { + public: + Elf32_Addr operator()(unsigned int base_addr, Elf64_Off offset, + Elf64_Sxword addend, unsigned int addr, + Elf64_Word value) { + // We don't care about sign_extend because the only case where this is + // going to be used only jumps forward. + Elf32_Addr tmp = (Elf32_Addr)(addr - offset - base_addr) >> 2; + tmp = (addend + tmp) & 0x00ffffff; + return (addend & 0xff000000) | tmp; + } + }; + + class arm_thm_jump24_relocation { + public: + Elf32_Addr operator()(unsigned int base_addr, Elf64_Off offset, + Elf64_Sxword addend, unsigned int addr, + Elf64_Word value) { + /* Follows description of b.w and bl instructions as per + ARM Architecture Reference Manual ARM® v7-A and ARM® v7-R edition, + A8.6.16 We limit ourselves to Encoding T4 of b.w and Encoding T1 of bl. + We don't care about sign_extend because the only case where this is + going to be used only jumps forward. */ + Elf32_Addr tmp = (Elf32_Addr)(addr - offset - base_addr); + unsigned int word0 = addend & 0xffff, word1 = addend >> 16; + + /* Encoding T4 of B.W is 10x1 ; Encoding T1 of BL is 11x1. */ + unsigned int type = (word1 & 0xd000) >> 12; + if (((word0 & 0xf800) != 0xf000) || ((type & 0x9) != 0x9)) + throw std::runtime_error( + "R_ARM_THM_JUMP24/R_ARM_THM_CALL relocation only supported for B.W " + "<label> and BL <label>"); + + /* When the target address points to ARM code, switch a BL to a + * BLX. This however can't be done with a B.W without adding a + * trampoline, which is not supported as of now. */ + if ((addr & 0x1) == 0) { + if (type == 0x9) + throw std::runtime_error( + "R_ARM_THM_JUMP24/R_ARM_THM_CALL relocation only supported for " + "BL <label> when label points to ARM code"); + /* The address of the target is always relative to a 4-bytes + * aligned address, so if the address of the BL instruction is + * not 4-bytes aligned, adjust for it. */ + if ((base_addr + offset) & 0x2) tmp += 2; + /* Encoding T2 of BLX is 11x0. */ + type = 0xc; + } + + unsigned int s = (word0 & (1 << 10)) >> 10; + unsigned int j1 = (word1 & (1 << 13)) >> 13; + unsigned int j2 = (word1 & (1 << 11)) >> 11; + unsigned int i1 = j1 ^ s ? 0 : 1; + unsigned int i2 = j2 ^ s ? 0 : 1; + + tmp += ((s << 24) | (i1 << 23) | (i2 << 22) | ((word0 & 0x3ff) << 12) | + ((word1 & 0x7ff) << 1)); + + s = (tmp & (1 << 24)) >> 24; + j1 = ((tmp & (1 << 23)) >> 23) ^ !s; + j2 = ((tmp & (1 << 22)) >> 22) ^ !s; + + return 0xf000 | (s << 10) | ((tmp & (0x3ff << 12)) >> 12) | (type << 28) | + (j1 << 29) | (j2 << 27) | ((tmp & 0xffe) << 15); + } + }; + + class gotoff_relocation { + public: + Elf32_Addr operator()(unsigned int base_addr, Elf64_Off offset, + Elf64_Sxword addend, unsigned int addr, + Elf64_Word value) { + return addr + addend; + } + }; + + template <int start, int end> + class abs_lo12_nc_relocation { + public: + Elf32_Addr operator()(unsigned int base_addr, Elf64_Off offset, + Elf64_Sxword addend, unsigned int addr, + Elf64_Word value) { + // Fill the bits [end:start] of the immediate value in an ADD, LDR or STR + // instruction, at bits [21:10]. + // per ARM® Architecture Reference Manual ARMv8, for ARMv8-A architecture + // profile C5.6.4, C5.6.83 or C5.6.178 and ELF for the ARM® 64-bit + // Architecture (AArch64) 4.6.6, Table 4-9. + Elf64_Word mask = (1 << (end + 1)) - 1; + return value | (((((addr + addend) & mask) >> start) & 0xfff) << 10); + } + }; + + class adr_prel_pg_hi21_relocation { + public: + Elf32_Addr operator()(unsigned int base_addr, Elf64_Off offset, + Elf64_Sxword addend, unsigned int addr, + Elf64_Word value) { + // Fill the bits [32:12] of the immediate value in a ADRP instruction, + // at bits [23:5]+[30:29]. + // per ARM® Architecture Reference Manual ARMv8, for ARMv8-A architecture + // profile C5.6.10 and ELF for the ARM® 64-bit Architecture + // (AArch64) 4.6.6, Table 4-9. + Elf64_Word imm = ((addr + addend) >> 12) - ((base_addr + offset) >> 12); + Elf64_Word immLo = (imm & 0x3) << 29; + Elf64_Word immHi = (imm & 0x1ffffc) << 3; + return value & 0x9f00001f | immLo | immHi; + } + }; + + class call26_relocation { + public: + Elf32_Addr operator()(unsigned int base_addr, Elf64_Off offset, + Elf64_Sxword addend, unsigned int addr, + Elf64_Word value) { + // Fill the bits [27:2] of the immediate value in a BL instruction, + // at bits [25:0]. + // per ARM® Architecture Reference Manual ARMv8, for ARMv8-A architecture + // profile C5.6.26 and ELF for the ARM® 64-bit Architecture + // (AArch64) 4.6.6, Table 4-10. + return value | (((addr + addend - offset - base_addr) & 0x0ffffffc) >> 2); + } + }; + + template <class relocation_type> + void apply_relocation(ElfSection* the_code, char* base, Elf_Rel* r, + unsigned int addr) { + relocation_type relocation; + Elf32_Addr value; + memcpy(&value, base + r->r_offset, 4); + value = relocation(the_code->getAddr(), r->r_offset, value, addr, value); + memcpy(base + r->r_offset, &value, 4); + } + + template <class relocation_type> + void apply_relocation(ElfSection* the_code, char* base, Elf_Rela* r, + unsigned int addr) { + relocation_type relocation; + Elf64_Word value; + memcpy(&value, base + r->r_offset, 4); + Elf32_Addr new_value = + relocation(the_code->getAddr(), r->r_offset, r->r_addend, addr, value); + memcpy(base + r->r_offset, &new_value, 4); + } + + template <typename Rel_Type> + void apply_relocations(ElfRel_Section<Rel_Type>* rel, ElfSection* the_code) { + assert(rel->getType() == Rel_Type::sh_type); + char* buf = data + (the_code->getAddr() - code.front()->getAddr()); + // TODO: various checks on the sections + ElfSymtab_Section* symtab = (ElfSymtab_Section*)rel->getLink(); + for (typename std::vector<Rel_Type>::iterator r = rel->rels.begin(); + r != rel->rels.end(); ++r) { + // TODO: various checks on the symbol + const char* name = symtab->syms[ELF64_R_SYM(r->r_info)].name; + unsigned int addr; + if (symtab->syms[ELF64_R_SYM(r->r_info)].value.getSection() == nullptr) { + if (strcmp(name, "relhack") == 0) { + addr = relhack_section.getAddr(); + } else if (strcmp(name, "relhack_end") == 0) { + addr = relhack_section.getAddr() + relhack_section.getSize(); + } else if (strcmp(name, "__ehdr_start") == 0) { + // TODO: change this ugly hack to something better + ElfSection* ehdr = parent.getSection(1)->getPrevious()->getPrevious(); + addr = ehdr->getAddr(); + } else if (strcmp(name, "original_init") == 0) { + if (init_trampoline) { + addr = init_trampoline->getAddr(); + } else { + addr = init; + } + } else if (strcmp(name, "real_original_init") == 0) { + addr = init; + } else if (relro && strcmp(name, "mprotect_cb") == 0) { + addr = mprotect_cb; + } else if (relro && strcmp(name, "sysconf_cb") == 0) { + addr = sysconf_cb; + } else if (relro && strcmp(name, "relro_start") == 0) { + addr = relro->getAddr(); + } else if (relro && strcmp(name, "relro_end") == 0) { + addr = (relro->getAddr() + relro->getMemSize()); + } 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 { + throw std::runtime_error("Unsupported symbol in relocation"); + } + } else { + ElfSection* section = + symtab->syms[ELF64_R_SYM(r->r_info)].value.getSection(); + assert((section->getType() == SHT_PROGBITS) && + (section->getFlags() & SHF_EXECINSTR)); + addr = symtab->syms[ELF64_R_SYM(r->r_info)].value.getValue(); + } + // Do the relocation +#define REL(machine, type) (EM_##machine | (R_##machine##_##type << 8)) + switch (elf->getMachine() | (ELF64_R_TYPE(r->r_info) << 8)) { + case REL(X86_64, PC32): + case REL(X86_64, PLT32): + case REL(386, PC32): + case REL(386, GOTPC): + case REL(ARM, GOTPC): + case REL(ARM, REL32): + case REL(AARCH64, PREL32): + case REL(AARCH64, + PREL64): // In theory PREL64 should have its own relocation + // function, but in practice it doesn't matter. + apply_relocation<pc32_relocation>(the_code, buf, &*r, addr); + break; + case REL(ARM, CALL): + case REL(ARM, JUMP24): + case REL(ARM, PLT32): + apply_relocation<arm_plt32_relocation>(the_code, buf, &*r, addr); + break; + case REL(ARM, THM_PC22 /* THM_CALL */): + case REL(ARM, THM_JUMP24): + apply_relocation<arm_thm_jump24_relocation>(the_code, buf, &*r, addr); + break; + case REL(386, GOTOFF): + case REL(ARM, GOTOFF): + apply_relocation<gotoff_relocation>(the_code, buf, &*r, addr); + break; + case REL(AARCH64, ADD_ABS_LO12_NC): + apply_relocation<abs_lo12_nc_relocation<0, 11>>(the_code, buf, &*r, + addr); + break; + case REL(AARCH64, ADR_PREL_PG_HI21): + apply_relocation<adr_prel_pg_hi21_relocation>(the_code, buf, &*r, + addr); + break; + case REL(AARCH64, LDST32_ABS_LO12_NC): + apply_relocation<abs_lo12_nc_relocation<2, 11>>(the_code, buf, &*r, + addr); + break; + case REL(AARCH64, LDST64_ABS_LO12_NC): + apply_relocation<abs_lo12_nc_relocation<3, 11>>(the_code, buf, &*r, + addr); + break; + case REL(AARCH64, CALL26): + apply_relocation<call26_relocation>(the_code, buf, &*r, addr); + break; + case REL(ARM, V4BX): + // Ignore R_ARM_V4BX relocations + break; + default: + throw std::runtime_error("Unsupported relocation type"); + } + } + } + + Elf *elf, &parent; + ElfRelHack_Section& relhack_section; + std::vector<ElfSection*> code; + unsigned int init; + ElfSection* init_trampoline; + unsigned int mprotect_cb; + unsigned int sysconf_cb; + int entry_point; + ElfSegment* relro; +}; + +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; +} + +unsigned int get_addend(Elf_Rela* rel, Elf* elf) { return rel->r_addend; } + +void set_relative_reloc(Elf_Rel* rel, Elf* elf, unsigned int value) { + ElfLocation loc(rel->r_offset, elf); + Elf_Addr addr; + addr.value = value; + addr.serialize(const_cast<char*>(loc.getBuffer()), + Elf_Addr::size(elf->getClass()), elf->getClass(), + elf->getData()); +} + +void set_relative_reloc(Elf_Rela* rel, Elf* elf, unsigned int value) { + // ld puts the value of relocated relocations both in the addend and + // at r_offset. For consistency, keep it that way. + set_relative_reloc((Elf_Rel*)rel, elf, value); + rel->r_addend = value; +} + +void maybe_split_segment(Elf* elf, ElfSegment* segment) { + std::list<ElfSection*>::iterator it = segment->begin(); + for (ElfSection* last = *(it++); it != segment->end(); last = *(it++)) { + // When two consecutive non-SHT_NOBITS sections are apart by more + // than the alignment of the section, the second can be moved closer + // to the first, but this requires the segment to be split. + if (((*it)->getType() != SHT_NOBITS) && (last->getType() != SHT_NOBITS) && + ((*it)->getOffset() - last->getOffset() - last->getSize() > + segment->getAlign())) { + // Probably very wrong. + Elf_Phdr phdr; + phdr.p_type = PT_LOAD; + phdr.p_vaddr = 0; + phdr.p_paddr = phdr.p_vaddr + segment->getVPDiff(); + phdr.p_flags = segment->getFlags(); + phdr.p_align = segment->getAlign(); + phdr.p_filesz = (Elf64_Xword)-1LL; + phdr.p_memsz = (Elf64_Xword)-1LL; + ElfSegment* newSegment = new ElfSegment(&phdr); + elf->insertSegmentAfter(segment, newSegment); + for (; it != segment->end(); ++it) { + newSegment->addSection(*it); + } + for (it = newSegment->begin(); it != newSegment->end(); ++it) { + segment->removeSection(*it); + } + break; + } + } +} + +// EH_FRAME constants +static const unsigned char DW_EH_PE_absptr = 0x00; +static const unsigned char DW_EH_PE_omit = 0xff; + +// Data size +static const unsigned char DW_EH_PE_LEB128 = 0x01; +static const unsigned char DW_EH_PE_data2 = 0x02; +static const unsigned char DW_EH_PE_data4 = 0x03; +static const unsigned char DW_EH_PE_data8 = 0x04; + +// Data signedness +static const unsigned char DW_EH_PE_signed = 0x08; + +// Modifiers +static const unsigned char DW_EH_PE_pcrel = 0x10; + +// Return the data size part of the encoding value +static unsigned char encoding_data_size(unsigned char encoding) { + return encoding & 0x07; +} + +// Advance `step` bytes in the buffer at `data` with size `size`, returning +// the advanced buffer pointer and remaining size. +// Returns true if step <= size. +static bool advance_buffer(char** data, size_t* size, size_t step) { + if (step > *size) return false; + + *data += step; + *size -= step; + return true; +} + +// Advance in the given buffer, skipping the full length of the variable-length +// encoded LEB128 type in CIE/FDE data. +static bool skip_LEB128(char** data, size_t* size) { + if (!*size) return false; + + while (*size && (*(*data)++ & (char)0x80)) { + (*size)--; + } + return true; +} + +// Advance in the given buffer, skipping the full length of a pointer encoded +// with the given encoding. +static bool skip_eh_frame_pointer(char** data, size_t* size, + unsigned char encoding) { + switch (encoding_data_size(encoding)) { + case DW_EH_PE_data2: + return advance_buffer(data, size, 2); + case DW_EH_PE_data4: + return advance_buffer(data, size, 4); + case DW_EH_PE_data8: + return advance_buffer(data, size, 8); + case DW_EH_PE_LEB128: + return skip_LEB128(data, size); + } + throw std::runtime_error("unreachable"); +} + +// Specialized implementations for adjust_eh_frame_pointer(). +template <typename T> +static bool adjust_eh_frame_sized_pointer(char** data, size_t* size, + ElfSection* eh_frame, + unsigned int origAddr, Elf* elf) { + if (*size < sizeof(T)) return false; + + serializable<FixedSizeData<T>> pointer(*data, *size, elf->getClass(), + elf->getData()); + mozilla::CheckedInt<T> value = pointer.value; + if (origAddr < eh_frame->getAddr()) { + unsigned int diff = eh_frame->getAddr() - origAddr; + value -= diff; + } else { + unsigned int diff = origAddr - eh_frame->getAddr(); + value += diff; + } + if (!value.isValid()) + throw std::runtime_error("Overflow while adjusting eh_frame"); + pointer.value = value.value(); + pointer.serialize(*data, *size, elf->getClass(), elf->getData()); + return advance_buffer(data, size, sizeof(T)); +} + +// In the given eh_frame section, adjust the pointer with the given encoding, +// pointed to by the given buffer (`data`, `size`), considering the eh_frame +// section was originally at `origAddr`. Also advances in the buffer. +static bool adjust_eh_frame_pointer(char** data, size_t* size, + unsigned char encoding, + ElfSection* eh_frame, unsigned int origAddr, + Elf* elf) { + if ((encoding & 0x70) != DW_EH_PE_pcrel) + return skip_eh_frame_pointer(data, size, encoding); + + if (encoding & DW_EH_PE_signed) { + switch (encoding_data_size(encoding)) { + case DW_EH_PE_data2: + return adjust_eh_frame_sized_pointer<int16_t>(data, size, eh_frame, + origAddr, elf); + case DW_EH_PE_data4: + return adjust_eh_frame_sized_pointer<int32_t>(data, size, eh_frame, + origAddr, elf); + case DW_EH_PE_data8: + return adjust_eh_frame_sized_pointer<int64_t>(data, size, eh_frame, + origAddr, elf); + } + } else { + switch (encoding_data_size(encoding)) { + case DW_EH_PE_data2: + return adjust_eh_frame_sized_pointer<uint16_t>(data, size, eh_frame, + origAddr, elf); + case DW_EH_PE_data4: + return adjust_eh_frame_sized_pointer<uint32_t>(data, size, eh_frame, + origAddr, elf); + case DW_EH_PE_data8: + return adjust_eh_frame_sized_pointer<uint64_t>(data, size, eh_frame, + origAddr, elf); + } + } + + throw std::runtime_error("Unsupported eh_frame pointer encoding"); +} + +// The eh_frame section may contain "PC"-relative pointers. If we move the +// section, those need to be adjusted. Other type of pointers are relative to +// sections we don't touch. +static void adjust_eh_frame(ElfSection* eh_frame, unsigned int origAddr, + Elf* elf) { + if (eh_frame->getAddr() == origAddr) // nothing to do; + return; + + char* data = const_cast<char*>(eh_frame->getData()); + size_t size = eh_frame->getSize(); + unsigned char LSDAencoding = DW_EH_PE_omit; + unsigned char FDEencoding = DW_EH_PE_absptr; + bool hasZ = false; + + // Decoding of eh_frame based on https://www.airs.com/blog/archives/460 + while (size) { + if (size < sizeof(uint32_t)) goto malformed; + + serializable<FixedSizeData<uint32_t>> entryLength( + data, size, elf->getClass(), elf->getData()); + if (!advance_buffer(&data, &size, sizeof(uint32_t))) goto malformed; + + char* cursor = data; + size_t length = entryLength.value; + + if (length == 0) { + continue; + } + + if (size < sizeof(uint32_t)) goto malformed; + + serializable<FixedSizeData<uint32_t>> id(data, size, elf->getClass(), + elf->getData()); + if (!advance_buffer(&cursor, &length, sizeof(uint32_t))) goto malformed; + + if (id.value == 0) { + // This is a Common Information Entry + if (length < 2) goto malformed; + // Reset LSDA and FDE encodings, and hasZ for subsequent FDEs. + LSDAencoding = DW_EH_PE_omit; + FDEencoding = DW_EH_PE_absptr; + hasZ = false; + // CIE version. Should only be 1 or 3. + char version = *cursor++; + length--; + if (version != 1 && version != 3) { + throw std::runtime_error("Unsupported eh_frame version"); + } + // NUL terminated string. + const char* augmentationString = cursor; + size_t l = strnlen(augmentationString, length - 1); + if (l == length - 1) goto malformed; + if (!advance_buffer(&cursor, &length, l + 1)) goto malformed; + // Skip code alignment factor (LEB128) + if (!skip_LEB128(&cursor, &length)) goto malformed; + // Skip data alignment factor (LEB128) + if (!skip_LEB128(&cursor, &length)) goto malformed; + // Skip return address register (single byte in CIE version 1, LEB128 + // in CIE version 3) + if (version == 1) { + if (!advance_buffer(&cursor, &length, 1)) goto malformed; + } else { + if (!skip_LEB128(&cursor, &length)) goto malformed; + } + // Past this, it's data driven by the contents of the augmentation string. + for (size_t i = 0; i < l; i++) { + if (!length) goto malformed; + switch (augmentationString[i]) { + case 'z': + if (!skip_LEB128(&cursor, &length)) goto malformed; + hasZ = true; + break; + case 'L': + LSDAencoding = *cursor++; + length--; + break; + case 'R': + FDEencoding = *cursor++; + length--; + break; + case 'P': { + unsigned char encoding = (unsigned char)*cursor++; + length--; + if (!adjust_eh_frame_pointer(&cursor, &length, encoding, eh_frame, + origAddr, elf)) + goto malformed; + } break; + default: + goto malformed; + } + } + } else { + // This is a Frame Description Entry + // Starting address + if (!adjust_eh_frame_pointer(&cursor, &length, FDEencoding, eh_frame, + origAddr, elf)) + goto malformed; + + if (LSDAencoding != DW_EH_PE_omit) { + // Skip number of bytes, same size as the starting address. + if (!skip_eh_frame_pointer(&cursor, &length, FDEencoding)) + goto malformed; + if (hasZ) { + if (!skip_LEB128(&cursor, &length)) goto malformed; + } + // pointer to the LSDA. + if (!adjust_eh_frame_pointer(&cursor, &length, LSDAencoding, eh_frame, + origAddr, elf)) + goto malformed; + } + } + + data += entryLength.value; + size -= entryLength.value; + } + return; + +malformed: + throw std::runtime_error("malformed .eh_frame"); +} + +template <typename Rel_Type> +int do_relocation_section(Elf* elf, unsigned int rel_type, + unsigned int rel_type2, bool force) { + ElfDynamic_Section* dyn = elf->getDynSection(); + if (dyn == nullptr) { + fprintf(stderr, "Couldn't find SHT_DYNAMIC section\n"); + return -1; + } + + ElfRel_Section<Rel_Type>* section = + (ElfRel_Section<Rel_Type>*)dyn->getSectionForType(Rel_Type::d_tag); + if (section == nullptr) { + fprintf(stderr, "No relocations\n"); + return -1; + } + assert(section->getType() == Rel_Type::sh_type); + + Elf64_Shdr relhack64_section = {0, + SHT_PROGBITS, + SHF_ALLOC, + 0, + (Elf64_Off)-1LL, + 0, + SHN_UNDEF, + 0, + Elf_Addr::size(elf->getClass()), + Elf_Addr::size(elf->getClass())}; + Elf64_Shdr relhackcode64_section = {0, + SHT_PROGBITS, + SHF_ALLOC | SHF_EXECINSTR, + 0, + (Elf64_Off)-1LL, + 0, + SHN_UNDEF, + 0, + 1, + 0}; + + unsigned int entry_sz = Elf_Addr::size(elf->getClass()); + + // The injected code needs to be executed before any init code in the + // binary. There are three possible cases: + // - The binary has no init code at all. In this case, we will add a + // DT_INIT entry pointing to the injected code. + // - The binary has a DT_INIT entry. In this case, we will interpose: + // we change DT_INIT to point to the injected code, and have the + // injected code call the original DT_INIT entry point. + // - The binary has no DT_INIT entry, but has a DT_INIT_ARRAY. In this + // case, we interpose as well, by replacing the first entry in the + // array to point to the injected code, and have the injected code + // call the original first entry. + // The binary may have .ctors instead of DT_INIT_ARRAY, for its init + // functions, but this falls into the second case above, since .ctors + // are actually run by DT_INIT code. + ElfValue* value = dyn->getValueForType(DT_INIT); + unsigned int original_init = value ? value->getValue() : 0; + ElfSection* init_array = nullptr; + if (!value || !value->getValue()) { + value = dyn->getValueForType(DT_INIT_ARRAYSZ); + if (value && value->getValue() >= entry_sz) + init_array = dyn->getSectionForType(DT_INIT_ARRAY); + } + + Elf_Shdr relhack_section(relhack64_section); + Elf_Shdr relhackcode_section(relhackcode64_section); + auto relhack_ptr = std::make_unique<ElfRelHack_Section>(relhack_section); + auto relhack = relhack_ptr.get(); + + ElfSymtab_Section* symtab = (ElfSymtab_Section*)section->getLink(); + Elf_SymValue* sym = symtab->lookup("__cxa_pure_virtual"); + + std::vector<Rel_Type> new_rels; + 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 (!ELF64_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. + // These functions are however normally never called. In the case + // where they would, jumping to the null address instead of calling + // __cxa_pure_virtual is going to work just as well. So we can remove + // relocations for the __cxa_pure_virtual symbol and null out the + // content at the offset pointed by the relocation. + if (sym) { + if (sym->defined) { + // If we are statically linked to libstdc++, the + // __cxa_pure_virtual symbol is defined in our lib, and we + // have relative relocations (rel_type) for it. + if (ELF64_R_TYPE(i->r_info) == rel_type) { + Elf_Addr addr(loc.getBuffer(), entry_sz, elf->getClass(), + elf->getData()); + if (addr.value == sym->value.getValue()) { + memset((char*)loc.getBuffer(), 0, entry_sz); + continue; + } + } + } else { + // If we are dynamically linked to libstdc++, the + // __cxa_pure_virtual symbol is undefined in our lib, and we + // have absolute relocations (rel_type2) for it. + if ((ELF64_R_TYPE(i->r_info) == rel_type2) && + (sym == &symtab->syms[ELF64_R_SYM(i->r_info)])) { + memset((char*)loc.getBuffer(), 0, entry_sz); + continue; + } + } + } + // 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) || + (ELF64_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 if (i->r_offset & 1) { + // RELR packing doesn't support relocations at an odd address, but + // there shouldn't be any. + 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 + // by the relocation offset to also contain the addend. Which is true with + // BFD ld and gold, but not lld, which leaves that nulled out. So if that + // value is nulled out, we update it to the addend. + Elf_Addr addr(loc.getBuffer(), entry_sz, elf->getClass(), elf->getData()); + unsigned int addend = get_addend(&*i, elf); + if (addr.value == 0) { + addr.value = addend; + addr.serialize(const_cast<char*>(loc.getBuffer()), entry_sz, + elf->getClass(), elf->getData()); + } else if (addr.value != addend) { + fprintf(stderr, + "Relocation addend inconsistent with content. Skipping\n"); + return -1; + } + relhack->push_back(i->r_offset); + } + } + + 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()); + 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 (ELF64_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 (ELF64_R_TYPE(rel.r_info) == rel_type) { + original_init = addend; + } else if (ELF64_R_TYPE(rel.r_info) == rel_type2) { + ElfSymtab_Section* symtab = (ElfSymtab_Section*)section->getLink(); + original_init = + symtab->syms[ELF64_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 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)) { + ElfSection* gnu_versym = dyn->getSectionForType(DT_VERSYM); + auto lookup = [&symtab, &gnu_versym](const char* symbol) { + Elf_SymValue* sym_value = symtab->lookup(symbol, STT(FUNC)); + if (!sym_value) { + symtab->syms.emplace_back(); + sym_value = &symtab->syms.back(); + symtab->grow(symtab->syms.size() * symtab->getEntSize()); + sym_value->name = + ((ElfStrtab_Section*)symtab->getLink())->getStr(symbol); + sym_value->info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC); + sym_value->other = STV_DEFAULT; + new (&sym_value->value) ElfLocation(nullptr, 0, ElfLocation::ABSOLUTE); + sym_value->size = 0; + sym_value->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. + if (gnu_versym) { + gnu_versym->grow(gnu_versym->getSize() + gnu_versym->getEntSize()); + } + } + return sym_value; + }; + + Elf_SymValue* mprotect = lookup("mprotect"); + Elf_SymValue* sysconf = lookup("sysconf"); + + // Add relocations for the mprotect and sysconf symbols. + auto add_relocation_to = [&new_rels, &symtab, rel_type2]( + Elf_SymValue* symbol, unsigned int location) { + new_rels.emplace_back(); + Rel_Type& rel = new_rels.back(); + memset(&rel, 0, sizeof(rel)); + rel.r_info = ELF64_R_INFO( + std::distance(symtab->syms.begin(), + std::vector<Elf_SymValue>::iterator(symbol)), + rel_type2); + rel.r_offset = location; + return location; + }; + + // Find the beginning of the bss section, and use an aligned location in + // there for the relocation. + for (ElfSection* s = elf->getSection(1); s != nullptr; s = s->getNext()) { + if (s->getType() != SHT_NOBITS || + (s->getFlags() & (SHF_TLS | SHF_WRITE)) != SHF_WRITE) { + continue; + } + size_t ptr_size = Elf_Addr::size(elf->getClass()); + size_t usable_start = (s->getAddr() + ptr_size - 1) & ~(ptr_size - 1); + size_t usable_end = (s->getAddr() + s->getSize()) & ~(ptr_size - 1); + if (usable_end - usable_start >= 2 * ptr_size) { + mprotect_cb = add_relocation_to(mprotect, usable_start); + sysconf_cb = add_relocation_to(sysconf, usable_start + ptr_size); + break; + } + } + + if (mprotect_cb == 0 || sysconf_cb == 0) { + fprintf(stderr, "Couldn't find .bss. Skipping\n"); + return -1; + } + } + + size_t old_size = section->getSize(); + + section->rels.assign(new_rels.begin(), new_rels.end()); + section->shrink(new_rels.size() * section->getEntSize()); + + auto relhackcode_ptr = std::make_unique<ElfRelHackCode_Section>( + relhackcode_section, *elf, *relhack, original_init, mprotect_cb, + sysconf_cb); + auto relhackcode = relhackcode_ptr.get(); + // Find the first executable section, and insert the relhack code before + // that. The relhack data is inserted between .rel.dyn and .rel.plt. + ElfSection* first_executable = nullptr; + for (ElfSection* s = elf->getSection(1); s != nullptr; s = s->getNext()) { + if (s->getFlags() & SHF_EXECINSTR) { + first_executable = s; + break; + } + } + + if (!first_executable) { + fprintf(stderr, "Couldn't find executable section. Skipping\n"); + return -1; + } + + // Once the pointers for relhack, relhackcode, and init are inserted, + // their ownership is transferred to the Elf object, which will free + // them when itself is freed. Hence the .release() calls here (and + // the init.release() call later on). Please note that the raw + // pointers will continue to be used after .release(), which is why + // we are caching them (since .release() will end up setting the + // smart pointer's internal raw pointer to nullptr). + + relhack->insertBefore(section); + relhack_ptr.release(); + + relhackcode->insertBefore(first_executable); + relhackcode_ptr.release(); + + // Don't try further if we can't gain from the relocation section size change. + // We account for the fact we're going to split the PT_LOAD before the + // injected code section, so the overhead of the page alignment for section + // needs to be accounted for. + size_t align = first_executable->getSegmentByType(PT_LOAD)->getAlign(); + size_t new_size = relhack->getSize() + section->getSize() + + relhackcode->getSize() + + (relhackcode->getAddr() & (align - 1)); + if (!force && (new_size >= old_size || old_size - new_size < align)) { + fprintf(stderr, "No gain. Skipping\n"); + return -1; + } + + // .eh_frame/.eh_frame_hdr may be between the relocation sections and the + // executable sections. When that happens, we may end up creating a separate + // PT_LOAD for just both of them because they are not considered relocatable. + // But they are, in fact, kind of relocatable, albeit with some manual work. + // Which we'll do here. + ElfSegment* eh_frame_segment = elf->getSegmentByType(PT_GNU_EH_FRAME); + ElfSection* eh_frame_hdr = + eh_frame_segment ? eh_frame_segment->getFirstSection() : nullptr; + // The .eh_frame section usually follows the eh_frame_hdr section. + ElfSection* eh_frame = eh_frame_hdr ? eh_frame_hdr->getNext() : nullptr; + ElfSection* first = eh_frame_hdr; + ElfSection* second = eh_frame; + if (eh_frame && strcmp(eh_frame->getName(), ".eh_frame")) { + // But sometimes it appears *before* the eh_frame_hdr section. + eh_frame = eh_frame_hdr->getPrevious(); + first = eh_frame; + second = eh_frame_hdr; + } + if (eh_frame_hdr && (!eh_frame || strcmp(eh_frame->getName(), ".eh_frame"))) { + throw std::runtime_error( + "Expected to find an .eh_frame section adjacent to .eh_frame_hdr"); + } + if (eh_frame && first->getAddr() > relhack->getAddr() && + second->getAddr() < first_executable->getAddr()) { + // The distance between both sections needs to be preserved because + // eh_frame_hdr contains relative offsets to eh_frame. Well, they could be + // relocated too, but it's not worth the effort for the few number of bytes + // this would save. + Elf64_Off distance = second->getAddr() - first->getAddr(); + Elf64_Addr origAddr = eh_frame->getAddr(); + ElfSection* previous = first->getPrevious(); + first->getShdr().sh_addr = (previous->getAddr() + previous->getSize() + + first->getAddrAlign() - 1) & + ~(first->getAddrAlign() - 1); + second->getShdr().sh_addr = + (first->getAddr() + std::min(first->getSize(), distance) + + second->getAddrAlign() - 1) & + ~(second->getAddrAlign() - 1); + // Re-adjust to keep the original distance. + // If the first section has a smaller alignment requirement than the second, + // the second will be farther away, so we need to adjust the first. + // If the second section has a smaller alignment requirement than the first, + // it will already be at the right distance. + first->getShdr().sh_addr = second->getAddr() - distance; + assert(distance == second->getAddr() - first->getAddr()); + first->markDirty(); + adjust_eh_frame(eh_frame, origAddr, elf); + } + + // Adjust PT_LOAD segments + for (ElfSegment* segment = elf->getSegmentByType(PT_LOAD); segment; + segment = elf->getSegmentByType(PT_LOAD, segment)) { + maybe_split_segment(elf, segment); + } + + // Ensure Elf sections will be at their final location. + elf->normalize(); + auto init = + std::make_unique<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 = §ion->rels[init_array_insert]; + rel->r_info = ELF64_R_INFO(0, rel_type); // Set as a relative relocation + set_relative_reloc(rel, elf, init->getValue()); + } else { + if (dyn->setValueForType(DT_INIT, init.get())) { + init.release(); + } else { + 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)); + + return 0; +} + +static inline int backup_file(const char* name) { + std::string fname(name); + fname += ".bak"; + return rename(name, fname.c_str()); +} + +void do_file(const char* name, bool backup = false, bool force = false) { + std::ifstream file(name, std::ios::in | std::ios::binary); + Elf elf(file); + unsigned int size = elf.getSize(); + fprintf(stderr, "%s: ", name); + if (elf.getType() != ET_DYN) { + fprintf(stderr, "Not a shared object. Skipping\n"); + return; + } + + for (ElfSection* section = elf.getSection(1); section != nullptr; + section = section->getNext()) { + if (section->getName() && + (strncmp(section->getName(), ".elfhack.", 9) == 0)) { + fprintf(stderr, "Already elfhacked. Skipping\n"); + return; + } + } + + int exit = -1; + switch (elf.getMachine()) { + case EM_386: + exit = + do_relocation_section<Elf_Rel>(&elf, R_386_RELATIVE, R_386_32, force); + break; + case EM_X86_64: + exit = do_relocation_section<Elf_Rela>(&elf, R_X86_64_RELATIVE, + R_X86_64_64, force); + break; + case EM_ARM: + exit = do_relocation_section<Elf_Rel>(&elf, R_ARM_RELATIVE, R_ARM_ABS32, + force); + break; + case EM_AARCH64: + exit = do_relocation_section<Elf_Rela>(&elf, R_AARCH64_RELATIVE, + R_AARCH64_ABS64, force); + break; + default: + throw std::runtime_error("unsupported architecture"); + } + if (exit == 0) { + if (!force && (elf.getSize() >= size)) { + fprintf(stderr, "No gain. Skipping\n"); + } else if (backup && backup_file(name) != 0) { + fprintf(stderr, "Couln't create backup file\n"); + } else { + std::ofstream ofile(name, + std::ios::out | std::ios::binary | std::ios::trunc); + elf.write(ofile); + fprintf(stderr, "Reduced by %d bytes\n", size - elf.getSize()); + } + } +} + +void undo_file(const char* name, bool backup = false) { + std::ifstream file(name, std::ios::in | std::ios::binary); + Elf elf(file); + unsigned int size = elf.getSize(); + fprintf(stderr, "%s: ", name); + if (elf.getType() != ET_DYN) { + fprintf(stderr, "Not a shared object. Skipping\n"); + return; + } + + ElfSection *data = nullptr, *text = nullptr; + for (ElfSection* section = elf.getSection(1); section != nullptr; + section = section->getNext()) { + if (section->getName() && (strcmp(section->getName(), elfhack_data) == 0)) + data = section; + if (section->getName() && (strcmp(section->getName(), elfhack_text) == 0)) + text = section; + } + + if (!data || !text) { + fprintf(stderr, "Not elfhacked. Skipping\n"); + return; + } + + // When both elfhack sections are in the same segment, try to merge + // the segment that contains them both and the following segment. + // When the elfhack sections are in separate segments, try to merge + // those segments. + ElfSegment* first = data->getSegmentByType(PT_LOAD); + ElfSegment* second = text->getSegmentByType(PT_LOAD); + if (first == second) { + second = elf.getSegmentByType(PT_LOAD, first); + } + + // Only merge the segments when their flags match. + if (second->getFlags() != first->getFlags()) { + fprintf(stderr, "Couldn't merge PT_LOAD segments. Skipping\n"); + return; + } + // Move sections from the second PT_LOAD to the first, and remove the + // second PT_LOAD segment. + for (std::list<ElfSection*>::iterator section = second->begin(); + section != second->end(); ++section) + first->addSection(*section); + + elf.removeSegment(second); + elf.normalize(); + + if (backup && backup_file(name) != 0) { + fprintf(stderr, "Couln't create backup file\n"); + } else { + std::ofstream ofile(name, + std::ios::out | std::ios::binary | std::ios::trunc); + elf.write(ofile); + fprintf(stderr, "Grown by %d bytes\n", elf.getSize() - size); + } +} + +int main(int argc, char* argv[]) { + int arg; + bool backup = false; + bool force = false; + bool revert = false; + char* lastSlash = rindex(argv[0], '/'); + if (lastSlash != nullptr) rundir = strndup(argv[0], lastSlash - argv[0]); + for (arg = 1; arg < argc; arg++) { + if (strcmp(argv[arg], "-f") == 0) + force = true; + else if (strcmp(argv[arg], "-b") == 0) + backup = true; + else if (strcmp(argv[arg], "-r") == 0) + revert = true; + else if (revert) { + undo_file(argv[arg], backup); + } else + do_file(argv[arg], backup, force); + } + + free(rundir); + return 0; +} diff --git a/build/unix/elfhack/elfxx.h b/build/unix/elfhack/elfxx.h new file mode 100644 index 0000000000..26dce9b9cd --- /dev/null +++ b/build/unix/elfhack/elfxx.h @@ -0,0 +1,700 @@ +/* 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 <stdexcept> +#include <list> +#include <vector> +#include <cstring> +#include <fstream> +#include <algorithm> +#include <elf.h> +#include <asm/byteorder.h> + +// Technically, __*_to_cpu and __cpu_to* function are equivalent, +// so swap can use either of both. +#define def_swap(endian, type, bits) \ + static inline type##bits##_t swap(type##bits##_t i) { \ + return __##endian##bits##_to_cpu(i); \ + } + +class little_endian { + public: + def_swap(le, uint, 16); + def_swap(le, uint, 32); + def_swap(le, uint, 64); + def_swap(le, int, 16); + def_swap(le, int, 32); + def_swap(le, int, 64); +}; + +class big_endian { + public: + def_swap(be, uint, 16); + def_swap(be, uint, 32); + def_swap(be, uint, 64); + def_swap(be, int, 16); + def_swap(be, int, 32); + def_swap(be, int, 64); +}; + +// forward declaration +class ElfSection; +class ElfSegment; +// TODO: Rename Elf_* types +class Elf_Ehdr; +class Elf_Phdr; +class Elf; +class ElfDynamic_Section; +class ElfStrtab_Section; + +template <typename X> +class FixedSizeData { + public: + struct Wrapper { + X value; + }; + typedef Wrapper Type32; + typedef Wrapper Type64; + + template <class endian, typename R, typename T> + static void swap(T& t, R& r) { + r.value = endian::swap(t.value); + } +}; + +class Elf_Ehdr_Traits { + public: + typedef Elf32_Ehdr Type32; + typedef Elf64_Ehdr Type64; + + template <class endian, typename R, typename T> + static void swap(T& t, R& r); +}; + +class Elf_Phdr_Traits { + public: + typedef Elf32_Phdr Type32; + typedef Elf64_Phdr Type64; + + template <class endian, typename R, typename T> + static void swap(T& t, R& r); +}; + +class Elf_Shdr_Traits { + public: + typedef Elf32_Shdr Type32; + typedef Elf64_Shdr Type64; + + template <class endian, typename R, typename T> + static void swap(T& t, R& r); +}; + +class Elf_Dyn_Traits { + public: + typedef Elf32_Dyn Type32; + typedef Elf64_Dyn Type64; + + template <class endian, typename R, typename T> + static void swap(T& t, R& r); +}; + +class Elf_Sym_Traits { + public: + typedef Elf32_Sym Type32; + typedef Elf64_Sym Type64; + + template <class endian, typename R, typename T> + static void swap(T& t, R& r); +}; + +class Elf_Rel_Traits { + public: + typedef Elf32_Rel Type32; + typedef Elf64_Rel Type64; + + template <class endian, typename R, typename T> + static void swap(T& t, R& r); +}; + +class Elf_Rela_Traits { + public: + typedef Elf32_Rela Type32; + typedef Elf64_Rela Type64; + + template <class endian, typename R, typename T> + static void swap(T& t, R& r); +}; + +class ElfValue { + public: + virtual unsigned int getValue() { return 0; } + virtual ElfSection* getSection() { return nullptr; } +}; + +class ElfPlainValue : public ElfValue { + unsigned int value; + + public: + ElfPlainValue(unsigned int val) : value(val){}; + unsigned int getValue() { return value; } +}; + +class ElfLocation : public ElfValue { + ElfSection* section; + unsigned int offset; + + public: + enum position { ABSOLUTE, RELATIVE }; + ElfLocation() : section(nullptr), offset(0){}; + ElfLocation(ElfSection* section, unsigned int off, + enum position pos = RELATIVE); + ElfLocation(unsigned int location, Elf* elf); + unsigned int getValue(); + ElfSection* getSection() { return section; } + const char* getBuffer(); +}; + +class ElfSize : public ElfValue { + ElfSection* section; + + public: + ElfSize(ElfSection* s) : section(s){}; + unsigned int getValue(); + ElfSection* getSection() { return section; } +}; + +class ElfEntSize : public ElfValue { + ElfSection* section; + + public: + ElfEntSize(ElfSection* s) : section(s){}; + unsigned int getValue(); + ElfSection* getSection() { return section; } +}; + +template <typename T> +class serializable : public T::Type64 { + public: + serializable(){}; + serializable(const typename T::Type64& p) : T::Type64(p){}; + + private: + template <typename R> + void init(const char* buf, size_t len, unsigned char ei_data) { + R e; + assert(len >= sizeof(e)); + memcpy(&e, buf, sizeof(e)); + if (ei_data == ELFDATA2LSB) { + T::template swap<little_endian>(e, *this); + return; + } else if (ei_data == ELFDATA2MSB) { + T::template swap<big_endian>(e, *this); + return; + } + throw std::runtime_error("Unsupported ELF data encoding"); + } + + template <typename R> + void serialize(const char* buf, size_t len, unsigned char ei_data) { + assert(len >= sizeof(R)); + if (ei_data == ELFDATA2LSB) { + T::template swap<little_endian>(*this, *(R*)buf); + return; + } else if (ei_data == ELFDATA2MSB) { + T::template swap<big_endian>(*this, *(R*)buf); + return; + } + throw std::runtime_error("Unsupported ELF data encoding"); + } + + public: + serializable(const char* buf, size_t len, unsigned char ei_class, + unsigned char ei_data) { + if (ei_class == ELFCLASS32) { + init<typename T::Type32>(buf, len, ei_data); + return; + } else if (ei_class == ELFCLASS64) { + init<typename T::Type64>(buf, len, ei_data); + return; + } + throw std::runtime_error("Unsupported ELF class"); + } + + serializable(std::ifstream& file, unsigned char ei_class, + unsigned char ei_data) { + if (ei_class == ELFCLASS32) { + typename T::Type32 e; + file.read((char*)&e, sizeof(e)); + init<typename T::Type32>((char*)&e, sizeof(e), ei_data); + return; + } else if (ei_class == ELFCLASS64) { + typename T::Type64 e; + file.read((char*)&e, sizeof(e)); + init<typename T::Type64>((char*)&e, sizeof(e), ei_data); + return; + } + throw std::runtime_error("Unsupported ELF class or data encoding"); + } + + void serialize(std::ofstream& file, unsigned char ei_class, + unsigned char ei_data) { + if (ei_class == ELFCLASS32) { + typename T::Type32 e; + serialize<typename T::Type32>((char*)&e, sizeof(e), ei_data); + file.write((char*)&e, sizeof(e)); + return; + } else if (ei_class == ELFCLASS64) { + typename T::Type64 e; + serialize<typename T::Type64>((char*)&e, sizeof(e), ei_data); + file.write((char*)&e, sizeof(e)); + return; + } + throw std::runtime_error("Unsupported ELF class or data encoding"); + } + + void serialize(char* buf, size_t len, unsigned char ei_class, + unsigned char ei_data) { + if (ei_class == ELFCLASS32) { + serialize<typename T::Type32>(buf, len, ei_data); + return; + } else if (ei_class == ELFCLASS64) { + serialize<typename T::Type64>(buf, len, ei_data); + return; + } + throw std::runtime_error("Unsupported ELF class"); + } + + static inline unsigned int size(unsigned char ei_class) { + if (ei_class == ELFCLASS32) + return sizeof(typename T::Type32); + else if (ei_class == ELFCLASS64) + return sizeof(typename T::Type64); + return 0; + } +}; + +typedef serializable<Elf_Shdr_Traits> Elf_Shdr; + +class Elf { + public: + Elf(std::ifstream& file); + ~Elf(); + + /* index == -1 is treated as index == ehdr.e_shstrndx */ + ElfSection* getSection(int index); + + ElfSection* getSectionAt(Elf64_Off offset); + + ElfSegment* getSegmentByType(unsigned int type, ElfSegment* last = nullptr); + + ElfDynamic_Section* getDynSection(); + + void normalize(); + void write(std::ofstream& file); + + unsigned char getClass(); + unsigned char getData(); + unsigned char getType(); + unsigned char getMachine(); + unsigned int getSize(); + + void insertSegmentAfter(ElfSegment* previous, ElfSegment* segment) { + std::vector<ElfSegment*>::iterator prev = + std::find(segments.begin(), segments.end(), previous); + segments.insert(prev + 1, segment); + } + + void removeSegment(ElfSegment* segment); + + private: + Elf_Ehdr* ehdr; + ElfLocation eh_entry; + ElfStrtab_Section* eh_shstrndx; + ElfSection** sections; + std::vector<ElfSegment*> segments; + ElfSection *shdr_section, *phdr_section; + /* Values used only during initialization */ + Elf_Shdr** tmp_shdr; + std::ifstream* tmp_file; +}; + +class ElfSection { + public: + typedef union { + ElfSection* section; + int index; + } SectionInfo; + + ElfSection(Elf_Shdr& s, std::ifstream* file, Elf* parent); + + virtual ~ElfSection() { free(data); } + + const char* getName() { return name; } + unsigned int getType() { return shdr.sh_type; } + unsigned int getFlags() { return shdr.sh_flags; } + Elf64_Addr getAddr(); + Elf64_Off 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) { + 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(); + } + } + + Elf64_Off getOffset(); + int getIndex(); + Elf_Shdr& getShdr(); + + ElfSection* getNext() { return next; } + ElfSection* getPrevious() { return previous; } + + virtual bool isRelocatable() { + return ((getType() == SHT_SYMTAB) || (getType() == SHT_STRTAB) || + (getType() == SHT_RELA) || (getType() == SHT_HASH) || + (getType() == SHT_NOTE) || (getType() == SHT_REL) || + (getType() == SHT_DYNSYM) || (getType() == SHT_GNU_HASH) || + (getType() == SHT_GNU_verdef) || (getType() == SHT_GNU_verneed) || + (getType() == SHT_GNU_versym) || getSegmentByType(PT_INTERP)) && + (getFlags() & SHF_ALLOC); + } + + void insertAfter(ElfSection* section, bool dirty = true) { + if (previous != nullptr) previous->next = next; + if (next != nullptr) next->previous = previous; + previous = section; + if (section != nullptr) { + next = section->next; + section->next = this; + } else + next = nullptr; + if (next != nullptr) next->previous = this; + if (dirty) markDirty(); + insertInSegments(section->segments); + } + + virtual void insertBefore(ElfSection* section, bool dirty = true) { + if (previous != nullptr) previous->next = next; + if (next != nullptr) next->previous = previous; + next = section; + if (section != nullptr) { + previous = section->previous; + section->previous = this; + } else + previous = nullptr; + if (previous != nullptr) previous->next = this; + if (dirty) markDirty(); + insertInSegments(section->segments); + } + + void markDirty() { + if (link != nullptr) shdr.sh_link = -1; + if (info.index) shdr.sh_info = -1; + shdr.sh_offset = -1; + if (isRelocatable()) shdr.sh_addr = -1; + if (next) next->markDirty(); + } + + virtual void serialize(std::ofstream& file, unsigned char ei_class, + unsigned char ei_data) { + if (getType() == SHT_NOBITS) return; + file.seekp(getOffset()); + file.write(data, getSize()); + } + + ElfSegment* getSegmentByType(unsigned int type); + + private: + friend class ElfSegment; + + void addToSegment(ElfSegment* segment) { segments.push_back(segment); } + + void removeFromSegment(ElfSegment* segment) { + std::vector<ElfSegment*>::iterator i = + std::find(segments.begin(), segments.end(), segment); + segments.erase(i, i + 1); + } + + void insertInSegments(std::vector<ElfSegment*>& segs); + + protected: + Elf_Shdr shdr; + char* data; + const char* name; + + private: + ElfSection* link; + SectionInfo info; + ElfSection *next, *previous; + int index; + std::vector<ElfSegment*> segments; +}; + +class ElfSegment { + public: + ElfSegment(Elf_Phdr* phdr); + + unsigned int getType() { return type; } + unsigned int getFlags() { return flags; } + unsigned int getAlign() { return align; } + + ElfSection* getFirstSection() { + return sections.empty() ? nullptr : sections.front(); + } + int getVPDiff() { return v_p_diff; } + unsigned int getFileSize(); + unsigned int getMemSize(); + unsigned int getOffset(); + unsigned int getAddr(); + + void addSection(ElfSection* section); + void removeSection(ElfSection* section); + + std::list<ElfSection*>::iterator begin() { return sections.begin(); } + std::list<ElfSection*>::iterator end() { return sections.end(); } + + void clear(); + + private: + unsigned int type; + int v_p_diff; // Difference between physical and virtual address + unsigned int flags; + unsigned int align; + std::list<ElfSection*> sections; + // The following are only really used for PT_GNU_RELRO until something + // better is found. + unsigned int vaddr; + unsigned int filesz, memsz; +}; + +class Elf_Ehdr : public serializable<Elf_Ehdr_Traits>, public ElfSection { + public: + Elf_Ehdr(std::ifstream& file, unsigned char ei_class, unsigned char ei_data); + void serialize(std::ofstream& file, unsigned char ei_class, + unsigned char ei_data) { + serializable<Elf_Ehdr_Traits>::serialize(file, ei_class, ei_data); + } +}; + +class Elf_Phdr : public serializable<Elf_Phdr_Traits> { + public: + Elf_Phdr(){}; + Elf_Phdr(std::ifstream& file, unsigned char ei_class, unsigned char ei_data) + : serializable<Elf_Phdr_Traits>(file, ei_class, ei_data){}; + bool contains(ElfSection* section) { + unsigned int size = section->getSize(); + unsigned int addr = section->getAddr(); + // This may be biased, but should work in most cases + if ((section->getFlags() & SHF_ALLOC) == 0) return false; + // Special case for PT_DYNAMIC. Eventually, this should + // be better handled than special cases + if ((p_type == PT_DYNAMIC) && (section->getType() != SHT_DYNAMIC)) + return false; + // Special case for PT_TLS. + if ((p_type == PT_TLS) && !(section->getFlags() & SHF_TLS)) return false; + return (addr >= p_vaddr) && (addr + size <= p_vaddr + p_memsz); + } +}; + +typedef serializable<Elf_Dyn_Traits> Elf_Dyn; + +struct Elf_DynValue { + unsigned int tag; + ElfValue* value; +}; + +class ElfDynamic_Section : public ElfSection { + public: + ElfDynamic_Section(Elf_Shdr& s, std::ifstream* file, Elf* parent); + ~ElfDynamic_Section(); + + void serialize(std::ofstream& file, unsigned char ei_class, + unsigned char ei_data); + + ElfValue* getValueForType(unsigned int tag); + ElfSection* getSectionForType(unsigned int tag); + bool setValueForType(unsigned int tag, ElfValue* val); + + private: + std::vector<Elf_DynValue> dyns; +}; + +typedef serializable<Elf_Sym_Traits> Elf_Sym; + +struct Elf_SymValue { + const char* name; + unsigned char info; + unsigned char other; + ElfLocation value; + unsigned int size; + bool defined; +}; + +#define STT(type) (1 << STT_##type) + +class ElfSymtab_Section : public ElfSection { + public: + ElfSymtab_Section(Elf_Shdr& s, std::ifstream* file, Elf* parent); + + void serialize(std::ofstream& file, unsigned char ei_class, + unsigned char ei_data); + + Elf_SymValue* lookup(const char* name, + unsigned int type_filter = STT(OBJECT) | STT(FUNC)); + + // private: // Until we have a real API + std::vector<Elf_SymValue> syms; +}; + +class Elf_Rel : public serializable<Elf_Rel_Traits> { + public: + Elf_Rel() : serializable<Elf_Rel_Traits>(){}; + + Elf_Rel(std::ifstream& file, unsigned char ei_class, unsigned char ei_data) + : serializable<Elf_Rel_Traits>(file, ei_class, ei_data){}; + + static const unsigned int sh_type = SHT_REL; + static const unsigned int d_tag = DT_REL; + static const unsigned int d_tag_count = DT_RELCOUNT; +}; + +class Elf_Rela : public serializable<Elf_Rela_Traits> { + public: + Elf_Rela() : serializable<Elf_Rela_Traits>(){}; + + Elf_Rela(std::ifstream& file, unsigned char ei_class, unsigned char ei_data) + : serializable<Elf_Rela_Traits>(file, ei_class, ei_data){}; + + static const unsigned int sh_type = SHT_RELA; + static const unsigned int d_tag = DT_RELA; + static const unsigned int d_tag_count = DT_RELACOUNT; +}; + +template <class Rel> +class ElfRel_Section : public ElfSection { + public: + ElfRel_Section(Elf_Shdr& s, std::ifstream* file, Elf* parent) + : ElfSection(s, file, parent) { + auto pos = file->tellg(); + file->seekg(shdr.sh_offset); + for (unsigned int i = 0; i < s.sh_size / s.sh_entsize; i++) { + Rel r(*file, parent->getClass(), parent->getData()); + rels.push_back(r); + } + file->seekg(pos); + } + + void serialize(std::ofstream& file, unsigned char ei_class, + unsigned char ei_data) { + for (typename std::vector<Rel>::iterator i = rels.begin(); i != rels.end(); + ++i) + (*i).serialize(file, ei_class, ei_data); + } + // private: // Until we have a real API + std::vector<Rel> rels; +}; + +class ElfStrtab_Section : public ElfSection { + public: + ElfStrtab_Section(Elf_Shdr& s, std::ifstream* file, Elf* parent) + : ElfSection(s, file, parent) { + table.push_back(table_storage(data, shdr.sh_size)); + } + + ~ElfStrtab_Section() { + for (std::vector<table_storage>::iterator t = table.begin() + 1; + t != table.end(); ++t) + delete[] t->buf; + } + + const char* getStr(unsigned int index); + + const char* getStr(const char* string); + + unsigned int getStrIndex(const char* string); + + void serialize(std::ofstream& file, unsigned char ei_class, + unsigned char ei_data); + + private: + struct table_storage { + unsigned int size, used; + char* buf; + + table_storage() : size(4096), used(0), buf(new char[4096]) {} + table_storage(const char* data, unsigned int sz) + : size(sz), used(sz), buf(const_cast<char*>(data)) {} + }; + std::vector<table_storage> table; +}; + +inline unsigned char Elf::getClass() { return ehdr->e_ident[EI_CLASS]; } + +inline unsigned char Elf::getData() { return ehdr->e_ident[EI_DATA]; } + +inline unsigned char Elf::getType() { return ehdr->e_type; } + +inline unsigned char Elf::getMachine() { return ehdr->e_machine; } + +inline unsigned int Elf::getSize() { + ElfSection* section; + for (section = shdr_section /* It's usually not far from the end */; + section->getNext() != nullptr; section = section->getNext()) + ; + return section->getOffset() + section->getSize(); +} + +inline ElfSegment* ElfSection::getSegmentByType(unsigned int type) { + for (std::vector<ElfSegment*>::iterator seg = segments.begin(); + seg != segments.end(); ++seg) + if ((*seg)->getType() == type) return *seg; + return nullptr; +} + +inline void ElfSection::insertInSegments(std::vector<ElfSegment*>& segs) { + for (std::vector<ElfSegment*>::iterator it = segs.begin(); it != segs.end(); + ++it) { + (*it)->addSection(this); + } +} + +inline ElfLocation::ElfLocation(ElfSection* section, unsigned int off, + enum position pos) + : section(section) { + if ((pos == ABSOLUTE) && section) + offset = off - section->getAddr(); + else + offset = off; +} + +inline ElfLocation::ElfLocation(unsigned int location, Elf* elf) { + section = elf->getSectionAt(location); + offset = location - (section ? section->getAddr() : 0); +} + +inline unsigned int ElfLocation::getValue() { + return (section ? section->getAddr() : 0) + offset; +} + +inline const char* ElfLocation::getBuffer() { + return section ? section->getData() + offset : nullptr; +} + +inline unsigned int ElfSize::getValue() { return section->getSize(); } + +inline unsigned int ElfEntSize::getValue() { return section->getEntSize(); } diff --git a/build/unix/elfhack/inject.c b/build/unix/elfhack/inject.c new file mode 100644 index 0000000000..f1a8e36e1c --- /dev/null +++ b/build/unix/elfhack/inject.c @@ -0,0 +1,225 @@ +/* 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 <unistd.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 +# define Elf_Phdr Elf64_Phdr +# define Elf_Addr Elf64_Addr +# define Elf_Word Elf64_Word +# define Elf_Dyn Elf64_Dyn +#else +# define Elf_Phdr Elf32_Phdr +# define Elf_Ehdr Elf32_Ehdr +# define Elf_Addr Elf32_Addr +# define Elf_Word Elf32_Word +# define Elf_Dyn Elf32_Dyn +#endif + +#ifdef RELRHACK +# include "relrhack.h" +# define mprotect_cb mprotect +# define sysconf_cb sysconf + +#else +// On ARM, PC-relative function calls have a limit in how far they can jump, +// which might not be enough for e.g. libxul.so. The easy way out would be +// to use the long_call attribute, which forces the compiler to generate code +// that can call anywhere, but clang doesn't support the attribute yet +// (https://bugs.llvm.org/show_bug.cgi?id=40623), and while the command-line +// equivalent does exist, it's currently broken +// (https://bugs.llvm.org/show_bug.cgi?id=40624). So we create a manual +// trampoline, corresponding to the code GCC generates with long_call. +# ifdef __arm__ +__attribute__((section(".text._init_trampoline"), naked)) int init_trampoline( + int argc, char** argv, char** env) { + __asm__ __volatile__( + // thumb doesn't allow to use r12/ip with ldr, and thus would require an + // additional push/pop to save/restore the modified register, which would + // also change the call into a blx. It's simpler to switch to arm. + ".arm\n" + " ldr ip, .LADDR\n" + ".LAFTER:\n" + " add ip, pc, ip\n" + " bx ip\n" + ".LADDR:\n" + " .word real_original_init-(.LAFTER+8)\n"); +} +# endif + +// On aarch64, a similar problem exists, but long_call is not an option at all +// (even GCC doesn't support them on aarch64). +# ifdef __aarch64__ +__attribute__((section(".text._init_trampoline"), naked)) int init_trampoline( + int argc, char** argv, char** env) { + __asm__ __volatile__( + " adrp x8, .LADDR\n" + " add x8, x8, :lo12:.LADDR\n" // adrp + add gives us the full address + // for .LADDR + " ldr x0, [x8]\n" // Load the address of real_original_init relative to + // .LADDR + " add x0, x8, x0\n" // Add the address of .LADDR + " br x0\n" // Branch to real_original_init + ".LADDR:\n" + " .xword real_original_init-.LADDR\n"); +} +# endif + +extern __attribute__((visibility("hidden"))) void original_init(int argc, + char** argv, + char** env); + +extern __attribute__((visibility("hidden"))) Elf_Addr relhack[]; +extern __attribute__((visibility("hidden"))) Elf_Addr relhack_end[]; + +extern __attribute__((visibility("hidden"))) int (*mprotect_cb)(void* addr, + size_t len, + int prot); +extern __attribute__((visibility("hidden"))) long (*sysconf_cb)(int name); +extern __attribute__((visibility("hidden"))) char relro_start[]; +extern __attribute__((visibility("hidden"))) char relro_end[]; +#endif + +extern __attribute__((visibility("hidden"))) Elf_Ehdr __ehdr_start; + +static inline __attribute__((always_inline)) void do_relocations( + Elf_Addr* relhack, Elf_Addr* relhack_end) { + Elf_Addr* ptr; + for (Elf_Addr* entry = relhack; entry < relhack_end; entry++) { + if ((*entry & 1) == 0) { + ptr = (Elf_Addr*)((intptr_t)&__ehdr_start + *entry); + *ptr += (intptr_t)&__ehdr_start; + } else { + Elf_Addr bits = *entry; + Elf_Addr* end = ptr + 8 * sizeof(Elf_Addr) - 1; + do { + ptr++; + bits >>= 1; + if (bits & 1) { + *ptr += (intptr_t)&__ehdr_start; + } + } while (ptr < end); + } + } +} + +#ifndef RELRHACK +__attribute__((section(".text._init_noinit"))) int init_noinit(int argc, + char** argv, + char** env) { + do_relocations(relhack, relhack_end); + return 0; +} + +__attribute__((section(".text._init"))) int init(int argc, char** argv, + char** env) { + do_relocations(relhack, relhack_end); + 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; +} +#endif + +static inline __attribute__((always_inline)) void do_relocations_with_relro( + Elf_Addr* relhack, Elf_Addr* relhack_end, char* relro_start, + char* relro_end) { + long page_size = sysconf_cb(_SC_PAGESIZE); + uintptr_t aligned_relro_start = ((uintptr_t)relro_start) & ~(page_size - 1); + // The relro segment may not end at a page boundary. If that's the case, the + // remainder of the page needs to stay read-write, so the last page is never + // set read-only. Thus the aligned relro end is page-rounded down. + uintptr_t aligned_relro_end = ((uintptr_t)relro_end) & ~(page_size - 1); + // 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((void*)aligned_relro_start, + aligned_relro_end - aligned_relro_start, PROT_READ | PROT_WRITE); + + do_relocations(relhack, relhack_end); + + mprotect_cb((void*)aligned_relro_start, + aligned_relro_end - aligned_relro_start, PROT_READ); +#ifndef RELRHACK + // mprotect_cb and sysconf_cb are allocated in .bss, so we need to restore + // them to a NULL value. + mprotect_cb = NULL; + sysconf_cb = NULL; +#endif +} + +#ifndef RELRHACK +__attribute__((section(".text._init_noinit_relro"))) int init_noinit_relro( + int argc, char** argv, char** env) { + do_relocations_with_relro(relhack, relhack_end, relro_start, relro_end); + return 0; +} + +__attribute__((section(".text._init_relro"))) int init_relro(int argc, + char** argv, + char** env) { + do_relocations_with_relro(relhack, relhack_end, relro_start, relro_end); + original_init(argc, argv, env); + return 0; +} +#else + +extern __attribute__((visibility("hidden"))) Elf_Dyn _DYNAMIC[]; + +static void _relrhack_init(void) { + // Get the location of the SHT_RELR data from the PT_DYNAMIC segment. + uintptr_t elf_header = (uintptr_t)&__ehdr_start; + Elf_Addr* relhack = NULL; + Elf_Word size = 0; + for (Elf_Dyn* dyn = _DYNAMIC; dyn->d_tag != DT_NULL; dyn++) { + if ((dyn->d_tag & ~DT_RELRHACK_BIT) == DT_RELR) { + relhack = (Elf_Addr*)(elf_header + dyn->d_un.d_ptr); + } else if ((dyn->d_tag & ~DT_RELRHACK_BIT) == DT_RELRSZ) { + size = dyn->d_un.d_val; + } + } + + Elf_Addr* relhack_end = (Elf_Addr*)((uintptr_t)relhack + size); + + // Find the location of the PT_GNU_RELRO segment in the program headers. + Elf_Phdr* phdr = (Elf_Phdr*)(elf_header + __ehdr_start.e_phoff); + char* relro_start = NULL; + char* relro_end = NULL; + for (int i = 0; i < __ehdr_start.e_phnum; i++) { + if (phdr[i].p_type == PT_GNU_RELRO) { + relro_start = (char*)(elf_header + phdr[i].p_vaddr); + relro_end = (char*)(relro_start + phdr[i].p_memsz); + break; + } + } + + if (relro_start != relro_end) { + do_relocations_with_relro(relhack, relhack_end, relro_start, relro_end); + } else { + do_relocations(relhack, relhack_end); + } +} + +// The Android CRT doesn't contain an init function. +# ifndef ANDROID +extern __attribute__((visibility("hidden"))) void _init(int argc, char** argv, + char** env); +# endif + +void _relrhack_wrap_init(int argc, char** argv, char** env) { + _relrhack_init(); +# ifndef ANDROID + _init(argc, argv, env); +# endif +} +#endif diff --git a/build/unix/elfhack/inject/copy_source.py b/build/unix/elfhack/inject/copy_source.py new file mode 100644 index 0000000000..02b4f6237e --- /dev/null +++ b/build/unix/elfhack/inject/copy_source.py @@ -0,0 +1,10 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + + +def copy(out_file, in_path): + with open(in_path, "r") as fh: + out_file.write(fh.read()) diff --git a/build/unix/elfhack/inject/moz.build b/build/unix/elfhack/inject/moz.build new file mode 100644 index 0000000000..92f48e6c0a --- /dev/null +++ b/build/unix/elfhack/inject/moz.build @@ -0,0 +1,56 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +# dummy library name to avoid skipping building the source here, which +# we only need the object for. +Library("elfhack_inject") + +DIST_INSTALL = False + +stem = CONFIG["TARGET_CPU"] +if CONFIG["RELRHACK"] and CONFIG["OS_TARGET"] == "Android": + stem += "-android" + +gen_src = "%s.c" % stem +GeneratedFile( + gen_src, script="copy_source.py", entry_point="copy", inputs=["../inject.c"] +) + +SOURCES += [ + "!%s" % gen_src, +] + +if CONFIG["RELRHACK"]: + DEFINES["RELRHACK"] = True + LOCAL_INCLUDES += [".."] + +NO_PGO = True + +for v in ("OS_CPPFLAGS", "OS_CFLAGS", "DEBUG", "CLANG_PLUGIN", "OPTIMIZE", "FRAMEPTR"): + flags = [] + idx = 0 + for flag in COMPILE_FLAGS[v]: + if flag == "-isystem": + flags.append("".join(COMPILE_FLAGS[v][idx : idx + 2])) + elif ( + flag.startswith(("-g", "-m", "-I", "-isystem", "--sysroot=")) + or flag == "-fPIC" + ): + flags.append(flag) + idx += 1 + COMPILE_FLAGS[v] = flags + +COMPILE_FLAGS["OS_CFLAGS"] += [ + "-O2", + "-fno-stack-protector", + "-fno-lto", + # The injected code runs early enough that it supporting unwinding is useless. + # Moreover, elfhack doesn't inject the eh_frame section anyways. + "-fno-asynchronous-unwind-tables", +] + +AllowCompilerWarnings() +NoVisibilityFlags() diff --git a/build/unix/elfhack/moz.build b/build/unix/elfhack/moz.build new file mode 100644 index 0000000000..6f8c81af25 --- /dev/null +++ b/build/unix/elfhack/moz.build @@ -0,0 +1,46 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DIST_INSTALL = False +DIRS += ["inject"] + +if CONFIG["RELRHACK"]: + HOST_SOURCES += [ + "relrhack.cpp", + ] + + HostProgram(CONFIG["RELRHACK_LINKER"]) + + HOST_OS_LIBS += CONFIG["RELRHACK_LIBS"] + + if CONFIG["MOZ_STDCXX_COMPAT"]: + HOST_DEFINES["MOZ_STDCXX_COMPAT"] = True +else: + HOST_SOURCES += [ + "elf.cpp", + "elfhack.cpp", + ] + + HostProgram("elfhack") + + if not CONFIG["CROSS_COMPILE"]: + SOURCES += [ + "dummy.c", + ] + SOURCES["dummy.c"].flags += ["-fno-lto"] + + SOURCES += [ + "test-array.c", + "test-ctors.c", + ] + SOURCES["test-array.c"].flags += ["-fno-lto"] + SOURCES["test-ctors.c"].flags += ["-fno-lto"] + +NO_PGO = True + +COMPILE_FLAGS["OS_CXXFLAGS"] = [ + f for f in COMPILE_FLAGS["OS_CXXFLAGS"] if f != "-fno-exceptions" +] + ["-fexceptions"] diff --git a/build/unix/elfhack/relrhack.cpp b/build/unix/elfhack/relrhack.cpp new file mode 100644 index 0000000000..2d78d783c9 --- /dev/null +++ b/build/unix/elfhack/relrhack.cpp @@ -0,0 +1,567 @@ +/* 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/. */ + +// This program acts as a linker wrapper. Its executable name is meant +// to be that of a linker, and it will find the next linker with the same +// name in $PATH. However, if for some reason the next linker cannot be +// found this way, the caller may pass its path via the --real-linker +// option. +// +// More in-depth background on https://glandium.org/blog/?p=4297 + +#include "relrhack.h" +#include <algorithm> +#include <cstring> +#include <filesystem> +#include <fstream> +#include <iostream> +#include <optional> +#include <spawn.h> +#include <sstream> +#include <stdexcept> +#include <sys/wait.h> +#include <unistd.h> +#include <unordered_map> +#include <utility> +#include <vector> + +namespace fs = std::filesystem; + +class CantSwapSections : public std::runtime_error { + public: + CantSwapSections(const char* what) : std::runtime_error(what) {} +}; + +template <int bits> +struct Elf {}; + +#define ELF(bits) \ + template <> \ + struct Elf<bits> { \ + using Ehdr = Elf##bits##_Ehdr; \ + using Phdr = Elf##bits##_Phdr; \ + using Shdr = Elf##bits##_Shdr; \ + using Dyn = Elf##bits##_Dyn; \ + using Addr = Elf##bits##_Addr; \ + using Word = Elf##bits##_Word; \ + using Off = Elf##bits##_Off; \ + using Verneed = Elf##bits##_Verneed; \ + using Vernaux = Elf##bits##_Vernaux; \ + } + +ELF(32); +ELF(64); + +template <int bits> +struct RelR : public Elf<bits> { + using Elf_Ehdr = typename Elf<bits>::Ehdr; + using Elf_Phdr = typename Elf<bits>::Phdr; + using Elf_Shdr = typename Elf<bits>::Shdr; + using Elf_Dyn = typename Elf<bits>::Dyn; + using Elf_Addr = typename Elf<bits>::Addr; + using Elf_Word = typename Elf<bits>::Word; + using Elf_Off = typename Elf<bits>::Off; + using Elf_Verneed = typename Elf<bits>::Verneed; + using Elf_Vernaux = typename Elf<bits>::Vernaux; + +#define TAG_NAME(t) \ + { t, #t } + class DynInfo { + public: + using Tag = decltype(Elf_Dyn::d_tag); + using Value = decltype(Elf_Dyn::d_un.d_val); + bool is_wanted(Tag tag) const { return tag_names.count(tag); } + void insert(off_t offset, Tag tag, Value val) { + data[tag] = std::make_pair(offset, val); + } + off_t offset(Tag tag) const { return data.at(tag).first; } + bool contains(Tag tag) const { return data.count(tag); } + Value& operator[](Tag tag) { + if (!is_wanted(tag)) { + std::stringstream msg; + msg << "Tag 0x" << std::hex << tag << " is not in DynInfo::tag_names"; + throw std::runtime_error(msg.str()); + } + return data[tag].second; + } + const char* name(Tag tag) const { return tag_names.at(tag); } + + private: + std::unordered_map<Tag, std::pair<off_t, Value>> data; + + const std::unordered_map<Tag, const char*> tag_names = { + TAG_NAME(DT_JMPREL), TAG_NAME(DT_PLTRELSZ), TAG_NAME(DT_RELR), + TAG_NAME(DT_RELRENT), TAG_NAME(DT_RELRSZ), TAG_NAME(DT_RELA), + TAG_NAME(DT_RELASZ), TAG_NAME(DT_RELAENT), TAG_NAME(DT_REL), + TAG_NAME(DT_RELSZ), TAG_NAME(DT_RELENT), TAG_NAME(DT_STRTAB), + TAG_NAME(DT_STRSZ), TAG_NAME(DT_VERNEED), TAG_NAME(DT_VERNEEDNUM), + }; + }; + + // Translate a virtual address into an offset in the file based on the program + // headers' PT_LOAD. + static Elf_Addr get_offset(const std::vector<Elf_Phdr>& phdr, Elf_Addr addr) { + for (const auto& p : phdr) { + if (p.p_type == PT_LOAD && addr >= p.p_vaddr && + addr < p.p_vaddr + p.p_filesz) { + return addr - (p.p_vaddr - p.p_paddr); + } + } + return 0; + } + + static bool hack(std::fstream& f); +}; + +template <typename T> +T read_one_at(std::istream& in, off_t pos) { + T result; + in.seekg(pos, std::ios::beg); + in.read(reinterpret_cast<char*>(&result), sizeof(T)); + return result; +} + +template <typename T> +std::vector<T> read_vector_at(std::istream& in, off_t pos, size_t num) { + std::vector<T> result(num); + in.seekg(pos, std::ios::beg); + in.read(reinterpret_cast<char*>(result.data()), num * sizeof(T)); + return result; +} + +void write_at(std::ostream& out, off_t pos, const char* buf, size_t len) { + out.seekp(pos, std::ios::beg); + out.write(buf, len); +} + +template <typename T> +void write_one_at(std::ostream& out, off_t pos, const T& data) { + write_at(out, pos, reinterpret_cast<const char*>(&data), sizeof(T)); +} + +template <typename T> +void write_vector_at(std::ostream& out, off_t pos, const std::vector<T>& vec) { + write_at(out, pos, reinterpret_cast<const char*>(&vec.front()), + vec.size() * sizeof(T)); +} + +template <int bits> +bool RelR<bits>::hack(std::fstream& f) { + auto ehdr = read_one_at<Elf_Ehdr>(f, 0); + if (ehdr.e_phentsize != sizeof(Elf_Phdr)) { + throw std::runtime_error("Invalid ELF?"); + } + auto phdr = read_vector_at<Elf_Phdr>(f, ehdr.e_phoff, ehdr.e_phnum); + const auto& dyn_phdr = + std::find_if(phdr.begin(), phdr.end(), + [](const auto& p) { return p.p_type == PT_DYNAMIC; }); + if (dyn_phdr == phdr.end()) { + return false; + } + if (dyn_phdr->p_filesz % sizeof(Elf_Dyn)) { + throw std::runtime_error("Invalid ELF?"); + } + auto dyn = read_vector_at<Elf_Dyn>(f, dyn_phdr->p_offset, + dyn_phdr->p_filesz / sizeof(Elf_Dyn)); + off_t dyn_offset = dyn_phdr->p_offset; + DynInfo dyn_info; + for (const auto& d : dyn) { + if (d.d_tag == DT_NULL) { + break; + } + + if (dyn_info.is_wanted(d.d_tag)) { + if (dyn_info.contains(d.d_tag)) { + std::stringstream msg; + msg << dyn_info.name(d.d_tag) << " appears twice?"; + throw std::runtime_error(msg.str()); + } + dyn_info.insert(dyn_offset, d.d_tag, d.d_un.d_val); + } + dyn_offset += sizeof(Elf_Dyn); + } + + // Find the location and size of the SHT_RELR section, which contains the + // packed-relative-relocs. + Elf_Addr relr_off = + dyn_info.contains(DT_RELR) ? get_offset(phdr, dyn_info[DT_RELR]) : 0; + Elf_Off relrsz = dyn_info[DT_RELRSZ]; + const decltype(Elf_Dyn::d_tag) rel_tags[3][2] = { + {DT_REL, DT_RELA}, {DT_RELSZ, DT_RELASZ}, {DT_RELENT, DT_RELAENT}}; + for (const auto& [rel_tag, rela_tag] : rel_tags) { + if (dyn_info.contains(rel_tag) && dyn_info.contains(rela_tag)) { + std::stringstream msg; + msg << "Both " << dyn_info.name(rel_tag) << " and " + << dyn_info.name(rela_tag) << " appear?"; + throw std::runtime_error(msg.str()); + } + } + Elf_Off relent = + dyn_info.contains(DT_RELENT) ? dyn_info[DT_RELENT] : dyn_info[DT_RELAENT]; + + // Estimate the size of the unpacked relative relocations corresponding + // to the SHT_RELR section. + auto relr = read_vector_at<Elf_Addr>(f, relr_off, relrsz / sizeof(Elf_Addr)); + size_t relocs = 0; + for (const auto& entry : relr) { + if ((entry & 1) == 0) { + // LSB is 0, this is a pointer for a single relocation. + relocs++; + } else { + // LSB is 1, remaining bits are a bitmap. Each bit represents a + // relocation. + relocs += __builtin_popcount(entry) - 1; + } + } + // If the packed relocations + some overhead (we pick 4K arbitrarily, the + // real size would require digging into the section sizes of the injected + // .o file, which is not worth the error) is larger than the estimated + // unpacked relocations, we'll just relink without packed relocations. + if (relocs * relent < relrsz + 4096) { + return false; + } + + // Change DT_RELR* tags to add DT_RELRHACK_BIT. + for (const auto tag : {DT_RELR, DT_RELRSZ, DT_RELRENT}) { + write_one_at(f, dyn_info.offset(tag), tag | DT_RELRHACK_BIT); + } + + bool is_glibc = false; + + if (dyn_info.contains(DT_VERNEEDNUM) && dyn_info.contains(DT_VERNEED) && + dyn_info.contains(DT_STRSZ) && dyn_info.contains(DT_STRTAB)) { + // Scan SHT_VERNEED for the GLIBC_ABI_DT_RELR version on the libc + // library. + Elf_Addr verneed_off = get_offset(phdr, dyn_info[DT_VERNEED]); + Elf_Off verneednum = dyn_info[DT_VERNEEDNUM]; + // SHT_STRTAB section, which contains the string table for, among other + // things, the symbol versions in the SHT_VERNEED section. + auto strtab = read_vector_at<char>(f, get_offset(phdr, dyn_info[DT_STRTAB]), + dyn_info[DT_STRSZ]); + // Guarantee a nul character at the end of the string table. + strtab.push_back(0); + while (verneednum--) { + auto verneed = read_one_at<Elf_Verneed>(f, verneed_off); + if (std::string_view{"libc.so.6"} == &strtab.at(verneed.vn_file)) { + is_glibc = true; + Elf_Addr vernaux_off = verneed_off + verneed.vn_aux; + Elf_Addr relr = 0; + Elf_Vernaux reuse; + for (auto n = 0; n < verneed.vn_cnt; n++) { + auto vernaux = read_one_at<Elf_Vernaux>(f, vernaux_off); + if (std::string_view{"GLIBC_ABI_DT_RELR"} == + &strtab.at(vernaux.vna_name)) { + relr = vernaux_off; + } else { + reuse = vernaux; + } + vernaux_off += vernaux.vna_next; + } + // In the case where we do have the GLIBC_ABI_DT_RELR version, we + // need to edit the binary to make the following changes: + // - Remove the GLIBC_ABI_DT_RELR version, we replace it with an + // arbitrary other version entry, which is simpler than completely + // removing it. We need to remove it because older versions of glibc + // don't have the version (after all, that's why the symbol version + // is there in the first place, to avoid running against older versions + // of glibc that don't support packed relocations). + // - Alter the DT_RELR* tags in the dynamic section, so that they + // are not recognized by ld.so, because, while all versions of ld.so + // ignore tags they don't know, glibc's ld.so versions that support + // packed relocations don't want to load a binary that has DT_RELR* + // tags but *not* a dependency on the GLIBC_ABI_DT_RELR version. + if (relr) { + // Don't overwrite vn_aux. + write_at(f, relr, reinterpret_cast<char*>(&reuse), + sizeof(reuse) - sizeof(Elf_Word)); + } + } + verneed_off += verneed.vn_next; + } + } + + // Location of the .rel.plt section. + Elf_Addr jmprel = dyn_info.contains(DT_JMPREL) ? dyn_info[DT_JMPREL] : 0; + if (is_glibc) { +#ifndef MOZ_STDCXX_COMPAT + try { +#endif + // ld.so in glibc 2.16 to 2.23 expects .rel.plt to strictly follow + // .rel.dyn. (https://sourceware.org/bugzilla/show_bug.cgi?id=14341) + // BFD ld places .relr.dyn after .rel.plt, so this works fine, but lld + // places it between both sections, which doesn't work out for us. In that + // case, we want to swap .relr.dyn and .rel.plt. + Elf_Addr rel_end = dyn_info.contains(DT_REL) + ? (dyn_info[DT_REL] + dyn_info[DT_RELSZ]) + : (dyn_info[DT_RELA] + dyn_info[DT_RELASZ]); + if (dyn_info.contains(DT_JMPREL) && dyn_info[DT_PLTRELSZ] && + dyn_info[DT_JMPREL] != rel_end) { + if (dyn_info[DT_RELR] != rel_end) { + throw CantSwapSections("RELR section doesn't follow REL/RELA?"); + } + if (dyn_info[DT_JMPREL] != dyn_info[DT_RELR] + dyn_info[DT_RELRSZ]) { + throw CantSwapSections("PLT REL/RELA doesn't follow RELR?"); + } + auto plt_rel = read_vector_at<char>( + f, get_offset(phdr, dyn_info[DT_JMPREL]), dyn_info[DT_PLTRELSZ]); + // Write the content of both sections swapped, and adjust the + // corresponding PT_DYNAMIC entries. + write_vector_at(f, relr_off, plt_rel); + write_vector_at(f, relr_off + plt_rel.size(), relr); + dyn_info[DT_JMPREL] = rel_end; + dyn_info[DT_RELR] = rel_end + plt_rel.size(); + for (const auto tag : {DT_JMPREL, DT_RELR}) { + write_one_at(f, dyn_info.offset(tag) + sizeof(typename DynInfo::Tag), + dyn_info[tag]); + } + } +#ifndef MOZ_STDCXX_COMPAT + } catch (const CantSwapSections& err) { + // When binary compatibility with older libstdc++/glibc is not enabled, we + // only emit a warning about why swapping the sections is not happening. + std::cerr << "WARNING: " << err.what() << std::endl; + } +#endif + } + + off_t shdr_offset = ehdr.e_shoff; + auto shdr = read_vector_at<Elf_Shdr>(f, ehdr.e_shoff, ehdr.e_shnum); + for (auto& s : shdr) { + // Some tools don't like sections of types they don't know, so change + // SHT_RELR, which might be unknown on older systems, to SHT_PROGBITS. + if (s.sh_type == SHT_RELR) { + s.sh_type = SHT_PROGBITS; + // If DT_RELR has been adjusted to swap with DT_JMPREL, also adjust + // the corresponding SHT_RELR section header. + if (s.sh_addr != dyn_info[DT_RELR]) { + s.sh_offset += dyn_info[DT_RELR] - s.sh_addr; + s.sh_addr = dyn_info[DT_RELR]; + } + write_one_at(f, shdr_offset, s); + } else if (jmprel && (s.sh_addr == jmprel) && + (s.sh_addr != dyn_info[DT_JMPREL])) { + // If DT_JMPREL has been adjusted to swap with DT_RELR, also adjust + // the corresponding section header. + s.sh_offset -= s.sh_addr - dyn_info[DT_JMPREL]; + s.sh_addr = dyn_info[DT_JMPREL]; + write_one_at(f, shdr_offset, s); + } + shdr_offset += sizeof(Elf_Shdr); + } + return true; +} + +std::vector<std::string> get_path() { + std::vector<std::string> result; + std::stringstream stream{std::getenv("PATH")}; + std::string item; + + while (std::getline(stream, item, ':')) { + result.push_back(std::move(item)); + } + + return result; +} + +std::optional<fs::path> next_program(fs::path& this_program, + std::optional<fs::path>& program) { + auto program_name = program ? *program : this_program.filename(); + for (const auto& dir : get_path()) { + auto path = fs::path(dir) / program_name; + auto status = fs::status(path); + if ((status.type() == fs::file_type::regular) && + ((status.permissions() & fs::perms::owner_exec) == + fs::perms::owner_exec) && + !fs::equivalent(path, this_program)) + return path; + } + return std::nullopt; +} + +unsigned char get_elf_class(unsigned char (&e_ident)[EI_NIDENT]) { + if (std::string_view{reinterpret_cast<char*>(e_ident), SELFMAG} != + std::string_view{ELFMAG, SELFMAG}) { + throw std::runtime_error("Not ELF?"); + } +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + if (e_ident[EI_DATA] != ELFDATA2LSB) { + throw std::runtime_error("Not Little Endian ELF?"); + } +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + if (e_ident[EI_DATA] != ELFDATA2MSB) { + throw std::runtime_error("Not Big Endian ELF?"); + } +#else +# error Unknown byte order. +#endif + if (e_ident[EI_VERSION] != 1) { + throw std::runtime_error("Not ELF version 1?"); + } + auto elf_class = e_ident[EI_CLASS]; + if (elf_class != ELFCLASS32 && elf_class != ELFCLASS64) { + throw std::runtime_error("Not 32 or 64-bits ELF?"); + } + return elf_class; +} + +unsigned char get_elf_class(std::istream& in) { + unsigned char e_ident[EI_NIDENT]; + in.read(reinterpret_cast<char*>(e_ident), sizeof(e_ident)); + return get_elf_class(e_ident); +} + +uint16_t get_elf_machine(std::istream& in) { + // As far as e_machine is concerned, both Elf32_Ehdr and Elf64_Ehdr are equal. + Elf32_Ehdr ehdr; + in.read(reinterpret_cast<char*>(&ehdr), sizeof(ehdr)); + // get_elf_class will throw exceptions for the cases we don't handle. + get_elf_class(ehdr.e_ident); + return ehdr.e_machine; +} + +int run_command(std::vector<const char*>& args) { + pid_t child_pid; + if (posix_spawn(&child_pid, args[0], nullptr, nullptr, + const_cast<char* const*>(args.data()), environ) != 0) { + throw std::runtime_error("posix_spawn failed"); + } + + int status; + waitpid(child_pid, &status, 0); + return WEXITSTATUS(status); +} + +int main(int argc, char* argv[]) { + auto this_program = fs::absolute(argv[0]); + + std::vector<const char*> args; + + int i, crti = 0; + std::optional<fs::path> output = std::nullopt; + std::optional<fs::path> real_linker = std::nullopt; + bool shared = false; + bool is_android = false; + uint16_t elf_machine = EM_NONE; + // Scan argv in order to prepare the following: + // - get the output file. That's the file we may need to adjust. + // - get the --real-linker if one was passed. + // - detect whether we're linking a shared library or something else. As of + // now, only shared libraries are handled. Technically speaking, programs + // could be handled as well, but for the purpose of Firefox, that actually + // doesn't work because programs contain a memory allocator that ends up + // being called before the injected code has any chance to apply relocations, + // and the allocator itself needs the relocations to have been applied. + // - detect the position of crti.o so that we can inject our own object + // right after it, and also to detect the machine type to pick the right + // object to inject. + // + // At the same time, we also construct a new list of arguments, with + // --real-linker filtered out. We'll later inject arguments in that list. + for (i = 1, argv++; i < argc && *argv; argv++, i++) { + std::string_view arg{*argv}; + if (arg == "-shared") { + shared = true; + } else if (arg == "-o") { + args.push_back(*(argv++)); + ++i; + output = *argv; + } else if (arg == "--real-linker") { + ++i; + real_linker = *(++argv); + continue; + } else if (elf_machine == EM_NONE) { + auto filename = fs::path(arg).filename(); + if (filename == "crti.o" || filename == "crtbegin_so.o") { + is_android = (filename == "crtbegin_so.o"); + crti = i; + std::fstream f{std::string(arg), f.binary | f.in}; + f.exceptions(f.failbit); + elf_machine = get_elf_machine(f); + } + } + args.push_back(*argv); + } + + if (!output) { + std::cerr << "Could not determine output file." << std::endl; + return 1; + } + + if (!crti) { + std::cerr << "Could not find CRT object on the command line." << std::endl; + return 1; + } + + if (!real_linker || !real_linker->has_parent_path()) { + auto linker = next_program(this_program, real_linker); + if (!linker) { + std::cerr << "Could not find next " + << (real_linker ? real_linker->filename() + : this_program.filename()) + << std::endl; + return 1; + } + real_linker = linker; + } + args.insert(args.begin(), real_linker->c_str()); + args.push_back(nullptr); + + std::string stem; + switch (elf_machine) { + case EM_NONE: + std::cerr << "Could not determine target machine type." << std::endl; + return 1; + case EM_386: + stem = "x86"; + break; + case EM_X86_64: + stem = "x86_64"; + break; + case EM_ARM: + stem = "arm"; + break; + case EM_AARCH64: + stem = "aarch64"; + break; + default: + std::cerr << "Unsupported target machine type." << std::endl; + return 1; + } + if (is_android) { + stem += "-android"; + } + + if (shared) { + std::vector<const char*> hacked_args(args); + auto inject = this_program.parent_path() / "inject" / (stem + ".o"); + hacked_args.insert(hacked_args.begin() + crti + 1, inject.c_str()); + hacked_args.insert(hacked_args.end() - 1, {"-z", "pack-relative-relocs", + "-init=_relrhack_wrap_init"}); + int status = run_command(hacked_args); + if (status) { + return status; + } + bool hacked = false; + try { + std::fstream f{*output, f.binary | f.in | f.out}; + f.exceptions(f.failbit); + auto elf_class = get_elf_class(f); + f.seekg(0, std::ios::beg); + if (elf_class == ELFCLASS32) { + hacked = RelR<32>::hack(f); + } else if (elf_class == ELFCLASS64) { + hacked = RelR<64>::hack(f); + } + } catch (const std::runtime_error& err) { + std::cerr << "Failed to hack " << output->string() << ": " << err.what() + << std::endl; + return 1; + } + if (hacked) { + return 0; + } + } + + return run_command(args); +} diff --git a/build/unix/elfhack/relrhack.h b/build/unix/elfhack/relrhack.h new file mode 100644 index 0000000000..3501f21079 --- /dev/null +++ b/build/unix/elfhack/relrhack.h @@ -0,0 +1,25 @@ +/* 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/. */ + +#ifndef __RELRHACK_H__ +#define __RELRHACK_H__ + +#include <elf.h> + +#define DT_RELRHACK_BIT 0x8000000 + +#ifndef DT_RELRSZ +# define DT_RELRSZ 35 +#endif +#ifndef DT_RELR +# define DT_RELR 36 +#endif +#ifndef DT_RELRENT +# define DT_RELRENT 37 +#endif +#ifndef SHR_RELR +# define SHT_RELR 19 +#endif + +#endif /* __RELRHACK_H__ */ diff --git a/build/unix/elfhack/test-array.c b/build/unix/elfhack/test-array.c new file mode 100644 index 0000000000..21aec3d360 --- /dev/null +++ b/build/unix/elfhack/test-array.c @@ -0,0 +1,8 @@ +/* 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 "test.c" + +__attribute__((section(".init_array"), used)) static void (*init_array[])() = { + end_test, test}; diff --git a/build/unix/elfhack/test-ctors.c b/build/unix/elfhack/test-ctors.c new file mode 100644 index 0000000000..d082411e3d --- /dev/null +++ b/build/unix/elfhack/test-ctors.c @@ -0,0 +1,16 @@ +/* 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 "test.c" + +/* Recent binutils would put .ctors content into a .init_array section */ +__attribute__((section(".manual_ctors"), used)) static void (*ctors[])() = { + (void (*)()) - 1, end_test, test, NULL}; + +__attribute__((section(".init"))) void _init() { + void (**func)() = &ctors[sizeof(ctors) / sizeof(void (*)()) - 1]; + while (*(--func) != (void (*)()) - 1) { + (*func)(); + } +} diff --git a/build/unix/elfhack/test.c b/build/unix/elfhack/test.c new file mode 100644 index 0000000000..f690ad8f6a --- /dev/null +++ b/build/unix/elfhack/test.c @@ -0,0 +1,221 @@ +/* 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/. */ + +#ifdef DEF +DEF(This) +DEF(is) +DEF(a) +DEF(test) +DEF(of) +DEF(string) +DEF(array) +DEF(for) +DEF(use) +DEF(with) +DEF(elfhack) +DEF(to) +DEF(see) +DEF(whether) +DEF(it) +DEF(breaks) +DEF(anything) +DEF(but) +DEF(one) +DEF(needs) +DEF(quite) +DEF(some) +DEF(strings) +DEF(before) +DEF(the) +DEF(program) +DEF(can) +DEF(do) +DEF(its) +DEF(work) +DEF(efficiently) +DEF(Without) +DEF(enough) +DEF(data) +DEF(relocation) +DEF(sections) +DEF(are) +// clang-format off +DEF(not) +// clang-format on +DEF(sufficiently) +DEF(large) +DEF(and) +DEF(injected) +DEF(code) +DEF(wouldnt) +DEF(fit) +DEF(Said) +DEF(otherwise) +DEF(we) +DEF(need) +DEF(more) +DEF(words) +DEF(than) +DEF(up) +DEF(here) +DEF(so) +DEF(that) +DEF(relocations) +DEF(take) +DEF(significant) +DEF(bytes) +DEF(amounts) +DEF(which) +DEF(isnt) +DEF(exactly) +DEF(easily) +DEF(achieved) +DEF(like) +DEF(this) +DEF(Actually) +DEF(I) +DEF(must) +DEF(cheat) +DEF(by) +DEF(including) +DEF(these) +DEF(phrases) +DEF(several) +DEF(times) + +#else +# pragma GCC visibility push(default) +# include <stdlib.h> +# include <stdio.h> + +# define DEF(w) static const char str_##w[] = #w; +# include "test.c" +# undef DEF + +const char* strings[] = { +# define DEF(w) str_##w, +# include "test.c" +# include "test.c" +# include "test.c" +}; + +/* Create a hole between two zones of relative relocations */ +int small_hole[] = {42, 42, 42, 42}; + +const char* strings2[] = { +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +}; + +/* Create a bigger hole between two zones of relative relocations */ +int bigger_hole[] = { + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, +}; + +const char* strings3[] = { +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# undef DEF +}; + +static int ret = 1; + +int print_status() { + fprintf(stderr, "%s\n", ret ? "FAIL" : "PASS"); + return ret; +} + +/* On ARM, this creates a .tbss section before .init_array, which + * elfhack could then pick instead of .init_array. + * Also, when .tbss is big enough, elfhack may wrongfully consider + * following sections as part of the PT_TLS segment. + * Finally, gold makes TLS segments end on an aligned virtual address, + * even when the underlying section ends before that, and elfhack + * sanity checks may yield an error. */ +__thread int foo; +__thread long long int bar[512]; + +/* We need a .bss that can hold at least 2 pointers. The static in + * end_test() plus this variable should do. */ +size_t dummy; + +void end_test() { + static size_t count = 0; + /* Only exit when both constructors have been called */ + if (++count == 2) { + ret = 0; + // Avoid the dummy variable being stripped out at link time because + // it's unused. + dummy = 1; + } +} + +void test() { + int i = 0, j = 0, k = 0; +# define DEF_(a, i, w) \ + if (a[i++] != str_##w) return; +# define DEF(w) DEF_(strings, i, w) +# include "test.c" +# include "test.c" +# include "test.c" +# undef DEF +# define DEF(w) DEF_(strings2, j, w) +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# undef DEF +# define DEF(w) DEF_(strings3, k, w) +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# include "test.c" +# undef DEF + if (i != sizeof(strings) / sizeof(strings[0]) && + j != sizeof(strings2) / sizeof(strings2[0]) && + k != sizeof(strings3) / sizeof(strings3[0])) + fprintf(stderr, "WARNING: Test doesn't cover the whole array\n"); + end_test(); +} + +# pragma GCC visibility pop +#endif |