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



commit 304272469896e63594fbeb20dbbb80d31c9f7f84
Author: Colin Walters <walters verbum org>
Date:   Tue Dec 6 14:06:45 2011 -0500

    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 files changed, 87 insertions(+), 32 deletions(-)
---
diff --git a/src/ostbuild/ostbuild-user-chroot.c b/src/ostbuild/ostbuild-user-chroot.c
index c0ed1a2..ed8d616 100644
--- a/src/ostbuild/ostbuild-user-chroot.c
+++ b/src/ostbuild/ostbuild-user-chroot.c
@@ -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,50 +35,71 @@
 #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));
-
-static void
-fatal_errno (const char *message)
+fatal (const char *fmt,
+       ...)
 {
-  perror (message);
+  va_list args;
+  
+  va_start (args, fmt);
+
+  vfprintf (stderr, fmt, args);
+  putc ('\n', stderr);
+  
+  va_end (args);
   exit (1);
 }
 
 static void
-initialize_chroot (const char *path)
+fatal_errno (const char *message)
 {
-  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);
+  perror (message);
+  exit (1);
 }
 
 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 <= 0)
+    return 1;
+
+  argv0 = argv[0];
+  argc--;
+  argv++;
 
-  if (argc < 3)
+  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;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]