summaryrefslogtreecommitdiffstats
path: root/linker/linker.c
diff options
context:
space:
mode:
Diffstat (limited to 'linker/linker.c')
-rw-r--r--linker/linker.c303
1 files changed, 228 insertions, 75 deletions
diff --git a/linker/linker.c b/linker/linker.c
index bcfa8dc..eb9cc3e 100644
--- a/linker/linker.c
+++ b/linker/linker.c
@@ -108,7 +108,10 @@ static const char *ldpreload_names[LDPRELOAD_MAX + 1];
static soinfo *preloads[LDPRELOAD_MAX + 1];
+#if LINKER_DEBUG
int debug_verbosity;
+#endif
+
static int pid;
/* This boolean is set if the program being loaded is setuid */
@@ -313,15 +316,6 @@ static void free_info(soinfo *si)
freelist = si;
}
-#ifndef LINKER_TEXT_BASE
-#error "linker's makefile must define LINKER_TEXT_BASE"
-#endif
-#ifndef LINKER_AREA_SIZE
-#error "linker's makefile must define LINKER_AREA_SIZE"
-#endif
-#define LINKER_BASE ((LINKER_TEXT_BASE) & 0xfff00000)
-#define LINKER_TOP (LINKER_BASE + (LINKER_AREA_SIZE))
-
const char *addr_to_name(unsigned addr)
{
soinfo *si;
@@ -332,10 +326,6 @@ const char *addr_to_name(unsigned addr)
}
}
- if((addr >= LINKER_BASE) && (addr < LINKER_TOP)){
- return "linker";
- }
-
return "";
}
@@ -354,12 +344,10 @@ _Unwind_Ptr dl_unwind_find_exidx(_Unwind_Ptr pc, int *pcount)
soinfo *si;
unsigned addr = (unsigned)pc;
- if ((addr < LINKER_BASE) || (addr >= LINKER_TOP)) {
- for (si = solist; si != 0; si = si->next){
- if ((addr >= si->base) && (addr < (si->base + si->size))) {
- *pcount = si->ARM_exidx_count;
- return (_Unwind_Ptr)(si->base + (unsigned long)si->ARM_exidx);
- }
+ for (si = solist; si != 0; si = si->next){
+ if ((addr >= si->base) && (addr < (si->base + si->size))) {
+ *pcount = si->ARM_exidx_count;
+ return (_Unwind_Ptr)(si->base + (unsigned long)si->ARM_exidx);
}
}
*pcount = 0;
@@ -443,7 +431,7 @@ _do_lookup(soinfo *si, const char *name, unsigned *base)
soinfo *lsi = si;
int i;
- /* Look for symbols in the local scope first (the object who is
+ /* Look for symbols in the local scope (the object who is
* searching). This happens with C++ templates on i386 for some
* reason.
*
@@ -452,6 +440,7 @@ _do_lookup(soinfo *si, const char *name, unsigned *base)
* dynamic linking. Some systems return the first definition found
* and some the first non-weak definition. This is system dependent.
* Here we return the first definition found for simplicity. */
+
s = _elf_lookup(si, elf_hash, name);
if(s != NULL)
goto done;
@@ -792,16 +781,15 @@ get_lib_extents(int fd, const char *name, void *__hdr, unsigned *total_sz)
return (unsigned)req_base;
}
-/* alloc_mem_region
+/* reserve_mem_region
*
* This function reserves a chunk of memory to be used for mapping in
- * the shared library. We reserve the entire memory region here, and
+ * a prelinked shared library. We reserve the entire memory region here, and
* then the rest of the linker will relocate the individual loadable
* segments into the correct locations within this memory range.
*
* Args:
- * si->base: The requested base of the allocation. If 0, a sane one will be
- * chosen in the range LIBBASE <= base < LIBLAST.
+ * si->base: The requested base of the allocation.
* si->size: The size of the allocation.
*
* Returns:
@@ -811,7 +799,7 @@ get_lib_extents(int fd, const char *name, void *__hdr, unsigned *total_sz)
static int reserve_mem_region(soinfo *si)
{
- void *base = mmap((void *)si->base, si->size, PROT_READ | PROT_EXEC,
+ void *base = mmap((void *)si->base, si->size, PROT_NONE,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (base == MAP_FAILED) {
DL_ERR("%5d can NOT map (%sprelinked) library '%s' at 0x%08x "
@@ -829,8 +817,7 @@ static int reserve_mem_region(soinfo *si)
return 0;
}
-static int
-alloc_mem_region(soinfo *si)
+static int alloc_mem_region(soinfo *si)
{
if (si->base) {
/* Attempt to mmap a prelinked library. */
@@ -841,7 +828,7 @@ alloc_mem_region(soinfo *si)
allocator.
*/
- void *base = mmap(NULL, si->size, PROT_READ | PROT_EXEC,
+ void *base = mmap(NULL, si->size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (base == MAP_FAILED) {
DL_ERR("%5d mmap of library '%s' failed: %d (%s)\n",
@@ -883,10 +870,10 @@ load_segments(int fd, void *header, soinfo *si)
{
Elf32_Ehdr *ehdr = (Elf32_Ehdr *)header;
Elf32_Phdr *phdr = (Elf32_Phdr *)((unsigned char *)header + ehdr->e_phoff);
- unsigned char *base = (unsigned char *)si->base;
+ Elf32_Addr base = (Elf32_Addr) si->base;
int cnt;
unsigned len;
- unsigned char *tmp;
+ Elf32_Addr tmp;
unsigned char *pbase;
unsigned char *extra_base;
unsigned extra_len;
@@ -910,7 +897,7 @@ load_segments(int fd, void *header, soinfo *si)
TRACE("[ %d - Trying to load segment from '%s' @ 0x%08x "
"(0x%08x). p_vaddr=0x%08x p_offset=0x%08x ]\n", pid, si->name,
(unsigned)tmp, len, phdr->p_vaddr, phdr->p_offset);
- pbase = mmap(tmp, len, PFLAGS_TO_PROT(phdr->p_flags),
+ pbase = mmap((void *)tmp, len, PFLAGS_TO_PROT(phdr->p_flags),
MAP_PRIVATE | MAP_FIXED, fd,
phdr->p_offset & (~PAGE_MASK));
if (pbase == MAP_FAILED) {
@@ -952,7 +939,7 @@ load_segments(int fd, void *header, soinfo *si)
* | |
* _+---------------------+ page boundary
*/
- tmp = (unsigned char *)(((unsigned)pbase + len + PAGE_SIZE - 1) &
+ tmp = (Elf32_Addr)(((unsigned)pbase + len + PAGE_SIZE - 1) &
(~PAGE_MASK));
if (tmp < (base + phdr->p_vaddr + phdr->p_memsz)) {
extra_len = base + phdr->p_vaddr + phdr->p_memsz - tmp;
@@ -1007,6 +994,17 @@ load_segments(int fd, void *header, soinfo *si)
DEBUG_DUMP_PHDR(phdr, "PT_DYNAMIC", pid);
/* this segment contains the dynamic linking information */
si->dynamic = (unsigned *)(base + phdr->p_vaddr);
+ } else if (phdr->p_type == PT_GNU_RELRO) {
+ if ((phdr->p_vaddr >= si->size)
+ || ((phdr->p_vaddr + phdr->p_memsz) > si->size)
+ || ((base + phdr->p_vaddr + phdr->p_memsz) < base)) {
+ DL_ERR("%d invalid GNU_RELRO in '%s' "
+ "p_vaddr=0x%08x p_memsz=0x%08x", pid, si->name,
+ phdr->p_vaddr, phdr->p_memsz);
+ goto fail;
+ }
+ si->gnu_relro_start = (Elf32_Addr) (base + phdr->p_vaddr);
+ si->gnu_relro_len = (unsigned) phdr->p_memsz;
} else {
#ifdef ANDROID_ARM_LINKER
if (phdr->p_type == PT_ARM_EXIDX) {
@@ -1228,10 +1226,29 @@ unsigned unload_library(soinfo *si)
TRACE("%5d unloading '%s'\n", pid, si->name);
call_destructors(si);
+ /*
+ * Make sure that we undo the PT_GNU_RELRO protections we added
+ * in link_image. This is needed to undo the DT_NEEDED hack below.
+ */
+ if ((si->gnu_relro_start != 0) && (si->gnu_relro_len != 0)) {
+ Elf32_Addr start = (si->gnu_relro_start & ~PAGE_MASK);
+ unsigned len = (si->gnu_relro_start - start) + si->gnu_relro_len;
+ if (mprotect((void *) start, len, PROT_READ | PROT_WRITE) < 0)
+ DL_ERR("%5d %s: could not undo GNU_RELRO protections. "
+ "Expect a crash soon. errno=%d (%s)",
+ pid, si->name, errno, strerror(errno));
+
+ }
+
for(d = si->dynamic; *d; d += 2) {
if(d[0] == DT_NEEDED){
soinfo *lsi = (soinfo *)d[1];
+
+ // The next line will segfault if the we don't undo the
+ // PT_GNU_RELRO protections (see comments above and in
+ // link_image().
d[1] = 0;
+
if (validate_soinfo(lsi)) {
TRACE("%5d %s needs to unload %s\n", pid,
si->name, lsi->name);
@@ -1499,8 +1516,24 @@ static void call_array(unsigned *ctor, int count, int reverse)
}
}
-static void call_constructors(soinfo *si)
+void call_constructors_recursive(soinfo *si)
{
+ if (si->constructors_called)
+ return;
+
+ // Set this before actually calling the constructors, otherwise it doesn't
+ // protect against recursive constructor calls. One simple example of
+ // constructor recursion is the libc debug malloc, which is implemented in
+ // libc_malloc_debug_leak.so:
+ // 1. The program depends on libc, so libc's constructor is called here.
+ // 2. The libc constructor calls dlopen() to load libc_malloc_debug_leak.so.
+ // 3. dlopen() calls call_constructors_recursive() with the newly created
+ // soinfo for libc_malloc_debug_leak.so.
+ // 4. The debug so depends on libc, so call_constructors_recursive() is
+ // called again with the libc soinfo. If it doesn't trigger the early-
+ // out above, the libc constructor will be called again (recursively!).
+ si->constructors_called = 1;
+
if (si->flags & FLAG_EXE) {
TRACE("[ %5d Calling preinit_array @ 0x%08x [%d] for '%s' ]\n",
pid, (unsigned)si->preinit_array, si->preinit_array_count,
@@ -1515,6 +1548,21 @@ static void call_constructors(soinfo *si)
}
}
+ if (si->dynamic) {
+ unsigned *d;
+ for(d = si->dynamic; *d; d += 2) {
+ if(d[0] == DT_NEEDED){
+ soinfo* lsi = (soinfo *)d[1];
+ if (!validate_soinfo(lsi)) {
+ DL_ERR("%5d bad DT_NEEDED pointer in %s",
+ pid, si->name);
+ } else {
+ call_constructors_recursive(lsi);
+ }
+ }
+ }
+ }
+
if (si->init_func) {
TRACE("[ %5d Calling init_func @ 0x%08x for '%s' ]\n", pid,
(unsigned)si->init_func, si->name);
@@ -1528,8 +1576,8 @@ static void call_constructors(soinfo *si)
call_array(si->init_array, si->init_array_count, 0);
TRACE("[ %5d Done calling init_array for '%s' ]\n", pid, si->name);
}
-}
+}
static void call_destructors(soinfo *si)
{
@@ -1628,10 +1676,10 @@ static int link_image(soinfo *si, unsigned wr_offset)
DEBUG("%5d si->base = 0x%08x si->flags = 0x%08x\n", pid,
si->base, si->flags);
- if (si->flags & FLAG_EXE) {
+ if (si->flags & (FLAG_EXE | FLAG_LINKER)) {
/* Locate the needed program segments (DYNAMIC/ARM_EXIDX) for
- * linkage info if this is the executable. If this was a
- * dynamic lib, that would have been done at load time.
+ * linkage info if this is the executable or the linker itself.
+ * If this was a dynamic lib, that would have been done at load time.
*
* TODO: It's unfortunate that small pieces of this are
* repeated from the load_library routine. Refactor this just
@@ -1650,16 +1698,17 @@ static int link_image(soinfo *si, unsigned wr_offset)
if (phdr->p_type == PT_LOAD) {
/* For the executable, we use the si->size field only in
dl_unwind_find_exidx(), so the meaning of si->size
- is not the size of the executable; it is the last
- virtual address of the loadable part of the executable;
- since si->base == 0 for an executable, we use the
- range [0, si->size) to determine whether a PC value
- falls within the executable section. Of course, if
- a value is below phdr->p_vaddr, it's not in the
- executable section, but a) we shouldn't be asking for
- such a value anyway, and b) if we have to provide
- an EXIDX for such a value, then the executable's
- EXIDX is probably the better choice.
+ is not the size of the executable; it is the distance
+ between the load location of the executable and the last
+ address of the loadable part of the executable.
+ We use the range [si->base, si->base + si->size) to
+ determine whether a PC value falls within the executable
+ section. Of course, if a value is between si->base and
+ (si->base + phdr->p_vaddr), it's not in the executable
+ section, but a) we shouldn't be asking for such a value
+ anyway, and b) if we have to provide an EXIDX for such a
+ value, then the executable's EXIDX is probably the better
+ choice.
*/
DEBUG_DUMP_PHDR(phdr, "PT_LOAD", pid);
if (phdr->p_vaddr + phdr->p_memsz > si->size)
@@ -1669,12 +1718,20 @@ static int link_image(soinfo *si, unsigned wr_offset)
if (!(phdr->p_flags & PF_W)) {
unsigned _end;
- if (phdr->p_vaddr < si->wrprotect_start)
- si->wrprotect_start = phdr->p_vaddr;
- _end = (((phdr->p_vaddr + phdr->p_memsz + PAGE_SIZE - 1) &
+ if (si->base + phdr->p_vaddr < si->wrprotect_start)
+ si->wrprotect_start = si->base + phdr->p_vaddr;
+ _end = (((si->base + phdr->p_vaddr + phdr->p_memsz + PAGE_SIZE - 1) &
(~PAGE_MASK)));
if (_end > si->wrprotect_end)
si->wrprotect_end = _end;
+ /* Make the section writable just in case we'll have to
+ * write to it during relocation (i.e. text segment).
+ * However, we will remember what range of addresses
+ * should be write protected.
+ */
+ mprotect((void *) (si->base + phdr->p_vaddr),
+ phdr->p_memsz,
+ PFLAGS_TO_PROT(phdr->p_flags) | PROT_WRITE);
}
} else if (phdr->p_type == PT_DYNAMIC) {
if (si->dynamic != (unsigned *)-1) {
@@ -1686,6 +1743,17 @@ static int link_image(soinfo *si, unsigned wr_offset)
}
DEBUG_DUMP_PHDR(phdr, "PT_DYNAMIC", pid);
si->dynamic = (unsigned *) (si->base + phdr->p_vaddr);
+ } else if (phdr->p_type == PT_GNU_RELRO) {
+ if ((phdr->p_vaddr >= si->size)
+ || ((phdr->p_vaddr + phdr->p_memsz) > si->size)
+ || ((si->base + phdr->p_vaddr + phdr->p_memsz) < si->base)) {
+ DL_ERR("%d invalid GNU_RELRO in '%s' "
+ "p_vaddr=0x%08x p_memsz=0x%08x", pid, si->name,
+ phdr->p_vaddr, phdr->p_memsz);
+ goto fail;
+ }
+ si->gnu_relro_start = (Elf32_Addr) (si->base + phdr->p_vaddr);
+ si->gnu_relro_len = (unsigned) phdr->p_memsz;
}
}
}
@@ -1827,7 +1895,7 @@ static int link_image(soinfo *si, unsigned wr_offset)
of the DT_NEEDED entry itself, so that we can retrieve the
soinfo directly later from the dynamic segment. This is a hack,
but it allows us to map from DT_NEEDED to soinfo efficiently
- later on when we resolve relocations, trying to look up a symgol
+ later on when we resolve relocations, trying to look up a symbol
with dlsym().
*/
d[1] = (unsigned)lsi;
@@ -1875,6 +1943,16 @@ static int link_image(soinfo *si, unsigned wr_offset)
}
#endif
+ if (si->gnu_relro_start != 0 && si->gnu_relro_len != 0) {
+ Elf32_Addr start = (si->gnu_relro_start & ~PAGE_MASK);
+ unsigned len = (si->gnu_relro_start - start) + si->gnu_relro_len;
+ if (mprotect((void *) start, len, PROT_READ) < 0) {
+ DL_ERR("%5d GNU_RELRO mprotect of library '%s' failed: %d (%s)\n",
+ pid, si->name, errno, strerror(errno));
+ goto fail;
+ }
+ }
+
/* If this is a SET?ID program, dup /dev/null to opened stdin,
stdout and stderr to close a security hole described in:
@@ -1884,7 +1962,6 @@ static int link_image(soinfo *si, unsigned wr_offset)
if (program_is_setuid)
nullify_closed_stdio ();
notify_gdb_of_load(si);
- call_constructors(si);
return 0;
fail:
@@ -1940,16 +2017,12 @@ static void parse_preloads(const char *path, char *delim)
}
}
-int main(int argc, char **argv)
-{
- return 0;
-}
-
-#define ANDROID_TLS_SLOTS BIONIC_TLS_SLOTS
-
-static void * __tls_area[ANDROID_TLS_SLOTS];
-
-unsigned __linker_init(unsigned **elfdata)
+/*
+ * This code is called after the linker has linked itself and
+ * fixed it's own GOT. It is safe to make references to externs
+ * and other non-local data at this point.
+ */
+static unsigned __linker_init_post_relocation(unsigned **elfdata)
{
static soinfo linker_soinfo;
@@ -1962,18 +2035,6 @@ unsigned __linker_init(unsigned **elfdata)
const char *ldpath_env = NULL;
const char *ldpreload_env = NULL;
- /* Setup a temporary TLS area that is used to get a working
- * errno for system calls.
- */
- __set_tls(__tls_area);
-
- pid = getpid();
-
-#if TIMING
- struct timeval t0, t1;
- gettimeofday(&t0, 0);
-#endif
-
/* NOTE: we store the elfdata pointer on a special location
* of the temporary TLS area in order to pass it to
* the C Library's runtime initializer.
@@ -1982,7 +2043,14 @@ unsigned __linker_init(unsigned **elfdata)
* to point to a different location to ensure that no other
* shared library constructor can access it.
*/
- __tls_area[TLS_SLOT_BIONIC_PREINIT] = elfdata;
+ __libc_init_tls(elfdata);
+
+ pid = getpid();
+
+#if TIMING
+ struct timeval t0, t1;
+ gettimeofday(&t0, 0);
+#endif
/* Initialize environment functions, and get to the ELF aux vectors table */
vecs = linker_env_init(vecs);
@@ -2009,10 +2077,12 @@ sanitize:
/* Get a few environment variables */
{
+#if LINKER_DEBUG
const char* env;
env = linker_env_get("DEBUG"); /* XXX: TODO: Change to LD_DEBUG */
if (env)
debug_verbosity = atoi(env);
+#endif
/* Normally, these are cleaned by linker_env_secure, but the test
* against program_is_setuid doesn't cost us anything */
@@ -2069,11 +2139,24 @@ sanitize:
vecs += 2;
}
+ /* Compute the value of si->base. We can't rely on the fact that
+ * the first entry is the PHDR because this will not be true
+ * for certain executables (e.g. some in the NDK unit test suite)
+ */
+ int nn;
si->base = 0;
+ for ( nn = 0; nn < si->phnum; nn++ ) {
+ if (si->phdr[nn].p_type == PT_PHDR) {
+ si->base = (Elf32_Addr) si->phdr - si->phdr[nn].p_vaddr;
+ break;
+ }
+ }
si->dynamic = (unsigned *)-1;
si->wrprotect_start = 0xffffffff;
si->wrprotect_end = 0;
si->refcount = 1;
+ si->gnu_relro_start = 0;
+ si->gnu_relro_len = 0;
/* Use LD_LIBRARY_PATH if we aren't setuid/setgid */
if (ldpath_env)
@@ -2090,6 +2173,8 @@ sanitize:
exit(-1);
}
+ call_constructors_recursive(si);
+
#if ALLOW_SYMBOLS_FROM_MAIN
/* Set somain after we've loaded all the libraries in order to prevent
* linking of symbols back to the main image, which is not set up at that
@@ -2138,3 +2223,71 @@ sanitize:
si->entry);
return si->entry;
}
+
+/*
+ * Find the value of AT_BASE passed to us by the kernel. This is the load
+ * location of the linker.
+ */
+static unsigned find_linker_base(unsigned **elfdata) {
+ int argc = (int) *elfdata;
+ char **argv = (char**) (elfdata + 1);
+ unsigned *vecs = (unsigned*) (argv + argc + 1);
+ while (vecs[0] != 0) {
+ vecs++;
+ }
+
+ /* The end of the environment block is marked by two NULL pointers */
+ vecs++;
+
+ while(vecs[0]) {
+ if (vecs[0] == AT_BASE) {
+ return vecs[1];
+ }
+ vecs += 2;
+ }
+
+ return 0; // should never happen
+}
+
+/*
+ * This is the entry point for the linker, called from begin.S. This
+ * method is responsible for fixing the linker's own relocations, and
+ * then calling __linker_init_post_relocation().
+ *
+ * Because this method is called before the linker has fixed it's own
+ * relocations, any attempt to reference an extern variable, extern
+ * function, or other GOT reference will generate a segfault.
+ */
+unsigned __linker_init(unsigned **elfdata) {
+ unsigned linker_addr = find_linker_base(elfdata);
+ Elf32_Ehdr *elf_hdr = (Elf32_Ehdr *) linker_addr;
+ Elf32_Phdr *phdr =
+ (Elf32_Phdr *)((unsigned char *) linker_addr + elf_hdr->e_phoff);
+
+ soinfo linker_so;
+ memset(&linker_so, 0, sizeof(soinfo));
+
+ linker_so.base = linker_addr;
+ linker_so.dynamic = (unsigned *) -1;
+ linker_so.phdr = phdr;
+ linker_so.phnum = elf_hdr->e_phnum;
+ linker_so.flags |= FLAG_LINKER;
+ linker_so.wrprotect_start = 0xffffffff;
+ linker_so.wrprotect_end = 0;
+ linker_so.gnu_relro_start = 0;
+ linker_so.gnu_relro_len = 0;
+
+ if (link_image(&linker_so, 0)) {
+ // It would be nice to print an error message, but if the linker
+ // can't link itself, there's no guarantee that we'll be able to
+ // call write() (because it involves a GOT reference).
+ //
+ // This situation should never occur unless the linker itself
+ // is corrupt.
+ exit(-1);
+ }
+
+ // We have successfully fixed our own relocations. It's safe to run
+ // the main part of the linker now.
+ return __linker_init_post_relocation(elfdata);
+}