rework ldso handling of global symbol table for consistency

when loading libraries with dlopen, the caller can request that the
library's symbols become part of the global symbol table, or that they
only be used for resolving relocations in the loaded library and its
dependencies. in the latter case, a subsequent dlopen of the same
library can upgrade it to global status.

previously, if a library was upgraded from local to global mode, its
symbols entered the symbol lookup search order at the point where the
library was originally loaded. this means that a new call to dlopen
could change the value of a symbol that already had a visible
definition, an inconsistency which applications could observe.

POSIX is unclear whether this should happen or whether it's permitted
to happen, but the resolution of Austin Group issue #982 made it
formally unspecified.

with this patch, a library whose mode is upgraded from local to global
enters the symbol lookup order at the point where it was made global,
so that symbol resolution before and after the upgrade are consistent.

in order to implement this change, the per-dso global flag is replaced
with a separate set of linked-list pointers for participation in the
global symbol table. this permits the order of dso objects for symbol
resolution to differ from the order used for iteration of all loaded
libraries. it also improves performance of find_sym, by avoiding a
branch per iteration and skipping, and especially in the case where
many non-global libraries have been loaded, by allowing the loop to
skip over them entirely. logic for temporarily adding non-global
libraries to the symbol table for relocation purposes is also mildly
simplified.
This commit is contained in:
Rich Felker 2017-03-12 21:03:05 -04:00
parent c9783e4d32
commit 4ff234f6cb

View File

