Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 1 | /* libminijailpreload.c - preload hack library |
| 2 | * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| 3 | * Use of this source code is governed by a BSD-style license that can be |
| 4 | * found in the LICENSE file. |
| 5 | * |
| 6 | * This library is preloaded into every program launched by minijail_run(). |
| 7 | * DO NOT EXPORT ANY SYMBOLS FROM THIS LIBRARY. They will replace other symbols |
| 8 | * in the programs it is preloaded into and cause impossible-to-debug failures. |
| 9 | * See the minijail0.1 for a design explanation. */ |
| 10 | |
| 11 | #include "libminijail.h" |
| 12 | #include "libminijail-private.h" |
| 13 | |
| 14 | #include <dlfcn.h> |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 15 | #include <stdio.h> |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 16 | #include <stdlib.h> |
| 17 | #include <string.h> |
| 18 | #include <sys/types.h> |
| 19 | #include <syslog.h> |
| 20 | #include <unistd.h> |
| 21 | |
| 22 | static int (*real_main)(int, char **, char **) = NULL; |
| 23 | static void *libc_handle = NULL; |
| 24 | |
| 25 | static void die(const char *failed) { |
| 26 | syslog(LOG_ERR, "libminijail: %s", failed); |
| 27 | abort(); |
| 28 | } |
| 29 | |
| 30 | static void unset_in_env(char **envp, const char *name) { |
| 31 | int i; |
| 32 | for (i = 0; envp[i]; i++) |
| 33 | if (!strncmp(envp[i], name, strlen(name))) |
| 34 | envp[i][0] = '\0'; |
| 35 | } |
| 36 | |
| 37 | static void splitarg(char *str, char **key, char **val) { |
| 38 | *key = strsep(&str, "="); |
| 39 | *val = strsep(&str, ""); |
| 40 | } |
| 41 | |
| 42 | /** @brief Fake main(), spliced in before the real call to main() by |
| 43 | * __libc_start_main (see below). |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 44 | * We get serialized commands from our invoking process over an fd specified |
| 45 | * by an environment variable (kFdEnvVar). The environment variable is a list |
| 46 | * of key=value pairs (see move_commands_to_env); we use them to construct a |
| 47 | * jail, then enter it. |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 48 | */ |
| 49 | static int fake_main(int argc, char **argv, char **envp) { |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 50 | char *fd_name = getenv(kFdEnvVar); |
| 51 | char *arg = NULL; |
| 52 | size_t arg_len; |
| 53 | int fd = -1; |
| 54 | FILE *args; |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 55 | struct minijail *j; |
| 56 | if (geteuid() != getuid() || getegid() != getgid()) |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 57 | /* If we didn't do this check, an attacker could set kFdEnvVar for |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 58 | * any setuid program that uses libminijail to cause it to get capabilities |
| 59 | * or a uid it did not expect. */ |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 60 | /* TODO(wad) why would libminijail interact here? */ |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 61 | return MINIJAIL_ERR_PRELOAD; |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 62 | if (!fd_name) |
| 63 | return MINIJAIL_ERR_PRELOAD; |
| 64 | fd = atoi(fd_name); |
| 65 | if (fd < 0) |
| 66 | return MINIJAIL_ERR_PRELOAD; |
| 67 | args = fdopen(fd, "r"); |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 68 | if (!args) |
| 69 | return MINIJAIL_ERR_PRELOAD; |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 70 | |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 71 | j = minijail_new(); |
| 72 | if (!j) |
| 73 | die("preload: out of memory"); |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 74 | while (getline(&arg, &arg_len, args) > 0) { |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 75 | char *key, *val; |
| 76 | unsigned long v; |
| 77 | splitarg(arg, &key, &val); |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 78 | if (!strcmp(arg, "eom\n")) { |
| 79 | break; |
| 80 | } else if (!strcmp(key, "caps")) { |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 81 | v = strtoul(val, NULL, 16); |
| 82 | minijail_use_caps(j, v); |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 83 | } else if (!strcmp(key, "ptrace")) { |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 84 | minijail_disable_ptrace(j); |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 85 | } else if (!strcmp(key, "uid")) { |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 86 | v = atoi(val); |
| 87 | minijail_change_uid(j, v); |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 88 | } else if (!strcmp(key, "gid")) { |
Ben Chan | 6f47038 | 2011-08-23 12:54:41 -0700 | [diff] [blame] | 89 | v = atoi(val); |
| 90 | minijail_change_gid(j, v); |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 91 | } else if (!strcmp(key, "seccomp")) { |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 92 | minijail_use_seccomp(j); |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 93 | } |
| 94 | free(arg); |
| 95 | arg = NULL; |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 96 | } |
Will Drewry | 2f54b6a | 2011-09-16 13:45:31 -0500 | [diff] [blame^] | 97 | if (!feof(args) && ferror(args)) |
| 98 | die("preload: unexpected failure during unmarshalling"); |
| 99 | fclose(args); |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 100 | /* TODO(ellyjones): this trashes existing preloads, so one can't do: |
| 101 | * LD_PRELOAD="/tmp/test.so libminijailpreload.so" prog; the descendants of |
| 102 | * prog will have no LD_PRELOAD set at all. */ |
Ben Chan | 541c7e5 | 2011-08-26 14:55:53 -0700 | [diff] [blame] | 103 | unset_in_env(envp, kLdPreloadEnvVar); |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 104 | minijail_enter(j); |
| 105 | minijail_destroy(j); |
Elly Jones | cd7a904 | 2011-07-22 13:56:51 -0400 | [diff] [blame] | 106 | dlclose(libc_handle); |
| 107 | return real_main(argc, argv, envp); |
| 108 | } |
| 109 | |
| 110 | /** @brief LD_PRELOAD override of __libc_start_main. |
| 111 | * |
| 112 | * It is really best if you do not look too closely at this function. |
| 113 | * We need to ensure that some of our code runs before the target program (see |
| 114 | * the minijail0.1 file in this directory for high-level details about this), and |
| 115 | * the only available place to hook is this function, which is normally |
| 116 | * responsible for calling main(). Our LD_PRELOAD will overwrite the real |
| 117 | * __libc_start_main with this one, so we have to look up the real one from |
| 118 | * libc and invoke it with a pointer to the fake main() we'd like to run before |
| 119 | * the real main(). We can't just run our setup code *here* because |
| 120 | * __libc_start_main is responsible for setting up the C runtime environment, |
| 121 | * so we can't rely on things like malloc() being available yet. |
| 122 | */ |
| 123 | |
| 124 | int __libc_start_main(int (*main) (int, char **, char **), |
| 125 | int argc, char ** ubp_av, void (*init) (void), |
| 126 | void (*fini) (void), void (*rtld_fini) (void), |
| 127 | void (* stack_end)) { |
| 128 | void *sym; |
| 129 | /* This hack is unfortunately required by C99 - casting directly from void* to |
| 130 | * function pointers is left undefined. See POSIX.1-2003, the Rationale for |
| 131 | * the specification of dlsym(), and dlsym(3). This deliberately violates |
| 132 | * strict-aliasing rules, but gcc can't tell. */ |
| 133 | union { |
| 134 | int (*fn)(int (*main) (int, char **, char **), int argc, |
| 135 | char **ubp_av, void (*init) (void), void (*fini) (void), |
| 136 | void (*rtld_fini) (void), void (* stack_end)); |
| 137 | void *symval; |
| 138 | } real_libc_start_main; |
| 139 | |
| 140 | /* We hold this handle for the duration of the real __libc_start_main() and |
| 141 | * drop it just before calling the real main(). */ |
| 142 | libc_handle = dlopen("libc.so.6", RTLD_NOW); |
| 143 | |
| 144 | if (!libc_handle) { |
| 145 | syslog(LOG_ERR, "can't dlopen() libc"); |
| 146 | /* We dare not use abort() here because it will run atexit() handlers and |
| 147 | * try to flush stdio. */ |
| 148 | _exit(1); |
| 149 | } |
| 150 | sym = dlsym(libc_handle, "__libc_start_main"); |
| 151 | if (!sym) { |
| 152 | syslog(LOG_ERR, "can't find the real __libc_start_main()"); |
| 153 | _exit(1); |
| 154 | } |
| 155 | real_libc_start_main.symval = sym; |
| 156 | real_main = main; |
| 157 | |
| 158 | /* Note that we swap fake_main in for main - fake_main knows that it should |
| 159 | * call real_main after it's done. */ |
| 160 | return real_libc_start_main.fn(fake_main, argc, ubp_av, init, fini, rtld_fini, |
| 161 | stack_end); |
| 162 | } |