You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
4.8 KiB
149 lines
4.8 KiB
4 months ago
|
/* 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 <dlfcn.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <syslog.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
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);
|
||
|
}
|