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