tls: in AES-GCM decoding, avoid memmove
[oweals/busybox.git] / util-linux / switch_root.c
index 6034485d7424873d06044c86ecc94d9c3ca57468..947dd0cdc15e95ac9d98a7db53f5807d3687af7d 100644 (file)
@@ -1,45 +1,63 @@
 /* vi: set sw=4 ts=4: */
-/* Copyright 2005 Rob Landley <rob@landley.net>
+/*
+ * Copyright 2005 Rob Landley <rob@landley.net>
  *
  * Switch from rootfs to another filesystem as the root of the mount tree.
  *
  * Licensed under GPLv2, see file LICENSE in this source tree.
  */
 //config:config SWITCH_ROOT
-//config:      bool "switch_root"
+//config:      bool "switch_root (5.2 kb)"
 //config:      default y
 //config:      select PLATFORM_LINUX
 //config:      help
-//config:        The switch_root utility is used from initramfs to select a new
-//config:        root device. Under initramfs, you have to use this instead of
-//config:        pivot_root. (Stop reading here if you don't care why.)
+//config:      The switch_root utility is used from initramfs to select a new
+//config:      root device. Under initramfs, you have to use this instead of
+//config:      pivot_root. (Stop reading here if you don't care why.)
+//config:
+//config:      Booting with initramfs extracts a gzipped cpio archive into rootfs
+//config:      (which is a variant of ramfs/tmpfs). Because rootfs can't be moved
+//config:      or unmounted*, pivot_root will not work from initramfs. Instead,
+//config:      switch_root deletes everything out of rootfs (including itself),
+//config:      does a mount --move that overmounts rootfs with the new root, and
+//config:      then execs the specified init program.
 //config:
-//config:        Booting with initramfs extracts a gzipped cpio archive into rootfs
-//config:        (which is a variant of ramfs/tmpfs). Because rootfs can't be moved
-//config:        or unmounted*, pivot_root will not work from initramfs. Instead,
-//config:        switch_root deletes everything out of rootfs (including itself),
-//config:        does a mount --move that overmounts rootfs with the new root, and
-//config:        then execs the specified init program.
+//config:      * Because the Linux kernel uses rootfs internally as the starting
+//config:      and ending point for searching through the kernel's doubly linked
+//config:      list of active mount points. That's why.
 //config:
-//config:        * Because the Linux kernel uses rootfs internally as the starting
-//config:        and ending point for searching through the kernel's doubly linked
-//config:        list of active mount points. That's why.
+// RUN_INIT config item is in klibc-utils
 
 //applet:IF_SWITCH_ROOT(APPLET(switch_root, BB_DIR_SBIN, BB_SUID_DROP))
+//                      APPLET_ODDNAME:name      main         location     suid_type     help
+//applet:IF_RUN_INIT(   APPLET_ODDNAME(run-init, switch_root, BB_DIR_SBIN, BB_SUID_DROP, run_init))
 
 //kbuild:lib-$(CONFIG_SWITCH_ROOT) += switch_root.o
-
-//usage:#define switch_root_trivial_usage
-//usage:       "[-c /dev/console] NEW_ROOT NEW_INIT [ARGS]"
-//usage:#define switch_root_full_usage "\n\n"
-//usage:       "Free initramfs and switch to another root fs:\n"
-//usage:       "chroot to NEW_ROOT, delete all in /, move NEW_ROOT to /,\n"
-//usage:       "execute NEW_INIT. PID must be 1. NEW_ROOT must be a mountpoint.\n"
-//usage:     "\n       -c DEV  Reopen stdio to DEV after switch"
+//kbuild:lib-$(CONFIG_RUN_INIT)    += switch_root.o
 
 #include <sys/vfs.h>
 #include <sys/mount.h>
+#if ENABLE_RUN_INIT
+# include <sys/prctl.h>
+# ifndef PR_CAPBSET_READ
+# define PR_CAPBSET_READ 23
+# endif
+# ifndef PR_CAPBSET_DROP
+# define PR_CAPBSET_DROP 24
+# endif
+# include <linux/capability.h>
+// #include <sys/capability.h>
+// This header is in libcap, but the functions are in libc.
+// Comment in the header says this above capset/capget:
+/* system calls - look to libc for function to system call mapping */
+extern int capset(cap_user_header_t header, cap_user_data_t data);
+extern int capget(cap_user_header_t header, const cap_user_data_t data);
+// so for bbox, let's just repeat the declarations.
+// This way, libcap needs not be installed in build environment.
+#endif
+
 #include "libbb.h"
