ostbuild: Allow binding arbitrary directories, don't hardcode /proc /dev

This is just more flexible, and eventually we want this to be a
generic user-chroot tool.
This commit is contained in:
Colin Walters 2011-12-06 14:06:45 -05:00
parent 0fb40b201f
commit 3042724698

View File

@ -25,6 +25,9 @@
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/prctl.h>
@ -32,10 +35,23 @@
#include <linux/securebits.h>
#include <sched.h>
typedef unsigned int bool;
static void fatal (const char *message, ...) __attribute__ ((noreturn)) __attribute__ ((format (printf, 1, 2)));
static void fatal_errno (const char *message) __attribute__ ((noreturn));
static void
fatal_errno (const char *message) __attribute__ ((noreturn));
fatal (const char *fmt,
...)
{
va_list args;
va_start (args, fmt);
vfprintf (stderr, fmt, args);
putc ('\n', stderr);
va_end (args);
exit (1);
}
static void
fatal_errno (const char *message)
@ -44,38 +60,46 @@ fatal_errno (const char *message)
exit (1);
}
static void
initialize_chroot (const char *path)
{
char *subpath;
asprintf (&subpath, "%s/proc", path);
if (mount ("/proc", subpath, NULL, MS_BIND, NULL) < 0)
fatal_errno ("bind mounting proc");
free (subpath);
asprintf (&subpath, "%s/dev", path);
if (mount ("/dev", subpath, NULL, MS_BIND, NULL) < 0)
fatal_errno ("bind mounting dev");
free (subpath);
}
int
main (int argc,
char **argv)
{
const char *argv0;
const char *chroot_dir;
const char *program;
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
int after_bind_arg_index;
int i;
char **program_argv;
char **argv_iter;
if (argc < 3)
if (argc <= 0)
return 1;
argv0 = argv[0];
argc--;
argv++;
if (argc < 1)
fatal ("ROOTDIR argument must be specified");
after_bind_arg_index = 0;
argv_iter = argv;
while (after_bind_arg_index < argc
&& strcmp (argv[after_bind_arg_index], "--bind") == 0)
{
fprintf (stderr, "usage: %s DIR PROGRAM ARGS...\n", argv[0]);
exit (1);
if ((argc - after_bind_arg_index) < 3)
fatal ("--bind takes two arguments");
after_bind_arg_index += 3;
argv_iter += 3;
}
chroot_dir = argv[1];
program = argv[2];
if ((argc - after_bind_arg_index) < 2)
fatal ("usage: %s [--bind SOURCE DEST] ROOTDIR PROGRAM ARGS...", argv0);
chroot_dir = argv[after_bind_arg_index];
program = argv[after_bind_arg_index+1];
program_argv = argv + after_bind_arg_index + 1;
if (getresgid (&rgid, &egid, &sgid) < 0)
fatal_errno ("getresgid");
@ -83,38 +107,69 @@ main (int argc,
fatal_errno ("getresuid");
if (ruid == 0)
{
fprintf (stderr, "error: ruid is 0\n");
exit (1);
}
fatal ("error: ruid is 0");
if (rgid == 0)
rgid = ruid;
/* Ensure we can't execute setuid programs - see prctl(2) and capabilities(7) */
/* Ensure we can't execute setuid programs. See prctl(2) and
* capabilities(7).
*
* This closes the main historical reason why only uid 0 can
* chroot(2) - because unprivileged users can create hard links to
* setuid binaries, and possibly confuse them into looking at data
* (or loading libraries) that they don't expect, and thus elevating
* privileges.
*/
if (prctl (PR_SET_SECUREBITS,
SECBIT_NOROOT | SECBIT_NOROOT_LOCKED) < 0)
fatal_errno ("prctl");
fatal_errno ("prctl (SECBIT_NOROOT)");
/* This call makes it so that when we create bind mounts, we're only
* affecting our children, not the entire system. This way it's
* harmless to bind mount e.g. /proc over an arbitrary directory.
*/
if (unshare (CLONE_NEWNS) < 0)
fatal_errno ("unshare (CLONE_NEWNS)");
/* This is necessary to undo the damage "sandbox" creates on Fedora
* by making / a shared mount instead of private. This isn't
* totally correct because the targets for our bind mounts may still
* be shared, but really, Fedora's sandbox is broken.
*/
if (mount ("/", "/", "none", MS_PRIVATE, NULL) < 0)
fatal_errno ("mount(/, MS_PRIVATE)");
initialize_chroot (chroot_dir);
/* Now let's set up our bind mounts */
for (i = 0; i < after_bind_arg_index; i += 3)
{
const char *bind_arg = argv[0]; /* --bind */
const char *bind_source = argv[i+1];
const char *bind_target = argv[i+2];
char *bind_abs_target;
assert (strcmp (bind_arg, "--bind") == 0);
asprintf (&bind_abs_target, "%s%s", chroot_dir, bind_target);
if (mount (bind_source, bind_abs_target, NULL, MS_BIND | MS_PRIVATE, NULL) < 0)
fatal_errno ("mount (MS_BIND)");
free (bind_abs_target);
}
/* Actually perform the chroot. */
if (chroot (chroot_dir) < 0)
fatal_errno ("chroot");
if (chdir ("/") < 0)
fatal_errno ("chdir");
/* These are irrevocable - see setuid(2) */
/* Switch back to the uid of our invoking process. These calls are
* irrevocable - see setuid(2) */
if (setgid (rgid) < 0)
fatal_errno ("setgid");
if (setuid (ruid) < 0)
fatal_errno ("setuid");
if (execv (program, argv + 2) < 0)
/* Finally, run the given child program. */
if (execv (program, program_argv) < 0)
fatal_errno ("execv");
return 1;