summaryrefslogtreecommitdiffstats
path: root/libexec
diff options
context:
space:
mode:
authorkib <kib@FreeBSD.org>2017-05-29 13:24:27 +0000
committerkib <kib@FreeBSD.org>2017-05-29 13:24:27 +0000
commit5e0514d85f068874075e262c4b61282d98f1740f (patch)
tree775650c6790291534d624d2e38299fe8b47a9d2b /libexec
parent03a6f9fd43c0dc1a4a0ea8b2757285d2e33b6bd1 (diff)
downloadFreeBSD-src-5e0514d85f068874075e262c4b61282d98f1740f.zip
FreeBSD-src-5e0514d85f068874075e262c4b61282d98f1740f.tar.gz
MFC direct execution mode for rtld.
MFC r318313: Make ld-elf.so.1 directly executable. MFC r318352 (by jonathan): Rename rtld's parse_libdir to parse_integer. MFC r318380: Pretend that there is some security when executing in direct mode. MFC r318431 (by jonathan): Allow rtld direct-exec to take a file descriptor. MFC r318445: Fix style, add static keyword before static function definition. MFC r318739: For ld.so direct execution mode, implement -p option.
Diffstat (limited to 'libexec')
-rw-r--r--libexec/rtld-elf/rtld.c262
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);
}
/*
OpenPOWER on IntegriCloud