You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
16 KiB
397 lines
16 KiB
From ab2c6236a959fe53109cc36f3642b3a7a2051746 Mon Sep 17 00:00:00 2001 |
|
From: Ismo Puustinen <ismo.puustinen@intel.com> |
|
Date: Thu, 31 Dec 2015 14:54:44 +0200 |
|
Subject: [PATCH] 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 |
|
|
|
Cherry-picked from: 755d4b6 |
|
Resolves: #1387398 |
|
--- |
|
src/core/dbus-execute.c | 19 ++++++ |
|
src/core/execute.c | 90 ++++++++++++++++++++++----- |
|
src/core/execute.h | 2 + |
|
src/core/load-fragment-gperf.gperf.m4 | 1 + |
|
src/core/load-fragment.c | 4 +- |
|
src/shared/capability.c | 55 ++++++++++++++++ |
|
src/shared/capability.h | 3 + |
|
src/shared/missing.h | 16 +++++ |
|
src/test/test-unit-file.c | 1 + |
|
9 files changed, 174 insertions(+), 17 deletions(-) |
|
|
|
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c |
|
index a564c53fae..817ef80d1f 100644 |
|
--- a/src/core/dbus-execute.c |
|
+++ b/src/core/dbus-execute.c |
|
@@ -327,6 +327,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, |
|
@@ -637,6 +655,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 40db11e284..4265b9c348 100644 |
|
--- a/src/core/execute.c |
|
+++ b/src/core/execute.c |
|
@@ -696,12 +696,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 |
|
@@ -717,16 +712,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 */ |
|
@@ -1723,6 +1726,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; |
|
@@ -1750,6 +1755,30 @@ static int exec_child( |
|
} |
|
} |
|
#endif |
|
+ /* 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); |
|
@@ -1757,14 +1786,32 @@ static int exec_child( |
|
*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_KEEP_CAPS; |
|
+ } |
|
} |
|
|
|
/* PR_GET_SECUREBITS is not privileged, while |
|
* PR_SET_SECUREBITS is. So to suppress |
|
* potential EPERMs we'll try not to call |
|
* PR_SET_SECUREBITS unless necessary. */ |
|
- if (prctl(PR_GET_SECUREBITS) != context->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; |
|
} |
|
@@ -2431,6 +2478,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 40f7b794c5..00bf99cbea 100644 |
|
--- a/src/core/execute.h |
|
+++ b/src/core/execute.h |
|
@@ -152,6 +152,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 e4ce292100..f996032cf2 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 dbaaf2fee7..7d1ac6c251 100644 |
|
--- a/src/core/load-fragment.c |
|
+++ b/src/core/load-fragment.c |
|
@@ -61,6 +61,7 @@ |
|
#include "af-list.h" |
|
#include "cap-list.h" |
|
#include "bus-internal.h" |
|
+#include "capability.h" |
|
|
|
#ifdef HAVE_SECCOMP |
|
#include "seccomp-util.h" |
|
@@ -1044,6 +1045,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 (;;) { |
|
@@ -1062,7 +1064,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; |
|
} |
|
|
|
diff --git a/src/shared/capability.c b/src/shared/capability.c |
|
index 3ed31df5ab..6e3d7d22e0 100644 |
|
--- a/src/shared/capability.c |
|
+++ b/src/shared/capability.c |
|
@@ -98,6 +98,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/shared/capability.h b/src/shared/capability.h |
|
index 04cd6e54e2..76a7568561 100644 |
|
--- a/src/shared/capability.h |
|
+++ b/src/shared/capability.h |
|
@@ -36,6 +36,9 @@ int capability_bounding_set_drop_usermode(uint64_t keep); |
|
|
|
int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilites); |
|
|
|
+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_capability(cap_value_t cv); |
|
|
|
DEFINE_TRIVIAL_CLEANUP_FUNC(cap_t, cap_free); |
|
diff --git a/src/shared/missing.h b/src/shared/missing.h |
|
index 4b36a9c93a..a7771bc996 100644 |
|
--- a/src/shared/missing.h |
|
+++ b/src/shared/missing.h |
|
@@ -1004,3 +1004,19 @@ static inline int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, uns |
|
#ifndef KCMP_FILE |
|
#define KCMP_FILE 0 |
|
#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/test/test-unit-file.c b/src/test/test-unit-file.c |
|
index 38ecfe972e..cfa3d23166 100644 |
|
--- a/src/test/test-unit-file.c |
|
+++ b/src/test/test-unit-file.c |
|
@@ -38,6 +38,7 @@ |
|
#include "strv.h" |
|
#include "fileio.h" |
|
#include "test-helper.h" |
|
+#include "capability.h" |
|
|
|
static int test_unit_file_get_set(void) { |
|
int r;
|
|
|