Blob Blame History Raw
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;