linker: avoid clobbering the .dynamic section of shared libs
This patch removes the DT_NEEDED hack which stores pointers to soinfo structs in the .dynamic section of the library being loaded. Instead, it caches the soinfo struct pointers on the stack during relocation time. After relocation time, i.e. when calling constructors and destructors of the shared library and its dependencies, uncached access is used instead, doing lookups using the string table entries pointed to by the DT_NEEDED entries. By removing this hack, it is no longer needed to undo the PT_GNURELRO protection, i.e., all non-writable mappings can remain non-writable during their entire lifespan. Even though, strictly speaking, the algorithmic complexity has increased somewhat, the real-world adverse effect is negligible on the systems I have tested. Change-Id: I2361502560b96b5878f7f94a8e8a215350d70d64 Signed-off-by: Ard Biesheuvel <ard.biesheuvel@gmail.com>
This commit is contained in:
parent
54655eaf92
commit
12c78bbded
3 changed files with 88 additions and 113 deletions
|
@ -93,12 +93,6 @@ static soinfo *somain; /* main process, always the one after libdl_info */
|
|||
#endif
|
||||
|
||||
|
||||
static inline int validate_soinfo(soinfo *si)
|
||||
{
|
||||
return (si >= sopool && si < sopool + SO_MAX) ||
|
||||
si == &libdl_info;
|
||||
}
|
||||
|
||||
static char ldpaths_buf[LDPATH_BUFSIZE];
|
||||
static const char *ldpaths[LDPATH_MAX + 1];
|
||||
|
||||
|
@ -427,11 +421,11 @@ static unsigned elfhash(const char *_name)
|
|||
}
|
||||
|
||||
static Elf32_Sym *
|
||||
soinfo_do_lookup(soinfo *si, const char *name, Elf32_Addr *offset)
|
||||
soinfo_do_lookup(soinfo *si, const char *name, Elf32_Addr *offset,
|
||||
soinfo *needed[])
|
||||
{
|
||||
unsigned elf_hash = elfhash(name);
|
||||
Elf32_Sym *s;
|
||||
unsigned *d;
|
||||
soinfo *lsi = si;
|
||||
int i;
|
||||
|
||||
|
@ -457,20 +451,13 @@ soinfo_do_lookup(soinfo *si, const char *name, Elf32_Addr *offset)
|
|||
goto done;
|
||||
}
|
||||
|
||||
for(d = si->dynamic; *d; d += 2) {
|
||||
if(d[0] == DT_NEEDED){
|
||||
lsi = (soinfo *)d[1];
|
||||
if (!validate_soinfo(lsi)) {
|
||||
DL_ERR("bad DT_NEEDED pointer in \"%s\"", lsi->name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEBUG("%5d %s: looking up %s in %s\n",
|
||||
pid, si->name, name, lsi->name);
|
||||
s = soinfo_elf_lookup(lsi, elf_hash, name);
|
||||
if (s != NULL)
|
||||
goto done;
|
||||
}
|
||||
for(i = 0; needed[i] != NULL; i++) {
|
||||
lsi = needed[i];
|
||||
DEBUG("%5d %s: looking up %s in %s\n",
|
||||
pid, si->name, name, lsi->name);
|
||||
s = soinfo_elf_lookup(lsi, elf_hash, name);
|
||||
if (s != NULL)
|
||||
goto done;
|
||||
}
|
||||
|
||||
#if ALLOW_SYMBOLS_FROM_MAIN
|
||||
|
@ -857,11 +844,29 @@ init_library(soinfo *si)
|
|||
return si;
|
||||
}
|
||||
|
||||
soinfo *find_library(const char *name)
|
||||
static soinfo *find_loaded_library(const char *name)
|
||||
{
|
||||
soinfo *si;
|
||||
const char *bname;
|
||||
|
||||
// TODO: don't use basename only for determining libraries
|
||||
// http://code.google.com/p/android/issues/detail?id=6670
|
||||
|
||||
bname = strrchr(name, '/');
|
||||
bname = bname ? bname + 1 : name;
|
||||
|
||||
for(si = solist; si != NULL; si = si->next){
|
||||
if(!strcmp(bname, si->name)) {
|
||||
return si;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
soinfo *find_library(const char *name)
|
||||
{
|
||||
soinfo *si;
|
||||
|
||||
#if ALLOW_SYMBOLS_FROM_MAIN
|
||||
if (name == NULL)
|
||||
return somain;
|
||||
|
@ -870,19 +875,15 @@ soinfo *find_library(const char *name)
|
|||
return NULL;
|
||||
#endif
|
||||
|
||||
bname = strrchr(name, '/');
|
||||
bname = bname ? bname + 1 : name;
|
||||
|
||||
for(si = solist; si != 0; si = si->next){
|
||||
if(!strcmp(bname, si->name)) {
|
||||
if(si->flags & FLAG_ERROR) {
|
||||
DL_ERR("\"%s\" failed to load previously", bname);
|
||||
return NULL;
|
||||
}
|
||||
if(si->flags & FLAG_LINKED) return si;
|
||||
DL_ERR("OOPS: recursive link to \"%s\"", si->name);
|
||||
si = find_loaded_library(name);
|
||||
if (si != NULL) {
|
||||
if(si->flags & FLAG_ERROR) {
|
||||
DL_ERR("\"%s\" failed to load previously", name);
|
||||
return NULL;
|
||||
}
|
||||
if(si->flags & FLAG_LINKED) return si;
|
||||
DL_ERR("OOPS: recursive link to \"%s\"", si->name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TRACE("[ %5d '%s' has not been loaded yet. Locating...]\n", pid, name);
|
||||
|
@ -903,27 +904,11 @@ unsigned soinfo_unload(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 soinfo_link_image. This is needed to undo the DT_NEEDED hack below.
|
||||
*/
|
||||
if (phdr_table_unprotect_gnu_relro(si->phdr, si->phnum,
|
||||
si->load_bias) < 0) {
|
||||
DL_ERR("%s: could not undo GNU_RELRO protections. "
|
||||
"Expect a crash soon. errno=%d (%s)",
|
||||
si->name, errno, strerror(errno));
|
||||
}
|
||||
|
||||
for(d = si->dynamic; *d; d += 2) {
|
||||
if(d[0] == DT_NEEDED){
|
||||
soinfo *lsi = (soinfo *)d[1];
|
||||
soinfo *lsi = find_loaded_library(si->strtab + d[1]);
|
||||
|
||||
// The next line will segfault if the we don't undo the
|
||||
// PT_GNU_RELRO protections (see comments above and in
|
||||
// soinfo_link_image().
|
||||
d[1] = 0;
|
||||
|
||||
if (validate_soinfo(lsi)) {
|
||||
if (lsi) {
|
||||
TRACE("%5d %s needs to unload %s\n", pid,
|
||||
si->name, lsi->name);
|
||||
soinfo_unload(lsi);
|
||||
|
@ -951,7 +936,8 @@ unsigned soinfo_unload(soinfo *si)
|
|||
* ideal. They should probably be either uint32_t, Elf32_Addr, or unsigned
|
||||
* long.
|
||||
*/
|
||||
static int soinfo_relocate(soinfo *si, Elf32_Rel *rel, unsigned count)
|
||||
static int soinfo_relocate(soinfo *si, Elf32_Rel *rel, unsigned count,
|
||||
soinfo *needed[])
|
||||
{
|
||||
Elf32_Sym *symtab = si->symtab;
|
||||
const char *strtab = si->strtab;
|
||||
|
@ -973,7 +959,7 @@ static int soinfo_relocate(soinfo *si, Elf32_Rel *rel, unsigned count)
|
|||
}
|
||||
if(sym != 0) {
|
||||
sym_name = (char *)(strtab + symtab[sym].st_name);
|
||||
s = soinfo_do_lookup(si, sym_name, &offset);
|
||||
s = soinfo_do_lookup(si, sym_name, &offset, needed);
|
||||
if(s == NULL) {
|
||||
/* We only allow an undefined symbol if this is a weak
|
||||
reference.. */
|
||||
|
@ -1171,7 +1157,7 @@ static int soinfo_relocate(soinfo *si, Elf32_Rel *rel, unsigned count)
|
|||
}
|
||||
|
||||
#ifdef ANDROID_MIPS_LINKER
|
||||
int mips_relocate_got(struct soinfo *si)
|
||||
int mips_relocate_got(struct soinfo *si, soinfo *needed[])
|
||||
{
|
||||
unsigned *got;
|
||||
unsigned local_gotno, gotsym, symtabno;
|
||||
|
@ -1216,7 +1202,7 @@ int mips_relocate_got(struct soinfo *si)
|
|||
|
||||
/* This is an undefined reference... try to locate it */
|
||||
sym_name = si->strtab + sym->st_name;
|
||||
s = soinfo_do_lookup(si, sym_name, &base);
|
||||
s = soinfo_do_lookup(si, sym_name, &base, needed);
|
||||
if (s == NULL) {
|
||||
/* We only allow an undefined symbol if this is a weak
|
||||
reference.. */
|
||||
|
@ -1312,9 +1298,10 @@ void soinfo_call_constructors(soinfo *si)
|
|||
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("bad DT_NEEDED pointer in \"%s\"", si->name);
|
||||
soinfo* lsi = find_loaded_library(si->strtab + d[1]);
|
||||
if (!lsi) {
|
||||
DL_ERR("\"%s\": could not initialize dependent library",
|
||||
si->name);
|
||||
} else {
|
||||
soinfo_call_constructors(lsi);
|
||||
}
|
||||
|
@ -1425,6 +1412,8 @@ static int soinfo_link_image(soinfo *si)
|
|||
const Elf32_Phdr *phdr = si->phdr;
|
||||
int phnum = si->phnum;
|
||||
int relocating_linker = (si->flags & FLAG_LINKER) != 0;
|
||||
soinfo **needed, **pneeded;
|
||||
size_t dynamic_count;
|
||||
|
||||
/* We can't debug anything until the linker is relocated */
|
||||
if (!relocating_linker) {
|
||||
|
@ -1434,7 +1423,8 @@ static int soinfo_link_image(soinfo *si)
|
|||
}
|
||||
|
||||
/* Extract dynamic section */
|
||||
si->dynamic = phdr_table_get_dynamic_section(phdr, phnum, base);
|
||||
phdr_table_get_dynamic_section(phdr, phnum, base, &si->dynamic,
|
||||
&dynamic_count);
|
||||
if (si->dynamic == NULL) {
|
||||
if (!relocating_linker) {
|
||||
DL_ERR("missing PT_DYNAMIC?!");
|
||||
|
@ -1607,6 +1597,9 @@ static int soinfo_link_image(soinfo *si)
|
|||
}
|
||||
}
|
||||
|
||||
/* dynamic_count is an upper bound for the number of needed libs */
|
||||
pneeded = needed = (soinfo**) alloca((1 + dynamic_count) * sizeof(soinfo*));
|
||||
|
||||
for(d = si->dynamic; *d; d += 2) {
|
||||
if(d[0] == DT_NEEDED){
|
||||
DEBUG("%5d %s needs %s\n", pid, si->name, si->strtab + d[1]);
|
||||
|
@ -1617,17 +1610,11 @@ static int soinfo_link_image(soinfo *si)
|
|||
si->strtab + d[1], si->name, tmp_err_buf);
|
||||
goto fail;
|
||||
}
|
||||
/* Save the soinfo of the loaded DT_NEEDED library in the payload
|
||||
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 symbol
|
||||
with dlsym().
|
||||
*/
|
||||
d[1] = (unsigned)lsi;
|
||||
*pneeded++ = lsi;
|
||||
lsi->refcount++;
|
||||
}
|
||||
}
|
||||
*pneeded = NULL;
|
||||
|
||||
if (si->has_text_relocations) {
|
||||
/* Unprotect the segments, i.e. make them writable, to allow
|
||||
|
@ -1644,17 +1631,17 @@ static int soinfo_link_image(soinfo *si)
|
|||
|
||||
if(si->plt_rel) {
|
||||
DEBUG("[ %5d relocating %s plt ]\n", pid, si->name );
|
||||
if(soinfo_relocate(si, si->plt_rel, si->plt_rel_count))
|
||||
if(soinfo_relocate(si, si->plt_rel, si->plt_rel_count, needed))
|
||||
goto fail;
|
||||
}
|
||||
if(si->rel) {
|
||||
DEBUG("[ %5d relocating %s ]\n", pid, si->name );
|
||||
if(soinfo_relocate(si, si->rel, si->rel_count))
|
||||
if(soinfo_relocate(si, si->rel, si->rel_count, needed))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifdef ANDROID_MIPS_LINKER
|
||||
if(mips_relocate_got(si)) {
|
||||
if(mips_relocate_got(si, needed)) {
|
||||
goto fail;
|
||||
}
|
||||
#endif
|
||||
|
@ -1849,8 +1836,8 @@ sanitize:
|
|||
Elf32_Ehdr *elf_hdr = (Elf32_Ehdr *) linker_base;
|
||||
Elf32_Phdr *phdr =
|
||||
(Elf32_Phdr *)((unsigned char *) linker_base + elf_hdr->e_phoff);
|
||||
linker_soinfo.dynamic =
|
||||
phdr_table_get_dynamic_section(phdr, elf_hdr->e_phnum, linker_base);
|
||||
phdr_table_get_dynamic_section(phdr, elf_hdr->e_phnum, linker_base,
|
||||
&linker_soinfo.dynamic, NULL);
|
||||
insert_soinfo_into_debug_map(&linker_soinfo);
|
||||
|
||||
/* extract information passed from the kernel */
|
||||
|
|
|
@ -468,11 +468,9 @@ _phdr_table_set_gnu_relro_prot(const Elf32_Phdr* phdr_table,
|
|||
* specified by one or more PT_GNU_RELRO segments. This must be always
|
||||
* performed after relocations.
|
||||
*
|
||||
* NOTE: One must call phdr_table_unprotect_gnu_relro() before calling
|
||||
* the library's destructors, in order to ensure that the .dynamic
|
||||
* section is writable (as well as the .data.relro section that
|
||||
* might contain the content of static constant C++ objects that
|
||||
* needs to be destroyed).
|
||||
* The areas typically covered are .got and .data.rel.ro, these are
|
||||
* read-only from the program's POV, but contain absolute addresses
|
||||
* that need to be relocated before use.
|
||||
*
|
||||
* Input:
|
||||
* phdr_table -> program header table
|
||||
|
@ -492,27 +490,6 @@ phdr_table_protect_gnu_relro(const Elf32_Phdr* phdr_table,
|
|||
PROT_READ);
|
||||
}
|
||||
|
||||
/* Un-apply GNU relro protection if specified by the program header.
|
||||
* See comment for phdr_table_protect_gnu_relro.
|
||||
*
|
||||
* Input:
|
||||
* phdr_table -> program header table
|
||||
* phdr_count -> number of entires in tables
|
||||
* load_bias -> load bias
|
||||
* Return:
|
||||
* 0 on error, -1 on failure (error code in errno).
|
||||
*/
|
||||
int
|
||||
phdr_table_unprotect_gnu_relro(const Elf32_Phdr* phdr_table,
|
||||
int phdr_count,
|
||||
Elf32_Addr load_bias)
|
||||
{
|
||||
return _phdr_table_set_gnu_relro_prot(phdr_table,
|
||||
phdr_count,
|
||||
load_bias,
|
||||
PROT_READ|PROT_WRITE);
|
||||
}
|
||||
|
||||
#ifdef ANDROID_ARM_LINKER
|
||||
|
||||
# ifndef PT_ARM_EXIDX
|
||||
|
@ -556,30 +533,44 @@ phdr_table_get_arm_exidx(const Elf32_Phdr* phdr_table,
|
|||
}
|
||||
#endif /* ANDROID_ARM_LINKER */
|
||||
|
||||
/* Return the address of the ELF file's .dynamic section in memory,
|
||||
/* Return the address and size of the ELF file's .dynamic section in memory,
|
||||
* or NULL if missing.
|
||||
*
|
||||
* Input:
|
||||
* phdr_table -> program header table
|
||||
* phdr_count -> number of entires in tables
|
||||
* load_bias -> load bias
|
||||
* Output:
|
||||
* dynamic -> address of table in memory (NULL on failure).
|
||||
* dynamic_count -> number of items in table (0 on failure).
|
||||
* Return:
|
||||
* 0 on error, -1 on failure (_no_ error code in errno)
|
||||
* void
|
||||
*/
|
||||
Elf32_Addr*
|
||||
void
|
||||
phdr_table_get_dynamic_section(const Elf32_Phdr* phdr_table,
|
||||
int phdr_count,
|
||||
Elf32_Addr load_bias)
|
||||
Elf32_Addr load_bias,
|
||||
Elf32_Addr** dynamic,
|
||||
size_t* dynamic_count)
|
||||
{
|
||||
const Elf32_Phdr* phdr = phdr_table;
|
||||
const Elf32_Phdr* phdr_limit = phdr + phdr_count;
|
||||
|
||||
for (phdr = phdr_table; phdr < phdr_limit; phdr++) {
|
||||
if (phdr->p_type == PT_DYNAMIC) {
|
||||
return (Elf32_Addr*)(load_bias + phdr->p_vaddr);
|
||||
if (phdr->p_type != PT_DYNAMIC) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*dynamic = (Elf32_Addr*)(load_bias + phdr->p_vaddr);
|
||||
if (dynamic_count) {
|
||||
*dynamic_count = (unsigned)(phdr->p_memsz / 8);
|
||||
}
|
||||
return;
|
||||
}
|
||||
*dynamic = NULL;
|
||||
if (dynamic_count) {
|
||||
*dynamic_count = 0;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Return the address of the program header table as it appears in the loaded
|
||||
|
|
|
@ -86,11 +86,6 @@ phdr_table_protect_gnu_relro(const Elf32_Phdr* phdr_table,
|
|||
int phdr_count,
|
||||
Elf32_Addr load_bias);
|
||||
|
||||
int
|
||||
phdr_table_unprotect_gnu_relro(const Elf32_Phdr* phdr_table,
|
||||
int phdr_count,
|
||||
Elf32_Addr load_bias);
|
||||
|
||||
const Elf32_Phdr*
|
||||
phdr_table_get_loaded_phdr(const Elf32_Phdr* phdr_table,
|
||||
int phdr_count,
|
||||
|
@ -105,10 +100,12 @@ phdr_table_get_arm_exidx(const Elf32_Phdr* phdr_table,
|
|||
unsigned* arm_exidix_count);
|
||||
#endif
|
||||
|
||||
Elf32_Addr*
|
||||
void
|
||||
phdr_table_get_dynamic_section(const Elf32_Phdr* phdr_table,
|
||||
int phdr_count,
|
||||
Elf32_Addr load_bias);
|
||||
Elf32_Addr load_bias,
|
||||
Elf32_Addr** dynamic,
|
||||
size_t* dynamic_count);
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue