Add -u and -g options to febootstrap-supermin-helper
authorMatthew Booth <mbooth@redhat.com>
Thu, 28 Oct 2010 14:17:21 +0000 (15:17 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Thu, 28 Oct 2010 15:05:09 +0000 (16:05 +0100)
Bash automatically resets euid to uid when it executes. This means that the
effective user id of a program at the point it calls febootstrap-supermin-helper
will be lost if any part of execution chain involved bash. This in turn can
result in:

* the generation of an incorrect checksum, which contains the uid.
* the generation of supermin files with a mixture of owners

The -u and -g options allow the caller to pass in an explicit user and group to
run as. febootstrap-supermin-helper will set(u|g)id as appropriate.

helper/febootstrap-supermin-helper.pod
helper/main.c
lib/.gitignore
m4/.gitignore
m4/gnulib-cache.m4

index c6c551c..1ef960f 100644 (file)
@@ -94,6 +94,19 @@ If this option is not specified, then every kernel module from the
 host will be included.  This is safer, but can produce rather large
 appliances which need a lot more memory to boot.
 
+=item B<-u user> | B<--user user> | B<-g group> | B<--group group>
+
+Run febootstrap-supermin-helper as an alternate user and/or group.
+C<user> and C<group> can be specified as either a name, which will
+be resolved using the system name service, or a uid/gid.  Use of these
+options requires root privileges.
+
+Use of these options is required if running febootstrap-supermin-helper
+as root with the effective uid/gid set to non-root.  Bash will reset
+the effective uid/gid to the real uid/gid when invoked.  As
+febootstrap-supermin-helper uses bash in parts, this will result in the
+creation of an appliance with a mixture of ownerships.
+
 =back
 
 =head1 SPEED
index f194008..b769bc7 100644 (file)
 #include <sys/types.h>
 #include <sys/time.h>
 #include <assert.h>
+#include <grp.h>
+#include <pwd.h>
 
 #include "error.h"
+#include "xstrtol.h"
 
 #include "helper.h"
 
 struct timeval start_t;
 int verbose = 0;
 
-static const char *format = "cpio";
-
 enum { HELP_OPTION = CHAR_MAX + 1 };
 
-static const char *options = "f:k:vV";
+static const char *options = "f:g:k:u:vV";
 static const struct option long_options[] = {
   { "help", 0, 0, HELP_OPTION },
   { "format", required_argument, 0, 'f' },
+  { "group", 0, 0, 'g' },
   { "kmods", required_argument, 0, 'k' },
+  { "user", 0, 0, 'u' },
   { "verbose", 0, 0, 'v' },
   { "version", 0, 0, 'V' },
   { 0, 0, 0, 0 }
@@ -76,6 +79,12 @@ usage (FILE *f, const char *progname)
           "       Display this help text and exit.\n"
           "  -f cpio|ext2|checksum | --format cpio|ext2|checksum\n"
           "       Specify output format (default: cpio).\n"
+          "  -u user\n"
+          "       The user name or uid the appliance will run as. Use of this\n"
+          "       option requires root privileges.\n"
+          "  -g group\n"
+          "       The group name or gid the appliance will run as. Use of\n"
+          "       this option requires root privileges.\n"
           "  -k file | --kmods file\n"
           "       Specify kernel module whitelist.\n"
           "  --verbose | -v\n"
@@ -85,14 +94,76 @@ usage (FILE *f, const char *progname)
           progname, progname, progname, progname, progname, progname);
 }
 
