diff options
author | kan <kan@FreeBSD.org> | 2005-12-18 19:43:33 +0000 |
---|---|---|
committer | kan <kan@FreeBSD.org> | 2005-12-18 19:43:33 +0000 |
commit | 8d6f05d58bc7dda5a91170bff51023509aa82558 (patch) | |
tree | 0d48e9fa3e1a8d20651242fc849efd6ba9f69557 /libexec/rtld-elf | |
parent | 116fa05e6020161c2d69796fd827231ba57cb838 (diff) | |
download | FreeBSD-src-8d6f05d58bc7dda5a91170bff51023509aa82558.zip FreeBSD-src-8d6f05d58bc7dda5a91170bff51023509aa82558.tar.gz |
Implement ELF symbol versioning using GNU semantics. This code aims
to be compatible with symbol versioning support as implemented by
GNU libc and documented by http://people.redhat.com/~drepper/symbol-versioning
and LSB 3.0.
Implement dlvsym() function to allow lookups for a specific version of
a given symbol.
Diffstat (limited to 'libexec/rtld-elf')
-rw-r--r-- | libexec/rtld-elf/alpha/reloc.c | 4 | ||||
-rw-r--r-- | libexec/rtld-elf/amd64/reloc.c | 4 | ||||
-rw-r--r-- | libexec/rtld-elf/arm/reloc.c | 4 | ||||
-rw-r--r-- | libexec/rtld-elf/i386/reloc.c | 4 | ||||
-rw-r--r-- | libexec/rtld-elf/map_object.c | 10 | ||||
-rw-r--r-- | libexec/rtld-elf/powerpc/reloc.c | 4 | ||||
-rw-r--r-- | libexec/rtld-elf/rtld.c | 610 | ||||
-rw-r--r-- | libexec/rtld-elf/rtld.h | 37 | ||||
-rw-r--r-- | libexec/rtld-elf/sparc64/reloc.c | 4 |
9 files changed, 548 insertions, 133 deletions
diff --git a/libexec/rtld-elf/alpha/reloc.c b/libexec/rtld-elf/alpha/reloc.c index e1ed135..364b59a 100644 --- a/libexec/rtld-elf/alpha/reloc.c +++ b/libexec/rtld-elf/alpha/reloc.c @@ -417,6 +417,7 @@ do_copy_relocation(Obj_Entry *dstobj, const Elf_Rela *rela) size_t size; const void *srcaddr; const Elf_Sym *srcsym; + const Ver_Entry *ve; Obj_Entry *srcobj; dstaddr = (void *) (dstobj->relocbase + rela->r_offset); @@ -424,9 +425,10 @@ do_copy_relocation(Obj_Entry *dstobj, const Elf_Rela *rela) name = dstobj->strtab + dstsym->st_name; hash = elf_hash(name); size = dstsym->st_size; + ve = fetch_ventry(dstobj, ELF_R_SYM(rela->r_info)); for (srcobj = dstobj->next; srcobj != NULL; srcobj = srcobj->next) - if ((srcsym = symlook_obj(name, hash, srcobj, false)) != NULL) + if ((srcsym = symlook_obj(name, hash, srcobj, ve, 0)) != NULL) break; if (srcobj == NULL) { diff --git a/libexec/rtld-elf/amd64/reloc.c b/libexec/rtld-elf/amd64/reloc.c index 72977d6..8f25278 100644 --- a/libexec/rtld-elf/amd64/reloc.c +++ b/libexec/rtld-elf/amd64/reloc.c @@ -74,15 +74,17 @@ do_copy_relocations(Obj_Entry *dstobj) const void *srcaddr; const Elf_Sym *srcsym; Obj_Entry *srcobj; + const Ver_Entry *ve; dstaddr = (void *) (dstobj->relocbase + rela->r_offset); dstsym = dstobj->symtab + ELF_R_SYM(rela->r_info); name = dstobj->strtab + dstsym->st_name; hash = elf_hash(name); size = dstsym->st_size; + ve = fetch_ventry(dstobj, ELF_R_SYM(rela->r_info)); for (srcobj = dstobj->next; srcobj != NULL; srcobj = srcobj->next) - if ((srcsym = symlook_obj(name, hash, srcobj, false)) != NULL) + if ((srcsym = symlook_obj(name, hash, srcobj, ve, 0)) != NULL) break; if (srcobj == NULL) { diff --git a/libexec/rtld-elf/arm/reloc.c b/libexec/rtld-elf/arm/reloc.c index ff9d195..e383892 100644 --- a/libexec/rtld-elf/arm/reloc.c +++ b/libexec/rtld-elf/arm/reloc.c @@ -41,15 +41,17 @@ do_copy_relocations(Obj_Entry *dstobj) const void *srcaddr; const Elf_Sym *srcsym; Obj_Entry *srcobj; + const Ver_Entry *ve; dstaddr = (void *) (dstobj->relocbase + rel->r_offset); dstsym = dstobj->symtab + ELF_R_SYM(rel->r_info); name = dstobj->strtab + dstsym->st_name; hash = elf_hash(name); size = dstsym->st_size; + ve = fetch_ventry(dstobj, ELF_R_SYM(rel->r_info)); for (srcobj = dstobj->next; srcobj != NULL; srcobj = srcobj->next) - if ((srcsym = symlook_obj(name, hash, srcobj, false)) != NULL) + if ((srcsym = symlook_obj(name, hash, srcobj, ve, 0)) != NULL) break; if (srcobj == NULL) { diff --git a/libexec/rtld-elf/i386/reloc.c b/libexec/rtld-elf/i386/reloc.c index db2fcf2..809f16e 100644 --- a/libexec/rtld-elf/i386/reloc.c +++ b/libexec/rtld-elf/i386/reloc.c @@ -74,6 +74,7 @@ do_copy_relocations(Obj_Entry *dstobj) size_t size; const void *srcaddr; const Elf_Sym *srcsym; + const Ver_Entry *ve; Obj_Entry *srcobj; dstaddr = (void *) (dstobj->relocbase + rel->r_offset); @@ -81,9 +82,10 @@ do_copy_relocations(Obj_Entry *dstobj) name = dstobj->strtab + dstsym->st_name; hash = elf_hash(name); size = dstsym->st_size; + ve = fetch_ventry(dstobj, ELF_R_SYM(rel->r_info)); for (srcobj = dstobj->next; srcobj != NULL; srcobj = srcobj->next) - if ((srcsym = symlook_obj(name, hash, srcobj, false)) != NULL) + if ((srcsym = symlook_obj(name, hash, srcobj, ve, 0)) != NULL) break; if (srcobj == NULL) { diff --git a/libexec/rtld-elf/map_object.c b/libexec/rtld-elf/map_object.c index 2b1ef24..333e08d 100644 --- a/libexec/rtld-elf/map_object.c +++ b/libexec/rtld-elf/map_object.c @@ -305,15 +305,19 @@ obj_free(Obj_Entry *obj) { Objlist_Entry *elm; - if (obj->tls_done) { + if (obj->tls_done) free_tls_offset(obj); - } free(obj->path); while (obj->needed != NULL) { Needed_Entry *needed = obj->needed; obj->needed = needed->next; free(needed); } + while (!STAILQ_EMPTY(&obj->names)) { + Name_Entry *entry = STAILQ_FIRST(&obj->names); + STAILQ_REMOVE_HEAD(&obj->names, link); + free(entry); + } while (!STAILQ_EMPTY(&obj->dldags)) { elm = STAILQ_FIRST(&obj->dldags); STAILQ_REMOVE_HEAD(&obj->dldags, link); @@ -324,6 +328,7 @@ obj_free(Obj_Entry *obj) STAILQ_REMOVE_HEAD(&obj->dagmembers, link); free(elm); } + free(obj->vertab); free(obj->origin_path); free(obj->priv); free(obj); @@ -337,6 +342,7 @@ obj_new(void) obj = CNEW(Obj_Entry); STAILQ_INIT(&obj->dldags); STAILQ_INIT(&obj->dagmembers); + STAILQ_INIT(&obj->names); return obj; } diff --git a/libexec/rtld-elf/powerpc/reloc.c b/libexec/rtld-elf/powerpc/reloc.c index b65e026..8841d7e 100644 --- a/libexec/rtld-elf/powerpc/reloc.c +++ b/libexec/rtld-elf/powerpc/reloc.c @@ -71,6 +71,7 @@ do_copy_relocations(Obj_Entry *dstobj) const void *srcaddr; const Elf_Sym *srcsym = NULL; Obj_Entry *srcobj; + const Ver_Entry *ve; if (ELF_R_TYPE(rela->r_info) != R_PPC_COPY) { continue; @@ -81,10 +82,11 @@ do_copy_relocations(Obj_Entry *dstobj) name = dstobj->strtab + dstsym->st_name; hash = elf_hash(name); size = dstsym->st_size; + ve = fetch_ventry(dstobj, ELF_R_SYM(rela->r_info)); for (srcobj = dstobj->next; srcobj != NULL; srcobj = srcobj->next) { - if ((srcsym = symlook_obj(name, hash, srcobj, false)) + if ((srcsym = symlook_obj(name, hash, srcobj, ve, 0)) != NULL) { break; } diff --git a/libexec/rtld-elf/rtld.c b/libexec/rtld-elf/rtld.c index ca538d5..e379004 100644 --- a/libexec/rtld-elf/rtld.c +++ b/libexec/rtld-elf/rtld.c @@ -80,10 +80,11 @@ typedef struct Struct_DoneList { * Function declarations. */ static const char *basename(const char *); -static void die(void); +static void die(void) __dead2; static void digest_dynamic(Obj_Entry *, int); static Obj_Entry *digest_phdr(const Elf_Phdr *, int, caddr_t, const char *); static Obj_Entry *dlcheck(void *); +static Obj_Entry *do_load_object(int, const char *, char *, struct stat *); static int do_search_info(const Obj_Entry *obj, int, struct dl_serinfo *); static bool donelist_check(DoneList *, const Obj_Entry *); static void errmsg_restore(char *); @@ -102,7 +103,7 @@ static void linkmap_add(Obj_Entry *); static void linkmap_delete(Obj_Entry *); static int load_needed_objects(Obj_Entry *); static int load_preload_objects(void); -static Obj_Entry *load_object(char *); +static Obj_Entry *load_object(const char *, const Obj_Entry *); static Obj_Entry *obj_from_addr(const void *); static void objlist_call_fini(Objlist *); static void objlist_call_init(Objlist *); @@ -118,17 +119,21 @@ static int relocate_objects(Obj_Entry *, bool, Obj_Entry *); static int rtld_dirname(const char *, char *); static void rtld_exit(void); static char *search_library_path(const char *, const char *); -static const void **get_program_var_addr(const char *name); +static const void **get_program_var_addr(const char *); static void set_program_var(const char *, const void *); -static const Elf_Sym *symlook_default(const char *, unsigned long hash, - const Obj_Entry *refobj, const Obj_Entry **defobj_out, bool in_plt); -static const Elf_Sym *symlook_list(const char *, unsigned long, - Objlist *, const Obj_Entry **, bool in_plt, DoneList *); +static const Elf_Sym *symlook_default(const char *, unsigned long, + const Obj_Entry *, const Obj_Entry **, const Ver_Entry *, int); +static const Elf_Sym *symlook_list(const char *, unsigned long, Objlist *, + const Obj_Entry **, const Ver_Entry *, int flags, DoneList *); static void trace_loaded_objects(Obj_Entry *obj); static void unlink_object(Obj_Entry *); static void unload_object(Obj_Entry *); static void unref_dag(Obj_Entry *); static void ref_dag(Obj_Entry *); +static int rtld_verify_versions(const Objlist *); +static int rtld_verify_object_versions(Obj_Entry *); +static void object_add_name(Obj_Entry *, const char *); +static int object_match_name(const Obj_Entry *, const char *); void r_debug_state(struct r_debug*, struct link_map*); @@ -182,6 +187,7 @@ static func_ptr_type exports[] = { (func_ptr_type) &dlerror, (func_ptr_type) &dlopen, (func_ptr_type) &dlsym, + (func_ptr_type) &dlvsym, (func_ptr_type) &dladdr, (func_ptr_type) &dllockinit, (func_ptr_type) &dlinfo, @@ -388,6 +394,10 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) obj->refcount++; } + dbg("checking for required versions"); + if (rtld_verify_versions(&list_main) == -1 && !ld_tracing) + die(); + if (ld_tracing) { /* We're done */ trace_loaded_objects(obj_main); exit(0); @@ -555,6 +565,7 @@ digest_dynamic(Obj_Entry *obj, int early) const Elf_Dyn *dynp; Needed_Entry **needed_tail = &obj->needed; const Elf_Dyn *dyn_rpath = NULL; + const Elf_Dyn *dyn_soname = NULL; int plttype = DT_REL; obj->bind_now = false; @@ -616,6 +627,29 @@ digest_dynamic(Obj_Entry *obj, int early) obj->strsize = dynp->d_un.d_val; break; + case DT_VERNEED: + obj->verneed = (const Elf_Verneed *) (obj->relocbase + + dynp->d_un.d_val); + break; + + case DT_VERNEEDNUM: + obj->verneednum = dynp->d_un.d_val; + break; + + case DT_VERDEF: + obj->verdef = (const Elf_Verdef *) (obj->relocbase + + dynp->d_un.d_val); + break; + + case DT_VERDEFNUM: + obj->verdefnum = dynp->d_un.d_val; + break; + + case DT_VERSYM: + obj->versyms = (const Elf_Versym *)(obj->relocbase + + dynp->d_un.d_val); + break; + case DT_HASH: { const Elf_Hashelt *hashtab = (const Elf_Hashelt *) @@ -661,7 +695,7 @@ digest_dynamic(Obj_Entry *obj, int early) break; case DT_SONAME: - /* Not used by the dynamic linker. */ + dyn_soname = dynp; break; case DT_INIT: @@ -715,6 +749,9 @@ digest_dynamic(Obj_Entry *obj, int early) if (dyn_rpath != NULL) obj->rpath = obj->strtab + dyn_rpath->d_un.d_val; + + if (dyn_soname != NULL) + object_add_name(obj, obj->strtab + dyn_soname->d_un.d_val); } /* @@ -902,11 +939,12 @@ find_library(const char *xname, const Obj_Entry *refobj) */ const Elf_Sym * find_symdef(unsigned long symnum, const Obj_Entry *refobj, - const Obj_Entry **defobj_out, bool in_plt, SymCache *cache) + const Obj_Entry **defobj_out, int flags, SymCache *cache) { const Elf_Sym *ref; const Elf_Sym *def; const Obj_Entry *defobj; + const Ver_Entry *ventry; const char *name; unsigned long hash; @@ -938,8 +976,9 @@ find_symdef(unsigned long symnum, const Obj_Entry *refobj, _rtld_error("%s: Bogus symbol table entry %lu", refobj->path, symnum); } + ventry = fetch_ventry(refobj, symnum); hash = elf_hash(name); - def = symlook_default(name, hash, refobj, &defobj, in_plt); + def = symlook_default(name, hash, refobj, &defobj, ventry, flags); } else { def = ref; defobj = refobj; @@ -1164,18 +1203,9 @@ load_needed_objects(Obj_Entry *first) Needed_Entry *needed; for (needed = obj->needed; needed != NULL; needed = needed->next) { - const char *name = obj->strtab + needed->name; - char *path = find_library(name, obj); - - needed->obj = NULL; - if (path == NULL && !ld_tracing) + needed->obj = load_object(obj->strtab + needed->name, obj); + if (needed->obj == NULL && !ld_tracing) return -1; - - if (path) { - needed->obj = load_object(path); - if (needed->obj == NULL && !ld_tracing) - return -1; /* XXX - cleanup */ - } } } @@ -1194,14 +1224,11 @@ load_preload_objects(void) p += strspn(p, delim); while (*p != '\0') { size_t len = strcspn(p, delim); - char *path; char savech; savech = p[len]; p[len] = '\0'; - if ((path = find_library(p, NULL)) == NULL) - return -1; - if (load_object(path) == NULL) + if (load_object(p, NULL) == NULL) return -1; /* XXX - cleanup */ p[len] = savech; p += len; @@ -1211,24 +1238,26 @@ load_preload_objects(void) } /* - * Load a shared object into memory, if it is not already loaded. The - * argument must be a string allocated on the heap. This function assumes - * responsibility for freeing it when necessary. + * Load a shared object into memory, if it is not already loaded. * * Returns a pointer to the Obj_Entry for the object. Returns NULL * on failure. */ static Obj_Entry * -load_object(char *path) +load_object(const char *name, const Obj_Entry *refobj) { Obj_Entry *obj; int fd = -1; struct stat sb; - struct statfs fs; + char *path; for (obj = obj_list->next; obj != NULL; obj = obj->next) - if (strcmp(obj->path, path) == 0) - break; + if (object_match_name(obj, name)) + return obj; + + path = find_library(name, refobj); + if (path == NULL) + return NULL; /* * If we didn't find a match by pathname, open the file and check @@ -1238,63 +1267,77 @@ load_object(char *path) * To avoid a race, we open the file and use fstat() rather than * using stat(). */ - if (obj == NULL) { - if ((fd = open(path, O_RDONLY)) == -1) { - _rtld_error("Cannot open \"%s\"", path); - return NULL; - } - if (fstat(fd, &sb) == -1) { - _rtld_error("Cannot fstat \"%s\"", path); + if ((fd = open(path, O_RDONLY)) == -1) { + _rtld_error("Cannot open \"%s\"", path); + free(path); + return NULL; + } + if (fstat(fd, &sb) == -1) { + _rtld_error("Cannot fstat \"%s\"", path); + close(fd); + free(path); + return NULL; + } + for (obj = obj_list->next; obj != NULL; obj = obj->next) { + if (obj->ino == sb.st_ino && obj->dev == sb.st_dev) { close(fd); - return NULL; - } - for (obj = obj_list->next; obj != NULL; obj = obj->next) { - if (obj->ino == sb.st_ino && obj->dev == sb.st_dev) { - close(fd); - break; - } + break; } } + if (obj != NULL) { + object_add_name(obj, name); + free(path); + close(fd); + return obj; + } - if (obj == NULL) { /* First use of this object, so we must map it in */ - /* - * but first, make sure that environment variables haven't been - * used to circumvent the noexec flag on a filesystem. - */ - if (dangerous_ld_env) { - if (fstatfs(fd, &fs) != 0) { - _rtld_error("Cannot fstatfs \"%s\"", path); - close(fd); - return NULL; - } - if (fs.f_flags & MNT_NOEXEC) { - _rtld_error("Cannot execute objects on %s\n", fs.f_mntonname); - close(fd); + /* First use of this object, so we must map it in */ + obj = do_load_object(fd, name, path, &sb); + if (obj == NULL) + free(path); + close(fd); + + return obj; +} + +static Obj_Entry * +do_load_object(int fd, const char *name, char *path, struct stat *sbp) +{ + Obj_Entry *obj; + struct statfs fs; + + /* + * but first, make sure that environment variables haven't been + * used to circumvent the noexec flag on a filesystem. + */ + if (dangerous_ld_env) { + if (fstatfs(fd, &fs) != 0) { + _rtld_error("Cannot fstatfs \"%s\"", path); return NULL; - } } - dbg("loading \"%s\"", path); - obj = map_object(fd, path, &sb); - close(fd); - if (obj == NULL) { - free(path); + if (fs.f_flags & MNT_NOEXEC) { + _rtld_error("Cannot execute objects on %s\n", fs.f_mntonname); return NULL; } + } + dbg("loading \"%s\"", path); + obj = map_object(fd, path, sbp); + if (obj == NULL) + return NULL; - obj->path = path; - digest_dynamic(obj, 0); + object_add_name(obj, name); + obj->path = path; + digest_dynamic(obj, 0); - *obj_tail = obj; - obj_tail = &obj->next; - obj_count++; - linkmap_add(obj); /* for GDB & dlinfo() */ + *obj_tail = obj; + obj_tail = &obj->next; + obj_count++; + linkmap_add(obj); /* for GDB & dlinfo() */ - dbg(" %p .. %p: %s", obj->mapbase, - obj->mapbase + obj->mapsize - 1, obj->path); - if (obj->textrel) - dbg(" WARNING: %s has impure text", obj->path); - } else - free(path); + dbg(" %p .. %p: %s", obj->mapbase, + obj->mapbase + obj->mapsize - 1, obj->path); + if (obj->textrel) + dbg(" WARNING: %s has impure text", obj->path); return obj; } @@ -1709,9 +1752,7 @@ dlopen(const char *name, int mode) obj = obj_main; obj->refcount++; } else { - char *path = find_library(name, obj_main); - if (path != NULL) - obj = load_object(path); + obj = load_object(name, obj_main); } if (obj) { @@ -1723,6 +1764,8 @@ dlopen(const char *name, int mode) assert(*old_obj_tail == obj); result = load_needed_objects(obj); + if (result != -1) + result = rtld_verify_versions(&obj->dagmembers); if (result != -1 && ld_tracing) goto trace; @@ -1763,8 +1806,9 @@ trace: exit(0); } -void * -dlsym(void *handle, const char *name) +static void * +do_dlsym(void *handle, const char *name, void *retaddr, const Ver_Entry *ve, + int flags) { const Obj_Entry *obj; unsigned long hash; @@ -1775,34 +1819,33 @@ dlsym(void *handle, const char *name) hash = elf_hash(name); def = NULL; defobj = NULL; + flags |= SYMLOOK_IN_PLT; lockstate = rlock_acquire(rtld_bind_lock); if (handle == NULL || handle == RTLD_NEXT || handle == RTLD_DEFAULT || handle == RTLD_SELF) { - void *retaddr; - retaddr = __builtin_return_address(0); /* __GNUC__ only */ if ((obj = obj_from_addr(retaddr)) == NULL) { _rtld_error("Cannot determine caller's shared object"); rlock_release(rtld_bind_lock, lockstate); return NULL; } if (handle == NULL) { /* Just the caller's shared object. */ - def = symlook_obj(name, hash, obj, true); + def = symlook_obj(name, hash, obj, ve, flags); defobj = obj; } else if (handle == RTLD_NEXT || /* Objects after caller's */ handle == RTLD_SELF) { /* ... caller included */ if (handle == RTLD_NEXT) obj = obj->next; for (; obj != NULL; obj = obj->next) { - if ((def = symlook_obj(name, hash, obj, true)) != NULL) { + if ((def = symlook_obj(name, hash, obj, ve, flags)) != NULL) { defobj = obj; break; } } } else { assert(handle == RTLD_DEFAULT); - def = symlook_default(name, hash, obj, &defobj, true); + def = symlook_default(name, hash, obj, &defobj, ve, flags); } } else { if ((obj = dlcheck(handle)) == NULL) { @@ -1815,14 +1858,14 @@ dlsym(void *handle, const char *name) /* Search main program and all libraries loaded by it. */ donelist_init(&donelist); - def = symlook_list(name, hash, &list_main, &defobj, true, + def = symlook_list(name, hash, &list_main, &defobj, ve, flags, &donelist); } else { /* * XXX - This isn't correct. The search should include the whole * DAG rooted at the given object. */ - def = symlook_obj(name, hash, obj, true); + def = symlook_obj(name, hash, obj, ve, flags); defobj = obj; } } @@ -1849,6 +1892,26 @@ dlsym(void *handle, const char *name) return NULL; } +void * +dlsym(void *handle, const char *name) +{ + return do_dlsym(handle, name, __builtin_return_address(0), NULL, + SYMLOOK_DLSYM); +} + +void * +dlvsym(void *handle, const char *name, const char *version) +{ + Ver_Entry ventry; + + ventry.name = version; + ventry.file = NULL; + ventry.hash = elf_hash(version); + ventry.flags= 0; + return do_dlsym(handle, name, __builtin_return_address(0), &ventry, + SYMLOOK_DLSYM); +} + int dladdr(const void *addr, Dl_info *info) { @@ -2163,7 +2226,7 @@ get_program_var_addr(const char *name) for (obj = obj_main; obj != NULL; obj = obj->next) { const Elf_Sym *def; - if ((def = symlook_obj(name, hash, obj, false)) != NULL) { + if ((def = symlook_obj(name, hash, obj, NULL, 0)) != NULL) { const void **addr; addr = (const void **)(obj->relocbase + def->st_value); @@ -2196,8 +2259,8 @@ set_program_var(const char *name, const void *value) * defining object via the reference parameter DEFOBJ_OUT. */ static const Elf_Sym * -symlook_default(const char *name, unsigned long hash, - const Obj_Entry *refobj, const Obj_Entry **defobj_out, bool in_plt) +symlook_default(const char *name, unsigned long hash, const Obj_Entry *refobj, + const Obj_Entry **defobj_out, const Ver_Entry *ventry, int flags) { DoneList donelist; const Elf_Sym *def; @@ -2211,7 +2274,7 @@ symlook_default(const char *name, unsigned long hash, /* Look first in the referencing object if linked symbolically. */ if (refobj->symbolic && !donelist_check(&donelist, refobj)) { - symp = symlook_obj(name, hash, refobj, in_plt); + symp = symlook_obj(name, hash, refobj, ventry, flags); if (symp != NULL) { def = symp; defobj = refobj; @@ -2220,7 +2283,8 @@ symlook_default(const char *name, unsigned long hash, /* Search all objects loaded at program start up. */ if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) { - symp = symlook_list(name, hash, &list_main, &obj, in_plt, &donelist); + symp = symlook_list(name, hash, &list_main, &obj, ventry, flags, + &donelist); if (symp != NULL && (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) { def = symp; @@ -2232,8 +2296,8 @@ symlook_default(const char *name, unsigned long hash, STAILQ_FOREACH(elm, &list_global, link) { if (def != NULL && ELF_ST_BIND(def->st_info) != STB_WEAK) break; - symp = symlook_list(name, hash, &elm->obj->dagmembers, &obj, in_plt, - &donelist); + symp = symlook_list(name, hash, &elm->obj->dagmembers, &obj, ventry, + flags, &donelist); if (symp != NULL && (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) { def = symp; @@ -2245,8 +2309,8 @@ symlook_default(const char *name, unsigned long hash, STAILQ_FOREACH(elm, &refobj->dldags, link) { if (def != NULL && ELF_ST_BIND(def->st_info) != STB_WEAK) break; - symp = symlook_list(name, hash, &elm->obj->dagmembers, &obj, in_plt, - &donelist); + symp = symlook_list(name, hash, &elm->obj->dagmembers, &obj, ventry, + flags, &donelist); if (symp != NULL && (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) { def = symp; @@ -2261,7 +2325,7 @@ symlook_default(const char *name, unsigned long hash, * in the "exports" array can be resolved from the dynamic linker. */ if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) { - symp = symlook_obj(name, hash, &obj_rtld, in_plt); + symp = symlook_obj(name, hash, &obj_rtld, ventry, flags); if (symp != NULL && is_exported(symp)) { def = symp; defobj = &obj_rtld; @@ -2275,7 +2339,8 @@ symlook_default(const char *name, unsigned long hash, static const Elf_Sym * symlook_list(const char *name, unsigned long hash, Objlist *objlist, - const Obj_Entry **defobj_out, bool in_plt, DoneList *dlp) + const Obj_Entry **defobj_out, const Ver_Entry *ventry, int flags, + DoneList *dlp) { const Elf_Sym *symp; const Elf_Sym *def; @@ -2287,7 +2352,7 @@ symlook_list(const char *name, unsigned long hash, Objlist *objlist, STAILQ_FOREACH(elm, objlist, link) { if (donelist_check(dlp, elm->obj)) continue; - if ((symp = symlook_obj(name, hash, elm->obj, in_plt)) != NULL) { + if ((symp = symlook_obj(name, hash, elm->obj, ventry, flags)) != NULL) { if (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK) { def = symp; defobj = elm->obj; @@ -2303,37 +2368,107 @@ symlook_list(const char *name, unsigned long hash, Objlist *objlist, /* * Search the symbol table of a single shared object for a symbol of - * the given name. Returns a pointer to the symbol, or NULL if no - * definition was found. + * the given name and version, if requested. Returns a pointer to the + * symbol, or NULL if no definition was found. * * The symbol's hash value is passed in for efficiency reasons; that * eliminates many recomputations of the hash value. */ const Elf_Sym * symlook_obj(const char *name, unsigned long hash, const Obj_Entry *obj, - bool in_plt) + const Ver_Entry *ventry, int flags) { - if (obj->buckets != NULL) { - unsigned long symnum = obj->buckets[hash % obj->nbuckets]; + unsigned long symnum; + const Elf_Sym *vsymp; + Elf_Versym verndx; + int vcount; + + if (obj->buckets == NULL) + return NULL; - while (symnum != STN_UNDEF) { - const Elf_Sym *symp; - const char *strp; + vsymp = NULL; + vcount = 0; + symnum = obj->buckets[hash % obj->nbuckets]; - if (symnum >= obj->nchains) + for (; symnum != STN_UNDEF; symnum = obj->chains[symnum]) { + const Elf_Sym *symp; + const char *strp; + + if (symnum >= obj->nchains) return NULL; /* Bad object */ - symp = obj->symtab + symnum; - strp = obj->strtab + symp->st_name; - if (name[0] == strp[0] && strcmp(name, strp) == 0) - return symp->st_shndx != SHN_UNDEF || - (!in_plt && symp->st_value != 0 && - ELF_ST_TYPE(symp->st_info) == STT_FUNC) ? symp : NULL; + symp = obj->symtab + symnum; + strp = obj->strtab + symp->st_name; + + switch (ELF_ST_TYPE(symp->st_info)) { + case STT_FUNC: + case STT_NOTYPE: + case STT_OBJECT: + if (symp->st_value == 0) + continue; + /* fallthrough */ + case STT_TLS: + if (symp->st_shndx != SHN_UNDEF || + ((flags & SYMLOOK_IN_PLT) == 0 && + ELF_ST_TYPE(symp->st_info) == STT_FUNC)) + break; + /* fallthrough */ + default: + continue; + } + if (name[0] != strp[0] || strcmp(name, strp) != 0) + continue; - symnum = obj->chains[symnum]; + if (ventry == NULL) { + if (obj->versyms != NULL) { + verndx = VER_NDX(obj->versyms[symnum]); + if (verndx > obj->vernum) { + _rtld_error("%s: symbol %s references wrong version %d", + obj->path, obj->strtab + symnum, verndx); + continue; + } + if (verndx >= VER_NDX_GIVEN) { + if (vsymp == NULL) + vsymp = symp; + vcount ++; + continue; + } + } + return symp; + } else { + if (obj->versyms == NULL) { + if (object_match_name(obj, ventry->name)) { + _rtld_error("%s: object %s should provide version %s for ", + "symbol %s", obj->path, ventry->name, + obj->strtab + symnum); + continue; + } + } else { + verndx = VER_NDX(obj->versyms[symnum]); + if (verndx > obj->vernum) { + _rtld_error("%s: symbol %s references wrong version %d", + obj->path, obj->strtab + symnum, verndx); + continue; + } + if (obj->vertab[verndx].hash != ventry->hash || + strcmp(obj->vertab[verndx].name, ventry->name)) { + /* + * Version does not match. Look if this is a default symbol + * and if it is not hidden. If default symbol (num < 2) + * is available, use it. Do not return symbol if we are + * called by dlvsym, because dlvsym looks for a specific + * version and default one is not what dlvsym wants. + */ + if ((flags & SYMLOOK_DLSYM) || + (obj->versyms[symnum] & VER_NDX_HIDDEN) || + (verndx >= VER_NDX_GIVEN)) + continue; + } + } + return symp; } } - return NULL; + return (vcount == 1) ? vsymp : NULL; } static void @@ -2476,7 +2611,7 @@ unlink_object(Obj_Entry *root) objlist_remove(&list_global, root); /* Remove the object from all objects' DAG lists. */ - STAILQ_FOREACH(elm, &root->dagmembers , link) { + STAILQ_FOREACH(elm, &root->dagmembers, link) { objlist_remove(&elm->obj->dldags, root); if (elm->obj != root) unlink_object(elm->obj); @@ -2489,7 +2624,7 @@ ref_dag(Obj_Entry *root) { Objlist_Entry *elm; - STAILQ_FOREACH(elm, &root->dagmembers , link) + STAILQ_FOREACH(elm, &root->dagmembers, link) elm->obj->refcount++; } @@ -2498,7 +2633,7 @@ unref_dag(Obj_Entry *root) { Objlist_Entry *elm; - STAILQ_FOREACH(elm, &root->dagmembers , link) + STAILQ_FOREACH(elm, &root->dagmembers, link) elm->obj->refcount--; } @@ -2840,3 +2975,234 @@ _rtld_free_tls(void *tcb, size_t tcbsize, size_t tcbalign) free_tls(tcb, tcbsize, tcbalign); wlock_release(rtld_bind_lock, lockstate); } + +static void +object_add_name(Obj_Entry *obj, const char *name) +{ + Name_Entry *entry; + size_t len; + + len = strlen(name); + entry = malloc(sizeof(Name_Entry) + len); + + if (entry != NULL) { + strcpy(entry->name, name); + STAILQ_INSERT_TAIL(&obj->names, entry, link); + } +} + +static int +object_match_name(const Obj_Entry *obj, const char *name) +{ + Name_Entry *entry; + + STAILQ_FOREACH(entry, &obj->names, link) { + if (strcmp(name, entry->name) == 0) + return (1); + } + return (0); +} + +static Obj_Entry * +locate_dependency(const Obj_Entry *obj, const char *name) +{ + const Objlist_Entry *entry; + const Needed_Entry *needed; + + STAILQ_FOREACH(entry, &list_main, link) { + if (object_match_name(entry->obj, name)) + return entry->obj; + } + + for (needed = obj->needed; needed != NULL; needed = needed->next) { + if (needed->obj == NULL) + continue; + if (object_match_name(needed->obj, name)) + return needed->obj; + } + _rtld_error("Unexpected inconsistency: %s not found in dependency list", + name); + die(); +} + +static int +check_object_provided_version(Obj_Entry *refobj, const Obj_Entry *depobj, + const Elf_Vernaux *vna) +{ + const Elf_Verdef *vd; + const char *vername; + + vername = refobj->strtab + vna->vna_name; + vd = depobj->verdef; + if (vd == NULL) { + _rtld_error("%s does not have version information, but %s requires it", + depobj->path, refobj->path); + *(char *)0 = 0; + return (-1); + } + for (;;) { + if (vd->vd_version != VER_DEF_CURRENT) { + _rtld_error("Unsupported version of Elf_Verdef entry in %s : %d", + depobj->path, vd->vd_version); + return (-1); + } + if (vna->vna_hash == vd->vd_hash) { + const Elf_Verdaux *aux = (const Elf_Verdaux *) + ((char *)vd + vd->vd_aux); + if (strcmp(vername, depobj->strtab + aux->vda_name) == 0) + return (0); + } + if (vd->vd_next == 0) + break; + vd = (const Elf_Verdef *) ((char *)vd + vd->vd_next); + } + if (vna->vna_flags & VER_FLG_WEAK) + return (0); + _rtld_error("Version %s required by %s can not be found in %s", vername, + refobj->path, depobj->path); + return (-1); +} + +static int +rtld_verify_object_versions(Obj_Entry *obj) +{ + const Elf_Verneed *vn; + const Elf_Verdef *vd; + const Elf_Verdaux *vda; + const Elf_Vernaux *vna; + const Obj_Entry *depobj; + int maxvernum, vernum; + + maxvernum = 0; + /* + * Walk over defined and required version records and figure out + * max index used by any of them. Do very basic sanity checking + * while there. + */ + vn = obj->verneed; + while (vn != NULL) { + if (vn->vn_version != VER_NEED_CURRENT) { + _rtld_error("Unsupported version of Elf_Verneed entry in %s: %d", + obj->path, vn->vn_version); + return (-1); + } + vna = (const Elf_Vernaux *) ((char *)vn + vn->vn_aux); + for (;;) { + vernum = VER_NEED_IDX(vna->vna_other); + if (vernum > maxvernum) + maxvernum = vernum; + if (vna->vna_next == 0) + break; + vna = (const Elf_Vernaux *) ((char *)vna + vna->vna_next); + } + if (vn->vn_next == 0) + break; + vn = (const Elf_Verneed *) ((char *)vn + vn->vn_next); + } + + vd = obj->verdef; + while (vd != NULL) { + if (vd->vd_version != VER_DEF_CURRENT) { + _rtld_error("Unsupported version of Elf_Verneed entry: %d", + vd->vd_version); + return (-1); + } + vernum = VER_DEF_IDX(vd->vd_ndx); + if (vernum > maxvernum) + maxvernum = vernum; + if (vd->vd_next == 0) + break; + vd = (const Elf_Verdef *) ((char *)vd + vd->vd_next); + } + + if (maxvernum == 0) + return (0); + + /* + * Store version information in array indexable by version index. + * Verify that object version requirements are satisfied along the + * way. + */ + obj->vernum = maxvernum + 1; + obj->vertab = calloc(obj->vernum, sizeof(Ver_Entry)); + + vd = obj->verdef; + while (vd != NULL) { + if ((vd->vd_flags & VER_FLG_BASE) == 0) { + vernum = VER_DEF_IDX(vd->vd_ndx); + assert(vernum <= maxvernum); + vda = (const Elf_Verdaux *)((char *)vd + vd->vd_aux); + obj->vertab[vernum].hash = vd->vd_hash; + obj->vertab[vernum].name = obj->strtab + vda->vda_name; + obj->vertab[vernum].file = NULL; + obj->vertab[vernum].flags = 0; + } + if (vd->vd_next == 0) + break; + vd = (const Elf_Verdef *) ((char *)vd + vd->vd_next); + } + + vn = obj->verneed; + while (vn != NULL) { + depobj = locate_dependency(obj, obj->strtab + vn->vn_file); + vna = (const Elf_Vernaux *) ((char *)vn + vn->vn_aux); + for (;;) { + if (check_object_provided_version(obj, depobj, vna)) + return (-1); + vernum = VER_NEED_IDX(vna->vna_other); + assert(vernum <= maxvernum); + obj->vertab[vernum].hash = vna->vna_hash; + obj->vertab[vernum].name = obj->strtab + vna->vna_name; + obj->vertab[vernum].file = obj->strtab + vn->vn_file; + obj->vertab[vernum].flags = (vna->vna_other & VER_NEED_HIDDEN) ? + VER_INFO_HIDDEN : 0; + if (vna->vna_next == 0) + break; + vna = (const Elf_Vernaux *) ((char *)vna + vna->vna_next); + } + if (vn->vn_next == 0) + break; + vn = (const Elf_Verneed *) ((char *)vn + vn->vn_next); + } + return 0; +} + +static int +rtld_verify_versions(const Objlist *objlist) +{ + Objlist_Entry *entry; + int rc; + + rc = 0; + STAILQ_FOREACH(entry, objlist, link) { + /* + * Skip dummy objects or objects that have their version requirements + * already checked. + */ + if (entry->obj->strtab == NULL || entry->obj->vertab != NULL) + continue; + if (rtld_verify_object_versions(entry->obj) == -1) { + rc = -1; + if (ld_tracing == NULL) + break; + } + } + return rc; +} + +const Ver_Entry * +fetch_ventry(const Obj_Entry *obj, unsigned long symnum) +{ + Elf_Versym vernum; + + if (obj->vertab) { + vernum = VER_NDX(obj->versyms[symnum]); + if (vernum >= obj->vernum) { + _rtld_error("%s: symbol %s has wrong verneed value %d", + obj->path, obj->strtab + symnum, vernum); + } else if (obj->vertab[vernum].hash != 0) { + return &obj->vertab[vernum]; + } + } + return NULL; +} diff --git a/libexec/rtld-elf/rtld.h b/libexec/rtld-elf/rtld.h index 28fe510..cc430e8 100644 --- a/libexec/rtld-elf/rtld.h +++ b/libexec/rtld-elf/rtld.h @@ -90,6 +90,11 @@ typedef struct Struct_Needed_Entry { unsigned long name; /* Offset of name in string table */ } Needed_Entry; +typedef struct Struct_Name_Entry { + STAILQ_ENTRY(Struct_Name_Entry) link; + char name[1]; +} Name_Entry; + /* Lock object */ typedef struct Struct_LockInfo { void *context; /* Client context for creating locks */ @@ -107,6 +112,15 @@ typedef struct Struct_LockInfo { void (*context_destroy)(void *context); } LockInfo; +typedef struct Struct_Ver_Entry { + Elf_Word hash; + unsigned int flags; + const char *name; + const char *file; +} Ver_Entry; + +#define VER_INFO_HIDDEN 0x01 + /* * Shared object descriptor. * @@ -165,6 +179,12 @@ typedef struct Struct_Obj_Entry { const char *strtab; /* String table */ unsigned long strsize; /* Size in bytes of string table */ + const Elf_Verneed *verneed; /* Required versions. */ + Elf_Word verneednum; /* Number of entries in verneed table */ + const Elf_Verdef *verdef; /* Provided versions. */ + Elf_Word verdefnum; /* Number of entries in verdef table */ + const Elf_Versym *versyms; /* Symbol versions table */ + const Elf_Hashelt *buckets; /* Hash table buckets array */ unsigned long nbuckets; /* Number of buckets */ const Elf_Hashelt *chains; /* Hash table chain array */ @@ -173,6 +193,11 @@ typedef struct Struct_Obj_Entry { const char *rpath; /* Search path specified in object */ Needed_Entry *needed; /* Shared objects needed by this one (%) */ + STAILQ_HEAD(, Struct_Name_Entry) names; /* List of names for this object we + know about. */ + Ver_Entry *vertab; /* Versions required /defined by this object */ + int vernum; /* Number of entries in vertab */ + Elf_Addr init; /* Initialization function to call */ Elf_Addr fini; /* Termination function to call */ @@ -199,6 +224,11 @@ typedef struct Struct_Obj_Entry { #define RTLD_STATIC_TLS_EXTRA 64 +/* Flags to be passed into symlook_ family of functions. */ +#define SYMLOOK_IN_PLT 0x01 /* Lookup for PLT symbol */ +#define SYMLOOK_DLSYM 0x02 /* Return newes versioned symbol. Used by + dlsym. */ + /* * Symbol cache entry used during relocation to avoid multiple lookups * of the same symbol. @@ -225,20 +255,21 @@ extern void dump_Elf_Rela (Obj_Entry *, const Elf_Rela *, u_long); */ unsigned long elf_hash(const char *); const Elf_Sym *find_symdef(unsigned long, const Obj_Entry *, - const Obj_Entry **, bool, SymCache *); + const Obj_Entry **, int, SymCache *); void init_pltgot(Obj_Entry *); void lockdflt_init(void); void obj_free(Obj_Entry *); Obj_Entry *obj_new(void); void _rtld_bind_start(void); -const Elf_Sym *symlook_obj(const char *, unsigned long, - const Obj_Entry *, bool); +const Elf_Sym *symlook_obj(const char *, unsigned long, const Obj_Entry *, + const Ver_Entry *, int); void *tls_get_addr_common(Elf_Addr** dtvp, int index, size_t offset); void *allocate_tls(Obj_Entry *, void *, size_t, size_t); void free_tls(void *, size_t, size_t); void *allocate_module_tls(int index); bool allocate_tls_offset(Obj_Entry *obj); void free_tls_offset(Obj_Entry *obj); +const Ver_Entry *fetch_ventry(const Obj_Entry *obj, unsigned long); /* * MD function declarations. diff --git a/libexec/rtld-elf/sparc64/reloc.c b/libexec/rtld-elf/sparc64/reloc.c index 4e66211..f264860 100644 --- a/libexec/rtld-elf/sparc64/reloc.c +++ b/libexec/rtld-elf/sparc64/reloc.c @@ -205,6 +205,7 @@ do_copy_relocations(Obj_Entry *dstobj) const Elf_Rela *rela; const Elf_Sym *dstsym; const Elf_Sym *srcsym; + const Ver_Entry *ve; void *dstaddr; const void *srcaddr; Obj_Entry *srcobj; @@ -222,11 +223,12 @@ do_copy_relocations(Obj_Entry *dstobj) name = dstobj->strtab + dstsym->st_name; hash = elf_hash(name); size = dstsym->st_size; + ve = fetch_ventry(dstobj, ELF_R_SYM(rela->r_info)); for (srcobj = dstobj->next; srcobj != NULL; srcobj = srcobj->next) if ((srcsym = symlook_obj(name, hash, srcobj, - false)) != NULL) + ve, 0)) != NULL) break; if (srcobj == NULL) { |