a19bc6
From ab2c6236a959fe53109cc36f3642b3a7a2051746 Mon Sep 17 00:00:00 2001
a19bc6
From: Ismo Puustinen <ismo.puustinen@intel.com>
a19bc6
Date: Thu, 31 Dec 2015 14:54:44 +0200
a19bc6
Subject: [PATCH] capabilities: added support for ambient capabilities.
a19bc6
a19bc6
This patch adds support for ambient capabilities in service files. The
a19bc6
idea with ambient capabilities is that the execed processes can run with
a19bc6
non-root user and get some inherited capabilities, without having any
a19bc6
need to add the capabilities to the executable file.
a19bc6
a19bc6
You need at least Linux 4.3 to use ambient capabilities. SecureBit
a19bc6
keep-caps is automatically added when you use ambient capabilities and
a19bc6
wish to change the user.
a19bc6
a19bc6
An example system service file might look like this:
a19bc6
a19bc6
[Unit]
a19bc6
Description=Service for testing caps
a19bc6
a19bc6
[Service]
a19bc6
ExecStart=/usr/bin/sleep 10000
a19bc6
User=nobody
a19bc6
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW
a19bc6
a19bc6
After starting the service it has these capabilities:
a19bc6
a19bc6
CapInh: 0000000000003000
a19bc6
CapPrm: 0000000000003000
a19bc6
CapEff: 0000000000003000
a19bc6
CapBnd: 0000003fffffffff
a19bc6
CapAmb: 0000000000003000
a19bc6
a19bc6
Cherry-picked from: 755d4b6
a19bc6
Resolves: #1387398
a19bc6
---
23b3cf
 src/core/dbus-execute.c               | 19 ++++++
23b3cf
 src/core/execute.c                    | 90 ++++++++++++++++++++++-----
a19bc6
 src/core/execute.h                    |  2 +
a19bc6
 src/core/load-fragment-gperf.gperf.m4 |  1 +
a19bc6
 src/core/load-fragment.c              |  4 +-
23b3cf
 src/shared/capability.c               | 55 ++++++++++++++++
23b3cf
 src/shared/capability.h               |  3 +
23b3cf
 src/shared/missing.h                  | 16 +++++
a19bc6
 src/test/test-unit-file.c             |  1 +
a19bc6
 9 files changed, 174 insertions(+), 17 deletions(-)
a19bc6
a19bc6
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
c62b8e
index a564c53fae..817ef80d1f 100644
a19bc6
--- a/src/core/dbus-execute.c
a19bc6
+++ b/src/core/dbus-execute.c
a19bc6
@@ -327,6 +327,24 @@ static int property_get_capability_bounding_set(
a19bc6
         return sd_bus_message_append(reply, "t", c->capability_bounding_set);
a19bc6
 }
a19bc6
 
a19bc6
+static int property_get_ambient_capabilities(
a19bc6
+                sd_bus *bus,
a19bc6
+                const char *path,
a19bc6
+                const char *interface,
a19bc6
+                const char *property,
a19bc6
+                sd_bus_message *reply,
a19bc6
+                void *userdata,
a19bc6
+                sd_bus_error *error) {
a19bc6
+
a19bc6
+        ExecContext *c = userdata;
a19bc6
+
a19bc6
+        assert(bus);
a19bc6
+        assert(reply);
a19bc6
+        assert(c);
a19bc6
+
a19bc6
+        return sd_bus_message_append(reply, "t", c->capability_ambient_set);
a19bc6
+}
a19bc6
+
a19bc6
 static int property_get_capabilities(
a19bc6
                 sd_bus *bus,
a19bc6
                 const char *path,
a19bc6
@@ -637,6 +655,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
a19bc6
         SD_BUS_PROPERTY("Capabilities", "s", property_get_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST),
a19bc6
         SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST),
a19bc6
         SD_BUS_PROPERTY("CapabilityBoundingSet", "t", property_get_capability_bounding_set, 0, SD_BUS_VTABLE_PROPERTY_CONST),
a19bc6
+        SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST),
a19bc6
         SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST),
a19bc6
         SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST),
a19bc6
         SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),
