From dcbc3787bfb9a272a010f13ac149d546b4b741d8 Mon Sep 17 00:00:00 2001 From: Nick Kralevich Date: Fri, 11 Nov 2011 15:53:17 -0800 Subject: [PATCH] Make the linker relocatable. Previously, the linker always loaded itself into the same location in memory, which inhibited the effectiveness of Android's ASLR implementation. Modify the linker code so it can be relocatable and link itself at runtime. Change-Id: Ia80273d7a00ff648b4da545f4b69debee6343968 --- linker/Android.mk | 22 +------ linker/linker.c | 142 ++++++++++++++++++++++++++++++++++++++-------- linker/linker.h | 1 + 3 files changed, 121 insertions(+), 44 deletions(-) diff --git a/linker/Android.mk b/linker/Android.mk index 52cdcfe66..8f8cc2b33 100644 --- a/linker/Android.mk +++ b/linker/Android.mk @@ -10,27 +10,9 @@ LOCAL_SRC_FILES:= \ dlfcn.c \ debugger.c -ifeq ($(TARGET_ARCH),sh) -# SH-4A series virtual address range from 0x00000000 to 0x7FFFFFFF. -LINKER_TEXT_BASE := 0x70000100 -else -# This is aligned to 4K page boundary so that both GNU ld and gold work. Gold -# actually produces a correct binary with starting address 0xB0000100 but the -# extra objcopy step to rename symbols causes the resulting binary to be misaligned -# and unloadable. Increasing the alignment adds an extra 3840 bytes in padding -# but switching to gold saves about 1M of space. -LINKER_TEXT_BASE := 0xB0001000 -endif +LOCAL_LDFLAGS := -shared -# The maximum size set aside for the linker, from -# LINKER_TEXT_BASE rounded down to a megabyte. -LINKER_AREA_SIZE := 0x01000000 - -LOCAL_LDFLAGS := -Wl,-Ttext,$(LINKER_TEXT_BASE) - -LOCAL_CFLAGS += -DPRELINK -LOCAL_CFLAGS += -DLINKER_TEXT_BASE=$(LINKER_TEXT_BASE) -LOCAL_CFLAGS += -DLINKER_AREA_SIZE=$(LINKER_AREA_SIZE) +LOCAL_CFLAGS += -fno-stack-protector # Set LINKER_DEBUG to either 1 or 0 # diff --git a/linker/linker.c b/linker/linker.c index 1e35f8719..883da3ccc 100644 --- a/linker/linker.c +++ b/linker/linker.c @@ -313,15 +313,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 +323,6 @@ const char *addr_to_name(unsigned addr) } } - if((addr >= LINKER_BASE) && (addr < LINKER_TOP)){ - return "linker"; - } - return ""; } @@ -354,12 +341,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; @@ -420,6 +405,33 @@ static Elf32_Sym *_elf_lookup(soinfo *si, unsigned hash, const char *name) return NULL; } +/* + * Essentially the same method as _elf_lookup() above, but only + * searches for LOCAL symbols + */ +static Elf32_Sym *_elf_lookup_local(soinfo *si, unsigned hash, const char *name) +{ + Elf32_Sym *symtab = si->symtab; + const char *strtab = si->strtab; + unsigned n = hash % si->nbucket;; + + TRACE_TYPE(LOOKUP, "%5d LOCAL SEARCH %s in %s@0x%08x %08x %d\n", pid, + name, si->name, si->base, hash, hash % si->nbucket); + for(n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n]){ + Elf32_Sym *s = symtab + n; + if (strcmp(strtab + s->st_name, name)) continue; + if (ELF32_ST_BIND(s->st_info) != STB_LOCAL) continue; + /* no section == undefined */ + if(s->st_shndx == 0) continue; + + TRACE_TYPE(LOOKUP, "%5d FOUND LOCAL %s in %s (%08x) %d\n", pid, + name, si->name, s->st_value, s->st_size); + return s; + } + + return NULL; +} + static unsigned elfhash(const char *_name) { const unsigned char *name = (const unsigned char *) _name; @@ -443,7 +455,17 @@ _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 + /* If we are trying to find a symbol for the linker itself, look + * for LOCAL symbols first. Avoid using LOCAL symbols for other + * shared libraries until we have a better understanding of what + * might break by doing so. */ + if (si->flags & FLAG_LINKER) { + s = _elf_lookup_local(si, elf_hash, name); + if(s != NULL) + goto done; + } + + /* Look for symbols in the local scope (the object who is * searching). This happens with C++ templates on i386 for some * reason. * @@ -452,6 +474,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; @@ -1628,10 +1651,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 @@ -1958,7 +1981,12 @@ int main(int argc, char **argv) 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; @@ -2158,3 +2186,69 @@ 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; + + 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); +} diff --git a/linker/linker.h b/linker/linker.h index d29484c48..e67ae5260 100644 --- a/linker/linker.h +++ b/linker/linker.h @@ -83,6 +83,7 @@ typedef struct soinfo soinfo; #define FLAG_LINKED 0x00000001 #define FLAG_ERROR 0x00000002 #define FLAG_EXE 0x00000004 // The main executable +#define FLAG_LINKER 0x00000010 // The linker itself #define SOINFO_NAME_LEN 128