]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
analyze: capability: add support for decoding capability masks 33727/head
authorIvan Shapovalov <intelfx@intelfx.name>
Mon, 15 Jul 2024 09:47:25 +0000 (11:47 +0200)
committerIvan Shapovalov <intelfx@intelfx.name>
Wed, 24 Jul 2024 15:25:47 +0000 (17:25 +0200)
This adds support in `systemd-analyze capability` for decoding
capability masks (sets), e.g.:

```console
$ systemd-analyze capability --mask 0000000000003c00
NAME                 NUMBER
cap_net_bind_service     10
cap_net_broadcast        11
cap_net_admin            12
cap_net_raw              13
```

This is intended as a convenience tool for pretty-printing capability
values as found in e.g. `/proc/$PID/status`.

man/systemd-analyze.xml
src/analyze/analyze-capability.c
src/analyze/analyze.c
src/analyze/analyze.h
test/units/TEST-65-ANALYZE.sh

index 67c7e07778a6c2dda5fe03afd2cdf2921a407819..91e7e1eda5c8ce174c1c4c7d51106edeb2ab5fbd 100644 (file)
       <command>systemd-analyze</command>
       <arg choice="opt" rep="repeat">OPTIONS</arg>
       <arg choice="plain">capability</arg>
-      <arg choice="opt" rep="repeat"><replaceable>CAPABILITY</replaceable></arg>
+      <group choice="opt">
+        <arg choice="plain" rep="repeat"><replaceable>CAPABILITY</replaceable></arg>
+        <arg choice="plain">
+          <group choice="req">
+            <arg choice="plain">-m</arg>
+            <arg choice="plain">--mask</arg>
+          </group>
+          <replaceable>MASK</replaceable>
+        </arg>
+      </group>
     </cmdsynopsis>
     <cmdsynopsis>
       <command>systemd-analyze</command>
@@ -441,7 +450,20 @@ DATAERR 65     BSD
     </refsect2>
 
     <refsect2>
-      <title><command>systemd-analyze capability <optional><replaceable>CAPABILITY</replaceable>...</optional></command></title>
+      <title>
+        <command>systemd-analyze capability
+          <group choice="opt">
+            <arg choice="plain" rep="repeat"><replaceable>CAPABILITY</replaceable></arg>
+            <arg choice="plain">
+              <group choice="req">
+                <arg choice="plain">-m</arg>
+                <arg choice="plain">--mask</arg>
+              </group>
+              <replaceable>MASK</replaceable>
+            </arg>
+          </group>
+        </command>
+      </title>
 
       <para>This command prints a list of Linux capabilities along with their numeric IDs. See <citerefentry
       project='man-pages'><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry>
@@ -451,6 +473,11 @@ DATAERR 65     BSD
       cabilities by name or numeric ID, in which case only the indicated capabilities are shown in the
       table.</para>
 
+      <para>Alternatively, if <option>--mask</option> is passed, a single numeric argument must be specified,
+      which is interpreted as a hexadecimal capability mask. In this case, only the capabilities present in
+      the mask are shown in the table. This mode is intended to aid in decoding capability sets available
+      via various debugging interfaces (e.g. <literal>/proc/PID/status</literal>).</para>
+
       <example>
         <title><command>Show some example capability names</command></title>
 
@@ -462,6 +489,18 @@ cap_audit_control     30
 cap_setfcap           31
 cap_mac_override      32</programlisting>
       </example>
+
+      <example>
+        <title><command>Decode a capability mask extracted from /proc</command></title>
+
+        <programlisting>$ systemd-analyze capability -m 0000000000003c00
+NAME                 NUMBER
+cap_net_bind_service     10
+cap_net_broadcast        11
+cap_net_admin            12
+cap_net_raw              13
+</programlisting>
+      </example>
     </refsect2>
 
     <refsect2>
index 07ac725a69103fc2c505995131e3a6eafea1d0f7..3e9b918ed4fdeae7b4ff4a63e31783987f7d2fcb 100644 (file)
@@ -5,6 +5,18 @@
 #include "cap-list.h"
 #include "capability-util.h"
 #include "format-table.h"
