From 3e7a029c2856e7814b930443cc2d4fb089377592 Mon Sep 17 00:00:00 2001 From: Ivan Shapovalov Date: Mon, 15 Jul 2024 11:47:25 +0200 Subject: [PATCH] analyze: capability: add support for decoding capability masks 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 | 43 ++++++++++++++++++++++-- src/analyze/analyze-capability.c | 57 +++++++++++++++++++++++++------- src/analyze/analyze.c | 11 +++++- src/analyze/analyze.h | 6 ++++ test/units/TEST-65-ANALYZE.sh | 9 +++++ 5 files changed, 111 insertions(+), 15 deletions(-) diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index 67c7e07778a..91e7e1eda5c 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -80,7 +80,16 @@ systemd-analyze OPTIONS capability - CAPABILITY + + CAPABILITY + + + -m + --mask + + MASK + + systemd-analyze @@ -441,7 +450,20 @@ DATAERR 65 BSD - <command>systemd-analyze capability <optional><replaceable>CAPABILITY</replaceable>...</optional></command> + + <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> + This command prints a list of Linux capabilities along with their numeric IDs. See capabilities7 @@ -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. + Alternatively, if 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. /proc/PID/status). + <command>Show some example capability names</command> @@ -462,6 +489,18 @@ cap_audit_control 30 cap_setfcap 31 cap_mac_override 32 + + + <command>Decode a capability mask extracted from /proc</command> + + $ systemd-analyze capability -m 0000000000003c00 +NAME NUMBER +cap_net_bind_service 10 +cap_net_broadcast 11 +cap_net_admin 12 +cap_net_raw 13 + + diff --git a/src/analyze/analyze-capability.c b/src/analyze/analyze-capability.c index 07ac725a691..3e9b918ed4f 100644 --- a/src/analyze/analyze-capability.c +++ b/src/analyze/analyze-capability.c @@ -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))) - for (unsigned c = 0; c <= last_cap; c++) { - r = table_add_many(table, - TABLE_STRING, capability_to_name(c) ?: "cap_???", - TABLE_UINT, c); - if (r < 0) - return table_log_add_error(r); + 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; } - else { + + (void) table_set_sort(table, (size_t) 1); + + } else if (argc == 1) { + for (unsigned c = 0; c <= last_cap; c++) { + r = table_add_capability(table, c); + if (r < 0) + return r; + } + } 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); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 2b4babc5c9a..c9675d42586 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -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 */ } diff --git a/src/analyze/analyze.h b/src/analyze/analyze.h index a6920b76532..a93603243c3 100644 --- a/src/analyze/analyze.h +++ b/src/analyze/analyze.h @@ -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; diff --git a/test/units/TEST-65-ANALYZE.sh b/test/units/TEST-65-ANALYZE.sh index fde15192f78..236c27e33b5 100755 --- a/test/units/TEST-65-ANALYZE.sh +++ b/test/units/TEST-65-ANALYZE.sh @@ -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"