|
@@ -0,0 +1,833 @@
|
|
|
+# HG changeset patch
|
|
|
+# User Mike Hommey <mh+mozilla@glandium.org>
|
|
|
+# Date 1694893945 0
|
|
|
+# Sat Sep 16 19:52:25 2023 +0000
|
|
|
+# Node ID 200dd4ded060b9f7eef115e74e739c7f3596b00b
|
|
|
+# Parent f20d83892c23b01759eb83dcf72efe6d8c393762
|
|
|
+Bug 1839740 - New relrhack tool, a modern replacement to elfhack. r=firefox-build-system-reviewers,sergesanspaille
|
|
|
+
|
|
|
+Elfhack is the main reason we're not using lld on Linux/Android
|
|
|
+shippable builds, because the way it works doesn't go well with how lld
|
|
|
+lays out ELF binaries. By leveraging the linker itself (BFD and lld both
|
|
|
+having recently gained the ability to generate the compact relocation
|
|
|
+info themselves), we can achieve a similar result to what elfhack is
|
|
|
+doing, while allowing to use lld.
|
|
|
+
|
|
|
+See more in-depth background on https://glandium.org/blog/?p=4297
|
|
|
+
|
|
|
+Differential Revision: https://phabricator.services.mozilla.com/D187089
|
|
|
+
|
|
|
+diff --git a/build/unix/elfhack/Makefile.in b/build/unix/elfhack/Makefile.in
|
|
|
+--- a/build/unix/elfhack/Makefile.in
|
|
|
++++ b/build/unix/elfhack/Makefile.in
|
|
|
+@@ -1,15 +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 $(topsrcdir)/config/rules.mk
|
|
|
+
|
|
|
++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 ===
|
|
|
+diff --git a/build/unix/elfhack/inject.c b/build/unix/elfhack/inject.c
|
|
|
+--- a/build/unix/elfhack/inject.c
|
|
|
++++ b/build/unix/elfhack/inject.c
|
|
|
+@@ -9,81 +9,96 @@
|
|
|
+ #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__
|
|
|
++# 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
|
|
|
++# 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__
|
|
|
++# 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
|
|
|
++# 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"))) Elf_Ehdr __ehdr_start;
|
|
|
+
|
|
|
+ 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
|
|
|
+
|
|
|
+-static inline __attribute__((always_inline)) void do_relocations(void) {
|
|
|
++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 {
|
|
|
+ size_t remaining = (8 * sizeof(Elf_Addr) - 1);
|
|
|
+ Elf_Addr bits = *entry;
|
|
|
+@@ -95,61 +110,113 @@ static inline __attribute__((always_inli
|
|
|
+ *ptr += (intptr_t)&__ehdr_start;
|
|
|
+ }
|
|
|
+ } while (bits);
|
|
|
+ ptr += remaining;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
++#ifndef RELRHACK
|
|
|
+ __attribute__((section(".text._init_noinit"))) int init_noinit(int argc,
|
|
|
+ char** argv,
|
|
|
+ char** env) {
|
|
|
+- do_relocations();
|
|
|
++ do_relocations(relhack, relhack_end);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ __attribute__((section(".text._init"))) int init(int argc, char** argv,
|
|
|
+ char** env) {
|
|
|
+- do_relocations();
|
|
|
++ 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(
|
|
|
+- void) {
|
|
|
++ 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();
|
|
|
++ 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();
|
|
|
++ 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();
|
|
|
++ 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);
|
|
|
++ }
|
|
|
++}
|
|
|
++
|
|
|
++extern __attribute__((visibility("hidden"))) void _init(int argc, char** argv,
|
|
|
++ char** env);
|
|
|
++
|
|
|
++void _relrhack_wrap_init(int argc, char** argv, char** env) {
|
|
|
++ _relrhack_init();
|
|
|
++ _init(argc, argv, env);
|
|
|
++}
|
|
|
++#endif
|
|
|
+diff --git a/build/unix/elfhack/inject/moz.build b/build/unix/elfhack/inject/moz.build
|
|
|
+--- a/build/unix/elfhack/inject/moz.build
|
|
|
++++ b/build/unix/elfhack/inject/moz.build
|
|
|
+@@ -15,16 +15,20 @@ cpu = CONFIG['CPU_ARCH']
|
|
|
+ gen_src = '%s.c' % cpu
|
|
|
+ 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':
|
|
|
+diff --git a/build/unix/elfhack/moz.build b/build/unix/elfhack/moz.build
|
|
|
+--- a/build/unix/elfhack/moz.build
|
|
|
++++ b/build/unix/elfhack/moz.build
|
|
|
+@@ -13,21 +13,30 @@ if not CONFIG['CROSS_COMPILE']:
|
|
|
+ 'test-array.c',
|
|
|
+ 'test-ctors.c',
|
|
|
+ ]
|
|
|
+
|
|
|
+ SOURCES['dummy.c'].flags += ['-fno-lto']
|
|
|
+ SOURCES['test-array.c'].flags += ['-fno-lto']
|
|
|
+ SOURCES['test-ctors.c'].flags += ['-fno-lto']
|
|
|
+
|
|
|
+-HOST_SOURCES += [
|
|
|
+- 'elf.cpp',
|
|
|
+- 'elfhack.cpp',
|
|
|
+-]
|
|
|
++if CONFIG["RELRHACK"]:
|
|
|
++ HOST_SOURCES += [
|
|
|
++ "relrhack.cpp",
|
|
|
++ ]
|
|
|
++
|
|
|
++ HostProgram(CONFIG["RELRHACK_LINKER"])
|
|
|
+
|
|
|
+-HostProgram('elfhack')
|
|
|
++ HOST_OS_LIBS += CONFIG["RELRHACK_LIBS"]
|
|
|
++else:
|
|
|
++ HOST_SOURCES += [
|
|
|
++ "elf.cpp",
|
|
|
++ "elfhack.cpp",
|
|
|
++ ]
|
|
|
++
|
|
|
++ HostProgram("elfhack")
|
|
|
+
|
|
|
+ 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
|
|
|
+--- /dev/null
|
|
|
++++ b/build/unix/elfhack/relrhack.cpp
|
|
|
+@@ -0,0 +1,451 @@
|
|
|
++/* 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 <sys/wait.h>
|
|
|
++#include <unistd.h>
|
|
|
++#include <vector>
|
|
|
++
|
|
|
++namespace fs = std::filesystem;
|
|
|
++
|
|
|
++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;
|
|
|
++
|
|
|
++ // 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;
|
|
|
++}
|
|
|
++
|
|
|
++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?");
|
|
|
++ }
|
|
|
++ // Find the location and size of several sections from the .dynamic section
|
|
|
++ // contents:
|
|
|
++ // - SHT_RELR section, which contains the packed-relative-relocs.
|
|
|
++ // - SHT_VERNEED section, which contains the symbol versions needed.
|
|
|
++ // - SHT_STRTAB section, which contains the string table for, among other
|
|
|
++ // things, those symbol versions.
|
|
|
++ // At the same time, we also change DT_RELR* tags to add DT_RELRHACK_BIT.
|
|
|
++ 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;
|
|
|
++ Elf_Addr strtab_off = 0, verneed_off = 0, relr_off = 0;
|
|
|
++ Elf_Off strsz = 0, verneednum = 0, relrsz = 0, relent = 0;
|
|
|
++ std::vector<std::pair<off_t, Elf_Off>> relr_tags;
|
|
|
++ for (const auto& d : dyn) {
|
|
|
++ if (d.d_tag == DT_NULL) {
|
|
|
++ break;
|
|
|
++ }
|
|
|
++ switch (d.d_tag) {
|
|
|
++ case DT_RELR:
|
|
|
++ case DT_RELRENT:
|
|
|
++ case DT_RELRSZ:
|
|
|
++ if (d.d_tag == DT_RELR) {
|
|
|
++ if (relr_off) {
|
|
|
++ throw std::runtime_error("DT_RELR appears twice?");
|
|
|
++ }
|
|
|
++ relr_off = get_offset(phdr, d.d_un.d_ptr);
|
|
|
++ } else if (d.d_tag == DT_RELRSZ) {
|
|
|
++ if (relrsz) {
|
|
|
++ throw std::runtime_error("DT_RELRSZ appears twice?");
|
|
|
++ }
|
|
|
++ relrsz = d.d_un.d_val;
|
|
|
++ }
|
|
|
++ relr_tags.push_back({dyn_offset, d.d_tag | DT_RELRHACK_BIT});
|
|
|
++ break;
|
|
|
++ case DT_RELAENT:
|
|
|
++ case DT_RELENT:
|
|
|
++ relent = d.d_un.d_val;
|
|
|
++ break;
|
|
|
++ case DT_STRTAB:
|
|
|
++ strtab_off = get_offset(phdr, d.d_un.d_ptr);
|
|
|
++ break;
|
|
|
++ case DT_STRSZ:
|
|
|
++ strsz = d.d_un.d_val;
|
|
|
++ break;
|
|
|
++ case DT_VERNEED:
|
|
|
++ verneed_off = get_offset(phdr, d.d_un.d_ptr);
|
|
|
++ break;
|
|
|
++ case DT_VERNEEDNUM:
|
|
|
++ verneednum = d.d_un.d_val;
|
|
|
++ break;
|
|
|
++ }
|
|
|
++ dyn_offset += sizeof(Elf_Dyn);
|
|
|
++ }
|
|
|
++
|
|
|
++ // 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;
|
|
|
++ }
|
|
|
++
|
|
|
++ if (verneednum && verneed_off && strsz && strtab_off) {
|
|
|
++ // Scan SHT_VERNEED for the GLIBC_ABI_DT_RELR version on the libc
|
|
|
++ // library.
|
|
|
++ auto strtab = read_vector_at<char>(f, strtab_off, 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)) {
|
|
|
++ 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) {
|
|
|
++ f.seekg(relr, std::ios::beg);
|
|
|
++ // Don't overwrite vn_aux.
|
|
|
++ f.write(reinterpret_cast<char*>(&reuse),
|
|
|
++ sizeof(reuse) - sizeof(Elf_Word));
|
|
|
++ for (const auto& [offset, tag] : relr_tags) {
|
|
|
++ f.seekg(offset, std::ios::beg);
|
|
|
++ f.write(reinterpret_cast<const char*>(&tag), sizeof(tag));
|
|
|
++ }
|
|
|
++ }
|
|
|
++ }
|
|
|
++ verneed_off += verneed.vn_next;
|
|
|
++ }
|
|
|
++ }
|
|
|
++ off_t shdr_offset = ehdr.e_shoff + offsetof(Elf_Shdr, sh_type);
|
|
|
++ auto shdr = read_vector_at<Elf_Shdr>(f, ehdr.e_shoff, ehdr.e_shnum);
|
|
|
++ for (const 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) {
|
|
|
++ Elf_Word progbits = SHT_PROGBITS;
|
|
|
++ f.seekg(shdr_offset, std::ios::beg);
|
|
|
++ f.write(reinterpret_cast<const char*>(&progbits), sizeof(progbits));
|
|
|
++ }
|
|
|
++ 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;
|
|
|
++ 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 && fs::path(arg).filename() == "crti.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 crti.o 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 (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
|
|
|
+--- /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/moz.build b/build/unix/moz.build
|
|
|
+--- a/build/unix/moz.build
|
|
|
++++ b/build/unix/moz.build
|
|
|
+@@ -2,14 +2,14 @@
|
|
|
+ # 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/.
|
|
|
+
|
|
|
+ if CONFIG['MOZ_LIBSTDCXX_TARGET_VERSION'] or CONFIG['MOZ_LIBSTDCXX_HOST_VERSION']:
|
|
|
+ DIRS += ['stdc++compat']
|
|
|
+
|
|
|
+-if CONFIG['USE_ELF_HACK']:
|
|
|
++if CONFIG["USE_ELF_HACK"] or CONFIG["RELRHACK"]:
|
|
|
+ DIRS += ['elfhack']
|
|
|
+
|
|
|
+ FINAL_TARGET_FILES += [
|
|
|
+ 'run-mozilla.sh',
|
|
|
+ ]
|