src/libexec/ld.so/loader.c

1175 lines
29 KiB
C

/* $OpenBSD: loader.c,v 1.220 2024/01/16 19:07:31 deraadt Exp $ */
/*
* Copyright (c) 1998 Per Fogelstrom, Opsycon AB
*
* 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.
*
*/
#define _DYN_LOADER
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/exec.h>
#ifdef __i386__
# include <machine/vmparam.h>
#endif
#include <string.h>
#include <link.h>
#include <limits.h> /* NAME_MAX */
#include <dlfcn.h>
#include <tib.h>
#include "syscall.h"
#include "util.h"
#include "resolve.h"
#include "path.h"
#include "sod.h"
/*
* Local decls.
*/
unsigned long _dl_boot(const char **, char **, const long, long *) __boot;
void _dl_debug_state(void);
void _dl_setup_env(const char *_argv0, char **_envp) __boot;
void _dl_dtors(void);
void _dl_dopreload(char *_paths) __boot;
void _dl_fixup_user_env(void) __boot;
void _dl_call_preinit(elf_object_t *) __boot;
void _dl_call_init_recurse(elf_object_t *object, int initfirst);
void _dl_clean_boot(void);
static inline void unprotect_if_textrel(elf_object_t *_object);
static inline void reprotect_if_textrel(elf_object_t *_object);
static void _dl_rreloc(elf_object_t *_object);
int _dl_pagesz __relro = 4096;
int _dl_bindnow __relro = 0;
int _dl_debug __relro = 0;
int _dl_trust __relro = 0;
char **_dl_libpath __relro = NULL;
const char **_dl_argv __relro = NULL;
int _dl_argc __relro = 0;
const char *_dl_libcname;
char *_dl_preload __boot_data = NULL;
char *_dl_tracefmt1 __boot_data = NULL;
char *_dl_tracefmt2 __boot_data = NULL;
char *_dl_traceprog __boot_data = NULL;
void *_dl_exec_hint __boot_data = NULL;
char **environ = NULL;
char *__progname = NULL;
int _dl_traceld;
struct r_debug *_dl_debug_map;
static dl_cb_cb _dl_cb_cb;
const struct dl_cb_0 callbacks_0 = {
.dl_allocate_tib = &_dl_allocate_tib,
.dl_free_tib = &_dl_free_tib,
#if DO_CLEAN_BOOT
.dl_clean_boot = &_dl_clean_boot,
#endif
.dlopen = &dlopen,
.dlclose = &dlclose,
.dlsym = &dlsym,
.dladdr = &dladdr,
.dlctl = &dlctl,
.dlerror = &dlerror,
.dl_iterate_phdr = &dl_iterate_phdr,
};
/*
* Run dtors for a single object.
*/
void
_dl_run_dtors(elf_object_t *obj)
{
if (obj->dyn.fini_array) {
int num = obj->dyn.fini_arraysz / sizeof(Elf_Addr);
int i;
DL_DEB(("doing finiarray obj %p @%p: [%s]\n",
obj, obj->dyn.fini_array, obj->load_name));
for (i = num; i > 0; i--)
(*obj->dyn.fini_array[i-1])();
}
if (obj->dyn.fini) {
DL_DEB(("doing dtors obj %p @%p: [%s]\n",
obj, obj->dyn.fini, obj->load_name));
(*obj->dyn.fini)();
}
}
/*
* Run dtors for all objects that are eligible.
*/
void
_dl_run_all_dtors(void)
{
elf_object_t *node;
int fini_complete;
int skip_initfirst;
int initfirst_skipped;
fini_complete = 0;
skip_initfirst = 1;
initfirst_skipped = 0;
while (fini_complete == 0) {
fini_complete = 1;
for (node = _dl_objects;
node != NULL;
node = node->next) {
if ((node->dyn.fini || node->dyn.fini_array) &&
(OBJECT_REF_CNT(node) == 0) &&
(node->status & STAT_INIT_DONE) &&
((node->status & STAT_FINI_DONE) == 0)) {
if (skip_initfirst &&
(node->obj_flags & DF_1_INITFIRST))
initfirst_skipped = 1;
else
node->status |= STAT_FINI_READY;
}
}
for (node = _dl_objects;
node != NULL;
node = node->next ) {
if ((node->dyn.fini || node->dyn.fini_array) &&
(OBJECT_REF_CNT(node) == 0) &&
(node->status & STAT_INIT_DONE) &&
((node->status & STAT_FINI_DONE) == 0) &&
(!skip_initfirst ||
(node->obj_flags & DF_1_INITFIRST) == 0)) {
struct object_vector vec = node->child_vec;
int i;
for (i = 0; i < vec.len; i++)
vec.vec[i]->status &= ~STAT_FINI_READY;
}
}
for (node = _dl_objects;
node != NULL;
node = node->next ) {
if (node->status & STAT_FINI_READY) {
fini_complete = 0;
node->status |= STAT_FINI_DONE;
node->status &= ~STAT_FINI_READY;
_dl_run_dtors(node);
}
}
if (fini_complete && initfirst_skipped)
fini_complete = initfirst_skipped = skip_initfirst = 0;
}
}
/*
* Routine to walk through all of the objects except the first
* (main executable).
*
* Big question, should dlopen()ed objects be unloaded before or after
* the destructor for the main application runs?
*/
void
_dl_dtors(void)
{
_dl_thread_kern_stop();
/* ORDER? */
_dl_unload_dlopen();
DL_DEB(("doing dtors\n"));
_dl_objects->opencount--;
_dl_notify_unload_shlib(_dl_objects);
_dl_run_all_dtors();
}
#if DO_CLEAN_BOOT
void
_dl_clean_boot(void)
{
extern char boot_text_start[], boot_text_end[];
#if 0 /* XXX breaks boehm-gc?!? */
extern char boot_data_start[], boot_data_end[];
#endif
_dl_mmap(boot_text_start, boot_text_end - boot_text_start,
PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
_dl_mimmutable(boot_text_start, boot_text_end - boot_text_start);
#if 0 /* XXX breaks boehm-gc?!? */
_dl_mmap(boot_data_start, boot_data_end - boot_data_start,
PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
_dl_mimmutable(boot_data_start, boot_data_end - boot_data_start);
#endif
}
#endif /* DO_CLEAN_BOOT */
void
_dl_dopreload(char *paths)
{
char *cp, *dp;
elf_object_t *shlib;
int count;
dp = paths = _dl_strdup(paths);
if (dp == NULL)
_dl_oom();
/* preallocate child_vec for the LD_PRELOAD objects */
count = 1;
while (*dp++ != '\0')
if (*dp == ':')
count++;
object_vec_grow(&_dl_objects->child_vec, count);
dp = paths;
while ((cp = _dl_strsep(&dp, ":")) != NULL) {
shlib = _dl_load_shlib(cp, _dl_objects, OBJTYPE_LIB,
_dl_objects->obj_flags, 1);
if (shlib == NULL)
_dl_die("can't preload library '%s'", cp);
_dl_add_object(shlib);
_dl_link_child(shlib, _dl_objects);
}
_dl_free(paths);
return;
}
/*
* grab interesting environment variables, zap bad env vars if
* issetugid, and set the exported environ and __progname variables
*/
void
_dl_setup_env(const char *argv0, char **envp)
{
static char progname_storage[NAME_MAX+1] = "";
/*
* Don't allow someone to change the search paths if he runs
* a suid program without credentials high enough.
*/
_dl_trust = !_dl_issetugid();
if (!_dl_trust) { /* Zap paths if s[ug]id... */
_dl_unsetenv("LD_DEBUG", envp);
_dl_unsetenv("LD_LIBRARY_PATH", envp);
_dl_unsetenv("LD_PRELOAD", envp);
_dl_unsetenv("LD_BIND_NOW", envp);
} else {
/*
* Get paths to various things we are going to use.
*/
_dl_debug = _dl_getenv("LD_DEBUG", envp) != NULL;
_dl_libpath = _dl_split_path(_dl_getenv("LD_LIBRARY_PATH",
envp));
_dl_preload = _dl_getenv("LD_PRELOAD", envp);
_dl_bindnow = _dl_getenv("LD_BIND_NOW", envp) != NULL;
}
/* these are usable even in setugid processes */
_dl_traceld = _dl_getenv("LD_TRACE_LOADED_OBJECTS", envp) != NULL;
_dl_tracefmt1 = _dl_getenv("LD_TRACE_LOADED_OBJECTS_FMT1", envp);
_dl_tracefmt2 = _dl_getenv("LD_TRACE_LOADED_OBJECTS_FMT2", envp);
_dl_traceprog = _dl_getenv("LD_TRACE_LOADED_OBJECTS_PROGNAME", envp);
environ = envp;
_dl_trace_setup(envp);
if (argv0 != NULL) { /* NULL ptr if argc = 0 */
const char *p = _dl_strrchr(argv0, '/');
if (p == NULL)
p = argv0;
else
p++;
_dl_strlcpy(progname_storage, p, sizeof(progname_storage));
}
__progname = progname_storage;
}
int
_dl_load_dep_libs(elf_object_t *object, int flags, int booting)
{
elf_object_t *dynobj, *obj;
Elf_Dyn *dynp;
unsigned int loop;
int libcount;
int depflags, nodelete = 0;
dynobj = object;
while (dynobj) {
DL_DEB(("examining: '%s'\n", dynobj->load_name));
libcount = 0;
/* propagate DF_1_NOW to deplibs (can be set by dynamic tags) */
depflags = flags | (dynobj->obj_flags & DF_1_NOW);
if (booting || object->nodelete)
nodelete = 1;
for (dynp = dynobj->load_dyn; dynp->d_tag; dynp++) {
if (dynp->d_tag == DT_NEEDED) {
libcount++;
}
}
if ( libcount != 0) {
struct listent {
Elf_Dyn *dynp;
elf_object_t *depobj;
} *liblist;
int *randomlist;
liblist = _dl_reallocarray(NULL, libcount,
sizeof(struct listent));
randomlist = _dl_reallocarray(NULL, libcount,
sizeof(int));
if (liblist == NULL || randomlist == NULL)
_dl_oom();
for (dynp = dynobj->load_dyn, loop = 0; dynp->d_tag;
dynp++)
if (dynp->d_tag == DT_NEEDED)
liblist[loop++].dynp = dynp;
/*
* We can't support multiple versions of libc
* in a single process. So remember the first
* libc SONAME we encounter as a dependency
* and use it in further loads of libc. In
* practice this means we will always use the
* libc version that the binary was linked
* against. This isn't entirely correct, but
* it will keep most binaries running when
* transitioning over a libc major bump.
*/
if (_dl_libcname == NULL) {
for (loop = 0; loop < libcount; loop++) {
const char *libname;
libname = dynobj->dyn.strtab;
libname +=
liblist[loop].dynp->d_un.d_val;
if (_dl_strncmp(libname,
"libc.so.", 8) == 0) {
_dl_libcname = libname;
break;
}
}
}
/* Randomize these */
for (loop = 0; loop < libcount; loop++)
randomlist[loop] = loop;
for (loop = 1; loop < libcount; loop++) {
unsigned int rnd;
int cur;
rnd = _dl_arc4random();
rnd = rnd % (loop+1);
cur = randomlist[rnd];
randomlist[rnd] = randomlist[loop];
randomlist[loop] = cur;
}
for (loop = 0; loop < libcount; loop++) {
elf_object_t *depobj;
const char *libname;
libname = dynobj->dyn.strtab;
libname +=
liblist[randomlist[loop]].dynp->d_un.d_val;
DL_DEB(("loading: %s required by %s\n", libname,
dynobj->load_name));
if (_dl_strncmp(libname, "libc.so.", 8) == 0) {
if (_dl_libcname)
libname = _dl_libcname;
}
depobj = _dl_load_shlib(libname, dynobj,
OBJTYPE_LIB, depflags, nodelete);
if (depobj == 0) {
if (booting) {
_dl_die(
"can't load library '%s'",
libname);
}
DL_DEB(("dlopen: failed to open %s\n",
libname));
_dl_free(liblist);
_dl_free(randomlist);
return (1);
}
liblist[randomlist[loop]].depobj = depobj;
}
object_vec_grow(&dynobj->child_vec, libcount);
for (loop = 0; loop < libcount; loop++) {
_dl_add_object(liblist[loop].depobj);
_dl_link_child(liblist[loop].depobj, dynobj);
}
_dl_free(liblist);
_dl_free(randomlist);
}
dynobj = dynobj->next;
}
_dl_cache_grpsym_list_setup(object);
/*
* XXX pinsyscall(SYS_execve,...) can be removed once pinsyscalls()
* is fully operational
*/
for (obj = _dl_objects; booting && obj != NULL; obj = obj->next) {
struct sym_res sr;
if (!obj->islibc)
continue;
sr = _dl_find_symbol("execve",
SYM_SEARCH_SELF|SYM_PLT|SYM_WARNNOTFOUND, NULL, obj);
if (sr.sym)
_dl_pinsyscall(SYS_execve,
(void *)sr.obj->obj_base + sr.sym->st_value,
sr.sym->st_size);
_dl_memset(&sr, 0, sizeof sr);
break;
}
return(0);
}
/* do any RWX -> RX fixups for executable PLTs and apply GNU_RELRO */
static inline void
_dl_self_relro(long loff)
{
Elf_Ehdr *ehdp;
Elf_Phdr *phdp;
int i;
ehdp = (Elf_Ehdr *)loff;
phdp = (Elf_Phdr *)(loff + ehdp->e_phoff);
for (i = 0; i < ehdp->e_phnum; i++, phdp++) {
switch (phdp->p_type) {
#if defined(__alpha__) || defined(__hppa__) || defined(__powerpc__) || \
defined(__sparc64__)
case PT_LOAD:
if ((phdp->p_flags & (PF_X | PF_W)) != (PF_X | PF_W))
break;
_dl_mprotect((void *)(phdp->p_vaddr + loff),
phdp->p_memsz, PROT_READ);
break;
#endif
case PT_GNU_RELRO:
_dl_mprotect((void *)(phdp->p_vaddr + loff),
phdp->p_memsz, PROT_READ);
_dl_mimmutable((void *)(phdp->p_vaddr + loff),
phdp->p_memsz);
break;
}
}
}
#define PFLAGS(X) ((((X) & PF_R) ? PROT_READ : 0) | \
(((X) & PF_W) ? PROT_WRITE : 0) | \
(((X) & PF_X) ? PROT_EXEC : 0))
/*
* To avoid kbind(2) becoming a powerful gadget, it is called inline to a
* function. Therefore we cannot create a precise pinsyscall label. Instead
* create a duplicate entry to force the kernel's pinsyscall code to skip
* validation, rather than labelling it illegal. kbind(2) remains safe
* because it self-protects by checking its calling address.
*/
#define __STRINGIFY(x) #x
#define STRINGIFY(x) __STRINGIFY(x)
#ifdef __arm__
__asm__(".pushsection .openbsd.syscalls,\"\",%progbits;"
".p2align 2;"
".long 0;"
".long " STRINGIFY(SYS_kbind) ";"
".popsection");
#else
__asm__(".pushsection .openbsd.syscalls,\"\",@progbits;"
".p2align 2;"
".long 0;"
".long " STRINGIFY(SYS_kbind) ";"
".popsection");
#endif
/*
* This is the dynamic loader entrypoint. When entering here, depending
* on architecture type, the stack and registers are set up according
* to the architectures ABI specification. The first thing required
* to do is to dig out all information we need to accomplish our task.
*/
unsigned long
_dl_boot(const char **argv, char **envp, const long dyn_loff, long *dl_data)
{
struct elf_object *exe_obj; /* Pointer to executable object */
struct elf_object *dyn_obj; /* Pointer to ld.so object */
struct r_debug **map_link; /* Where to put pointer for gdb */
struct r_debug *debug_map;
struct load_list *next_load, *load_list = NULL;
Elf_Dyn *dynp;
Elf_Phdr *phdp;
Elf_Ehdr *ehdr;
char *us = NULL;
unsigned int loop;
int failed;
struct dep_node *n;
Elf_Addr minva, maxva, exe_loff, exec_end, cur_exec_end;
Elf_Addr relro_addr = 0, relro_size = 0;
Elf_Phdr *ptls = NULL;
int align;
if (dl_data[AUX_pagesz] != 0)
_dl_pagesz = dl_data[AUX_pagesz];
_dl_malloc_init();
_dl_argv = argv;
while (_dl_argv[_dl_argc] != NULL)
_dl_argc++;
_dl_setup_env(argv[0], envp);
/*
* Make read-only the GOT and PLT and variables initialized
* during the ld.so setup above.
*/
_dl_self_relro(dyn_loff);
align = _dl_pagesz - 1;
#define ROUND_PG(x) (((x) + align) & ~(align))
#define TRUNC_PG(x) ((x) & ~(align))
if (_dl_bindnow) {
/* Lazy binding disabled, so disable kbind */
_dl_kbind(NULL, 0, 0);
}
DL_DEB(("ld.so loading: '%s'\n", __progname));
/* init this in runtime, not statically */
TAILQ_INIT(&_dlopened_child_list);
exe_obj = NULL;
_dl_loading_object = NULL;
minva = ELF_NO_ADDR;
maxva = exe_loff = exec_end = 0;
/*
* Examine the user application and set up object information.
*/
phdp = (Elf_Phdr *)dl_data[AUX_phdr];
for (loop = 0; loop < dl_data[AUX_phnum]; loop++) {
switch (phdp->p_type) {
case PT_PHDR:
exe_loff = (Elf_Addr)dl_data[AUX_phdr] - phdp->p_vaddr;
us += exe_loff;
DL_DEB(("exe load offset: 0x%lx\n", exe_loff));
break;
case PT_DYNAMIC:
minva = TRUNC_PG(minva);
maxva = ROUND_PG(maxva);
exe_obj = _dl_finalize_object(argv[0] ? argv[0] : "",
(Elf_Dyn *)(phdp->p_vaddr + exe_loff),
(Elf_Phdr *)dl_data[AUX_phdr],
dl_data[AUX_phnum], OBJTYPE_EXE, minva + exe_loff,
exe_loff);
_dl_add_object(exe_obj);
break;
case PT_INTERP:
us += phdp->p_vaddr;
break;
case PT_LOAD:
if (phdp->p_vaddr < minva)
minva = phdp->p_vaddr;
if (phdp->p_vaddr > maxva)
maxva = phdp->p_vaddr + phdp->p_memsz;
next_load = _dl_calloc(1, sizeof(struct load_list));
if (next_load == NULL)
_dl_oom();
next_load->next = load_list;
load_list = next_load;
next_load->start = (char *)TRUNC_PG(phdp->p_vaddr) + exe_loff;
next_load->size = (phdp->p_vaddr & align) + phdp->p_filesz;
next_load->prot = PFLAGS(phdp->p_flags);
cur_exec_end = (Elf_Addr)next_load->start + next_load->size;
if ((next_load->prot & PROT_EXEC) != 0 &&
cur_exec_end > exec_end)
exec_end = cur_exec_end;
break;
case PT_TLS:
if (phdp->p_filesz > phdp->p_memsz)
_dl_die("invalid tls data");
ptls = phdp;
break;
case PT_GNU_RELRO:
relro_addr = phdp->p_vaddr + exe_loff;
relro_size = phdp->p_memsz;
break;
}
phdp++;
}
exe_obj->load_list = load_list;
exe_obj->obj_flags |= DF_1_GLOBAL;
exe_obj->nodelete = 1;
exe_obj->load_size = maxva - minva;
exe_obj->relro_addr = relro_addr;
exe_obj->relro_size = relro_size;
_dl_set_sod(exe_obj->load_name, &exe_obj->sod);
#ifdef __i386__
if (exec_end > I386_MAX_EXE_ADDR)
_dl_exec_hint = (void *)ROUND_PG(exec_end-I386_MAX_EXE_ADDR);
DL_DEB(("_dl_exec_hint: 0x%lx\n", _dl_exec_hint));
#endif
/* TLS bits in the base executable */
if (ptls != NULL && ptls->p_memsz)
_dl_set_tls(exe_obj, ptls, exe_loff, NULL);
n = _dl_malloc(sizeof *n);
if (n == NULL)
_dl_oom();
n->data = exe_obj;
TAILQ_INSERT_TAIL(&_dlopened_child_list, n, next_sib);
exe_obj->opencount++;
if (_dl_preload != NULL)
_dl_dopreload(_dl_preload);
_dl_load_dep_libs(exe_obj, exe_obj->obj_flags, 1);
/*
* Now add the dynamic loader itself last in the object list
* so we can use the _dl_ code when serving dl.... calls.
* Intentionally left off the exe child_vec.
*/
dynp = (Elf_Dyn *)((void *)_DYNAMIC);
ehdr = (Elf_Ehdr *)dl_data[AUX_base];
dyn_obj = _dl_finalize_object(us, dynp,
(Elf_Phdr *)((char *)dl_data[AUX_base] + ehdr->e_phoff),
ehdr->e_phnum, OBJTYPE_LDR, dl_data[AUX_base], dyn_loff);
_dl_add_object(dyn_obj);
dyn_obj->refcount++;
_dl_link_grpsym(dyn_obj);
dyn_obj->status |= STAT_RELOC_DONE;
_dl_set_sod(dyn_obj->load_name, &dyn_obj->sod);
/* calculate the offsets for static TLS allocations */
_dl_allocate_tls_offsets();
/*
* Make something to help gdb when poking around in the code.
* Do this poking at the .dynamic section now, before relocation
* renders it read-only
*/
map_link = NULL;
#ifdef __mips__
for (dynp = exe_obj->load_dyn; dynp->d_tag; dynp++) {
if (dynp->d_tag == DT_MIPS_RLD_MAP_REL) {
map_link = (struct r_debug **)
(dynp->d_un.d_ptr + (Elf_Addr)dynp);
break;
} else if (dynp->d_tag == DT_MIPS_RLD_MAP) {
map_link = (struct r_debug **)
(dynp->d_un.d_ptr + exe_loff);
break;
}
}
#endif
if (map_link == NULL) {
for (dynp = exe_obj->load_dyn; dynp->d_tag; dynp++) {
if (dynp->d_tag == DT_DEBUG) {
map_link = (struct r_debug **)&dynp->d_un.d_ptr;
break;
}
}
if (dynp->d_tag != DT_DEBUG)
DL_DEB(("failed to mark DTDEBUG\n"));
}
if (map_link) {
debug_map = _dl_malloc(sizeof(*debug_map));
if (debug_map == NULL)
_dl_oom();
debug_map->r_version = 1;
debug_map->r_map = (struct link_map *)_dl_objects;
debug_map->r_brk = (Elf_Addr)_dl_debug_state;
debug_map->r_state = RT_CONSISTENT;
debug_map->r_ldbase = dyn_loff;
_dl_debug_map = debug_map;
#ifdef __mips__
relro_addr = exe_obj->relro_addr;
if (dynp->d_tag == DT_DEBUG &&
((Elf_Addr)map_link + sizeof(*map_link) <= relro_addr ||
(Elf_Addr)map_link >= relro_addr + exe_obj->relro_size)) {
_dl_mprotect(map_link, sizeof(*map_link),
PROT_READ|PROT_WRITE);
*map_link = _dl_debug_map;
_dl_mprotect(map_link, sizeof(*map_link),
PROT_READ|PROT_EXEC);
} else
#endif
*map_link = _dl_debug_map;
}
/*
* Everything should be in place now for doing the relocation
* and binding. Call _dl_rtld to do the job. Fingers crossed.
*/
failed = 0;
if (!_dl_traceld)
failed = _dl_rtld(_dl_objects);
if (_dl_debug || _dl_traceld) {
if (_dl_traceld)
_dl_pledge("stdio rpath", NULL);
_dl_show_objects(NULL);
}
DL_DEB(("dynamic loading done, %s.\n",
(failed == 0) ? "success":"failed"));
if (failed != 0)
_dl_die("relocation failed");
if (_dl_traceld)
_dl_exit(0);
_dl_loading_object = NULL;
/* set up the TIB for the initial thread */
_dl_allocate_first_tib();
_dl_fixup_user_env();
_dl_debug_state();
/*
* Do not run init code if run from ldd.
*/
if (_dl_objects->next != NULL) {
_dl_call_preinit(_dl_objects);
_dl_call_init(_dl_objects);
}
DL_DEB(("entry point: 0x%lx\n", dl_data[AUX_entry]));
/*
* Return the entry point.
*/
return(dl_data[AUX_entry]);
}
int
_dl_rtld(elf_object_t *object)
{
struct load_list *llist;
int fails = 0;
if (object->next)
fails += _dl_rtld(object->next);
if (object->status & STAT_RELOC_DONE)
return 0;
/*
* Do relocation information first, then GOT.
*/
unprotect_if_textrel(object);
_dl_rreloc(object);
fails =_dl_md_reloc(object, DT_REL, DT_RELSZ);
fails += _dl_md_reloc(object, DT_RELA, DT_RELASZ);
reprotect_if_textrel(object);
/*
* We do lazy resolution by default, doing eager resolution if
* - the object requests it with -znow, OR
* - LD_BIND_NOW is set and this object isn't being ltraced
*
* Note that -znow disables ltrace for the object: on at least
* amd64 'ld' doesn't generate the trampoline for lazy relocation
* when -znow is used.
*/
fails += _dl_md_reloc_got(object, !(object->obj_flags & DF_1_NOW) &&
!(_dl_bindnow && !object->traced));
/*
* Look for W&X segments and make them read-only.
*/
for (llist = object->load_list; llist != NULL; llist = llist->next) {
if ((llist->prot & PROT_WRITE) && (llist->prot & PROT_EXEC)) {
_dl_mprotect(llist->start, llist->size,
llist->prot & ~PROT_WRITE);
}
}
/*
* TEXTREL binaries are loaded without immutable on un-writeable sections.
* After text relocations are finished, these regions can become
* immutable. OPENBSD_MUTABLE section always overlaps writeable LOADs,
* so don't be afraid.
*/
if (object->dyn.textrel) {
for (llist = object->load_list; llist != NULL; llist = llist->next)
if ((llist->prot & PROT_WRITE) == 0)
_dl_mimmutable(llist->start, llist->size);
}
if (fails == 0)
object->status |= STAT_RELOC_DONE;
return (fails);
}
void
_dl_call_preinit(elf_object_t *object)
{
if (object->dyn.preinit_array) {
int num = object->dyn.preinit_arraysz / sizeof(Elf_Addr);
int i;
DL_DEB(("doing preinitarray obj %p @%p: [%s]\n",
object, object->dyn.preinit_array, object->load_name));
for (i = 0; i < num; i++)
(*object->dyn.preinit_array[i])(_dl_argc, _dl_argv,
environ, &_dl_cb_cb);
}
}
void
_dl_call_init(elf_object_t *object)
{
_dl_call_init_recurse(object, 1);
_dl_call_init_recurse(object, 0);
}
static void
_dl_relro(elf_object_t *object)
{
/*
* Handle GNU_RELRO
*/
if (object->relro_addr != 0 && object->relro_size != 0) {
Elf_Addr addr = object->relro_addr;
DL_DEB(("protect RELRO [0x%lx,0x%lx) in %s\n",
addr, addr + object->relro_size, object->load_name));
_dl_mprotect((void *)addr, object->relro_size, PROT_READ);
/* if library will never be unloaded, RELRO can be immutable */
if (object->nodelete)
_dl_mimmutable((void *)addr, object->relro_size);
}
}
void
_dl_call_init_recurse(elf_object_t *object, int initfirst)
{
struct object_vector vec;
int visited_flag = initfirst ? STAT_VISIT_INITFIRST : STAT_VISIT_INIT;
int i;
object->status |= visited_flag;
for (vec = object->child_vec, i = 0; i < vec.len; i++) {
if (vec.vec[i]->status & visited_flag)
continue;
_dl_call_init_recurse(vec.vec[i], initfirst);
}
if (object->status & STAT_INIT_DONE)
return;
if (initfirst && (object->obj_flags & DF_1_INITFIRST) == 0)
return;
if (!initfirst) {
_dl_relro(object);
_dl_apply_immutable(object);
}
if (object->dyn.init) {
DL_DEB(("doing ctors obj %p @%p: [%s]\n",
object, object->dyn.init, object->load_name));
(*object->dyn.init)();
}
if (object->dyn.init_array) {
int num = object->dyn.init_arraysz / sizeof(Elf_Addr);
int i;
DL_DEB(("doing initarray obj %p @%p: [%s]\n",
object, object->dyn.init_array, object->load_name));
for (i = 0; i < num; i++)
(*object->dyn.init_array[i])(_dl_argc, _dl_argv,
environ, &_dl_cb_cb);
}
if (initfirst) {
_dl_relro(object);
_dl_apply_immutable(object);
}
object->status |= STAT_INIT_DONE;
}
char *
_dl_getenv(const char *var, char **env)
{
const char *ep;
while ((ep = *env++)) {
const char *vp = var;
while (*vp && *vp == *ep) {
vp++;
ep++;
}
if (*vp == '\0' && *ep++ == '=')
return((char *)ep);
}
return(NULL);
}
void
_dl_unsetenv(const char *var, char **env)
{
char *ep;
while ((ep = *env)) {
const char *vp = var;
while (*vp && *vp == *ep) {
vp++;
ep++;
}
if (*vp == '\0' && *ep++ == '=') {
char **P;
for (P = env;; ++P)
if (!(*P = *(P + 1)))
break;
} else
env++;
}
}
static inline void
fixup_sym(struct elf_object *dummy_obj, const char *name, void *addr)
{
struct sym_res sr;
sr = _dl_find_symbol(name, SYM_SEARCH_ALL|SYM_NOWARNNOTFOUND|SYM_PLT,
NULL, dummy_obj);
if (sr.sym != NULL) {
void *p = (void *)(sr.sym->st_value + sr.obj->obj_base);
if (p != addr) {
DL_DEB(("setting %s %p@%s[%p] from %p\n", name,
p, sr.obj->load_name, (void *)sr.obj, addr));
*(void **)p = *(void **)addr;
}
}
}
/*
* _dl_fixup_user_env()
*
* Set the user environment so that programs can use the environment
* while running constructors. Specifically, MALLOC_OPTIONS= for malloc()
*/
void
_dl_fixup_user_env(void)
{
struct elf_object dummy_obj;
dummy_obj.dyn.symbolic = 0;
dummy_obj.load_name = "ld.so";
fixup_sym(&dummy_obj, "environ", &environ);
fixup_sym(&dummy_obj, "__progname", &__progname);
}
const void *
_dl_cb_cb(int version)
{
DL_DEB(("version %d callbacks requested\n", version));
if (version == 0)
return &callbacks_0;
return NULL;
}
static inline void
unprotect_if_textrel(elf_object_t *object)
{
struct load_list *ll;
if (__predict_false(object->dyn.textrel == 1)) {
for (ll = object->load_list; ll != NULL; ll = ll->next) {
if ((ll->prot & PROT_WRITE) == 0)
_dl_mprotect(ll->start, ll->size,
PROT_READ | PROT_WRITE);
}
}
}
static inline void
reprotect_if_textrel(elf_object_t *object)
{
struct load_list *ll;
if (__predict_false(object->dyn.textrel == 1)) {
for (ll = object->load_list; ll != NULL; ll = ll->next) {
if ((ll->prot & PROT_WRITE) == 0)
_dl_mprotect(ll->start, ll->size, ll->prot);
}
}
}
static void
_dl_rreloc(elf_object_t *object)
{
const Elf_Relr *reloc, *rend;
Elf_Addr loff = object->obj_base;
reloc = object->dyn.relr;
rend = (const Elf_Relr *)((char *)reloc + object->dyn.relrsz);
while (reloc < rend) {
Elf_Addr *where;
where = (Elf_Addr *)(*reloc + loff);
*where++ += loff;
for (reloc++; reloc < rend && (*reloc & 1); reloc++) {
Elf_Addr bits = *reloc >> 1;
Elf_Addr *here = where;
while (bits != 0) {
if (bits & 1) {
*here += loff;
}
bits >>= 1;
here++;
}
where += (8 * sizeof *reloc) - 1;
}
}
}
void
_dl_push_range(struct range_vector *v, vaddr_t s, vaddr_t e)
{
int i = v->count;
if (i == nitems(v->slice)) {
_dl_die("too many ranges");
}
/* Skips the empty ranges (s == e). */
if (s < e) {
v->slice[i].start = s;
v->slice[i].end = e;
v->count++;
} else if (s > e) {
_dl_die("invalid range");
}
}
void
_dl_push_range_size(struct range_vector *v, vaddr_t s, vsize_t size)
{
_dl_push_range(v, s, s + size);
}
/*
* Finds the truly immutable ranges by taking mutable ones out. Implements
* interval difference of imut and mut. Interval splitting necessitates
* intermediate storage and complex double buffering.
*/
void
_dl_apply_immutable(elf_object_t *object)
{
struct range_vector acc[2]; /* flips out to avoid copying */
struct addr_range *m, *im;
int i, j, imut, in, out;
if (object->obj_type != OBJTYPE_LIB)
return;
for (imut = 0; imut < object->imut.count; imut++) {
im = &object->imut.slice[imut];
out = 0;
acc[out].count = 0;
_dl_push_range(&acc[out], im->start, im->end);
for (i = 0; i < object->mut.count; i++) {
m = &object->mut.slice[i];
in = out;
out = 1 - in;
acc[out].count = 0;
for (j = 0; j < acc[in].count; j++) {
const vaddr_t ms = m->start, me = m->end;
const vaddr_t is = acc[in].slice[j].start,
ie = acc[in].slice[j].end;
if (ie <= ms || me <= is) {
/* is .. ie .. ms .. me -> is .. ie */
/* ms .. me .. is .. ie -> is .. ie */
_dl_push_range(&acc[out], is, ie);
} else if (ms <= is && ie <= me) {
/* PROVIDED: ms < ie && is < me */
/* ms .. is .. ie .. me -> [] */
;
} else if (ie <= me) {
/* is .. ms .. ie .. me -> is .. ms */
_dl_push_range(&acc[out], is, ms);
} else if (is < ms) {
/* is .. ms .. me .. ie -> is .. ms */
_dl_push_range(&acc[out], is, ms);
_dl_push_range(&acc[out], me, ie);
} else {
/* ms .. is .. me .. ie -> me .. ie */
_dl_push_range(&acc[out], me, ie);
}
}
}
/* and now, install immutability for objects */
for (i = 0; i < acc[out].count; i++) {
const struct addr_range *ar = &acc[out].slice[i];
_dl_mimmutable((void *)ar->start, ar->end - ar->start);
}
}
}