diff --git a/Makefile.am b/Makefile.am index ab9f086690f769adaeabe6a44ebf991ea28ea4bd..0ce55e0d0c7278f24aa7795dd893ad43321d39bc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -44,7 +44,9 @@ libcapsule_la_SOURCES = capsule/capsule-dlmopen.c \ capsule/capsule-init.c \ utils/utils.c utils/dump.c \ utils/process-pt-dynamic.c \ - utils/mmap-info.c + utils/mmap-info.c \ + utils/ld-cache.c \ + utils/ld-libs.c libcapsule_la_LDFLAGS = -lelf ############################################################################ diff --git a/capsule/capsule-dlmopen.c b/capsule/capsule-dlmopen.c index 5c0db6e6546b8f9b9411c48770e92a5cd3e84402..947854debfcf34ec9e2e2e1357b58ed8b9fabdd9 100644 --- a/capsule/capsule-dlmopen.c +++ b/capsule/capsule-dlmopen.c @@ -38,827 +38,8 @@ #include "utils/dump.h" #include "utils/mmap-info.h" #include "utils/process-pt-dynamic.h" - -// ========================================================================== -// this is stolen from the ld.so config (dl-cache.h) since we need -// to use a slightly re-brained version of the linker to do our -// filthy, filthy business (and these details are not in a shipped header) - -#define FLAG_ANY -1 -#define FLAG_TYPE_MASK 0x00ff -#define FLAG_LIBC4 0x0000 -#define FLAG_ELF 0x0001 -#define FLAG_ELF_LIBC5 0x0002 -#define FLAG_ELF_LIBC6 0x0003 -#define FLAG_REQUIRED_MASK 0xff00 -#define FLAG_SPARC_LIB64 0x0100 -#define FLAG_IA64_LIB64 0x0200 -#define FLAG_X8664_LIB64 0x0300 -#define FLAG_S390_LIB64 0x0400 -#define FLAG_POWERPC_LIB64 0x0500 -#define FLAG_MIPS64_LIBN32 0x0600 -#define FLAG_MIPS64_LIBN64 0x0700 -#define FLAG_X8664_LIBX32 0x0800 -#define FLAG_ARM_LIBHF 0x0900 -#define FLAG_AARCH64_LIB64 0x0a00 -#define FLAG_ARM_LIBSF 0x0b00 -#define FLAG_MIPS_LIB32_NAN2008 0x0c00 -#define FLAG_MIPS64_LIBN32_NAN2008 0x0d00 -#define FLAG_MIPS64_LIBN64_NAN2008 0x0e00 - -static const char *flag_descr[] = { "libc4", "ELF", "libc5", "libc6"}; - -static const char *const abi_tag_os[] = -{ - [0] = "Linux", - [1] = "Hurd", - [2] = "Solaris", - [3] = "FreeBSD", - [4] = "kNetBSD", - [5] = "Syllable", - [6] = "Unknown OS" -}; -#define MAXTAG (sizeof abi_tag_os / sizeof abi_tag_os[0] - 1) - -#define CACHEMAGIC "ld.so-1.7.0" - -#define CACHEMAGIC_NEW "glibc-ld.so.cache" -#define CACHE_VERSION "1.1" -#define CACHEMAGIC_VERSION_NEW CACHEMAGIC_NEW CACHE_VERSION - -struct file_entry_new -{ - int32_t flags; /* This is 1 for an ELF library. */ - uint32_t key, value; /* String table indices. */ - uint32_t osversion; /* Required OS version. */ - uint64_t hwcap; /* Hwcap entry. */ -}; - -struct cache_file_new -{ - char magic[sizeof CACHEMAGIC_NEW - 1]; - char version[sizeof CACHE_VERSION - 1]; - uint32_t nlibs; /* Number of entries. */ - uint32_t len_strings; /* Size of string table. */ - uint32_t unused[5]; /* Leave space for future extensions - and align to 8 byte boundary. */ - struct file_entry_new libs[0]; /* Entries describing libraries. */ - /* After this the string table of size len_strings is found. */ -}; - -struct file_entry -{ - int flags; /* This is 1 for an ELF library. */ - unsigned int key, value; /* String table indices. */ -}; - -struct cache_file -{ - char magic[sizeof CACHEMAGIC - 1]; - unsigned int nlibs; - struct file_entry libs[0]; -}; - -/* Used to align cache_file_new. */ -#define ALIGN_CACHE(addr) \ - (((addr) + __alignof__ (struct cache_file_new) -1) \ - & (~(__alignof__ (struct cache_file_new) - 1))) - -// end of stolen header structures - -// ========================================================================== -// And now some definitions related to us handling the ld.so.cache ourselves: - -// we only handle up to this many library dependencies - -// yes, hardwired limits are bad but there's already enough complexity -// here - can revisit this decision if it ever becomes close to being -// an issue (shouldn't affect the api or abi): -#define DSO_LIMIT 256 - -struct dso_cache_search -{ - int idx; - const char *name; -}; - -typedef enum -{ - CACHE_NONE, - CACHE_NEW , - CACHE_OLD , -} cache_type; - -typedef struct -{ - int fd; - char *name; - char path[PATH_MAX]; - int requestors[DSO_LIMIT]; - int depcount; - Elf *dso; -} dso_needed_t; - -typedef struct -{ - int cache_fd; - size_t cache_map_size; - struct cache_file *cache_mmap; - const char *cache_data; - union { struct cache_file *old; struct cache_file_new *new; } cache; - cache_type ctype; - int last_idx; - int elf_class; - Elf64_Half elf_machine; - struct { char path[PATH_MAX]; size_t len; } prefix; - const char **exclude; - dso_needed_t needed[DSO_LIMIT]; - char *not_found[DSO_LIMIT]; - char *error; - int last_not_found; - unsigned long debug; -} ldlibs_t; - -typedef int (*ldcache_entry_cb) (ldlibs_t *ldlibs, - const char *name, - int flag, - unsigned int osv, - uint64_t hwcap, - const char *path, - void *data); - -// ========================================================================== -static void clear_needed (dso_needed_t *needed) -{ - elf_end( needed->dso ); - needed->dso = NULL; - - if( needed->fd >= 0 ) - close( needed->fd ); - needed->fd = -1; - - free( needed->name ); - needed->name = NULL; - - needed->depcount = 0; - - memset( needed->path, 0, PATH_MAX ); - memset( needed->requestors, 0, sizeof(int) * DSO_LIMIT ); -} - -// set the ldlibs elf class and machine based on the link map entry -// passed to us if possible: if we found values for these, return 1, -// otherwise return 0: -static int -find_elf_constraints(ldlibs_t *ldlibs, struct link_map *m) -{ - int fd = -1; - Elf *dso = NULL; - GElf_Ehdr ehdr = { }; - - // absolute path or it's a "fake" link map entry which we can't use - // as there's no actual file to open and inspect: - if( !m || !m->l_name || (m->l_name[0] != '/')) - return 0; - - // if we can't open the DSO pointed to by the link map, bail: - fd = open( m->l_name, O_RDONLY ); - - if( fd < 0 ) - return 0; - - dso = elf_begin( fd, ELF_C_READ_MMAP, NULL ); - - if( dso && gelf_getehdr( dso, &ehdr ) ) - { - ldlibs->elf_class = gelf_getclass( dso ); - ldlibs->elf_machine = ehdr.e_machine; - DEBUG( DEBUG_SEARCH|DEBUG_CAPSULE, - "elf class: %d; elf machine: %d; set from: %s", - ldlibs->elf_class, ldlibs->elf_machine, m->l_name ); - } - - if( dso != NULL ) - elf_end( dso ); - - if( fd >= 0 ) - close( fd ); - - return ( ldlibs->elf_class != ELFCLASSNONE ); -} - - -// record the class & machine of the start of the link chain -// so that we can only consider matching libraries later -// this matters on multi-arch systems so we don't pick an -// i386 or x32 DSO to statisfy a DT_NEEDED from an x86-64 one. -// return true if we found a valid DSO, false (can't happen?) otherwise -static int -set_elf_constraints (ldlibs_t *ldlibs) -{ - void *handle; - struct link_map *map; - struct link_map *m; - - if( (handle = dlopen( NULL, RTLD_LAZY|RTLD_NOLOAD )) && - (dlinfo( handle, RTLD_DI_LINKMAP, &map ) == 0) ) - { - // we're not guaranteed to be at the start of the link map chain: - while( map->l_prev ) - map = map->l_prev; - - // check link maps until we find one we can fill in - // our constraints from: - for( m = map; m; m = m->l_next ) - if( find_elf_constraints(ldlibs, m) ) - break; - } - else - { - // this would be frankly beyond bizarre: - fprintf(stderr, "dlopen/dlinfo on self failed: %s\n", dlerror() ); - } - - return ( ( ldlibs->elf_class != ELFCLASSNONE ) && - ( ldlibs->elf_machine |= EM_NONE ) ); -} - -// check that the currently opened DSO at offset idx in the needed array -// matches the class & architecture of the DSO we started with: -// return true on a match, false otherwise -static int -check_elf_constraints (ldlibs_t *ldlibs, int idx) -{ - GElf_Ehdr ehdr = {}; - int eclass; - - // bogus ELF DSO - no ehdr available? - if( !gelf_getehdr( ldlibs->needed[ idx ].dso, &ehdr ) ) - return 0; - - eclass = gelf_getclass( ldlibs->needed[ idx ].dso ); - - // check class (32 vs 64 bit) - if( ldlibs->elf_class != eclass ) - return 0; - - // check target architecture (i386, x86-64) - // x32 ABI is class 32 but machine x86-64 - if( ldlibs->elf_machine != ehdr.e_machine ) - return 0; - - DEBUG( DEBUG_ELF, "constraints: class %d; machine: %d;", - ldlibs->elf_class, ldlibs->elf_machine ); - DEBUG( DEBUG_ELF, "results : class %d; machine: %d;", - eclass, ehdr.e_machine ); - - // both the class (word size) and machine (architecture) match - return 1; -} - -// make sure all the string buffers are zeroed out -static inline void sanitise_ldlibs(ldlibs_t *ldlibs) -{ - ldlibs->prefix.path[ ldlibs->prefix.len ] = '\0'; -} - -// as we are pulling in files from a non '/' prefix ('/host' by default) -// we need to compensate for this when resolving symlinks. -// this will keep following the path at entry i in ldlibs until -// it finds something that is not a symlink. -void resolve_symlink_prefixed (ldlibs_t *ldlibs, int i) -{ - int count = 0; - char resolved[PATH_MAX]; - char link_dir[PATH_MAX]; - - sanitise_ldlibs(ldlibs); - // prefix is unset or is /, nothing to do here (we can rely on - // libc's built-in symlink following if there's no prefix): - if( ldlibs->prefix.len == 0 || - (ldlibs->prefix.path[0] == '/' && ldlibs->prefix.path[1] == '\0') ) - return; - - LDLIB_DEBUG( ldlibs, DEBUG_PATH, - "resolving (un)prefixed link in %s", ldlibs->needed[i].path ); - - // set the resolved path to the current needed path as a starting point: - safe_strncpy( resolved, ldlibs->needed[i].path, PATH_MAX ); - - // now keep poking resolve_link (resolved will be updated each time) - // until it returns false: - while( resolve_link(ldlibs->prefix.path, resolved, link_dir) ) - { - LDLIB_DEBUG( ldlibs, DEBUG_PATH, " resolved to: %s", resolved ); - - if( ++count > MAXSYMLINKS ) - { - fprintf( stderr, "%s: MAXSYMLINKS (%d) exceeded resolving %s\n", - __PRETTY_FUNCTION__, MAXSYMLINKS, - ldlibs->needed[i].path ); - break; - } - } - - // if the path changed, copy `resolved' back into needed[].path: - if( count ) - safe_strncpy( ldlibs->needed[i].path, resolved, PATH_MAX ); -} - -// open the dso at offset i in the needed array, but only accept it -// if it matches the class & architecture of the starting DSO: -// return a true value only if we finish with a valid fd for the DSO -// -// will set up the needed entry at offset i correctly if we are -// successful, and clear it if we are not: -// -// designed to be called on a populated ldlib needed entry, 'name' -// is the original requested name (typically an absolute path to -// a DSO or a standard DT_NEEDED style specifier following 'libfoo.so.X') -// -// note that this expects the prefix to have already been prepended -// to the DSO path in the ldlibs->needed[i].path buffer if necessary -// this is to allow both prefixed and unprefixed DSOs to be handled -// here: -static int -ldlib_open (ldlibs_t *ldlibs, const char *name, int i) -{ - - LDLIB_DEBUG( ldlibs, DEBUG_SEARCH, - "ldlib_open: target -: %s", ldlibs->needed[i].path ); - - // resolve the symlink manually if there's a prefix: - resolve_symlink_prefixed( ldlibs, i ); - - LDLIB_DEBUG( ldlibs, DEBUG_SEARCH, - "ldlib_open: target +: %s", ldlibs->needed[i].path ); - - ldlibs->needed[i].fd = open( ldlibs->needed[i].path, O_RDONLY ); - - if( ldlibs->needed[i].fd >= 0 ) - { - int acceptable = 0; - - ldlibs->needed[i].name = NULL; - ldlibs->needed[i].dso = - elf_begin( ldlibs->needed[i].fd, ELF_C_READ_MMAP, NULL ); - - acceptable = check_elf_constraints( ldlibs, i ); - - LDLIB_DEBUG( ldlibs, DEBUG_SEARCH, - "[%03d] %s on fd #%d; elf: %p; acceptable: %d", - i, - ldlibs->needed[i].path, - ldlibs->needed[i].fd , - ldlibs->needed[i].dso , - acceptable ); - - // either clean up the current entry so we can find a better DSO - // or (for a valid candidate) copy the original requested name in: - if( !acceptable ) - clear_needed( &ldlibs->needed[i] ); - else - ldlibs->needed[i].name = strdup( name ); - } - - // the fd will only be valid if everything worked out: - return ldlibs->needed[i].fd >= 0; -} - -// iterate over the ld.so.cache loaded into the ldlibs structure, -// calling cb for each entry, until cb returns true or we run out -// of entries. will return true as soon as cb returns true, or -// false if we get to the end of the cache without cb returning true: -static int -iterate_ldcache (ldlibs_t *ldlibs, ldcache_entry_cb cb, void *data) -{ - int rval = 0; - const char *base = ldlibs->cache_data; - - switch (ldlibs->ctype) - { - case CACHE_OLD: - for (int i = 0; !rval && (i < ldlibs->cache.old->nlibs); i++) - { - struct file_entry *f = &ldlibs->cache.old->libs[i]; - rval = cb( ldlibs, base + f->key, f->flags, - 0, 0, base + f->value, data ); - } - break; - - case CACHE_NEW: - for (int i = 0; !rval && (i < ldlibs->cache.new->nlibs); i++) - { - struct file_entry_new *f = &ldlibs->cache.new->libs[i]; - rval = cb( ldlibs, base + f->key, f->flags, - f->osversion, f->hwcap, base + f->value, data ); - } - break; - - default: - fprintf( stderr, "Invalid ld cache type %d, cannot parse", - ldlibs->ctype ); - exit(22); - } - - return rval; -} - -// search callback for search_ldcache. see search_ldcache and iterate_ldcache: -// returning a true value means we found (and set up) the DSO we wanted: -static int -search_ldcache_cb (ldlibs_t *ldlibs, - const char *name, // name of the DSO in the ldcache - int flag, // 1 for an ELF DSO - unsigned int osv, // OS version. we don't use this - uint64_t hwcap, // HW caps. Ibid. - const char *path, // absolute path to DSO (may be a symlink) - struct dso_cache_search *target) -{ - // passed an empty query, just abort the whole search - if( !target->name || !(*target->name) ) - return 1; - - // what would this even mean? malformed cache entry? - // skip it and move on - if( !name || !*name ) - return 0; - - if( strcmp( name, target->name ) == 0 ) - { - int idx = target->idx; - char *prefix = ldlibs->prefix.path; - size_t plen = ldlibs->prefix.len; - char *lpath = ldlibs->needed[ idx ].path; - - LDLIB_DEBUG( ldlibs, DEBUG_SEARCH|DEBUG_LDCACHE, - "checking %s vs %s [%s]", - target->name, name, path ); - // copy in the prefix and append the DSO path to it - safe_strncpy( lpath, prefix, PATH_MAX ); - safe_strncpy( lpath + plen, path, PATH_MAX - plen ); - - // try to open the DSO. This will finish setting up the - // needed[idx] slot if successful, and reset it ready for - // another attempt if it fails: - return ldlib_open( ldlibs, name, idx ); - } - - return 0; -} - -// search the ld.so.cache loaded into ldlibs for one matching `name' -// name should be unadorned: eg just libfoo.so.X - no path elements -// attached (as the cache lookup is for unadorned library names): -// -// if a match is found, the needed array entry at i will be populated -// and will contain a valid fd for the DSO. (and search_ldcache will -// return true). Otherwise the entry will be empty and we will return false: -// -// this function will respect any path prefix specified in ldlibs -static int -search_ldcache (const char *name, ldlibs_t *ldlibs, int i) -{ - struct dso_cache_search target; - - target.idx = i; - target.name = name; - - iterate_ldcache( ldlibs, (ldcache_entry_cb)search_ldcache_cb, &target ); - - return ldlibs->needed[i].fd >= 0; -} - -// search a : separated path (such as LD_LIBRARY_PATH from the environment) -// for a DSO matching the bare `name' (eg libfoo.so.X) -// -// if a match is found, the needed array entry at i will be populated -// and will contain a valid fd for the DSO. (and search_ldcache will -// return true). Otherwise the entry will be empty and we will return false: -// -// this function will respect any path prefix specified in ldlibs -static int -search_ldpath (const char *name, const char *ldpath, ldlibs_t *ldlibs, int i) -{ - char *sp = (char *)ldpath; - char *prefix = ldlibs->prefix.path; - size_t plen = ldlibs->prefix.len; - - prefix[plen] = '\0'; - - sanitise_ldlibs(ldlibs); - - LDLIB_DEBUG( ldlibs, DEBUG_SEARCH, - "searching for %s in %s (prefix: %s)", - name, ldpath, plen ? prefix : "-none-" ); - - while( sp && *sp ) - { - size_t len; - char *end; - - end = strchr( sp, ':' ); - if( end ) - len = MIN((end - sp), PATH_MAX - plen - 1); - else - len = MIN(strlen( sp ), PATH_MAX - plen - 1); - - safe_strncpy( prefix + plen, sp, len + 1); - prefix[plen + len + 1] = '\0'; - - LDLIB_DEBUG( ldlibs, DEBUG_SEARCH, " searchpath element: %s", prefix ); - // append the target name, without overflowing, then resolve - if( (plen + len + strlen( name ) + 1 < PATH_MAX) ) - { - prefix[plen + len] = '/'; - safe_strncpy( prefix + plen + len + 1, name, - PATH_MAX - plen - len - 1 ); - - LDLIB_DEBUG( ldlibs, DEBUG_SEARCH, "examining %s", prefix ); - // if path resolution succeeds _and_ we can open an acceptable - // DSO at that location, we're good to go (ldlib_open will - // finish setting up or clearing the needed[] entry for us): - // FIXME: this is broken if the target is an absolute symlink - if( realpath( prefix, ldlibs->needed[i].path ) && - ldlib_open( ldlibs, name, i ) ) - return 1; - } - - // search the next path element if there is one - if( !end ) - break; - - sp = end + 1; - } - - return 0; -} - -// find a DSO using an algorithm that matches the one used by the -// normal dynamic linker and set up the needed array entry at offset i. -// -// Exceptions: -// we don't support DT_RPATH/DT_RUNPATH -// we don't handle ${ORIGIN} and similar -// we will respect any path prefix specified in ldlibs -// -// if a match is found, the needed array entry at i will be populated -// and will contain a valid fd for the DSO. (and search_ldcache will -// return true). Otherwise the entry will be empty and we will return false: -static int -dso_find (const char *name, ldlibs_t *ldlibs, int i) -{ - int found = 0; - const char *ldpath = NULL; - int absolute = (name && (name[0] == '/')); - - // 'name' is an absolute path, or relative to CWD: - // we may to need to do some path manipulation - if( strchr( name, '/' ) ) - { - size_t plen = ldlibs->prefix.len; - char prefixed[PATH_MAX]; - const char *target; - - // we have a path prefix, so yes, we need to do some path manipulation: - if( ldlibs->prefix.len ) - { - sanitise_ldlibs(ldlibs); - safe_strncpy( prefixed, ldlibs->prefix.path, PATH_MAX ); - - if( absolute ) - { - safe_strncpy( prefixed + plen, name, PATH_MAX - plen ); - LDLIB_DEBUG( ldlibs, DEBUG_SEARCH|DEBUG_PATH, - "absolute path to DSO %s", prefixed ); - } - else - { // name is relative... this is probably wrong? - // I don't think this can ever really happen but - // worst case is we'll simply not open a DSO whose - // path we couldn't resolve, and then move on: - safe_strncpy( prefixed + plen, "/", PATH_MAX - plen ); - safe_strncpy( prefixed + plen + 1, name, PATH_MAX - plen - 1); - LDLIB_DEBUG( ldlibs, DEBUG_SEARCH|DEBUG_PATH, - "relative path to DSO %s", prefixed ); - } - - target = prefixed; - } - else - { // name is a standard bare 'libfoo.so.X' spec: - target = name; - } - - LDLIB_DEBUG( ldlibs, DEBUG_SEARCH|DEBUG_PATH, - "resolving path %s", target ); - // this will fail for a non-absolute path, but that's OK - // if realpath lookup succeeds needed[i].path will be set correctly: - if( realpath( target, ldlibs->needed[i].path ) ) - return ldlib_open( ldlibs, name, i ); - } - - // path was absolute and we couldn't resolve it. give up: - if( absolute ) - return 0; - - LDLIB_DEBUG( ldlibs, DEBUG_SEARCH, "target DSO is %s", name ); - // now search LD_LIBRARY_PATH, the ld.so.cache, and the default locations - // in that order (similar algorithm to the linker, but with the RPATH and - // ${ORIGIN} support dropped) - if( (ldpath = getenv( "LD_LIBRARY_PATH" )) ) - if( (found = search_ldpath( name, ldpath, ldlibs, i )) ) - return found; - - if( (found = search_ldcache( name, ldlibs, i )) ) - return found; - - if( (found = search_ldpath( name, "/lib:/usr/lib", ldlibs, i )) ) - return found; - - return 0; -} - -// if a DSO has already been requested and found as a result of a DT_NEEDED -// entry we've seen before then it's already in the needed array - check -// for such pre-required entries and simply record the dependency instead -// of reopening the DSO (and return true to indicate that we already have the -// DSO) -// -// we have assumed that the root of the DSO chain can never be already-needed -// as this would indicate a circular dependency. -static int -already_needed (dso_needed_t *needed, int requesting_idx, const char *name) -{ - for( int i = DSO_LIMIT - 1; i > 0; i-- ) - { - if( needed[i].name && strcmp( needed[i].name, name ) == 0) - { - needed[i].requestors[requesting_idx] = 1; - return i; - } - } - - return 0; -} - -// we're getting to the meat of it: process a DSO at offset idx in the -// needed array, extract each SHT_DYNAMIC section, then make sure we -// can find a DSO to satisfy every DT_NEEDED sub-entry in the section. -// this function recurses into itself each time it finds a previously -// unseen DT_NEEDED value (but not if the DT_NEEDED value is for a DSO -// it has already found and recorded in the needed array) -// -// NOTE: you must use dso_find to seed the 0th entry in the needed array -// or the elf handle in needed[0].dso will not be set up and hilarity* -// will ensue. -static void -_dso_iterate_sections (ldlibs_t *ldlibs, int idx) -{ - Elf_Scn *scn = NULL; - - //debug(" ldlibs: %p; idx: %d (%s)", ldlibs, idx, ldlibs->needed[idx].name); - - ldlibs->last_idx = idx; - - LDLIB_DEBUG( ldlibs, DEBUG_ELF, - "%03d: fd:%d dso:%p ↠%s", - idx, - ldlibs->needed[idx].fd, - ldlibs->needed[idx].dso, - ldlibs->needed[idx].path ); - - while((scn = elf_nextscn( ldlibs->needed[idx].dso, scn )) != NULL) - { - GElf_Shdr shdr = {}; - gelf_getshdr( scn, &shdr ); - - // SHT_DYNAMIC is the only section type we care about here: - if( shdr.sh_type == SHT_DYNAMIC ) - { - int i = 0; - GElf_Dyn dyn = {}; - Elf_Data *edata = NULL; - - edata = elf_getdata( scn, edata ); - - // process eaach DT_* entry in the SHT_DYNAMIC section: - while( !ldlibs->error && - gelf_getdyn( edata, i++, &dyn ) && - (dyn.d_tag != DT_NULL) ) - { - int skip = 0; - int next = ldlibs->last_idx; - dso_needed_t *needed = ldlibs->needed; - char *next_dso; // name of the dependency we're going to need - - // we're only gathering DT_NEEDED (dependency) entries here: - if( dyn.d_tag != DT_NEEDED ) - continue; - - next_dso = - elf_strptr( needed[idx].dso, shdr.sh_link, dyn.d_un.d_val ); - - ////////////////////////////////////////////////// - // ignore the linker itself - if( strstr( next_dso, "ld-" ) == next_dso ) - continue; - - // ignore any DSOs we've been specifically told to leave out: - for( char **x = (char **)ldlibs->exclude; x && *x; x++ ) - { - if( strcmp( *x, next_dso ) == 0 ) - { - LDLIB_DEBUG( ldlibs, DEBUG_SEARCH|DEBUG_ELF, - "skipping %s / %s", next_dso, *x ); - skip = 1; - break; - } - } - - if( skip ) - continue; - - ////////////////////////////////////////////////// - // if we got this far, we have another dependency: - needed[idx].depcount++; - - // already on our list, no need to do anything else here: - if( already_needed( needed, idx, next_dso ) ) - continue; - - next++; - if( next >= DSO_LIMIT ) - { - ldlibs->error = strdup( "Too many dependencies: abort" ); - break; - } - - if( !dso_find( next_dso, ldlibs, next ) ) - { - ldlibs->not_found[ ldlibs->last_not_found++ ] = - strdup( next_dso ); - ldlibs->error = strdup( "Missing dependencies:" ); - } - else - { - // record which DSO requested the new library we found: - needed[next].requestors[idx] = 1; - // now find the dependencies of our newest dependency: - _dso_iterate_sections( ldlibs, next ); - } - } - } - } -} - -static void -_dso_iterator_format_error (ldlibs_t * ldlibs) -{ - size_t extra_space = 0; - - if( ! ldlibs->error ) - return; - - if( ! ldlibs->not_found[0] ) - return; - - for( int i = 0; (i < DSO_LIMIT) && ldlibs->not_found[i]; i++ ) - extra_space += strlen( ldlibs->not_found[i] ) + 1; - - if( extra_space ) - { - char *append_here; - char *end; - size_t prev_space = strlen( ldlibs->error ); - - ldlibs->error = - realloc( ldlibs->error, prev_space + extra_space + 2 ); - append_here = ldlibs->error + prev_space; - end = ldlibs->error + prev_space + extra_space + 1; - memset( append_here, 0, extra_space + 2 ); - - for( int i = 0; (i < DSO_LIMIT) && ldlibs->not_found[i]; i++ ) - { - append_here += - snprintf( append_here, end - append_here, - " %s", ldlibs->not_found[i] ); - free( ldlibs->not_found[i] ); - ldlibs->not_found[i] = NULL; - } - } -} - -// wrapper to format any accumulated errors and similar after -// invoking the actual dso iterator: returns true if we gathered -// all the needed info witout error, false otherwise: -static int -dso_iterate_sections (ldlibs_t *ldlibs, int idx) -{ - _dso_iterate_sections( ldlibs, idx ); - _dso_iterator_format_error( ldlibs ); - - return ldlibs->error == NULL; -} +#include "utils/ld-cache.h" +#include "utils/ld-libs.h" // map the ld.so.cache for the system into memory so that we can search it // for DSOs in the same way as the dynamic linker. @@ -867,445 +48,34 @@ dso_iterate_sections (ldlibs_t *ldlibs, int idx) // // this function respects any path prefix specified in ldlibs static int -load_ld_cache (ldlibs_t *libs, const char *path) +load_ld_cache (ld_libs_t *libs, const char *path) { - struct stat ldcache = {}; - const char *cachepath; int rv; if( libs->prefix.len == 0 ) { - cachepath = path; - libs->cache_fd = open( path, O_RDONLY ); + libs->ldcache.fd = open( path, O_RDONLY ); } else { safe_strncpy( libs->prefix.path + libs->prefix.len, path, PATH_MAX - libs->prefix.len ); - cachepath = libs->prefix.path; - libs->cache_fd = open( libs->prefix.path, O_RDONLY ); - } - - if( libs->cache_fd < 0 ) - { - fprintf( stderr, "failed to open ld.so cache file %s: %s\n", - cachepath, strerror( errno ) ); - goto no_cache; - } - - fstat( libs->cache_fd, &ldcache ); - - // cache file must be at least this big or it's invalid: - if( ldcache.st_size < sizeof( struct cache_file ) ) - goto no_cache; - - libs->cache_mmap = - mmap( NULL, ldcache.st_size, PROT_READ, MAP_PRIVATE, libs->cache_fd, 0 ); - - if( libs->cache_mmap == MAP_FAILED ) - { - fprintf( stderr, "failed to mmap ld.so cache file %s: %s\n", - path, strerror( errno ) ); - goto no_cache; - } - - libs->cache_map_size = ldcache.st_size; - - // plain modern (circa 2016) cache map: - if( memcmp( libs->cache_mmap->magic, CACHEMAGIC, sizeof(CACHEMAGIC) -1 ) ) - { - DEBUG( DEBUG_LDCACHE, "New format ld cache" ); - libs->cache.new = (struct cache_file_new *)libs->cache_mmap; - - // if the magic strings don't reside at the expected offsets, bail out: - if( memcmp( libs->cache.new->magic, CACHEMAGIC_NEW, - sizeof(CACHEMAGIC_NEW) - 1 ) || - memcmp( libs->cache.new->version, CACHE_VERSION, - sizeof(CACHE_VERSION) - 1) ) - { - fprintf( stderr, "invalid cache, expected %s: %s\n", - CACHEMAGIC_NEW, CACHE_VERSION ); - goto no_cache; - } - - libs->cache_data = (char *)libs->cache.new; - libs->ctype = CACHE_NEW; + libs->ldcache.fd = open( libs->prefix.path, O_RDONLY ); } - else - { - size_t header = sizeof( struct cache_file ); - size_t entry = sizeof( struct file_entry ); - size_t block = header + (libs->cache_mmap->nlibs * entry); - size_t offset = ALIGN_CACHE( block ); - int nlibs = libs->cache_mmap->nlibs; - - DEBUG( DEBUG_LDCACHE, "Old format ld cache" ); - // it's an old-style cache, unless we successfully probe for a - // nested new cache inside it: - libs->ctype = CACHE_OLD; + rv = ld_cache_open( &libs->ldcache, libs->prefix.path ); - /* This is where the strings start. */ - libs->cache_data = (const char *) &libs->cache_mmap->libs[ nlibs ]; - - if( libs->cache_map_size > (offset + sizeof( struct cache_file_new )) ) - { - libs->cache.new = (void *)libs->cache_mmap + offset; - - // this is the probe: as in the pervious if block, except - // that if we don't find a new cache it's not an error, - // it just means we're in an old style cache: - if( memcmp( libs->cache.new->magic, CACHEMAGIC_NEW, - sizeof(CACHEMAGIC_NEW) - 1 ) || - memcmp( libs->cache.new->version, CACHE_VERSION, - sizeof(CACHE_VERSION) - 1) ) - { - // nope, no encapsulated new cache: - libs->cache.old = libs->cache_mmap; - } - else - { - DEBUG( DEBUG_LDCACHE, "... with a new style cache inside" ); - libs->ctype = CACHE_NEW; - libs->cache_data = (char *)libs->cache.new; - } - } - } - - if( libs->cache_fd >= 0 ) - { - DEBUG( DEBUG_LDCACHE, "Opened ld.cache at %s", cachepath ); - rv = 1; - } - else - { - DEBUG( DEBUG_LDCACHE, "No ld.cache at %s", cachepath ); - rv = 0; - } - - sanitise_ldlibs( libs ); return rv; - -no_cache: - - if( libs->cache_fd >= 0 ) - close( libs->cache_fd ); - - if( libs->cache_mmap != MAP_FAILED && libs->cache_mmap != NULL ) - munmap( libs->cache_mmap, libs->cache_map_size ); - - libs->cache_fd = -1; - libs->cache_map_size = 0; - libs->ctype = CACHE_NONE; - libs->cache_mmap = NULL; - libs->cache_data = NULL; - libs->cache.new = NULL; - - return 0; -} - -static void -cleanup_ldlibs (ldlibs_t *ldlibs) -{ - for( int i = 0; i < DSO_LIMIT; i++ ) - clear_needed( &ldlibs->needed[i] ); - - for( int i = ldlibs->last_not_found; i >= 0; i-- ) - { - free( ldlibs->not_found[i] ); - ldlibs->not_found[i] = NULL; - } - - ldlibs->last_not_found = 0; - - if( ldlibs->cache_mmap ) - { - munmap( ldlibs->cache_mmap, ldlibs->cache_map_size ); - ldlibs->cache_map_size = 0; - } - // these are into the region we just munmap()ed - ldlibs->cache_data = NULL; - ldlibs->cache.new = NULL; - ldlibs->cache.old = NULL; - - if( ldlibs->cache_fd >= 0 ) - { - close( ldlibs->cache_fd ); - ldlibs->cache_fd = -1; - } - - ldlibs->ctype = CACHE_NONE; - ldlibs->last_idx = 0; - ldlibs->elf_class = ELFCLASSNONE; - ldlibs->elf_machine = EM_NONE; - - ldlibs->prefix.len = 0; - ldlibs->prefix.path[0] = '\0'; - - if( ldlibs->error ) - free( ldlibs->error ); - - ldlibs->error = NULL; -} - -static const char * -_rtldstr(int flag) -{ - char flags[160] = { 0 }; - char *f = &flags[0]; - - if( !flag) - return "LOCAL"; - -#define RTLDFLAGSTR(x) \ - if( x & flag ) f += snprintf(f, &flags[80] - f, " %s", & #x [5]) - - RTLDFLAGSTR(RTLD_LAZY); - RTLDFLAGSTR(RTLD_NOW); - RTLDFLAGSTR(RTLD_NOLOAD); - RTLDFLAGSTR(RTLD_DEEPBIND); - RTLDFLAGSTR(RTLD_GLOBAL); - RTLDFLAGSTR(RTLD_NODELETE); - - return ( flags[0] == ' ' ) ? &flags[1] : &flags[0]; -} - -// And now we actually open everything we have found, in reverse -// dependency order (which prevents dlmopen from going and finding -// DT_NEEDED values from outside the capsule), which it will do -// if we don't work backwards: -static void * -load_ldlibs (ldlibs_t *ldlibs, Lmid_t *namespace, int flag, int *errcode, char **error) -{ - int go; - Lmid_t lm = (*namespace > 0) ? *namespace : LM_ID_NEWLM; - void *ret = NULL; - - if( !flag ) - flag = RTLD_LAZY; - - do - { - go = 0; - - for( int j = 0; j < DSO_LIMIT; j++ ) - { - // reached the end of the list - if( !ldlibs->needed[j].name ) - continue; - - // library has no further dependencies which have not already - // been satisfied (except for the libc and linker DSOs), - // this means we can safely open it without dlmopen accidentally - // pulling in DSOs from outside the encapsulated tree: - if( ldlibs->needed[j].depcount == 0 ) - { - const char *path = ldlibs->needed[j].path; - go++; - - LDLIB_DEBUG( ldlibs, DEBUG_CAPSULE, - "DLMOPEN %p %s %s", - (void *)lm, _rtldstr(flag), path ); - - // The actual dlmopen. If this was the first one, it may - // have created a new link map id, wich we record later on: - - // note that since we do the opens in reverse dependency order, - // the _last_ one we open will be the DSO we actually asked for - // so if we succeed, ret has to contain the right handle. - ret = dlmopen( lm, path, flag ); - - if( !ret ) - { - if( error ) - *error = dlerror(); - - if( errcode ) - *errcode = EINVAL; - - return NULL; - } - - // If this was the first dlmopen, record the new LM Id - // for return to our caller: - if( lm == LM_ID_NEWLM ) - { - dlinfo( ret, RTLD_DI_LMID, namespace ); - lm = *namespace; - LDLIB_DEBUG( ldlibs, DEBUG_CAPSULE, - "new Lmid_t handle %p\n", (void *)lm ); - } - - // go through the map of DSOs and reduce the dependency - // count for any DSOs which had the current DSO as a dep: - for( int k = 0; k < DSO_LIMIT; k++ ) - if( ldlibs->needed[j].requestors[k] ) - ldlibs->needed[k].depcount--; - - clear_needed( &ldlibs->needed[j] ); - } - } - } while (go); - - return ret; -} - -static void -init_ldlibs (ldlibs_t *ldlibs, - const char **exclude, - const char *prefix, - unsigned long dbg, - int *errcode, - char **error) -{ - memset( ldlibs, 0, sizeof(ldlibs_t) ); - ldlibs->cache_fd = -1; - ldlibs->ctype = CACHE_NONE; - ldlibs->elf_class = ELFCLASSNONE; - ldlibs->elf_machine = EM_NONE; - ldlibs->exclude = exclude; - ldlibs->debug = dbg; - - if( errcode ) - *errcode = 0; - - // super important, 0 is valid but is usually stdin, - // don't want to go stomping all over that by accident: - for( int x = 0; x < DSO_LIMIT; x++ ) - ldlibs->needed[x].fd = -1; - - set_elf_constraints(ldlibs); - // ================================================================== - // set up the path prefix at which we expect to find the encapsulated - // library and its ld.so.cache and dependencies and so forth: - if( prefix ) - { - size_t prefix_len = strlen( prefix ); - ssize_t space = PATH_MAX - prefix_len; - - // if we don't have at least this much space it's not - // going to work out: - if( (space - strlen( "/usr/lib/libx.so.x" )) <= 0 ) - { - if( error ) - *error = strdup( "capsule_dlmopen: prefix is too large" ); - - if( errcode ) - *errcode = ENAMETOOLONG; - - return; - } - - safe_strncpy( ldlibs->prefix.path, prefix, PATH_MAX ); - ldlibs->prefix.len = prefix_len; - } - else - { - ldlibs->prefix.path[0] = '\0'; - ldlibs->prefix.len = 0; - } } // ========================================================================== // some pretty printers for debugging: -static int -dump_ld_entry (ldlibs_t *ldlibs, - const char *name, - int flag, - unsigned int osv, - uint64_t hwcap, - const char *path, - void *cb_data) -{ - fprintf( stderr, "%s → %s\n", name, path ); - - fputs( " type: ", stderr ); - switch (flag & FLAG_TYPE_MASK) - { - case FLAG_LIBC4: - case FLAG_ELF: - case FLAG_ELF_LIBC5: - case FLAG_ELF_LIBC6: - fputs( flag_descr[flag & FLAG_TYPE_MASK], stderr ); - break; - default: - fputs ("???", stdout); - break; - } - fputs( "\n", stderr ); - - fputs( " requires: ", stderr ); - switch (flag & FLAG_REQUIRED_MASK) - { - case FLAG_SPARC_LIB64: - fputs ("Sparc 64bit", stderr); - break; - case FLAG_IA64_LIB64: - fputs ("IA-64", stderr); - break; - case FLAG_X8664_LIB64: - fputs ("x86-64", stderr); - break; - case FLAG_S390_LIB64: - fputs ("s390 64bit", stderr); - break; - case FLAG_POWERPC_LIB64: - fputs ("PPC 64bit", stderr); - break; - case FLAG_MIPS64_LIBN32: - fputs ("MIPS N32", stderr); - break; - case FLAG_MIPS64_LIBN64: - fputs ("MIPS 64bit", stderr); - break; - case FLAG_X8664_LIBX32: - fputs ("x32", stderr); - break; - case FLAG_ARM_LIBHF: - fputs ("Arm hard-float", stderr); - break; - case FLAG_AARCH64_LIB64: - fputs ("AArch64", stderr); - break; - /* Uses the ARM soft-float ABI. */ - case FLAG_ARM_LIBSF: - fputs ("Arm soft-float", stderr); - break; - case FLAG_MIPS_LIB32_NAN2008: - fputs ("MIPS nan2008", stderr); - break; - case FLAG_MIPS64_LIBN32_NAN2008: - fputs ("MIPS N32 nan2008", stderr); - break; - case FLAG_MIPS64_LIBN64_NAN2008: - fputs ("IPS 64bit nan2008", stderr); - break; - case 0: - break; - default: - fprintf (stderr, "%0x", flag & FLAG_REQUIRED_MASK); - break; - } - fputs( "\n", stderr ); - - unsigned int os = osv >> 24; - - fprintf( stderr, " OS ABI: %s %d.%d.%d (%0x)\n", - abi_tag_os[ os > MAXTAG ? MAXTAG : os ], - (osv >> 16) & 0xff, - (osv >> 8 ) & 0xff, - osv & 0xff, - osv ); - - fprintf( stderr, " hwcaps: %0"FMT_XU64"\n", hwcap ); - return 0; -} // dump out the contents of the ld cache to stderr: static void -dump_ld_cache (ldlibs_t *ldlibs) +dump_ld_cache (ld_libs_t *ldlibs) { - iterate_ldcache( ldlibs, dump_ld_entry, NULL ); + ld_cache_foreach( &ldlibs->ldcache, ld_entry_dump, stderr ); } static void @@ -1439,6 +209,7 @@ static int install_wrappers ( void *dl_handle, return replacements; } + // ========================================================================== void * capsule_dlmopen (const char *dso, @@ -1451,23 +222,12 @@ capsule_dlmopen (const char *dso, char **error) { void *ret = NULL; - ldlibs_t ldlibs = { 0 }; + ld_libs_t ldlibs = {}; if( dbg == 0 ) dbg = debug_flags; - if( elf_version(EV_CURRENT) == EV_NONE ) - { - if( error ) - *error = strdup( "capsule_dlmopen: incompatible libelf version" ); - - if( errcode ) - *errcode = elf_errno(); - - return NULL; - } - - init_ldlibs( &ldlibs, exclude, prefix, dbg, errcode, error ); + ld_libs_init( &ldlibs, exclude, prefix, dbg, errcode ); if( errcode && *errcode ) return NULL; @@ -1495,31 +255,20 @@ capsule_dlmopen (const char *dso, // ================================================================== // find the starting point of our capsule - if( !dso_find( dso, &ldlibs, 0 ) ) + if( !ld_libs_set_target( &ldlibs, dso ) ) { - int rv = (errno == 0) ? EINVAL : errno; - if( error ) { - int elf_rv; - - if( ldlibs.error ) - { - *error = ldlibs.error; - ldlibs.error = NULL; - } - else if( (elf_rv = elf_errno()) ) - { - *error = strdup( elf_errmsg(elf_rv) ); - } - else - { - *error = strdup( "capsule_dlmopen: could not open dso" ); - } + *error = ldlibs.error; + ldlibs.error = NULL; + } + else + { + free( ldlibs.error ); } if( errcode ) - *errcode = rv; + *errcode = ENOENT; goto cleanup; } @@ -1528,7 +277,7 @@ capsule_dlmopen (const char *dso, // once we have the starting point recursively find all its DT_NEEDED // entries, except for the linker itself and libc, which must not // be different between the capsule and the "real" DSO environment: - dso_iterate_sections( &ldlibs, 0 ); + ld_libs_find_dependencies( &ldlibs ); if( ldlibs.error ) { @@ -1546,7 +295,8 @@ capsule_dlmopen (const char *dso, // ================================================================== // load the stack of DSOs we need: - ret = load_ldlibs( &ldlibs, namespace, 0, errcode, error ); + ret = ld_libs_load( &ldlibs, namespace, 0, errcode ); + if( !ret ) goto cleanup; @@ -1557,7 +307,7 @@ capsule_dlmopen (const char *dso, install_wrappers( ret, wrappers, exclude, errcode, error ); cleanup: - cleanup_ldlibs( &ldlibs ); + ld_libs_finish( &ldlibs ); return ret; } @@ -1571,7 +321,7 @@ capsule_shim_dlopen(Lmid_t ns, void *res = NULL; int code = 0; char *errors = NULL; - ldlibs_t ldlibs = { 0 }; + ld_libs_t ldlibs = {}; DEBUG( DEBUG_WRAPPERS, "dlopen(%s, %x) wrapper: LMID: %ld; prefix: %s;", @@ -1579,7 +329,7 @@ capsule_shim_dlopen(Lmid_t ns, if( prefix && strcmp(prefix, "/") ) { - init_ldlibs( &ldlibs, exclude, prefix, debug_flags, &code, &errors ); + ld_libs_init( &ldlibs, exclude, prefix, debug_flags, &code ); if( !load_ld_cache( &ldlibs, "/etc/ld.so.cache" ) ) { @@ -1591,7 +341,7 @@ capsule_shim_dlopen(Lmid_t ns, } // find the initial DSO (ie what the caller actually asked for): - if( !dso_find( file, &ldlibs, 0 ) ) + if( !ld_libs_set_target( &ldlibs, file ) ) { int rv = (errno == 0) ? EINVAL : errno; @@ -1602,7 +352,7 @@ capsule_shim_dlopen(Lmid_t ns, } // harvest all the requested DSO's dependencies: - dso_iterate_sections( &ldlibs, 0 ); + ld_libs_find_dependencies( &ldlibs ); if( ldlibs.error ) { @@ -1611,7 +361,7 @@ capsule_shim_dlopen(Lmid_t ns, } // load them up in reverse dependency order: - res = load_ldlibs( &ldlibs, &ns, flag, &code, &errors ); + res = ld_libs_load( &ldlibs, &ns, flag, &code ); if( !res ) DEBUG( DEBUG_WRAPPERS, @@ -1631,6 +381,6 @@ capsule_shim_dlopen(Lmid_t ns, return res; cleanup: - cleanup_ldlibs( &ldlibs ); + ld_libs_finish( &ldlibs ); return res; }