/* 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 GPL version 2, see file LICENSE in this tarball for details.
+ * Licensed under GPLv2, see file LICENSE in this source tree.
*/
+//config:config 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:
+//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: * 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:
+// 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
+//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)
}
}
+#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++;
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();
}
// 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
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
- xchdir("/");
+ /*xchdir("/"); - done in xchroot */
// 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]);
}
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.
That's why the careful sequencing above: we cd into the new mount point before
we do the mount --move. Moving the mount point would otherwise make it
-totally inaccessible to is because cd-ing to the old path wouldn't give it to
+totally inaccessible to us because cd-ing to the old path wouldn't give it to
us anymore, and cd "/" just gives us the cached dentry from when the process
was created (in this case the old initramfs one). But the "." symlink gives
us the dentry of the filesystem we just moved, so we can then "chroot ." to