From 304272469896e63594fbeb20dbbb80d31c9f7f84 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 6 Dec 2011 14:06:45 -0500 Subject: [PATCH] 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. --- src/ostbuild/ostbuild-user-chroot.c | 119 ++++++++++++++++++++-------- 1 file changed, 87 insertions(+), 32 deletions(-) diff --git a/src/ostbuild/ostbuild-user-chroot.c b/src/ostbuild/ostbuild-user-chroot.c index c0ed1a2b..ed8d6168 100644 --- a/src/ostbuild/ostbuild-user-chroot.c +++ b/src/ostbuild/ostbuild-user-chroot.c @@ -25,6 +25,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -32,10 +35,23 @@ #include #include -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;