/* $OpenBSD: loader.c,v 1.223 2024/01/22 02:08: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 #include #include #include #ifdef __i386__ # include #endif #include #include #include /* NAME_MAX */ #include #include #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; 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); 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); } } }