Support non-zero p_vaddr in first PT_LOAD segment

Before changing mmap() permission to PROT_NONE in alloc_mem_region(),
such libraries once worked with a bug that uses mmap'ed region outside of
memory region allocated by alloc_mem_region().  This possibly incurs
SIGSEGV because it can overwrite memory region of previously loaded
library, but it sometimes worked, depending on loading order.

After PROT_NONE, this caused SIGSEGV earlier during calculation of
si->phdr in load_library(), but this was fixed by:

    75917c84d16c35b8fceff6977fa717a3de9ef65d  Use mmap to read an ...

Now the behaviour is the same as before applying PROT_NONE in
alloc_mem_region().

This CL fixed the original issue, supporting shared libraries that have
non-zero p_vaddr in first (= with lowest p_vaddr) PT_LOAD segment.

Bug: 6561258
Change-Id: Ib6176dd3e44c4d99a340eb1cbd16fb037586b0bc
This commit is contained in:
Ji-Hwan Lee 2012-05-31 20:20:36 +09:00
parent 31431f454a
commit f186a1819a
2 changed files with 83 additions and 40 deletions

View File

@ -347,7 +347,7 @@ _Unwind_Ptr dl_unwind_find_exidx(_Unwind_Ptr pc, int *pcount)
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);
return (_Unwind_Ptr)si->ARM_exidx;
}
}
*pcount = 0;
@ -423,7 +423,7 @@ static unsigned elfhash(const char *_name)
}
static Elf32_Sym *
_do_lookup(soinfo *si, const char *name, unsigned *base)
_do_lookup(soinfo *si, const char *name, unsigned *base, Elf32_Addr *offset)
{
unsigned elf_hash = elfhash(name);
Elf32_Sym *s;
@ -486,9 +486,11 @@ _do_lookup(soinfo *si, const char *name, unsigned *base)
done:
if(s != NULL) {
TRACE_TYPE(LOOKUP, "%5d si %s sym %s s->st_value = 0x%08x, "
"found in %s, base = 0x%08x\n",
pid, si->name, name, s->st_value, lsi->name, lsi->base);
"found in %s, base = 0x%08x, load offset = 0x%08x\n",
pid, si->name, name, s->st_value,
lsi->name, lsi->base, lsi->load_offset);
*base = lsi->base;
*offset = lsi->load_offset;
return s;
}
@ -866,7 +868,8 @@ 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);
Elf32_Addr base = (Elf32_Addr) si->base;
Elf32_Phdr *phdr0 = 0; /* program header for the first LOAD segment */
Elf32_Addr base;
int cnt;
unsigned len;
Elf32_Addr tmp;
@ -880,8 +883,27 @@ load_segments(int fd, void *header, soinfo *si)
TRACE("[ %5d - Begin loading segments for '%s' @ 0x%08x ]\n",
pid, si->name, (unsigned)si->base);
for (cnt = 0; cnt < ehdr->e_phnum; ++cnt, ++phdr) {
if (phdr->p_type == PT_LOAD) {
phdr0 = phdr;
/*
* ELF specification section 2-2.
*
* PT_LOAD: "Loadable segment entries in the program
* header table appear in ascending order, sorted on the
* p_vaddr member."
*/
si->load_offset = phdr->p_vaddr & (~PAGE_MASK);
break;
}
}
/* "base" might wrap around UINT32_MAX. */
base = (Elf32_Addr)(si->base - si->load_offset);
/* Now go through all the PT_LOAD segments and map them into memory
* at the appropriate locations. */
phdr = (Elf32_Phdr *)((unsigned char *)header + ehdr->e_phoff);
for (cnt = 0; cnt < ehdr->e_phnum; ++cnt, ++phdr) {
if (phdr->p_type == PT_LOAD) {
DEBUG_DUMP_PHDR(phdr, "PT_LOAD", pid);
@ -991,9 +1013,9 @@ load_segments(int fd, void *header, soinfo *si)
/* 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)) {
if (((base + phdr->p_vaddr) >= si->base + si->size)
|| ((base + phdr->p_vaddr + phdr->p_memsz) > si->base + si->size)
|| ((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);
@ -1007,7 +1029,7 @@ load_segments(int fd, void *header, soinfo *si)
DEBUG_DUMP_PHDR(phdr, "PT_ARM_EXIDX", pid);
/* exidx entries (used for stack unwinding) are 8 bytes each.
*/
si->ARM_exidx = (unsigned *)phdr->p_vaddr;
si->ARM_exidx = (unsigned *)(base + phdr->p_vaddr);
si->ARM_exidx_count = phdr->p_memsz / 8;
}
#endif
@ -1023,6 +1045,21 @@ load_segments(int fd, void *header, soinfo *si)
goto fail;
}
/* vaddr : Real virtual address in process' address space.
* p_vaddr : Relative virtual address in ELF object
* p_offset : File offset in ELF object
*
* vaddr p_vaddr p_offset
* ----- ------------ --------
* base 0
* si->base phdr0->p_vaddr & ~PAGE_MASK
* phdr0->p_vaddr phdr0->p_offset
* phdr ehdr->e_phoff
*/
si->phdr = (Elf32_Phdr *)(base + phdr0->p_vaddr +
ehdr->e_phoff - phdr0->p_offset);
si->phnum = ehdr->e_phnum;
TRACE("[ %5d - Finish loading segments for '%s' @ 0x%08x. "
"Total memory footprint: 0x%08x bytes ]\n", pid, si->name,
(unsigned)si->base, si->size);
@ -1143,9 +1180,6 @@ load_library(const char *name)
goto fail;
}
si->phdr = (Elf32_Phdr *)((unsigned char *)si->base + hdr->e_phoff);
si->phnum = hdr->e_phnum;
munmap(hdr, sb.st_size);
close(fd);
return si;
@ -1282,13 +1316,14 @@ static int reloc_library(soinfo *si, Elf32_Rel *rel, unsigned count)
const char *strtab = si->strtab;
Elf32_Sym *s;
unsigned base;
Elf32_Addr offset;
Elf32_Rel *start = rel;
unsigned idx;
for (idx = 0; idx < count; ++idx) {
unsigned type = ELF32_R_TYPE(rel->r_info);
unsigned sym = ELF32_R_SYM(rel->r_info);
unsigned reloc = (unsigned)(rel->r_offset + si->base);
unsigned reloc = (unsigned)(rel->r_offset + si->base - si->load_offset);
unsigned sym_addr = 0;
char *sym_name = NULL;
@ -1296,7 +1331,7 @@ static int reloc_library(soinfo *si, Elf32_Rel *rel, unsigned count)
si->name, idx);
if(sym != 0) {
sym_name = (char *)(strtab + symtab[sym].st_name);
s = _do_lookup(si, sym_name, &base);
s = _do_lookup(si, sym_name, &base, &offset);
if(s == NULL) {
/* We only allow an undefined symbol if this is a weak
reference.. */
@ -1363,7 +1398,7 @@ static int reloc_library(soinfo *si, Elf32_Rel *rel, unsigned count)
return -1;
}
#endif
sym_addr = (unsigned)(s->st_value + base);
sym_addr = (unsigned)(s->st_value + base - offset);
}
COUNT_RELOC(RELOC_SYMBOL);
} else {
@ -1668,6 +1703,8 @@ static int nullify_closed_stdio (void)
static int link_image(soinfo *si, unsigned wr_offset)
{
unsigned *d;
/* "base" might wrap around UINT32_MAX. */
Elf32_Addr base = si->base - si->load_offset;
Elf32_Phdr *phdr = si->phdr;
int phnum = si->phnum;
@ -1703,7 +1740,7 @@ static int link_image(soinfo *si, unsigned wr_offset)
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
(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
@ -1717,9 +1754,9 @@ static int link_image(soinfo *si, unsigned wr_offset)
if (!(phdr->p_flags & PF_W)) {
unsigned _end;
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) &
if (base + phdr->p_vaddr < si->wrprotect_start)
si->wrprotect_start = base + phdr->p_vaddr;
_end = (((base + phdr->p_vaddr + phdr->p_memsz + PAGE_SIZE - 1) &
(~PAGE_MASK)));
if (_end > si->wrprotect_end)
si->wrprotect_end = _end;
@ -1728,7 +1765,7 @@ static int link_image(soinfo *si, unsigned wr_offset)
* However, we will remember what range of addresses
* should be write protected.
*/
mprotect((void *) (si->base + phdr->p_vaddr),
mprotect((void *) (base + phdr->p_vaddr),
phdr->p_memsz,
PFLAGS_TO_PROT(phdr->p_flags) | PROT_WRITE);
}
@ -1736,22 +1773,22 @@ static int link_image(soinfo *si, unsigned wr_offset)
if (si->dynamic != (unsigned *)-1) {
DL_ERR("%5d multiple PT_DYNAMIC segments found in '%s'. "
"Segment at 0x%08x, previously one found at 0x%08x",
pid, si->name, si->base + phdr->p_vaddr,
pid, si->name, base + phdr->p_vaddr,
(unsigned)si->dynamic);
goto fail;
}
DEBUG_DUMP_PHDR(phdr, "PT_DYNAMIC", pid);
si->dynamic = (unsigned *) (si->base + phdr->p_vaddr);
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)
|| ((si->base + phdr->p_vaddr + phdr->p_memsz) < si->base)) {
if ((base + phdr->p_vaddr >= si->base + si->size)
|| ((base + phdr->p_vaddr + phdr->p_memsz) > si->base + si->size)
|| ((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_start = (Elf32_Addr) (base + phdr->p_vaddr);
si->gnu_relro_len = (unsigned) phdr->p_memsz;
}
}
@ -1769,16 +1806,16 @@ static int link_image(soinfo *si, unsigned wr_offset)
DEBUG("%5d d = %p, d[0] = 0x%08x d[1] = 0x%08x\n", pid, d, d[0], d[1]);
switch(*d++){
case DT_HASH:
si->nbucket = ((unsigned *) (si->base + *d))[0];
si->nchain = ((unsigned *) (si->base + *d))[1];
si->bucket = (unsigned *) (si->base + *d + 8);
si->chain = (unsigned *) (si->base + *d + 8 + si->nbucket * 4);
si->nbucket = ((unsigned *) (base + *d))[0];
si->nchain = ((unsigned *) (base + *d))[1];
si->bucket = (unsigned *) (base + *d + 8);
si->chain = (unsigned *) (base + *d + 8 + si->nbucket * 4);
break;
case DT_STRTAB:
si->strtab = (const char *) (si->base + *d);
si->strtab = (const char *) (base + *d);
break;
case DT_SYMTAB:
si->symtab = (Elf32_Sym *) (si->base + *d);
si->symtab = (Elf32_Sym *) (base + *d);
break;
case DT_PLTREL:
if(*d != DT_REL) {
@ -1787,20 +1824,20 @@ static int link_image(soinfo *si, unsigned wr_offset)
}
break;
case DT_JMPREL:
si->plt_rel = (Elf32_Rel*) (si->base + *d);
si->plt_rel = (Elf32_Rel*) (base + *d);
break;
case DT_PLTRELSZ:
si->plt_rel_count = *d / 8;
break;
case DT_REL:
si->rel = (Elf32_Rel*) (si->base + *d);
si->rel = (Elf32_Rel*) (base + *d);
break;
case DT_RELSZ:
si->rel_count = *d / 8;
break;
case DT_PLTGOT:
/* Save this in case we decide to do lazy binding. We don't yet. */
si->plt_got = (unsigned *)(si->base + *d);
si->plt_got = (unsigned *)(base + *d);
break;
case DT_DEBUG:
// Set the DT_DEBUG entry to the addres of _r_debug for GDB
@ -1810,17 +1847,17 @@ static int link_image(soinfo *si, unsigned wr_offset)
DL_ERR("%5d DT_RELA not supported", pid);
goto fail;
case DT_INIT:
si->init_func = (void (*)(void))(si->base + *d);
si->init_func = (void (*)(void))(base + *d);
DEBUG("%5d %s constructors (init func) found at %p\n",
pid, si->name, si->init_func);
break;
case DT_FINI:
si->fini_func = (void (*)(void))(si->base + *d);
si->fini_func = (void (*)(void))(base + *d);
DEBUG("%5d %s destructors (fini func) found at %p\n",
pid, si->name, si->fini_func);
break;
case DT_INIT_ARRAY:
si->init_array = (unsigned *)(si->base + *d);
si->init_array = (unsigned *)(base + *d);
DEBUG("%5d %s constructors (init_array) found at %p\n",
pid, si->name, si->init_array);
break;
@ -1828,7 +1865,7 @@ static int link_image(soinfo *si, unsigned wr_offset)
si->init_array_count = ((unsigned)*d) / sizeof(Elf32_Addr);
break;
case DT_FINI_ARRAY:
si->fini_array = (unsigned *)(si->base + *d);
si->fini_array = (unsigned *)(base + *d);
DEBUG("%5d %s destructors (fini_array) found at %p\n",
pid, si->name, si->fini_array);
break;
@ -1836,7 +1873,7 @@ static int link_image(soinfo *si, unsigned wr_offset)
si->fini_array_count = ((unsigned)*d) / sizeof(Elf32_Addr);
break;
case DT_PREINIT_ARRAY:
si->preinit_array = (unsigned *)(si->base + *d);
si->preinit_array = (unsigned *)(base + *d);
DEBUG("%5d %s constructors (preinit_array) found at %p\n",
pid, si->name, si->preinit_array);
break;
@ -2150,6 +2187,7 @@ sanitize:
break;
}
}
si->load_offset = 0;
si->dynamic = (unsigned *)-1;
si->wrprotect_start = 0xffffffff;
si->wrprotect_end = 0;
@ -2267,6 +2305,7 @@ unsigned __linker_init(unsigned **elfdata) {
memset(&linker_so, 0, sizeof(soinfo));
linker_so.base = linker_addr;
linker_so.load_offset = 0;
linker_so.dynamic = (unsigned *) -1;
linker_so.phdr = phdr;
linker_so.phnum = elf_hdr->e_phnum;

View File

@ -148,6 +148,10 @@ struct soinfo
Elf32_Addr gnu_relro_start;
unsigned gnu_relro_len;
/* When you read a virtual address from the ELF file, add the load
* address (= "base" field) minus this value (= "load_offset") to get the
* real, corresponding address in the process' address space */
Elf32_Addr load_offset;
};