/* 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.
*
//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)
}
}
+#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
}
}
- // 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]);
}