gecko-dev/build/unix/elfhack/inject.c
Mike Hommey 310043662a Bug 1470701 - Use run-time page size when changing mapping permissions in elfhack injected code. r=froydnj
When a binary has a PT_GNU_RELRO segment, the elfhack injected code
uses mprotect to add the writable flag to relocated pages before
applying relocations, removing it afterwards. To do so, the elfhack
program uses the location and size of the PT_GNU_RELRO segment, and
adjusts it to be aligned according to the PT_LOAD alignment.

The problem here is that the PT_LOAD alignment doesn't necessarily match
the actual page alignment, and the resulting mprotect may end up not
covering the full extent of what the dynamic linker has protected
read-only according to the PT_GNU_RELRO segment. In turn, this can lead
to a crash on startup when trying to apply relocations to the still
read-only locations.

Practically speaking, this doesn't end up being a problem on x86, where
the PT_LOAD alignment is usually 4096, which happens to be the page
size, but on Debian armhf, it is 64k, while the run time page size can be
4k.

--HG--
extra : rebase_source : 5ac7356f685d87c1628727e6c84f7615409c57a5
2018-06-24 09:02:38 +09:00

103 lines
3.3 KiB
C

/* 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_Addr Elf64_Addr
#else
#define Elf_Ehdr Elf32_Ehdr
#define Elf_Addr Elf32_Addr
#endif
extern __attribute__((visibility("hidden"))) void original_init(int argc, char **argv, char **env);
extern __attribute__((visibility("hidden"))) Elf32_Rel relhack[];
extern __attribute__((visibility("hidden"))) Elf_Ehdr elf_header;
extern __attribute__((visibility("hidden"))) int (*mprotect_cb)(void *addr, size_t len, int prot);
extern __attribute__((visibility("hidden"))) long (*sysconf_cb)(int name);
extern __attribute__((visibility("hidden"))) char relro_start[];
extern __attribute__((visibility("hidden"))) char relro_end[];
static inline __attribute__((always_inline))
void do_relocations(void)
{
Elf32_Rel *rel;
Elf_Addr *ptr, *start;
for (rel = relhack; rel->r_offset; rel++) {
start = (Elf_Addr *)((intptr_t)&elf_header + rel->r_offset);
for (ptr = start; ptr < &start[rel->r_info]; ptr++)
*ptr += (intptr_t)&elf_header;
}
}
__attribute__((section(".text._init_noinit")))
int init_noinit(int argc, char **argv, char **env)
{
do_relocations();
return 0;
}
__attribute__((section(".text._init")))
int init(int argc, char **argv, char **env)
{
do_relocations();
original_init(argc, argv, env);
// Ensure there is no tail-call optimization, avoiding the use of the
// B.W instruction in Thumb for the call above.
return 0;
}
static inline __attribute__((always_inline))
void do_relocations_with_relro(void)
{
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();
mprotect_cb((void *)aligned_relro_start,
aligned_relro_end - aligned_relro_start,
PROT_READ);
// 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;
}
__attribute__((section(".text._init_noinit_relro")))
int init_noinit_relro(int argc, char **argv, char **env)
{
do_relocations_with_relro();
return 0;
}
__attribute__((section(".text._init_relro")))
int init_relro(int argc, char **argv, char **env)
{
do_relocations_with_relro();
original_init(argc, argv, env);
return 0;
}