diff options
-rw-r--r-- | libexec/rtld-elf/rtld.c | 262 |
1 files changed, 243 insertions, 19 deletions
diff --git a/libexec/rtld-elf/rtld.c b/libexec/rtld-elf/rtld.c index fa9d3cc..3b41acf 100644 --- a/libexec/rtld-elf/rtld.c +++ b/libexec/rtld-elf/rtld.c @@ -115,8 +115,11 @@ 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 int parse_libdir(const char *); +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 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); @@ -339,17 +342,20 @@ _LD(const char *var) func_ptr_type _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) { - Elf_Auxinfo *aux, *auxp, *aux_info[AT_COUNT]; + Elf_Auxinfo *aux, *auxp, *auxpf, *aux_info[AT_COUNT]; Objlist_Entry *entry; Obj_Entry *last_interposer, *obj, *preload_tail; const Elf_Phdr *phdr; Objlist initlist; RtldLockState lockstate; - char **argv, *argv0, **env, *kexecpath, *library_path_rpath; + 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; + 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. @@ -359,6 +365,7 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) */ /* Find the auxiliary vector on the stack. */ + argcp = sp; argc = *sp++; argv = (char **) sp; sp += argc + 1; /* Skip over arguments and NULL terminator */ @@ -410,6 +417,89 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) 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")); /* @@ -470,8 +560,7 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) * Load the main program, or process its program header if it is * already loaded. */ - if (aux_info[AT_EXECFD] != NULL) { /* Load the main program. */ - 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); @@ -492,7 +581,7 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) rtld_die(); } - if (aux_info[AT_EXECPATH] != NULL) { + 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] == '/') @@ -504,7 +593,7 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) else obj_main->path = xstrdup(buf); } else { - dbg("No AT_EXECPATH"); + dbg("No AT_EXECPATH or direct exec"); obj_main->path = xstrdup(argv0); } dbg("obj_main path %s", obj_main->path); @@ -2980,9 +3069,12 @@ search_library_pathfds(const char *name, const char *path, int *fdp) envcopy = xstrdup(path); for (fdstr = strtok_r(envcopy, ":", &last_token); fdstr != NULL; fdstr = strtok_r(NULL, ":", &last_token)) { - dirfd = parse_libdir(fdstr); - if (dirfd < 0) + 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; @@ -5178,34 +5270,166 @@ symlook_init_from_req(SymLook *dst, const SymLook *src) 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_libdir(const char *str) +parse_integer(const char *str) { static const int RADIX = 10; /* XXXJA: possibly support hex? */ const char *orig; - int fd; + int n; char c; orig = str; - fd = 0; + n = 0; for (c = *str; c != '\0'; c = *++str) { if (c < '0' || c > '9') return (-1); - fd *= RADIX; - fd += c - '0'; + n *= RADIX; + n += c - '0'; } /* Make sure we actually parsed something. */ - if (str == orig) { - _rtld_error("failed to parse directory FD from '%s'", str); + if (str == orig) return (-1); - } - return (fd); + return (n); +} + +static void +print_usage(const char *argv0) +{ + + rtld_printf("Usage: %s [-h] [-f <FD>] [--] <binary> [<args>]\n" + "\n" + "Options:\n" + " -h Display this help message\n" + " -p Search in PATH for named binary\n" + " -f <FD> Execute <FD> instead of searching for <binary>\n" + " -- End of RTLD options\n" + " <binary> Name of process to execute\n" + " <args> Arguments to the executed process\n", argv0); } /* |