diff options
author | 2021-04-16 14:59:55 -0400 | |
---|---|---|
committer | 2021-04-16 14:59:55 -0400 | |
commit | c68cfa4e47d59e9903509a1d90da178a982eb27e (patch) | |
tree | c7638d8512c260daac2e6c252d0157894298e4d7 /paxldso.c | |
parent | build: support debugging/testing object internals (diff) | |
download | pax-utils-c68cfa4e47d59e9903509a1d90da178a982eb27e.tar.gz pax-utils-c68cfa4e47d59e9903509a1d90da178a982eb27e.tar.bz2 pax-utils-c68cfa4e47d59e9903509a1d90da178a982eb27e.zip |
paxldso: add support for "new" glibc cache format
Glibc has supported a "new" cache format for a while now (almost 20
years), but since it generated both, we didn't bother to implement
it, only relying on the old cache format. With glibc-2.32+ though,
it now only outputs the new format.
Rework the code so we support the old, new, and compat cache formats
(where the new format is appended to the old one).
Bug: https://bugs.gentoo.org/739014
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Diffstat (limited to 'paxldso.c')
-rw-r--r-- | paxldso.c | 235 |
1 files changed, 179 insertions, 56 deletions
@@ -14,19 +14,28 @@ #if PAX_LDSO_CACHE -static void *ldcache = NULL; +/* Memory region containing a specific cache. Will be a subset of the mmap. */ +static const void *ldcache = NULL; static size_t ldcache_size = 0; +/* Entire memory mapped cache file. */ +static void *ldcache_mmap_base = NULL; +static size_t ldcache_mmap_size = 0; + static char *ldso_cache_buf = NULL; static size_t ldso_cache_buf_size = 0; #if defined(__GLIBC__) || defined(__UCLIBC__) /* Defines can be seen in glibc's sysdeps/generic/ldconfig.h */ -#define LDSO_CACHE_MAGIC "ld.so-" -#define LDSO_CACHE_MAGIC_LEN (sizeof LDSO_CACHE_MAGIC -1) -#define LDSO_CACHE_VER "1.7.0" -#define LDSO_CACHE_VER_LEN (sizeof LDSO_CACHE_VER -1) +#define LDSO_CACHE_MAGIC_OLD "ld.so-" +#define LDSO_CACHE_MAGIC_OLD_LEN (sizeof LDSO_CACHE_MAGIC_OLD - 1) +#define LDSO_CACHE_VER_OLD "1.7.0" +#define LDSO_CACHE_VER_OLD_LEN (sizeof LDSO_CACHE_VER_OLD - 1) +#define LDSO_CACHE_MAGIC_NEW "glibc-ld.so.cache" +#define LDSO_CACHE_MAGIC_NEW_LEN (sizeof LDSO_CACHE_MAGIC_NEW - 1) +#define LDSO_CACHE_VER_NEW "1.1" +#define LDSO_CACHE_VER_NEW_LEN (sizeof LDSO_CACHE_VER_NEW - 1) #define FLAG_ANY -1 #define FLAG_TYPE_MASK 0x00ff #define FLAG_LIBC4 0x0000 @@ -48,14 +57,45 @@ static size_t ldso_cache_buf_size = 0; #define FLAG_MIPS_LIB32_NAN2008 0x0c00 #define FLAG_MIPS64_LIBN32_NAN2008 0x0d00 #define FLAG_MIPS64_LIBN64_NAN2008 0x0e00 +#define FLAG_RISCV_FLOAT_ABI_SOFT 0x0f00 +#define FLAG_RISCV_FLOAT_ABI_DOUBLE 0x1000 typedef struct { int flags; unsigned int sooffset; unsigned int liboffset; -} libentry_t; +} libentry_old_t; + +typedef struct { + const char magic[LDSO_CACHE_MAGIC_OLD_LEN]; + const char version[LDSO_CACHE_VER_OLD_LEN]; + unsigned int nlibs; + libentry_old_t libs[0]; +} header_old_t; + +typedef struct { + int32_t flags; + uint32_t sooffset; + uint32_t liboffset; + uint32_t osversion; + uint64_t hwcap; +} libentry_new_t; -static bool is_compatible(elfobj *elf, libentry_t *libent) +typedef struct { + const char magic[LDSO_CACHE_MAGIC_NEW_LEN]; + const char version[LDSO_CACHE_VER_NEW_LEN]; + uint32_t nlibs; + uint32_t len_strings; + uint8_t flags; + uint8_t _pad_flags[3]; + uint32_t extension_offset; + uint32_t _pad_ext[3]; + libentry_new_t libs[0]; +} header_new_t; + +static bool ldcache_is_new; + +static bool is_compatible(elfobj *elf, const libentry_old_t *libent) { int flags = libent->flags & FLAG_REQUIRED_MASK; @@ -136,74 +176,124 @@ static bool is_compatible(elfobj *elf, libentry_t *libent) return false; } -char *ldso_cache_lookup_lib(elfobj *elf, const char *fname) +static void ldso_cache_load(void) { - unsigned int nlib; - char *ret = NULL; - char *strs; + int fd; + const char *cachefile; + struct stat st; + const header_old_t *header_old; + const header_new_t *header_new; - typedef struct { - char magic[LDSO_CACHE_MAGIC_LEN]; - char version[LDSO_CACHE_VER_LEN]; - unsigned int nlibs; - } header_t; - header_t *header; + if (ldcache_mmap_base != NULL) + return; - libentry_t *libent; + cachefile = root_rel_path(ldcache_path); - if (fname == NULL) - return NULL; + if (fstatat(root_fd, cachefile, &st, 0)) + return; - if (ldcache == NULL) { - int fd; - const char *cachefile = root_rel_path(ldcache_path); - struct stat st; + fd = openat(root_fd, cachefile, O_RDONLY); + if (fd == -1) + return; - if (fstatat(root_fd, cachefile, &st, 0)) - return NULL; + /* cache these values so we only map/unmap the cache file once */ + ldcache_mmap_size = st.st_size; + ldcache_mmap_base = mmap(0, ldcache_mmap_size, PROT_READ, MAP_SHARED, fd, 0); + close(fd); - fd = openat(root_fd, cachefile, O_RDONLY); - if (fd == -1) - return NULL; - - /* cache these values so we only map/unmap the cache file once */ - ldcache_size = st.st_size; - header = ldcache = mmap(0, ldcache_size, PROT_READ, MAP_SHARED, fd, 0); - close(fd); + if (ldcache_mmap_base == MAP_FAILED) { + ldcache_mmap_base = NULL; + return; + } - if (ldcache == MAP_FAILED) { - ldcache = NULL; - return NULL; + ldcache_size = ldcache_mmap_size; + ldcache = ldcache_mmap_base; + header_old = ldcache; + header_new = ldcache; +#define memeq(mem1, mem2) (memcmp(mem1, mem2, sizeof(mem2) - 1) == 0) + if (memeq(header_new->magic, LDSO_CACHE_MAGIC_NEW) && + memeq(header_new->version, LDSO_CACHE_VER_NEW)) { + ldcache_is_new = true; + } else if (memeq(header_old->magic, LDSO_CACHE_MAGIC_OLD) && + memeq(header_old->version, LDSO_CACHE_VER_OLD)) { + /* See if the new cache format is appended after the old cache. */ + uintptr_t end = + (uintptr_t)ldcache + sizeof(header_old_t) + + (header_old->nlibs * sizeof(libentry_old_t)); + header_new = (const void *)ALIGN_UP(end, __alignof__(header_new_t)); + if (memeq(header_new->magic, LDSO_CACHE_MAGIC_NEW) && + memeq(header_new->version, LDSO_CACHE_VER_NEW)) { + ldcache_is_new = true; + ldcache_size -= ((uintptr_t)header_new - (uintptr_t)ldcache); + ldcache = header_new; + } else { + ldcache_is_new = false; } + } else { + munmap(ldcache_mmap_base, ldcache_mmap_size); + ldcache_mmap_base = NULL; + return; + } +#undef memq - if (memcmp(header->magic, LDSO_CACHE_MAGIC, LDSO_CACHE_MAGIC_LEN) || - memcmp(header->version, LDSO_CACHE_VER, LDSO_CACHE_VER_LEN)) - { - munmap(ldcache, ldcache_size); - ldcache = NULL; - return NULL; - } + ldso_cache_buf_size = 4096; + ldso_cache_buf = xrealloc(ldso_cache_buf, ldso_cache_buf_size); +} - ldso_cache_buf_size = 4096; - ldso_cache_buf = xrealloc(ldso_cache_buf, ldso_cache_buf_size); - } else - header = ldcache; +char *ldso_cache_lookup_lib(elfobj *elf, const char *fname) +{ + unsigned int nlib, nlibs; + char *ret = NULL; + const char *strs; + const libentry_old_t *libent_old; + const libentry_new_t *libent_new; - libent = ldcache + sizeof(header_t); - strs = (char *) &libent[header->nlibs]; + if (fname == NULL) + return NULL; - for (nlib = 0; nlib < header->nlibs; ++nlib) { + ldso_cache_load(); + if (ldcache == NULL) + return NULL; + + if (ldcache_is_new) { + const header_new_t *header = ldcache; + libent_old = NULL; + libent_new = &header->libs[0]; + strs = (const char *)header; + nlibs = header->nlibs; + } else { + const header_old_t *header = ldcache; + libent_old = &header->libs[0]; + libent_new = NULL; + strs = (const char *)&libent_old[header->nlibs]; + nlibs = header->nlibs; + } + + /* + * TODO: Should add memory range checking in case cache file is corrupt. + * TODO: We search the cache from start to finish, but since we know the cache + * is sorted, we really should be doing a binary search to speed it up. + */ + for (nlib = 0; nlib < nlibs; ++nlib) { const char *lib; size_t lib_len; - if (!is_compatible(elf, &libent[nlib])) + /* The first few fields are the same between new/old formats. */ + const libentry_old_t *libent; + if (ldcache_is_new) { + libent = (void *)&libent_new[nlib]; + } else { + libent = &libent_old[nlib]; + } + + if (!is_compatible(elf, libent)) continue; - if (strcmp(fname, strs + libent[nlib].sooffset) != 0) + if (strcmp(fname, strs + libent->sooffset) != 0) continue; /* Return first hit because that is how the ldso rolls */ - lib = strs + libent[nlib].liboffset; + lib = strs + libent->liboffset; lib_len = strlen(lib) + 1; if (lib_len > ldso_cache_buf_size) { ldso_cache_buf = xrealloc(ldso_cache_buf, ldso_cache_buf_size + 4096); @@ -223,8 +313,8 @@ static void ldso_cache_cleanup(void) { free(ldso_cache_buf); - if (ldcache != NULL) - munmap(ldcache, ldcache_size); + if (ldcache_mmap_base != NULL) + munmap(ldcache_mmap_base, ldcache_mmap_size); } #else @@ -379,12 +469,45 @@ const char argv0[] = "paxldso"; int main(int argc, char *argv[]) { elfobj *elf = readelf(argv[0]); + ldso_cache_load(); + printf("cache file memory base is %p\n", ldcache_mmap_base); + printf("cache memory base is %p\n", ldcache); for (int i = 1; i < argc; ++i) { const char *search = argv[i]; const char *lib = ldso_cache_lookup_lib(elf, search); printf("%s -> %s\n", search, lib); } unreadelf(elf); + + if (ldcache) { + unsigned int nlib; + const char *strs, *s; + + if (ldcache_is_new) { + const header_new_t *header = ldcache; + const libentry_new_t *libents = &header->libs[0]; + strs = (const char *)header; + printf("dumping new cache format\n"); + + for (nlib = 0; nlib < header->nlibs; ++nlib) { + const libentry_new_t *libent = &libents[nlib]; + s = strs + libent->sooffset; + printf("%p: %s\n", libent, s); + } + } else { + const header_old_t *header = ldcache; + const libentry_old_t *libents = &header->libs[0]; + strs = (const char *)&libents[header->nlibs]; + printf("dumping old cache format\n"); + + for (nlib = 0; nlib < header->nlibs; ++nlib) { + const libentry_old_t *libent = &libents[nlib]; + s = strs + libent->sooffset; + printf("%p: %s\n", libent, s); + } + } + } + paxldso_cleanup(); } |