diff --git a/libexec/rtld-elf/Makefile b/libexec/rtld-elf/Makefile --- a/libexec/rtld-elf/Makefile +++ b/libexec/rtld-elf/Makefile @@ -90,6 +90,10 @@ ${PROG_FULL}: ${VERSION_MAP} .include +.if ${MK_SHLIBRANDOM} != "no" +CFLAGS+= -DSHLIBRANDOM +.endif + .if ${COMPILER_TYPE} == "gcc" # GCC warns about redeclarations even though they have __exported # and are therefore not identical to the ones from the system headers. diff --git a/libexec/rtld-elf/rtld.c b/libexec/rtld-elf/rtld.c --- a/libexec/rtld-elf/rtld.c +++ b/libexec/rtld-elf/rtld.c @@ -141,6 +141,10 @@ static int parse_integer(const char *); static void *path_enumerate(const char *, path_enum_proc, const char *, void *); static void print_usage(const char *argv0); +static const char *printable_path(const char *); +#if defined(SHLIBRANDOM) +static void randomize_neededs(Obj_Entry *, int); +#endif static void release_object(Obj_Entry *); static int relocate_object_dag(Obj_Entry *root, bool bind_now, Obj_Entry *rtldobj, int flags, RtldLockState *lockstate); @@ -2448,6 +2452,338 @@ return (0); } +#if defined(SHLIBRANDOM) +#define MAX_SHL_SYMBOLS (16*1024) +struct shl_symbol { + const char *name; + const char *lib_name; + uint32_t order; + Needed_Entry *need; +}; + +static int +_symtab_cmp(void* v1, void* v2) +{ + struct shl_symbol *s1, *s2; + int ret; + + s1 = v1; + s2 = v2; + + ret = strcmp(s1->name, s2->name); + if (ret != 0) + return ret; + + return (s1->order > s2->order); +} + +static void +_swap(void* v1, void* v2, int size) +{ + uint8_t buffer[size]; + + memcpy(buffer, v1, size); + memcpy(v1, v2, size); + memcpy(v2, buffer, size); +} + +static void +_qsort(void *v, int size, int left, int right, int (*comp)(void*, void*)) +{ + void *vt, *v3; + int i, last, mid = (left + right) / 2; + if (left >= right) + return; + + void *vl = (void*) ((uintptr_t)v + (left * size)); + void *vr = (void*) ((uintptr_t)v + (mid * size)); + _swap(vl, vr, size); + last = left; + for (i = left + 1; i <= right; i++) { + vt = (void*) ((uintptr_t)v + (i * size)); + if ((*comp)(vl, vt) > 0) { + ++last; + v3 = (void*) ((uintptr_t)v + (last * size)); + _swap(vt, v3, size); + } + } + v3 = (void*) ((uintptr_t)v + (last * size)); + _swap(vl, v3, size); + _qsort(v, size, left, last - 1, comp); + _qsort(v, size, last + 1, right, comp); +} + +/* + * Function checks if needed libraries can be safely randomized. + * It disables randomization if it detects problems. + * By default, neededs chain is divided inti critical and + * non-critical path. Critical-path must retain loading order. + * + * @return 1 if randomization can be done, 0 otherwise + */ +static int +can_randomize(Obj_Entry *refobj, uint32_t nneeded, Needed_Entry **critpath, uint32_t *critpath_cnt, + Needed_Entry **otherneeded, uint32_t *otherneeded_cnt) +{ + Needed_Entry *need; + char *path; + int *fd = NULL; + Obj_Entry **obj = NULL; + struct stat sb; + int ret = 1; + uint32_t a, b; + struct shl_symbol *symtab = NULL; + uint32_t symtab_cnt = 0; + Needed_Entry **critpath_tmp = NULL; + + /* Allocate helper structures */ + obj = xcalloc(nneeded, sizeof(Obj_Entry)); + if (obj == NULL) { + ret = 0; + goto exit1; + } + fd = xcalloc(nneeded, sizeof(int)); + if (fd == NULL) { + ret = 0; + goto exit1; + } + for (a = 0; a < nneeded; a++) + fd[a] = -1; + symtab = xcalloc(MAX_SHL_SYMBOLS, sizeof(struct shl_symbol)); + if (symtab == NULL) { + ret = 0; + goto exit1; + } + critpath_tmp = xcalloc(nneeded, sizeof(Needed_Entry *)); + if (critpath_tmp == NULL) { + ret = 0; + goto exit1; + } + + /* Map objects */ + a = 0; + for (need = refobj->needed; need != NULL; need = need->next, a++) { + path = find_library(refobj->strtab + need->name, refobj, &fd[a]); + + if (fstat(fd[a], &sb) == -1) { + ret = 0; + goto exit; + } + + obj[a] = map_object(fd[a], printable_path(path), &sb); + if (obj[a] == NULL) { + ret = 0; + goto exit; + } + if (!digest_dynamic(obj[a], 0)) { + ret = 0; + goto exit; + } + } + + /* Fill symtab */ + a = 0; + for (need = refobj->needed; need != NULL; need = need->next, a++) { + const Elf_Sym *sym = obj[a]->symtab; + for (b = 0; b < obj[a]->dynsymcount; b++, sym++) { + if (sym->st_size == 0) + continue; + + if (symtab_cnt == MAX_SHL_SYMBOLS) { + ret = 0; + if (getenv(_LD("SHLIBRANDOM_VERBOSE"))) + rtld_printf("max symbols reached\n"); + goto exit; + } + symtab[symtab_cnt].name = obj[a]->strtab + sym->st_name; + symtab[symtab_cnt].order = a; + symtab[symtab_cnt].need = need; + symtab[symtab_cnt].lib_name = refobj->strtab + need->name; + symtab_cnt++; + } + } + + /* Sort symtab */ + _qsort(symtab, sizeof(struct shl_symbol), 0, symtab_cnt - 1, _symtab_cmp); + + /* + * Find duplicates + * Ignore duplicates in the same lib (might be different symbol version, WEAK attribute etc.). + */ + for (a = 1; a < symtab_cnt; a++) { + if ((strcmp(symtab[a-1].name, symtab[a].name) == 0) && + (strcmp(symtab[a-1].lib_name, symtab[a].lib_name) != 0)) { + + if (getenv(_LD("SHLIBRANDOM_VERBOSE"))) + rtld_printf("duplicate found %s:%d:(%s and %s)\n", symtab[a].name, + symtab[a].order, symtab[a - 1].lib_name, symtab[a].lib_name); + + /* If collision, mark that current and previous order are on the critical path */ + critpath_tmp[symtab[a].order] = symtab[a].need; + critpath_tmp[symtab[a - 1].order] = symtab[a - 1].need; + } + } + + /* Fill critical and non-critical path */ + *critpath_cnt = 0; + *otherneeded_cnt = 0; + a = 0; + for (need = refobj->needed; need != NULL; need = need->next, a++) { + if (critpath_tmp[a] != NULL) { + critpath[*critpath_cnt] = critpath_tmp[a]; + *critpath_cnt = *critpath_cnt + 1; + } else { + otherneeded[*otherneeded_cnt] = need; + *otherneeded_cnt = *otherneeded_cnt + 1; + } + } + + if (getenv(_LD("SHLIBRANDOM_VERBOSE"))) { + /* Dump critical path */ + rtld_printf("CRITICAL PATH: "); + for (a = 0; a < *critpath_cnt; a++) { + rtld_printf("%s ", refobj->strtab + critpath[a]->name); + } + rtld_printf("\n"); + + /* Dump others */ + rtld_printf("NON-CRITICAL PATH: "); + for (a = 0; a < *otherneeded_cnt; a++) { + rtld_printf("%s ", refobj->strtab + otherneeded[a]->name); + } + rtld_printf("\n"); + } + +exit: + /* Unmap objects and free helper structures */ + for (a = 0; a < nneeded; a++) { + if (obj[a] != NULL) { + munmap(obj[a]->mapbase, obj[a]->mapsize); + obj_free(obj[a]); + } + if (fd[a] != -1) { + close(fd[a]); + } + } + +exit1: + if (fd) + free(fd); + if (obj) + free(obj); + if (symtab) + free(symtab); + if (critpath_tmp) + free(critpath_tmp); + + return (ret); +} + +static void +randomize_neededs(Obj_Entry *obj, int flags) +{ + Needed_Entry *need, **critpath=NULL, **noncritpath=NULL; + unsigned int i, j, k, nneed, critpath_cnt, noncritpath_cnt; + size_t sz = sizeof(unsigned int); + int mib[2]; + + if (!(obj->needed) || (flags & RTLD_LO_FILTEES)) + return; + + mib[0] = CTL_KERN; + mib[1] = KERN_ARND; + + for (nneed = 0, need = obj->needed; need != NULL; need = need->next) + nneed++; + + if (nneed < 2) + return; + + critpath = xcalloc(nneed, sizeof(Needed_Entry *)); + if (!critpath) + goto err; + noncritpath = xcalloc(nneed, sizeof(Needed_Entry *)); + if (!noncritpath) + goto err; + + if (can_randomize(obj, nneed, critpath, &critpath_cnt, noncritpath, &noncritpath_cnt) == 0) { + if (getenv(_LD("SHLIBRANDOM_VERBOSE"))) + rtld_printf("randomization disabled for %d needed for object %s\n", nneed, obj->path); + goto err; + } else { + if (getenv(_LD("SHLIBRANDOM_VERBOSE"))) + rtld_printf("randomization enabled for %d needed for object %s\n", nneed, obj->path); + } + + /* Shuffle non-critical path neededs */ + if (noncritpath_cnt < 2) { + /* do nothing */ + } else if (noncritpath_cnt == 2) { + if (sysctl(mib, 2, &j, &sz, NULL, 0)) + goto err; + + if (j % 2) { + need = noncritpath[0]; + noncritpath[0] = noncritpath[1]; + noncritpath[1] = need; + } + } else { + for (i=0; i < noncritpath_cnt; i++) { + do { + if (sysctl(mib, 2, &j, &sz, NULL, 0)) + goto err; + + j %= noncritpath_cnt; + } while (j == i); + + need = noncritpath[i]; + noncritpath[i] = noncritpath[j]; + noncritpath[j] = need; + } + } + + /* Merge non-critical path entries to critical path */ + for (i = 0; i < noncritpath_cnt; i++) { + /* Get a place where to put i-th non-critical path entry */ + if (sysctl(mib, 2, &j, &sz, NULL, 0)) + goto err; + j = j % (critpath_cnt + 1); + + /* Move all data from j-th place to the right */ + for (k = critpath_cnt; k > j; k--) + critpath[k] = critpath[k-1]; + /* Store non-critical path object to critical path table */ + critpath[j] = noncritpath[i]; + + /* Increase critical path length */ + critpath_cnt++; + } + + if (getenv(_LD("SHLIBRANDOM_VERBOSE"))) { + /* Dump critical path */ + rtld_printf("RANDOMIZED NEEDEDs: "); + for (i = 0; i < critpath_cnt; i++) { + rtld_printf("%s ", obj->strtab + critpath[i]->name); + } + rtld_printf("\n"); + } + + for (i=0; i < critpath_cnt; i++) + critpath[i]->next = i + 1 < critpath_cnt ? critpath[i + 1] : NULL; + + obj->needed = critpath[0]; + +err: + if (critpath) + free(critpath); + if (noncritpath) + free(noncritpath); + + return; +} +#endif + + /* * Given a shared object, traverse its list of needed objects, and load * each of them. Returns 0 on success. Generates an error message and @@ -2461,6 +2797,9 @@ for (obj = first; obj != NULL; obj = TAILQ_NEXT(obj, next)) { if (obj->marker) continue; +#if defined(SHLIBRANDOM) + randomize_neededs(obj, flags); +#endif if (process_needed(obj, obj->needed, flags) == -1) return (-1); } diff --git a/share/mk/src.opts.mk b/share/mk/src.opts.mk --- a/share/mk/src.opts.mk +++ b/share/mk/src.opts.mk @@ -212,6 +212,7 @@ OPENLDAP \ REPRODUCIBLE_BUILD \ RPCBIND_WARMSTART_SUPPORT \ + SHLIBRANDOM \ SORT_THREADS \ SVN \ ZONEINFO_LEAPSECONDS_SUPPORT \ diff --git a/tools/build/options/WITH_SHLIBRANDOM b/tools/build/options/WITH_SHLIBRANDOM new file mode 100644 --- /dev/null +++ b/tools/build/options/WITH_SHLIBRANDOM @@ -0,0 +1 @@ +Enable randomizing the load order of shared objects.