summaryrefslogtreecommitdiffstats
path: root/build/unix/elfhack/inject.c
blob: f1a8e36e1c90592d3a1e9603add3ce0e5ef74268 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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