]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nspawn: don't hard fail when setting capabilities
authorAnita Zhang <the.anitazha@gmail.com>
Mon, 3 Jun 2019 23:25:43 +0000 (16:25 -0700)
committerLennart Poettering <lennart@poettering.net>
Thu, 20 Jun 2019 19:46:36 +0000 (21:46 +0200)
The OCI changes in #9762 broke a use case in which we use nspawn from
inside a container that has dropped capabilities from the bounding set
that nspawn expected to retain. In an attempt to keep OCI compliance
and support our use case, I made hard failing on setting capabilities
not in the bounding set optional (hard fail if using OCI and log only
if using nspawn cmdline).

Fixes #12539

src/basic/capability-util.c
src/basic/capability-util.h
src/nspawn/nspawn.c

index e3ed14f806cec6663b05f6cdc83efab82d421b87..d62bb62de4773501f767b7d449bbc6111f27a652 100644 (file)
@@ -10,6 +10,7 @@
 
 #include "alloc-util.h"
 #include "capability-util.h"
+#include "cap-list.h"
 #include "fileio.h"
 #include "log.h"
 #include "macro.h"
@@ -364,6 +365,43 @@ bool ambient_capabilities_supported(void) {
         return cache;
 }
 
+bool capability_quintet_mangle(CapabilityQuintet *q) {
+        unsigned long i;
+        uint64_t combined, drop = 0;
+        bool ambient_supported;
+
+        assert(q);
+
+        combined = q->effective | q->bounding | q->inheritable | q->permitted;
+
+        ambient_supported = q->ambient != (uint64_t) -1;
+        if (ambient_supported)
+                combined |= q->ambient;
+
+        for (i = 0; i <= cap_last_cap(); i++) {
+                unsigned long bit = UINT64_C(1) << i;
+                if (!FLAGS_SET(combined, bit))
+                        continue;
+
+                if (prctl(PR_CAPBSET_READ, i) > 0)
+                        continue;
+
+                drop |= bit;
+
+                log_debug("Not in the current bounding set: %s", capability_to_name(i));
+        }
+
+        q->effective &= ~drop;
+        q->bounding &= ~drop;
+        q->inheritable &= ~drop;
+        q->permitted &= ~drop;
+
+        if (ambient_supported)
+                q->ambient &= ~drop;
+
+        return drop != 0; /* Let the caller know we changed something */
+}
+
 int capability_quintet_enforce(const CapabilityQuintet *q) {
         _cleanup_cap_free_ cap_t c = NULL, modified = NULL;
         int r;
index e69b2fbb957854da3ec0eb52ca977729155e4e3d..b5bce29ab53841d672a7d0871ff95fd917c5df4f 100644 (file)
@@ -69,4 +69,9 @@ static inline bool capability_quintet_is_set(const CapabilityQuintet *q) {
                 q->ambient != (uint64_t) -1;
 }
 
+/* Mangles the specified caps quintet taking the current bounding set into account:
+ * drops all caps from all five sets if our bounding set doesn't allow them.
+ * Returns true if the quintet was modified. */
+bool capability_quintet_mangle(CapabilityQuintet *q);
+
 int capability_quintet_enforce(const CapabilityQuintet *q);
index 5079918ae15c40d0ba6baab1e240a9124da97c15..1c0187ae5c1122db788dd517849a646952c0478a 100644 (file)
@@ -2317,7 +2317,11 @@ static int drop_capabilities(uid_t uid) {
 
                 if (q.ambient == (uint64_t) -1 && ambient_capabilities_supported())
                         q.ambient = 0;
-        } else
+
+                if (capability_quintet_mangle(&q))
+                        return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Cannot set capabilities that are not in the current bounding set.");
+
+        } else {
                 q = (CapabilityQuintet) {
                         .bounding = arg_caps_retain,
                         .effective = uid == 0 ? arg_caps_retain : 0,
@@ -2326,6 +2330,13 @@ static int drop_capabilities(uid_t uid) {
                         .ambient = ambient_capabilities_supported() ? 0 : (uint64_t) -1,
                 };
 
+                /* If we're not using OCI, proceed with mangled capabilities (so we don't error out)
+                 * in order to maintain the same behavior as systemd < 242. */
+                if (capability_quintet_mangle(&q))
+                        log_warning("Some capabilities will not be set because they are not in the current bounding set.");
+
+        }
+
         return capability_quintet_enforce(&q);
 }