a19bc6
diff --git a/src/core/execute.c b/src/core/execute.c
c62b8e
index 40db11e284..4265b9c348 100644
a19bc6
--- a/src/core/execute.c
a19bc6
+++ b/src/core/execute.c
a19bc6
@@ -696,12 +696,7 @@ static int enforce_user(const ExecContext *context, uid_t uid) {
a19bc6
         /* Sets (but doesn't lookup) the uid and make sure we keep the
a19bc6
          * capabilities while doing so. */
a19bc6
 
a19bc6
-        if (context->capabilities) {
a19bc6
-                _cleanup_cap_free_ cap_t d = NULL;
a19bc6
-                static const cap_value_t bits[] = {
a19bc6
-                        CAP_SETUID,   /* Necessary so that we can run setresuid() below */
a19bc6
-                        CAP_SETPCAP   /* Necessary so that we can set PR_SET_SECUREBITS later on */
a19bc6
-                };
a19bc6
+        if (context->capabilities || context->capability_ambient_set != 0) {
a19bc6
 
a19bc6
                 /* First step: If we need to keep capabilities but
a19bc6
                  * drop privileges we need to make sure we keep our
a19bc6
@@ -717,16 +712,24 @@ static int enforce_user(const ExecContext *context, uid_t uid) {
a19bc6
                 /* Second step: set the capabilities. This will reduce
a19bc6
                  * the capabilities to the minimum we need. */
a19bc6
 
a19bc6
-                d = cap_dup(context->capabilities);
a19bc6
-                if (!d)
a19bc6
-                        return -errno;
a19bc6
+                if (context->capabilities) {
a19bc6
+                        _cleanup_cap_free_ cap_t d = NULL;
a19bc6
+                        static const cap_value_t bits[] = {
a19bc6
+                                CAP_SETUID,   /* Necessary so that we can run setresuid() below */
a19bc6
+                                CAP_SETPCAP   /* Necessary so that we can set PR_SET_SECUREBITS later on */
a19bc6
+                        };
a19bc6
 
a19bc6
-                if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 ||
a19bc6
-                    cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0)
a19bc6
-                        return -errno;
a19bc6
+                        d = cap_dup(context->capabilities);
a19bc6
+                        if (!d)
a19bc6
+                                return -errno;
a19bc6
 
a19bc6
-                if (cap_set_proc(d) < 0)
a19bc6
-                        return -errno;
a19bc6
+                        if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 ||
a19bc6
+                            cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0)
a19bc6
+                                return -errno;
a19bc6
+
a19bc6
+                        if (cap_set_proc(d) < 0)
a19bc6
+                                return -errno;
a19bc6
+                }
a19bc6
         }
a19bc6
 
a19bc6
         /* Third step: actually set the uids */