+#include "parse-util.h"
+
+static int table_add_capability(Table *table, int c) {
+        int r;
+
+        r = table_add_many(table,
+                           TABLE_STRING, capability_to_name(c) ?: "cap_???",
+                           TABLE_UINT, c);
+        if (r < 0)
+                return table_log_add_error(r);
+        return 0;
+}
 
 int verb_capabilities(int argc, char *argv[], void *userdata) {
         _cleanup_(table_unrefp) Table *table = NULL;
@@ -20,15 +32,38 @@ int verb_capabilities(int argc, char *argv[], void *userdata) {
         /* Determine the maximum of the last cap known by the kernel and by us */
         last_cap = MAX((unsigned) CAP_LAST_CAP, cap_last_cap());
 
-        if (strv_isempty(strv_skip(argv, 1)))
+        if (arg_capability == CAPABILITY_MASK) {
+                uint64_t cap_mask;
+
+                if (argc != 2)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Exactly 1 positional argument expected.");
+
+                r = safe_atoux64(argv[1], &cap_mask);
+                if (r < 0)
+                        return log_error_errno(r, "Capability mask \"%s\" is not valid.", argv[1]);
+
+                for (unsigned c = 0; cap_mask != 0; ) {
+                        if (cap_mask & 0b1) {
+                                if (c > last_cap)
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Capability %u is not known.", c);
+
+                                r = table_add_capability(table, c);
+                                if (r < 0)
+                                        return r;
+                        }
+                        ++c;
+                        cap_mask >>= 1;
+                }
+
+                (void) table_set_sort(table, (size_t) 1);
+
+        } else if (argc == 1) {
                 for (unsigned c = 0; c <= last_cap; c++) {
-                        r = table_add_many(table,
-                                           TABLE_STRING, capability_to_name(c) ?: "cap_???",
-                                           TABLE_UINT, c);
+                        r = table_add_capability(table, c);
                         if (r < 0)
-                                return table_log_add_error(r);
+                                return r;
                 }
-        else {
+        else {
                 for (int i = 1; i < argc; i++) {
                         int c;
 
@@ -36,11 +71,9 @@ int verb_capabilities(int argc, char *argv[], void *userdata) {
                         if (c < 0 || (unsigned) c > last_cap)
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Capability \"%s\" is not known.", argv[i]);
 
-                        r = table_add_many(table,
-                                           TABLE_STRING, capability_to_name(c) ?: "cap_???",
-                                           TABLE_UINT, (unsigned) c);
+                        r = table_add_capability(table, c);
                         if (r < 0)
-                                return table_log_add_error(r);
+                                return r;
                 }
 
                 (void) table_set_sort(table, (size_t) 1);
index 2b4babc5c9a79227782b0e19a4f14f51e26cf654..c9675d42586f865aee4711bc7236363a11809fb7 100644 (file)
@@ -91,6 +91,7 @@
 #include "verbs.h"
 
 DotMode arg_dot = DEP_ALL;
+CapabilityMode arg_capability = CAPABILITY_LITERAL;
 char **arg_dot_from_patterns = NULL, **arg_dot_to_patterns = NULL;
 usec_t arg_fuzz = 0;
 PagerFlags arg_pager_flags = 0;
@@ -358,6 +359,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "table",            optional_argument, NULL, ARG_TABLE            },
                 { "no-legend",        optional_argument, NULL, ARG_NO_LEGEND        },
                 { "tldr",             no_argument,       NULL, ARG_TLDR             },
+                { "mask",             no_argument,       NULL, 'm'                  },
                 {}
         };
 
@@ -366,7 +368,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "hH:M:U:q", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "hqH:M:U:m", options, NULL)) >= 0)
                 switch (c) {
 
                 case 'h':
@@ -551,6 +553,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_cat_flags = CAT_TLDR;
                         break;
 
+                case 'm':
+                        arg_capability = CAPABILITY_MASK;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -614,6 +620,9 @@ static int parse_argv(int argc, char *argv[]) {
         if (arg_table && !FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--table and --json= are mutually exclusive.");
 
+        if (arg_capability != CAPABILITY_LITERAL && !streq_ptr(argv[optind], "capability"))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --mask is only supported for capability.");
+
         return 1; /* work to do */
 }
 
index a6920b765329ea11ef1c71e57d85090499f04f94..a93603243c35a389b1d75d22d22389ab41e5e236 100644 (file)
@@ -18,7 +18,13 @@ typedef enum DotMode {
         DEP_REQUIRE,
 } DotMode;
 
+typedef enum CapabilityMode {
+        CAPABILITY_LITERAL,
+        CAPABILITY_MASK,
+} CapabilityMode;
+
 extern DotMode arg_dot;
+extern CapabilityMode arg_capability;
 extern char **arg_dot_from_patterns, **arg_dot_to_patterns;
 extern usec_t arg_fuzz;
 extern PagerFlags arg_pager_flags;
index fde15192f78af651d9226bc96c04884bd6e0d78b..236c27e33b5e61787f4db762c4d5e5e1ce2acf0b 100755 (executable)
@@ -105,6 +105,15 @@ systemd-analyze capability cap_chown CAP_KILL
 systemd-analyze capability 0 1 {30..32}
 (! systemd-analyze capability cap_chown CAP_KILL "hello*")
 (! systemd-analyze capability --global)
+systemd-analyze capability -m 0000000000003c00
+(! systemd-analyze capability -m 8000000000000000)
+cap="$(systemd-analyze capability -m 0000000000003c00)"
+[[ $cap != *cap_linux_immutable* ]]
+[[ $cap == *cap_net_bind_service* ]]
+[[ $cap == *cap_net_broadcast* ]]
+[[ $cap == *cap_net_admin* ]]
+[[ $cap == *cap_net_raw* ]]
+[[ $cap != *cap_ipc_lock* ]]
 # condition
 mkdir -p /run/systemd/system
 UNIT_NAME="analyze-condition-$RANDOM.service"