From 51201235db9dad9fe1823d9de46ed90f5e160fd0 Mon Sep 17 00:00:00 2001 From: Etienne CHAMPETIER Date: Wed, 26 Aug 2015 23:26:45 +0000 Subject: jail: add capabilities support If there is one or more capabilities in cap.keep, drop all capabilities not in cap.keep. Always drop all capabalities in cap.drop exemple json syntax: { "cap.keep": [ "cap_net_raw" ], "cap.drop": [] } Signed-off-by: Etienne CHAMPETIER --- CMakeLists.txt | 18 +++++--- jail/capabilities.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++++ jail/capabilities.h | 14 ++++++ jail/jail.c | 15 +++++-- make_capabilities_h.sh | 10 +++++ 5 files changed, 164 insertions(+), 9 deletions(-) create mode 100644 jail/capabilities.c create mode 100644 jail/capabilities.h create mode 100755 make_capabilities_h.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 805e2ed..cc1e4a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,7 +67,14 @@ ADD_CUSTOM_COMMAND( COMMAND ./make_syscall_h.sh ${CMAKE_C_COMPILER} > ./syscall-names.h DEPENDS ./make_syscall_h.sh ) -ADD_CUSTOM_TARGET(headers DEPENDS syscall-names.h) +ADD_CUSTOM_TARGET(syscall-names-h DEPENDS syscall-names.h) + +ADD_CUSTOM_COMMAND( + OUTPUT capabilities-names.h + COMMAND ./make_capabilities_h.sh ${CMAKE_C_COMPILER} > ./capabilities-names.h + DEPENDS ./make_capabilities_h.sh +) +ADD_CUSTOM_TARGET(capabilities-names-h DEPENDS capabilities-names.h) IF(SECCOMP_SUPPORT) ADD_LIBRARY(preload-seccomp SHARED jail/preload.c jail/seccomp.c) @@ -75,15 +82,16 @@ TARGET_LINK_LIBRARIES(preload-seccomp dl ubox blobmsg_json) INSTALL(TARGETS preload-seccomp LIBRARY DESTINATION lib ) -ADD_DEPENDENCIES(preload-seccomp headers) +ADD_DEPENDENCIES(preload-seccomp syscall-names-h) endif() IF(JAIL_SUPPORT) -ADD_EXECUTABLE(ujail jail/jail.c jail/elf.c) -TARGET_LINK_LIBRARIES(ujail ubox) +ADD_EXECUTABLE(ujail jail/jail.c jail/elf.c jail/capabilities.c) +TARGET_LINK_LIBRARIES(ujail ubox blobmsg_json) INSTALL(TARGETS ujail RUNTIME DESTINATION sbin ) +ADD_DEPENDENCIES(ujail capabilities-names-h) endif() IF(UTRACE_SUPPORT) @@ -92,7 +100,7 @@ TARGET_LINK_LIBRARIES(utrace ubox ${json} blobmsg_json) INSTALL(TARGETS utrace RUNTIME DESTINATION sbin ) -ADD_DEPENDENCIES(utrace headers) +ADD_DEPENDENCIES(utrace syscall-names-h) ADD_LIBRARY(preload-trace SHARED trace/preload.c) TARGET_LINK_LIBRARIES(preload-trace dl) diff --git a/jail/capabilities.c b/jail/capabilities.c new file mode 100644 index 0000000..b5ea965 --- /dev/null +++ b/jail/capabilities.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2015 Etienne CHAMPETIER + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define _GNU_SOURCE 1 +#include +#include + +#include +#include + +#include "log.h" +#include "../capabilities-names.h" +#include "capabilities.h" + +static int find_capabilities(const char *name) +{ + int i; + + for (i = 0; i <= CAP_LAST_CAP; i++) + if (capabilities_names[i] && !strcmp(capabilities_names[i], name)) + return i; + + return -1; +} + +int drop_capabilities(const char *file) +{ + enum { + CAP_KEEP, + CAP_DROP, + __CAP_MAX + }; + static const struct blobmsg_policy policy[__CAP_MAX] = { + [CAP_KEEP] = { .name = "cap.keep", .type = BLOBMSG_TYPE_ARRAY }, + [CAP_DROP] = { .name = "cap.drop", .type = BLOBMSG_TYPE_ARRAY }, + }; + struct blob_buf b = { 0 }; + struct blob_attr *tb[__CAP_MAX]; + struct blob_attr *cur; + int rem, cap; + char *name; + uint64_t capdrop = 0LLU; + + DEBUG("dropping capabilities\n"); + + blob_buf_init(&b, 0); + if (!blobmsg_add_json_from_file(&b, file)) { + ERROR("failed to load %s\n", file); + return -1; + } + + blobmsg_parse(policy, __CAP_MAX, tb, blob_data(b.head), blob_len(b.head)); + if (!tb[CAP_KEEP] && !tb[CAP_DROP]) { + ERROR("failed to parse %s\n", file); + return -1; + } + + blobmsg_for_each_attr(cur, tb[CAP_KEEP], rem) { + name = blobmsg_get_string(cur); + if (!name) { + ERROR("invalid capability name in cap.keep\n"); + return -1; + } + cap = find_capabilities(name); + if (cap == -1) { + ERROR("unknown capability %s in cap.keep\n", name); + return -1; + } + capdrop |= (1LLU << cap); + } + + if (capdrop == 0LLU) { + DEBUG("cap.keep empty -> only dropping capabilities from cap.drop (blacklist)\n"); + capdrop = 0xffffffffffffffffLLU; + } else { + DEBUG("cap.keep has at least one capability -> dropping every capabilities not in cap.keep (whitelist)\n"); + } + + blobmsg_for_each_attr(cur, tb[CAP_DROP], rem) { + name = blobmsg_get_string(cur); + if (!name) { + ERROR("invalid capability name in cap.drop\n"); + return -1; + } + cap = find_capabilities(name); + if (cap == -1) { + ERROR("unknown capability %s in cap.drop\n", name); + return -1; + } + capdrop &= ~(1LLU << cap); + } + + for (cap = 0; cap <= CAP_LAST_CAP; cap++) { + if ( (capdrop & (1LLU << cap)) == 0) { + DEBUG("dropping capability %s (%d)\n", capabilities_names[cap], cap); + if (prctl(PR_CAPBSET_DROP, cap, 0, 0, 0)) { + ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s\n", cap, strerror(errno)); + return errno; + } + } else { + DEBUG("keeping capability %s (%d)\n", capabilities_names[cap], cap); + } + } + + return 0; +} diff --git a/jail/capabilities.h b/jail/capabilities.h new file mode 100644 index 0000000..e6699e9 --- /dev/null +++ b/jail/capabilities.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2015 Etienne CHAMPETIER + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +int drop_capabilities(const char *file); diff --git a/jail/jail.c b/jail/jail.c index f8139b8..3d0830e 100644 --- a/jail/jail.c +++ b/jail/jail.c @@ -37,19 +37,21 @@ #include #include "elf.h" +#include "capabilities.h" #include #include #include #define STACK_SIZE (1024 * 1024) -#define OPT_ARGS "P:S:n:r:w:d:psulo" +#define OPT_ARGS "P:S:C:n:r:w:d:psulo" static struct { char *path; char *name; char **jail_argv; char *seccomp; + char *capabilities; int procfs; int ronly; int sysfs; @@ -243,6 +245,7 @@ static void usage(void) fprintf(stderr, "ujail -- \n"); fprintf(stderr, " -P \tpath where the jail will be staged\n"); fprintf(stderr, " -S \tseccomp filter\n"); + fprintf(stderr, " -C \tcapabilities drop config\n"); fprintf(stderr, " -n \tthe name of the jail\n"); fprintf(stderr, " -r \treadonly files that should be staged\n"); fprintf(stderr, " -w \twriteable files that should be staged\n"); @@ -255,7 +258,7 @@ static void usage(void) fprintf(stderr, "\nWarning: by default root inside the jail is the same\n\ and he has the same powers as root outside the jail,\n\ thus he can escape the jail and/or break stuff.\n\ -Please use an appropriate seccomp filter (-S) to restrict his powers\n"); +Please use an appropriate seccomp/capabilities filter (-S/-C) to restrict his powers\n"); } static int spawn_jail(void *arg) @@ -273,8 +276,8 @@ static int spawn_jail(void *arg) if (!envp) exit(EXIT_FAILURE); - //TODO: drop capabilities() here - //prctl(PR_CAPBSET_DROP, ..., 0, 0, 0); + if (opts.capabilities && drop_capabilities(opts.capabilities)) + exit(EXIT_FAILURE); INFO("exec-ing %s\n", *opts.jail_argv); execve(*opts.jail_argv, opts.jail_argv, envp); @@ -354,6 +357,10 @@ int main(int argc, char **argv) opts.seccomp = optarg; add_extra(optarg, 1); break; + case 'C': + opts.capabilities = optarg; + add_extra(optarg, 1); + break; case 'P': opts.path = optarg; break; diff --git a/make_capabilities_h.sh b/make_capabilities_h.sh new file mode 100755 index 0000000..635e740 --- /dev/null +++ b/make_capabilities_h.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +CC=$1 +[ -n "$TARGET_CC_NOCACHE" ] && CC=$TARGET_CC_NOCACHE + +echo "#include " +echo "static const char *capabilities_names[] = {" +echo "#include " | ${CC} -E -dM - | grep '#define CAP' | grep -vE '(CAP_TO|CAP_LAST_CAP)' | \ + awk '{print $3" "$2}' | sort -n | awk '{print " ["$1"]\t= \""tolower($2)"\","}' +echo "};" -- cgit v1.2.3