Merge pull request #26508 from poettering/cap-fixes

various fixes to capability handling
This commit is contained in:
Lennart Poettering 2023-02-20 19:04:13 +01:00 committed by GitHub
commit e43e735add
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 135 additions and 95 deletions

View file

@ -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;
}

View file

@ -3,9 +3,17 @@
#include <inttypes.h>
/* 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);

View file

@ -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;

View file

@ -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:

View file

@ -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);

View file

@ -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);
}

View file

@ -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 {

View file

@ -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;

View file

@ -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;

View file

@ -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);
}