Mike Waychison
2011-Jul-19 20:38 UTC
[klibc] [PATCH v1 0/2] Support dropping of capabilities from early userspace.
This patchset applies to klibc mainline. As is it will probably collide with Maximilian's recent patch to rename run-init to switch_root posted last week. To boot an untrusted environment with certain capabilities locked out, we'd like to be able to drop the capabilities up front from early userspace, before we actually transition onto the root volume. This patchset implements this by adding a "drop capabilities" ability to both kinit and run-init in the klibc package. For kinit, it now understands a new kernel command line option, "drop_capabilities" that specifies a comma separated list of capability names that should be dropped right before execing the next init binary on the next root device. run-init also has the ability to use this drop_capabilities function by specifying capabilities that should be dropped with a new command line flag, '-d'. Given that this patchset is meant to help secure boots, we treat any errors as total failure to boot by exiting the process with a failing exit code. Thanks, Mike Waychison Related discussions ================== - Thread discussing my wanting to compile out kernel interfaces that we do not want to expose to the userspace environment, with Alan Cox convincing me that I really just want to disable certain capabilities: https://lkml.org/lkml/2011/7/15/412 Patchset summary =============== syscalls: Add capset and capget run-init: Add drop_capabilities support.
Mike Waychison
2011-Jul-19 20:38 UTC
[klibc] [PATCH v1 1/2] syscalls: Add capset and capget
Add the capset and capget system calls to klibc so that userland can invoke them. Signed-off-by: Mike Waychison <mikew at google.com> --- usr/include/sys/capability.h | 10 ++++++++++ usr/klibc/SYSCALLS.def | 6 ++++++ usr/klibc/syscalls/syscommon.h | 1 + 3 files changed, 17 insertions(+), 0 deletions(-) create mode 100644 usr/include/sys/capability.h diff --git a/usr/include/sys/capability.h b/usr/include/sys/capability.h new file mode 100644 index 0000000..84ad419 --- /dev/null +++ b/usr/include/sys/capability.h @@ -0,0 +1,10 @@ +#ifndef _SYS_CAPABILITY_H +#define _SYS_CAPABILITY_H + +#include <klibc/extern.h> +#include <linux/capability.h> + +__extern int capget(cap_user_header_t hdrp, cap_user_data_t datap); +__extern int capset(cap_user_header_t hdrp, const cap_user_data_t datap); + +#endif /* _SYS_CAPABILITY_H */ diff --git a/usr/klibc/SYSCALLS.def b/usr/klibc/SYSCALLS.def index d3279c7..ee3ffa9 100644 --- a/usr/klibc/SYSCALLS.def +++ b/usr/klibc/SYSCALLS.def @@ -77,6 +77,12 @@ int setfsgid32,setfsgid::setfsgid(gid_t); int setresuid32,setresuid::setresuid(int, uid_t, uid_t, uid_t); /* + * POSIX Capabilities + */ +int capget(cap_user_header_t, cap_user_data_t); +int capset(cap_user_header_t, cap_user_data_t); + +/* * Filesystem-related system calls */ int mount(const char *, const char *, const char *, unsigned long, const void *); diff --git a/usr/klibc/syscalls/syscommon.h b/usr/klibc/syscalls/syscommon.h index 0acae12..78f8858 100644 --- a/usr/klibc/syscalls/syscommon.h +++ b/usr/klibc/syscalls/syscommon.h @@ -12,6 +12,7 @@ #include <poll.h> #include <sched.h> +#include <sys/capability.h> #include <sys/dirent.h> #include <sys/klog.h> #include <sys/mman.h>
Mike Waychison
2011-Jul-19 20:38 UTC
[klibc] [PATCH v1 2/2] run-init: Add drop_capabilities support.
This patch adds the ability to run-init to allow the dropping of POSIX capabilities. This works by adding a "-d" flag to run-init, which takes a comma separated list of capability names that should be dropped right before exec'ing the real init binary. kinit is also modified by this change, such that it understands the same argument when prepended with "drop_capabilities=" on the kernel command line. When processing capabilities to drop, CAP_SETPCAP is special cased to be dropped last, so that the order that capabilities are given does not cause dropping of later enumerated capabilities to fail if it is listed early on. Dropping of capabilities happens in three parts. We explicitly drop the capability from init's inherited, permitted and effective masks. We also drop the capability from the bounding set using PR_CAPBSET_DROP. Lastly, if available, we drop the capabilities from the bset and inheritted masks exposed at /proc/sys/kernel/usermodehelper if available (introduced in v3.0.0). In all paths, we treat errors as fatal, as we do not want to continue to boot if there was a problem dropping capabilities. The only exception to this rule is the handling of /proc/sys/kernel/usermodehelper, where we print out a warning if we notice that the kernel is new enough to support this interface, but could not find the proc file (as it may or may not be available after the pivot, depending on early portions of the boot strap process). Signed-off-by: Mike Waychison <mikew at google.com> --- usr/kinit/kinit.c | 4 - usr/kinit/run-init/Kbuild | 2 usr/kinit/run-init/capabilities.c | 278 +++++++++++++++++++++++++++++++++++++ usr/kinit/run-init/capabilities.h | 6 + usr/kinit/run-init/run-init.c | 11 + usr/kinit/run-init/run-init.h | 3 usr/kinit/run-init/runinitlib.c | 11 + 7 files changed, 307 insertions(+), 8 deletions(-) create mode 100644 usr/kinit/run-init/capabilities.c create mode 100644 usr/kinit/run-init/capabilities.h diff --git a/usr/kinit/kinit.c b/usr/kinit/kinit.c index 4a1f40b..ae50ed6 100644 --- a/usr/kinit/kinit.c +++ b/usr/kinit/kinit.c @@ -307,7 +307,9 @@ int main(int argc, char *argv[]) init_argv[0] = strrchr(init_path, '/') + 1; - errmsg = run_init("/root", "/dev/console", init_path, init_argv); + errmsg = run_init("/root", "/dev/console", + get_arg(cmdc, cmdv, "drop_capabilities="), + init_path, init_argv); /* If run_init returned, something went bad */ fprintf(stderr, "%s: %s: %s\n", progname, errmsg, strerror(errno)); diff --git a/usr/kinit/run-init/Kbuild b/usr/kinit/run-init/Kbuild index bf6e140..6451dd4 100644 --- a/usr/kinit/run-init/Kbuild +++ b/usr/kinit/run-init/Kbuild @@ -6,7 +6,7 @@ static-y := static/run-init shared-y := shared/run-init # common .o files -objs := run-init.o runinitlib.o +objs := run-init.o runinitlib.o capabilities.o # TODO - do we want a stripped version # TODO - do we want the static.g + shared.g directories? diff --git a/usr/kinit/run-init/capabilities.c b/usr/kinit/run-init/capabilities.c new file mode 100644 index 0000000..d262c01 --- /dev/null +++ b/usr/kinit/run-init/capabilities.c @@ -0,0 +1,278 @@ +/* + * Copyright 2011 Google Inc. All Rights Reserved + * Author: mikew at google.com (Mike Waychison) + */ + +/* + * We have to include the klibc types.h here to keep the kernel's + * types.h from being used. + */ +#include <sys/types.h> + +#include <linux/version.h> +#include <sys/capability.h> +#include <sys/prctl.h> +#include <sys/utsname.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "capabilities.h" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +#define MAKE_CAP(cap) [cap] = { .cap_name = #cap } + +struct capability { + const char *cap_name; +} capabilities[] = { + MAKE_CAP(CAP_CHOWN), + MAKE_CAP(CAP_DAC_OVERRIDE), + MAKE_CAP(CAP_DAC_READ_SEARCH), + MAKE_CAP(CAP_FOWNER), + MAKE_CAP(CAP_FSETID), + MAKE_CAP(CAP_KILL), + MAKE_CAP(CAP_SETGID), + MAKE_CAP(CAP_SETUID), + MAKE_CAP(CAP_SETPCAP), + MAKE_CAP(CAP_LINUX_IMMUTABLE), + MAKE_CAP(CAP_NET_BIND_SERVICE), + MAKE_CAP(CAP_NET_BROADCAST), + MAKE_CAP(CAP_NET_ADMIN), + MAKE_CAP(CAP_NET_RAW), + MAKE_CAP(CAP_IPC_LOCK), + MAKE_CAP(CAP_IPC_OWNER), + MAKE_CAP(CAP_SYS_MODULE), + MAKE_CAP(CAP_SYS_RAWIO), + MAKE_CAP(CAP_SYS_CHROOT), + MAKE_CAP(CAP_SYS_PTRACE), + MAKE_CAP(CAP_SYS_PACCT), + MAKE_CAP(CAP_SYS_ADMIN), + MAKE_CAP(CAP_SYS_BOOT), + MAKE_CAP(CAP_SYS_NICE), + MAKE_CAP(CAP_SYS_RESOURCE), + MAKE_CAP(CAP_SYS_TIME), + MAKE_CAP(CAP_SYS_TTY_CONFIG), + MAKE_CAP(CAP_MKNOD), + MAKE_CAP(CAP_LEASE), + MAKE_CAP(CAP_AUDIT_WRITE), + MAKE_CAP(CAP_AUDIT_CONTROL), + MAKE_CAP(CAP_SETFCAP), + MAKE_CAP(CAP_MAC_OVERRIDE), + MAKE_CAP(CAP_MAC_ADMIN), + MAKE_CAP(CAP_SYSLOG), +}; + +static void fail(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + exit(1); +} + +/* + * Returns the currently running kernel version X.Y.Z in a format + * compatible with the KERNEL_VERSION macro. + */ +static unsigned kernel_version(void) +{ + struct utsname utsname; + int ret; + unsigned char version, patchlevel, sublevel; + + ret = uname(&utsname); + if (ret != 0) + fail("uname returned %d\n", ret); + + ret = sscanf(utsname.release, "%hhu.%hhu.%hhu", + &version, &patchlevel, &sublevel); + if (ret != 3) { + /* Try two level name? */ + sublevel = 0; + ret = sscanf(utsname.release, "%hhu.%hhu", + &version, &patchlevel); + if (ret != 2) + fail("Couldn't parse kernel version \"%s\"\n", + utsname.release); + } + + return KERNEL_VERSION(version, patchlevel, sublevel); +} + +/* + * Find the capability ordinal by name, and return its ordinal. + * Returns -1 on failure. + */ +static int find_capability(const char *s) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(capabilities); i++) { + if (capabilities[i].cap_name + && strcasecmp(s, capabilities[i].cap_name) == 0) { + return i; + } + } + return -1; +} + +static void do_capset(int cap_ordinal) +{ + struct __user_cap_header_struct hdr; + struct __user_cap_data_struct caps[2]; + + /* Get the current capability mask */ + hdr.version = _LINUX_CAPABILITY_VERSION_3; + hdr.pid = getpid(); + if (capget(&hdr, caps)) { + perror("capget()"); + exit(1); + } + + /* Drop the bits */ + if (cap_ordinal < 32) { + caps[0].effective &= ~(1U << cap_ordinal); + caps[0].permitted &= ~(1U << cap_ordinal); + caps[0].inheritable &= ~(1U << cap_ordinal); + } else { + caps[1].effective &= ~(1U << (cap_ordinal - 32)); + caps[1].permitted &= ~(1U << (cap_ordinal - 32)); + caps[1].inheritable &= ~(1U << (cap_ordinal - 32)); + } + + /* And drop the capability. */ + hdr.version = _LINUX_CAPABILITY_VERSION_3; + hdr.pid = getpid(); + if (capset(&hdr, caps)) + fail("Couldn't drop the capability \"%s\"\n", + capabilities[cap_ordinal].cap_name); +} + +static void do_bset(int cap_ordinal) +{ + int ret; + + ret = prctl(PR_CAPBSET_READ, cap_ordinal); + if (ret == 1) { + ret = prctl(PR_CAPBSET_DROP, cap_ordinal); + if (ret != 0) + fail("Error dropping capability %s from bset\n", + capabilities[cap_ordinal].cap_name); + } else if (ret < 0) + fail("Kernel doesn't recognize capability %d\n", cap_ordinal); +} + +static void do_usermodehelper_file(const char *filename, int cap_ordinal) +{ + uint32_t lo32, hi32; + FILE *file; + static const size_t buf_size = 80; + char buf[buf_size]; + char tail; + size_t bytes_read; + int ret; + + /* Try and open the file */ + file = fopen(filename, "r+"); + if (!file && errno == ENOENT) { + /* Check if this kernel even supports this interface. */ + if (kernel_version() >= KERNEL_VERSION(3, 0, 0)) { + static int printed_once; + if (!printed_once++) + fprintf(stderr, "WARNING: Could not disable " + "capabilities for usermode helpers!\n"); + } + return; + } + if (!file) + fail("Failed to access file %s errno %d\n", filename, errno); + + /* Read and process the current bits */ + bytes_read = fread(buf, 1, buf_size - 1, file); + if (bytes_read == 0) + fail("Trouble reading %s\n", filename); + buf[bytes_read] = '\0'; + ret = sscanf(buf, "%u %u%c", &lo32, &hi32, &tail); + if (ret != 2) + fail("Failed to understand %s\n", filename); + + /* Clear the bits in the local copy */ + if (cap_ordinal < 32) + lo32 &= ~(1 << cap_ordinal); + else + hi32 &= ~(1 << (cap_ordinal - 32)); + + /* Commit the new bit masks to the kernel */ + sprintf(buf, "%u %u", lo32, hi32); + ret = fwrite(buf, 1, strlen(buf) + 1, file); + if (ret != strlen(buf) + 1) + fail("Failed to commit usermode helper bitmasks: %d\n", ret); + + /* Cleanup */ + fclose(file); +} + +static void do_usermodehelper(int cap_ordinal) +{ + static const char * const files[] = { + "/proc/sys/kernel/bset", + "/proc/sys/kernel/inheritable", + }; + int i; + + for (i = 0; i < ARRAY_SIZE(files); i++) + do_usermodehelper_file(files[i], cap_ordinal); +} + +static void drop_capability(int cap_ordinal) +{ + do_usermodehelper(cap_ordinal); + do_bset(cap_ordinal); + do_capset(cap_ordinal); + + printf("Dropped capability: %s\n", capabilities[cap_ordinal].cap_name); +} + +int do_capabilities(const char *drop_capabilities) +{ + char *s, *saveptr = NULL; + char *token; + int drop_setpcap = 0; + + if (!drop_capabilities) + return 0; + + /* Create a duplicate string that can be modified. */ + s = strdup(drop_capabilities); + if (!s) + fail("Failed to drop caps as requested. Exiting\n"); + + token = strtok_r(s, ",", &saveptr); + while (token) { + int cap_ordinal = find_capability(token); + + if (cap_ordinal < 0) + fail("Could not understand capability name \"%s\" " + "on command line, failing init\n", token); + + /* We handle CAP_SETPCAP last because it is needed to + * drop all other caps. */ + if (cap_ordinal == CAP_SETPCAP) + drop_setpcap = 1; + else + drop_capability(cap_ordinal); + + token = strtok_r(NULL, ",", &saveptr); + } + + if (drop_setpcap) + drop_capability(CAP_SETPCAP); + + free(s); + return 0; +} diff --git a/usr/kinit/run-init/capabilities.h b/usr/kinit/run-init/capabilities.h new file mode 100644 index 0000000..bf51eec --- /dev/null +++ b/usr/kinit/run-init/capabilities.h @@ -0,0 +1,6 @@ +#ifndef CAPABILITIES_H +#define CAPABILITIES_H + +int do_capabilities(const char *drop_capabilities); + +#endif /* CAPABILITIES_H */ diff --git a/usr/kinit/run-init/run-init.c b/usr/kinit/run-init/run-init.c index 0f150dd..cc602ef 100644 --- a/usr/kinit/run-init/run-init.c +++ b/usr/kinit/run-init/run-init.c @@ -35,6 +35,7 @@ * - Remounts /real-root onto the root filesystem; * - Chroots; * - Opens /dev/console; + * - Drops capabilities * - Spawns the specified init program (with arguments.) */ @@ -50,7 +51,8 @@ static const char *program; static void __attribute__ ((noreturn)) usage(void) { fprintf(stderr, - "Usage: exec %s [-c consoledev] /real-root /sbin/init [args]\n", + "Usage: exec %s [-c consoledev] [-d <CAP_NAME,...>] " + "/real-root /sbin/init [args]\n", program); exit(1); } @@ -62,6 +64,7 @@ int main(int argc, char *argv[]) const char *realroot; const char *init; const char *error; + const char *drop_capabilities = NULL; char **initargs; /* Variables... */ @@ -70,9 +73,11 @@ int main(int argc, char *argv[]) /* Parse the command line */ program = argv[0]; - while ((o = getopt(argc, argv, "c:")) != -1) { + while ((o = getopt(argc, argv, "c:d:")) != -1) { if (o == 'c') { console = optarg; + } else if (o == 'd') { + drop_capabilities = optarg; } else { usage(); } @@ -85,7 +90,7 @@ int main(int argc, char *argv[]) init = argv[optind + 1]; initargs = argv + optind + 1; - error = run_init(realroot, console, init, initargs); + error = run_init(realroot, console, drop_capabilities, init, initargs); /* If run_init returns, something went wrong */ fprintf(stderr, "%s: %s: %s\n", program, error, strerror(errno)); diff --git a/usr/kinit/run-init/run-init.h b/usr/kinit/run-init/run-init.h index a95328e..30f78bf 100644 --- a/usr/kinit/run-init/run-init.h +++ b/usr/kinit/run-init/run-init.h @@ -29,6 +29,7 @@ #define RUN_INIT_H const char *run_init(const char *realroot, const char *console, - const char *init, char **initargs); + const char *drop_capabilities, const char *init, + char **initargs); #endif diff --git a/usr/kinit/run-init/runinitlib.c b/usr/kinit/run-init/runinitlib.c index 8f1562f..a5cb10c 100644 --- a/usr/kinit/run-init/runinitlib.c +++ b/usr/kinit/run-init/runinitlib.c @@ -26,7 +26,7 @@ * ----------------------------------------------------------------------- */ /* - * run_init(consoledev, realroot, init, initargs) + * run_init(consoledev, realroot, drop_capabilities, init, initargs) * * This function should be called as the last thing in kinit, * from initramfs, it does the following: @@ -35,6 +35,7 @@ * - Remounts /real-root onto the root filesystem; * - Chroots; * - Opens /dev/console; + * - Drops capabilities if needed; * - Spawns the specified init program (with arguments.) * * On failure, returns a human-readable error message. @@ -52,7 +53,9 @@ #include <sys/stat.h> #include <sys/types.h> #include <sys/vfs.h> + #include "run-init.h" +#include "capabilities.h" /* Make it possible to compile on glibc by including constants that the always-behind shipped glibc headers may not include. Classic example @@ -154,7 +157,8 @@ static int nuke(const char *what) } const char *run_init(const char *realroot, const char *console, - const char *init, char **initargs) + const char *drop_capabilities, const char *init, + char **initargs) { struct stat rst, cst; struct statfs sfs; @@ -203,6 +207,9 @@ const char *run_init(const char *realroot, const char *console, dup2(confd, 2); close(confd); + /* Drop capabilities */ + do_capabilities(drop_capabilities); + /* Spawn init */ execv(init, initargs); return init; /* Failed to spawn init */
Maximilian Attems
2011-Jul-29 20:41 UTC
[klibc] [PATCH v1 1/2] syscalls: Add capset and capget
On Tue, 19 Jul 2011, Mike Waychison wrote:> Add the capset and capget system calls to klibc so that userland can > invoke them. > > Signed-off-by: Mike Waychison <mikew at google.com> > --- > usr/include/sys/capability.h | 10 ++++++++++ > usr/klibc/SYSCALLS.def | 6 ++++++ > usr/klibc/syscalls/syscommon.h | 1 + > 3 files changed, 17 insertions(+), 0 deletions(-) > create mode 100644 usr/include/sys/capability.h > > diff --git a/usr/include/sys/capability.h b/usr/include/sys/capability.h > new file mode 100644 > index 0000000..84ad419 > --- /dev/null > +++ b/usr/include/sys/capability.h > @@ -0,0 +1,10 @@ > +#ifndef _SYS_CAPABILITY_H > +#define _SYS_CAPABILITY_H > + > +#include <klibc/extern.h> > +#include <linux/capability.h> > + > +__extern int capget(cap_user_header_t hdrp, cap_user_data_t datap); > +__extern int capset(cap_user_header_t hdrp, const cap_user_data_t datap); > + > +#endif /* _SYS_CAPABILITY_H */ > diff --git a/usr/klibc/SYSCALLS.def b/usr/klibc/SYSCALLS.def > index d3279c7..ee3ffa9 100644 > --- a/usr/klibc/SYSCALLS.def > +++ b/usr/klibc/SYSCALLS.def > @@ -77,6 +77,12 @@ int setfsgid32,setfsgid::setfsgid(gid_t); > int setresuid32,setresuid::setresuid(int, uid_t, uid_t, uid_t); > > /* > + * POSIX Capabilities > + */ > +int capget(cap_user_header_t, cap_user_data_t); > +int capset(cap_user_header_t, cap_user_data_t); > + > +/* > * Filesystem-related system calls > */ > int mount(const char *, const char *, const char *, unsigned long, const void *); > diff --git a/usr/klibc/syscalls/syscommon.h b/usr/klibc/syscalls/syscommon.h > index 0acae12..78f8858 100644 > --- a/usr/klibc/syscalls/syscommon.h > +++ b/usr/klibc/syscalls/syscommon.h > @@ -12,6 +12,7 @@ > > #include <poll.h> > #include <sched.h> > +#include <sys/capability.h> > #include <sys/dirent.h> > #include <sys/klog.h> > #include <sys/mman.h>this looks good to me. thanks will add to klibc repo soonest. -- maks
Maximilian Attems
2011-Jul-29 23:06 UTC
[klibc] [PATCH v1 1/2] syscalls: Add capset and capget
On Tue, 19 Jul 2011, Mike Waychison wrote:> Add the capset and capget system calls to klibc so that userland can > invoke them. > > Signed-off-by: Mike Waychison <mikew at google.com> > --- > usr/include/sys/capability.h | 10 ++++++++++ > usr/klibc/SYSCALLS.def | 6 ++++++ > usr/klibc/syscalls/syscommon.h | 1 + > 3 files changed, 17 insertions(+), 0 deletions(-) > create mode 100644 usr/include/sys/capability.h >applied and pushed. thank you. -- maks