+static uid_t
+parseuser (const char *id, const char *progname)
+{
+
+  struct passwd *pwd;
+
+  errno = 0;
+  pwd = getpwnam (id);
+
+  if (NULL == pwd) {
+    if (errno != 0) {
+      fprintf (stderr, "Error looking up user: %m\n");
+      exit (EXIT_FAILURE);
+    }
+
+    long val;
+    int err = xstrtol (id, NULL, 10, &val, "");
+    if (err != LONGINT_OK) {
+        fprintf (stderr, "%s is not a valid user name or uid\n", id);
+        usage (stderr, progname);
+        exit (EXIT_FAILURE);
+    }
+
+    return (uid_t) val;
+  }
+
+  return pwd->pw_uid;
+}
+
+static gid_t
+parsegroup (const char *id, const char *progname)
+{
+
+  struct group *grp;
+
+  errno = 0;
+  grp = getgrnam (id);
+
+  if (NULL == grp) {
+    if (errno != 0) {
+      fprintf (stderr, "Error looking up group: %m\n");
+      exit (EXIT_FAILURE);
+    }
+
+    long val;
+    int err = xstrtol (id, NULL, 10, &val, "");
+    if (err != LONGINT_OK) {
+        fprintf (stderr, "%s is not a valid group name or gid\n", id);
+        usage (stderr, progname);
+        exit (EXIT_FAILURE);
+    }
+
+    return (gid_t) val;
+  }
+
+  return grp->gr_gid;
+}
+
 int
 main (int argc, char *argv[])
 {
   /* First thing: start the clock. */
   gettimeofday (&start_t, NULL);
 
+  const char *format = "cpio";
   const char *whitelist = NULL;
 
+  uid_t euid = geteuid ();
+  gid_t egid = getegid ();
+
   /* Command line arguments. */
   for (;;) {
     int c = getopt_long (argc, argv, options, long_options, NULL);
@@ -107,6 +178,14 @@ main (int argc, char *argv[])
       format = optarg;
       break;
 
+    case 'u':
+      euid = parseuser (optarg, argv[0]);
+      break;
+
+    case 'g':
+      egid = parsegroup (optarg, argv[0]);
+      break;
+
     case 'k':
       whitelist = optarg;
       break;
@@ -125,6 +204,37 @@ main (int argc, char *argv[])
     }
   }
 
+  /* We need to set the real, not effective, uid here to work round a
+   * misfeature in bash. bash will automatically reset euid to uid when
+   * invoked. As shell is used in places by febootstrap-supermin-helper, this
+   * results in code running with varying privilege. */
+  uid_t uid = getuid ();
+  gid_t gid = getgid ();
+
+  if (uid != euid || gid != egid) {
+    if (uid != 0) {
+      fprintf (stderr, "The -u and -g options require root privileges.\n");
+      usage (stderr, argv[0]);
+      exit (EXIT_FAILURE);
+    }
+
+    /* Need to become root first because setgid and setuid require it */
+    if (seteuid (0) == -1) {
+        perror ("seteuid");
+        exit (EXIT_FAILURE);
+    }
+
+    /* Set gid and uid to command-line parameters */
+    if (setgid (egid) == -1) {
+      perror ("setgid");
+      exit (EXIT_FAILURE);
+    }
+    if (setuid (euid) == -1) {
+      perror ("setuid");
+      exit (EXIT_FAILURE);
+    }
+  }
+
   /* Select the correct writer module. */
   struct writer *writer;
   int nr_outputs;
index 3dcbcf7..c76f105 100644 (file)
 /hash-pjw.c
 /hash-pjw.h
 /sys_wait.in.h
+/getopt.c
+/getopt.in.h
+/getopt1.c
+/getopt_int.h
+/inttypes.h
+/inttypes.in.h
+/xstrtol-error.c
+/xstrtol.c
+/xstrtol.h
+/xstrtoul.c
index 97d2c1b..bbef2cd 100644 (file)
@@ -90,3 +90,7 @@
 /xvasprintf.m4
 /asm-underscore.m4
 /sys_wait_h.m4
+/getopt.m4
+/inttypes-pri.m4
+/inttypes.m4
+/xstrtol.m4
index ebb8cc4..3210f5c 100644 (file)
@@ -15,7 +15,7 @@
 
 
 # Specification in the form of a command-line invocation:
-#   gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=. --no-libtool --macro-prefix=gl error filevercmp fts full-write hash hash-pjw xalloc xvasprintf
+#   gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=. --no-libtool --macro-prefix=gl error filevercmp fts full-write hash hash-pjw xalloc xstrtol xvasprintf
 
 # Specification in the form of a few gnulib-tool.m4 macro invocations:
 gl_LOCAL_DIR([])
@@ -27,6 +27,7 @@ gl_MODULES([
   hash
   hash-pjw
   xalloc
+  xstrtol
   xvasprintf
 ])
 gl_AVOID([])