+
 // Make up for header deficiencies
 #ifndef RAMFS_MAGIC
 # define RAMFS_MAGIC ((unsigned)0x858458f6)
@@ -89,17 +107,125 @@ static void delete_contents(const char *directory, dev_t rootdev)
        }
 }
 
+#if ENABLE_RUN_INIT
+DEFINE_STRUCT_CAPS;
+
+static void drop_capset(int cap_idx)
+{
+       struct caps caps;
+
+       getcaps(&caps);
+       caps.data[CAP_TO_INDEX(cap_idx)].inheritable &= ~CAP_TO_MASK(cap_idx);
+       if (capset(&caps.header, caps.data) != 0)
+               bb_perror_msg_and_die("capset");
+}
+
+static void drop_bounding_set(int cap_idx)
+{
+       int ret;
+
+       ret = prctl(PR_CAPBSET_READ, cap_idx, 0, 0, 0);
+       if (ret < 0)
+               bb_perror_msg_and_die("prctl: %s", "PR_CAPBSET_READ");
+
+       if (ret == 1) {
+               ret = prctl(PR_CAPBSET_DROP, cap_idx, 0, 0, 0);
+               if (ret != 0)
+                       bb_perror_msg_and_die("prctl: %s", "PR_CAPBSET_DROP");
+       }
+}
+
+static void drop_usermodehelper(const char *filename, int cap_idx)
+{
+       unsigned lo, hi;
+       char buf[sizeof(int)*3 * 2 + 8];
+       int fd;
+       int ret;
+
+       ret = open_read_close(filename, buf, sizeof(buf) - 1);
+       if (ret < 0)
+               return; /* assuming files do not exist */
+
+       buf[ret] = '\0';
+       ret = sscanf(buf, "%u %u", &lo, &hi);
+       if (ret != 2)
+               bb_perror_msg_and_die("can't parse file '%s'", filename);
+
+       if (cap_idx < 32)
+               lo &= ~(1 << cap_idx);
+       else
+               hi &= ~(1 << (cap_idx - 32));
+
+       fd = xopen(filename, O_WRONLY);
+       fdprintf(fd, "%u %u", lo, hi);
+       close(fd);
+}
+
+static void drop_capabilities(char *string)
+{
+       char *cap;
+
+       cap = strtok(string, ",");
+       while (cap) {
+               unsigned cap_idx;
+
+               cap_idx = cap_name_to_number(cap);
+               drop_usermodehelper("/proc/sys/kernel/usermodehelper/bset", cap_idx);
+               drop_usermodehelper("/proc/sys/kernel/usermodehelper/inheritable", cap_idx);
+               drop_bounding_set(cap_idx);
+               drop_capset(cap_idx);
+               bb_error_msg("dropped capability: %s", cap);
+               cap = strtok(NULL, ",");
+       }
+}
+#endif
+
 int switch_root_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int switch_root_main(int argc UNUSED_PARAM, char **argv)
 {
        char *newroot, *console = NULL;
        struct stat st;
        struct statfs stfs;
+       unsigned dry_run = 0;
        dev_t rootdev;
 
-       // Parse args (-c console)
-       opt_complementary = "-2"; // minimum 2 params
-       getopt32(argv, "+c:", &console); // '+': stop at first non-option
+       // Parse args. '+': stop at first non-option
+       if (ENABLE_SWITCH_ROOT && (!ENABLE_RUN_INIT || applet_name[0] == 's')) {
+//usage:#define switch_root_trivial_usage
+//usage:       "[-c CONSOLE_DEV] NEW_ROOT NEW_INIT [ARGS]"
+//usage:#define switch_root_full_usage "\n\n"
+//usage:       "Free initramfs and switch to another root fs:\n"
+//usage:       "chroot to NEW_ROOT, delete all in /, move NEW_ROOT to /,\n"
+//usage:       "execute NEW_INIT. PID must be 1. NEW_ROOT must be a mountpoint.\n"
+//usage:     "\n       -c DEV  Reopen stdio to DEV after switch"
+               getopt32(argv, "^+"
+                       "c:"
+                       "\0" "-2" /* minimum 2 args */,
+                       &console
+               );
+       } else {
+#if ENABLE_RUN_INIT
+//usage:#define run_init_trivial_usage
+//usage:       "[-d CAP,CAP...] [-n] [-c CONSOLE_DEV] NEW_ROOT NEW_INIT [ARGS]"
+//usage:#define run_init_full_usage "\n\n"
+//usage:       "Free initramfs and switch to another root fs:\n"
+//usage:       "chroot to NEW_ROOT, delete all in /, move NEW_ROOT to /,\n"
+//usage:       "execute NEW_INIT. PID must be 1. NEW_ROOT must be a mountpoint.\n"
+//usage:     "\n       -c DEV  Reopen stdio to DEV after switch"
+//usage:     "\n       -d CAPS Drop capabilities"
+//usage:     "\n       -n      Dry run"
+               char *cap_list = NULL;
+               dry_run = getopt32(argv, "^+"
+                       "c:d:n"
+                       "\0" "-2" /* minimum 2 args */,
+                       &console,
+                       &cap_list
+               );
+               dry_run >>= 2; // -n
+               if (cap_list)
+                       drop_capabilities(cap_list);
+#endif
+       }
        argv += optind;
        newroot = *argv++;
 
@@ -108,9 +234,12 @@ int switch_root_main(int argc UNUSED_PARAM, char **argv)
        xstat("/", &st);
        rootdev = st.st_dev;
        xstat(".", &st);
-       if (st.st_dev == rootdev || getpid() != 1) {
+       if (st.st_dev == rootdev) {
                // Show usage, it says new root must be a mountpoint
-               // and we must be PID 1
+               bb_show_usage();
+       }
+       if (!dry_run && getpid() != 1) {
+               // Show usage, it says we must be PID 1
                bb_show_usage();
        }
 
@@ -118,7 +247,7 @@ int switch_root_main(int argc UNUSED_PARAM, char **argv)
        // we mean it. I could make this a CONFIG option, but I would get email
        // from all the people who WILL destroy their filesystems.
        if (stat("/init", &st) != 0 || !S_ISREG(st.st_mode)) {
-               bb_error_msg_and_die("/init is not a regular file");
+               bb_error_msg_and_die("'%s' is not a regular file", "/init");
        }
        statfs("/", &stfs); // this never fails
        if ((unsigned)stfs.f_type != RAMFS_MAGIC
@@ -127,13 +256,15 @@ int switch_root_main(int argc UNUSED_PARAM, char **argv)
                bb_error_msg_and_die("root filesystem is not ramfs/tmpfs");
        }
 
-       // Zap everything out of rootdev
-       delete_contents("/", rootdev);
+       if (!dry_run) {
+               // Zap everything out of rootdev
+               delete_contents("/", rootdev);
 
-       // Overmount / with newdir and chroot into it
-       if (mount(".", "/", NULL, MS_MOVE, NULL)) {
-               // For example, fails when newroot is not a mountpoint
-               bb_perror_msg_and_die("error moving root");
+               // Overmount / with newdir and chroot into it
+               if (mount(".", "/", NULL, MS_MOVE, NULL)) {
+                       // For example, fails when newroot is not a mountpoint
+                       bb_perror_msg_and_die("error moving root");
+               }
        }
        xchroot(".");
        // The chdir is needed to recalculate "." and ".." links
@@ -141,14 +272,25 @@ int switch_root_main(int argc UNUSED_PARAM, char **argv)
 
        // If a new console specified, redirect stdin/stdout/stderr to it
        if (console) {
-               close(0);
-               xopen(console, O_RDWR);
-               xdup2(0, 1);
-               xdup2(0, 2);
+               int fd = open_or_warn(console, O_RDWR);
+               if (fd >= 0) {
+                       xmove_fd(fd, 0);
+                       xdup2(0, 1);
+                       xdup2(0, 2);
+               }
        }
 
-       // Exec real init
-       execv(argv[0], argv);
+       if (dry_run) {
+               // Does NEW_INIT look like it can be executed?
+               //xstat(argv[0], &st);
+               //if (!S_ISREG(st.st_mode))
+               //      bb_perror_msg_and_die("'%s' is not a regular file", argv[0]);
+               if (access(argv[0], X_OK) == 0)
+                       return 0;
+       } else {
+               // Exec NEW_INIT
+               execv(argv[0], argv);
+       }
        bb_perror_msg_and_die("can't execute '%s'", argv[0]);
 }
 
@@ -181,7 +323,7 @@ So there's a step that needs to be sort of atomic but can't be as a shell
 script.  (You can work around this with static linking or very carefully laid
 out paths and sequencing, but it's brittle, ugly, and non-obvious.)
 
-2) The "find | rm" bit will acually delete everything because the mount points
+2) The "find | rm" bit will actually delete everything because the mount points
 still show up (even if their contents don't), and rm -rf will then happily zap
 that.  So the first line is an oversimplification of what you need to do _not_
 to descend into other filesystems and delete their contents.