diff --git a/src/basic/cap-list.c b/src/basic/cap-list.c index fb1ad14fd3..45a5b371e8 100644 --- a/src/basic/cap-list.c +++ b/src/basic/cap-list.c @@ -10,6 +10,7 @@ #include "macro.h" #include "parse-util.h" #include "stdio-util.h" +#include "string-util.h" static const struct capability_name* lookup_capability(register const char *str, register GPERF_LEN_TYPE len); @@ -19,13 +20,28 @@ static const struct capability_name* lookup_capability(register const char *str, const char *capability_to_name(int id) { if (id < 0) return NULL; - - if ((size_t) id >= ELEMENTSOF(capability_names)) + if (id >= capability_list_length()) return NULL; return capability_names[id]; } +const char *capability_to_string(int id, char buf[static CAPABILITY_TO_STRING_MAX]) { + const char *p; + + if (id < 0) + return NULL; + if (id > CAP_LIMIT) /* refuse caps > 62 since we can't store them in a uint64_t mask anymore, and still retain UINT64_MAX as marker for "unset" */ + return NULL; + + p = capability_to_name(id); + if (p) + return p; + + sprintf(buf, "0x%x", (unsigned) id); /* numerical fallback */ + return buf; +} + int capability_from_name(const char *name) { const struct capability_name *sc; int r, i; @@ -35,10 +51,10 @@ int capability_from_name(const char *name) { /* Try to parse numeric capability */ r = safe_atoi(name, &i); if (r >= 0) { - if (i >= 0 && i < 64) - return i; - else + if (i < 0 || i > CAP_LIMIT) return -EINVAL; + + return i; } /* Try to parse string capability */ @@ -49,73 +65,67 @@ int capability_from_name(const char *name) { return sc->id; } -/* This is the number of capability names we are *compiled* with. - * For the max capability number of the currently-running kernel, - * use cap_last_cap(). */ +/* This is the number of capability names we are *compiled* with. For the max capability number of the + * currently-running kernel, use cap_last_cap(). Note that this one returns the size of the array, i.e. one + * value larger than the last known capability. This is different from cap_last_cap() which returns the + * highest supported capability. Hence with everyone agreeing on the same capabilities list, this function + * will return one higher than cap_last_cap(). */ int capability_list_length(void) { - return (int) ELEMENTSOF(capability_names); + return MIN((int) ELEMENTSOF(capability_names), CAP_LIMIT + 1); } -int capability_set_to_string_alloc(uint64_t set, char **s) { +int capability_set_to_string(uint64_t set, char **ret) { _cleanup_free_ char *str = NULL; - size_t n = 0; - assert(s); + assert(ret); - for (unsigned i = 0; i <= cap_last_cap(); i++) - if (set & (UINT64_C(1) << i)) { - const char *p; - char buf[2 + 16 + 1]; - size_t add; + for (unsigned i = 0; i <= cap_last_cap(); i++) { + const char *p; - p = capability_to_name(i); - if (!p) { - xsprintf(buf, "0x%x", i); - p = buf; - } + if (!FLAGS_SET(set, UINT64_C(1) << i)) + continue; - add = strlen(p); + p = CAPABILITY_TO_STRING(i); + assert(p); - if (!GREEDY_REALLOC(str, n + add + 2)) - return -ENOMEM; + if (!strextend_with_separator(&str, " ", p)) + return -ENOMEM; + } - strcpy(mempcpy(str + n, p, add), " "); - n += add + 1; - } - - if (!GREEDY_REALLOC(str, n + 1)) - return -ENOMEM; - - str[n > 0 ? n - 1 : 0] = '\0'; /* truncate the last space, if it's there */ - - *s = TAKE_PTR(str); + if (!str) { + str = new0(char, 1); + if (!str) + return -ENOMEM; + } + *ret = TAKE_PTR(str); return 0; } -int capability_set_from_string(const char *s, uint64_t *set) { +int capability_set_from_string(const char *s, uint64_t *ret) { uint64_t val = 0; - - assert(set); + bool good = true; for (const char *p = s;;) { _cleanup_free_ char *word = NULL; int r; - r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE); - if (r == -ENOMEM) + r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX); + if (r < 0) return r; - if (r <= 0) + if (r == 0) break; r = capability_from_name(word); - if (r < 0) - continue; - - val |= ((uint64_t) UINT64_C(1)) << (uint64_t) r; + if (r < 0) { + log_debug_errno(r, "Failed to parse capability '%s', ignoring: %m", word); + good = false; + } else + val |= UINT64_C(1) << r; } - *set = val; + if (ret) + *ret = val; - return 0; + return good; } diff --git a/src/basic/cap-list.h b/src/basic/cap-list.h index 71235d6a28..bddadcbae5 100644 --- a/src/basic/cap-list.h +++ b/src/basic/cap-list.h @@ -3,9 +3,17 @@ #include +/* Space for capability_to_string() in case we write out a numeric capability because we don't know the name + * for it. "0x3e" is the largest string we might output, in both sensese of the word "largest": two chars for + * "0x", two bytes for the hex value, and one trailing NUL byte. */ +#define CAPABILITY_TO_STRING_MAX (2 + 2 + 1) + const char *capability_to_name(int id); +const char *capability_to_string(int id, char buf[static CAPABILITY_TO_STRING_MAX]); +#define CAPABILITY_TO_STRING(id) capability_to_string(id, (char[CAPABILITY_TO_STRING_MAX]) {}) + int capability_from_name(const char *name); int capability_list_length(void); -int capability_set_to_string_alloc(uint64_t set, char **s); -int capability_set_from_string(const char *s, uint64_t *set); +int capability_set_to_string(uint64_t set, char **ret); +int capability_set_from_string(const char *s, uint64_t *ret); diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index 5ab110d341..bda7cbf45c 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -47,11 +47,13 @@ unsigned cap_last_cap(void) { r = safe_atolu(content, &p); if (r >= 0) { - if (p > 63) /* Safety for the future: if one day the kernel learns more than 64 caps, - * then we are in trouble (since we, as much userspace and kernel space - * store capability masks in uint64_t types). Let's hence protect - * ourselves against that and always cap at 63 for now. */ - p = 63; + if (p > CAP_LIMIT) /* Safety for the future: if one day the kernel learns more than + * 64 caps, then we are in trouble (since we, as much userspace + * and kernel space store capability masks in uint64_t types). We + * also want to use UINT64_MAX as marker for "unset". Hence let's + * hence protect ourselves against that and always cap at 62 for + * now. */ + p = CAP_LIMIT; saved = p; valid = true; @@ -60,7 +62,7 @@ unsigned cap_last_cap(void) { } /* fall back to syscall-probing for pre linux-3.2 */ - p = MIN((unsigned long) CAP_LAST_CAP, 63U); + p = (unsigned long) MIN(CAP_LAST_CAP, CAP_LIMIT); if (prctl(PR_CAPBSET_READ, p) < 0) { @@ -72,7 +74,7 @@ unsigned cap_last_cap(void) { } else { /* Hmm, look upwards, until we find one that doesn't work */ - for (; p < 63; p++) + for (; p < CAP_LIMIT; p++) if (prctl(PR_CAPBSET_READ, p+1) < 0) break; } @@ -281,8 +283,8 @@ static int drop_from_file(const char *fn, uint64_t keep) { if (current == after) return 0; - lo = after & UINT32_C(0xFFFFFFFF); - hi = (after >> 32) & UINT32_C(0xFFFFFFFF); + lo = after & UINT32_MAX; + hi = (after >> 32) & UINT32_MAX; return write_string_filef(fn, 0, "%" PRIu32 " %" PRIu32, lo, hi); } @@ -405,7 +407,7 @@ bool capability_quintet_mangle(CapabilityQuintet *q) { combined = q->effective | q->bounding | q->inheritable | q->permitted; - ambient_supported = q->ambient != UINT64_MAX; + ambient_supported = q->ambient != CAP_MASK_UNSET; if (ambient_supported) combined |= q->ambient; @@ -437,7 +439,7 @@ int capability_quintet_enforce(const CapabilityQuintet *q) { _cleanup_cap_free_ cap_t c = NULL, modified = NULL; int r; - if (q->ambient != UINT64_MAX) { + if (q->ambient != CAP_MASK_UNSET) { bool changed = false; c = cap_get_proc(); @@ -479,7 +481,7 @@ int capability_quintet_enforce(const CapabilityQuintet *q) { return r; } - if (q->inheritable != UINT64_MAX || q->permitted != UINT64_MAX || q->effective != UINT64_MAX) { + if (q->inheritable != CAP_MASK_UNSET || q->permitted != CAP_MASK_UNSET || q->effective != CAP_MASK_UNSET) { bool changed = false; if (!c) { @@ -492,7 +494,7 @@ int capability_quintet_enforce(const CapabilityQuintet *q) { uint64_t m = UINT64_C(1) << i; cap_value_t cv = (cap_value_t) i; - if (q->inheritable != UINT64_MAX) { + if (q->inheritable != CAP_MASK_UNSET) { cap_flag_value_t old_value, new_value; if (cap_get_flag(c, cv, CAP_INHERITABLE, &old_value) < 0) { @@ -515,7 +517,7 @@ int capability_quintet_enforce(const CapabilityQuintet *q) { } } - if (q->permitted != UINT64_MAX) { + if (q->permitted != CAP_MASK_UNSET) { cap_flag_value_t old_value, new_value; if (cap_get_flag(c, cv, CAP_PERMITTED, &old_value) < 0) { @@ -535,7 +537,7 @@ int capability_quintet_enforce(const CapabilityQuintet *q) { } } - if (q->effective != UINT64_MAX) { + if (q->effective != CAP_MASK_UNSET) { cap_flag_value_t old_value, new_value; if (cap_get_flag(c, cv, CAP_EFFECTIVE, &old_value) < 0) { @@ -559,7 +561,7 @@ int capability_quintet_enforce(const CapabilityQuintet *q) { if (changed) { /* In order to change the bounding caps, we need to keep CAP_SETPCAP for a bit * longer. Let's add it to our list hence for now. */ - if (q->bounding != UINT64_MAX) { + if (q->bounding != CAP_MASK_UNSET) { cap_value_t cv = CAP_SETPCAP; modified = cap_dup(c); @@ -587,7 +589,7 @@ int capability_quintet_enforce(const CapabilityQuintet *q) { } } - if (q->bounding != UINT64_MAX) { + if (q->bounding != CAP_MASK_UNSET) { r = capability_bounding_set_drop(q->bounding, false); if (r < 0) return r; diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index 4d1b0521f4..07b13862c7 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -9,7 +9,15 @@ #include "macro.h" #include "missing_capability.h" -#define CAP_ALL UINT64_MAX +/* Special marker used when storing a capabilities mask as "unset" */ +#define CAP_MASK_UNSET UINT64_MAX + +/* All possible capabilities bits on */ +#define CAP_MASK_ALL UINT64_C(0x7fffffffffffffff) + +/* The largest capability we can deal with, given we want to be able to store cap masks in uint64_t but still + * be able to use UINT64_MAX as indicator for "not set". The latter makes capability 63 unavailable. */ +#define CAP_LIMIT 62 unsigned cap_last_cap(void); int have_effective_cap(int value); @@ -59,14 +67,14 @@ typedef struct CapabilityQuintet { assert_cc(CAP_LAST_CAP < 64); -#define CAPABILITY_QUINTET_NULL { UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX } +#define CAPABILITY_QUINTET_NULL { CAP_MASK_UNSET, CAP_MASK_UNSET, CAP_MASK_UNSET, CAP_MASK_UNSET, CAP_MASK_UNSET } static inline bool capability_quintet_is_set(const CapabilityQuintet *q) { - return q->effective != UINT64_MAX || - q->bounding != UINT64_MAX || - q->inheritable != UINT64_MAX || - q->permitted != UINT64_MAX || - q->ambient != UINT64_MAX; + return q->effective != CAP_MASK_UNSET || + q->bounding != CAP_MASK_UNSET || + q->inheritable != CAP_MASK_UNSET || + q->permitted != CAP_MASK_UNSET || + q->ambient != CAP_MASK_UNSET; } /* Mangles the specified caps quintet taking the current bounding set into account: diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index f514b8fd12..b07b5775ff 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -1671,7 +1671,7 @@ static BUS_DEFINE_SET_TRANSIENT_PARSE(proc_subset, ProcSubset, proc_subset_from_ static BUS_DEFINE_SET_TRANSIENT_PARSE(preserve_mode, ExecPreserveMode, exec_preserve_mode_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(personality, unsigned long, parse_personality); static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(secure_bits, "i", int32_t, int, "%" PRIi32, secure_bits_to_string_alloc_with_check); -static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(capability, "t", uint64_t, uint64_t, "%" PRIu64, capability_set_to_string_alloc); +static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(capability, "t", uint64_t, uint64_t, "%" PRIu64, capability_set_to_string); static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(namespace_flag, "t", uint64_t, unsigned long, "%" PRIu64, namespace_flags_to_string); static BUS_DEFINE_SET_TRANSIENT_TO_STRING(mount_flags, "t", uint64_t, unsigned long, "%" PRIu64, mount_propagation_flag_to_string_with_check); diff --git a/src/core/execute.c b/src/core/execute.c index 36d664c7ee..f38a5a41fe 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -5466,7 +5466,7 @@ void exec_context_init(ExecContext *c) { for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) c->directories[t].mode = 0755; c->timeout_clean_usec = USEC_INFINITY; - c->capability_bounding_set = CAP_ALL; + c->capability_bounding_set = CAP_MASK_UNSET; assert_cc(NAMESPACE_FLAGS_INITIAL != NAMESPACE_FLAGS_ALL); c->restrict_namespaces = NAMESPACE_FLAGS_INITIAL; c->log_level_max = -1; @@ -6192,10 +6192,10 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { fprintf(f, "%sSecure Bits: %s\n", prefix, str); } - if (c->capability_bounding_set != CAP_ALL) { + if (c->capability_bounding_set != CAP_MASK_UNSET) { _cleanup_free_ char *str = NULL; - r = capability_set_to_string_alloc(c->capability_bounding_set, &str); + r = capability_set_to_string(c->capability_bounding_set, &str); if (r >= 0) fprintf(f, "%sCapabilityBoundingSet: %s\n", prefix, str); } @@ -6203,7 +6203,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { if (c->capability_ambient_set != 0) { _cleanup_free_ char *str = NULL; - r = capability_set_to_string_alloc(c->capability_ambient_set, &str); + r = capability_set_to_string(c->capability_ambient_set, &str); if (r >= 0) fprintf(f, "%sAmbientCapabilities: %s\n", prefix, str); } diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index ec782703a7..d4a874eafa 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -1886,7 +1886,7 @@ int config_parse_capability_set( void *userdata) { uint64_t *capability_set = ASSERT_PTR(data); - uint64_t sum = 0, initial = 0; + uint64_t sum = 0, initial, def; bool invert = false; int r; @@ -1899,9 +1899,11 @@ int config_parse_capability_set( rvalue++; } - if (streq(lvalue, "CapabilityBoundingSet")) - initial = CAP_ALL; /* initialized to all bits on */ - /* else "AmbientCapabilities" initialized to all bits off */ + if (streq(lvalue, "CapabilityBoundingSet")) { + initial = CAP_MASK_ALL; /* initialized to all bits on */ + def = CAP_MASK_UNSET; /* not set */ + } else + def = initial = 0; /* All bits off */ r = capability_set_from_string(rvalue, &sum); if (r < 0) { @@ -1909,7 +1911,7 @@ int config_parse_capability_set( return 0; } - if (sum == 0 || *capability_set == initial) + if (sum == 0 || *capability_set == def) /* "", "~" or uninitialized data -> replace */ *capability_set = invert ? ~sum : sum; else { diff --git a/src/core/main.c b/src/core/main.c index f28448f9e4..1af9b8b505 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -2458,7 +2458,7 @@ static void reset_arguments(void) { arg_manager_environment = strv_free(arg_manager_environment); rlimit_free_all(arg_default_rlimit); - arg_capability_bounding_set = CAP_ALL; + arg_capability_bounding_set = CAP_MASK_UNSET; arg_no_new_privs = false; arg_timer_slack_nsec = NSEC_INFINITY; arg_default_timer_accuracy_usec = 1 * USEC_PER_MINUTE; diff --git a/src/shared/bus-print-properties.c b/src/shared/bus-print-properties.c index 9369866fa1..db41ad2495 100644 --- a/src/shared/bus-print-properties.c +++ b/src/shared/bus-print-properties.c @@ -145,7 +145,7 @@ static int bus_print_property(const char *name, const char *expected_value, sd_b } else if (STR_IN_SET(name, "CapabilityBoundingSet", "AmbientCapabilities")) { _cleanup_free_ char *s = NULL; - r = capability_set_to_string_alloc(u, &s); + r = capability_set_to_string(u, &s); if (r < 0) return r; diff --git a/src/test/test-cap-list.c b/src/test/test-cap-list.c index 68f0461a62..8df425bdd2 100644 --- a/src/test/test-cap-list.c +++ b/src/test/test-cap-list.c @@ -14,6 +14,13 @@ TEST(cap_list) { assert_se(!capability_to_name(-1)); assert_se(!capability_to_name(capability_list_length())); + assert_se(!capability_to_name(63)); + assert_se(!capability_to_name(64)); + + assert_se(!CAPABILITY_TO_STRING(-1)); + if (capability_list_length() <= 62) + assert_se(streq(CAPABILITY_TO_STRING(62), "0x3e")); + assert_se(!CAPABILITY_TO_STRING(64)); for (int i = 0; i < capability_list_length(); i++) { const char *n; @@ -21,6 +28,8 @@ TEST(cap_list) { assert_se(n = capability_to_name(i)); assert_se(capability_from_name(n) == i); printf("%s = %i\n", n, i); + + assert_se(streq(CAPABILITY_TO_STRING(i), n)); } assert_se(capability_from_name("asdfbsd") == -EINVAL); @@ -29,7 +38,8 @@ TEST(cap_list) { assert_se(capability_from_name("cAp_aUdIt_rEAd") == CAP_AUDIT_READ); assert_se(capability_from_name("0") == 0); assert_se(capability_from_name("15") == 15); - assert_se(capability_from_name("63") == 63); + assert_se(capability_from_name("62") == 62); + assert_se(capability_from_name("63") == -EINVAL); assert_se(capability_from_name("64") == -EINVAL); assert_se(capability_from_name("-1") == -EINVAL); @@ -57,10 +67,10 @@ static void test_capability_set_one(uint64_t c, const char *t) { _cleanup_free_ char *t1 = NULL; uint64_t c1, c_masked = c & all_capabilities(); - assert_se(capability_set_to_string_alloc(c, &t1) == 0); + assert_se(capability_set_to_string(c, &t1) == 0); assert_se(streq(t1, t)); - assert_se(capability_set_from_string(t1, &c1) == 0); + assert_se(capability_set_from_string(t1, &c1) > 0); assert_se(c1 == c_masked); free(t1); @@ -73,19 +83,19 @@ static void test_capability_set_one(uint64_t c, const char *t) { TEST(capability_set_from_string) { uint64_t c; - assert_se(capability_set_from_string(NULL, &c) == 0); + assert_se(capability_set_from_string(NULL, &c) > 0); assert_se(c == 0); - assert_se(capability_set_from_string("", &c) == 0); + assert_se(capability_set_from_string("", &c) > 0); assert_se(c == 0); - assert_se(capability_set_from_string("0", &c) == 0); + assert_se(capability_set_from_string("0", &c) > 0); assert_se(c == UINT64_C(1)); - assert_se(capability_set_from_string("1", &c) == 0); + assert_se(capability_set_from_string("1", &c) > 0); assert_se(c == UINT64_C(1) << 1); - assert_se(capability_set_from_string("0 1 2 3", &c) == 0); + assert_se(capability_set_from_string("0 1 2 3", &c) > 0); assert_se(c == (UINT64_C(1) << 4) - 1); } @@ -117,9 +127,9 @@ static void test_capability_set_to_string_invalid(uint64_t invalid_cap_set) { TEST(capability_set_to_string) { test_capability_set_to_string_invalid(0); - /* once the kernel supports 63 caps, there are no 'invalid' numbers + /* once the kernel supports 62 caps, there are no 'invalid' numbers * for us to test with */ - if (cap_last_cap() < 63) + if (cap_last_cap() < 62) test_capability_set_to_string_invalid(all_capabilities() + 1); }