From a103496ca585e22bb5e386e3238b468d133f5659 Mon Sep 17 00:00:00 2001 From: Ismo Puustinen Date: Fri, 8 Jan 2016 00:00:04 +0200 Subject: [PATCH 1/4] capabilities: keep bounding set in non-inverted format. Change the capability bounding set parser and logic so that the bounding set is kept as a positive set internally. This means that the set reflects those capabilities that we want to keep instead of drop. --- src/basic/capability-util.c | 16 +++++------ src/basic/capability-util.h | 12 ++++++-- src/core/dbus-execute.c | 4 +-- src/core/execute.c | 9 +++--- src/core/execute.h | 2 +- src/core/load-fragment-gperf.gperf.m4 | 2 +- src/core/load-fragment.c | 26 +++++++++-------- src/core/load-fragment.h | 2 +- src/core/main.c | 10 +++---- src/core/unit.c | 2 +- src/import/import-common.c | 4 +-- src/nspawn/nspawn.c | 2 +- src/test/test-unit-file.c | 41 ++++++++++++++------------- 13 files changed, 71 insertions(+), 61 deletions(-) diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index fef722b6f2..881f0f671e 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -96,7 +96,7 @@ unsigned long cap_last_cap(void) { return p; } -int capability_bounding_set_drop(uint64_t drop, bool right_now) { +int capability_bounding_set_drop(uint64_t keep, bool right_now) { _cleanup_cap_free_ cap_t after_cap = NULL; cap_flag_value_t fv; unsigned long i; @@ -137,7 +137,7 @@ int capability_bounding_set_drop(uint64_t drop, bool right_now) { for (i = 0; i <= cap_last_cap(); i++) { - if (drop & ((uint64_t) 1ULL << (uint64_t) i)) { + if (!(keep & (UINT64_C(1) << i))) { cap_value_t v; /* Drop it from the bounding set */ @@ -176,7 +176,7 @@ finish: return r; } -static int drop_from_file(const char *fn, uint64_t drop) { +static int drop_from_file(const char *fn, uint64_t keep) { int r, k; uint32_t hi, lo; uint64_t current, after; @@ -196,7 +196,7 @@ static int drop_from_file(const char *fn, uint64_t drop) { return -EIO; current = (uint64_t) lo | ((uint64_t) hi << 32ULL); - after = current & ~drop; + after = current & keep; if (current == after) return 0; @@ -213,14 +213,14 @@ static int drop_from_file(const char *fn, uint64_t drop) { return r; } -int capability_bounding_set_drop_usermode(uint64_t drop) { +int capability_bounding_set_drop_usermode(uint64_t keep) { int r; - r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", drop); + r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", keep); if (r < 0) return r; - r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", drop); + r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", keep); if (r < 0) return r; @@ -257,7 +257,7 @@ int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) { return log_error_errno(errno, "Failed to disable keep capabilities flag: %m"); /* Drop all caps from the bounding set, except the ones we want */ - r = capability_bounding_set_drop(~keep_capabilities, true); + r = capability_bounding_set_drop(keep_capabilities, true); if (r < 0) return log_error_errno(r, "Failed to drop capabilities: %m"); diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index 6bbf7318fd..f6a48b7916 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -29,10 +29,12 @@ #include "macro.h" #include "util.h" +#define CAP_ALL (uint64_t) -1 + unsigned long cap_last_cap(void); int have_effective_cap(int value); -int capability_bounding_set_drop(uint64_t drop, bool right_now); -int capability_bounding_set_drop_usermode(uint64_t drop); +int capability_bounding_set_drop(uint64_t keep, bool right_now); +int capability_bounding_set_drop_usermode(uint64_t keep); int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities); @@ -46,3 +48,9 @@ static inline void cap_free_charpp(char **p) { cap_free(*p); } #define _cleanup_cap_free_charp_ _cleanup_(cap_free_charpp) + +static inline bool cap_test_all(uint64_t caps) { + uint64_t m; + m = (UINT64_C(1) << (cap_last_cap() + 1)) - 1; + return (caps & m) == m; +} diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 1f736b2686..825cef7694 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -293,9 +293,7 @@ static int property_get_capability_bounding_set( assert(reply); assert(c); - /* We store this negated internally, to match the kernel, but - * we expose it normalized. */ - return sd_bus_message_append(reply, "t", ~c->capability_bounding_set_drop); + return sd_bus_message_append(reply, "t", c->capability_bounding_set); } static int property_get_capabilities( diff --git a/src/core/execute.c b/src/core/execute.c index 9b76861919..7aeb5f1144 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1866,8 +1866,8 @@ static int exec_child( } } - if (context->capability_bounding_set_drop) { - r = capability_bounding_set_drop(context->capability_bounding_set_drop, false); + if (!cap_test_all(context->capability_bounding_set)) { + r = capability_bounding_set_drop(context->capability_bounding_set, false); if (r < 0) { *exit_status = EXIT_CAPABILITIES; return r; @@ -2114,6 +2114,7 @@ void exec_context_init(ExecContext *c) { c->timer_slack_nsec = NSEC_INFINITY; c->personality = PERSONALITY_INVALID; c->runtime_directory_mode = 0755; + c->capability_bounding_set = CAP_ALL; } void exec_context_done(ExecContext *c) { @@ -2517,12 +2518,12 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { (c->secure_bits & 1<secure_bits & 1<capability_bounding_set_drop) { + if (c->capability_bounding_set != CAP_ALL) { unsigned long l; fprintf(f, "%sCapabilityBoundingSet:", prefix); for (l = 0; l <= cap_last_cap(); l++) - if (!(c->capability_bounding_set_drop & ((uint64_t) 1ULL << (uint64_t) l))) + if (c->capability_bounding_set & (UINT64_C(1) << l)) fprintf(f, " %s", strna(capability_to_name(l))); fputs("\n", f); diff --git a/src/core/execute.h b/src/core/execute.h index be5be9f531..9d2cdb8728 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -155,7 +155,7 @@ struct ExecContext { char **read_write_dirs, **read_only_dirs, **inaccessible_dirs; unsigned long mount_flags; - uint64_t capability_bounding_set_drop; + uint64_t capability_bounding_set; cap_t capabilities; int secure_bits; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 0408b9a829..862cb8dd3a 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -47,7 +47,7 @@ $1.SyslogLevel, config_parse_log_level, 0, $1.SyslogLevelPrefix, config_parse_bool, 0, offsetof($1, exec_context.syslog_level_prefix) $1.Capabilities, config_parse_exec_capabilities, 0, offsetof($1, exec_context) $1.SecureBits, config_parse_exec_secure_bits, 0, offsetof($1, exec_context) -$1.CapabilityBoundingSet, config_parse_bounding_set, 0, offsetof($1, exec_context.capability_bounding_set_drop) +$1.CapabilityBoundingSet, config_parse_capability_set, 0, offsetof($1, exec_context.capability_bounding_set) $1.TimerSlackNSec, config_parse_nsec, 0, offsetof($1, exec_context.timer_slack_nsec) $1.NoNewPrivileges, config_parse_no_new_privileges, 0, offsetof($1, exec_context) m4_ifdef(`HAVE_SECCOMP', diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index cb553e1252..da14337ba8 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -38,6 +38,7 @@ #include "bus-internal.h" #include "bus-util.h" #include "cap-list.h" +#include "capability-util.h" #include "cgroup.h" #include "conf-parser.h" #include "cpu-set-util.h" @@ -1024,7 +1025,7 @@ int config_parse_exec_secure_bits(const char *unit, return 0; } -int config_parse_bounding_set( +int config_parse_capability_set( const char *unit, const char *filename, unsigned line, @@ -1036,8 +1037,8 @@ int config_parse_bounding_set( void *data, void *userdata) { - uint64_t *capability_bounding_set_drop = data; - uint64_t capability_bounding_set, sum = 0; + uint64_t *capability_set = data; + uint64_t sum = 0, initial = 0; bool invert = false; const char *p; @@ -1051,10 +1052,8 @@ int config_parse_bounding_set( rvalue++; } - /* Note that we store this inverted internally, since the - * kernel wants it like this. But we actually expose it - * non-inverted everywhere to have a fully normalized - * interface. */ + if (strcmp(lvalue, "CapabilityBoundingSet") == 0) + initial = CAP_ALL; /* initialized to all bits on */ p = rvalue; for (;;) { @@ -1080,11 +1079,14 @@ int config_parse_bounding_set( sum |= ((uint64_t) UINT64_C(1)) << (uint64_t) cap; } - capability_bounding_set = invert ? ~sum : sum; - if (*capability_bounding_set_drop != 0 && capability_bounding_set != 0) - *capability_bounding_set_drop = ~(~*capability_bounding_set_drop | capability_bounding_set); + sum = invert ? ~sum : sum; + + if (sum == 0 || *capability_set == initial) + /* "" or uninitialized data -> replace */ + *capability_set = sum; else - *capability_bounding_set_drop = ~capability_bounding_set; + /* previous data -> merge */ + *capability_set |= sum; return 0; } @@ -4002,7 +4004,7 @@ void unit_dump_config_items(FILE *f) { { config_parse_log_level, "LEVEL" }, { config_parse_exec_capabilities, "CAPABILITIES" }, { config_parse_exec_secure_bits, "SECUREBITS" }, - { config_parse_bounding_set, "BOUNDINGSET" }, + { config_parse_capability_set, "BOUNDINGSET" }, { config_parse_limit, "LIMIT" }, { config_parse_unit_deps, "UNIT [...]" }, { config_parse_exec, "PATH [ARGUMENT [...]]" }, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index a451fc164a..f0027a6b43 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -56,7 +56,7 @@ int config_parse_exec_cpu_sched_prio(const char *unit, const char *filename, uns int config_parse_exec_cpu_affinity(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_exec_capabilities(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_exec_secure_bits(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_bounding_set(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_capability_set(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_bytes_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_sec_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/core/main.c b/src/core/main.c index f9de54028e..7a428fcccf 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -117,7 +117,7 @@ static usec_t arg_runtime_watchdog = 0; static usec_t arg_shutdown_watchdog = 10 * USEC_PER_MINUTE; static char **arg_default_environment = NULL; static struct rlimit *arg_default_rlimit[_RLIMIT_MAX] = {}; -static uint64_t arg_capability_bounding_set_drop = 0; +static uint64_t arg_capability_bounding_set = CAP_ALL; static nsec_t arg_timer_slack_nsec = NSEC_INFINITY; static usec_t arg_default_timer_accuracy_usec = 1 * USEC_PER_MINUTE; static Set* arg_syscall_archs = NULL; @@ -644,7 +644,7 @@ static int parse_config_file(void) { { "Manager", "JoinControllers", config_parse_join_controllers, 0, &arg_join_controllers }, { "Manager", "RuntimeWatchdogSec", config_parse_sec, 0, &arg_runtime_watchdog }, { "Manager", "ShutdownWatchdogSec", config_parse_sec, 0, &arg_shutdown_watchdog }, - { "Manager", "CapabilityBoundingSet", config_parse_bounding_set, 0, &arg_capability_bounding_set_drop }, + { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, #ifdef HAVE_SECCOMP { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, #endif @@ -1631,14 +1631,14 @@ int main(int argc, char *argv[]) { if (prctl(PR_SET_TIMERSLACK, arg_timer_slack_nsec) < 0) log_error_errno(errno, "Failed to adjust timer slack: %m"); - if (arg_capability_bounding_set_drop) { - r = capability_bounding_set_drop_usermode(arg_capability_bounding_set_drop); + if (!cap_test_all(arg_capability_bounding_set)) { + r = capability_bounding_set_drop_usermode(arg_capability_bounding_set); if (r < 0) { log_emergency_errno(r, "Failed to drop capability bounding set of usermode helpers: %m"); error_message = "Failed to drop capability bounding set of usermode helpers"; goto finish; } - r = capability_bounding_set_drop(arg_capability_bounding_set_drop, true); + r = capability_bounding_set_drop(arg_capability_bounding_set, true); if (r < 0) { log_emergency_errno(r, "Failed to drop capability bounding set: %m"); error_message = "Failed to drop capability bounding set"; diff --git a/src/core/unit.c b/src/core/unit.c index b977ac7f0c..e1bc6c75cb 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -3231,7 +3231,7 @@ int unit_patch_contexts(Unit *u) { ec->no_new_privileges = true; if (ec->private_devices) - ec->capability_bounding_set_drop |= (uint64_t) 1ULL << (uint64_t) CAP_MKNOD; + ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD); } cc = unit_get_cgroup_context(u); diff --git a/src/import/import-common.c b/src/import/import-common.c index a8551ca9e8..8a48bd7bf9 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -134,7 +134,7 @@ int import_fork_tar_x(const char *path, pid_t *ret) { if (unshare(CLONE_NEWNET) < 0) log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); - r = capability_bounding_set_drop(~retain, true); + r = capability_bounding_set_drop(retain, true); if (r < 0) log_error_errno(r, "Failed to drop capabilities, ignoring: %m"); @@ -208,7 +208,7 @@ int import_fork_tar_c(const char *path, pid_t *ret) { if (unshare(CLONE_NEWNET) < 0) log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); - r = capability_bounding_set_drop(~retain, true); + r = capability_bounding_set_drop(retain, true); if (r < 0) log_error_errno(r, "Failed to drop capabilities, ignoring: %m"); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index a4e13bd6aa..d619206dd6 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1482,7 +1482,7 @@ static int setup_journal(const char *directory) { } static int drop_capabilities(void) { - return capability_bounding_set_drop(~arg_retain, false); + return capability_bounding_set_drop(arg_retain, false); } static int reset_audit_loginuid(void) { diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c index 0b3630f77c..cd1e4e4698 100644 --- a/src/test/test-unit-file.c +++ b/src/test/test-unit-file.c @@ -28,6 +28,7 @@ #include #include "alloc-util.h" +#include "capability-util.h" #include "fd-util.h" #include "fileio.h" #include "hashmap.h" @@ -625,8 +626,8 @@ static uint64_t make_cap(int cap) { return ((uint64_t) 1ULL << (uint64_t) cap); } -static void test_config_parse_bounding_set(void) { - /* int config_parse_bounding_set( +static void test_config_parse_capability_set(void) { + /* int config_parse_capability_set( const char *unit, const char *filename, unsigned line, @@ -638,38 +639,38 @@ static void test_config_parse_bounding_set(void) { void *data, void *userdata) */ int r; - uint64_t capability_bounding_set_drop = 0; + uint64_t capability_bounding_set = 0; - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, "CAP_NET_RAW", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == ~make_cap(CAP_NET_RAW)); + assert_se(capability_bounding_set == make_cap(CAP_NET_RAW)); - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, "CAP_NET_ADMIN", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == ~(make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); + assert_se(capability_bounding_set == (make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, "", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == ~((uint64_t) 0ULL)); + assert_se(capability_bounding_set == UINT64_C(0)); - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, "~", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == (uint64_t) 0ULL); + assert_se(cap_test_all(capability_bounding_set)); - capability_bounding_set_drop = 0; - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + capability_bounding_set = 0; + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, " 'CAP_NET_RAW' WAT_CAP??? CAP_NET_ADMIN CAP'_trailing_garbage", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == ~(make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); + assert_se(capability_bounding_set == (make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); } static void test_config_parse_rlimit(void) { @@ -829,7 +830,7 @@ int main(int argc, char *argv[]) { r = test_unit_file_get_set(); test_config_parse_exec(); - test_config_parse_bounding_set(); + test_config_parse_capability_set(); test_config_parse_rlimit(); test_config_parse_pass_environ(); test_load_env_file_1(); From 755d4b67a471ed1a3472b8536cb51315d4e4e3c1 Mon Sep 17 00:00:00 2001 From: Ismo Puustinen Date: Thu, 31 Dec 2015 14:54:44 +0200 Subject: [PATCH 2/4] capabilities: added support for ambient capabilities. This patch adds support for ambient capabilities in service files. The idea with ambient capabilities is that the execed processes can run with non-root user and get some inherited capabilities, without having any need to add the capabilities to the executable file. You need at least Linux 4.3 to use ambient capabilities. SecureBit keep-caps is automatically added when you use ambient capabilities and wish to change the user. An example system service file might look like this: [Unit] Description=Service for testing caps [Service] ExecStart=/usr/bin/sleep 10000 User=nobody AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW After starting the service it has these capabilities: CapInh: 0000000000003000 CapPrm: 0000000000003000 CapEff: 0000000000003000 CapBnd: 0000003fffffffff CapAmb: 0000000000003000 --- src/basic/capability-util.c | 55 ++++++++++++++++ src/basic/capability-util.h | 3 + src/basic/missing.h | 16 +++++ src/core/dbus-execute.c | 19 ++++++ src/core/execute.c | 91 ++++++++++++++++++++++----- src/core/execute.h | 2 + src/core/load-fragment-gperf.gperf.m4 | 1 + src/core/load-fragment.c | 3 +- 8 files changed, 173 insertions(+), 17 deletions(-) diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index 881f0f671e..49c2d61afe 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -96,6 +96,61 @@ unsigned long cap_last_cap(void) { return p; } +int capability_update_inherited_set(cap_t caps, uint64_t set) { + unsigned long i; + + /* Add capabilities in the set to the inherited caps. Do not apply + * them yet. */ + + for (i = 0; i < cap_last_cap(); i++) { + + if (set & (UINT64_C(1) << i)) { + cap_value_t v; + + v = (cap_value_t) i; + + /* Make the capability inheritable. */ + if (cap_set_flag(caps, CAP_INHERITABLE, 1, &v, CAP_SET) < 0) + return -errno; + } + } + + return 0; +} + +int capability_ambient_set_apply(uint64_t set, bool also_inherit) { + unsigned long i; + _cleanup_cap_free_ cap_t caps = NULL; + + /* Add the capabilities to the ambient set. */ + + if (also_inherit) { + int r; + caps = cap_get_proc(); + if (!caps) + return -errno; + + r = capability_update_inherited_set(caps, set); + if (r < 0) + return -errno; + + if (cap_set_proc(caps) < 0) + return -errno; + } + + for (i = 0; i < cap_last_cap(); i++) { + + if (set & (UINT64_C(1) << i)) { + + /* Add the capability to the ambient set. */ + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0) < 0) + return -errno; + } + } + + return 0; +} + int capability_bounding_set_drop(uint64_t keep, bool right_now) { _cleanup_cap_free_ cap_t after_cap = NULL; cap_flag_value_t fv; diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index f6a48b7916..be41475441 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -36,6 +36,9 @@ int have_effective_cap(int value); int capability_bounding_set_drop(uint64_t keep, bool right_now); int capability_bounding_set_drop_usermode(uint64_t keep); +int capability_ambient_set_apply(uint64_t set, bool also_inherit); +int capability_update_inherited_set(cap_t caps, uint64_t ambient_set); + int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities); int drop_capability(cap_value_t cv); diff --git a/src/basic/missing.h b/src/basic/missing.h index 880e724cb4..2d2785bead 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -1129,3 +1129,19 @@ static inline key_serial_t request_key(const char *type, const char *description #ifndef KEY_SPEC_USER_KEYRING #define KEY_SPEC_USER_KEYRING -4 #endif + +#ifndef PR_CAP_AMBIENT +#define PR_CAP_AMBIENT 47 +#endif + +#ifndef PR_CAP_AMBIENT_IS_SET +#define PR_CAP_AMBIENT_IS_SET 1 +#endif + +#ifndef PR_CAP_AMBIENT_RAISE +#define PR_CAP_AMBIENT_RAISE 2 +#endif + +#ifndef PR_CAP_AMBIENT_CLEAR_ALL +#define PR_CAP_AMBIENT_CLEAR_ALL 4 +#endif diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 825cef7694..c2238c8c43 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -296,6 +296,24 @@ static int property_get_capability_bounding_set( return sd_bus_message_append(reply, "t", c->capability_bounding_set); } +static int property_get_ambient_capabilities( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "t", c->capability_ambient_set); +} + static int property_get_capabilities( sd_bus *bus, const char *path, @@ -687,6 +705,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("Capabilities", "s", property_get_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CapabilityBoundingSet", "t", property_get_capability_bounding_set, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST), diff --git a/src/core/execute.c b/src/core/execute.c index 7aeb5f1144..ac91568b63 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -737,12 +737,7 @@ static int enforce_user(const ExecContext *context, uid_t uid) { /* Sets (but doesn't lookup) the uid and make sure we keep the * capabilities while doing so. */ - if (context->capabilities) { - _cleanup_cap_free_ cap_t d = NULL; - static const cap_value_t bits[] = { - CAP_SETUID, /* Necessary so that we can run setresuid() below */ - CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */ - }; + if (context->capabilities || context->capability_ambient_set != 0) { /* First step: If we need to keep capabilities but * drop privileges we need to make sure we keep our @@ -758,16 +753,24 @@ static int enforce_user(const ExecContext *context, uid_t uid) { /* Second step: set the capabilities. This will reduce * the capabilities to the minimum we need. */ - d = cap_dup(context->capabilities); - if (!d) - return -errno; + if (context->capabilities) { + _cleanup_cap_free_ cap_t d = NULL; + static const cap_value_t bits[] = { + CAP_SETUID, /* Necessary so that we can run setresuid() below */ + CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */ + }; - if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 || - cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0) - return -errno; + d = cap_dup(context->capabilities); + if (!d) + return -errno; - if (cap_set_proc(d) < 0) - return -errno; + if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 || + cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0) + return -errno; + + if (cap_set_proc(d) < 0) + return -errno; + } } /* Third step: actually set the uids */ @@ -1856,6 +1859,8 @@ static int exec_child( if (params->apply_permissions) { + int secure_bits = context->secure_bits; + for (i = 0; i < _RLIMIT_MAX; i++) { if (!context->rlimit[i]) continue; @@ -1874,20 +1879,63 @@ static int exec_child( } } + /* This is done before enforce_user, but ambient set + * does not survive over setresuid() if keep_caps is not set. */ + if (context->capability_ambient_set != 0) { + r = capability_ambient_set_apply(context->capability_ambient_set, true); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + + if (context->capabilities) { + + /* The capabilities in ambient set need to be also in the inherited + * set. If they aren't, trying to get them will fail. Add the ambient + * set inherited capabilities to the capability set in the context. + * This is needed because if capabilities are set (using "Capabilities=" + * keyword), they will override whatever we set now. */ + + r = capability_update_inherited_set(context->capabilities, context->capability_ambient_set); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + } + } + if (context->user) { r = enforce_user(context, uid); if (r < 0) { *exit_status = EXIT_USER; return r; } + if (context->capability_ambient_set != 0) { + + /* Fix the ambient capabilities after user change. */ + r = capability_ambient_set_apply(context->capability_ambient_set, false); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + + /* If we were asked to change user and ambient capabilities + * were requested, we had to add keep-caps to the securebits + * so that we would maintain the inherited capability set + * through the setresuid(). Make sure that the bit is added + * also to the context secure_bits so that we don't try to + * drop the bit away next. */ + + secure_bits |= 1<secure_bits) - if (prctl(PR_SET_SECUREBITS, context->secure_bits) < 0) { + if (prctl(PR_GET_SECUREBITS) != secure_bits) + if (prctl(PR_SET_SECUREBITS, secure_bits) < 0) { *exit_status = EXIT_SECUREBITS; return -errno; } @@ -2529,6 +2577,17 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { fputs("\n", f); } + if (c->capability_ambient_set != 0) { + unsigned long l; + fprintf(f, "%sAmbientCapabilities:", prefix); + + for (l = 0; l <= cap_last_cap(); l++) + if (c->capability_ambient_set & (UINT64_C(1) << l)) + fprintf(f, " %s", strna(capability_to_name(l))); + + fputs("\n", f); + } + if (c->user) fprintf(f, "%sUser: %s\n", prefix, c->user); if (c->group) diff --git a/src/core/execute.h b/src/core/execute.h index 9d2cdb8728..8649620830 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -157,6 +157,8 @@ struct ExecContext { uint64_t capability_bounding_set; + uint64_t capability_ambient_set; + cap_t capabilities; int secure_bits; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 862cb8dd3a..29ab1b6b9e 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -48,6 +48,7 @@ $1.SyslogLevelPrefix, config_parse_bool, 0, $1.Capabilities, config_parse_exec_capabilities, 0, offsetof($1, exec_context) $1.SecureBits, config_parse_exec_secure_bits, 0, offsetof($1, exec_context) $1.CapabilityBoundingSet, config_parse_capability_set, 0, offsetof($1, exec_context.capability_bounding_set) +$1.AmbientCapabilities, config_parse_capability_set, 0, offsetof($1, exec_context.capability_ambient_set) $1.TimerSlackNSec, config_parse_nsec, 0, offsetof($1, exec_context.timer_slack_nsec) $1.NoNewPrivileges, config_parse_no_new_privileges, 0, offsetof($1, exec_context) m4_ifdef(`HAVE_SECCOMP', diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index da14337ba8..d3880b4e3c 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -1054,6 +1054,7 @@ int config_parse_capability_set( if (strcmp(lvalue, "CapabilityBoundingSet") == 0) initial = CAP_ALL; /* initialized to all bits on */ + /* else "AmbientCapabilities" initialized to all bits off */ p = rvalue; for (;;) { @@ -1072,7 +1073,7 @@ int config_parse_capability_set( cap = capability_from_name(word); if (cap < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability in bounding set, ignoring: %s", word); + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability in bounding/ambient set, ignoring: %s", word); continue; } From 70d7aea5c7270764ee71d6828e76402001afed13 Mon Sep 17 00:00:00 2001 From: Ismo Puustinen Date: Tue, 5 Jan 2016 13:34:41 +0200 Subject: [PATCH 3/4] tests: test ambient capabilities. The ambient capability tests are only run if the kernel has support for ambient capabilities. --- src/test/test-capability.c | 68 ++++++++++++++++++- src/test/test-execute.c | 16 +++++ .../exec-capabilityambientset-merge.service | 9 +++ .../exec-capabilityambientset.service | 8 +++ 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 test/test-execute/exec-capabilityambientset-merge.service create mode 100644 test/test-execute/exec-capabilityambientset.service diff --git a/src/test/test-capability.c b/src/test/test-capability.c index fc8d3ffe0d..629bb63c81 100644 --- a/src/test/test-capability.c +++ b/src/test/test-capability.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -66,8 +67,9 @@ static void show_capabilities(void) { cap_free(text); } -static int setup_tests(void) { +static int setup_tests(bool *run_ambient) { struct passwd *nobody; + int r; nobody = getpwnam("nobody"); if (!nobody) { @@ -77,6 +79,18 @@ static int setup_tests(void) { test_uid = nobody->pw_uid; test_gid = nobody->pw_gid; + *run_ambient = false; + + r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); + + /* There's support for PR_CAP_AMBIENT if the prctl() call + * succeeded or error code was something else than EINVAL. The + * EINVAL check should be good enough to rule out false + * positives. */ + + if (r >= 0 || errno != EINVAL) + *run_ambient = true; + return 0; } @@ -140,8 +154,53 @@ static void test_have_effective_cap(void) { assert_se(!have_effective_cap(CAP_CHOWN)); } +static void test_update_inherited_set(void) { + cap_t caps; + uint64_t set = 0; + cap_flag_value_t fv; + + caps = cap_get_proc(); + assert_se(caps); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert(fv == CAP_CLEAR); + + set = (UINT64_C(1) << CAP_CHOWN); + + assert_se(!capability_update_inherited_set(caps, set)); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert(fv == CAP_SET); + + cap_free(caps); +} + +static void test_set_ambient_caps(void) { + cap_t caps; + uint64_t set = 0; + cap_flag_value_t fv; + + caps = cap_get_proc(); + assert_se(caps); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert(fv == CAP_CLEAR); + cap_free(caps); + + assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 0); + + set = (UINT64_C(1) << CAP_CHOWN); + + assert_se(!capability_ambient_set_apply(set, true)); + + caps = cap_get_proc(); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert(fv == CAP_SET); + cap_free(caps); + + assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 1); +} + int main(int argc, char *argv[]) { int r; + bool run_ambient; log_parse_environment(); log_open(); @@ -149,14 +208,19 @@ int main(int argc, char *argv[]) { if (getuid() != 0) return EXIT_TEST_SKIP; - r = setup_tests(); + r = setup_tests(&run_ambient); if (r < 0) return -r; show_capabilities(); test_drop_privileges(); + test_update_inherited_set(); + fork_test(test_have_effective_cap); + if (run_ambient) + fork_test(test_set_ambient_caps); + return 0; } diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 753afadb0a..92857cb5e2 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "fileio.h" @@ -224,6 +225,20 @@ static void test_exec_capabilityboundingset(Manager *m) { test(m, "exec-capabilityboundingset-invert.service", 0, CLD_EXITED); } +static void test_exec_capabilityambientset(Manager *m) { + int r; + + /* Check if the kernel has support for ambient capabilities. Run + * the tests only if that's the case. Clearing all ambient + * capabilities is fine, since we are expecting them to be unset + * in the first place for the tests. */ + r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); + if (r >= 0 || errno != EINVAL) { + test(m, "exec-capabilityambientset.service", 0, CLD_EXITED); + test(m, "exec-capabilityambientset-merge.service", 0, CLD_EXITED); + } +} + static void test_exec_privatenetwork(Manager *m) { int r; @@ -266,6 +281,7 @@ int main(int argc, char *argv[]) { test_exec_umask, test_exec_runtimedirectory, test_exec_capabilityboundingset, + test_exec_capabilityambientset, test_exec_oomscoreadjust, test_exec_ioschedulingclass, NULL, diff --git a/test/test-execute/exec-capabilityambientset-merge.service b/test/test-execute/exec-capabilityambientset-merge.service new file mode 100644 index 0000000000..64964380e2 --- /dev/null +++ b/test/test-execute/exec-capabilityambientset-merge.service @@ -0,0 +1,9 @@ +[Unit] +Description=Test for AmbientCapabilities + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000003000"' +Type=oneshot +User=nobody +AmbientCapabilities=CAP_NET_ADMIN +AmbientCapabilities=CAP_NET_RAW diff --git a/test/test-execute/exec-capabilityambientset.service b/test/test-execute/exec-capabilityambientset.service new file mode 100644 index 0000000000..d63f884ef8 --- /dev/null +++ b/test/test-execute/exec-capabilityambientset.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for AmbientCapabilities + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000003000"' +Type=oneshot +User=nobody +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW From ece87975a97509b48a01b1e3da2e99c1c7dfd77a Mon Sep 17 00:00:00 2001 From: Ismo Puustinen Date: Mon, 11 Jan 2016 09:36:14 +0200 Subject: [PATCH 4/4] man: add AmbientCapabilities entry. --- man/systemd.exec.xml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 5f98ef163c..f0f77c5091 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -806,6 +806,35 @@ settings. + + AmbientCapabilities= + + Controls which capabilities to include in the + ambient capability set for the executed process. Takes a + whitespace-separated list of capability names as read by + cap_from_name3, + e.g. CAP_SYS_ADMIN, + CAP_DAC_OVERRIDE, + CAP_SYS_PTRACE. This option may appear more than + once in which case the ambient capability sets are merged. + If the list of capabilities is prefixed with ~, all + but the listed capabilities will be included, the effect of the + assignment inverted. If the empty string is + assigned to this option, the ambient capability set is reset to + the empty capability set, and all prior settings have no effect. + If set to ~ (without any further argument), the + ambient capability set is reset to the full set of available + capabilities, also undoing any previous settings. Note that adding + capabilities to ambient capability set adds them to the process's + inherited capability set. + + Ambient capability sets are useful if you want to execute a process + as a non-privileged user but still want to give it some capabilities. + Note that in this case option keep-caps is + automatically added to SecureBits= to retain the + capabilities over the user change. + + SecureBits= Controls the secure bits set for the executed