/* libminijailpreload.c - preload hack library * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * This library is preloaded into every program launched by minijail_run(). * DO NOT EXPORT ANY SYMBOLS FROM THIS LIBRARY. They will replace other symbols * in the programs it is preloaded into and cause impossible-to-debug failures. * See the minijail0.1 for a design explanation. */ #include "libminijail.h" #include "libminijail-private.h" #include #include #include #include #include #include #include static int (*real_main) (int, char **, char **); static void *libc_handle; static void die(const char *failed) { syslog(LOG_ERR, "libminijail: %s", failed); abort(); } static void unset_in_env(char **envp, const char *name) { int i; for (i = 0; envp[i]; i++) if (!strncmp(envp[i], name, strlen(name))) envp[i][0] = '\0'; } /** @brief Fake main(), spliced in before the real call to main() by * __libc_start_main (see below). * We get serialized commands from our invoking process over an fd specified * by an environment variable (kFdEnvVar). The environment variable is a list * of key=value pairs (see move_commands_to_env); we use them to construct a * jail, then enter it. */ static int fake_main(int argc, char **argv, char **envp) { char *fd_name = getenv(kFdEnvVar); int fd = -1; struct minijail *j; if (geteuid() != getuid() || getegid() != getgid()) { /* * If we didn't do this check, an attacker could set kFdEnvVar * for any setuid program that uses libminijail to cause it to * get capabilities or a uid it did not expect. */ /* TODO(wad): why would libminijail interact here? */ return MINIJAIL_ERR_PRELOAD; } if (!fd_name) return MINIJAIL_ERR_PRELOAD; fd = atoi(fd_name); if (fd < 0) return MINIJAIL_ERR_PRELOAD; j = minijail_new(); if (!j) die("preload: out of memory"); if (minijail_from_fd(fd, j)) die("preload: failed to parse minijail from parent"); close(fd); unset_in_env(envp, kFdEnvVar); /* TODO(ellyjones): this trashes existing preloads, so one can't do: * LD_PRELOAD="/tmp/test.so libminijailpreload.so" prog; the * descendants of prog will have no LD_PRELOAD set at all. */ unset_in_env(envp, kLdPreloadEnvVar); /* Strip out flags meant for the parent. */ minijail_preenter(j); minijail_enter(j); minijail_destroy(j); dlclose(libc_handle); return real_main(argc, argv, envp); } /** @brief LD_PRELOAD override of __libc_start_main. * * It is really best if you do not look too closely at this function. We need * to ensure that some of our code runs before the target program (see the * minijail0.1 file in this directory for high-level details about this), and * the only available place to hook is this function, which is normally * responsible for calling main(). Our LD_PRELOAD will overwrite the real * __libc_start_main with this one, so we have to look up the real one from * libc and invoke it with a pointer to the fake main() we'd like to run before * the real main(). We can't just run our setup code *here* because * __libc_start_main is responsible for setting up the C runtime environment, * so we can't rely on things like malloc() being available yet. */ int API __libc_start_main(int (*main)(int, char **, char **), int argc, char **ubp_av, void (*init)(void), void (*fini)(void), void (*rtld_fini)(void), void(*stack_end)) { void *sym; /* * This hack is unfortunately required by C99 - casting directly from * void* to function pointers is left undefined. See POSIX.1-2003, the * Rationale for the specification of dlsym(), and dlsym(3). This * deliberately violates strict-aliasing rules, but gcc can't tell. */ union { int (*fn)(int (*main)(int, char **, char **), int argc, char **ubp_av, void (*init)(void), void (*fini)(void), void (*rtld_fini)(void), void(*stack_end)); void *symval; } real_libc_start_main; /* * We hold this handle for the duration of the real __libc_start_main() * and drop it just before calling the real main(). */ libc_handle = dlopen("libc.so.6", RTLD_NOW); if (!libc_handle) { syslog(LOG_ERR, "can't dlopen() libc"); /* * We dare not use abort() here because it will run atexit() * handlers and try to flush stdio. */ _exit(1); } sym = dlsym(libc_handle, "__libc_start_main"); if (!sym) { syslog(LOG_ERR, "can't find the real __libc_start_main()"); _exit(1); } real_libc_start_main.symval = sym; real_main = main; /* * Note that we swap fake_main in for main - fake_main knows that it * should call real_main after it's done. */ return real_libc_start_main.fn(fake_main, argc, ubp_av, init, fini, rtld_fini, stack_end); }