diff --git a/include/libbb.h b/include/libbb.h index 9535f5fb3..95a7470a8 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -1475,6 +1475,15 @@ const char *get_shell_name(void) FAST_FUNC; unsigned cap_name_to_number(const char *cap) FAST_FUNC; void printf_cap(const char *pfx, unsigned cap_no) FAST_FUNC; +void drop_capability(int cap_ordinal) FAST_FUNC; +/* Structures inside "struct caps" are Linux-specific and libcap-specific: */ +#define DEFINE_STRUCT_CAPS \ +struct caps { \ + struct __user_cap_header_struct header; \ + unsigned u32s; \ + struct __user_cap_data_struct data[2]; \ +} +void getcaps(void *caps) FAST_FUNC; unsigned cap_name_to_number(const char *name) FAST_FUNC; void printf_cap(const char *pfx, unsigned cap_no) FAST_FUNC; diff --git a/klibc-utils/run-init.c b/klibc-utils/run-init.c new file mode 100644 index 000000000..a70d1bfbf --- /dev/null +++ b/klibc-utils/run-init.c @@ -0,0 +1,27 @@ +/* + * run-init implementation for busybox + * + * Copyright (c) 2017 Denys Vlasenko + * + * Licensed under GPLv2, see file LICENSE in this source tree. + */ +//config:config RUN_INIT +//config: bool "run-init" +//config: default y +//config: select PLATFORM_LINUX +//config: help +//config: The run-init utility is used from initramfs to select a new +//config: root device. Under initramfs, you have to use this instead of +//config: pivot_root. +//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: run-init 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: util-linux has a similar tool, switch-root. +//config: run-init differs by also having a "-d CAPS_TO_DROP" option. + +/* applet and kbuild hooks are in switch_root.c */ diff --git a/libbb/capability.c b/libbb/capability.c index 692024f2f..f60062bfc 100644 --- a/libbb/capability.c +++ b/libbb/capability.c @@ -6,6 +6,14 @@ //kbuild:lib-$(CONFIG_PLATFORM_LINUX) += capability.o #include +// #include +// 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. #include "libbb.h" static const char *const capabilities[] = { @@ -77,3 +85,42 @@ void FAST_FUNC printf_cap(const char *pfx, unsigned cap_no) } printf("%scap_%u", pfx, cap_no); } + +DEFINE_STRUCT_CAPS; + +void FAST_FUNC getcaps(void *arg) +{ + static const uint8_t versions[] = { + _LINUX_CAPABILITY_U32S_3, /* = 2 (fits into byte) */ + _LINUX_CAPABILITY_U32S_2, /* = 2 */ + _LINUX_CAPABILITY_U32S_1, /* = 1 */ + }; + int i; + struct caps *caps = arg; + + caps->header.pid = 0; + for (i = 0; i < ARRAY_SIZE(versions); i++) { + caps->header.version = versions[i]; + if (capget(&caps->header, NULL) == 0) + goto got_it; + } + bb_simple_perror_msg_and_die("capget"); + got_it: + + switch (caps->header.version) { + case _LINUX_CAPABILITY_VERSION_1: + caps->u32s = _LINUX_CAPABILITY_U32S_1; + break; + case _LINUX_CAPABILITY_VERSION_2: + caps->u32s = _LINUX_CAPABILITY_U32S_2; + break; + case _LINUX_CAPABILITY_VERSION_3: + caps->u32s = _LINUX_CAPABILITY_U32S_3; + break; + default: + bb_error_msg_and_die("unsupported capability version"); + } + + if (capget(&caps->header, caps->data) != 0) + bb_simple_perror_msg_and_die("capget"); +} diff --git a/util-linux/setpriv.c b/util-linux/setpriv.c index 9f2793949..12ab1bd66 100644 --- a/util-linux/setpriv.c +++ b/util-linux/setpriv.c @@ -124,48 +124,7 @@ enum { }; #if ENABLE_FEATURE_SETPRIV_CAPABILITIES -struct caps { - struct __user_cap_header_struct header; - cap_user_data_t data; - int u32s; -}; - -static void getcaps(struct caps *caps) -{ - static const uint8_t versions[] = { - _LINUX_CAPABILITY_U32S_3, /* = 2 (fits into byte) */ - _LINUX_CAPABILITY_U32S_2, /* = 2 */ - _LINUX_CAPABILITY_U32S_1, /* = 1 */ - }; - int i; - - caps->header.pid = 0; - for (i = 0; i < ARRAY_SIZE(versions); i++) { - caps->header.version = versions[i]; - if (capget(&caps->header, NULL) == 0) - goto got_it; - } - bb_simple_perror_msg_and_die("capget"); - got_it: - - switch (caps->header.version) { - case _LINUX_CAPABILITY_VERSION_1: - caps->u32s = _LINUX_CAPABILITY_U32S_1; - break; - case _LINUX_CAPABILITY_VERSION_2: - caps->u32s = _LINUX_CAPABILITY_U32S_2; - break; - case _LINUX_CAPABILITY_VERSION_3: - caps->u32s = _LINUX_CAPABILITY_U32S_3; - break; - default: - bb_error_msg_and_die("unsupported capability version"); - } - - caps->data = xmalloc(sizeof(caps->data[0]) * caps->u32s); - if (capget(&caps->header, caps->data) < 0) - bb_simple_perror_msg_and_die("capget"); -} +DEFINE_STRUCT_CAPS; static unsigned parse_cap(const char *cap) { @@ -195,7 +154,7 @@ static void set_inh_caps(char *capstring) cap = parse_cap(capstring); if (CAP_TO_INDEX(cap) >= caps.u32s) - bb_error_msg_and_die("invalid capability cap"); + bb_error_msg_and_die("invalid capability '%s'", capstring); if (capstring[0] == '+') caps.data[CAP_TO_INDEX(cap)].inheritable |= CAP_TO_MASK(cap); @@ -204,11 +163,8 @@ static void set_inh_caps(char *capstring) capstring = strtok(NULL, ","); } - if ((capset(&caps.header, caps.data)) < 0) + if (capset(&caps.header, caps.data) != 0) bb_perror_msg_and_die("capset"); - - if (ENABLE_FEATURE_CLEAN_UP) - free(caps.data); } static void set_ambient_caps(char *string) @@ -322,10 +278,9 @@ static int dump(void) bb_putchar('\n'); # endif - if (ENABLE_FEATURE_CLEAN_UP) { - IF_FEATURE_SETPRIV_CAPABILITIES(free(caps.data);) + if (ENABLE_FEATURE_CLEAN_UP) free(gids); - } + return EXIT_SUCCESS; } #endif /* FEATURE_SETPRIV_DUMP */ diff --git a/util-linux/switch_root.c b/util-linux/switch_root.c index fb6057a02..fe9ab68d0 100644 --- a/util-linux/switch_root.c +++ b/util-linux/switch_root.c @@ -26,20 +26,46 @@ //config: list of active mount points. That's why. //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 //usage:#define switch_root_trivial_usage -//usage: "[-c /dev/console] NEW_ROOT NEW_INIT [ARGS]" +//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" +//usage:#define run_init_trivial_usage +//usage: "[-d CAP,CAP...] [-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" + #include #include +#if ENABLE_RUN_INIT +# include +# include +// #include +// 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,6 +115,84 @@ 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; + + /* Get the current capability mask */ + getcaps(&caps); + + /* Drop the bit */ + caps.data[CAP_TO_INDEX(cap_idx)].inheritable &= ~CAP_TO_MASK(cap_idx); + + /* And drop the capability. */ + 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) { @@ -98,7 +202,25 @@ int switch_root_main(int argc UNUSED_PARAM, char **argv) dev_t rootdev; // Parse args (-c console). '+': stop at first non-option - getopt32(argv, "^+" "c:" "\0" "-2" /* minimum 2 args */, &console); + if (ENABLE_SWITCH_ROOT && (!ENABLE_RUN_INIT || applet_name[0] == 's')) { + getopt32(argv, "^+" + "c:" + "\0" "-2" /* minimum 2 args */, + &console + ); + } else { +#if ENABLE_RUN_INIT + char *cap_list = NULL; + getopt32(argv, "^+" + "c:d:" + "\0" "-2" /* minimum 2 args */, + &console, + &cap_list + ); + if (cap_list) + drop_capabilities(cap_list); +#endif + } argv += optind; newroot = *argv++;