a19bc6
@@ -1723,6 +1726,8 @@ static int exec_child(
a19bc6
 
a19bc6
         if (params->apply_permissions) {
a19bc6
 
a19bc6
+                int secure_bits = context->secure_bits;
a19bc6
+
a19bc6
                 for (i = 0; i < _RLIMIT_MAX; i++) {
a19bc6
                         if (!context->rlimit[i])
a19bc6
                                 continue;
a19bc6
@@ -1750,6 +1755,30 @@ static int exec_child(
a19bc6
                         }
a19bc6
                 }
a19bc6
 #endif
a19bc6
+                /* This is done before enforce_user, but ambient set
a19bc6
+                 * does not survive over setresuid() if keep_caps is not set. */
a19bc6
+                if (context->capability_ambient_set != 0) {
a19bc6
+                        r = capability_ambient_set_apply(context->capability_ambient_set, true);
a19bc6
+                        if (r < 0) {
a19bc6
+                                *exit_status = EXIT_CAPABILITIES;
a19bc6
+                                return r;
a19bc6
+                        }
a19bc6
+
a19bc6
+                        if (context->capabilities) {
a19bc6
+
a19bc6
+                                /* The capabilities in ambient set need to be also in the inherited
a19bc6
+                                 * set. If they aren't, trying to get them will fail. Add the ambient
a19bc6
+                                 * set inherited capabilities to the capability set in the context.
a19bc6
+                                 * This is needed because if capabilities are set (using "Capabilities="
a19bc6
+                                 * keyword), they will override whatever we set now. */
a19bc6
+
a19bc6
+                                r = capability_update_inherited_set(context->capabilities, context->capability_ambient_set);
a19bc6
+                                if (r < 0) {
a19bc6
+                                        *exit_status = EXIT_CAPABILITIES;
a19bc6
+                                        return r;
a19bc6
+                                }
a19bc6
+                        }
a19bc6
+                }
a19bc6
 
a19bc6
                 if (context->user) {
a19bc6
                         r = enforce_user(context, uid);
a19bc6
@@ -1757,14 +1786,32 @@ static int exec_child(
a19bc6
                                 *exit_status = EXIT_USER;
a19bc6
                                 return r;
a19bc6
                         }
a19bc6
+                        if (context->capability_ambient_set != 0) {
a19bc6
+
a19bc6
+                                /* Fix the ambient capabilities after user change. */
a19bc6
+                                r = capability_ambient_set_apply(context->capability_ambient_set, false);
a19bc6
+                                if (r < 0) {
a19bc6
+                                        *exit_status = EXIT_CAPABILITIES;
a19bc6
+                                        return r;
a19bc6
+                                }
a19bc6
+
a19bc6
+                                /* If we were asked to change user and ambient capabilities
a19bc6
+                                 * were requested, we had to add keep-caps to the securebits
a19bc6
+                                 * so that we would maintain the inherited capability set
a19bc6
+                                 * through the setresuid(). Make sure that the bit is added
a19bc6
+                                 * also to the context secure_bits so that we don't try to
a19bc6
+                                 * drop the bit away next. */
a19bc6
+
a19bc6
+                                 secure_bits |= 1<
a19bc6
+                        }
a19bc6
                 }
a19bc6
 
a19bc6
                 /* PR_GET_SECUREBITS is not privileged, while
a19bc6
                  * PR_SET_SECUREBITS is. So to suppress
a19bc6
                  * potential EPERMs we'll try not to call
a19bc6
                  * PR_SET_SECUREBITS unless necessary. */
a19bc6
-                if (prctl(PR_GET_SECUREBITS) != context->secure_bits)
a19bc6
-                        if (prctl(PR_SET_SECUREBITS, context->secure_bits) < 0) {
a19bc6
+                if (prctl(PR_GET_SECUREBITS) != secure_bits)
a19bc6
+                        if (prctl(PR_SET_SECUREBITS, secure_bits) < 0) {
a19bc6
                                 *exit_status = EXIT_SECUREBITS;
a19bc6
                                 return -errno;
a19bc6
                         }
a19bc6
@@ -2431,6 +2478,17 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
a19bc6
                 fputs("\n", f);
a19bc6
         }
a19bc6
 
a19bc6
+        if (c->capability_ambient_set != 0) {
a19bc6
+                unsigned long l;
a19bc6
+                fprintf(f, "%sAmbientCapabilities:", prefix);
a19bc6
+
a19bc6
+                for (l = 0; l <= cap_last_cap(); l++)
a19bc6
+                        if (c->capability_ambient_set & (UINT64_C(1) << l))
a19bc6
+                                fprintf(f, " %s", strna(capability_to_name(l)));
a19bc6
+
a19bc6
+                fputs("\n", f);
a19bc6
+        }
a19bc6
+
a19bc6
         if (c->user)
a19bc6
                 fprintf(f, "%sUser: %s\n", prefix, c->user);
a19bc6
         if (c->group)
a19bc6
diff --git a/src/core/execute.h b/src/core/execute.h
c62b8e
index 40f7b794c5..00bf99cbea 100644
a19bc6
--- a/src/core/execute.h
a19bc6
+++ b/src/core/execute.h
a19bc6
@@ -152,6 +152,8 @@ struct ExecContext {
a19bc6
 
a19bc6
         uint64_t capability_bounding_set;
a19bc6
 
a19bc6
+        uint64_t capability_ambient_set;
a19bc6
+
a19bc6
         cap_t capabilities;
a19bc6
         int secure_bits;
a19bc6
 
a19bc6
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
c62b8e
index e4ce292100..f996032cf2 100644
a19bc6
--- a/src/core/load-fragment-gperf.gperf.m4
a19bc6
+++ b/src/core/load-fragment-gperf.gperf.m4
a19bc6
@@ -48,6 +48,7 @@ $1.SyslogLevelPrefix,            config_parse_bool,                  0,
a19bc6
 $1.Capabilities,                 config_parse_exec_capabilities,     0,                             offsetof($1, exec_context)
a19bc6
 $1.SecureBits,                   config_parse_exec_secure_bits,      0,                             offsetof($1, exec_context)
a19bc6
 $1.CapabilityBoundingSet,        config_parse_capability_set,        0,                             offsetof($1, exec_context.capability_bounding_set)
a19bc6
+$1.AmbientCapabilities,          config_parse_capability_set,        0,                             offsetof($1, exec_context.capability_ambient_set)
a19bc6
 $1.TimerSlackNSec,               config_parse_nsec,                  0,                             offsetof($1, exec_context.timer_slack_nsec)
a19bc6
 $1.NoNewPrivileges,              config_parse_no_new_privileges,     0,                             offsetof($1, exec_context)
a19bc6
 m4_ifdef(`HAVE_SECCOMP',
a19bc6
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
c62b8e
index dbaaf2fee7..7d1ac6c251 100644
a19bc6
--- a/src/core/load-fragment.c
a19bc6
+++ b/src/core/load-fragment.c
a19bc6
@@ -61,6 +61,7 @@
a19bc6
 #include "af-list.h"
a19bc6
 #include "cap-list.h"
a19bc6
 #include "bus-internal.h"
a19bc6
+#include "capability.h"
a19bc6
 
a19bc6
 #ifdef HAVE_SECCOMP
a19bc6
 #include "seccomp-util.h"
a19bc6
@@ -1044,6 +1045,7 @@ int config_parse_capability_set(
a19bc6
 
a19bc6
         if (strcmp(lvalue, "CapabilityBoundingSet") == 0)
a19bc6
                 initial = CAP_ALL; /* initialized to all bits on */
a19bc6
+        /* else "AmbientCapabilities" initialized to all bits off */
a19bc6
 
a19bc6
         p = rvalue;
a19bc6
         for (;;) {
a19bc6
@@ -1062,7 +1064,7 @@ int config_parse_capability_set(
a19bc6
 
a19bc6
                 cap = capability_from_name(word);
a19bc6
                 if (cap < 0) {
a19bc6
-                        log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability in bounding set, ignoring: %s", word);
a19bc6
+                        log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability in bounding/ambient set, ignoring: %s", word);
a19bc6
                         continue;
a19bc6
                 }
a19bc6
 
a19bc6
diff --git a/src/shared/capability.c b/src/shared/capability.c
c62b8e
index 3ed31df5ab..6e3d7d22e0 100644
a19bc6
--- a/src/shared/capability.c
a19bc6
+++ b/src/shared/capability.c
a19bc6
@@ -98,6 +98,61 @@ unsigned long cap_last_cap(void) {
a19bc6
         return p;
a19bc6
 }
a19bc6
 
a19bc6
+int capability_update_inherited_set(cap_t caps, uint64_t set) {
a19bc6
+        unsigned long i;
a19bc6
+
a19bc6
+        /* Add capabilities in the set to the inherited caps. Do not apply
a19bc6
+         * them yet. */
a19bc6
+
a19bc6
+        for (i = 0; i < cap_last_cap(); i++) {
a19bc6
+
a19bc6
+                if (set & (UINT64_C(1) << i)) {
a19bc6
+                        cap_value_t v;
a19bc6
+
a19bc6
+                        v = (cap_value_t) i;
a19bc6
+
a19bc6
+                        /* Make the capability inheritable. */
a19bc6
+                        if (cap_set_flag(caps, CAP_INHERITABLE, 1, &v, CAP_SET) < 0)
a19bc6
+                                return -errno;
a19bc6
+                }
a19bc6
+        }
a19bc6
+
a19bc6
+        return 0;
a19bc6
+}
a19bc6
+
a19bc6
+int capability_ambient_set_apply(uint64_t set, bool also_inherit) {
a19bc6
+        unsigned long i;
a19bc6
+        _cleanup_cap_free_ cap_t caps = NULL;
a19bc6
+
a19bc6
+        /* Add the capabilities to the ambient set. */
a19bc6
+
a19bc6
+        if (also_inherit) {
a19bc6
+                int r;
a19bc6
+                caps = cap_get_proc();
a19bc6
+                if (!caps)
a19bc6
+                        return -errno;
a19bc6
+
a19bc6
+                r = capability_update_inherited_set(caps, set);
a19bc6
+                if (r < 0)
a19bc6
+                        return -errno;
a19bc6
+
a19bc6
+                if (cap_set_proc(caps) < 0)
a19bc6
+                        return -errno;
a19bc6
+        }
a19bc6
+
a19bc6
+        for (i = 0; i < cap_last_cap(); i++) {
a19bc6
+
a19bc6
+                if (set & (UINT64_C(1) << i)) {
a19bc6
+
a19bc6
+                        /* Add the capability to the ambient set. */
a19bc6
+                        if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0) < 0)
a19bc6
+                                return -errno;
a19bc6
+                }
a19bc6
+        }
a19bc6
+
a19bc6
+        return 0;
a19bc6
+}
a19bc6
+
a19bc6
 int capability_bounding_set_drop(uint64_t keep, bool right_now) {
a19bc6
         _cleanup_cap_free_ cap_t after_cap = NULL;
a19bc6
         cap_flag_value_t fv;
a19bc6
diff --git a/src/shared/capability.h b/src/shared/capability.h
c62b8e
index 04cd6e54e2..76a7568561 100644
a19bc6
--- a/src/shared/capability.h
a19bc6
+++ b/src/shared/capability.h
a19bc6
@@ -36,6 +36,9 @@ int capability_bounding_set_drop_usermode(uint64_t keep);
a19bc6
 
a19bc6
 int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilites);
a19bc6
 
a19bc6
+int capability_ambient_set_apply(uint64_t set, bool also_inherit);
a19bc6
+int capability_update_inherited_set(cap_t caps, uint64_t ambient_set);
a19bc6
+
a19bc6
 int drop_capability(cap_value_t cv);
a19bc6
 
a19bc6
 DEFINE_TRIVIAL_CLEANUP_FUNC(cap_t, cap_free);
a19bc6
diff --git a/src/shared/missing.h b/src/shared/missing.h
c62b8e
index 4b36a9c93a..a7771bc996 100644
a19bc6
--- a/src/shared/missing.h
a19bc6
+++ b/src/shared/missing.h
a19bc6
@@ -1004,3 +1004,19 @@ static inline int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, uns
a19bc6
 #ifndef KCMP_FILE
a19bc6
 #define KCMP_FILE 0
a19bc6
 #endif
a19bc6
+
a19bc6
+#ifndef PR_CAP_AMBIENT
a19bc6
+#define PR_CAP_AMBIENT 47
a19bc6
+#endif
a19bc6
+
a19bc6
+#ifndef PR_CAP_AMBIENT_IS_SET
a19bc6
+#define PR_CAP_AMBIENT_IS_SET 1
a19bc6
+#endif
a19bc6
+
a19bc6
+#ifndef PR_CAP_AMBIENT_RAISE
a19bc6
+#define PR_CAP_AMBIENT_RAISE 2
a19bc6
+#endif
a19bc6
+
a19bc6
+#ifndef PR_CAP_AMBIENT_CLEAR_ALL
a19bc6
+#define PR_CAP_AMBIENT_CLEAR_ALL 4
a19bc6
+#endif
a19bc6
diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c
c62b8e
index 38ecfe972e..cfa3d23166 100644
a19bc6
--- a/src/test/test-unit-file.c
a19bc6
+++ b/src/test/test-unit-file.c
a19bc6
@@ -38,6 +38,7 @@
a19bc6
 #include "strv.h"
a19bc6
 #include "fileio.h"
a19bc6
 #include "test-helper.h"
a19bc6
+#include "capability.h"
a19bc6
 
a19bc6
 static int test_unit_file_get_set(void) {
a19bc6
         int r;