|
|
|
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;
|