diff --git a/libexec/rtld-elf/Makefile b/libexec/rtld-elf/Makefile index 9ca390d..cf9eb34 100644 --- a/libexec/rtld-elf/Makefile +++ b/libexec/rtld-elf/Makefile @@ -1,59 +1,94 @@ -# $Id: Makefile 115 2016-01-14 02:55:19Z reddawg $ -# Application Makefile (C) 2002-2004 The UbixOS Project +# $FreeBSD: releng/11.1/libexec/rtld-elf/Makefile 319016 2017-05-28 00:13:44Z rgrimes $ -# Include Global 'Source' Options -include ../../Makefile.incl -include ../Makefile.incl +# Use the following command to build local debug version of dynamic +# linker: +# make DEBUG_FLAGS=-g DEBUG=-DDEBUG WITHOUT_TESTS=yes all -INCLUDES = -I../../include.new -I./ +.include +PACKAGE= clibs +MK_SSP= no -CFLAGS+= -Wall -DFREEBSD_ELF -DIN_RTLD -DPIC -CFLAGS = -m32 -O2 -fno-strict-aliasing -pipe -Wall -DFREEBSD_ELF -DIN_RTLD -I./ -fpic -DPIC -std=gnu99 -Wformat=2 -Wno-format-extra-args -Werror -DDEBUG -#MrOlsen 2016-01-13 CFLAGS = -O2 -fno-strict-aliasing -pipe -Wall -DFREEBSD_ELF -DIN_RTLD -I./ -fpic -elf -DPIC -std=gnu99 -Wformat=2 -Wno-format-extra-args -Werror -DDEBUG +PROG?= ld-elf.so.1 +.if (${PROG:M*ld-elf32*} != "") +TAGS+= lib32 +.endif +SRCS= rtld_start.S \ + reloc.c rtld.c rtld_lock.c rtld_printf.c map_object.c \ + malloc.c xmalloc.c debug.c libmap.c +MAN= rtld.1 +CSTD?= gnu99 +CFLAGS+= -Wall -DFREEBSD_ELF -DIN_RTLD -ffreestanding +CFLAGS+= -I${SRCTOP}/lib/csu/common +.if exists(${.CURDIR}/${MACHINE_ARCH}) +RTLD_ARCH= ${MACHINE_ARCH} +.else +RTLD_ARCH= ${MACHINE_CPUARCH} +.endif +CFLAGS+= -I${.CURDIR}/${RTLD_ARCH} -I${.CURDIR} +.if ${MACHINE_ARCH} == "powerpc64" +LDFLAGS+= -nostdlib -e _rtld_start +.else +LDFLAGS+= -nostdlib -e .rtld_start +.endif +WARNS?= 2 +INSTALLFLAGS= -C -b +PRECIOUSPROG= +BINDIR= /libexec +SYMLINKS= ../..${BINDIR}/${PROG} ${LIBEXECDIR}/${PROG} +MLINKS= rtld.1 ld-elf.so.1.1 \ + rtld.1 ld.so.1 + +.if ${MACHINE_CPUARCH} == "sparc64" +CFLAGS+= -fPIC +.else +CFLAGS+= -fpic +.endif +CFLAGS+= -DPIC $(DEBUG) +.if ${MACHINE_CPUARCH} == "amd64" || ${MACHINE_CPUARCH} == "i386" +CFLAGS+= -fvisibility=hidden +.endif +LDFLAGS+= -shared -Wl,-Bsymbolic -Wl,-z,defs +LIBADD= c_pic +.if ${MK_TOOLCHAIN} == "no" +LDFLAGS+= -L${LIBCDIR} +.endif + +.if ${MACHINE_CPUARCH} == "arm" +# Some of the required math functions (div & mod) are implemented in +# libcompiler_rt on ARM. The library also needs to be placed first to be +# correctly linked. As some of the functions are used before we have +# shared libraries. +LIBADD+= compiler_rt +.endif -#Linker -LD = ld -#Binary File Name -OUTPUT = ld-elf.so.1 +.if ${MK_SYMVER} == "yes" +VERSION_DEF= ${LIBCSRCDIR}/Versions.def +SYMBOL_MAPS= ${.CURDIR}/Symbol.map +VERSION_MAP= Version.map +LDFLAGS+= -Wl,--version-script=${VERSION_MAP} -#Delete Program -REMOVE = rm -f +.if exists(${.CURDIR}/${RTLD_ARCH}/Symbol.map) +SYMBOL_MAPS+= ${.CURDIR}/${RTLD_ARCH}/Symbol.map +.endif +.endif -#Objects -OBJS = rtld_start.o reloc.o rtld.o rtld_lock.o map_object.o malloc.o xmalloc.o debug.o libmap.o +.sinclude "${.CURDIR}/${RTLD_ARCH}/Makefile.inc" -LIBRARIES = #../../lib/libc/libc.so +# Since moving rtld-elf to /libexec, we need to create a symlink. +# Fixup the existing binary that's there so we can symlink over it. +beforeinstall: +.if exists(${DESTDIR}/usr/libexec/${PROG}) && ${MK_STAGING} == "no" + -chflags -h noschg ${DESTDIR}/usr/libexec/${PROG} +.endif +.PATH: ${.CURDIR}/${RTLD_ARCH} -# Link The Binary -$(OUTPUT) : $(OBJS) -# $(CC) -fpic -nostdlib -shared -Wl,-soname,$(OUTPUT) -e .rtld_start -o $(OUTPUT) $(OBJS) $(LIBRARIES) $(SUBS) -# $(CC) $(CFLAGS) -nostdlib -e .rtld_start -elf -shared -Wl,-Bsymbolic -o $(OUTPUT) $(OBJS) -lc_pic -# $(CC) $(CFLAGS) -nostdlib -e .rtld_start -elf -shared -Wl,-Bsymbolic -o $(OUTPUT) $(OBJS) ./libc_pic.a #-lc_pic - $(CC) $(CFLAGS) -nostdlib -e .rtld_start -elf -shared -Wl,-Bsymbolic -o ../../build/libexec/$(OUTPUT) $(OBJS) ../../lib/libc/pic.a #-lc_pic -# strip $(OUTPUT) +.if ${MK_TESTS} != "no" +SUBDIR+= tests +.endif -# Compile the source files -.cpp.o: - $(CXX) -Wall -O $(CFLAGS) $(INCLUDES) -c -o $@ $< - -.cc.o: - $(CXX) -Wall -O $(CFLAGS) $(INCLUDES) -c -o $@ $< - -.cc.s: - $(CXX) -Wall -O $(CFLAGS) $(INCLUDES) -S -o $@ $< - -.c.o: - $(CC) -Wall -O $(CFLAGS) $(INCLUDES) -c -o $@ $< - -.c.s: - $(CC) -Wall -O $(CFLAGS) $(INCLUDES) -S -o $@ $< - -.S.o: - $(CC) -Wall $(CFLAGS) $(INCLUDES) -c -o $@ $< - -# Clean Up The junk -clean: - $(REMOVE) $(OBJS) $(OUTPUT) +.include +${PROG_FULL}: ${VERSION_MAP} +.include diff --git a/libexec/rtld-elf/debug.c b/libexec/rtld-elf/debug.c index 3a83b66..dfca5fa 100644 --- a/libexec/rtld-elf/debug.c +++ b/libexec/rtld-elf/debug.c @@ -22,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD: src/libexec/rtld-elf/debug.c,v 1.4 2003/06/19 16:09:18 mdodd Exp $ + * $FreeBSD: releng/11.1/libexec/rtld-elf/debug.c 294373 2016-01-20 07:21:33Z kib $ */ /* @@ -34,6 +34,7 @@ #include "debug.h" #include "rtld.h" +#include "rtld_printf.h" static const char rel_header[] = " symbol name r_info r_offset st_value st_size address value\n" @@ -49,9 +50,8 @@ va_list ap; va_start(ap, format); - fflush(stdout); - vfprintf(stderr, format, ap); - putc('\n', stderr); + rtld_vfdprintf(STDERR_FILENO, format, ap); + rtld_fdputchar(STDERR_FILENO, '\n'); va_end(ap); } @@ -62,7 +62,8 @@ { Obj_Entry *obj; - for (obj = obj0; obj != NULL; obj = obj->next) { + for (obj = globallist_curr(obj0); obj != NULL; + obj = globallist_next(obj)) { dump_obj_relocations(obj); } } @@ -71,28 +72,28 @@ dump_obj_relocations (Obj_Entry *obj) { - printf("Object \"%s\", relocbase %p\n", obj->path, obj->relocbase); + rtld_printf("Object \"%s\", relocbase %p\n", obj->path, obj->relocbase); if (obj->relsize) { - printf("Non-PLT Relocations: %ld\n", + rtld_printf("Non-PLT Relocations: %ld\n", (obj->relsize / sizeof(Elf_Rel))); dump_Elf_Rel(obj, obj->rel, obj->relsize); } if (obj->relasize) { - printf("Non-PLT Relocations with Addend: %ld\n", + rtld_printf("Non-PLT Relocations with Addend: %ld\n", (obj->relasize / sizeof(Elf_Rela))); dump_Elf_Rela(obj, obj->rela, obj->relasize); } if (obj->pltrelsize) { - printf("PLT Relocations: %ld\n", + rtld_printf("PLT Relocations: %ld\n", (obj->pltrelsize / sizeof(Elf_Rel))); dump_Elf_Rel(obj, obj->pltrel, obj->pltrelsize); } if (obj->pltrelasize) { - printf("PLT Relocations with Addend: %ld\n", + rtld_printf("PLT Relocations with Addend: %ld\n", (obj->pltrelasize / sizeof(Elf_Rela))); dump_Elf_Rela(obj, obj->pltrela, obj->pltrelasize); } @@ -106,12 +107,12 @@ const Elf_Sym *sym; Elf_Addr *dstaddr; - printf("%s", rel_header); + rtld_putstr(rel_header); rellim = (const Elf_Rel *)((const char *)rel0 + relsize); for (rel = rel0; rel < rellim; rel++) { dstaddr = (Elf_Addr *)(obj->relocbase + rel->r_offset); sym = obj->symtab + ELF_R_SYM(rel->r_info); - printf(rel_format, + rtld_printf(rel_format, obj->strtab + sym->st_name, (u_long)rel->r_info, (u_long)rel->r_offset, (u_long)sym->st_value, (int)sym->st_size, @@ -128,12 +129,12 @@ const Elf_Sym *sym; Elf_Addr *dstaddr; - printf("%s", rel_header); + rtld_putstr(rel_header); relalim = (const Elf_Rela *)((const char *)rela0 + relasize); for (rela = rela0; rela < relalim; rela++) { dstaddr = (Elf_Addr *)(obj->relocbase + rela->r_offset); sym = obj->symtab + ELF_R_SYM(rela->r_info); - printf(rel_format, + rtld_printf(rel_format, obj->strtab + sym->st_name, (u_long)rela->r_info, (u_long)rela->r_offset, (u_long)sym->st_value, (int)sym->st_size, diff --git a/libexec/rtld-elf/debug.h b/libexec/rtld-elf/debug.h index 68faa0a..93fc88f 100644 --- a/libexec/rtld-elf/debug.h +++ b/libexec/rtld-elf/debug.h @@ -22,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD: src/libexec/rtld-elf/debug.h,v 1.6 2004/03/21 01:21:26 peter Exp $ + * $FreeBSD: releng/11.1/libexec/rtld-elf/debug.h 282551 2015-05-06 15:29:11Z emaste $ */ /* @@ -32,10 +32,6 @@ #ifndef DEBUG_H #define DEBUG_H 1 -#ifndef __GNUC__ -#error "This file must be compiled with GCC" -#endif - #include #include @@ -45,9 +41,9 @@ extern int debug; #ifdef DEBUG -#define dbg(format, args...) debug_printf(format , ## args) +#define dbg(...) debug_printf(__VA_ARGS__) #else -#define dbg(format, args...) ((void) 0) +#define dbg(...) ((void) 0) #endif #ifndef COMPAT_32BIT diff --git a/libexec/rtld-elf/libmap.c b/libexec/rtld-elf/libmap.c index b78fe4c..677e29d 100644 --- a/libexec/rtld-elf/libmap.c +++ b/libexec/rtld-elf/libmap.c @@ -1,32 +1,27 @@ /* - * $FreeBSD: src/libexec/rtld-elf/libmap.c,v 1.14 2005/02/04 02:46:41 mdodd Exp $ + * $FreeBSD: releng/11.1/libexec/rtld-elf/libmap.c 290223 2015-10-31 04:39:55Z imp $ */ -#include -#include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include "debug.h" #include "rtld.h" #include "libmap.h" - -#ifndef _PATH_LIBMAP_CONF -#define _PATH_LIBMAP_CONF "/etc/libmap.conf" -#endif - -#ifdef COMPAT_32BIT -#undef _PATH_LIBMAP_CONF -#define _PATH_LIBMAP_CONF "/etc/libmap32.conf" -#endif +#include "paths.h" TAILQ_HEAD(lm_list, lm); struct lm { char *f; char *t; - TAILQ_ENTRY(lm) lm_link; }; @@ -38,76 +33,198 @@ TAILQ_ENTRY(lmp) lmp_link; }; -static int lm_count; +static TAILQ_HEAD(lmc_list, lmc) lmc_head = TAILQ_HEAD_INITIALIZER(lmc_head); +struct lmc { + char *path; + TAILQ_ENTRY(lmc) next; +}; -static void lmc_parse (FILE *); -static void lm_add (const char *, const char *, const char *); -static void lm_free (struct lm_list *); -static char * lml_find (struct lm_list *, const char *); -static struct lm_list * lmp_find (const char *); -static struct lm_list * lmp_init (char *); -static const char * quickbasename (const char *); -static int readstrfn (void * cookie, char *buf, int len); -static int closestrfn (void * cookie); +static int lm_count; + +static void lmc_parse(char *, size_t); +static void lmc_parse_file(char *); +static void lmc_parse_dir(char *); +static void lm_add(const char *, const char *, const char *); +static void lm_free(struct lm_list *); +static char *lml_find(struct lm_list *, const char *); +static struct lm_list *lmp_find(const char *); +static struct lm_list *lmp_init(char *); +static const char *quickbasename(const char *); #define iseol(c) (((c) == '#') || ((c) == '\0') || \ ((c) == '\n') || ((c) == '\r')) +/* + * Do not use ctype.h macros, which rely on working TLS. It is + * too early to have thread-local variables functional. + */ +#define rtld_isspace(c) ((c) == ' ' || (c) == '\t') + int -lm_init (char *libmap_override) +lm_init(char *libmap_override) { - FILE *fp; + char *p; - dbg("%s(\"%s\")", __func__, libmap_override); - + dbg("lm_init(\"%s\")", libmap_override); TAILQ_INIT(&lmp_head); - fp = fopen(_PATH_LIBMAP_CONF, "r"); - if (fp) { - lmc_parse(fp); - fclose(fp); - } + lmc_parse_file(ld_path_libmap_conf); if (libmap_override) { - char *p; - /* do some character replacement to make $LIBMAP look like a - text file, then "open" it with funopen */ + /* + * Do some character replacement to make $LDLIBMAP look + * like a text file, then parse it. + */ libmap_override = xstrdup(libmap_override); - for (p = libmap_override; *p; p++) { switch (*p) { - case '=': - *p = ' '; break; - case ',': - *p = '\n'; break; + case '=': + *p = ' '; + break; + case ',': + *p = '\n'; + break; } } - fp = funopen(libmap_override, readstrfn, NULL, NULL, closestrfn); - if (fp) { - lmc_parse(fp); - fclose(fp); - } + lmc_parse(libmap_override, p - libmap_override); + free(libmap_override); } return (lm_count == 0); } static void -lmc_parse (FILE *fp) +lmc_parse_file(char *path) { - char *cp; - char *f, *t, *c, *p; - char prog[MAXPATHLEN]; - char line[MAXPATHLEN + 2]; + struct lmc *p; + struct stat st; + int fd; + char *rpath; + char *lm_map; - dbg("%s(%p)", __func__, fp); - + rpath = realpath(path, NULL); + if (rpath == NULL) + return; + + TAILQ_FOREACH(p, &lmc_head, next) { + if (strcmp(p->path, rpath) == 0) { + free(rpath); + return; + } + } + + fd = open(rpath, O_RDONLY | O_CLOEXEC); + if (fd == -1) { + dbg("lm_parse_file: open(\"%s\") failed, %s", rpath, + rtld_strerror(errno)); + free(rpath); + return; + } + if (fstat(fd, &st) == -1) { + close(fd); + dbg("lm_parse_file: fstat(\"%s\") failed, %s", rpath, + rtld_strerror(errno)); + free(rpath); + return; + } + lm_map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (lm_map == (const char *)MAP_FAILED) { + close(fd); + dbg("lm_parse_file: mmap(\"%s\") failed, %s", rpath, + rtld_strerror(errno)); + free(rpath); + return; + } + close(fd); + p = xmalloc(sizeof(struct lmc)); + p->path = rpath; + TAILQ_INSERT_HEAD(&lmc_head, p, next); + lmc_parse(lm_map, st.st_size); + munmap(lm_map, st.st_size); +} + +static void +lmc_parse_dir(char *idir) +{ + DIR *d; + struct dirent *dp; + struct lmc *p; + char conffile[MAXPATHLEN]; + char *ext; + char *rpath; + + rpath = realpath(idir, NULL); + if (rpath == NULL) + return; + + TAILQ_FOREACH(p, &lmc_head, next) { + if (strcmp(p->path, rpath) == 0) { + free(rpath); + return; + } + } + d = opendir(idir); + if (d == NULL) { + free(rpath); + return; + } + + p = xmalloc(sizeof(struct lmc)); + p->path = rpath; + TAILQ_INSERT_HEAD(&lmc_head, p, next); + + while ((dp = readdir(d)) != NULL) { + if (dp->d_ino == 0) + continue; + if (dp->d_type != DT_REG) + continue; + ext = strrchr(dp->d_name, '.'); + if (ext == NULL) + continue; + if (strcmp(ext, ".conf") != 0) + continue; + if (strlcpy(conffile, idir, MAXPATHLEN) >= MAXPATHLEN) + continue; /* too long */ + if (strlcat(conffile, "/", MAXPATHLEN) >= MAXPATHLEN) + continue; /* too long */ + if (strlcat(conffile, dp->d_name, MAXPATHLEN) >= MAXPATHLEN) + continue; /* too long */ + lmc_parse_file(conffile); + } + closedir(d); +} + +static void +lmc_parse(char *lm_p, size_t lm_len) +{ + char *cp, *f, *t, *c, *p; + char prog[MAXPATHLEN]; + /* allow includedir + full length path */ + char line[MAXPATHLEN + 13]; + size_t cnt; + int i; + + cnt = 0; p = NULL; - while ((cp = fgets(line, MAXPATHLEN + 1, fp)) != NULL) { + while (cnt < lm_len) { + i = 0; + while (cnt < lm_len && lm_p[cnt] != '\n' && + i < sizeof(line) - 1) { + line[i] = lm_p[cnt]; + cnt++; + i++; + } + line[i] = '\0'; + while (cnt < lm_len && lm_p[cnt] != '\n') + cnt++; + /* skip over nl */ + cnt++; + + cp = &line[0]; t = f = c = NULL; /* Skip over leading space */ - while (isspace(*cp)) cp++; + while (rtld_isspace(*cp)) cp++; /* Found a comment or EOL */ if (iseol(*cp)) continue; @@ -117,7 +234,7 @@ cp++; /* Skip leading space */ - while (isspace(*cp)) cp++; + while (rtld_isspace(*cp)) cp++; /* Found comment, EOL or end of selector */ if (iseol(*cp) || *cp == ']') @@ -125,11 +242,11 @@ c = cp++; /* Skip to end of word */ - while (!isspace(*cp) && !iseol(*cp) && *cp != ']') + while (!rtld_isspace(*cp) && !iseol(*cp) && *cp != ']') cp++; /* Skip and zero out trailing space */ - while (isspace(*cp)) *cp++ = '\0'; + while (rtld_isspace(*cp)) *cp++ = '\0'; /* Check if there is a closing brace */ if (*cp != ']') continue; @@ -141,36 +258,42 @@ * There should be nothing except whitespace or comment from this point to the end of the line. */ - while(isspace(*cp)) cp++; + while(rtld_isspace(*cp)) cp++; if (!iseol(*cp)) continue; - strcpy(prog, c); + if (strlcpy(prog, c, sizeof prog) >= sizeof prog) + continue; p = prog; continue; } /* Parse the 'from' candidate. */ f = cp++; - while (!isspace(*cp) && !iseol(*cp)) cp++; + while (!rtld_isspace(*cp) && !iseol(*cp)) cp++; /* Skip and zero out the trailing whitespace */ - while (isspace(*cp)) *cp++ = '\0'; + while (rtld_isspace(*cp)) *cp++ = '\0'; /* Found a comment or EOL */ if (iseol(*cp)) continue; /* Parse 'to' mapping */ t = cp++; - while (!isspace(*cp) && !iseol(*cp)) cp++; + while (!rtld_isspace(*cp) && !iseol(*cp)) cp++; /* Skip and zero out the trailing whitespace */ - while (isspace(*cp)) *cp++ = '\0'; + while (rtld_isspace(*cp)) *cp++ = '\0'; /* Should be no extra tokens at this point */ if (!iseol(*cp)) continue; *cp = '\0'; - lm_add(p, f, t); + if (strcmp(f, "includedir") == 0) + lmc_parse_dir(t); + else if (strcmp(f, "include") == 0) + lmc_parse_file(t); + else + lm_add(p, f, t); } } @@ -195,9 +318,17 @@ lm_fini (void) { struct lmp *lmp; + struct lmc *p; dbg("%s()", __func__); + while (!TAILQ_EMPTY(&lmc_head)) { + p = TAILQ_FIRST(&lmc_head); + TAILQ_REMOVE(&lmc_head, p, next); + free(p->path); + free(p); + } + while (!TAILQ_EMPTY(&lmp_head)) { lmp = TAILQ_FIRST(&lmp_head); TAILQ_REMOVE(&lmp_head, lmp, lmp_link); @@ -257,26 +388,22 @@ /* Given a libmap translation list and a library name, return the replacement library, or NULL */ -#ifdef COMPAT_32BIT char * lm_findn (const char *p, const char *f, const int n) { char pathbuf[64], *s, *t; - if (n < sizeof(pathbuf) - 1) { - memcpy(pathbuf, f, n); - pathbuf[n] = '\0'; + if (n < sizeof(pathbuf) - 1) s = pathbuf; - } else { + else s = xmalloc(n + 1); - strcpy(s, f); - } + memcpy(s, f, n); + s[n] = '\0'; t = lm_find(p, s); if (s != pathbuf) free(s); return (t); } -#endif static char * lml_find (struct lm_list *lmh, const char *f) @@ -341,31 +468,3 @@ } return (p); } - -static int -readstrfn(void * cookie, char *buf, int len) -{ - static char *current; - static int left; - int copied; - - copied = 0; - if (!current) { - current = cookie; - left = strlen(cookie); - } - while (*current && left && len) { - *buf++ = *current++; - left--; - len--; - copied++; - } - return copied; -} - -static int -closestrfn(void * cookie) -{ - free(cookie); - return 0; -} diff --git a/libexec/rtld-elf/libmap.h b/libexec/rtld-elf/libmap.h index 6b0991d..9a7b668 100644 --- a/libexec/rtld-elf/libmap.h +++ b/libexec/rtld-elf/libmap.h @@ -1,10 +1,8 @@ /* - * $FreeBSD: src/libexec/rtld-elf/libmap.h,v 1.4 2005/02/04 02:46:41 mdodd Exp $ + * $FreeBSD: releng/11.1/libexec/rtld-elf/libmap.h 255765 2013-09-21 21:03:52Z des $ */ int lm_init (char *); void lm_fini (void); char * lm_find (const char *, const char *); -#ifdef COMPAT_32BIT char * lm_findn (const char *, const char *, const int); -#endif diff --git a/libexec/rtld-elf/malloc.c b/libexec/rtld-elf/malloc.c index 3c43e5f..1f30265 100644 --- a/libexec/rtld-elf/malloc.c +++ b/libexec/rtld-elf/malloc.c @@ -29,7 +29,7 @@ #if defined(LIBC_SCCS) && !defined(lint) /*static char *sccsid = "from: @(#)malloc.c 5.11 (Berkeley) 2/23/91";*/ -static char *rcsid = "$FreeBSD: releng/10.2/libexec/rtld-elf/malloc.c 281452 2015-04-12 06:43:13Z kib $"; +static char *rcsid = "$FreeBSD: releng/11.1/libexec/rtld-elf/malloc.c 298897 2016-05-01 19:39:23Z pfg $"; #endif /* LIBC_SCCS and not lint */ /* @@ -45,7 +45,6 @@ #include #include -#include #include #include #include @@ -225,7 +224,7 @@ * Record allocated size of block and * bound space with magic numbers. */ - op->ov_size = (nbytes + RSLOP - 1) & ~(RSLOP - 1); + op->ov_size = roundup2(nbytes, RSLOP); op->ov_rmagic = RMAGIC; *(u_short *)((caddr_t)(op + 1) + op->ov_size) = RMAGIC; #endif @@ -329,7 +328,7 @@ * old malloc man page, it realloc's an already freed block. Usually * this is the last block it freed; occasionally it might be farther * back. We have to search all the free lists for the block in order - * to determine its bucket: 1st we make one pass thru the lists + * to determine its bucket: 1st we make one pass through the lists * checking only the first block in each; if that fails we search * ``realloc_srchlen'' blocks in each list for a match (the variable * is extern so the caller can modify it). If that fails we just copy @@ -389,7 +388,7 @@ } if (nbytes <= onb && nbytes > (size_t)i) { #ifdef RCHECK - op->ov_size = (nbytes + RSLOP - 1) & ~(RSLOP - 1); + op->ov_size = roundup2(nbytes, RSLOP); *(u_short *)((caddr_t)(op + 1) + op->ov_size) = RMAGIC; #endif return(cp); diff --git a/libexec/rtld-elf/map_object.c b/libexec/rtld-elf/map_object.c index 8d5f24a..9b13a13 100644 --- a/libexec/rtld-elf/map_object.c +++ b/libexec/rtld-elf/map_object.c @@ -22,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD: src/libexec/rtld-elf/map_object.c,v 1.16 2005/02/27 12:55:40 dfr Exp $ + * $FreeBSD: releng/11.1/libexec/rtld-elf/map_object.c 319134 2017-05-29 13:18:20Z kib $ */ #include @@ -38,8 +38,7 @@ #include "debug.h" #include "rtld.h" -static Elf_Ehdr *get_elf_header (int, const char *); -static int convert_prot(int); /* Elf flags -> mmap protection */ +static Elf_Ehdr *get_elf_header(int, const char *, const struct stat *); static int convert_flags(int); /* Elf flags -> mmap flags */ /* @@ -61,15 +60,14 @@ Elf_Phdr **segs; int nsegs; Elf_Phdr *phdyn; - Elf_Phdr *phphdr; Elf_Phdr *phinterp; Elf_Phdr *phtls; caddr_t mapbase; size_t mapsize; - Elf_Off base_offset; Elf_Addr base_vaddr; Elf_Addr base_vlimit; caddr_t base_addr; + int base_flags; Elf_Off data_offset; Elf_Addr data_vaddr; Elf_Addr data_vlimit; @@ -79,26 +77,41 @@ Elf_Addr clear_vaddr; caddr_t clear_addr; caddr_t clear_page; - size_t nclear; + Elf_Addr phdr_vaddr; + size_t nclear, phsize; Elf_Addr bss_vaddr; Elf_Addr bss_vlimit; caddr_t bss_addr; + Elf_Word stack_flags; + Elf_Addr relro_page; + size_t relro_size; + Elf_Addr note_start; + Elf_Addr note_end; + char *note_map; + size_t note_map_len; - hdr = get_elf_header(fd, path); + hdr = get_elf_header(fd, path, sb); if (hdr == NULL) return (NULL); /* * Scan the program header entries, and save key information. * - * We rely on there being exactly two load segments, text and data, - * in that order. + * We expect that the loadable segments are ordered by load address. */ phdr = (Elf_Phdr *) ((char *)hdr + hdr->e_phoff); + phsize = hdr->e_phnum * sizeof (phdr[0]); phlimit = phdr + hdr->e_phnum; nsegs = -1; - phdyn = phphdr = phinterp = phtls = NULL; + phdyn = phinterp = phtls = NULL; + phdr_vaddr = 0; + relro_page = 0; + relro_size = 0; + note_start = 0; + note_end = 0; + note_map = NULL; segs = alloca(sizeof(segs[0]) * hdr->e_phnum); + stack_flags = RTLD_DEFAULT_STACK_PF_EXEC | PF_R | PF_W; while (phdr < phlimit) { switch (phdr->p_type) { @@ -108,15 +121,16 @@ case PT_LOAD: segs[++nsegs] = phdr; - if (segs[nsegs]->p_align < PAGE_SIZE) { + if ((segs[nsegs]->p_align & (PAGE_SIZE - 1)) != 0) { _rtld_error("%s: PT_LOAD segment %d not page-aligned", path, nsegs); - return NULL; + goto error; } break; case PT_PHDR: - phphdr = phdr; + phdr_vaddr = phdr->p_vaddr; + phsize = phdr->p_memsz; break; case PT_DYNAMIC: @@ -126,45 +140,75 @@ case PT_TLS: phtls = phdr; break; + + case PT_GNU_STACK: + stack_flags = phdr->p_flags; + break; + + case PT_GNU_RELRO: + relro_page = phdr->p_vaddr; + relro_size = phdr->p_memsz; + break; + + case PT_NOTE: + if (phdr->p_offset > PAGE_SIZE || + phdr->p_offset + phdr->p_filesz > PAGE_SIZE) { + note_map_len = round_page(phdr->p_offset + + phdr->p_filesz) - trunc_page(phdr->p_offset); + note_map = mmap(NULL, note_map_len, PROT_READ, + MAP_PRIVATE, fd, trunc_page(phdr->p_offset)); + if (note_map == MAP_FAILED) { + _rtld_error("%s: error mapping PT_NOTE (%d)", path, errno); + goto error; + } + note_start = (Elf_Addr)(note_map + phdr->p_offset - + trunc_page(phdr->p_offset)); + } else { + note_start = (Elf_Addr)(char *)hdr + phdr->p_offset; + } + note_end = note_start + phdr->p_filesz; + break; } ++phdr; } if (phdyn == NULL) { _rtld_error("%s: object is not dynamically-linked", path); - return NULL; + goto error; } if (nsegs < 0) { _rtld_error("%s: too few PT_LOAD segments", path); - return NULL; + goto error; } /* * Map the entire address space of the object, to stake out our * contiguous region, and to establish the base address for relocation. */ - base_offset = trunc_page(segs[0]->p_offset); base_vaddr = trunc_page(segs[0]->p_vaddr); base_vlimit = round_page(segs[nsegs]->p_vaddr + segs[nsegs]->p_memsz); mapsize = base_vlimit - base_vaddr; - base_addr = hdr->e_type == ET_EXEC ? (caddr_t) base_vaddr : NULL; + base_addr = (caddr_t) base_vaddr; + base_flags = MAP_PRIVATE | MAP_ANON | MAP_NOCORE; + if (npagesizes > 1 && round_page(segs[0]->p_filesz) >= pagesizes[1]) + base_flags |= MAP_ALIGNED_SUPER; + if (base_vaddr != 0) + base_flags |= MAP_FIXED | MAP_EXCL; - mapbase = mmap(base_addr, mapsize, convert_prot(segs[0]->p_flags), - convert_flags(segs[0]->p_flags), fd, base_offset); + mapbase = mmap(base_addr, mapsize, PROT_NONE, base_flags, -1, 0); if (mapbase == (caddr_t) -1) { _rtld_error("%s: mmap of entire address space failed: %s", - path, strerror(errno)); - return NULL; + path, rtld_strerror(errno)); + goto error; } if (base_addr != NULL && mapbase != base_addr) { _rtld_error("%s: mmap returned wrong address: wanted %p, got %p", path, base_addr, mapbase); - munmap(mapbase, mapsize); - return NULL; + goto error1; } - for (i = 0; i <= nsegs; i++) { + for (i = 0; i <= nsegs; i++) { /* Overlay the segment onto the proper region. */ data_offset = trunc_page(segs[i]->p_offset); data_vaddr = trunc_page(segs[i]->p_vaddr); @@ -172,44 +216,55 @@ data_addr = mapbase + (data_vaddr - base_vaddr); data_prot = convert_prot(segs[i]->p_flags); data_flags = convert_flags(segs[i]->p_flags) | MAP_FIXED; - /* Do not call mmap on the first segment - this is redundant */ - if (i && mmap(data_addr, data_vlimit - data_vaddr, data_prot, - data_flags, fd, data_offset) == (caddr_t) -1) { - _rtld_error("%s: mmap of data failed: %s", path, strerror(errno)); - return NULL; + if (mmap(data_addr, data_vlimit - data_vaddr, data_prot, + data_flags | MAP_PREFAULT_READ, fd, data_offset) == (caddr_t) -1) { + _rtld_error("%s: mmap of data failed: %s", path, + rtld_strerror(errno)); + goto error1; } - /* Clear any BSS in the last page of the segment. */ - clear_vaddr = segs[i]->p_vaddr + segs[i]->p_filesz; - clear_addr = mapbase + (clear_vaddr - base_vaddr); - clear_page = mapbase + (trunc_page(clear_vaddr) - base_vaddr); - if ((nclear = data_vlimit - clear_vaddr) > 0) { - /* Make sure the end of the segment is writable */ - if ((data_prot & PROT_WRITE) == 0 && - -1 == mprotect(clear_page, PAGE_SIZE, data_prot|PROT_WRITE)) { + /* Do BSS setup */ + if (segs[i]->p_filesz != segs[i]->p_memsz) { + + /* Clear any BSS in the last page of the segment. */ + clear_vaddr = segs[i]->p_vaddr + segs[i]->p_filesz; + clear_addr = mapbase + (clear_vaddr - base_vaddr); + clear_page = mapbase + (trunc_page(clear_vaddr) - base_vaddr); + + if ((nclear = data_vlimit - clear_vaddr) > 0) { + /* Make sure the end of the segment is writable */ + if ((data_prot & PROT_WRITE) == 0 && -1 == + mprotect(clear_page, PAGE_SIZE, data_prot|PROT_WRITE)) { _rtld_error("%s: mprotect failed: %s", path, - strerror(errno)); - return NULL; + rtld_strerror(errno)); + goto error1; + } + + memset(clear_addr, 0, nclear); + + /* Reset the data protection back */ + if ((data_prot & PROT_WRITE) == 0) + mprotect(clear_page, PAGE_SIZE, data_prot); } - memset(clear_addr, 0, nclear); - - /* Reset the data protection back */ - if ((data_prot & PROT_WRITE) == 0) - mprotect(clear_page, PAGE_SIZE, data_prot); + /* Overlay the BSS segment onto the proper region. */ + bss_vaddr = data_vlimit; + bss_vlimit = round_page(segs[i]->p_vaddr + segs[i]->p_memsz); + bss_addr = mapbase + (bss_vaddr - base_vaddr); + if (bss_vlimit > bss_vaddr) { /* There is something to do */ + if (mmap(bss_addr, bss_vlimit - bss_vaddr, data_prot, + data_flags | MAP_ANON, -1, 0) == (caddr_t)-1) { + _rtld_error("%s: mmap of bss failed: %s", path, + rtld_strerror(errno)); + goto error1; + } + } } - /* Overlay the BSS segment onto the proper region. */ - bss_vaddr = data_vlimit; - bss_vlimit = round_page(segs[i]->p_vaddr + segs[i]->p_memsz); - bss_addr = mapbase + (bss_vaddr - base_vaddr); - if (bss_vlimit > bss_vaddr) { /* There is something to do */ - if (mmap(bss_addr, bss_vlimit - bss_vaddr, data_prot, - MAP_PRIVATE|MAP_FIXED|MAP_ANON, -1, 0) == (caddr_t) -1) { - _rtld_error("%s: mmap of bss failed: %s", path, - strerror(errno)); - return NULL; - } + if (phdr_vaddr == 0 && data_offset <= hdr->e_phoff && + (data_vlimit - data_vaddr + data_offset) >= + (hdr->e_phoff + hdr->e_phnum * sizeof (Elf_Phdr))) { + phdr_vaddr = data_vaddr + hdr->e_phoff - data_offset; } } @@ -227,10 +282,19 @@ obj->dynamic = (const Elf_Dyn *) (obj->relocbase + phdyn->p_vaddr); if (hdr->e_entry != 0) obj->entry = (caddr_t) (obj->relocbase + hdr->e_entry); - if (phphdr != NULL) { - obj->phdr = (const Elf_Phdr *) (obj->relocbase + phphdr->p_vaddr); - obj->phsize = phphdr->p_memsz; + if (phdr_vaddr != 0) { + obj->phdr = (const Elf_Phdr *) (obj->relocbase + phdr_vaddr); + } else { + obj->phdr = malloc(phsize); + if (obj->phdr == NULL) { + obj_free(obj); + _rtld_error("%s: cannot allocate program header", path); + goto error1; + } + memcpy((char *)obj->phdr, (char *)hdr + hdr->e_phoff, phsize); + obj->phdr_alloc = true; } + obj->phsize = phsize; if (phinterp != NULL) obj->interp = (const char *) (obj->relocbase + phinterp->p_vaddr); if (phtls != NULL) { @@ -241,63 +305,87 @@ obj->tlsinitsize = phtls->p_filesz; obj->tlsinit = mapbase + phtls->p_vaddr; } - return obj; + obj->stack_flags = stack_flags; + obj->relro_page = obj->relocbase + trunc_page(relro_page); + obj->relro_size = round_page(relro_size); + if (note_start < note_end) + digest_notes(obj, note_start, note_end); + if (note_map != NULL) + munmap(note_map, note_map_len); + munmap(hdr, PAGE_SIZE); + return (obj); + +error1: + munmap(mapbase, mapsize); +error: + if (note_map != NULL && note_map != MAP_FAILED) + munmap(note_map, note_map_len); + munmap(hdr, PAGE_SIZE); + return (NULL); } static Elf_Ehdr * -get_elf_header (int fd, const char *path) +get_elf_header(int fd, const char *path, const struct stat *sbp) { - static union { - Elf_Ehdr hdr; - char buf[PAGE_SIZE]; - } u; - ssize_t nbytes; + Elf_Ehdr *hdr; - if ((nbytes = read(fd, u.buf, PAGE_SIZE)) == -1) { - _rtld_error("%s: read error: %s", path, strerror(errno)); - return NULL; - } + /* Make sure file has enough data for the ELF header */ + if (sbp != NULL && sbp->st_size < sizeof(Elf_Ehdr)) { + _rtld_error("%s: invalid file format", path); + return (NULL); + } - /* Make sure the file is valid */ - if (nbytes < (ssize_t)sizeof(Elf_Ehdr) || !IS_ELF(u.hdr)) { - _rtld_error("%s: invalid file format", path); - return NULL; - } - if (u.hdr.e_ident[EI_CLASS] != ELF_TARG_CLASS - || u.hdr.e_ident[EI_DATA] != ELF_TARG_DATA) { - _rtld_error("%s: unsupported file layout", path); - return NULL; - } - if (u.hdr.e_ident[EI_VERSION] != EV_CURRENT - || u.hdr.e_version != EV_CURRENT) { - _rtld_error("%s: unsupported file version", path); - return NULL; - } - if (u.hdr.e_type != ET_EXEC && u.hdr.e_type != ET_DYN) { - _rtld_error("%s: unsupported file type", path); - return NULL; - } - if (u.hdr.e_machine != ELF_TARG_MACH) { - _rtld_error("%s: unsupported machine", path); - return NULL; - } + hdr = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_PRIVATE | MAP_PREFAULT_READ, + fd, 0); + if (hdr == (Elf_Ehdr *)MAP_FAILED) { + _rtld_error("%s: read error: %s", path, rtld_strerror(errno)); + return (NULL); + } - /* - * We rely on the program header being in the first page. This is - * not strictly required by the ABI specification, but it seems to - * always true in practice. And, it simplifies things considerably. - */ - if (u.hdr.e_phentsize != sizeof(Elf_Phdr)) { - _rtld_error( - "%s: invalid shared object: e_phentsize != sizeof(Elf_Phdr)", path); - return NULL; - } - if (u.hdr.e_phoff + u.hdr.e_phnum * sizeof(Elf_Phdr) > (size_t)nbytes) { - _rtld_error("%s: program header too large", path); - return NULL; - } + /* Make sure the file is valid */ + if (!IS_ELF(*hdr)) { + _rtld_error("%s: invalid file format", path); + goto error; + } + if (hdr->e_ident[EI_CLASS] != ELF_TARG_CLASS || + hdr->e_ident[EI_DATA] != ELF_TARG_DATA) { + _rtld_error("%s: unsupported file layout", path); + goto error; + } + if (hdr->e_ident[EI_VERSION] != EV_CURRENT || + hdr->e_version != EV_CURRENT) { + _rtld_error("%s: unsupported file version", path); + goto error; + } + if (hdr->e_type != ET_EXEC && hdr->e_type != ET_DYN) { + _rtld_error("%s: unsupported file type", path); + goto error; + } + if (hdr->e_machine != ELF_TARG_MACH) { + _rtld_error("%s: unsupported machine", path); + goto error; + } - return (&u.hdr); + /* + * We rely on the program header being in the first page. This is + * not strictly required by the ABI specification, but it seems to + * always true in practice. And, it simplifies things considerably. + */ + if (hdr->e_phentsize != sizeof(Elf_Phdr)) { + _rtld_error( + "%s: invalid shared object: e_phentsize != sizeof(Elf_Phdr)", path); + goto error; + } + if (hdr->e_phoff + hdr->e_phnum * sizeof(Elf_Phdr) > + (size_t)PAGE_SIZE) { + _rtld_error("%s: program header too large", path); + goto error; + } + return (hdr); + +error: + munmap(hdr, PAGE_SIZE); + return (NULL); } void @@ -305,15 +393,18 @@ { 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,8 +415,18 @@ STAILQ_REMOVE_HEAD(&obj->dagmembers, link); free(elm); } - free(obj->origin_path); - free(obj->priv); + if (obj->vertab) + free(obj->vertab); + if (obj->origin_path) + free(obj->origin_path); + if (obj->z_origin) + free(obj->rpath); + if (obj->priv) + free(obj->priv); + if (obj->path) + free(obj->path); + if (obj->phdr_alloc) + free((void *)obj->phdr); free(obj); } @@ -337,6 +438,7 @@ obj = CNEW(Obj_Entry); STAILQ_INIT(&obj->dldags); STAILQ_INIT(&obj->dagmembers); + STAILQ_INIT(&obj->names); return obj; } @@ -344,7 +446,7 @@ * Given a set of ELF protection flags, return the corresponding protection * flags for MMAP. */ -static int +int convert_prot(int elfflags) { int prot = 0; diff --git a/libexec/rtld-elf/reloc.c b/libexec/rtld-elf/reloc.c deleted file mode 100644 index ba9be47..0000000 --- a/libexec/rtld-elf/reloc.c +++ /dev/null @@ -1,367 +0,0 @@ -/*- - * Copyright 1996, 1997, 1998, 1999 John D. Polstra. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: src/libexec/rtld-elf/i386/reloc.c,v 1.18 2005/06/29 23:15:36 peter Exp $ - */ - -/* - * Dynamic linker for ELF. - * - * John Polstra . - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "debug.h" -#include "rtld.h" - -/* - * Process the special R_386_COPY relocations in the main program. These - * copy data from a shared object into a region in the main program's BSS - * segment. - * - * Returns 0 on success, -1 on failure. - */ -int -do_copy_relocations(Obj_Entry *dstobj) -{ - const Elf_Rel *rellim; - const Elf_Rel *rel; - - assert(dstobj->mainprog); /* COPY relocations are invalid elsewhere */ - - rellim = (const Elf_Rel *) ((caddr_t) dstobj->rel + dstobj->relsize); - for (rel = dstobj->rel; rel < rellim; rel++) { - if (ELF_R_TYPE(rel->r_info) == R_386_COPY) { - void *dstaddr; - const Elf_Sym *dstsym; - const char *name; - unsigned long hash; - size_t size; - const void *srcaddr; - const Elf_Sym *srcsym; - Obj_Entry *srcobj; - - 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; - - for (srcobj = dstobj->next; srcobj != NULL; srcobj = srcobj->next) - if ((srcsym = symlook_obj(name, hash, srcobj, false)) != NULL) - break; - - if (srcobj == NULL) { - _rtld_error("Undefined symbol \"%s\" referenced from COPY" - " relocation in %s", name, dstobj->path); - return -1; - } - - srcaddr = (const void *) (srcobj->relocbase + srcsym->st_value); - memcpy(dstaddr, srcaddr, size); - } - } - - return 0; -} - -/* Initialize the special GOT entries. */ -void -init_pltgot(Obj_Entry *obj) -{ - if (obj->pltgot != NULL) { - obj->pltgot[1] = (Elf_Addr) obj; - obj->pltgot[2] = (Elf_Addr) &_rtld_bind_start; - } -} - -/* Process the non-PLT relocations. */ -int -reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld) -{ - const Elf_Rel *rellim; - const Elf_Rel *rel; - SymCache *cache; - int bytes = obj->nchains * sizeof(SymCache); - int r = -1; - - /* - * The dynamic loader may be called from a thread, we have - * limited amounts of stack available so we cannot use alloca(). - */ - cache = mmap(NULL, bytes, PROT_READ|PROT_WRITE, MAP_ANON, -1, 0); - if (cache == MAP_FAILED) - cache = NULL; - - rellim = (const Elf_Rel *) ((caddr_t) obj->rel + obj->relsize); - for (rel = obj->rel; rel < rellim; rel++) { - Elf_Addr *where = (Elf_Addr *) (obj->relocbase + rel->r_offset); - - switch (ELF_R_TYPE(rel->r_info)) { - - case R_386_NONE: - break; - - case R_386_32: - { - const Elf_Sym *def; - const Obj_Entry *defobj; - - def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, - false, cache); - - if (def == NULL) - goto done; - *where += (Elf_Addr) (defobj->relocbase + def->st_value); - } - break; - - case R_386_PC32: - /* - * I don't think the dynamic linker should ever see this - * type of relocation. But the binutils-2.6 tools sometimes - * generate it. - */ - { - const Elf_Sym *def; - const Obj_Entry *defobj; -//dbg("A.1"); - def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, - false, cache); -//dbg("A.2"); - if (def == NULL) - goto done; - - *where += - (Elf_Addr) (defobj->relocbase + def->st_value) - - (Elf_Addr) where; - } - break; - - case R_386_COPY: - /* - * These are deferred until all other relocations have - * been done. All we do here is make sure that the COPY - * relocation is not in a shared library. They are allowed - * only in executable files. - */ -//dbg("386_COPY"); - if (!obj->mainprog) { - _rtld_error("%s: Unexpected R_386_COPY relocation" - " in shared library", obj->path); - goto done; - } - break; - - case R_386_GLOB_DAT: - { -//dbg("386_GD"); - const Elf_Sym *def; - const Obj_Entry *defobj; - - def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, - false, cache); - if (def == NULL) - goto done; - - *where = (Elf_Addr) (defobj->relocbase + def->st_value); - } - break; - - case R_386_RELATIVE: - *where += (Elf_Addr) obj->relocbase; - break; - - case R_386_TLS_TPOFF: - { - const Elf_Sym *def; - const Obj_Entry *defobj; - - def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, - false, cache); - if (def == NULL) - goto done; - - /* - * We lazily allocate offsets for static TLS as we - * see the first relocation that references the - * TLS block. This allows us to support (small - * amounts of) static TLS in dynamically loaded - * modules. If we run out of space, we generate an - * error. - */ - if (!defobj->tls_done) { - if (!allocate_tls_offset((Obj_Entry*) defobj)) { - _rtld_error("%s: No space available for static " - "Thread Local Storage", obj->path); - goto done; - } - } - - *where += (Elf_Addr) (def->st_value - defobj->tlsoffset); - } - break; - - case R_386_TLS_DTPMOD32: - { - const Elf_Sym *def; - const Obj_Entry *defobj; - - def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, - false, cache); - if (def == NULL) - goto done; - - *where += (Elf_Addr) defobj->tlsindex; - } - break; - - case R_386_TLS_DTPOFF32: - { - const Elf_Sym *def; - const Obj_Entry *defobj; - - def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, - false, cache); - if (def == NULL) - goto done; - - *where += (Elf_Addr) def->st_value; - } - break; - - default: - _rtld_error("%s: Unsupported relocation type %d" - " in non-PLT relocations\n", obj->path, - ELF_R_TYPE(rel->r_info)); - goto done; - } - } - r = 0; -done: - if (cache) - munmap(cache, bytes); - return(r); -} - -/* Process the PLT relocations. */ -int -reloc_plt(Obj_Entry *obj) -{ - const Elf_Rel *rellim; - const Elf_Rel *rel; - - rellim = (const Elf_Rel *)((char *)obj->pltrel + obj->pltrelsize); - for (rel = obj->pltrel; rel < rellim; rel++) { - Elf_Addr *where; - - assert(ELF_R_TYPE(rel->r_info) == R_386_JMP_SLOT); - - /* Relocate the GOT slot pointing into the PLT. */ - where = (Elf_Addr *)(obj->relocbase + rel->r_offset); - *where += (Elf_Addr)obj->relocbase; - } - return 0; -} - -/* Relocate the jump slots in an object. */ -int -reloc_jmpslots(Obj_Entry *obj) -{ - const Elf_Rel *rellim; - const Elf_Rel *rel; - - if (obj->jmpslots_done) - return 0; - rellim = (const Elf_Rel *)((char *)obj->pltrel + obj->pltrelsize); - for (rel = obj->pltrel; rel < rellim; rel++) { - Elf_Addr *where, target; - const Elf_Sym *def; - const Obj_Entry *defobj; - - assert(ELF_R_TYPE(rel->r_info) == R_386_JMP_SLOT); - where = (Elf_Addr *)(obj->relocbase + rel->r_offset); - def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, true, NULL); - if (def == NULL) - return -1; - target = (Elf_Addr)(defobj->relocbase + def->st_value); - reloc_jmpslot(where, target, defobj, obj, rel); - } - obj->jmpslots_done = true; - return 0; -} - -void -allocate_initial_tls(Obj_Entry *objs) -{ - void* tls; - - /* - * Fix the size of the static TLS block by using the maximum - * offset allocated so far and adding a bit for dynamic modules to - * use. - */ - tls_static_space = tls_last_offset + RTLD_STATIC_TLS_EXTRA; - tls = allocate_tls(objs, NULL, 2*sizeof(Elf_Addr), sizeof(Elf_Addr)); - i386_set_gsbase(tls); -} - -/* GNU ABI */ -__attribute__((__regparm__(1))) -void *___tls_get_addr(tls_index *ti) -{ - Elf_Addr** segbase; - Elf_Addr* dtv; - - __asm __volatile("movl %%gs:0, %0" : "=r" (segbase)); - dtv = segbase[1]; - - return tls_get_addr_common(&segbase[1], ti->ti_module, ti->ti_offset); -} - -/* Sun ABI */ -void *__tls_get_addr(tls_index *ti) -{ - Elf_Addr** segbase; - Elf_Addr* dtv; - - __asm __volatile("movl %%gs:0, %0" : "=r" (segbase)); - dtv = segbase[1]; - - return tls_get_addr_common(&segbase[1], ti->ti_module, ti->ti_offset); -} diff --git a/libexec/rtld-elf/rtld.c b/libexec/rtld-elf/rtld.c index 789071b..bffcff1 100644 --- a/libexec/rtld-elf/rtld.c +++ b/libexec/rtld-elf/rtld.c @@ -1,8 +1,14 @@ /*- * Copyright 1996, 1997, 1998, 1999, 2000 John D. Polstra. * Copyright 2003 Alexander Kabaev . + * Copyright 2009-2013 Konstantin Belousov . + * Copyright 2012 John Marino . + * Copyright 2014-2017 The FreeBSD Foundation * All rights reserved. * + * Portions of this software were developed by Konstantin Belousov + * under sponsorship from the FreeBSD Foundation. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -22,8 +28,6 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $Id: rtld.c 114 2016-01-14 02:30:09Z reddawg $ */ /* @@ -32,14 +36,17 @@ * John Polstra . */ -#ifndef __GNUC__ -#error "GCC is needed to compile this file" -#endif +#include +__FBSDID("$FreeBSD: releng/11.1/libexec/rtld-elf/rtld.c 320800 2017-07-08 04:53:12Z delphij $"); #include #include #include #include +#include +#include +#include +#include #include #include @@ -54,105 +61,146 @@ #include "debug.h" #include "rtld.h" #include "libmap.h" +#include "paths.h" #include "rtld_tls.h" - -#ifndef COMPAT_32BIT -#define PATH_RTLD "/libexec/ld-elf.so.1" -#else -#define PATH_RTLD "/libexec/ld-elf32.so.1" -#endif +#include "rtld_printf.h" +#include "rtld_utrace.h" +#include "notes.h" /* Types. */ typedef void (*func_ptr_type)(); typedef void * (*path_enum_proc) (const char *path, size_t len, void *arg); /* - * This structure provides a reentrant way to keep a list of objects and - * check which ones have already been processed in some way. - */ -typedef struct Struct_DoneList { - const Obj_Entry **objs; /* Array of object pointers */ - unsigned int num_alloc; /* Allocated size of the array */ - unsigned int num_used; /* Number of array slots used */ -} DoneList; - -/* * Function declarations. */ static const char *basename(const char *); -static void die(void); +static void digest_dynamic1(Obj_Entry *, int, const Elf_Dyn **, + const Elf_Dyn **, const Elf_Dyn **); +static void digest_dynamic2(Obj_Entry *, const Elf_Dyn *, const Elf_Dyn *, + const Elf_Dyn *); 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 int dlclose_locked(void *, RtldLockState *); +static Obj_Entry *dlopen_object(const char *name, int fd, Obj_Entry *refobj, + int lo_flags, int mode, RtldLockState *lockstate); +static Obj_Entry *do_load_object(int, const char *, char *, struct stat *, int); 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 *); static char *errmsg_save(void); static void *fill_search_info(const char *, size_t, void *); -static char *find_library(const char *, const Obj_Entry *); -static const char *gethints(void); +static char *find_library(const char *, const Obj_Entry *, int *); +static const char *gethints(bool); +static void hold_object(Obj_Entry *); +static void unhold_object(Obj_Entry *); static void init_dag(Obj_Entry *); -static void init_dag1(Obj_Entry *root, Obj_Entry *obj, DoneList *); -static void init_rtld(caddr_t); -static void initlist_add_neededs(Needed_Entry *needed, Objlist *list); -static void initlist_add_objects(Obj_Entry *obj, Obj_Entry **tail, - Objlist *list); -static bool is_exported(const Elf_Sym *); +static void init_marker(Obj_Entry *); +static void init_pagesizes(Elf_Auxinfo **aux_info); +static void init_rtld(caddr_t, Elf_Auxinfo **); +static void initlist_add_neededs(Needed_Entry *, Objlist *); +static void initlist_add_objects(Obj_Entry *, Obj_Entry *, Objlist *); static void linkmap_add(Obj_Entry *); static void linkmap_delete(Obj_Entry *); -static int load_needed_objects(Obj_Entry *); +static void load_filtees(Obj_Entry *, int flags, RtldLockState *); +static void unload_filtees(Obj_Entry *, RtldLockState *); +static int load_needed_objects(Obj_Entry *, int); static int load_preload_objects(void); -static Obj_Entry *load_object(char *); +static Obj_Entry *load_object(const char *, int fd, const Obj_Entry *, int); +static void map_stacks_exec(RtldLockState *); +static int obj_enforce_relro(Obj_Entry *); static Obj_Entry *obj_from_addr(const void *); -static void objlist_call_fini(Objlist *); -static void objlist_call_init(Objlist *); +static void objlist_call_fini(Objlist *, Obj_Entry *, RtldLockState *); +static void objlist_call_init(Objlist *, RtldLockState *); static void objlist_clear(Objlist *); static Objlist_Entry *objlist_find(Objlist *, const Obj_Entry *); static void objlist_init(Objlist *); static void objlist_push_head(Objlist *, Obj_Entry *); static void objlist_push_tail(Objlist *, Obj_Entry *); +static void objlist_put_after(Objlist *, Obj_Entry *, Obj_Entry *); static void objlist_remove(Objlist *, Obj_Entry *); -static void objlist_remove_unref(Objlist *); +static int open_binary_fd(const char *argv0, bool search_in_path); +static int parse_args(char* argv[], int argc, bool *use_pathp, int *fdp); +static int parse_integer(const char *); static void *path_enumerate(const char *, path_enum_proc, void *); -static int relocate_objects(Obj_Entry *, bool, Obj_Entry *); +static void print_usage(const char *argv0); +static void release_object(Obj_Entry *); +static int relocate_object_dag(Obj_Entry *root, bool bind_now, + Obj_Entry *rtldobj, int flags, RtldLockState *lockstate); +static int relocate_object(Obj_Entry *obj, bool bind_now, Obj_Entry *rtldobj, + int flags, RtldLockState *lockstate); +static int relocate_objects(Obj_Entry *, bool, Obj_Entry *, int, + RtldLockState *); +static int resolve_objects_ifunc(Obj_Entry *first, bool bind_now, + int flags, RtldLockState *lockstate); static int rtld_dirname(const char *, char *); +static int rtld_dirname_abs(const char *, char *); +static void *rtld_dlopen(const char *name, int fd, int mode); 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 char *search_library_pathfds(const char *, const char *, int *); +static const void **get_program_var_addr(const char *, RtldLockState *); 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 void trace_loaded_objects(Obj_Entry *obj); +static int symlook_default(SymLook *, const Obj_Entry *refobj); +static int symlook_global(SymLook *, DoneList *); +static void symlook_init_from_req(SymLook *, const SymLook *); +static int symlook_list(SymLook *, const Objlist *, DoneList *); +static int symlook_needed(SymLook *, const Needed_Entry *, DoneList *); +static int symlook_obj1_sysv(SymLook *, const Obj_Entry *); +static int symlook_obj1_gnu(SymLook *, const Obj_Entry *); +static void trace_loaded_objects(Obj_Entry *); static void unlink_object(Obj_Entry *); -static void unload_object(Obj_Entry *); +static void unload_object(Obj_Entry *, RtldLockState *lockstate); static void unref_dag(Obj_Entry *); static void ref_dag(Obj_Entry *); +static char *origin_subst_one(Obj_Entry *, char *, const char *, + const char *, bool); +static char *origin_subst(Obj_Entry *, char *); +static bool obj_resolve_origin(Obj_Entry *obj); +static void preinit_main(void); +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 *); +static void ld_utrace_log(int, void *, void *, size_t, int, const char *); +static void rtld_fill_dl_phdr_info(const Obj_Entry *obj, + struct dl_phdr_info *phdr_info); +static uint32_t gnu_hash(const char *); +static bool matched_symbol(SymLook *, const Obj_Entry *, Sym_Match_Result *, + const unsigned long); -void r_debug_state(struct r_debug*, struct link_map*); +void r_debug_state(struct r_debug *, struct link_map *) __noinline __exported; +void _r_debug_postinit(struct link_map *) __noinline __exported; + +int __sys_openat(int, const char *, int, ...); /* * Data declarations. */ static char *error_message; /* Message for dlerror(), or NULL */ -struct r_debug r_debug; /* for GDB; */ +struct r_debug r_debug __exported; /* for GDB; */ static bool libmap_disable; /* Disable libmap */ +static bool ld_loadfltr; /* Immediate filters processing */ static char *libmap_override; /* Maps to use in addition to libmap.conf */ static bool trust; /* False for setuid and setgid programs */ static bool dangerous_ld_env; /* True if environment variables have been used to affect the libraries loaded */ +bool ld_bind_not; /* Disable PLT update */ static char *ld_bind_now; /* Environment variable for immediate binding */ static char *ld_debug; /* Environment variable for debugging */ static char *ld_library_path; /* Environment variable for search path */ +static char *ld_library_dirs; /* Environment variable for library descriptors */ static char *ld_preload; /* Environment variable for libraries to load first */ +static char *ld_elf_hints_path; /* Environment variable for alternative hints path */ static char *ld_tracing; /* Called from ldd to print libs */ -static Obj_Entry *obj_list; /* Head of linked list of shared objects */ -static Obj_Entry **obj_tail; /* Link field of last object in list */ +static char *ld_utrace; /* Use utrace() to log events. */ +static struct obj_entry_q obj_list; /* Queue of all loaded objects */ static Obj_Entry *obj_main; /* The main program shared object */ static Obj_Entry obj_rtld; /* The dynamic linker shared object */ static unsigned int obj_count; /* Number of objects in obj_list */ +static unsigned int obj_loads; /* Number of loads of objects (gen count) */ static Objlist list_global = /* Objects dlopened with RTLD_GLOBAL */ STAILQ_HEAD_INITIALIZER(list_global); @@ -161,39 +209,37 @@ static Objlist list_fini = /* Objects needing fini() calls */ STAILQ_HEAD_INITIALIZER(list_fini); -static Elf_Sym sym_zero; /* For resolving undefined weak refs. */ +Elf_Sym sym_zero; /* For resolving undefined weak refs. */ #define GDB_STATE(s,m) r_debug.r_state = s; r_debug_state(&r_debug,m); extern Elf_Dyn _DYNAMIC; #pragma weak _DYNAMIC -#ifndef RTLD_IS_DYNAMIC -#define RTLD_IS_DYNAMIC() (&_DYNAMIC != NULL) -#endif -/* - * These are the functions the dynamic linker exports to application - * programs. They are the only symbols the dynamic linker is willing - * to export from itself. - */ -static func_ptr_type exports[] = { - (func_ptr_type) &_rtld_error, - (func_ptr_type) &dlclose, - (func_ptr_type) &dlerror, - (func_ptr_type) &dlopen, - (func_ptr_type) &dlsym, - (func_ptr_type) &dladdr, - (func_ptr_type) &dllockinit, - (func_ptr_type) &dlinfo, - (func_ptr_type) &_rtld_thread_init, -#ifdef __i386__ - (func_ptr_type) &___tls_get_addr, -#endif - (func_ptr_type) &__tls_get_addr, - (func_ptr_type) &_rtld_allocate_tls, - (func_ptr_type) &_rtld_free_tls, - NULL -}; +int dlclose(void *) __exported; +char *dlerror(void) __exported; +void *dlopen(const char *, int) __exported; +void *fdlopen(int, int) __exported; +void *dlsym(void *, const char *) __exported; +dlfunc_t dlfunc(void *, const char *) __exported; +void *dlvsym(void *, const char *, const char *) __exported; +int dladdr(const void *, Dl_info *) __exported; +void dllockinit(void *, void *(*)(void *), void (*)(void *), void (*)(void *), + void (*)(void *), void (*)(void *), void (*)(void *)) __exported; +int dlinfo(void *, int , void *) __exported; +int dl_iterate_phdr(__dl_iterate_hdr_callback, void *) __exported; +int _rtld_addr_phdr(const void *, struct dl_phdr_info *) __exported; +int _rtld_get_stack_prot(void) __exported; +int _rtld_is_dlopened(void *) __exported; +void _rtld_error(const char *, ...) __exported; + +int npagesizes, osreldate; +size_t *pagesizes; + +long __stack_chk_guard[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + +static int stack_prot = PROT_READ | PROT_WRITE | RTLD_DEFAULT_STACK_EXEC; +static int max_stack_flags; /* * Global declarations normally provided by crt1. The dynamic linker is @@ -203,14 +249,32 @@ char **environ; /* + * Used to pass argc, argv to init functions. + */ +int main_argc; +char **main_argv; + +/* * Globals to control TLS allocation. */ size_t tls_last_offset; /* Static TLS offset of last module */ size_t tls_last_size; /* Static TLS size of last module */ size_t tls_static_space; /* Static TLS space allocated */ +size_t tls_static_max_align; int tls_dtv_generation = 1; /* Used to detect when dtv size changes */ int tls_max_index = 1; /* Largest module index allocated */ +bool ld_library_path_rpath = false; + +/* + * Globals for path names, and such + */ +char *ld_elf_hints_default = _PATH_ELF_HINTS; +char *ld_path_libmap_conf = _PATH_LIBMAP_CONF; +char *ld_path_rtld = _PATH_RTLD; +char *ld_standard_library_path = STANDARD_LIBRARY_PATH; +char *ld_env_prefix = LD_; + /* * Fill in a DoneList with an allocation large enough to hold all of * the currently-loaded objects. Keep this as a macro since it calls @@ -222,6 +286,48 @@ (dlp)->num_alloc = obj_count, \ (dlp)->num_used = 0) +#define LD_UTRACE(e, h, mb, ms, r, n) do { \ + if (ld_utrace != NULL) \ + ld_utrace_log(e, h, mb, ms, r, n); \ +} while (0) + +static void +ld_utrace_log(int event, void *handle, void *mapbase, size_t mapsize, + int refcnt, const char *name) +{ + struct utrace_rtld ut; + static const char rtld_utrace_sig[RTLD_UTRACE_SIG_SZ] = RTLD_UTRACE_SIG; + + memcpy(ut.sig, rtld_utrace_sig, sizeof(ut.sig)); + ut.event = event; + ut.handle = handle; + ut.mapbase = mapbase; + ut.mapsize = mapsize; + ut.refcnt = refcnt; + bzero(ut.name, sizeof(ut.name)); + if (name) + strlcpy(ut.name, name, sizeof(ut.name)); + utrace(&ut, sizeof(ut)); +} + +#ifdef RTLD_VARIANT_ENV_NAMES +/* + * construct the env variable based on the type of binary that's + * running. + */ +static inline const char * +_LD(const char *var) +{ + static char buffer[128]; + + strlcpy(buffer, ld_env_prefix, sizeof(buffer)); + strlcat(buffer, var, sizeof(buffer)); + return (buffer); +} +#else +#define _LD(x) LD_ x +#endif + /* * Main entry point for dynamic linking. The first argument is the * stack pointer. The stack is expected to be laid out as described @@ -241,19 +347,20 @@ func_ptr_type _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) { - Elf_Auxinfo *aux_info[AT_COUNT]; - int i; - int argc; - char **argv; - char **env; - Elf_Auxinfo *aux; - Elf_Auxinfo *auxp; - const char *argv0; + Elf_Auxinfo *aux, *auxp, *auxpf, *aux_info[AT_COUNT]; Objlist_Entry *entry; - Obj_Entry *obj; - Obj_Entry **preload_tail; + Obj_Entry *last_interposer, *obj, *preload_tail; + const Elf_Phdr *phdr; Objlist initlist; - int lockstate; + RtldLockState lockstate; + struct stat st; + Elf_Addr *argcp; + char **argv, *argv0, **env, **envp, *kexecpath, *library_path_rpath; + caddr_t imgentry; + char buf[MAXPATHLEN]; + int argc, fd, i, mib[2], phnum, rtld_argc; + size_t len; + bool dir_enable, explicit_fd, search_in_path; /* * On entry, the dynamic linker itself has not been relocated yet. @@ -263,76 +370,209 @@ */ /* Find the auxiliary vector on the stack. */ + argcp = sp; argc = *sp++; argv = (char **) sp; - sp += argc + 1; /* Skip over arguments and NULL terminator */ env = (char **) sp; - - while (*sp++ != 0); /* Skip over environment, and NULL terminator */ - + while (*sp++ != 0) /* Skip over environment, and NULL terminator */ + ; aux = (Elf_Auxinfo *) sp; - /* Digest the auxiliary vector. */ for (i = 0; i < AT_COUNT; i++) aux_info[i] = NULL; - for (auxp = aux; auxp->a_type != AT_NULL; auxp++) { - if (auxp->a_type < AT_COUNT) { - aux_info[auxp->a_type] = auxp; - } + if (auxp->a_type < AT_COUNT) + aux_info[auxp->a_type] = auxp; } /* Initialize and relocate ourselves. */ assert(aux_info[AT_BASE] != NULL); - - init_rtld((caddr_t) aux_info[AT_BASE]->a_un.a_ptr); + init_rtld((caddr_t) aux_info[AT_BASE]->a_un.a_ptr, aux_info); __progname = obj_rtld.path; argv0 = argv[0] != NULL ? argv[0] : "(null)"; environ = env; + main_argc = argc; + main_argv = argv; + + if (aux_info[AT_CANARY] != NULL && + aux_info[AT_CANARY]->a_un.a_ptr != NULL) { + i = aux_info[AT_CANARYLEN]->a_un.a_val; + if (i > sizeof(__stack_chk_guard)) + i = sizeof(__stack_chk_guard); + memcpy(__stack_chk_guard, aux_info[AT_CANARY]->a_un.a_ptr, i); + } else { + mib[0] = CTL_KERN; + mib[1] = KERN_ARND; + + len = sizeof(__stack_chk_guard); + if (sysctl(mib, 2, __stack_chk_guard, &len, NULL, 0) == -1 || + len != sizeof(__stack_chk_guard)) { + /* If sysctl was unsuccessful, use the "terminator canary". */ + ((unsigned char *)(void *)__stack_chk_guard)[0] = 0; + ((unsigned char *)(void *)__stack_chk_guard)[1] = 0; + ((unsigned char *)(void *)__stack_chk_guard)[2] = '\n'; + ((unsigned char *)(void *)__stack_chk_guard)[3] = 255; + } + } trust = !issetugid(); - ld_bind_now = getenv(LD_ "BIND_NOW"); - if (trust) { - ld_debug = getenv(LD_ "DEBUG"); - libmap_disable = getenv(LD_ "LIBMAP_DISABLE") != NULL; - libmap_override = getenv(LD_ "LIBMAP"); - ld_library_path = "/lib";//getenv(LD_ "LIBRARY_PATH"); - ld_preload = getenv(LD_ "PRELOAD"); - dangerous_ld_env = libmap_disable || (libmap_override != NULL) || - (ld_library_path != NULL) || (ld_preload != NULL); - } else - dangerous_ld_env = 0; - ld_tracing = getenv(LD_ "TRACE_LOADED_OBJECTS"); + md_abi_variant_hook(aux_info); + fd = -1; + if (aux_info[AT_EXECFD] != NULL) { + fd = aux_info[AT_EXECFD]->a_un.a_val; + } else { + assert(aux_info[AT_PHDR] != NULL); + phdr = (const Elf_Phdr *)aux_info[AT_PHDR]->a_un.a_ptr; + if (phdr == obj_rtld.phdr) { + if (!trust) { + rtld_printf("Tainted process refusing to run binary %s\n", + argv0); + rtld_die(); + } + dbg("opening main program in direct exec mode"); + if (argc >= 2) { + rtld_argc = parse_args(argv, argc, &search_in_path, &fd); + argv0 = argv[rtld_argc]; + explicit_fd = (fd != -1); + if (!explicit_fd) + fd = open_binary_fd(argv0, search_in_path); + if (fstat(fd, &st) == -1) { + _rtld_error("failed to fstat FD %d (%s): %s", fd, + explicit_fd ? "user-provided descriptor" : argv0, + rtld_strerror(errno)); + rtld_die(); + } + + /* + * Rough emulation of the permission checks done by + * execve(2), only Unix DACs are checked, ACLs are + * ignored. Preserve the semantic of disabling owner + * to execute if owner x bit is cleared, even if + * others x bit is enabled. + * mmap(2) does not allow to mmap with PROT_EXEC if + * binary' file comes from noexec mount. We cannot + * set VV_TEXT on the binary. + */ + dir_enable = false; + if (st.st_uid == geteuid()) { + if ((st.st_mode & S_IXUSR) != 0) + dir_enable = true; + } else if (st.st_gid == getegid()) { + if ((st.st_mode & S_IXGRP) != 0) + dir_enable = true; + } else if ((st.st_mode & S_IXOTH) != 0) { + dir_enable = true; + } + if (!dir_enable) { + rtld_printf("No execute permission for binary %s\n", + argv0); + rtld_die(); + } + + /* + * For direct exec mode, argv[0] is the interpreter + * name, we must remove it and shift arguments left + * before invoking binary main. Since stack layout + * places environment pointers and aux vectors right + * after the terminating NULL, we must shift + * environment and aux as well. + */ + main_argc = argc - rtld_argc; + for (i = 0; i <= main_argc; i++) + argv[i] = argv[i + rtld_argc]; + *argcp -= rtld_argc; + environ = env = envp = argv + main_argc + 1; + do { + *envp = *(envp + rtld_argc); + envp++; + } while (*envp != NULL); + aux = auxp = (Elf_Auxinfo *)envp; + auxpf = (Elf_Auxinfo *)(envp + rtld_argc); + for (;; auxp++, auxpf++) { + *auxp = *auxpf; + if (auxp->a_type == AT_NULL) + break; + } + } else { + rtld_printf("no binary\n"); + rtld_die(); + } + } + } + + ld_bind_now = getenv(_LD("BIND_NOW")); + + /* + * If the process is tainted, then we un-set the dangerous environment + * variables. The process will be marked as tainted until setuid(2) + * is called. If any child process calls setuid(2) we do not want any + * future processes to honor the potentially un-safe variables. + */ + if (!trust) { + if (unsetenv(_LD("PRELOAD")) || unsetenv(_LD("LIBMAP")) || + unsetenv(_LD("LIBRARY_PATH")) || unsetenv(_LD("LIBRARY_PATH_FDS")) || + unsetenv(_LD("LIBMAP_DISABLE")) || unsetenv(_LD("BIND_NOT")) || + unsetenv(_LD("DEBUG")) || unsetenv(_LD("ELF_HINTS_PATH")) || + unsetenv(_LD("LOADFLTR")) || unsetenv(_LD("LIBRARY_PATH_RPATH"))) { + _rtld_error("environment corrupt; aborting"); + rtld_die(); + } + } + ld_debug = getenv(_LD("DEBUG")); + if (ld_bind_now == NULL) + ld_bind_not = getenv(_LD("BIND_NOT")) != NULL; + libmap_disable = getenv(_LD("LIBMAP_DISABLE")) != NULL; + libmap_override = getenv(_LD("LIBMAP")); + ld_library_path = getenv(_LD("LIBRARY_PATH")); + ld_library_dirs = getenv(_LD("LIBRARY_PATH_FDS")); + ld_preload = getenv(_LD("PRELOAD")); + ld_elf_hints_path = getenv(_LD("ELF_HINTS_PATH")); + ld_loadfltr = getenv(_LD("LOADFLTR")) != NULL; + library_path_rpath = getenv(_LD("LIBRARY_PATH_RPATH")); + if (library_path_rpath != NULL) { + if (library_path_rpath[0] == 'y' || + library_path_rpath[0] == 'Y' || + library_path_rpath[0] == '1') + ld_library_path_rpath = true; + else + ld_library_path_rpath = false; + } + dangerous_ld_env = libmap_disable || (libmap_override != NULL) || + (ld_library_path != NULL) || (ld_preload != NULL) || + (ld_elf_hints_path != NULL) || ld_loadfltr; + ld_tracing = getenv(_LD("TRACE_LOADED_OBJECTS")); + ld_utrace = getenv(_LD("UTRACE")); + + if ((ld_elf_hints_path == NULL) || strlen(ld_elf_hints_path) == 0) + ld_elf_hints_path = ld_elf_hints_default; if (ld_debug != NULL && *ld_debug != '\0') debug = 1; - dbg("%s is initialized, base address = %p", __progname, (caddr_t) aux_info[AT_BASE]->a_un.a_ptr); dbg("RTLD dynamic = %p", obj_rtld.dynamic); dbg("RTLD pltgot = %p", obj_rtld.pltgot); + dbg("initializing thread locks"); + lockdflt_init(); + /* * Load the main program, or process its program header if it is * already loaded. */ - if (aux_info[AT_EXECFD] != NULL) { /* Load the main program. */ - int fd = aux_info[AT_EXECFD]->a_un.a_val; + if (fd != -1) { /* Load the main program. */ dbg("loading main program"); obj_main = map_object(fd, argv0, NULL); close(fd); if (obj_main == NULL) - die(); + rtld_die(); + max_stack_flags = obj->stack_flags; } else { /* Main program already loaded. */ - const Elf_Phdr *phdr; - int phnum; - caddr_t entry; - dbg("processing main program's program header"); assert(aux_info[AT_PHDR] != NULL); phdr = (const Elf_Phdr *) aux_info[AT_PHDR]->a_un.a_ptr; @@ -341,14 +581,34 @@ assert(aux_info[AT_PHENT] != NULL); assert(aux_info[AT_PHENT]->a_un.a_val == sizeof(Elf_Phdr)); assert(aux_info[AT_ENTRY] != NULL); - entry = (caddr_t) aux_info[AT_ENTRY]->a_un.a_ptr; - if ((obj_main = digest_phdr(phdr, phnum, entry, argv0)) == NULL) - die(); + imgentry = (caddr_t) aux_info[AT_ENTRY]->a_un.a_ptr; + if ((obj_main = digest_phdr(phdr, phnum, imgentry, argv0)) == NULL) + rtld_die(); } - obj_main->path = xstrdup(argv0); + if (aux_info[AT_EXECPATH] != NULL && fd == -1) { + kexecpath = aux_info[AT_EXECPATH]->a_un.a_ptr; + dbg("AT_EXECPATH %p %s", kexecpath, kexecpath); + if (kexecpath[0] == '/') + obj_main->path = kexecpath; + else if (getcwd(buf, sizeof(buf)) == NULL || + strlcat(buf, "/", sizeof(buf)) >= sizeof(buf) || + strlcat(buf, kexecpath, sizeof(buf)) >= sizeof(buf)) + obj_main->path = xstrdup(argv0); + else + obj_main->path = xstrdup(buf); + } else { + dbg("No AT_EXECPATH or direct exec"); + obj_main->path = xstrdup(argv0); + } + dbg("obj_main path %s", obj_main->path); obj_main->mainprog = true; + if (aux_info[AT_STACKPROT] != NULL && + aux_info[AT_STACKPROT]->a_un.a_val != 0) + stack_prot = aux_info[AT_STACKPROT]->a_un.a_val; + +#ifndef COMPAT_32BIT /* * Get the actual dynamic linker pathname from the executable if * possible. (It should always be possible.) That ensures that @@ -361,54 +621,72 @@ obj_rtld.path = xstrdup(obj_main->interp); __progname = obj_rtld.path; } - +#endif digest_dynamic(obj_main, 0); + dbg("%s valid_hash_sysv %d valid_hash_gnu %d dynsymcount %d", + obj_main->path, obj_main->valid_hash_sysv, obj_main->valid_hash_gnu, + obj_main->dynsymcount); linkmap_add(obj_main); linkmap_add(&obj_rtld); /* Link the main program into the list of objects. */ - *obj_tail = obj_main; - obj_tail = &obj_main->next; + TAILQ_INSERT_HEAD(&obj_list, obj_main, next); obj_count++; - /* Make sure we don't call the main program's init and fini functions. */ - obj_main->init = obj_main->fini = (Elf_Addr)NULL; + obj_loads++; /* Initialize a fake symbol for resolving undefined weak references. */ sym_zero.st_info = ELF_ST_INFO(STB_GLOBAL, STT_NOTYPE); sym_zero.st_shndx = SHN_UNDEF; + sym_zero.st_value = -(uintptr_t)obj_main->relocbase; if (!libmap_disable) libmap_disable = (bool)lm_init(libmap_override); dbg("loading LD_PRELOAD libraries"); if (load_preload_objects() == -1) - die(); - preload_tail = obj_tail; + rtld_die(); + preload_tail = globallist_curr(TAILQ_LAST(&obj_list, obj_entry_q)); dbg("loading needed objects"); - if (load_needed_objects(obj_main) == -1) - die(); + if (load_needed_objects(obj_main, 0) == -1) + rtld_die(); /* Make a list of all objects loaded at startup. */ - for (obj = obj_list; obj != NULL; obj = obj->next) { - objlist_push_tail(&list_main, obj); + last_interposer = obj_main; + TAILQ_FOREACH(obj, &obj_list, next) { + if (obj->marker) + continue; + if (obj->z_interpose && obj != obj_main) { + objlist_put_after(&list_main, last_interposer, obj); + last_interposer = obj; + } else { + objlist_push_tail(&list_main, obj); + } obj->refcount++; } + dbg("checking for required versions"); + if (rtld_verify_versions(&list_main) == -1 && !ld_tracing) + rtld_die(); + if (ld_tracing) { /* We're done */ trace_loaded_objects(obj_main); exit(0); } - if (getenv(LD_ "DUMP_REL_PRE") != NULL) { + if (getenv(_LD("DUMP_REL_PRE")) != NULL) { dump_relocations(obj_main); exit (0); } - /* setup TLS for main thread */ - dbg("initializing initial thread local storage"); + /* + * Processing tls relocations requires having the tls offsets + * initialized. Prepare offsets before starting initial + * relocation processing. + */ + dbg("initializing initial thread local storage offsets"); STAILQ_FOREACH(entry, &list_main, link) { /* * Allocate all the initial objects out of the static TLS @@ -417,39 +695,78 @@ allocate_tls_offset(entry->obj); } - allocate_initial_tls(obj_list); - -debug = 1; if (relocate_objects(obj_main, - ld_bind_now != NULL && *ld_bind_now != '\0', &obj_rtld) == -1) - die(); + ld_bind_now != NULL && *ld_bind_now != '\0', + &obj_rtld, SYMLOOK_EARLY, NULL) == -1) + rtld_die(); dbg("doing copy relocations"); if (do_copy_relocations(obj_main) == -1) - die(); + rtld_die(); - if (getenv(LD_ "DUMP_REL_POST") != NULL) { + dbg("enforcing main obj relro"); + if (obj_enforce_relro(obj_main) == -1) + rtld_die(); + + if (getenv(_LD("DUMP_REL_POST")) != NULL) { dump_relocations(obj_main); exit (0); } + /* + * Setup TLS for main thread. This must be done after the + * relocations are processed, since tls initialization section + * might be the subject for relocations. + */ + dbg("initializing initial thread local storage"); + allocate_initial_tls(globallist_curr(TAILQ_FIRST(&obj_list))); + dbg("initializing key program variables"); set_program_var("__progname", argv[0] != NULL ? basename(argv[0]) : ""); set_program_var("environ", env); - - dbg("initializing thread locks"); - lockdflt_init(); + set_program_var("__elf_aux_vector", aux); /* Make a list of init functions to call. */ objlist_init(&initlist); - initlist_add_objects(obj_list, preload_tail, &initlist); + initlist_add_objects(globallist_curr(TAILQ_FIRST(&obj_list)), + preload_tail, &initlist); r_debug_state(NULL, &obj_main->linkmap); /* say hello to gdb! */ - objlist_call_init(&initlist); - lockstate = wlock_acquire(rtld_bind_lock); + map_stacks_exec(NULL); + ifunc_init(aux); + + dbg("resolving ifuncs"); + if (resolve_objects_ifunc(obj_main, + ld_bind_now != NULL && *ld_bind_now != '\0', SYMLOOK_EARLY, + NULL) == -1) + rtld_die(); + + if (!obj_main->crt_no_init) { + /* + * Make sure we don't call the main program's init and fini + * functions for binaries linked with old crt1 which calls + * _init itself. + */ + obj_main->init = obj_main->fini = (Elf_Addr)NULL; + obj_main->preinit_array = obj_main->init_array = + obj_main->fini_array = (Elf_Addr)NULL; + } + + wlock_acquire(rtld_bind_lock, &lockstate); + if (obj_main->crt_no_init) + preinit_main(); + objlist_call_init(&initlist, &lockstate); + _r_debug_postinit(&obj_main->linkmap); objlist_clear(&initlist); - wlock_release(rtld_bind_lock, lockstate); + dbg("loading filtees"); + TAILQ_FOREACH(obj, &obj_list, next) { + if (obj->marker) + continue; + if (ld_loadfltr || obj->z_loadfltr) + load_filtees(obj, 0, &lockstate); + } + lock_release(rtld_bind_lock, &lockstate); dbg("transferring control to program entry point = %p", obj_main->entry); @@ -459,6 +776,21 @@ return (func_ptr_type) obj_main->entry; } +void * +rtld_resolve_ifunc(const Obj_Entry *obj, const Elf_Sym *def) +{ + void *ptr; + Elf_Addr target; + + ptr = (void *)make_function_pointer(def, obj); + target = call_ifunc_resolver(ptr); + return ((void *)target); +} + +/* + * NB: MIPS uses a private version of this function (_mips_rtld_bind). + * Changes to this function should be applied there as well. + */ Elf_Addr _rtld_bind(Obj_Entry *obj, Elf_Size reloff) { @@ -467,20 +799,25 @@ const Obj_Entry *defobj; Elf_Addr *where; Elf_Addr target; - int lockstate; + RtldLockState lockstate; - lockstate = rlock_acquire(rtld_bind_lock); + rlock_acquire(rtld_bind_lock, &lockstate); + if (sigsetjmp(lockstate.env, 0) != 0) + lock_upgrade(rtld_bind_lock, &lockstate); if (obj->pltrel) rel = (const Elf_Rel *) ((caddr_t) obj->pltrel + reloff); else rel = (const Elf_Rel *) ((caddr_t) obj->pltrela + reloff); where = (Elf_Addr *) (obj->relocbase + rel->r_offset); - def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, true, NULL); + def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, SYMLOOK_IN_PLT, + NULL, &lockstate); if (def == NULL) - die(); - - target = (Elf_Addr)(defobj->relocbase + def->st_value); + rtld_die(); + if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC) + target = (Elf_Addr)rtld_resolve_ifunc(defobj, def); + else + target = (Elf_Addr)(defobj->relocbase + def->st_value); dbg("\"%s\" in \"%s\" ==> %p in \"%s\"", defobj->strtab + def->st_name, basename(obj->path), @@ -494,7 +831,7 @@ * that the trampoline needs. */ target = reloc_jmpslot(where, target, defobj, obj, rel); - rlock_release(rtld_bind_lock, lockstate); + lock_release(rtld_bind_lock, &lockstate); return target; } @@ -510,7 +847,7 @@ va_list ap; va_start(ap, fmt); - vsnprintf(buf, sizeof buf, fmt, ap); + rtld_vsnprintf(buf, sizeof buf, fmt, ap); error_message = buf; va_end(ap); } @@ -546,14 +883,103 @@ return p != NULL ? p + 1 : name; } -static void -die(void) +static struct utsname uts; + +static char * +origin_subst_one(Obj_Entry *obj, char *real, const char *kw, + const char *subst, bool may_free) +{ + char *p, *p1, *res, *resp; + int subst_len, kw_len, subst_count, old_len, new_len; + + kw_len = strlen(kw); + + /* + * First, count the number of the keyword occurrences, to + * preallocate the final string. + */ + for (p = real, subst_count = 0;; p = p1 + kw_len, subst_count++) { + p1 = strstr(p, kw); + if (p1 == NULL) + break; + } + + /* + * If the keyword is not found, just return. + * + * Return non-substituted string if resolution failed. We + * cannot do anything more reasonable, the failure mode of the + * caller is unresolved library anyway. + */ + if (subst_count == 0 || (obj != NULL && !obj_resolve_origin(obj))) + return (may_free ? real : xstrdup(real)); + if (obj != NULL) + subst = obj->origin_path; + + /* + * There is indeed something to substitute. Calculate the + * length of the resulting string, and allocate it. + */ + subst_len = strlen(subst); + old_len = strlen(real); + new_len = old_len + (subst_len - kw_len) * subst_count; + res = xmalloc(new_len + 1); + + /* + * Now, execute the substitution loop. + */ + for (p = real, resp = res, *resp = '\0';;) { + p1 = strstr(p, kw); + if (p1 != NULL) { + /* Copy the prefix before keyword. */ + memcpy(resp, p, p1 - p); + resp += p1 - p; + /* Keyword replacement. */ + memcpy(resp, subst, subst_len); + resp += subst_len; + *resp = '\0'; + p = p1 + kw_len; + } else + break; + } + + /* Copy to the end of string and finish. */ + strcat(resp, p); + if (may_free) + free(real); + return (res); +} + +static char * +origin_subst(Obj_Entry *obj, char *real) +{ + char *res1, *res2, *res3, *res4; + + if (obj == NULL || !trust) + return (xstrdup(real)); + if (uts.sysname[0] == '\0') { + if (uname(&uts) != 0) { + _rtld_error("utsname failed: %d", errno); + return (NULL); + } + } + res1 = origin_subst_one(obj, real, "$ORIGIN", NULL, false); + res2 = origin_subst_one(NULL, res1, "$OSNAME", uts.sysname, true); + res3 = origin_subst_one(NULL, res2, "$OSREL", uts.release, true); + res4 = origin_subst_one(NULL, res3, "$PLATFORM", uts.machine, true); + return (res4); +} + +void +rtld_die(void) { const char *msg = dlerror(); if (msg == NULL) msg = "Fatal error"; - errx(1, "%s", msg); + rtld_fdputstr(STDERR_FILENO, msg); + rtld_fdputchar(STDERR_FILENO, '\n'); + _exit(1); } /* @@ -561,13 +987,23 @@ * information in its Obj_Entry structure. */ static void -digest_dynamic(Obj_Entry *obj, int early) +digest_dynamic1(Obj_Entry *obj, int early, const Elf_Dyn **dyn_rpath, + const Elf_Dyn **dyn_soname, const Elf_Dyn **dyn_runpath) { const Elf_Dyn *dynp; Needed_Entry **needed_tail = &obj->needed; - const Elf_Dyn *dyn_rpath = NULL; + Needed_Entry **needed_filtees_tail = &obj->needed_filtees; + Needed_Entry **needed_aux_filtees_tail = &obj->needed_aux_filtees; + const Elf_Hashelt *hashtab; + const Elf32_Word *hashval; + Elf32_Word bkt, nmaskwords; + int bloom_size32; int plttype = DT_REL; + *dyn_rpath = NULL; + *dyn_soname = NULL; + *dyn_runpath = NULL; + obj->bind_now = false; for (dynp = obj->dynamic; dynp->d_tag != DT_NULL; dynp++) { switch (dynp->d_tag) { @@ -627,14 +1063,59 @@ 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 *) - (obj->relocbase + dynp->d_un.d_ptr); + hashtab = (const Elf_Hashelt *)(obj->relocbase + + dynp->d_un.d_ptr); obj->nbuckets = hashtab[0]; obj->nchains = hashtab[1]; obj->buckets = hashtab + 2; obj->chains = obj->buckets + obj->nbuckets; + obj->valid_hash_sysv = obj->nbuckets > 0 && obj->nchains > 0 && + obj->buckets != NULL; + } + break; + + case DT_GNU_HASH: + { + hashtab = (const Elf_Hashelt *)(obj->relocbase + + dynp->d_un.d_ptr); + obj->nbuckets_gnu = hashtab[0]; + obj->symndx_gnu = hashtab[1]; + nmaskwords = hashtab[2]; + bloom_size32 = (__ELF_WORD_SIZE / 32) * nmaskwords; + obj->maskwords_bm_gnu = nmaskwords - 1; + obj->shift2_gnu = hashtab[3]; + obj->bloom_gnu = (Elf_Addr *) (hashtab + 4); + obj->buckets_gnu = hashtab + 4 + bloom_size32; + obj->chain_zero_gnu = obj->buckets_gnu + obj->nbuckets_gnu - + obj->symndx_gnu; + /* Number of bitmask words is required to be power of 2 */ + obj->valid_hash_gnu = powerof2(nmaskwords) && + obj->nbuckets_gnu > 0 && obj->buckets_gnu != NULL; } break; @@ -650,6 +1131,30 @@ } break; + case DT_FILTER: + if (!obj->rtld) { + Needed_Entry *nep = NEW(Needed_Entry); + nep->name = dynp->d_un.d_val; + nep->obj = NULL; + nep->next = NULL; + + *needed_filtees_tail = nep; + needed_filtees_tail = &nep->next; + } + break; + + case DT_AUXILIARY: + if (!obj->rtld) { + Needed_Entry *nep = NEW(Needed_Entry); + nep->name = dynp->d_un.d_val; + nep->obj = NULL; + nep->next = NULL; + + *needed_aux_filtees_tail = nep; + needed_aux_filtees_tail = &nep->next; + } + break; + case DT_PLTGOT: obj->pltgot = (Elf_Addr *) (obj->relocbase + dynp->d_un.d_ptr); break; @@ -663,47 +1168,119 @@ break; case DT_RPATH: - case DT_RUNPATH: /* XXX: process separately */ /* * We have to wait until later to process this, because we * might not have gotten the address of the string table yet. */ - dyn_rpath = dynp; + *dyn_rpath = dynp; break; case DT_SONAME: - /* Not used by the dynamic linker. */ + *dyn_soname = dynp; + break; + + case DT_RUNPATH: + *dyn_runpath = dynp; break; case DT_INIT: obj->init = (Elf_Addr) (obj->relocbase + dynp->d_un.d_ptr); break; + case DT_PREINIT_ARRAY: + obj->preinit_array = (Elf_Addr)(obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_PREINIT_ARRAYSZ: + obj->preinit_array_num = dynp->d_un.d_val / sizeof(Elf_Addr); + break; + + case DT_INIT_ARRAY: + obj->init_array = (Elf_Addr)(obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_INIT_ARRAYSZ: + obj->init_array_num = dynp->d_un.d_val / sizeof(Elf_Addr); + break; + case DT_FINI: obj->fini = (Elf_Addr) (obj->relocbase + dynp->d_un.d_ptr); break; + case DT_FINI_ARRAY: + obj->fini_array = (Elf_Addr)(obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_FINI_ARRAYSZ: + obj->fini_array_num = dynp->d_un.d_val / sizeof(Elf_Addr); + break; + + /* + * Don't process DT_DEBUG on MIPS as the dynamic section + * is mapped read-only. DT_MIPS_RLD_MAP is used instead. + */ + +#ifndef __mips__ case DT_DEBUG: - /* XXX - not implemented yet */ if (!early) dbg("Filling in DT_DEBUG entry"); ((Elf_Dyn*)dynp)->d_un.d_ptr = (Elf_Addr) &r_debug; break; +#endif case DT_FLAGS: - if (dynp->d_un.d_val & DF_ORIGIN) { - obj->origin_path = xmalloc(PATH_MAX); - if (rtld_dirname(obj->path, obj->origin_path) == -1) - die(); - } + if (dynp->d_un.d_val & DF_ORIGIN) + obj->z_origin = true; if (dynp->d_un.d_val & DF_SYMBOLIC) obj->symbolic = true; if (dynp->d_un.d_val & DF_TEXTREL) obj->textrel = true; if (dynp->d_un.d_val & DF_BIND_NOW) obj->bind_now = true; - if (dynp->d_un.d_val & DF_STATIC_TLS) - ; + /*if (dynp->d_un.d_val & DF_STATIC_TLS) + ;*/ + break; +#ifdef __mips__ + case DT_MIPS_LOCAL_GOTNO: + obj->local_gotno = dynp->d_un.d_val; + break; + + case DT_MIPS_SYMTABNO: + obj->symtabno = dynp->d_un.d_val; + break; + + case DT_MIPS_GOTSYM: + obj->gotsym = dynp->d_un.d_val; + break; + + case DT_MIPS_RLD_MAP: + *((Elf_Addr *)(dynp->d_un.d_ptr)) = (Elf_Addr) &r_debug; + break; +#endif + +#ifdef __powerpc64__ + case DT_PPC64_GLINK: + obj->glink = (Elf_Addr) (obj->relocbase + dynp->d_un.d_ptr); + break; +#endif + + case DT_FLAGS_1: + if (dynp->d_un.d_val & DF_1_NOOPEN) + obj->z_noopen = true; + if (dynp->d_un.d_val & DF_1_ORIGIN) + obj->z_origin = true; + if (dynp->d_un.d_val & DF_1_GLOBAL) + obj->z_global = true; + if (dynp->d_un.d_val & DF_1_BIND_NOW) + obj->bind_now = true; + if (dynp->d_un.d_val & DF_1_NODELETE) + obj->z_nodelete = true; + if (dynp->d_un.d_val & DF_1_LOADFLTR) + obj->z_loadfltr = true; + if (dynp->d_un.d_val & DF_1_INTERPOSE) + obj->z_interpose = true; + if (dynp->d_un.d_val & DF_1_NODEFLIB) + obj->z_nodeflib = true; break; default: @@ -724,8 +1301,61 @@ obj->pltrelsize = 0; } - if (dyn_rpath != NULL) - obj->rpath = obj->strtab + dyn_rpath->d_un.d_val; + /* Determine size of dynsym table (equal to nchains of sysv hash) */ + if (obj->valid_hash_sysv) + obj->dynsymcount = obj->nchains; + else if (obj->valid_hash_gnu) { + obj->dynsymcount = 0; + for (bkt = 0; bkt < obj->nbuckets_gnu; bkt++) { + if (obj->buckets_gnu[bkt] == 0) + continue; + hashval = &obj->chain_zero_gnu[obj->buckets_gnu[bkt]]; + do + obj->dynsymcount++; + while ((*hashval++ & 1u) == 0); + } + obj->dynsymcount += obj->symndx_gnu; + } +} + +static bool +obj_resolve_origin(Obj_Entry *obj) +{ + + if (obj->origin_path != NULL) + return (true); + obj->origin_path = xmalloc(PATH_MAX); + return (rtld_dirname_abs(obj->path, obj->origin_path) != -1); +} + +static void +digest_dynamic2(Obj_Entry *obj, const Elf_Dyn *dyn_rpath, + const Elf_Dyn *dyn_soname, const Elf_Dyn *dyn_runpath) +{ + + if (obj->z_origin && !obj_resolve_origin(obj)) + rtld_die(); + + if (dyn_runpath != NULL) { + obj->runpath = (char *)obj->strtab + dyn_runpath->d_un.d_val; + obj->runpath = origin_subst(obj, obj->runpath); + } else if (dyn_rpath != NULL) { + obj->rpath = (char *)obj->strtab + dyn_rpath->d_un.d_val; + obj->rpath = origin_subst(obj, obj->rpath); + } + if (dyn_soname != NULL) + object_add_name(obj, obj->strtab + dyn_soname->d_un.d_val); +} + +static void +digest_dynamic(Obj_Entry *obj, int early) +{ + const Elf_Dyn *dyn_rpath; + const Elf_Dyn *dyn_soname; + const Elf_Dyn *dyn_runpath; + + digest_dynamic1(obj, early, &dyn_rpath, &dyn_soname, &dyn_runpath); + digest_dynamic2(obj, dyn_rpath, dyn_soname, dyn_runpath); } /* @@ -740,30 +1370,33 @@ Obj_Entry *obj; const Elf_Phdr *phlimit = phdr + phnum; const Elf_Phdr *ph; + Elf_Addr note_start, note_end; int nsegs = 0; obj = obj_new(); for (ph = phdr; ph < phlimit; ph++) { + if (ph->p_type != PT_PHDR) + continue; + + obj->phdr = phdr; + obj->phsize = ph->p_memsz; + obj->relocbase = (caddr_t)phdr - ph->p_vaddr; + break; + } + + obj->stack_flags = PF_X | PF_R | PF_W; + + for (ph = phdr; ph < phlimit; ph++) { switch (ph->p_type) { - case PT_PHDR: - if ((const Elf_Phdr *)ph->p_vaddr != phdr) { - _rtld_error("%s: invalid PT_PHDR", path); - return NULL; - } - obj->phdr = (const Elf_Phdr *) ph->p_vaddr; - obj->phsize = ph->p_memsz; - break; - case PT_INTERP: - obj->interp = (const char *) ph->p_vaddr; + obj->interp = (const char *)(ph->p_vaddr + obj->relocbase); break; case PT_LOAD: if (nsegs == 0) { /* First load segment */ obj->vaddrbase = trunc_page(ph->p_vaddr); - obj->mapbase = (caddr_t) obj->vaddrbase; - obj->relocbase = obj->mapbase - obj->vaddrbase; + obj->mapbase = obj->vaddrbase + obj->relocbase; obj->textsize = round_page(ph->p_vaddr + ph->p_memsz) - obj->vaddrbase; } else { /* Last load segment */ @@ -774,7 +1407,7 @@ break; case PT_DYNAMIC: - obj->dynamic = (const Elf_Dyn *) ph->p_vaddr; + obj->dynamic = (const Elf_Dyn *)(ph->p_vaddr + obj->relocbase); break; case PT_TLS: @@ -782,7 +1415,22 @@ obj->tlssize = ph->p_memsz; obj->tlsalign = ph->p_align; obj->tlsinitsize = ph->p_filesz; - obj->tlsinit = (void*) ph->p_vaddr; + obj->tlsinit = (void*)(ph->p_vaddr + obj->relocbase); + break; + + case PT_GNU_STACK: + obj->stack_flags = ph->p_flags; + break; + + case PT_GNU_RELRO: + obj->relro_page = obj->relocbase + trunc_page(ph->p_vaddr); + obj->relro_size = round_page(ph->p_memsz); + break; + + case PT_NOTE: + note_start = (Elf_Addr)obj->relocbase + ph->p_vaddr; + note_end = note_start + ph->p_filesz; + digest_notes(obj, note_start, note_end); break; } } @@ -795,14 +1443,53 @@ return obj; } +void +digest_notes(Obj_Entry *obj, Elf_Addr note_start, Elf_Addr note_end) +{ + const Elf_Note *note; + const char *note_name; + uintptr_t p; + + for (note = (const Elf_Note *)note_start; (Elf_Addr)note < note_end; + note = (const Elf_Note *)((const char *)(note + 1) + + roundup2(note->n_namesz, sizeof(Elf32_Addr)) + + roundup2(note->n_descsz, sizeof(Elf32_Addr)))) { + if (note->n_namesz != sizeof(NOTE_FREEBSD_VENDOR) || + note->n_descsz != sizeof(int32_t)) + continue; + if (note->n_type != NT_FREEBSD_ABI_TAG && + note->n_type != NT_FREEBSD_NOINIT_TAG) + continue; + note_name = (const char *)(note + 1); + if (strncmp(NOTE_FREEBSD_VENDOR, note_name, + sizeof(NOTE_FREEBSD_VENDOR)) != 0) + continue; + switch (note->n_type) { + case NT_FREEBSD_ABI_TAG: + /* FreeBSD osrel note */ + p = (uintptr_t)(note + 1); + p += roundup2(note->n_namesz, sizeof(Elf32_Addr)); + obj->osrel = *(const int32_t *)(p); + dbg("note osrel %d", obj->osrel); + break; + case NT_FREEBSD_NOINIT_TAG: + /* FreeBSD 'crt does not call init' note */ + obj->crt_no_init = true; + dbg("note crt_no_init"); + break; + } + } +} + static Obj_Entry * dlcheck(void *handle) { Obj_Entry *obj; - for (obj = obj_list; obj != NULL; obj = obj->next) + TAILQ_FOREACH(obj, &obj_list, next) { if (obj == (Obj_Entry *) handle) break; + } if (obj == NULL || obj->refcount == 0 || obj->dl_refcount == 0) { _rtld_error("Invalid shared object handle %p", handle); @@ -855,6 +1542,23 @@ } /* + * The GNU hash function is the Daniel J. Bernstein hash clipped to 32 bits + * unsigned in case it's implemented with a wider type. + */ +static uint32_t +gnu_hash(const char *s) +{ + uint32_t h; + unsigned char c; + + h = 5381; + for (c = *s; c != '\0'; c = *++s) + h = h * 33 + c; + return (h & 0xffffffff); +} + + +/* * Find the library with the given name, and return its full pathname. * The returned string is dynamically allocated. Generates an error * message and returns NULL if the library cannot be found. @@ -862,40 +1566,76 @@ * If the second argument is non-NULL, then it refers to an already- * loaded shared object, whose library search path will be searched. * + * If a library is successfully located via LD_LIBRARY_PATH_FDS, its + * descriptor (which is close-on-exec) will be passed out via the third + * argument. + * * The search order is: + * DT_RPATH in the referencing file _unless_ DT_RUNPATH is present (1) + * DT_RPATH of the main object if DSO without defined DT_RUNPATH (1) * LD_LIBRARY_PATH - * rpath in the referencing file - * ldconfig hints - * /lib:/usr/lib + * DT_RUNPATH in the referencing file + * ldconfig hints (if -z nodefaultlib, filter out default library directories + * from list) + * /lib:/usr/lib _unless_ the referencing file is linked with -z nodefaultlib + * + * (1) Handled in digest_dynamic2 - rpath left NULL if runpath defined. */ static char * -find_library(const char *xname, const Obj_Entry *refobj) +find_library(const char *xname, const Obj_Entry *refobj, int *fdp) { char *pathname; char *name; + bool nodeflib, objgiven; + objgiven = refobj != NULL; if (strchr(xname, '/') != NULL) { /* Hard coded pathname */ if (xname[0] != '/' && !trust) { _rtld_error("Absolute pathname required for shared object \"%s\"", xname); return NULL; } - return xstrdup(xname); + return (origin_subst(__DECONST(Obj_Entry *, refobj), + __DECONST(char *, xname))); } - if (libmap_disable || (refobj == NULL) || + if (libmap_disable || !objgiven || (name = lm_find(refobj->path, xname)) == NULL) name = (char *)xname; dbg(" Searching for \"%s\"", name); - if ((pathname = search_library_path(name, ld_library_path)) != NULL || - (refobj != NULL && - (pathname = search_library_path(name, refobj->rpath)) != NULL) || - (pathname = search_library_path(name, gethints())) != NULL || - (pathname = search_library_path(name, STANDARD_LIBRARY_PATH)) != NULL) - return pathname; - if(refobj != NULL && refobj->path != NULL) { + /* + * If refobj->rpath != NULL, then refobj->runpath is NULL. Fall + * back to pre-conforming behaviour if user requested so with + * LD_LIBRARY_PATH_RPATH environment variable and ignore -z + * nodeflib. + */ + if (objgiven && refobj->rpath != NULL && ld_library_path_rpath) { + if ((pathname = search_library_path(name, ld_library_path)) != NULL || + (refobj != NULL && + (pathname = search_library_path(name, refobj->rpath)) != NULL) || + (pathname = search_library_pathfds(name, ld_library_dirs, fdp)) != NULL || + (pathname = search_library_path(name, gethints(false))) != NULL || + (pathname = search_library_path(name, ld_standard_library_path)) != NULL) + return (pathname); + } else { + nodeflib = objgiven ? refobj->z_nodeflib : false; + if ((objgiven && + (pathname = search_library_path(name, refobj->rpath)) != NULL) || + (objgiven && refobj->runpath == NULL && refobj != obj_main && + (pathname = search_library_path(name, obj_main->rpath)) != NULL) || + (pathname = search_library_path(name, ld_library_path)) != NULL || + (objgiven && + (pathname = search_library_path(name, refobj->runpath)) != NULL) || + (pathname = search_library_pathfds(name, ld_library_dirs, fdp)) != NULL || + (pathname = search_library_path(name, gethints(nodeflib))) != NULL || + (objgiven && !nodeflib && + (pathname = search_library_path(name, ld_standard_library_path)) != NULL)) + return (pathname); + } + + if (objgiven && refobj->path != NULL) { _rtld_error("Shared object \"%s\" not found, required by \"%s\"", name, basename(refobj->path)); } else { @@ -912,21 +1652,22 @@ */ 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, + RtldLockState *lockstate) { const Elf_Sym *ref; const Elf_Sym *def; const Obj_Entry *defobj; + SymLook req; const char *name; - unsigned long hash; + int res; /* * If we have already found this symbol, get the information from * the cache. */ - if (symnum >= refobj->nchains) + if (symnum >= refobj->dynsymcount) return NULL; /* Bad object */ - if (cache != NULL && cache[symnum].sym != NULL) { *defobj_out = cache[symnum].obj; return cache[symnum].sym; @@ -934,6 +1675,7 @@ ref = refobj->symtab + symnum; name = refobj->strtab + ref->st_name; + def = NULL; defobj = NULL; /* @@ -949,8 +1691,15 @@ _rtld_error("%s: Bogus symbol table entry %lu", refobj->path, symnum); } - hash = elf_hash(name); - def = symlook_default(name, hash, refobj, &defobj, in_plt); + symlook_init(&req, name); + req.flags = flags; + req.ventry = fetch_ventry(refobj, symnum); + req.lockstate = lockstate; + res = symlook_default(&req, refobj); + if (res == 0) { + def = req.sym_out; + defobj = req.defobj_out; + } } else { def = ref; defobj = refobj; @@ -981,83 +1730,305 @@ /* * Return the search path from the ldconfig hints file, reading it if - * necessary. Returns NULL if there are problems with the hints file, + * necessary. If nostdlib is true, then the default search paths are + * not added to result. + * + * Returns NULL if there are problems with the hints file, * or if the search path there is empty. */ static const char * -gethints(void) +gethints(bool nostdlib) { - static char *hints; - - if (hints == NULL) { - int fd; - struct elfhints_hdr hdr; + static char *hints, *filtered_path; + static struct elfhints_hdr hdr; + struct fill_search_info_args sargs, hargs; + struct dl_serinfo smeta, hmeta, *SLPinfo, *hintinfo; + struct dl_serpath *SLPpath, *hintpath; char *p; + struct stat hint_stat; + unsigned int SLPndx, hintndx, fndx, fcount; + int fd; + size_t flen; + uint32_t dl; + bool skip; - /* Keep from trying again in case the hints file is bad. */ - hints = ""; + /* First call, read the hints file */ + if (hints == NULL) { + /* Keep from trying again in case the hints file is bad. */ + hints = ""; - if ((fd = open(_PATH_ELF_HINTS, O_RDONLY)) == -1) - return NULL; - if (read(fd, &hdr, sizeof hdr) != sizeof hdr || - hdr.magic != ELFHINTS_MAGIC || - hdr.version != 1) { - close(fd); - return NULL; + if ((fd = open(ld_elf_hints_path, O_RDONLY | O_CLOEXEC)) == -1) + return (NULL); + + /* + * Check of hdr.dirlistlen value against type limit + * intends to pacify static analyzers. Further + * paranoia leads to checks that dirlist is fully + * contained in the file range. + */ + if (read(fd, &hdr, sizeof hdr) != sizeof hdr || + hdr.magic != ELFHINTS_MAGIC || + hdr.version != 1 || hdr.dirlistlen > UINT_MAX / 2 || + fstat(fd, &hint_stat) == -1) { +cleanup1: + close(fd); + hdr.dirlistlen = 0; + return (NULL); + } + dl = hdr.strtab; + if (dl + hdr.dirlist < dl) + goto cleanup1; + dl += hdr.dirlist; + if (dl + hdr.dirlistlen < dl) + goto cleanup1; + dl += hdr.dirlistlen; + if (dl > hint_stat.st_size) + goto cleanup1; + p = xmalloc(hdr.dirlistlen + 1); + + if (lseek(fd, hdr.strtab + hdr.dirlist, SEEK_SET) == -1 || + read(fd, p, hdr.dirlistlen + 1) != + (ssize_t)hdr.dirlistlen + 1 || p[hdr.dirlistlen] != '\0') { + free(p); + goto cleanup1; + } + hints = p; + close(fd); } - p = xmalloc(hdr.dirlistlen + 1); - if (lseek(fd, hdr.strtab + hdr.dirlist, SEEK_SET) == -1 || - read(fd, p, hdr.dirlistlen + 1) != (ssize_t)hdr.dirlistlen + 1) { - free(p); - close(fd); - return NULL; + + /* + * If caller agreed to receive list which includes the default + * paths, we are done. Otherwise, if we still did not + * calculated filtered result, do it now. + */ + if (!nostdlib) + return (hints[0] != '\0' ? hints : NULL); + if (filtered_path != NULL) + goto filt_ret; + + /* + * Obtain the list of all configured search paths, and the + * list of the default paths. + * + * First estimate the size of the results. + */ + smeta.dls_size = __offsetof(struct dl_serinfo, dls_serpath); + smeta.dls_cnt = 0; + hmeta.dls_size = __offsetof(struct dl_serinfo, dls_serpath); + hmeta.dls_cnt = 0; + + sargs.request = RTLD_DI_SERINFOSIZE; + sargs.serinfo = &smeta; + hargs.request = RTLD_DI_SERINFOSIZE; + hargs.serinfo = &hmeta; + + path_enumerate(ld_standard_library_path, fill_search_info, &sargs); + path_enumerate(hints, fill_search_info, &hargs); + + SLPinfo = xmalloc(smeta.dls_size); + hintinfo = xmalloc(hmeta.dls_size); + + /* + * Next fetch both sets of paths. + */ + sargs.request = RTLD_DI_SERINFO; + sargs.serinfo = SLPinfo; + sargs.serpath = &SLPinfo->dls_serpath[0]; + sargs.strspace = (char *)&SLPinfo->dls_serpath[smeta.dls_cnt]; + + hargs.request = RTLD_DI_SERINFO; + hargs.serinfo = hintinfo; + hargs.serpath = &hintinfo->dls_serpath[0]; + hargs.strspace = (char *)&hintinfo->dls_serpath[hmeta.dls_cnt]; + + path_enumerate(ld_standard_library_path, fill_search_info, &sargs); + path_enumerate(hints, fill_search_info, &hargs); + + /* + * Now calculate the difference between two sets, by excluding + * standard paths from the full set. + */ + fndx = 0; + fcount = 0; + filtered_path = xmalloc(hdr.dirlistlen + 1); + hintpath = &hintinfo->dls_serpath[0]; + for (hintndx = 0; hintndx < hmeta.dls_cnt; hintndx++, hintpath++) { + skip = false; + SLPpath = &SLPinfo->dls_serpath[0]; + /* + * Check each standard path against current. + */ + for (SLPndx = 0; SLPndx < smeta.dls_cnt; SLPndx++, SLPpath++) { + /* matched, skip the path */ + if (!strcmp(hintpath->dls_name, SLPpath->dls_name)) { + skip = true; + break; + } + } + if (skip) + continue; + /* + * Not matched against any standard path, add the path + * to result. Separate consequtive paths with ':'. + */ + if (fcount > 0) { + filtered_path[fndx] = ':'; + fndx++; + } + fcount++; + flen = strlen(hintpath->dls_name); + strncpy((filtered_path + fndx), hintpath->dls_name, flen); + fndx += flen; } - hints = p; - close(fd); - } - return hints[0] != '\0' ? hints : NULL; + filtered_path[fndx] = '\0'; + + free(SLPinfo); + free(hintinfo); + +filt_ret: + return (filtered_path[0] != '\0' ? filtered_path : NULL); } static void init_dag(Obj_Entry *root) { + const Needed_Entry *needed; + const Objlist_Entry *elm; DoneList donelist; + if (root->dag_inited) + return; donelist_init(&donelist); - init_dag1(root, root, &donelist); + + /* Root object belongs to own DAG. */ + objlist_push_tail(&root->dldags, root); + objlist_push_tail(&root->dagmembers, root); + donelist_check(&donelist, root); + + /* + * Add dependencies of root object to DAG in breadth order + * by exploiting the fact that each new object get added + * to the tail of the dagmembers list. + */ + STAILQ_FOREACH(elm, &root->dagmembers, link) { + for (needed = elm->obj->needed; needed != NULL; needed = needed->next) { + if (needed->obj == NULL || donelist_check(&donelist, needed->obj)) + continue; + objlist_push_tail(&needed->obj->dldags, root); + objlist_push_tail(&root->dagmembers, needed->obj); + } + } + root->dag_inited = true; } static void -init_dag1(Obj_Entry *root, Obj_Entry *obj, DoneList *dlp) +init_marker(Obj_Entry *marker) { - const Needed_Entry *needed; - if (donelist_check(dlp, obj)) - return; - - obj->refcount++; - objlist_push_tail(&obj->dldags, root); - objlist_push_tail(&root->dagmembers, obj); - for (needed = obj->needed; needed != NULL; needed = needed->next) - if (needed->obj != NULL) - init_dag1(root, needed->obj, dlp); + bzero(marker, sizeof(*marker)); + marker->marker = true; } +Obj_Entry * +globallist_curr(const Obj_Entry *obj) +{ + + for (;;) { + if (obj == NULL) + return (NULL); + if (!obj->marker) + return (__DECONST(Obj_Entry *, obj)); + obj = TAILQ_PREV(obj, obj_entry_q, next); + } +} + +Obj_Entry * +globallist_next(const Obj_Entry *obj) +{ + + for (;;) { + obj = TAILQ_NEXT(obj, next); + if (obj == NULL) + return (NULL); + if (!obj->marker) + return (__DECONST(Obj_Entry *, obj)); + } +} + +/* Prevent the object from being unmapped while the bind lock is dropped. */ +static void +hold_object(Obj_Entry *obj) +{ + + obj->holdcount++; +} + +static void +unhold_object(Obj_Entry *obj) +{ + + assert(obj->holdcount > 0); + if (--obj->holdcount == 0 && obj->unholdfree) + release_object(obj); +} + +static void +process_z(Obj_Entry *root) +{ + const Objlist_Entry *elm; + Obj_Entry *obj; + + /* + * Walk over object DAG and process every dependent object + * that is marked as DF_1_NODELETE or DF_1_GLOBAL. They need + * to grow their own DAG. + * + * For DF_1_GLOBAL, DAG is required for symbol lookups in + * symlook_global() to work. + * + * For DF_1_NODELETE, the DAG should have its reference upped. + */ + STAILQ_FOREACH(elm, &root->dagmembers, link) { + obj = elm->obj; + if (obj == NULL) + continue; + if (obj->z_nodelete && !obj->ref_nodel) { + dbg("obj %s -z nodelete", obj->path); + init_dag(obj); + ref_dag(obj); + obj->ref_nodel = true; + } + if (obj->z_global && objlist_find(&list_global, obj) == NULL) { + dbg("obj %s -z global", obj->path); + objlist_push_tail(&list_global, obj); + init_dag(obj); + } + } +} /* * Initialize the dynamic linker. The argument is the address at which * the dynamic linker has been mapped into memory. The primary task of * this function is to relocate the dynamic linker. */ static void -init_rtld(caddr_t mapbase) +init_rtld(caddr_t mapbase, Elf_Auxinfo **aux_info) { Obj_Entry objtmp; /* Temporary rtld object */ + const Elf_Ehdr *ehdr; + const Elf_Dyn *dyn_rpath; + const Elf_Dyn *dyn_soname; + const Elf_Dyn *dyn_runpath; + +#ifdef RTLD_INIT_PAGESIZES_EARLY + /* The page size is required by the dynamic memory allocator. */ + init_pagesizes(aux_info); +#endif /* * Conjure up an Obj_Entry structure for the dynamic linker. * - * The "path" member can't be initialized yet because string constatns - * cannot yet be acessed. Below we will set it correctly. + * The "path" member can't be initialized yet because string constants + * cannot yet be accessed. Below we will set it correctly. */ memset(&objtmp, 0, sizeof(objtmp)); objtmp.path = NULL; @@ -1066,34 +2037,92 @@ #ifdef PIC objtmp.relocbase = mapbase; #endif - if (RTLD_IS_DYNAMIC()) { - objtmp.dynamic = rtld_dynamic(&objtmp); - digest_dynamic(&objtmp, 1); - assert(objtmp.needed == NULL); - assert(!objtmp.textrel); - /* - * Temporarily put the dynamic linker entry into the object list, so - * that symbols can be found. - */ + objtmp.dynamic = rtld_dynamic(&objtmp); + digest_dynamic1(&objtmp, 1, &dyn_rpath, &dyn_soname, &dyn_runpath); + assert(objtmp.needed == NULL); +#if !defined(__mips__) + /* MIPS has a bogus DT_TEXTREL. */ + assert(!objtmp.textrel); +#endif + /* + * Temporarily put the dynamic linker entry into the object list, so + * that symbols can be found. + */ + relocate_objects(&objtmp, true, &objtmp, 0, NULL); - relocate_objects(&objtmp, true, &objtmp); - } + ehdr = (Elf_Ehdr *)mapbase; + objtmp.phdr = (Elf_Phdr *)((char *)mapbase + ehdr->e_phoff); + objtmp.phsize = ehdr->e_phnum * sizeof(objtmp.phdr[0]); /* Initialize the object list. */ - obj_tail = &obj_list; + TAILQ_INIT(&obj_list); /* Now that non-local variables can be accesses, copy out obj_rtld. */ memcpy(&obj_rtld, &objtmp, sizeof(obj_rtld)); +#ifndef RTLD_INIT_PAGESIZES_EARLY + /* The page size is required by the dynamic memory allocator. */ + init_pagesizes(aux_info); +#endif + + if (aux_info[AT_OSRELDATE] != NULL) + osreldate = aux_info[AT_OSRELDATE]->a_un.a_val; + + digest_dynamic2(&obj_rtld, dyn_rpath, dyn_soname, dyn_runpath); + /* Replace the path with a dynamically allocated copy. */ - obj_rtld.path = xstrdup(PATH_RTLD); + obj_rtld.path = xstrdup(ld_path_rtld); r_debug.r_brk = r_debug_state; r_debug.r_state = RT_CONSISTENT; } /* + * Retrieve the array of supported page sizes. The kernel provides the page + * sizes in increasing order. + */ +static void +init_pagesizes(Elf_Auxinfo **aux_info) +{ + static size_t psa[MAXPAGESIZES]; + int mib[2]; + size_t len, size; + + if (aux_info[AT_PAGESIZES] != NULL && aux_info[AT_PAGESIZESLEN] != + NULL) { + size = aux_info[AT_PAGESIZESLEN]->a_un.a_val; + pagesizes = aux_info[AT_PAGESIZES]->a_un.a_ptr; + } else { + len = 2; + if (sysctlnametomib("hw.pagesizes", mib, &len) == 0) + size = sizeof(psa); + else { + /* As a fallback, retrieve the base page size. */ + size = sizeof(psa[0]); + if (aux_info[AT_PAGESZ] != NULL) { + psa[0] = aux_info[AT_PAGESZ]->a_un.a_val; + goto psa_filled; + } else { + mib[0] = CTL_HW; + mib[1] = HW_PAGESIZE; + len = 2; + } + } + if (sysctl(mib, len, psa, &size, NULL, 0) == -1) { + _rtld_error("sysctl for hw.pagesize(s) failed"); + rtld_die(); + } +psa_filled: + pagesizes = psa; + } + npagesizes = size / sizeof(pagesizes[0]); + /* Discard any invalid entries at the end of the array. */ + while (npagesizes > 0 && pagesizes[npagesizes - 1] == 0) + npagesizes--; +} + +/* * Add the init functions from a needed object list (and its recursive * needed objects) to "list". This is not used directly; it is a helper * function for initlist_add_objects(). The write lock must be held @@ -1108,7 +2137,7 @@ /* Process the current needed object. */ if (needed->obj != NULL) - initlist_add_objects(needed->obj, &needed->obj->next, list); + initlist_add_objects(needed->obj, needed->obj, list); } /* @@ -1121,44 +2150,108 @@ * held when this function is called. */ static void -initlist_add_objects(Obj_Entry *obj, Obj_Entry **tail, Objlist *list) +initlist_add_objects(Obj_Entry *obj, Obj_Entry *tail, Objlist *list) { - if (obj->init_done) + Obj_Entry *nobj; + + if (obj->init_scanned || obj->init_done) return; - obj->init_done = true; + obj->init_scanned = true; /* Recursively process the successor objects. */ - if (&obj->next != tail) - initlist_add_objects(obj->next, tail, list); + nobj = globallist_next(obj); + if (nobj != NULL && obj != tail) + initlist_add_objects(nobj, tail, list); /* Recursively process the needed objects. */ if (obj->needed != NULL) initlist_add_neededs(obj->needed, list); + if (obj->needed_filtees != NULL) + initlist_add_neededs(obj->needed_filtees, list); + if (obj->needed_aux_filtees != NULL) + initlist_add_neededs(obj->needed_aux_filtees, list); /* Add the object to the init list. */ - if (obj->init != (Elf_Addr)NULL) + if (obj->preinit_array != (Elf_Addr)NULL || obj->init != (Elf_Addr)NULL || + obj->init_array != (Elf_Addr)NULL) objlist_push_tail(list, obj); /* Add the object to the global fini list in the reverse order. */ - if (obj->fini != (Elf_Addr)NULL) + if ((obj->fini != (Elf_Addr)NULL || obj->fini_array != (Elf_Addr)NULL) + && !obj->on_fini_list) { objlist_push_head(&list_fini, obj); + obj->on_fini_list = true; + } } #ifndef FPTR_TARGET #define FPTR_TARGET(f) ((Elf_Addr) (f)) #endif -static bool -is_exported(const Elf_Sym *def) +static void +free_needed_filtees(Needed_Entry *n, RtldLockState *lockstate) { - Elf_Addr value; - const func_ptr_type *p; + Needed_Entry *needed, *needed1; - value = (Elf_Addr)(obj_rtld.relocbase + def->st_value); - for (p = exports; *p != NULL; p++) - if (FPTR_TARGET(*p) == value) - return true; - return false; + for (needed = n; needed != NULL; needed = needed->next) { + if (needed->obj != NULL) { + dlclose_locked(needed->obj, lockstate); + needed->obj = NULL; + } + } + for (needed = n; needed != NULL; needed = needed1) { + needed1 = needed->next; + free(needed); + } +} + +static void +unload_filtees(Obj_Entry *obj, RtldLockState *lockstate) +{ + + free_needed_filtees(obj->needed_filtees, lockstate); + obj->needed_filtees = NULL; + free_needed_filtees(obj->needed_aux_filtees, lockstate); + obj->needed_aux_filtees = NULL; + obj->filtees_loaded = false; +} + +static void +load_filtee1(Obj_Entry *obj, Needed_Entry *needed, int flags, + RtldLockState *lockstate) +{ + + for (; needed != NULL; needed = needed->next) { + needed->obj = dlopen_object(obj->strtab + needed->name, -1, obj, + flags, ((ld_loadfltr || obj->z_loadfltr) ? RTLD_NOW : RTLD_LAZY) | + RTLD_LOCAL, lockstate); + } +} + +static void +load_filtees(Obj_Entry *obj, int flags, RtldLockState *lockstate) +{ + + lock_restart_for_upgrade(lockstate); + if (!obj->filtees_loaded) { + load_filtee1(obj, obj->needed_filtees, flags, lockstate); + load_filtee1(obj, obj->needed_aux_filtees, flags, lockstate); + obj->filtees_loaded = true; + } +} + +static int +process_needed(Obj_Entry *obj, Needed_Entry *needed, int flags) +{ + Obj_Entry *obj1; + + for (; needed != NULL; needed = needed->next) { + obj1 = needed->obj = load_object(obj->strtab + needed->name, -1, obj, + flags & ~RTLD_LO_NOLOAD); + if (obj1 == NULL && !ld_tracing && (flags & RTLD_LO_FILTEES) == 0) + return (-1); + } + return (0); } /* @@ -1167,36 +2260,24 @@ * returns -1 on failure. */ static int -load_needed_objects(Obj_Entry *first) +load_needed_objects(Obj_Entry *first, int flags) { Obj_Entry *obj; - for (obj = first; obj != NULL; obj = obj->next) { - 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) - return -1; -printf("\n\nPATH: [%s]\n",path); - if (path) { - needed->obj = load_object(path); - if (needed->obj == NULL && !ld_tracing) - return -1; /* XXX - cleanup */ - } - } + for (obj = first; obj != NULL; obj = TAILQ_NEXT(obj, next)) { + if (obj->marker) + continue; + if (process_needed(obj, obj->needed, flags) == -1) + return (-1); } - - return 0; + return (0); } static int load_preload_objects(void) { char *p = ld_preload; + Obj_Entry *obj; static const char delim[] = " \t:;"; if (p == NULL) @@ -1205,109 +2286,180 @@ 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) + obj = load_object(p, -1, NULL, 0); + if (obj == NULL) return -1; /* XXX - cleanup */ + obj->z_interpose = true; p[len] = savech; p += len; p += strspn(p, delim); } + LD_UTRACE(UTRACE_PRELOAD_FINISHED, NULL, NULL, 0, 0, NULL); return 0; } +static const char * +printable_path(const char *path) +{ + + return (path == NULL ? "" : path); +} + /* * 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. + * object may be specified by name or by user-supplied file descriptor + * fd_u. In the later case, the fd_u descriptor is not closed, but its + * duplicate is. * * 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, int fd_u, const Obj_Entry *refobj, int flags) { Obj_Entry *obj; - int fd = -1; + int fd; 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; + fd = -1; + if (name != NULL) { + TAILQ_FOREACH(obj, &obj_list, next) { + if (obj->marker || obj->doomed) + continue; + if (object_match_name(obj, name)) + return (obj); + } - /* - * If we didn't find a match by pathname, open the file and check - * again by device and inode. This avoids false mismatches caused - * by multiple links or ".." in pathnames. - * - * 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) { + path = find_library(name, refobj, &fd); + if (path == NULL) + return (NULL); + } else + path = NULL; + + if (fd >= 0) { + /* + * search_library_pathfds() opens a fresh file descriptor for the + * library, so there is no need to dup(). + */ + } else if (fd_u == -1) { + /* + * If we didn't find a match by pathname, or the name is not + * supplied, open the file and check again by device and inode. + * This avoids false mismatches caused by multiple links or ".." + * in pathnames. + * + * To avoid a race, we open the file and use fstat() rather than + * using stat(). + */ + if ((fd = open(path, O_RDONLY | O_CLOEXEC | O_VERIFY)) == -1) { _rtld_error("Cannot open \"%s\"", path); - return NULL; + free(path); + return (NULL); } - if (fstat(fd, &sb) == -1) { - _rtld_error("Cannot fstat \"%s\"", path); - 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; - } + } else { + fd = fcntl(fd_u, F_DUPFD_CLOEXEC, 0); + if (fd == -1) { + _rtld_error("Cannot dup fd"); + free(path); + return (NULL); } } - - 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); - return NULL; - } - } - dbg("loading \"%s\"", path); - printf("\n\n loading \"%s\"\n", path); - obj = map_object(fd, path, &sb); - printf("\nmapped object\n"); + if (fstat(fd, &sb) == -1) { + _rtld_error("Cannot fstat \"%s\"", printable_path(path)); close(fd); - if (obj == NULL) { - free(path); + free(path); + return NULL; + } + TAILQ_FOREACH(obj, &obj_list, next) { + if (obj->marker || obj->doomed) + continue; + if (obj->ino == sb.st_ino && obj->dev == sb.st_dev) + break; + } + if (obj != NULL && name != NULL) { + object_add_name(obj, name); + free(path); + close(fd); + return obj; + } + if (flags & RTLD_LO_NOLOAD) { + free(path); + close(fd); + return (NULL); + } + + /* First use of this object, so we must map it in */ + obj = do_load_object(fd, name, path, &sb, flags); + 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, + int flags) +{ + 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\"", printable_path(path)); return NULL; } + if (fs.f_flags & MNT_NOEXEC) { + _rtld_error("Cannot execute objects on %s\n", fs.f_mntonname); + return NULL; + } + } + dbg("loading \"%s\"", printable_path(path)); + obj = map_object(fd, printable_path(path), sbp); + if (obj == NULL) + return NULL; - obj->path = path; - digest_dynamic(obj, 0); + /* + * If DT_SONAME is present in the object, digest_dynamic2 already + * added it to the object names. + */ + if (name != NULL) + object_add_name(obj, name); + obj->path = path; + digest_dynamic(obj, 0); + dbg("%s valid_hash_sysv %d valid_hash_gnu %d dynsymcount %d", obj->path, + obj->valid_hash_sysv, obj->valid_hash_gnu, obj->dynsymcount); + if (obj->z_noopen && (flags & (RTLD_LO_DLOPEN | RTLD_LO_TRACE)) == + RTLD_LO_DLOPEN) { + dbg("refusing to load non-loadable \"%s\"", obj->path); + _rtld_error("Cannot dlopen non-loadable %s", obj->path); + munmap(obj->mapbase, obj->mapsize); + obj_free(obj); + return (NULL); + } - *obj_tail = obj; - obj_tail = &obj->next; - obj_count++; - linkmap_add(obj); /* for GDB & dlinfo() */ + obj->dlopened = (flags & RTLD_LO_DLOPEN) != 0; + TAILQ_INSERT_TAIL(&obj_list, obj, next); + obj_count++; + obj_loads++; + linkmap_add(obj); /* for GDB & dlinfo() */ + max_stack_flags |= obj->stack_flags; - 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); + LD_UTRACE(UTRACE_LOAD_OBJECT, obj, obj->mapbase, obj->mapsize, 0, + obj->path); return obj; } @@ -1317,7 +2469,9 @@ { Obj_Entry *obj; - for (obj = obj_list; obj != NULL; obj = obj->next) { + TAILQ_FOREACH(obj, &obj_list, next) { + if (obj->marker) + continue; if (addr < (void *) obj->mapbase) continue; if (addr < (void *) (obj->mapbase + obj->mapsize)) @@ -1326,29 +2480,101 @@ return NULL; } +static void +preinit_main(void) +{ + Elf_Addr *preinit_addr; + int index; + + preinit_addr = (Elf_Addr *)obj_main->preinit_array; + if (preinit_addr == NULL) + return; + + for (index = 0; index < obj_main->preinit_array_num; index++) { + if (preinit_addr[index] != 0 && preinit_addr[index] != 1) { + dbg("calling preinit function for %s at %p", obj_main->path, + (void *)preinit_addr[index]); + LD_UTRACE(UTRACE_INIT_CALL, obj_main, (void *)preinit_addr[index], + 0, 0, obj_main->path); + call_init_pointer(obj_main, preinit_addr[index]); + } + } +} + /* * Call the finalization functions for each of the objects in "list" - * which are unreferenced. All of the objects are expected to have - * non-NULL fini functions. + * belonging to the DAG of "root" and referenced once. If NULL "root" + * is specified, every finalization function will be called regardless + * of the reference count and the list elements won't be freed. All of + * the objects are expected to have non-NULL fini functions. */ static void -objlist_call_fini(Objlist *list) +objlist_call_fini(Objlist *list, Obj_Entry *root, RtldLockState *lockstate) { Objlist_Entry *elm; char *saved_msg; + Elf_Addr *fini_addr; + int index; + + assert(root == NULL || root->refcount == 1); + + if (root != NULL) + root->doomed = true; /* * Preserve the current error message since a fini function might * call into the dynamic linker and overwrite it. */ saved_msg = errmsg_save(); - STAILQ_FOREACH(elm, list, link) { - if (elm->obj->refcount == 0) { - dbg("calling fini function for %s at %p", elm->obj->path, - (void *)elm->obj->fini); - call_initfini_pointer(elm->obj, elm->obj->fini); + do { + STAILQ_FOREACH(elm, list, link) { + if (root != NULL && (elm->obj->refcount != 1 || + objlist_find(&root->dagmembers, elm->obj) == NULL)) + continue; + /* Remove object from fini list to prevent recursive invocation. */ + STAILQ_REMOVE(list, elm, Struct_Objlist_Entry, link); + /* Ensure that new references cannot be acquired. */ + elm->obj->doomed = true; + + hold_object(elm->obj); + lock_release(rtld_bind_lock, lockstate); + /* + * It is legal to have both DT_FINI and DT_FINI_ARRAY defined. + * When this happens, DT_FINI_ARRAY is processed first. + */ + fini_addr = (Elf_Addr *)elm->obj->fini_array; + if (fini_addr != NULL && elm->obj->fini_array_num > 0) { + for (index = elm->obj->fini_array_num - 1; index >= 0; + index--) { + if (fini_addr[index] != 0 && fini_addr[index] != 1) { + dbg("calling fini function for %s at %p", + elm->obj->path, (void *)fini_addr[index]); + LD_UTRACE(UTRACE_FINI_CALL, elm->obj, + (void *)fini_addr[index], 0, 0, elm->obj->path); + call_initfini_pointer(elm->obj, fini_addr[index]); + } + } + } + if (elm->obj->fini != (Elf_Addr)NULL) { + dbg("calling fini function for %s at %p", elm->obj->path, + (void *)elm->obj->fini); + LD_UTRACE(UTRACE_FINI_CALL, elm->obj, (void *)elm->obj->fini, + 0, 0, elm->obj->path); + call_initfini_pointer(elm->obj, elm->obj->fini); + } + wlock_acquire(rtld_bind_lock, lockstate); + unhold_object(elm->obj); + /* No need to free anything if process is going down. */ + if (root != NULL) + free(elm); + /* + * We must restart the list traversal after every fini call + * because a dlclose() call from the fini function or from + * another thread might have modified the reference counts. + */ + break; } - } + } while (elm != NULL); errmsg_restore(saved_msg); } @@ -1358,10 +2584,24 @@ * functions. */ static void -objlist_call_init(Objlist *list) +objlist_call_init(Objlist *list, RtldLockState *lockstate) { Objlist_Entry *elm; + Obj_Entry *obj; char *saved_msg; + Elf_Addr *init_addr; + int index; + + /* + * Clean init_scanned flag so that objects can be rechecked and + * possibly initialized earlier if any of vectors called below + * cause the change by using dlopen. + */ + TAILQ_FOREACH(obj, &obj_list, next) { + if (obj->marker) + continue; + obj->init_scanned = false; + } /* * Preserve the current error message since an init function might @@ -1369,9 +2609,42 @@ */ saved_msg = errmsg_save(); STAILQ_FOREACH(elm, list, link) { - dbg("calling init function for %s at %p", elm->obj->path, - (void *)elm->obj->init); - call_initfini_pointer(elm->obj, elm->obj->init); + if (elm->obj->init_done) /* Initialized early. */ + continue; + /* + * Race: other thread might try to use this object before current + * one completes the initialization. Not much can be done here + * without better locking. + */ + elm->obj->init_done = true; + hold_object(elm->obj); + lock_release(rtld_bind_lock, lockstate); + + /* + * It is legal to have both DT_INIT and DT_INIT_ARRAY defined. + * When this happens, DT_INIT is processed first. + */ + if (elm->obj->init != (Elf_Addr)NULL) { + dbg("calling init function for %s at %p", elm->obj->path, + (void *)elm->obj->init); + LD_UTRACE(UTRACE_INIT_CALL, elm->obj, (void *)elm->obj->init, + 0, 0, elm->obj->path); + call_initfini_pointer(elm->obj, elm->obj->init); + } + init_addr = (Elf_Addr *)elm->obj->init_array; + if (init_addr != NULL) { + for (index = 0; index < elm->obj->init_array_num; index++) { + if (init_addr[index] != 0 && init_addr[index] != 1) { + dbg("calling init function for %s at %p", elm->obj->path, + (void *)init_addr[index]); + LD_UTRACE(UTRACE_INIT_CALL, elm->obj, + (void *)init_addr[index], 0, 0, elm->obj->path); + call_init_pointer(elm->obj, init_addr[index]); + } + } + } + wlock_acquire(rtld_bind_lock, lockstate); + unhold_object(elm->obj); } errmsg_restore(saved_msg); } @@ -1426,6 +2699,23 @@ } static void +objlist_put_after(Objlist *list, Obj_Entry *listobj, Obj_Entry *obj) +{ + Objlist_Entry *elm, *listelm; + + STAILQ_FOREACH(listelm, list, link) { + if (listelm->obj == listobj) + break; + } + elm = NEW(Objlist_Entry); + elm->obj = obj; + if (listelm != NULL) + STAILQ_INSERT_AFTER(list, listelm, elm, link); + else + STAILQ_INSERT_TAIL(list, elm, link); +} + +static void objlist_remove(Objlist *list, Obj_Entry *obj) { Objlist_Entry *elm; @@ -1437,77 +2727,118 @@ } /* - * Remove all of the unreferenced objects from "list". + * Relocate dag rooted in the specified object. + * Returns 0 on success, or -1 on failure. */ -static void -objlist_remove_unref(Objlist *list) -{ - Objlist newlist; - Objlist_Entry *elm; - STAILQ_INIT(&newlist); - while (!STAILQ_EMPTY(list)) { - elm = STAILQ_FIRST(list); - STAILQ_REMOVE_HEAD(list, link); - if (elm->obj->refcount == 0) - free(elm); - else - STAILQ_INSERT_TAIL(&newlist, elm, link); - } - *list = newlist; +static int +relocate_object_dag(Obj_Entry *root, bool bind_now, Obj_Entry *rtldobj, + int flags, RtldLockState *lockstate) +{ + Objlist_Entry *elm; + int error; + + error = 0; + STAILQ_FOREACH(elm, &root->dagmembers, link) { + error = relocate_object(elm->obj, bind_now, rtldobj, flags, + lockstate); + if (error == -1) + break; + } + return (error); } /* - * Relocate newly-loaded shared objects. The argument is a pointer to - * the Obj_Entry for the first such object. All objects from the first - * to the end of the list of objects are relocated. Returns 0 on success, - * or -1 on failure. + * Prepare for, or clean after, relocating an object marked with + * DT_TEXTREL or DF_TEXTREL. Before relocating, all read-only + * segments are remapped read-write. After relocations are done, the + * segment's permissions are returned back to the modes specified in + * the phdrs. If any relocation happened, or always for wired + * program, COW is triggered. */ static int -relocate_objects(Obj_Entry *first, bool bind_now, Obj_Entry *rtldobj) +reloc_textrel_prot(Obj_Entry *obj, bool before) { - Obj_Entry *obj; + const Elf_Phdr *ph; + void *base; + size_t l, sz; + int prot; - for (obj = first; obj != NULL; obj = obj->next) { + for (l = obj->phsize / sizeof(*ph), ph = obj->phdr; l > 0; + l--, ph++) { + if (ph->p_type != PT_LOAD || (ph->p_flags & PF_W) != 0) + continue; + base = obj->relocbase + trunc_page(ph->p_vaddr); + sz = round_page(ph->p_vaddr + ph->p_filesz) - + trunc_page(ph->p_vaddr); + prot = convert_prot(ph->p_flags) | (before ? PROT_WRITE : 0); + if (mprotect(base, sz, prot) == -1) { + _rtld_error("%s: Cannot write-%sable text segment: %s", + obj->path, before ? "en" : "dis", + rtld_strerror(errno)); + return (-1); + } + } + return (0); +} + +/* + * Relocate single object. + * Returns 0 on success, or -1 on failure. + */ +static int +relocate_object(Obj_Entry *obj, bool bind_now, Obj_Entry *rtldobj, + int flags, RtldLockState *lockstate) +{ + + if (obj->relocated) + return (0); + obj->relocated = true; if (obj != rtldobj) - dbg("relocating \"%s\"", obj->path); - if (obj->nbuckets == 0 || obj->nchains == 0 || obj->buckets == NULL || - obj->symtab == NULL || obj->strtab == NULL) { - _rtld_error("%s: Shared object has no run-time symbol table", - obj->path); - return -1; + dbg("relocating \"%s\"", obj->path); + + if (obj->symtab == NULL || obj->strtab == NULL || + !(obj->valid_hash_sysv || obj->valid_hash_gnu)) { + _rtld_error("%s: Shared object has no run-time symbol table", + obj->path); + return (-1); } - if (obj->textrel) { - /* There are relocations to the write-protected text segment. */ - if (mprotect(obj->mapbase, obj->textsize, - PROT_READ|PROT_WRITE|PROT_EXEC) == -1) { - _rtld_error("%s: Cannot write-enable text segment: %s", - obj->path, strerror(errno)); - return -1; - } - } + /* There are relocations to the write-protected text segment. */ + if (obj->textrel && reloc_textrel_prot(obj, true) != 0) + return (-1); - /* Process the non-PLT relocations. */ - if (reloc_non_plt(obj, rtldobj)) - return -1; + /* Process the non-PLT non-IFUNC relocations. */ + if (reloc_non_plt(obj, rtldobj, flags, lockstate)) + return (-1); - if (obj->textrel) { /* Re-protected the text segment. */ - if (mprotect(obj->mapbase, obj->textsize, - PROT_READ|PROT_EXEC) == -1) { - _rtld_error("%s: Cannot write-protect text segment: %s", - obj->path, strerror(errno)); - return -1; - } - } + /* Re-protected the text segment. */ + if (obj->textrel && reloc_textrel_prot(obj, false) != 0) + return (-1); + + /* Set the special PLT or GOT entries. */ + init_pltgot(obj); /* Process the PLT relocations. */ if (reloc_plt(obj) == -1) - return -1; + return (-1); /* Relocate the jump slots if we are doing immediate binding. */ if (obj->bind_now || bind_now) - if (reloc_jmpslots(obj) == -1) - return -1; + if (reloc_jmpslots(obj, flags, lockstate) == -1) + return (-1); + + /* + * Process the non-PLT IFUNC relocations. The relocations are + * processed in two phases, because IFUNC resolvers may + * reference other symbols, which must be readily processed + * before resolvers are called. + */ + if (obj->non_plt_gnu_ifunc && + reloc_non_plt(obj, rtldobj, flags | SYMLOOK_IFUNC, lockstate)) + return (-1); + + if (!obj->mainprog && obj_enforce_relro(obj) == -1) + return (-1); /* * Set up the magic number and version in the Obj_Entry. These @@ -1517,11 +2848,84 @@ obj->magic = RTLD_MAGIC; obj->version = RTLD_VERSION; - /* Set the special PLT or GOT entries. */ - init_pltgot(obj); - } + return (0); +} - return 0; +/* + * Relocate newly-loaded shared objects. The argument is a pointer to + * the Obj_Entry for the first such object. All objects from the first + * to the end of the list of objects are relocated. Returns 0 on success, + * or -1 on failure. + */ +static int +relocate_objects(Obj_Entry *first, bool bind_now, Obj_Entry *rtldobj, + int flags, RtldLockState *lockstate) +{ + Obj_Entry *obj; + int error; + + for (error = 0, obj = first; obj != NULL; + obj = TAILQ_NEXT(obj, next)) { + if (obj->marker) + continue; + error = relocate_object(obj, bind_now, rtldobj, flags, + lockstate); + if (error == -1) + break; + } + return (error); +} + +/* + * The handling of R_MACHINE_IRELATIVE relocations and jumpslots + * referencing STT_GNU_IFUNC symbols is postponed till the other + * relocations are done. The indirect functions specified as + * ifunc are allowed to call other symbols, so we need to have + * objects relocated before asking for resolution from indirects. + * + * The R_MACHINE_IRELATIVE slots are resolved in greedy fashion, + * instead of the usual lazy handling of PLT slots. It is + * consistent with how GNU does it. + */ +static int +resolve_object_ifunc(Obj_Entry *obj, bool bind_now, int flags, + RtldLockState *lockstate) +{ + if (obj->irelative && reloc_iresolve(obj, lockstate) == -1) + return (-1); + if ((obj->bind_now || bind_now) && obj->gnu_ifunc && + reloc_gnu_ifunc(obj, flags, lockstate) == -1) + return (-1); + return (0); +} + +static int +resolve_objects_ifunc(Obj_Entry *first, bool bind_now, int flags, + RtldLockState *lockstate) +{ + Obj_Entry *obj; + + for (obj = first; obj != NULL; obj = TAILQ_NEXT(obj, next)) { + if (obj->marker) + continue; + if (resolve_object_ifunc(obj, bind_now, flags, lockstate) == -1) + return (-1); + } + return (0); +} + +static int +initlist_objects_ifunc(Objlist *list, bool bind_now, int flags, + RtldLockState *lockstate) +{ + Objlist_Entry *elm; + + STAILQ_FOREACH(elm, list, link) { + if (resolve_object_ifunc(elm->obj, bind_now, flags, + lockstate) == -1) + return (-1); + } + return (0); } /* @@ -1531,24 +2935,25 @@ static void rtld_exit(void) { - Obj_Entry *obj; + RtldLockState lockstate; + wlock_acquire(rtld_bind_lock, &lockstate); dbg("rtld_exit()"); - /* Clear all the reference counts so the fini functions will be called. */ - for (obj = obj_list; obj != NULL; obj = obj->next) - obj->refcount = 0; - objlist_call_fini(&list_fini); + objlist_call_fini(&list_fini, NULL, &lockstate); /* No need to remove the items from the list, since we are exiting. */ if (!libmap_disable) lm_fini(); + lock_release(rtld_bind_lock, &lockstate); } +/* + * Iterate over a search path, translate each element, and invoke the + * callback on the result. + */ static void * path_enumerate(const char *path, path_enum_proc callback, void *arg) { -#ifdef COMPAT_32BIT const char *trans; -#endif if (path == NULL) return (NULL); @@ -1558,13 +2963,11 @@ char *res; len = strcspn(path, ":;"); -#ifdef COMPAT_32BIT trans = lm_findn(NULL, path, len); if (trans) res = callback(trans, strlen(trans), arg); else -#endif - res = callback(path, len, arg); + res = callback(path, len, arg); if (res != NULL) return (res); @@ -1618,7 +3021,7 @@ if (path == NULL) return NULL; -printf("name: [%s]\n",name); + arg.name = name; arg.namelen = strlen(name); arg.buffer = xmalloc(PATH_MAX); @@ -1631,40 +3034,115 @@ return (p); } + +/* + * Finds the library with the given name using the directory descriptors + * listed in the LD_LIBRARY_PATH_FDS environment variable. + * + * Returns a freshly-opened close-on-exec file descriptor for the library, + * or -1 if the library cannot be found. + */ +static char * +search_library_pathfds(const char *name, const char *path, int *fdp) +{ + char *envcopy, *fdstr, *found, *last_token; + size_t len; + int dirfd, fd; + + dbg("%s('%s', '%s', fdp)", __func__, name, path); + + /* Don't load from user-specified libdirs into setuid binaries. */ + if (!trust) + return (NULL); + + /* We can't do anything if LD_LIBRARY_PATH_FDS isn't set. */ + if (path == NULL) + return (NULL); + + /* LD_LIBRARY_PATH_FDS only works with relative paths. */ + if (name[0] == '/') { + dbg("Absolute path (%s) passed to %s", name, __func__); + return (NULL); + } + + /* + * Use strtok_r() to walk the FD:FD:FD list. This requires a local + * copy of the path, as strtok_r rewrites separator tokens + * with '\0'. + */ + found = NULL; + envcopy = xstrdup(path); + for (fdstr = strtok_r(envcopy, ":", &last_token); fdstr != NULL; + fdstr = strtok_r(NULL, ":", &last_token)) { + dirfd = parse_integer(fdstr); + if (dirfd < 0) { + _rtld_error("failed to parse directory FD: '%s'", + fdstr); + break; + } + fd = __sys_openat(dirfd, name, O_RDONLY | O_CLOEXEC | O_VERIFY); + if (fd >= 0) { + *fdp = fd; + len = strlen(fdstr) + strlen(name) + 3; + found = xmalloc(len); + if (rtld_snprintf(found, len, "#%d/%s", dirfd, name) < 0) { + _rtld_error("error generating '%d/%s'", + dirfd, name); + rtld_die(); + } + dbg("open('%s') => %d", found, fd); + break; + } + } + free(envcopy); + + return (found); +} + + int dlclose(void *handle) { - Obj_Entry *root; - int lockstate; + RtldLockState lockstate; + int error; - lockstate = wlock_acquire(rtld_bind_lock); + wlock_acquire(rtld_bind_lock, &lockstate); + error = dlclose_locked(handle, &lockstate); + lock_release(rtld_bind_lock, &lockstate); + return (error); +} + +static int +dlclose_locked(void *handle, RtldLockState *lockstate) +{ + Obj_Entry *root; + root = dlcheck(handle); - if (root == NULL) { - wlock_release(rtld_bind_lock, lockstate); + if (root == NULL) return -1; - } + LD_UTRACE(UTRACE_DLCLOSE_START, handle, NULL, 0, root->dl_refcount, + root->path); /* Unreference the object and its dependencies. */ root->dl_refcount--; - unref_dag(root); - - if (root->refcount == 0) { + if (root->refcount == 1) { /* - * The object is no longer referenced, so we must unload it. - * First, call the fini functions with no locks held. + * The object will be no longer referenced, so we must unload it. + * First, call the fini functions. */ - wlock_release(rtld_bind_lock, lockstate); - objlist_call_fini(&list_fini); - lockstate = wlock_acquire(rtld_bind_lock); - objlist_remove_unref(&list_fini); + objlist_call_fini(&list_fini, root, lockstate); + + unref_dag(root); /* Finish cleaning up the newly-unreferenced objects. */ GDB_STATE(RT_DELETE,&root->linkmap); - unload_object(root); + unload_object(root, lockstate); GDB_STATE(RT_CONSISTENT,NULL); - } - wlock_release(rtld_bind_lock, lockstate); + } else + unref_dag(root); + + LD_UTRACE(UTRACE_DLCLOSE_STOP, handle, NULL, 0, 0, NULL); return 0; } @@ -1701,164 +3179,370 @@ void * dlopen(const char *name, int mode) { - Obj_Entry **old_obj_tail; + + return (rtld_dlopen(name, -1, mode)); +} + +void * +fdlopen(int fd, int mode) +{ + + return (rtld_dlopen(NULL, fd, mode)); +} + +static void * +rtld_dlopen(const char *name, int fd, int mode) +{ + RtldLockState lockstate; + int lo_flags; + + LD_UTRACE(UTRACE_DLOPEN_START, NULL, NULL, 0, mode, name); + ld_tracing = (mode & RTLD_TRACE) == 0 ? NULL : "1"; + if (ld_tracing != NULL) { + rlock_acquire(rtld_bind_lock, &lockstate); + if (sigsetjmp(lockstate.env, 0) != 0) + lock_upgrade(rtld_bind_lock, &lockstate); + environ = (char **)*get_program_var_addr("environ", &lockstate); + lock_release(rtld_bind_lock, &lockstate); + } + lo_flags = RTLD_LO_DLOPEN; + if (mode & RTLD_NODELETE) + lo_flags |= RTLD_LO_NODELETE; + if (mode & RTLD_NOLOAD) + lo_flags |= RTLD_LO_NOLOAD; + if (ld_tracing != NULL) + lo_flags |= RTLD_LO_TRACE; + + return (dlopen_object(name, fd, obj_main, lo_flags, + mode & (RTLD_MODEMASK | RTLD_GLOBAL), NULL)); +} + +static void +dlopen_cleanup(Obj_Entry *obj, RtldLockState *lockstate) +{ + + obj->dl_refcount--; + unref_dag(obj); + if (obj->refcount == 0) + unload_object(obj, lockstate); +} + +static Obj_Entry * +dlopen_object(const char *name, int fd, Obj_Entry *refobj, int lo_flags, + int mode, RtldLockState *lockstate) +{ + Obj_Entry *old_obj_tail; Obj_Entry *obj; Objlist initlist; - int result, lockstate; - - ld_tracing = (mode & RTLD_TRACE) == 0 ? NULL : "1"; - if (ld_tracing != NULL) - environ = (char **)*get_program_var_addr("environ"); + RtldLockState mlockstate; + int result; objlist_init(&initlist); - lockstate = wlock_acquire(rtld_bind_lock); + if (lockstate == NULL && !(lo_flags & RTLD_LO_EARLY)) { + wlock_acquire(rtld_bind_lock, &mlockstate); + lockstate = &mlockstate; + } GDB_STATE(RT_ADD,NULL); - old_obj_tail = obj_tail; + old_obj_tail = globallist_curr(TAILQ_LAST(&obj_list, obj_entry_q)); obj = NULL; - if (name == NULL) { + if (name == NULL && fd == -1) { obj = obj_main; obj->refcount++; } else { - char *path = find_library(name, obj_main); - if (path != NULL) - obj = load_object(path); + obj = load_object(name, fd, refobj, lo_flags); } if (obj) { obj->dl_refcount++; if (mode & RTLD_GLOBAL && objlist_find(&list_global, obj) == NULL) objlist_push_tail(&list_global, obj); - mode &= RTLD_MODEMASK; - if (*old_obj_tail != NULL) { /* We loaded something new. */ - assert(*old_obj_tail == obj); - - result = load_needed_objects(obj); + if (globallist_next(old_obj_tail) != NULL) { + /* We loaded something new. */ + assert(globallist_next(old_obj_tail) == obj); + result = load_needed_objects(obj, + lo_flags & (RTLD_LO_DLOPEN | RTLD_LO_EARLY)); + init_dag(obj); + ref_dag(obj); + if (result != -1) + result = rtld_verify_versions(&obj->dagmembers); if (result != -1 && ld_tracing) goto trace; - - if (result == -1 || - (init_dag(obj), relocate_objects(obj, mode == RTLD_NOW, - &obj_rtld)) == -1) { - obj->dl_refcount--; - unref_dag(obj); - if (obj->refcount == 0) - unload_object(obj); + if (result == -1 || relocate_object_dag(obj, + (mode & RTLD_MODEMASK) == RTLD_NOW, &obj_rtld, + (lo_flags & RTLD_LO_EARLY) ? SYMLOOK_EARLY : 0, + lockstate) == -1) { + dlopen_cleanup(obj, lockstate); obj = NULL; + } else if (lo_flags & RTLD_LO_EARLY) { + /* + * Do not call the init functions for early loaded + * filtees. The image is still not initialized enough + * for them to work. + * + * Our object is found by the global object list and + * will be ordered among all init calls done right + * before transferring control to main. + */ } else { /* Make list of init functions to call. */ - initlist_add_objects(obj, &obj->next, &initlist); + initlist_add_objects(obj, obj, &initlist); } + /* + * Process all no_delete or global objects here, given + * them own DAGs to prevent their dependencies from being + * unloaded. This has to be done after we have loaded all + * of the dependencies, so that we do not miss any. + */ + if (obj != NULL) + process_z(obj); } else { - - /* Bump the reference counts for objects on this DAG. */ + /* + * Bump the reference counts for objects on this DAG. If + * this is the first dlopen() call for the object that was + * already loaded as a dependency, initialize the dag + * starting at it. + */ + init_dag(obj); ref_dag(obj); - if (ld_tracing) + if ((lo_flags & RTLD_LO_TRACE) != 0) goto trace; } + if (obj != NULL && ((lo_flags & RTLD_LO_NODELETE) != 0 || + obj->z_nodelete) && !obj->ref_nodel) { + dbg("obj %s nodelete", obj->path); + ref_dag(obj); + obj->z_nodelete = obj->ref_nodel = true; + } } + LD_UTRACE(UTRACE_DLOPEN_STOP, obj, NULL, 0, obj ? obj->dl_refcount : 0, + name); GDB_STATE(RT_CONSISTENT,obj ? &obj->linkmap : NULL); - /* Call the init functions with no locks held. */ - wlock_release(rtld_bind_lock, lockstate); - objlist_call_init(&initlist); - lockstate = wlock_acquire(rtld_bind_lock); + if (!(lo_flags & RTLD_LO_EARLY)) { + map_stacks_exec(lockstate); + } + + if (initlist_objects_ifunc(&initlist, (mode & RTLD_MODEMASK) == RTLD_NOW, + (lo_flags & RTLD_LO_EARLY) ? SYMLOOK_EARLY : 0, + lockstate) == -1) { + objlist_clear(&initlist); + dlopen_cleanup(obj, lockstate); + if (lockstate == &mlockstate) + lock_release(rtld_bind_lock, lockstate); + return (NULL); + } + + if (!(lo_flags & RTLD_LO_EARLY)) { + /* Call the init functions. */ + objlist_call_init(&initlist, lockstate); + } objlist_clear(&initlist); - wlock_release(rtld_bind_lock, lockstate); + if (lockstate == &mlockstate) + lock_release(rtld_bind_lock, lockstate); return obj; trace: trace_loaded_objects(obj); - wlock_release(rtld_bind_lock, lockstate); + if (lockstate == &mlockstate) + lock_release(rtld_bind_lock, lockstate); exit(0); } +static void * +do_dlsym(void *handle, const char *name, void *retaddr, const Ver_Entry *ve, + int flags) +{ + DoneList donelist; + const Obj_Entry *obj, *defobj; + const Elf_Sym *def; + SymLook req; + RtldLockState lockstate; + tls_index ti; + void *sym; + int res; + + def = NULL; + defobj = NULL; + symlook_init(&req, name); + req.ventry = ve; + req.flags = flags | SYMLOOK_IN_PLT; + req.lockstate = &lockstate; + + LD_UTRACE(UTRACE_DLSYM_START, handle, NULL, 0, 0, name); + rlock_acquire(rtld_bind_lock, &lockstate); + if (sigsetjmp(lockstate.env, 0) != 0) + lock_upgrade(rtld_bind_lock, &lockstate); + if (handle == NULL || handle == RTLD_NEXT || + handle == RTLD_DEFAULT || handle == RTLD_SELF) { + + if ((obj = obj_from_addr(retaddr)) == NULL) { + _rtld_error("Cannot determine caller's shared object"); + lock_release(rtld_bind_lock, &lockstate); + LD_UTRACE(UTRACE_DLSYM_STOP, handle, NULL, 0, 0, name); + return NULL; + } + if (handle == NULL) { /* Just the caller's shared object. */ + res = symlook_obj(&req, obj); + if (res == 0) { + def = req.sym_out; + defobj = req.defobj_out; + } + } else if (handle == RTLD_NEXT || /* Objects after caller's */ + handle == RTLD_SELF) { /* ... caller included */ + if (handle == RTLD_NEXT) + obj = globallist_next(obj); + for (; obj != NULL; obj = TAILQ_NEXT(obj, next)) { + if (obj->marker) + continue; + res = symlook_obj(&req, obj); + if (res == 0) { + if (def == NULL || + ELF_ST_BIND(req.sym_out->st_info) != STB_WEAK) { + def = req.sym_out; + defobj = req.defobj_out; + if (ELF_ST_BIND(def->st_info) != STB_WEAK) + break; + } + } + } + /* + * Search the dynamic linker itself, and possibly resolve the + * symbol from there. This is how the application links to + * dynamic linker services such as dlopen. + */ + if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) { + res = symlook_obj(&req, &obj_rtld); + if (res == 0) { + def = req.sym_out; + defobj = req.defobj_out; + } + } + } else { + assert(handle == RTLD_DEFAULT); + res = symlook_default(&req, obj); + if (res == 0) { + defobj = req.defobj_out; + def = req.sym_out; + } + } + } else { + if ((obj = dlcheck(handle)) == NULL) { + lock_release(rtld_bind_lock, &lockstate); + LD_UTRACE(UTRACE_DLSYM_STOP, handle, NULL, 0, 0, name); + return NULL; + } + + donelist_init(&donelist); + if (obj->mainprog) { + /* Handle obtained by dlopen(NULL, ...) implies global scope. */ + res = symlook_global(&req, &donelist); + if (res == 0) { + def = req.sym_out; + defobj = req.defobj_out; + } + /* + * Search the dynamic linker itself, and possibly resolve the + * symbol from there. This is how the application links to + * dynamic linker services such as dlopen. + */ + if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) { + res = symlook_obj(&req, &obj_rtld); + if (res == 0) { + def = req.sym_out; + defobj = req.defobj_out; + } + } + } + else { + /* Search the whole DAG rooted at the given object. */ + res = symlook_list(&req, &obj->dagmembers, &donelist); + if (res == 0) { + def = req.sym_out; + defobj = req.defobj_out; + } + } + } + + if (def != NULL) { + lock_release(rtld_bind_lock, &lockstate); + + /* + * The value required by the caller is derived from the value + * of the symbol. this is simply the relocated value of the + * symbol. + */ + if (ELF_ST_TYPE(def->st_info) == STT_FUNC) + sym = make_function_pointer(def, defobj); + else if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC) + sym = rtld_resolve_ifunc(defobj, def); + else if (ELF_ST_TYPE(def->st_info) == STT_TLS) { + ti.ti_module = defobj->tlsindex; + ti.ti_offset = def->st_value; + sym = __tls_get_addr(&ti); + } else + sym = defobj->relocbase + def->st_value; + LD_UTRACE(UTRACE_DLSYM_STOP, handle, sym, 0, 0, name); + return (sym); + } + + _rtld_error("Undefined symbol \"%s\"", name); + lock_release(rtld_bind_lock, &lockstate); + LD_UTRACE(UTRACE_DLSYM_STOP, handle, NULL, 0, 0, name); + return NULL; +} + void * dlsym(void *handle, const char *name) { + return do_dlsym(handle, name, __builtin_return_address(0), NULL, + SYMLOOK_DLSYM); +} + +dlfunc_t +dlfunc(void *handle, const char *name) +{ + union { + void *d; + dlfunc_t f; + } rv; + + rv.d = do_dlsym(handle, name, __builtin_return_address(0), NULL, + SYMLOOK_DLSYM); + return (rv.f); +} + +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 +_rtld_addr_phdr(const void *addr, struct dl_phdr_info *phdr_info) +{ const Obj_Entry *obj; - unsigned long hash; - const Elf_Sym *def; - const Obj_Entry *defobj; - int lockstate; + RtldLockState lockstate; - hash = elf_hash(name); - def = NULL; - defobj = NULL; - - 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); - 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) { - defobj = obj; - break; - } - } - } else { - assert(handle == RTLD_DEFAULT); - def = symlook_default(name, hash, obj, &defobj, true); - } - } else { - if ((obj = dlcheck(handle)) == NULL) { - rlock_release(rtld_bind_lock, lockstate); - return NULL; - } - - if (obj->mainprog) { - DoneList donelist; - - /* Search main program and all libraries loaded by it. */ - donelist_init(&donelist); - def = symlook_list(name, hash, &list_main, &defobj, true, - &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); - defobj = obj; - } + rlock_acquire(rtld_bind_lock, &lockstate); + obj = obj_from_addr(addr); + if (obj == NULL) { + _rtld_error("No shared object contains address"); + lock_release(rtld_bind_lock, &lockstate); + return (0); } - - if (def != NULL) { - rlock_release(rtld_bind_lock, lockstate); - - /* - * The value required by the caller is derived from the value - * of the symbol. For the ia64 architecture, we need to - * construct a function descriptor which the caller can use to - * call the function with the right 'gp' value. For other - * architectures and for non-functions, the value is simply - * the relocated value of the symbol. - */ - if (ELF_ST_TYPE(def->st_info) == STT_FUNC) - return make_function_pointer(def, defobj); - else - return defobj->relocbase + def->st_value; - } - - _rtld_error("Undefined symbol \"%s\"", name); - rlock_release(rtld_bind_lock, lockstate); - return NULL; + rtld_fill_dl_phdr_info(obj, phdr_info); + lock_release(rtld_bind_lock, &lockstate); + return (1); } int @@ -1868,13 +3552,13 @@ const Elf_Sym *def; void *symbol_addr; unsigned long symoffset; - int lockstate; + RtldLockState lockstate; - lockstate = rlock_acquire(rtld_bind_lock); + rlock_acquire(rtld_bind_lock, &lockstate); obj = obj_from_addr(addr); if (obj == NULL) { _rtld_error("No shared object contains address"); - rlock_release(rtld_bind_lock, lockstate); + lock_release(rtld_bind_lock, &lockstate); return 0; } info->dli_fname = obj->path; @@ -1886,7 +3570,7 @@ * Walk the symbol list looking for the symbol whose address is * closest to the address sent in. */ - for (symoffset = 0; symoffset < obj->nchains; symoffset++) { + for (symoffset = 0; symoffset < obj->dynsymcount; symoffset++) { def = obj->symtab + symoffset; /* @@ -1913,7 +3597,7 @@ if (info->dli_saddr == addr) break; } - rlock_release(rtld_bind_lock, lockstate); + lock_release(rtld_bind_lock, &lockstate); return 1; } @@ -1921,9 +3605,10 @@ dlinfo(void *handle, int request, void *p) { const Obj_Entry *obj; - int error, lockstate; + RtldLockState lockstate; + int error; - lockstate = rlock_acquire(rtld_bind_lock); + rlock_acquire(rtld_bind_lock, &lockstate); if (handle == NULL || handle == RTLD_SELF) { void *retaddr; @@ -1935,7 +3620,7 @@ obj = dlcheck(handle); if (obj == NULL) { - rlock_release(rtld_bind_lock, lockstate); + lock_release(rtld_bind_lock, &lockstate); return (-1); } @@ -1958,18 +3643,65 @@ error = -1; } - rlock_release(rtld_bind_lock, lockstate); + lock_release(rtld_bind_lock, &lockstate); return (error); } -struct fill_search_info_args { - int request; - unsigned int flags; - Dl_serinfo *serinfo; - Dl_serpath *serpath; - char *strspace; -}; +static void +rtld_fill_dl_phdr_info(const Obj_Entry *obj, struct dl_phdr_info *phdr_info) +{ + + phdr_info->dlpi_addr = (Elf_Addr)obj->relocbase; + phdr_info->dlpi_name = obj->path; + phdr_info->dlpi_phdr = obj->phdr; + phdr_info->dlpi_phnum = obj->phsize / sizeof(obj->phdr[0]); + phdr_info->dlpi_tls_modid = obj->tlsindex; + phdr_info->dlpi_tls_data = obj->tlsinit; + phdr_info->dlpi_adds = obj_loads; + phdr_info->dlpi_subs = obj_loads - obj_count; +} + +int +dl_iterate_phdr(__dl_iterate_hdr_callback callback, void *param) +{ + struct dl_phdr_info phdr_info; + Obj_Entry *obj, marker; + RtldLockState bind_lockstate, phdr_lockstate; + int error; + + init_marker(&marker); + error = 0; + + wlock_acquire(rtld_phdr_lock, &phdr_lockstate); + wlock_acquire(rtld_bind_lock, &bind_lockstate); + for (obj = globallist_curr(TAILQ_FIRST(&obj_list)); obj != NULL;) { + TAILQ_INSERT_AFTER(&obj_list, obj, &marker, next); + rtld_fill_dl_phdr_info(obj, &phdr_info); + hold_object(obj); + lock_release(rtld_bind_lock, &bind_lockstate); + + error = callback(&phdr_info, sizeof phdr_info, param); + + wlock_acquire(rtld_bind_lock, &bind_lockstate); + unhold_object(obj); + obj = globallist_next(&marker); + TAILQ_REMOVE(&obj_list, &marker, next); + if (error != 0) { + lock_release(rtld_bind_lock, &bind_lockstate); + lock_release(rtld_phdr_lock, &phdr_lockstate); + return (error); + } + } + + if (error == 0) { + rtld_fill_dl_phdr_info(&obj_rtld, &phdr_info); + lock_release(rtld_bind_lock, &bind_lockstate); + error = callback(&phdr_info, sizeof(phdr_info), param); + } + lock_release(rtld_phdr_lock, &phdr_lockstate); + return (error); +} static void * fill_search_info(const char *dir, size_t dirlen, void *param) @@ -1980,7 +3712,7 @@ if (arg->request == RTLD_DI_SERINFOSIZE) { arg->serinfo->dls_cnt ++; - arg->serinfo->dls_size += sizeof(Dl_serpath) + dirlen + 1; + arg->serinfo->dls_size += sizeof(struct dl_serpath) + dirlen + 1; } else { struct dl_serpath *s_entry; @@ -2010,10 +3742,12 @@ _info.dls_size = __offsetof(struct dl_serinfo, dls_serpath); _info.dls_cnt = 0; - path_enumerate(ld_library_path, fill_search_info, &args); path_enumerate(obj->rpath, fill_search_info, &args); - path_enumerate(gethints(), fill_search_info, &args); - path_enumerate(STANDARD_LIBRARY_PATH, fill_search_info, &args); + path_enumerate(ld_library_path, fill_search_info, &args); + path_enumerate(obj->runpath, fill_search_info, &args); + path_enumerate(gethints(obj->z_nodeflib), fill_search_info, &args); + if (!obj->z_nodeflib) + path_enumerate(ld_standard_library_path, fill_search_info, &args); if (request == RTLD_DI_SERINFOSIZE) { @@ -2032,20 +3766,26 @@ args.serpath = &info->dls_serpath[0]; args.strspace = (char *)&info->dls_serpath[_info.dls_cnt]; + args.flags = LA_SER_RUNPATH; + if (path_enumerate(obj->rpath, fill_search_info, &args) != NULL) + return (-1); + args.flags = LA_SER_LIBPATH; if (path_enumerate(ld_library_path, fill_search_info, &args) != NULL) return (-1); args.flags = LA_SER_RUNPATH; - if (path_enumerate(obj->rpath, fill_search_info, &args) != NULL) + if (path_enumerate(obj->runpath, fill_search_info, &args) != NULL) return (-1); args.flags = LA_SER_CONFIG; - if (path_enumerate(gethints(), fill_search_info, &args) != NULL) + if (path_enumerate(gethints(obj->z_nodeflib), fill_search_info, &args) + != NULL) return (-1); args.flags = LA_SER_DEFAULT; - if (path_enumerate(STANDARD_LIBRARY_PATH, fill_search_info, &args) != NULL) + if (!obj->z_nodeflib && + path_enumerate(ld_standard_library_path, fill_search_info, &args) != NULL) return (-1); return (0); } @@ -2093,6 +3833,22 @@ return (0); } +static int +rtld_dirname_abs(const char *path, char *base) +{ + char *last; + + if (realpath(path, base) == NULL) + return (-1); + dbg("%s -> %s", path, base); + last = strrchr(base, '/'); + if (last == NULL) + return (-1); + if (last != base) + *last = '\0'; + return (0); +} + static void linkmap_add(Obj_Entry *obj) { @@ -2160,29 +3916,65 @@ void r_debug_state(struct r_debug* rd, struct link_map *m) { + /* + * The following is a hack to force the compiler to emit calls to + * this function, even when optimizing. If the function is empty, + * the compiler is not obliged to emit any code for calls to it, + * even when marked __noinline. However, gdb depends on those + * calls being made. + */ + __compiler_membar(); +} + +/* + * A function called after init routines have completed. This can be used to + * break before a program's entry routine is called, and can be used when + * main is not available in the symbol table. + */ +void +_r_debug_postinit(struct link_map *m) +{ + + /* See r_debug_state(). */ + __compiler_membar(); +} + +static void +release_object(Obj_Entry *obj) +{ + + if (obj->holdcount > 0) { + obj->unholdfree = true; + return; + } + munmap(obj->mapbase, obj->mapsize); + linkmap_delete(obj); + obj_free(obj); } /* * Get address of the pointer variable in the main program. + * Prefer non-weak symbol over the weak one. */ static const void ** -get_program_var_addr(const char *name) +get_program_var_addr(const char *name, RtldLockState *lockstate) { - const Obj_Entry *obj; - unsigned long hash; + SymLook req; + DoneList donelist; - hash = elf_hash(name); - for (obj = obj_main; obj != NULL; obj = obj->next) { - const Elf_Sym *def; - - if ((def = symlook_obj(name, hash, obj, false)) != NULL) { - const void **addr; - - addr = (const void **)(obj->relocbase + def->st_value); - return addr; - } - } - return NULL; + symlook_init(&req, name); + req.lockstate = lockstate; + donelist_init(&donelist); + if (symlook_global(&req, &donelist) != 0) + return (NULL); + if (ELF_ST_TYPE(req.sym_out->st_info) == STT_FUNC) + return ((const void **)make_function_pointer(req.sym_out, + req.defobj_out)); + else if (ELF_ST_TYPE(req.sym_out->st_info) == STT_GNU_IFUNC) + return ((const void **)rtld_resolve_ifunc(req.defobj_out, req.sym_out)); + else + return ((const void **)(req.defobj_out->relocbase + + req.sym_out->st_value)); } /* @@ -2195,157 +3987,437 @@ { const void **addr; - if ((addr = get_program_var_addr(name)) != NULL) { + if ((addr = get_program_var_addr(name, NULL)) != NULL) { dbg("\"%s\": *%p <-- %p", name, addr, value); *addr = value; } } /* + * Search the global objects, including dependencies and main object, + * for the given symbol. + */ +static int +symlook_global(SymLook *req, DoneList *donelist) +{ + SymLook req1; + const Objlist_Entry *elm; + int res; + + symlook_init_from_req(&req1, req); + + /* Search all objects loaded at program start up. */ + if (req->defobj_out == NULL || + ELF_ST_BIND(req->sym_out->st_info) == STB_WEAK) { + res = symlook_list(&req1, &list_main, donelist); + if (res == 0 && (req->defobj_out == NULL || + ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK)) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + assert(req->defobj_out != NULL); + } + } + + /* Search all DAGs whose roots are RTLD_GLOBAL objects. */ + STAILQ_FOREACH(elm, &list_global, link) { + if (req->defobj_out != NULL && + ELF_ST_BIND(req->sym_out->st_info) != STB_WEAK) + break; + res = symlook_list(&req1, &elm->obj->dagmembers, donelist); + if (res == 0 && (req->defobj_out == NULL || + ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK)) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + assert(req->defobj_out != NULL); + } + } + + return (req->sym_out != NULL ? 0 : ESRCH); +} + +/* * Given a symbol name in a referencing object, find the corresponding * definition of the symbol. Returns a pointer to the symbol, or NULL if * no definition was found. Returns a pointer to the Obj_Entry of the * 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) +static int +symlook_default(SymLook *req, const Obj_Entry *refobj) { DoneList donelist; - const Elf_Sym *def; - const Elf_Sym *symp; - const Obj_Entry *obj; - const Obj_Entry *defobj; const Objlist_Entry *elm; - def = NULL; - defobj = NULL; + SymLook req1; + int res; + donelist_init(&donelist); + symlook_init_from_req(&req1, req); - /* Look first in the referencing object if linked symbolically. */ - if (refobj->symbolic && !donelist_check(&donelist, refobj)) { - symp = symlook_obj(name, hash, refobj, in_plt); - if (symp != NULL) { - def = symp; - defobj = refobj; - } + /* + * Look first in the referencing object if linked symbolically, + * and similarly handle protected symbols. + */ + res = symlook_obj(&req1, refobj); + if (res == 0 && (refobj->symbolic || + ELF_ST_VISIBILITY(req1.sym_out->st_other) == STV_PROTECTED)) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + assert(req->defobj_out != NULL); } + if (refobj->symbolic || req->defobj_out != NULL) + donelist_check(&donelist, refobj); - /* 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); - if (symp != NULL && - (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) { - def = symp; - defobj = obj; - } - } - - /* Search all DAGs whose roots are RTLD_GLOBAL objects. */ - 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); - if (symp != NULL && - (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) { - def = symp; - defobj = obj; - } - } + symlook_global(req, &donelist); /* Search all dlopened DAGs containing the referencing object. */ STAILQ_FOREACH(elm, &refobj->dldags, link) { - if (def != NULL && ELF_ST_BIND(def->st_info) != STB_WEAK) + if (req->sym_out != NULL && + ELF_ST_BIND(req->sym_out->st_info) != STB_WEAK) break; - symp = symlook_list(name, hash, &elm->obj->dagmembers, &obj, in_plt, - &donelist); - if (symp != NULL && - (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) { - def = symp; - defobj = obj; + res = symlook_list(&req1, &elm->obj->dagmembers, &donelist); + if (res == 0 && (req->sym_out == NULL || + ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK)) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + assert(req->defobj_out != NULL); } } /* * Search the dynamic linker itself, and possibly resolve the * symbol from there. This is how the application links to - * dynamic linker services such as dlopen. Only the values listed - * in the "exports" array can be resolved from the dynamic linker. + * dynamic linker services such as dlopen. */ - if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) { - symp = symlook_obj(name, hash, &obj_rtld, in_plt); - if (symp != NULL && is_exported(symp)) { - def = symp; - defobj = &obj_rtld; + if (req->sym_out == NULL || + ELF_ST_BIND(req->sym_out->st_info) == STB_WEAK) { + res = symlook_obj(&req1, &obj_rtld); + if (res == 0) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + assert(req->defobj_out != NULL); } } - if (def != NULL) - *defobj_out = defobj; - return def; + return (req->sym_out != NULL ? 0 : ESRCH); } -static const Elf_Sym * -symlook_list(const char *name, unsigned long hash, Objlist *objlist, - const Obj_Entry **defobj_out, bool in_plt, DoneList *dlp) +static int +symlook_list(SymLook *req, const Objlist *objlist, DoneList *dlp) { - const Elf_Sym *symp; const Elf_Sym *def; const Obj_Entry *defobj; const Objlist_Entry *elm; + SymLook req1; + int res; def = NULL; defobj = NULL; STAILQ_FOREACH(elm, objlist, link) { if (donelist_check(dlp, elm->obj)) continue; - if ((symp = symlook_obj(name, hash, elm->obj, in_plt)) != NULL) { - if (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK) { - def = symp; - defobj = elm->obj; + symlook_init_from_req(&req1, req); + if ((res = symlook_obj(&req1, elm->obj)) == 0) { + if (def == NULL || ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK) { + def = req1.sym_out; + defobj = req1.defobj_out; if (ELF_ST_BIND(def->st_info) != STB_WEAK) break; } } } - if (def != NULL) - *defobj_out = defobj; - return def; + if (def != NULL) { + req->sym_out = def; + req->defobj_out = defobj; + return (0); + } + return (ESRCH); +} + +/* + * Search the chain of DAGS cointed to by the given Needed_Entry + * for a symbol of the given name. Each DAG is scanned completely + * before advancing to the next one. Returns a pointer to the symbol, + * or NULL if no definition was found. + */ +static int +symlook_needed(SymLook *req, const Needed_Entry *needed, DoneList *dlp) +{ + const Elf_Sym *def; + const Needed_Entry *n; + const Obj_Entry *defobj; + SymLook req1; + int res; + + def = NULL; + defobj = NULL; + symlook_init_from_req(&req1, req); + for (n = needed; n != NULL; n = n->next) { + if (n->obj == NULL || + (res = symlook_list(&req1, &n->obj->dagmembers, dlp)) != 0) + continue; + if (def == NULL || ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK) { + def = req1.sym_out; + defobj = req1.defobj_out; + if (ELF_ST_BIND(def->st_info) != STB_WEAK) + break; + } + } + if (def != NULL) { + req->sym_out = def; + req->defobj_out = defobj; + return (0); + } + return (ESRCH); } /* * 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. If the object is + * filter, return filtered symbol from filtee. * * 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) +int +symlook_obj(SymLook *req, const Obj_Entry *obj) { - if (obj->buckets != NULL) { - unsigned long symnum = obj->buckets[hash % obj->nbuckets]; + DoneList donelist; + SymLook req1; + int flags, res, mres; - while (symnum != STN_UNDEF) { - const Elf_Sym *symp; - const char *strp; + /* + * If there is at least one valid hash at this point, we prefer to + * use the faster GNU version if available. + */ + if (obj->valid_hash_gnu) + mres = symlook_obj1_gnu(req, obj); + else if (obj->valid_hash_sysv) + mres = symlook_obj1_sysv(req, obj); + else + return (EINVAL); - 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; - - symnum = obj->chains[symnum]; + if (mres == 0) { + if (obj->needed_filtees != NULL) { + flags = (req->flags & SYMLOOK_EARLY) ? RTLD_LO_EARLY : 0; + load_filtees(__DECONST(Obj_Entry *, obj), flags, req->lockstate); + donelist_init(&donelist); + symlook_init_from_req(&req1, req); + res = symlook_needed(&req1, obj->needed_filtees, &donelist); + if (res == 0) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + } + return (res); + } + if (obj->needed_aux_filtees != NULL) { + flags = (req->flags & SYMLOOK_EARLY) ? RTLD_LO_EARLY : 0; + load_filtees(__DECONST(Obj_Entry *, obj), flags, req->lockstate); + donelist_init(&donelist); + symlook_init_from_req(&req1, req); + res = symlook_needed(&req1, obj->needed_aux_filtees, &donelist); + if (res == 0) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + return (res); + } } } - return NULL; + return (mres); +} + +/* Symbol match routine common to both hash functions */ +static bool +matched_symbol(SymLook *req, const Obj_Entry *obj, Sym_Match_Result *result, + const unsigned long symnum) +{ + Elf_Versym verndx; + const Elf_Sym *symp; + const char *strp; + + 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: + case STT_COMMON: + case STT_GNU_IFUNC: + if (symp->st_value == 0) + return (false); + /* fallthrough */ + case STT_TLS: + if (symp->st_shndx != SHN_UNDEF) + break; +#ifndef __mips__ + else if (((req->flags & SYMLOOK_IN_PLT) == 0) && + (ELF_ST_TYPE(symp->st_info) == STT_FUNC)) + break; + /* fallthrough */ +#endif + default: + return (false); + } + if (req->name[0] != strp[0] || strcmp(req->name, strp) != 0) + return (false); + + if (req->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); + return (false); + } + /* + * If we are not called from dlsym (i.e. this + * is a normal relocation from unversioned + * binary), accept the symbol immediately if + * it happens to have first version after this + * shared object became versioned. Otherwise, + * if symbol is versioned and not hidden, + * remember it. If it is the only symbol with + * this name exported by the shared object, it + * will be returned as a match by the calling + * function. If symbol is global (verndx < 2) + * accept it unconditionally. + */ + if ((req->flags & SYMLOOK_DLSYM) == 0 && + verndx == VER_NDX_GIVEN) { + result->sym_out = symp; + return (true); + } + else if (verndx >= VER_NDX_GIVEN) { + if ((obj->versyms[symnum] & VER_NDX_HIDDEN) + == 0) { + if (result->vsymp == NULL) + result->vsymp = symp; + result->vcount++; + } + return (false); + } + } + result->sym_out = symp; + return (true); + } + if (obj->versyms == NULL) { + if (object_match_name(obj, req->ventry->name)) { + _rtld_error("%s: object %s should provide version %s " + "for symbol %s", obj_rtld.path, obj->path, + req->ventry->name, obj->strtab + symnum); + return (false); + } + } 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); + return (false); + } + if (obj->vertab[verndx].hash != req->ventry->hash || + strcmp(obj->vertab[verndx].name, req->ventry->name)) { + /* + * Version does not match. Look if this is a + * global symbol and if it is not hidden. If + * global symbol (verndx < 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 ((req->flags & SYMLOOK_DLSYM) || + (verndx >= VER_NDX_GIVEN) || + (obj->versyms[symnum] & VER_NDX_HIDDEN)) + return (false); + } + } + result->sym_out = symp; + return (true); +} + +/* + * Search for symbol using SysV hash function. + * obj->buckets is known not to be NULL at this point; the test for this was + * performed with the obj->valid_hash_sysv assignment. + */ +static int +symlook_obj1_sysv(SymLook *req, const Obj_Entry *obj) +{ + unsigned long symnum; + Sym_Match_Result matchres; + + matchres.sym_out = NULL; + matchres.vsymp = NULL; + matchres.vcount = 0; + + for (symnum = obj->buckets[req->hash % obj->nbuckets]; + symnum != STN_UNDEF; symnum = obj->chains[symnum]) { + if (symnum >= obj->nchains) + return (ESRCH); /* Bad object */ + + if (matched_symbol(req, obj, &matchres, symnum)) { + req->sym_out = matchres.sym_out; + req->defobj_out = obj; + return (0); + } + } + if (matchres.vcount == 1) { + req->sym_out = matchres.vsymp; + req->defobj_out = obj; + return (0); + } + return (ESRCH); +} + +/* Search for symbol using GNU hash function */ +static int +symlook_obj1_gnu(SymLook *req, const Obj_Entry *obj) +{ + Elf_Addr bloom_word; + const Elf32_Word *hashval; + Elf32_Word bucket; + Sym_Match_Result matchres; + unsigned int h1, h2; + unsigned long symnum; + + matchres.sym_out = NULL; + matchres.vsymp = NULL; + matchres.vcount = 0; + + /* Pick right bitmask word from Bloom filter array */ + bloom_word = obj->bloom_gnu[(req->hash_gnu / __ELF_WORD_SIZE) & + obj->maskwords_bm_gnu]; + + /* Calculate modulus word size of gnu hash and its derivative */ + h1 = req->hash_gnu & (__ELF_WORD_SIZE - 1); + h2 = ((req->hash_gnu >> obj->shift2_gnu) & (__ELF_WORD_SIZE - 1)); + + /* Filter out the "definitely not in set" queries */ + if (((bloom_word >> h1) & (bloom_word >> h2) & 1) == 0) + return (ESRCH); + + /* Locate hash chain and corresponding value element*/ + bucket = obj->buckets_gnu[req->hash_gnu % obj->nbuckets_gnu]; + if (bucket == 0) + return (ESRCH); + hashval = &obj->chain_zero_gnu[bucket]; + do { + if (((*hashval ^ req->hash_gnu) >> 1) == 0) { + symnum = hashval - obj->chain_zero_gnu; + if (matched_symbol(req, obj, &matchres, symnum)) { + req->sym_out = matchres.sym_out; + req->defobj_out = obj; + return (0); + } + } + } while ((*hashval++ & 1) == 0); + if (matchres.vcount == 1) { + req->sym_out = matchres.vsymp; + req->defobj_out = obj; + return (0); + } + return (ESRCH); } static void @@ -2354,24 +4426,26 @@ char *fmt1, *fmt2, *fmt, *main_local, *list_containers; int c; - if ((main_local = getenv(LD_ "TRACE_LOADED_OBJECTS_PROGNAME")) == NULL) + if ((main_local = getenv(_LD("TRACE_LOADED_OBJECTS_PROGNAME"))) == NULL) main_local = ""; - if ((fmt1 = getenv(LD_ "TRACE_LOADED_OBJECTS_FMT1")) == NULL) + if ((fmt1 = getenv(_LD("TRACE_LOADED_OBJECTS_FMT1"))) == NULL) fmt1 = "\t%o => %p (%x)\n"; - if ((fmt2 = getenv(LD_ "TRACE_LOADED_OBJECTS_FMT2")) == NULL) + if ((fmt2 = getenv(_LD("TRACE_LOADED_OBJECTS_FMT2"))) == NULL) fmt2 = "\t%o (%x)\n"; - list_containers = getenv(LD_ "TRACE_LOADED_OBJECTS_ALL"); + list_containers = getenv(_LD("TRACE_LOADED_OBJECTS_ALL")); - for (; obj; obj = obj->next) { + for (; obj != NULL; obj = TAILQ_NEXT(obj, next)) { Needed_Entry *needed; char *name, *path; bool is_lib; + if (obj->marker) + continue; if (list_containers && obj->needed != NULL) - printf("%s:\n", obj->path); + rtld_printf("%s:\n", obj->path); for (needed = obj->needed; needed; needed = needed->next) { if (needed->obj != NULL) { if (needed->obj->traced && !list_containers) @@ -2388,17 +4462,17 @@ while ((c = *fmt++) != '\0') { switch (c) { default: - putchar(c); + rtld_putchar(c); continue; case '\\': switch (c = *fmt) { case '\0': continue; case 'n': - putchar('\n'); + rtld_putchar('\n'); break; case 't': - putchar('\t'); + rtld_putchar('\t'); break; } break; @@ -2408,30 +4482,31 @@ continue; case '%': default: - putchar(c); + rtld_putchar(c); break; case 'A': - printf("%s", main_local); + rtld_putstr(main_local); break; case 'a': - printf("%s", obj_main->path); + rtld_putstr(obj_main->path); break; case 'o': - printf("%s", name); + rtld_putstr(name); break; #if 0 case 'm': - printf("%d", sodp->sod_major); + rtld_printf("%d", sodp->sod_major); break; case 'n': - printf("%d", sodp->sod_minor); + rtld_printf("%d", sodp->sod_minor); break; #endif case 'p': - printf("%s", path); + rtld_putstr(path); break; case 'x': - printf("%p", needed->obj ? needed->obj->mapbase : 0); + rtld_printf("%p", needed->obj ? needed->obj->mapbase : + 0); break; } break; @@ -2449,33 +4524,46 @@ * reference count of 0. */ static void -unload_object(Obj_Entry *root) +unload_object(Obj_Entry *root, RtldLockState *lockstate) { - Obj_Entry *obj; - Obj_Entry **linkp; + Obj_Entry marker, *obj, *next; - assert(root->refcount == 0); + assert(root->refcount == 0); - /* - * Pass over the DAG removing unreferenced objects from - * appropriate lists. - */ - unlink_object(root); + /* + * Pass over the DAG removing unreferenced objects from + * appropriate lists. + */ + unlink_object(root); - /* Unmap all objects that are no longer referenced. */ - linkp = &obj_list->next; - while ((obj = *linkp) != NULL) { - if (obj->refcount == 0) { - dbg("unloading \"%s\"", obj->path); - munmap(obj->mapbase, obj->mapsize); - linkmap_delete(obj); - *linkp = obj->next; - obj_count--; - obj_free(obj); - } else - linkp = &obj->next; - } - obj_tail = linkp; + /* Unmap all objects that are no longer referenced. */ + for (obj = TAILQ_FIRST(&obj_list); obj != NULL; obj = next) { + next = TAILQ_NEXT(obj, next); + if (obj->marker || obj->refcount != 0) + continue; + LD_UTRACE(UTRACE_UNLOAD_OBJECT, obj, obj->mapbase, + obj->mapsize, 0, obj->path); + dbg("unloading \"%s\"", obj->path); + /* + * Unlink the object now to prevent new references from + * being acquired while the bind lock is dropped in + * recursive dlclose() invocations. + */ + TAILQ_REMOVE(&obj_list, obj, next); + obj_count--; + + if (obj->filtees_loaded) { + if (next != NULL) { + init_marker(&marker); + TAILQ_INSERT_BEFORE(next, &marker, next); + unload_filtees(obj, lockstate); + next = TAILQ_NEXT(&marker, next); + TAILQ_REMOVE(&obj_list, &marker, next); + } else + unload_filtees(obj, lockstate); + } + release_object(obj); + } } static void @@ -2488,7 +4576,7 @@ 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); @@ -2501,7 +4589,8 @@ { Objlist_Entry *elm; - STAILQ_FOREACH(elm, &root->dagmembers , link) + assert(root->dag_inited); + STAILQ_FOREACH(elm, &root->dagmembers, link) elm->obj->refcount++; } @@ -2510,26 +4599,27 @@ { Objlist_Entry *elm; - STAILQ_FOREACH(elm, &root->dagmembers , link) + assert(root->dag_inited); + STAILQ_FOREACH(elm, &root->dagmembers, link) elm->obj->refcount--; } /* * Common code for MD __tls_get_addr(). */ -void * -tls_get_addr_common(Elf_Addr** dtvp, int index, size_t offset) +static void *tls_get_addr_slow(Elf_Addr **, int, size_t) __noinline; +static void * +tls_get_addr_slow(Elf_Addr **dtvp, int index, size_t offset) { - Elf_Addr* dtv = *dtvp; - int lockstate; + Elf_Addr *newdtv, *dtv; + RtldLockState lockstate; + int to_copy; + dtv = *dtvp; /* Check dtv generation in case new modules have arrived */ if (dtv[0] != tls_dtv_generation) { - Elf_Addr* newdtv; - int to_copy; - - lockstate = wlock_acquire(rtld_bind_lock); - newdtv = calloc(1, (tls_max_index + 2) * sizeof(Elf_Addr)); + wlock_acquire(rtld_bind_lock, &lockstate); + newdtv = xcalloc(tls_max_index + 2, sizeof(Elf_Addr)); to_copy = dtv[1]; if (to_copy > tls_max_index) to_copy = tls_max_index; @@ -2537,24 +4627,36 @@ newdtv[0] = tls_dtv_generation; newdtv[1] = tls_max_index; free(dtv); - wlock_release(rtld_bind_lock, lockstate); - *dtvp = newdtv; + lock_release(rtld_bind_lock, &lockstate); + dtv = *dtvp = newdtv; } /* Dynamically allocate module TLS if necessary */ - if (!dtv[index + 1]) { + if (dtv[index + 1] == 0) { /* Signal safe, wlock will block out signals. */ - lockstate = wlock_acquire(rtld_bind_lock); + wlock_acquire(rtld_bind_lock, &lockstate); if (!dtv[index + 1]) dtv[index + 1] = (Elf_Addr)allocate_module_tls(index); - wlock_release(rtld_bind_lock, lockstate); + lock_release(rtld_bind_lock, &lockstate); } - return (void*) (dtv[index + 1] + offset); + return ((void *)(dtv[index + 1] + offset)); } -/* XXX not sure what variants to use for arm. */ +void * +tls_get_addr_common(Elf_Addr **dtvp, int index, size_t offset) +{ + Elf_Addr *dtv; -#if defined(__ia64__) || defined(__alpha__) || defined(__powerpc__) + dtv = *dtvp; + /* Check dtv generation in case new modules have arrived */ + if (__predict_true(dtv[0] == tls_dtv_generation && + dtv[index + 1] != 0)) + return ((void *)(dtv[index + 1] + offset)); + return (tls_get_addr_slow(dtvp, index, offset)); +} + +#if defined(__aarch64__) || defined(__arm__) || defined(__mips__) || \ + defined(__powerpc__) || defined(__riscv__) /* * Allocate Static TLS using the Variant I method. @@ -2573,7 +4675,7 @@ return (oldtcb); assert(tcbsize >= TLS_TCB_SIZE); - tcb = calloc(1, tls_static_space - TLS_TCB_SIZE + tcbsize); + tcb = xcalloc(1, tls_static_space - TLS_TCB_SIZE + tcbsize); tls = (Elf_Addr **)(tcb + tcbsize - TLS_TCB_SIZE); if (oldtcb != NULL) { @@ -2589,19 +4691,20 @@ } } } else { - dtv = calloc(tls_max_index + 2, sizeof(Elf_Addr)); + dtv = xcalloc(tls_max_index + 2, sizeof(Elf_Addr)); tls[0] = dtv; dtv[0] = tls_dtv_generation; dtv[1] = tls_max_index; - for (obj = objs; obj; obj = obj->next) { - if (obj->tlsoffset) { + for (obj = globallist_curr(objs); obj != NULL; + obj = globallist_next(obj)) { + if (obj->tlsoffset > 0) { addr = (Elf_Addr)tls + obj->tlsoffset; - memset((void*) (addr + obj->tlsinitsize), - 0, obj->tlssize - obj->tlsinitsize); - if (obj->tlsinit) - memcpy((void*) addr, obj->tlsinit, - obj->tlsinitsize); + if (obj->tlsinitsize > 0) + memcpy((void*) addr, obj->tlsinit, obj->tlsinitsize); + if (obj->tlssize > obj->tlsinitsize) + memset((void*) (addr + obj->tlsinitsize), 0, + obj->tlssize - obj->tlsinitsize); dtv[obj->tlsindex + 1] = addr; } } @@ -2635,8 +4738,7 @@ #endif -#if defined(__i386__) || defined(__amd64__) || defined(__sparc64__) || \ - defined(__arm__) +#if defined(__i386__) || defined(__amd64__) || defined(__sparc64__) /* * Allocate Static TLS using the Variant II method. @@ -2645,19 +4747,22 @@ allocate_tls(Obj_Entry *objs, void *oldtls, size_t tcbsize, size_t tcbalign) { Obj_Entry *obj; - size_t size; + size_t size, ralign; char *tls; Elf_Addr *dtv, *olddtv; Elf_Addr segbase, oldsegbase, addr; int i; - size = round(tls_static_space, tcbalign); + ralign = tcbalign; + if (tls_static_max_align > ralign) + ralign = tls_static_max_align; + size = round(tls_static_space, ralign) + round(tcbsize, ralign); assert(tcbsize >= 2*sizeof(Elf_Addr)); - tls = malloc(size + tcbsize); - dtv = calloc(1, (tls_max_index + 2) * sizeof(Elf_Addr)); + tls = malloc_aligned(size, ralign); + dtv = xcalloc(tls_max_index + 2, sizeof(Elf_Addr)); - segbase = (Elf_Addr)(tls + size); + segbase = (Elf_Addr)(tls + round(tls_static_space, ralign)); ((Elf_Addr*)segbase)[0] = segbase; ((Elf_Addr*)segbase)[1] = (Elf_Addr) dtv; @@ -2691,15 +4796,15 @@ */ free_tls(oldtls, 2*sizeof(Elf_Addr), sizeof(Elf_Addr)); } else { - for (obj = objs; obj; obj = obj->next) { - if (obj->tlsoffset) { + for (obj = objs; obj != NULL; obj = TAILQ_NEXT(obj, next)) { + if (obj->marker || obj->tlsoffset == 0) + continue; addr = segbase - obj->tlsoffset; memset((void*) (addr + obj->tlsinitsize), 0, obj->tlssize - obj->tlsinitsize); if (obj->tlsinit) memcpy((void*) addr, obj->tlsinit, obj->tlsinitsize); dtv[obj->tlsindex + 1] = addr; - } } } @@ -2709,8 +4814,8 @@ void free_tls(void *tls, size_t tcbsize, size_t tcbalign) { - size_t size; Elf_Addr* dtv; + size_t size, ralign; int dtvsize, i; Elf_Addr tlsstart, tlsend; @@ -2718,19 +4823,23 @@ * Figure out the size of the initial TLS block so that we can * find stuff which ___tls_get_addr() allocated dynamically. */ - size = round(tls_static_space, tcbalign); + ralign = tcbalign; + if (tls_static_max_align > ralign) + ralign = tls_static_max_align; + size = round(tls_static_space, ralign); dtv = ((Elf_Addr**)tls)[1]; dtvsize = dtv[1]; tlsend = (Elf_Addr) tls; tlsstart = tlsend - size; for (i = 0; i < dtvsize; i++) { - if (dtv[i+2] && (dtv[i+2] < tlsstart || dtv[i+2] > tlsend)) { - free((void*) dtv[i+2]); + if (dtv[i + 2] != 0 && (dtv[i + 2] < tlsstart || dtv[i + 2] > tlsend)) { + free_aligned((void *)dtv[i + 2]); } } - free((void*) tlsstart); + free_aligned((void *)tlsstart); + free((void*) dtv); } #endif @@ -2744,16 +4853,18 @@ Obj_Entry* obj; char* p; - for (obj = obj_list; obj; obj = obj->next) { + TAILQ_FOREACH(obj, &obj_list, next) { + if (obj->marker) + continue; if (obj->tlsindex == index) break; } if (!obj) { _rtld_error("Can't find module with TLS index %d", index); - die(); + rtld_die(); } - p = malloc(obj->tlssize); + p = malloc_aligned(obj->tlssize, obj->tlsalign); memcpy(p, obj->tlsinit, obj->tlsinitsize); memset(p + obj->tlsinitsize, 0, obj->tlssize - obj->tlsinitsize); @@ -2773,7 +4884,7 @@ return true; } - if (obj->tlsindex == 1) + if (tls_last_offset == 0) off = calculate_first_tls_offset(obj->tlssize, obj->tlsalign); else off = calculate_tls_offset(tls_last_offset, tls_last_size, @@ -2785,9 +4896,11 @@ * leave a small amount of space spare to be used for dynamically * loading modules which use static TLS. */ - if (tls_static_space) { + if (tls_static_space != 0) { if (calculate_tls_end(off, obj->tlssize) > tls_static_space) return false; + } else if (obj->tlsalign > tls_static_max_align) { + tls_static_max_align = obj->tlsalign; } tls_last_offset = obj->tlsoffset = off; @@ -2800,41 +4913,595 @@ void free_tls_offset(Obj_Entry *obj) { -#if defined(__i386__) || defined(__amd64__) || defined(__sparc64__) || \ - defined(__arm__) + /* * If we were the last thing to allocate out of the static TLS * block, we give our space back to the 'allocator'. This is a * simplistic workaround to allow libGL.so.1 to be loaded and - * unloaded multiple times. We only handle the Variant II - * mechanism for now - this really needs a proper allocator. + * unloaded multiple times. */ if (calculate_tls_end(obj->tlsoffset, obj->tlssize) == calculate_tls_end(tls_last_offset, tls_last_size)) { tls_last_offset -= obj->tlssize; tls_last_size = 0; } -#endif } void * _rtld_allocate_tls(void *oldtls, size_t tcbsize, size_t tcbalign) { void *ret; - int lockstate; + RtldLockState lockstate; - lockstate = wlock_acquire(rtld_bind_lock); - ret = allocate_tls(obj_list, oldtls, tcbsize, tcbalign); - wlock_release(rtld_bind_lock, lockstate); + wlock_acquire(rtld_bind_lock, &lockstate); + ret = allocate_tls(globallist_curr(TAILQ_FIRST(&obj_list)), oldtls, + tcbsize, tcbalign); + lock_release(rtld_bind_lock, &lockstate); return (ret); } void _rtld_free_tls(void *tcb, size_t tcbsize, size_t tcbalign) { - int lockstate; + RtldLockState lockstate; - lockstate = wlock_acquire(rtld_bind_lock); + wlock_acquire(rtld_bind_lock, &lockstate); free_tls(tcb, tcbsize, tcbalign); - wlock_release(rtld_bind_lock, lockstate); + lock_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 (strcmp(obj->strtab + needed->name, name) == 0 || + (needed->obj != NULL && object_match_name(needed->obj, name))) { + /* + * If there is DT_NEEDED for the name we are looking for, + * we are all set. Note that object might not be found if + * dependency was not loaded yet, so the function can + * return NULL here. This is expected and handled + * properly by the caller. + */ + return (needed->obj); + } + } + _rtld_error("%s: Unexpected inconsistency: dependency %s not found", + obj->path, name); + rtld_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: version %s required by %s not defined", + depobj->path, vername, refobj->path); + return (-1); + } + for (;;) { + if (vd->vd_version != VER_DEF_CURRENT) { + _rtld_error("%s: Unsupported version %d of Elf_Verdef entry", + 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("%s: version %s required by %s not found", + depobj->path, vername, refobj->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; + + if (obj->ver_checked) + return (0); + obj->ver_checked = true; + + 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("%s: Unsupported version %d of Elf_Verneed entry", + 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("%s: Unsupported version %d of Elf_Verdef entry", + obj->path, 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 = xcalloc(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); + if (depobj == NULL) + return (-1); + 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; + } + } + if (rc == 0 || ld_tracing != NULL) + rc = rtld_verify_object_versions(&obj_rtld); + 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; +} + +int +_rtld_get_stack_prot(void) +{ + + return (stack_prot); +} + +int +_rtld_is_dlopened(void *arg) +{ + Obj_Entry *obj; + RtldLockState lockstate; + int res; + + rlock_acquire(rtld_bind_lock, &lockstate); + obj = dlcheck(arg); + if (obj == NULL) + obj = obj_from_addr(arg); + if (obj == NULL) { + _rtld_error("No shared object contains address"); + lock_release(rtld_bind_lock, &lockstate); + return (-1); + } + res = obj->dlopened ? 1 : 0; + lock_release(rtld_bind_lock, &lockstate); + return (res); +} + +int +obj_enforce_relro(Obj_Entry *obj) +{ + + if (obj->relro_size > 0 && mprotect(obj->relro_page, obj->relro_size, + PROT_READ) == -1) { + _rtld_error("%s: Cannot enforce relro protection: %s", + obj->path, rtld_strerror(errno)); + return (-1); + } + return (0); +} + +static void +map_stacks_exec(RtldLockState *lockstate) +{ + void (*thr_map_stacks_exec)(void); + + if ((max_stack_flags & PF_X) == 0 || (stack_prot & PROT_EXEC) != 0) + return; + thr_map_stacks_exec = (void (*)(void))(uintptr_t) + get_program_var_addr("__pthread_map_stacks_exec", lockstate); + if (thr_map_stacks_exec != NULL) { + stack_prot |= PROT_EXEC; + thr_map_stacks_exec(); + } +} + +void +symlook_init(SymLook *dst, const char *name) +{ + + bzero(dst, sizeof(*dst)); + dst->name = name; + dst->hash = elf_hash(name); + dst->hash_gnu = gnu_hash(name); +} + +static void +symlook_init_from_req(SymLook *dst, const SymLook *src) +{ + + dst->name = src->name; + dst->hash = src->hash; + dst->hash_gnu = src->hash_gnu; + dst->ventry = src->ventry; + dst->flags = src->flags; + dst->defobj_out = NULL; + dst->sym_out = NULL; + dst->lockstate = src->lockstate; +} + +static int +open_binary_fd(const char *argv0, bool search_in_path) +{ + char *pathenv, *pe, binpath[PATH_MAX]; + int fd; + + if (search_in_path && strchr(argv0, '/') == NULL) { + pathenv = getenv("PATH"); + if (pathenv == NULL) { + rtld_printf("-p and no PATH environment variable\n"); + rtld_die(); + } + pathenv = strdup(pathenv); + if (pathenv == NULL) { + rtld_printf("Cannot allocate memory\n"); + rtld_die(); + } + fd = -1; + errno = ENOENT; + while ((pe = strsep(&pathenv, ":")) != NULL) { + if (strlcpy(binpath, pe, sizeof(binpath)) >= + sizeof(binpath)) + continue; + if (binpath[0] != '\0' && + strlcat(binpath, "/", sizeof(binpath)) >= + sizeof(binpath)) + continue; + if (strlcat(binpath, argv0, sizeof(binpath)) >= + sizeof(binpath)) + continue; + fd = open(binpath, O_RDONLY | O_CLOEXEC | O_VERIFY); + if (fd != -1 || errno != ENOENT) + break; + } + free(pathenv); + } else { + fd = open(argv0, O_RDONLY | O_CLOEXEC | O_VERIFY); + } + + if (fd == -1) { + rtld_printf("Opening %s: %s\n", argv0, + rtld_strerror(errno)); + rtld_die(); + } + return (fd); +} + +/* + * Parse a set of command-line arguments. + */ +static int +parse_args(char* argv[], int argc, bool *use_pathp, int *fdp) +{ + const char *arg; + int fd, i, j, arglen; + char opt; + + dbg("Parsing command-line arguments"); + *use_pathp = false; + *fdp = -1; + + for (i = 1; i < argc; i++ ) { + arg = argv[i]; + dbg("argv[%d]: '%s'", i, arg); + + /* + * rtld arguments end with an explicit "--" or with the first + * non-prefixed argument. + */ + if (strcmp(arg, "--") == 0) { + i++; + break; + } + if (arg[0] != '-') + break; + + /* + * All other arguments are single-character options that can + * be combined, so we need to search through `arg` for them. + */ + arglen = strlen(arg); + for (j = 1; j < arglen; j++) { + opt = arg[j]; + if (opt == 'h') { + print_usage(argv[0]); + rtld_die(); + } else if (opt == 'f') { + /* + * -f XX can be used to specify a descriptor for the + * binary named at the command line (i.e., the later + * argument will specify the process name but the + * descriptor is what will actually be executed) + */ + if (j != arglen - 1) { + /* -f must be the last option in, e.g., -abcf */ + _rtld_error("invalid options: %s", arg); + rtld_die(); + } + i++; + fd = parse_integer(argv[i]); + if (fd == -1) { + _rtld_error("invalid file descriptor: '%s'", + argv[i]); + rtld_die(); + } + *fdp = fd; + break; + } else if (opt == 'p') { + *use_pathp = true; + } else { + rtld_printf("invalid argument: '%s'\n", arg); + print_usage(argv[0]); + rtld_die(); + } + } + } + + return (i); +} + +/* + * Parse a file descriptor number without pulling in more of libc (e.g. atoi). + */ +static int +parse_integer(const char *str) +{ + static const int RADIX = 10; /* XXXJA: possibly support hex? */ + const char *orig; + int n; + char c; + + orig = str; + n = 0; + for (c = *str; c != '\0'; c = *++str) { + if (c < '0' || c > '9') + return (-1); + + n *= RADIX; + n += c - '0'; + } + + /* Make sure we actually parsed something. */ + if (str == orig) + return (-1); + return (n); +} + +static void +print_usage(const char *argv0) +{ + + rtld_printf("Usage: %s [-h] [-f ] [--] []\n" + "\n" + "Options:\n" + " -h Display this help message\n" + " -p Search in PATH for named binary\n" + " -f Execute instead of searching for \n" + " -- End of RTLD options\n" + " Name of process to execute\n" + " Arguments to the executed process\n", argv0); +} + +/* + * Overrides for libc_pic-provided functions. + */ + +int +__getosreldate(void) +{ + size_t len; + int oid[2]; + int error, osrel; + + if (osreldate != 0) + return (osreldate); + + oid[0] = CTL_KERN; + oid[1] = KERN_OSRELDATE; + osrel = 0; + len = sizeof(osrel); + error = sysctl(oid, 2, &osrel, &len, NULL, 0); + if (error == 0 && osrel > 0 && len == sizeof(osrel)) + osreldate = osrel; + return (osreldate); +} + +void +exit(int status) +{ + + _exit(status); +} + +void (*__cleanup)(void); +int __isthreaded = 0; +int _thread_autoinit_dummy_decl = 1; + +/* + * No unresolved symbols for rtld. + */ +void +__pthread_cxa_finalize(struct dl_phdr_info *a) +{ +} + +void +__stack_chk_fail(void) +{ + + _rtld_error("stack overflow detected; terminated"); + rtld_die(); +} +__weak_reference(__stack_chk_fail, __stack_chk_fail_local); + +void +__chk_fail(void) +{ + + _rtld_error("buffer overflow detected; terminated"); + rtld_die(); +} + +const char * +rtld_strerror(int errnum) +{ + + if (errnum < 0 || errnum >= sys_nerr) + return ("Unknown error"); + return (sys_errlist[errnum]); } diff --git a/libexec/rtld-elf/rtld.h b/libexec/rtld-elf/rtld.h index 890c5e5..858342b 100644 --- a/libexec/rtld-elf/rtld.h +++ b/libexec/rtld-elf/rtld.h @@ -22,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD: releng/10.2/libexec/rtld-elf/rtld.h 282118 2015-04-28 01:15:17Z emaste $ + * $FreeBSD: releng/11.1/libexec/rtld-elf/rtld.h 316135 2017-03-29 11:03:08Z kib $ */ #ifndef RTLD_H /* { */ @@ -41,22 +41,6 @@ #include "rtld_lock.h" #include "rtld_machdep.h" -#ifdef COMPAT_32BIT -#undef STANDARD_LIBRARY_PATH -#undef _PATH_ELF_HINTS -#define _PATH_ELF_HINTS "/var/run/ld-elf32.so.hints" -/* For running 32 bit binaries */ -#define STANDARD_LIBRARY_PATH "/lib32:/usr/lib32" -#define LD_ "LD_32_" -#endif - -#ifndef STANDARD_LIBRARY_PATH -#define STANDARD_LIBRARY_PATH "/lib:/usr/lib" -#endif -#ifndef LD_ -#define LD_ "LD_" -#endif - #define NEW(type) ((type *) xmalloc(sizeof(type))) #define CNEW(type) ((type *) xcalloc(1, sizeof(type))) @@ -155,10 +139,11 @@ Elf_Size magic; /* Magic number (sanity check) */ Elf_Size version; /* Version number of struct format */ - struct Struct_Obj_Entry *next; + TAILQ_ENTRY(Struct_Obj_Entry) next; char *path; /* Pathname of underlying file (%) */ char *origin_path; /* Directory path of origin file */ - int refcount; + int refcount; /* DAG references */ + int holdcount; /* Count of transient references */ int dl_refcount; /* Number of times loaded by dlopen */ /* These items are computed by map_object() or by digest_phdr(). */ @@ -203,6 +188,9 @@ Elf_Word symtabno; /* Number of dynamic symbols */ Elf_Word gotsym; /* First dynamic symbol in GOT */ #endif +#ifdef __powerpc64__ + Elf_Addr glink; /* GLINK PLT call stub section */ +#endif const Elf_Verneed *verneed; /* Required versions. */ Elf_Word verneednum; /* Number of entries in verneed table */ @@ -277,6 +265,9 @@ bool valid_hash_sysv : 1; /* A valid System V hash hash tag is available */ bool valid_hash_gnu : 1; /* A valid GNU hash tag is available */ bool dlopened : 1; /* dlopen()-ed (vs. load statically) */ + bool marker : 1; /* marker on the global obj list */ + bool unholdfree : 1; /* unmap upon last unhold */ + bool doomed : 1; /* Object cannot be referenced */ struct link_map linkmap; /* For GDB and dlinfo() */ Objlist dldags; /* Object belongs to these dlopened DAGs (%) */ @@ -289,6 +280,8 @@ #define RTLD_MAGIC 0xd550b87a #define RTLD_VERSION 1 +TAILQ_HEAD(obj_entry_q, Struct_Obj_Entry); + #define RTLD_STATIC_TLS_EXTRA 128 /* Flags to be passed into symlook_ family of functions. */ @@ -365,6 +358,7 @@ void free_aligned(void *ptr); extern Elf_Addr _GLOBAL_OFFSET_TABLE_[]; extern Elf_Sym sym_zero; /* For resolving undefined weak refs. */ +extern bool ld_bind_not; void dump_relocations(Obj_Entry *); void dump_obj_relocations(Obj_Entry *); @@ -377,9 +371,12 @@ unsigned long elf_hash(const char *); const Elf_Sym *find_symdef(unsigned long, const Obj_Entry *, const Obj_Entry **, int, SymCache *, struct Struct_RtldLockState *); +void ifunc_init(Elf_Auxinfo[__min_size(AT_COUNT)]); void init_pltgot(Obj_Entry *); void lockdflt_init(void); void digest_notes(Obj_Entry *, Elf_Addr, Elf_Addr); +Obj_Entry *globallist_curr(const Obj_Entry *obj); +Obj_Entry *globallist_next(const Obj_Entry *obj); void obj_free(Obj_Entry *); Obj_Entry *obj_new(void); void _rtld_bind_start(void); @@ -393,6 +390,7 @@ 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); +int convert_prot(int elfflags); /* * MD function declarations. diff --git a/libexec/rtld-elf/rtld_lock.c b/libexec/rtld-elf/rtld_lock.c index 0e4babf..336b560 100644 --- a/libexec/rtld-elf/rtld_lock.c +++ b/libexec/rtld-elf/rtld_lock.c @@ -23,7 +23,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * from: FreeBSD: src/libexec/rtld-elf/sparc64/lockdflt.c,v 1.3 2002/10/09 - * $FreeBSD: releng/10.2/libexec/rtld-elf/rtld_lock.c 281453 2015-04-12 06:45:40Z kib $ + * $FreeBSD: releng/11.1/libexec/rtld-elf/rtld_lock.c 312693 2017-01-24 11:13:41Z kib $ */ /* @@ -38,8 +38,8 @@ * In this algorithm the lock is a single word. Its low-order bit is * set when a writer holds the lock. The remaining high-order bits * contain a count of readers desiring the lock. The algorithm requires - * atomic "compare_and_store" and "add" operations, which we implement - * using assembly language sequences in "rtld_start.S". + * atomic "compare_and_store" and "add" operations, which we take + * from machine/atomic.h. */ #include @@ -64,10 +64,10 @@ } Lock; static sigset_t fullsigmask, oldsigmask; -static int thread_flag; +static int thread_flag, wnested; static void * -def_lock_create() +def_lock_create(void) { void *base; char *p; @@ -117,29 +117,34 @@ static void def_wlock_acquire(void *lock) { - Lock *l = (Lock *)lock; - sigset_t tmp_oldsigmask; + Lock *l; + sigset_t tmp_oldsigmask; - for ( ; ; ) { - sigprocmask(SIG_BLOCK, &fullsigmask, &tmp_oldsigmask); - if (atomic_cmpset_acq_int(&l->lock, 0, WAFLAG)) - break; - sigprocmask(SIG_SETMASK, &tmp_oldsigmask, NULL); - } - oldsigmask = tmp_oldsigmask; + l = (Lock *)lock; + for (;;) { + sigprocmask(SIG_BLOCK, &fullsigmask, &tmp_oldsigmask); + if (atomic_cmpset_acq_int(&l->lock, 0, WAFLAG)) + break; + sigprocmask(SIG_SETMASK, &tmp_oldsigmask, NULL); + } + if (atomic_fetchadd_int(&wnested, 1) == 0) + oldsigmask = tmp_oldsigmask; } static void def_lock_release(void *lock) { - Lock *l = (Lock *)lock; + Lock *l; - if ((l->lock & WAFLAG) == 0) - atomic_add_rel_int(&l->lock, -RC_INCR); - else { - atomic_add_rel_int(&l->lock, -WAFLAG); - sigprocmask(SIG_SETMASK, &oldsigmask, NULL); - } + l = (Lock *)lock; + if ((l->lock & WAFLAG) == 0) + atomic_add_rel_int(&l->lock, -RC_INCR); + else { + assert(wnested > 0); + atomic_add_rel_int(&l->lock, -WAFLAG); + if (atomic_fetchadd_int(&wnested, -1) == 1) + sigprocmask(SIG_SETMASK, &oldsigmask, NULL); + } } static int @@ -269,7 +274,7 @@ } void -lockdflt_init() +lockdflt_init(void) { int i; @@ -373,12 +378,12 @@ return; /* - * Warning: this does not work with the rtld compat locks - * above, since the thread signal mask is corrupted (set to - * all signals blocked) if two locks are taken in write mode. - * The caller of the _rtld_atfork_pre() must provide the - * working implementation of the locks, and libthr locks are - * fine. + * Warning: this did not worked well with the rtld compat + * locks above, when the thread signal mask was corrupted (set + * to all signals blocked) if two locks were taken + * simultaneously in the write mode. The caller of the + * _rtld_atfork_pre() must provide the working implementation + * of the locks anyway, and libthr locks are fine. */ wlock_acquire(rtld_phdr_lock, &ls[0]); wlock_acquire(rtld_bind_lock, &ls[1]); diff --git a/libexec/rtld-elf/rtld_lock.h b/libexec/rtld-elf/rtld_lock.h index f30c63f..29a57fb 100644 --- a/libexec/rtld-elf/rtld_lock.h +++ b/libexec/rtld-elf/rtld_lock.h @@ -22,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD: releng/10.2/libexec/rtld-elf/rtld_lock.h 281453 2015-04-12 06:45:40Z kib $ + * $FreeBSD: releng/11.1/libexec/rtld-elf/rtld_lock.h 280816 2015-03-29 18:53:21Z kib $ */ #ifndef _RTLD_LOCK_H_ diff --git a/libexec/rtld-elf/rtld_machdep.h b/libexec/rtld-elf/rtld_machdep.h deleted file mode 100644 index 3b2ccb9..0000000 --- a/libexec/rtld-elf/rtld_machdep.h +++ /dev/null @@ -1,83 +0,0 @@ -/*- - * Copyright (c) 1999, 2000 John D. Polstra. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * $FreeBSD: releng/10.2/libexec/rtld-elf/i386/rtld_machdep.h 281453 2015-04-12 06:45:40Z kib $ - */ - -#ifndef RTLD_MACHDEP_H -#define RTLD_MACHDEP_H 1 - -#include -#include - -struct Struct_Obj_Entry; - -/* Return the address of the .dynamic section in the dynamic linker. */ -#define rtld_dynamic(obj) \ - ((const Elf_Dyn *)((obj)->relocbase + (Elf_Addr)&_DYNAMIC)) - -/* Fixup the jump slot at "where" to transfer control to "target". */ -static inline Elf_Addr -reloc_jmpslot(Elf_Addr *where, Elf_Addr target, - const struct Struct_Obj_Entry *obj, - const struct Struct_Obj_Entry *refobj, const Elf_Rel *rel) -{ -#ifdef dbg - dbg("reloc_jmpslot: *%p = %p", (void *)(where), - (void *)(target)); -#endif - (*(Elf_Addr *)(where) = (Elf_Addr)(target)); - return target; -} - -#define make_function_pointer(def, defobj) \ - ((defobj)->relocbase + (def)->st_value) - -#define call_initfini_pointer(obj, target) \ - (((InitFunc)(target))()) - -#define call_init_pointer(obj, target) \ - (((InitArrFunc)(target))(main_argc, main_argv, environ)) - -#define round(size, align) \ - (((size) + (align) - 1) & ~((align) - 1)) -#define calculate_first_tls_offset(size, align) \ - round(size, align) -#define calculate_tls_offset(prev_offset, prev_size, size, align) \ - round((prev_offset) + (size), align) -#define calculate_tls_end(off, size) (off) - -typedef struct { - unsigned long ti_module; - unsigned long ti_offset; -} tls_index; - -void *___tls_get_addr(tls_index *ti) __attribute__((__regparm__(1))) __exported; -void *__tls_get_addr(tls_index *ti) __exported; - -#define RTLD_DEFAULT_STACK_PF_EXEC PF_X -#define RTLD_DEFAULT_STACK_EXEC PROT_EXEC - -#endif diff --git a/libexec/rtld-elf/rtld_printf.h b/libexec/rtld-elf/rtld_printf.h index b37bee6..f82158c 100644 --- a/libexec/rtld-elf/rtld_printf.h +++ b/libexec/rtld-elf/rtld_printf.h @@ -22,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD: releng/10.2/libexec/rtld-elf/rtld_printf.h 225152 2011-08-24 20:05:13Z kib $ + * $FreeBSD: releng/11.1/libexec/rtld-elf/rtld_printf.h 267678 2014-06-20 17:08:32Z jonathan $ */ #ifndef RTLD_PRINTF_H @@ -31,6 +31,8 @@ #include #include +int rtld_snprintf(char *buf, size_t bufsize, const char *fmt, ...) + __printflike(3, 4); int rtld_vsnprintf(char *buf, size_t bufsize, const char *fmt, va_list ap); int rtld_vfdprintf(int fd, const char *fmt, va_list ap); int rtld_fdprintf(int fd, const char *fmt, ...) __printflike(2, 3); diff --git a/libexec/rtld-elf/rtld_start.S b/libexec/rtld-elf/rtld_start.S deleted file mode 100644 index 65d900d..0000000 --- a/libexec/rtld-elf/rtld_start.S +++ /dev/null @@ -1,91 +0,0 @@ -/*- - * Copyright 1996-1998 John D. Polstra. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $FreeBSD: src/libexec/rtld-elf/i386/rtld_start.S,v 1.4 2005/05/19 07:32:42 dfr Exp $ - */ - - .text - .align 4 - .globl .rtld_start - .type .rtld_start,@function -.rtld_start: - xorl %ebp,%ebp # Clear frame pointer for good form - movl %esp,%eax # Save initial stack pointer - movl %esp,%esi # Save initial stack pointer - andl $0xfffffff0,%esp # Align stack pointer - subl $16,%esp # A place to store exit procedure addr - movl %esp,%ebx # save address of exit proc - movl %esp,%ecx # construct address of obj_main - addl $4,%ecx - subl $4,%esp # Keep stack aligned - pushl %ecx # Pass address of obj_main - pushl %ebx # Pass address of exit proc - pushl %eax # Pass initial stack pointer to rtld - call _rtld@PLT # Call rtld(sp); returns entry point - addl $16,%esp # Remove arguments from stack - popl %edx # Get exit procedure address - movl %esi,%esp # Ignore obj_main -/* - * At this point, %eax contains the entry point of the main program, and - * %edx contains a pointer to a termination function that should be - * registered with atexit(). (crt1.o registers it.) - */ -.globl .rtld_goto_main -.rtld_goto_main: # This symbol exists just to make debugging easier. - jmp *%eax # Enter main program - - -/* - * Binder entry point. Control is transferred to here by code in the PLT. - * On entry, there are two arguments on the stack. In ascending address - * order, they are (1) "obj", a pointer to the calling object's Obj_Entry, - * and (2) "reloff", the byte offset of the appropriate relocation entry - * in the PLT relocation table. - * - * We are careful to preserve all registers, even the the caller-save - * registers. That is because this code may be invoked by low-level - * assembly-language code that is not ABI-compliant. - */ - .align 4 - .globl _rtld_bind_start - .type _rtld_bind_start,@function -_rtld_bind_start: - pushf # Save eflags - pushl %eax # Save %eax - pushl %edx # Save %edx - pushl %ecx # Save %ecx - pushl 20(%esp) # Copy reloff argument - pushl 20(%esp) # Copy obj argument - - call _rtld_bind@PLT # Transfer control to the binder - /* Now %eax contains the entry point of the function being called. */ - - addl $8,%esp # Discard binder arguments - movl %eax,20(%esp) # Store target over obj argument - popl %ecx # Restore %ecx - popl %edx # Restore %edx - popl %eax # Restore %eax - popf # Restore eflags - leal 4(%esp),%esp # Discard reloff, do not change eflags - ret # "Return" to target address diff --git a/libexec/rtld-elf/rtld_tls.h b/libexec/rtld-elf/rtld_tls.h index 4e17934..ba00a30 100644 --- a/libexec/rtld-elf/rtld_tls.h +++ b/libexec/rtld-elf/rtld_tls.h @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/libexec/rtld-elf/rtld_tls.h,v 1.1 2004/08/03 08:50:58 dfr Exp $ + * $FreeBSD: releng/11.1/libexec/rtld-elf/rtld_tls.h 280816 2015-03-29 18:53:21Z kib $ */ /* @@ -57,13 +57,14 @@ * The value returned from this function is suitable for installing * directly into the thread pointer register. */ -extern void *_rtld_allocate_tls(void* oldtls, size_t tcbsize, size_t tcbalign); +void *_rtld_allocate_tls(void* oldtls, size_t tcbsize, size_t tcbalign) + __exported; /* * Free a TLS block allocated using _rtld_allocate_tls(). The tcbsize * and tcbalign parameters must be the same as those used to allocate * the block. */ -extern void _rtld_free_tls(void *tcb, size_t tcbsize, size_t tcbalign); +void _rtld_free_tls(void *tcb, size_t tcbsize, size_t tcbalign) __exported; #endif diff --git a/libexec/rtld-elf/xmalloc.c b/libexec/rtld-elf/xmalloc.c index 785e162..c11084b 100644 --- a/libexec/rtld-elf/xmalloc.c +++ b/libexec/rtld-elf/xmalloc.c @@ -22,38 +22,76 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD: src/libexec/rtld-elf/xmalloc.c,v 1.3 2003/06/19 05:28:26 mdodd Exp $ + * $FreeBSD: releng/11.1/libexec/rtld-elf/xmalloc.c 262334 2014-02-22 11:06:48Z davidxu $ */ -#include #include #include #include - -void *xcalloc(size_t); -void *xmalloc(size_t); -char *xstrdup(const char *); +#include +#include "rtld.h" +#include "rtld_printf.h" void * -xcalloc(size_t size) +xcalloc(size_t number, size_t size) { - return memset(xmalloc(size), 0, size); + void *p; + + p = calloc(number, size); + if (p == NULL) { + rtld_fdputstr(STDERR_FILENO, "Out of memory\n"); + _exit(1); + } + return (p); } void * xmalloc(size_t size) { void *p = malloc(size); - if (p == NULL) - err(1, "Out of memory"); + if (p == NULL) { + rtld_fdputstr(STDERR_FILENO, "Out of memory\n"); + _exit(1); + } return p; } char * -xstrdup(const char *s) +xstrdup(const char *str) { - char *p = strdup(s); - if (p == NULL) - err(1, "Out of memory"); - return p; + char *copy; + size_t len; + + len = strlen(str) + 1; + copy = xmalloc(len); + memcpy(copy, str, len); + return (copy); +} + +void * +malloc_aligned(size_t size, size_t align) +{ + void *mem, *res; + + if (align < sizeof(void *)) + align = sizeof(void *); + + mem = xmalloc(size + sizeof(void *) + align - 1); + res = (void *)round((uintptr_t)mem + sizeof(void *), align); + *(void **)((uintptr_t)res - sizeof(void *)) = mem; + return (res); +} + +void +free_aligned(void *ptr) +{ + void *mem; + uintptr_t x; + + if (ptr == NULL) + return; + x = (uintptr_t)ptr; + x -= sizeof(void *); + mem = *(void **)x; + free(mem); }