@ -58,11 +58,11 @@ struct dso {
uint32_t *ghashtab;
int16_t *versym;
char *strings;
struct dso *syms_next;
unsigned char *map;
size_t map_len;
dev_t dev;
ino_t ino;
signed char global;
char relocated;
char constructed;
char kernel_mapped;
@ -113,7 +113,7 @@ static struct builtin_tls {
static size_t *saved_addends, *apply_addends_to;
static struct dso ldso;
static struct dso *head, *tail, *fini_head;
static struct dso *head, *tail, *fini_head, *syms_tail;
static char *env_path, *sys_path;
static unsigned long long gencnt;
static int runtime;
@ -261,9 +261,8 @@ static struct symdef find_sym(struct dso *dso, const char *s, int need_def)
uint32_t h = 0, gh, gho, *ght;
size_t ghm = 0;
struct symdef def = {0};
for (; dso; dso=dso->next) {
for (; dso; dso=dso->syms_next) {
Sym *sym;
if (!dso->global) continue;
if ((ght = dso->ghashtab)) {
if (!ghm) {
gh = gnu_hash(s);
@ -329,7 +328,7 @@ static void do_relocs(struct dso *dso, size_t *rel, size_t rel_size, size_t stri
if (sym_index) {
sym = syms + sym_index;
name = strings + sym->st_name;
ctx = type==REL_COPY ? head->next : head;
ctx = type==REL_COPY ? head->syms_next : head;
def = (sym->st_info&0xf) == STT_SECTION
? (struct symdef){ .dso = dso, .sym = sym }
: find_sym(ctx, name, type==REL_PLT);
@ -932,7 +931,7 @@ static struct dso *load_library(const char *name, struct dso *needed_by)
if (!ldso.prev) {
tail->next = &ldso;
ldso.prev = tail;
tail = ldso.next ? ldso.next : &ldso;
tail = &ldso;
}
return &ldso;
}
@ -1113,9 +1112,24 @@ static void load_preload(char *s)
}
}
static void make_global(struct dso *p)
static void add_syms(struct dso *p)
{
for (; p; p=p->next) p->global = 1;
if (!p->syms_next && syms_tail != p) {
syms_tail->syms_next = p;
syms_tail = p;
}
}
static void revert_syms(struct dso *old_tail)
{
struct dso *p, *next;
/* Chop off the tail of the list of dsos that participate in
* the global symbol table, reverting them to RTLD_LOCAL. */
for (p=old_tail; p; p=next) {
next = p->syms_next;
p->syms_next = 0;
}
syms_tail = old_tail;
}
static void do_mips_relocs(struct dso *p, size_t *got)
@ -1344,7 +1358,6 @@ void __dls2(unsigned char *base, size_t *sp)
}
Ehdr *ehdr = (void *)ldso.base;
ldso.name = ldso.shortname = "libc.so";
ldso.global = 1;
ldso.phnum = ehdr->e_phnum;
ldso.phdr = laddr(&ldso, ehdr->e_phoff);
ldso.phentsize = ehdr->e_phentsize;
@ -1532,7 +1545,6 @@ _Noreturn void __dls3(size_t *sp)
#endif
tls_align = MAXP2(tls_align, app.tls.align);
}
app.global = 1;
decode_dyn(&app);
if (DL_FDPIC) {
makefuncdescs(&app);
@ -1547,7 +1559,21 @@ _Noreturn void __dls3(size_t *sp)
argv[-3] = (void *)app.loadmap;
}
/* Attach to vdso, if provided by the kernel */
/* Initial dso chain consists only of the app. */
head = tail = syms_tail = &app;
/* Donate unused parts of app and library mapping to malloc */
reclaim_gaps(&app);
reclaim_gaps(&ldso);
/* Load preload/needed libraries, add symbols to global namespace. */
if (env_preload) load_preload(env_preload);
load_deps(&app);
for (struct dso *p=head; p; p=p->next)
add_syms(p);
/* Attach to vdso, if provided by the kernel, last so that it does
* not become part of the global namespace. */
if (search_vec(auxv, &vdso_base, AT_SYSINFO_EHDR) && vdso_base) {
Ehdr *ehdr = (void *)vdso_base;
Phdr *phdr = vdso.phdr = (void *)(vdso_base + ehdr->e_phoff);
@ -1561,26 +1587,13 @@ _Noreturn void __dls3(size_t *sp)
}
vdso.name = "";
vdso.shortname = "linux-gate.so.1";
vdso.global = 1;
vdso.relocated = 1;
decode_dyn(&vdso);
vdso.prev = &ldso;
ldso.next = &vdso;
vdso.prev = tail;
tail->next = &vdso;
tail = &vdso;
}
/* Initial dso chain consists only of the app. */
head = tail = &app;
/* Donate unused parts of app and library mapping to malloc */
reclaim_gaps(&app);
reclaim_gaps(&ldso);
/* Load preload/needed libraries, add their symbols to the global
* namespace, and perform all remaining relocations. */
if (env_preload) load_preload(env_preload);
load_deps(&app);
make_global(&app);
for (i=0; app.dynv[i]; i+=2) {
if (!DT_DEBUG_INDIRECT && app.dynv[i]==DT_DEBUG)
app.dynv[i+1] = (size_t)&debug;
@ -1641,7 +1654,7 @@ _Noreturn void __dls3(size_t *sp)
void *dlopen(const char *file, int mode)
{
struct dso *volatile p, *orig_tail, *next;
struct dso *volatile p, *orig_tail, *orig_syms_tail, *next;
struct tls_module *orig_tls_tail;
size_t orig_tls_cnt, orig_tls_offset, orig_tls_align;
size_t i;
@ -1659,15 +1672,14 @@ void *dlopen(const char *file, int mode)
orig_tls_cnt = tls_cnt;
orig_tls_offset = tls_offset;
orig_tls_align = tls_align;
orig_syms_tail = syms_tail;
orig_tail = tail;
noload = mode & RTLD_NOLOAD;
rtld_fail = &jb;
if (setjmp(*rtld_fail)) {
/* Clean up anything new that was (partially) loaded */
if (p && p->deps) for (i=0; p->deps[i]; i++)
if (p->deps[i]->global < 0)
p->deps[i]->global = 0;
revert_syms(orig_syms_tail);
for (p=orig_tail->next; p; p=next) {
next = p->next;
while (p->td_index) {
@ -1703,24 +1715,21 @@ void *dlopen(const char *file, int mode)
}
/* First load handling */
if (!p->deps) {
if (!p->relocated) {
load_deps(p);
/* Make new symbols global, at least temporarily, so we can do
* relocations. If not RTLD_GLOBAL, this is reverted below. */
add_syms(p);
if (p->deps) for (i=0; p->deps[i]; i++)
if (!p->deps[i]->global)
p->deps[i]->global = -1;
if (!p->global) p->global = -1;
add_syms(p->deps[i]);
reloc_all(p);
if (p->deps) for (i=0; p->deps[i]; i++)
if (p->deps[i]->global < 0)
p->deps[i]->global = 0;
if (p->global < 0) p->global = 0;
}
if (mode & RTLD_GLOBAL) {
if (p->deps) for (i=0; p->deps[i]; i++)
p->deps[i]->global = 1;
p->global = 1;
}
/* If RTLD_GLOBAL was not specified, undo any new additions
* to the global symbol table. This is a nop if the library was
* previously loaded and already global. */
if (!(mode & RTLD_GLOBAL))
revert_syms(orig_syms_tail);
update_tls_size();
_dl_debug_state();