From 2a6b084cc6ed20e6a9e843b0d598f65a8a2c248a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Oct 2025 22:38:35 +0900 Subject: [PATCH 01/15] capability-list: make capability_list_length() return unsigned --- src/basic/capability-list.c | 8 ++++---- src/basic/capability-list.h | 2 +- src/nspawn/nspawn.c | 2 +- src/test/test-capability-list.c | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/basic/capability-list.c b/src/basic/capability-list.c index 804dbcb112..6c60888327 100644 --- a/src/basic/capability-list.c +++ b/src/basic/capability-list.c @@ -20,7 +20,7 @@ static const struct capability_name* lookup_capability(register const char *str, const char* capability_to_name(int id) { if (id < 0) return NULL; - if (id >= capability_list_length()) + if ((unsigned) id >= capability_list_length()) return NULL; return capability_names[id]; @@ -65,13 +65,13 @@ 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 +/* 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 MIN((int) ELEMENTSOF(capability_names), CAP_LIMIT + 1); +unsigned capability_list_length(void) { + return MIN((unsigned) ELEMENTSOF(capability_names), (unsigned) (CAP_LIMIT + 1)); } int capability_set_to_string(uint64_t set, char **ret) { diff --git a/src/basic/capability-list.h b/src/basic/capability-list.h index e588e87dee..2f73c74cde 100644 --- a/src/basic/capability-list.h +++ b/src/basic/capability-list.h @@ -13,7 +13,7 @@ const char* capability_to_string(int id, char buf[static CAPABILITY_TO_STRING_MA #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); +unsigned capability_list_length(void); int capability_set_to_string(uint64_t set, char **ret); int capability_set_to_string_negative(uint64_t set, char **ret); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index a47fcb2294..bc1ed4c9eb 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -480,7 +480,7 @@ static int parse_capability_spec(const char *spec, uint64_t *ret_mask) { break; if (streq(t, "help")) { - for (int i = 0; i < capability_list_length(); i++) { + for (unsigned i = 0; i < capability_list_length(); i++) { const char *name; name = capability_to_name(i); diff --git a/src/test/test-capability-list.c b/src/test/test-capability-list.c index c72c241683..0f5d53276d 100644 --- a/src/test/test-capability-list.c +++ b/src/test/test-capability-list.c @@ -28,12 +28,12 @@ TEST(cap_list) { ASSERT_STREQ(CAPABILITY_TO_STRING(62), "0x3e"); assert_se(!CAPABILITY_TO_STRING(64)); - for (int i = 0; i < capability_list_length(); i++) { + for (unsigned i = 0; i < capability_list_length(); i++) { const char *n; - assert_se(n = capability_to_name(i)); - assert_se(capability_from_name(n) == i); - printf("%s = %i\n", n, i); + ASSERT_NOT_NULL(n = capability_to_name(i)); + ASSERT_OK_EQ(capability_from_name(n), (int) i); + printf("%s = %u\n", n, i); ASSERT_STREQ(CAPABILITY_TO_STRING(i), n); } @@ -49,7 +49,7 @@ TEST(cap_list) { assert_se(capability_from_name("64") == -EINVAL); assert_se(capability_from_name("-1") == -EINVAL); - for (int i = 0; i < capability_list_length(); i++) { + for (unsigned i = 0; i < capability_list_length(); i++) { _cleanup_(cap_free_charpp) char *a = NULL; const char *b; unsigned u; From 4c0cdc4a2ccce078349099ec545c0f685ab71405 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 24 Oct 2025 01:52:02 +0900 Subject: [PATCH 02/15] capability-util: tighten requirement for CAP_LAST_CAP off by one Otherwise, we cannot use UINT64_MAX as 'unset'. --- src/basic/capability-util.h | 2 +- src/test/test-capability-util.c | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index 09b612b1e6..fa8b591759 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -8,7 +8,6 @@ /* Special marker used when storing a capabilities mask as "unset". This would need to be updated as soon as * Linux learns more than 63 caps. */ #define CAP_MASK_UNSET UINT64_MAX -assert_cc(CAP_LAST_CAP < 64); /* All possible capabilities bits on */ #define CAP_MASK_ALL UINT64_C(0x7fffffffffffffff) @@ -16,6 +15,7 @@ assert_cc(CAP_LAST_CAP < 64); /* 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 +assert_cc(CAP_LAST_CAP <= CAP_LIMIT); static inline bool capability_is_set(uint64_t v) { return v != CAP_MASK_UNSET; diff --git a/src/test/test-capability-util.c b/src/test/test-capability-util.c index 881aafbc7f..67a61c7eaf 100644 --- a/src/test/test-capability-util.c +++ b/src/test/test-capability-util.c @@ -241,11 +241,9 @@ static void test_ensure_cap_64_bit(void) { ASSERT_OK(safe_atolu(content, &p)); - /* If caps don't fit into 64-bit anymore, we have a problem, fail the test. */ - assert_se(p <= 63); - - /* Also check for the header definition */ - assert_cc(CAP_LAST_CAP <= 63); + /* If caps don't fit into 64-bit anymore, we have a problem, fail the test. Moreover, we use + * UINT64_MAX as unset, hence it must be smaller than or equals to 62 (CAP_LIMIT). */ + assert_se(p <= CAP_LIMIT); } static void test_capability_get_ambient(void) { From 7de349c1ed8c6a2a0747305af837209c15ac87e1 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 24 Oct 2025 01:52:56 +0900 Subject: [PATCH 03/15] test: use CAP_LIMIT at one more place --- src/test/test-capability-list.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/test-capability-list.c b/src/test/test-capability-list.c index 0f5d53276d..c57b57acf8 100644 --- a/src/test/test-capability-list.c +++ b/src/test/test-capability-list.c @@ -150,9 +150,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 62 caps, there are no 'invalid' numbers + /* once the kernel supports 62 (CAP_LIMIT) caps, there are no 'invalid' numbers * for us to test with */ - if (cap_last_cap() < 62) + if (cap_last_cap() < CAP_LIMIT) test_capability_set_to_string_invalid(all_capabilities() + 1); } From 50053a0212b2fd90ba631ead83d646711d228b09 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Oct 2025 22:42:07 +0900 Subject: [PATCH 04/15] capability-util: move several definitions --- src/basic/capability-util.c | 30 +++++++++++++++--------------- src/basic/capability-util.h | 37 ++++++++++++++++++++++--------------- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index 2e99af317b..4be71f64a4 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -20,21 +20,6 @@ #include "stat-util.h" #include "user-util.h" -int have_effective_cap(int value) { - _cleanup_cap_free_ cap_t cap = NULL; - cap_flag_value_t fv = CAP_CLEAR; /* To avoid false-positive use-of-uninitialized-value error reported - * by fuzzers. */ - - cap = cap_get_proc(); - if (!cap) - return -errno; - - if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0) - return -errno; - - return fv == CAP_SET; -} - unsigned cap_last_cap(void) { static atomic_int saved = INT_MAX; int r, c; @@ -89,6 +74,21 @@ unsigned cap_last_cap(void) { return c; } +int have_effective_cap(int value) { + _cleanup_cap_free_ cap_t cap = NULL; + cap_flag_value_t fv = CAP_CLEAR; /* To avoid false-positive use-of-uninitialized-value error reported + * by fuzzers. */ + + cap = cap_get_proc(); + if (!cap) + return -errno; + + if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0) + return -errno; + + return fv == CAP_SET; +} + int capability_update_inherited_set(cap_t caps, uint64_t set) { /* Add capabilities in the set to the inherited caps, drops capabilities not in the set. * Do not apply them yet. */ diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index fa8b591759..202e32cbf7 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -17,6 +17,28 @@ #define CAP_LIMIT 62 assert_cc(CAP_LAST_CAP <= CAP_LIMIT); +/* Identical to linux/capability.h's CAP_TO_MASK(), but uses an unsigned 1U instead of a signed 1 for shifting left, in + * order to avoid complaints about shifting a signed int left by 31 bits, which would make it negative. */ +#define CAP_TO_MASK_CORRECTED(x) (1U << ((x) & 31U)) + +typedef struct CapabilityQuintet { + /* Stores all five types of capabilities in one go. */ + uint64_t effective; + uint64_t bounding; + uint64_t inheritable; + uint64_t permitted; + uint64_t ambient; +} CapabilityQuintet; + +#define CAPABILITY_QUINTET_NULL \ + (const CapabilityQuintet) { \ + CAP_MASK_UNSET, \ + CAP_MASK_UNSET, \ + CAP_MASK_UNSET, \ + CAP_MASK_UNSET, \ + CAP_MASK_UNSET, \ + } + static inline bool capability_is_set(uint64_t v) { return v != CAP_MASK_UNSET; } @@ -46,21 +68,6 @@ static inline bool cap_test_all(uint64_t caps) { return FLAGS_SET(caps, all_capabilities()); } -/* Identical to linux/capability.h's CAP_TO_MASK(), but uses an unsigned 1U instead of a signed 1 for shifting left, in - * order to avoid complaints about shifting a signed int left by 31 bits, which would make it negative. */ -#define CAP_TO_MASK_CORRECTED(x) (1U << ((x) & 31U)) - -typedef struct CapabilityQuintet { - /* Stores all five types of capabilities in one go. */ - uint64_t effective; - uint64_t bounding; - uint64_t inheritable; - uint64_t permitted; - uint64_t ambient; -} CapabilityQuintet; - -#define CAPABILITY_QUINTET_NULL (const CapabilityQuintet) { 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 capability_is_set(q->effective) || capability_is_set(q->bounding) || From 2038ad725d3a428c5670f317d85e4eed6c6d0213 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 24 Oct 2025 01:41:53 +0900 Subject: [PATCH 05/15] capability-util: introduce capability_quintet_equal() helper function Currently unused, but will be used later. --- src/basic/capability-util.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index 202e32cbf7..d658bbe065 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -84,6 +84,14 @@ static inline bool capability_quintet_is_fully_set(const CapabilityQuintet *q) { capability_is_set(q->ambient); } +static inline bool capability_quintet_equal(const CapabilityQuintet *a, const CapabilityQuintet *b) { + return a->effective == b->effective && + a->bounding == b->bounding && + a->inheritable == b->inheritable && + a->permitted == b->permitted && + a->ambient == b->ambient; +} + /* 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. */ From e804256b80d1fc01b0fe85efa05731b117607ae3 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Oct 2025 22:57:29 +0900 Subject: [PATCH 06/15] capability-util: several coding style updates - rebreak comments, - add short comment for constant arguments, - drop unnecessary {}, - use BIT_SET() macro. --- src/basic/capability-util.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index 4be71f64a4..bf7fd151c8 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -137,7 +137,7 @@ int capability_ambient_set_apply(uint64_t set, bool also_inherit) { return -errno; } - for (unsigned i = 0; i <= cap_last_cap(); i++) { + for (unsigned i = 0; i <= cap_last_cap(); i++) if (BIT_SET(set, i)) { /* Add the capability to the ambient set. */ if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0) < 0) @@ -151,7 +151,6 @@ int capability_ambient_set_apply(uint64_t set, bool also_inherit) { if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_LOWER, i, 0, 0) < 0) return -errno; } - } return 0; } @@ -180,8 +179,9 @@ int capability_gain_cap_setpcap(cap_t *ret_before_caps) { if (cap_set_proc(temp_cap) < 0) log_debug_errno(errno, "Can't acquire effective CAP_SETPCAP bit, ignoring: %m"); - /* If we didn't manage to acquire the CAP_SETPCAP bit, we continue anyway, after all this just means - * we'll fail later, when we actually intend to drop some capabilities or try to set securebits. */ + /* If we didn't manage to acquire the CAP_SETPCAP bit, we continue anyway, after all this + * just means we'll fail later, when we actually intend to drop some capabilities or try to + * set securebits. */ } if (ret_before_caps) /* Return the capabilities as they have been before setting CAP_SETPCAP */ @@ -194,10 +194,8 @@ int capability_bounding_set_drop(uint64_t keep, bool right_now) { _cleanup_cap_free_ cap_t before_cap = NULL, after_cap = NULL; int r; - /* If we are run as PID 1 we will lack CAP_SETPCAP by default - * in the effective set (yes, the kernel drops that when - * executing init!), so get it back temporarily so that we can - * call PR_CAPBSET_DROP. */ + /* If we are run as PID 1 we will lack CAP_SETPCAP by default in the effective set (yes, the kernel + * drops that when executing init!), so get it back temporarily so that we can call PR_CAPBSET_DROP. */ r = capability_gain_cap_setpcap(&before_cap); if (r < 0) @@ -210,31 +208,29 @@ int capability_bounding_set_drop(uint64_t keep, bool right_now) { for (unsigned i = 0; i <= cap_last_cap(); i++) { cap_value_t v; - if ((keep & (UINT64_C(1) << i))) + if (BIT_SET(keep, i)) continue; /* Drop it from the bounding set */ if (prctl(PR_CAPBSET_DROP, i) < 0) { r = -errno; - /* If dropping the capability failed, let's see if we didn't have it in the first place. If so, - * continue anyway, as dropping a capability we didn't have in the first place doesn't really - * matter anyway. */ + /* If dropping the capability failed, let's see if we didn't have it in the first + * place. If so, continue anyway, as dropping a capability we didn't have in the + * first place doesn't really matter anyway. */ if (prctl(PR_CAPBSET_READ, i) != 0) goto finish; } v = (cap_value_t) i; - /* Also drop it from the inheritable set, so - * that anything we exec() loses the - * capability for good. */ + /* Also drop it from the inheritable set, so that anything we exec() loses the capability for + * good. */ if (cap_set_flag(after_cap, CAP_INHERITABLE, 1, &v, CAP_CLEAR) < 0) { r = -errno; goto finish; } - /* If we shall apply this right now drop it - * also from our own capability sets. */ + /* If we shall apply this right now drop it also from our own capability sets. */ if (right_now) { if (cap_set_flag(after_cap, CAP_PERMITTED, 1, &v, CAP_CLEAR) < 0 || cap_set_flag(after_cap, CAP_EFFECTIVE, 1, &v, CAP_CLEAR) < 0) { @@ -307,7 +303,7 @@ int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) { if (setresgid(gid, gid, gid) < 0) return log_error_errno(errno, "Failed to change group ID: %m"); - r = maybe_setgroups(0, NULL); + r = maybe_setgroups(/* size= */ 0, /* list= */ NULL); if (r < 0) return log_error_errno(r, "Failed to drop auxiliary groups list: %m"); @@ -325,7 +321,7 @@ int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) { /* Drop all caps from the bounding set (as well as the inheritable/permitted/effective sets), except * the ones we want to keep */ - r = capability_bounding_set_drop(keep_capabilities, true); + r = capability_bounding_set_drop(keep_capabilities, /* right_now= */ true); if (r < 0) return log_error_errno(r, "Failed to drop capabilities: %m"); @@ -568,7 +564,7 @@ int capability_quintet_enforce(const CapabilityQuintet *q) { } if (q->bounding != CAP_MASK_UNSET) { - r = capability_bounding_set_drop(q->bounding, false); + r = capability_bounding_set_drop(q->bounding, /* right_now= */ false); if (r < 0) return r; } From e1c134ba9ce2e4ca9ffa70fc94a24898b863cc8a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Oct 2025 23:07:13 +0900 Subject: [PATCH 07/15] capability-util: introduce capability_get() and use it in have_effective_cap() capability_get() is a wrapper of capget() syscall and converts its result to CapabilityQuintet. This also introduce have_inheritable_cap(), which is similar to have_effective_cap(). It is currently unused, but will be used later. --- src/basic/capability-util.c | 55 ++++++++++++++++++++++++++++++------- src/basic/capability-util.h | 5 +++- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index bf7fd151c8..ee665df6f9 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "alloc-util.h" @@ -20,6 +21,29 @@ #include "stat-util.h" #include "user-util.h" +int capability_get(CapabilityQuintet *ret) { + assert(ret); + + struct __user_cap_header_struct hdr = { + .version = _LINUX_CAPABILITY_VERSION_3, + .pid = getpid_cached(), + }; + + assert_cc(_LINUX_CAPABILITY_U32S_3 == 2); + struct __user_cap_data_struct data[_LINUX_CAPABILITY_U32S_3]; + if (syscall(SYS_capget, &hdr, data) < 0) + return -errno; + + *ret = (CapabilityQuintet) { + .effective = (uint64_t) data[0].effective | ((uint64_t) data[1].effective << 32), + .bounding = UINT64_MAX, + .inheritable = (uint64_t) data[0].inheritable | ((uint64_t) data[1].inheritable << 32), + .permitted = (uint64_t) data[0].permitted | ((uint64_t) data[1].permitted << 32), + .ambient = UINT64_MAX, + }; + return 0; +} + unsigned cap_last_cap(void) { static atomic_int saved = INT_MAX; int r, c; @@ -74,19 +98,30 @@ unsigned cap_last_cap(void) { return c; } -int have_effective_cap(int value) { - _cleanup_cap_free_ cap_t cap = NULL; - cap_flag_value_t fv = CAP_CLEAR; /* To avoid false-positive use-of-uninitialized-value error reported - * by fuzzers. */ +int have_effective_cap(unsigned cap) { + CapabilityQuintet q; + int r; - cap = cap_get_proc(); - if (!cap) - return -errno; + assert(cap <= CAP_LIMIT); - if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0) - return -errno; + r = capability_get(&q); + if (r < 0) + return r; - return fv == CAP_SET; + return BIT_SET(q.effective, cap); +} + +int have_inheritable_cap(unsigned cap) { + CapabilityQuintet q; + int r; + + assert(cap <= CAP_LIMIT); + + r = capability_get(&q); + if (r < 0) + return r; + + return BIT_SET(q.inheritable, cap); } int capability_update_inherited_set(cap_t caps, uint64_t set) { diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index d658bbe065..78bd75db7b 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -43,8 +43,11 @@ static inline bool capability_is_set(uint64_t v) { return v != CAP_MASK_UNSET; } +int capability_get(CapabilityQuintet *ret); + unsigned cap_last_cap(void); -int have_effective_cap(int value); +int have_effective_cap(unsigned cap); +int have_inheritable_cap(unsigned cap); int capability_gain_cap_setpcap(cap_t *ret_before_caps); int capability_bounding_set_drop(uint64_t keep, bool right_now); int capability_bounding_set_drop_usermode(uint64_t keep); From aa8ab67a6dbdfd42490ad49821435f85d22909e0 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Oct 2025 23:16:06 +0900 Subject: [PATCH 08/15] capability-util: introduce capability_apply() and use it in capability_ambient_set_apply() --- src/basic/capability-util.c | 57 ++++++++++++++++++--------------- src/basic/capability-util.h | 1 - src/test/test-capability-util.c | 19 ----------- 3 files changed, 32 insertions(+), 45 deletions(-) diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index ee665df6f9..801292ac79 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -11,6 +11,7 @@ #include "bitfield.h" #include "capability-list.h" #include "capability-util.h" +#include "errno-util.h" #include "fd-util.h" #include "fileio.h" #include "log.h" @@ -44,6 +45,29 @@ int capability_get(CapabilityQuintet *ret) { return 0; } +static int capability_apply(const CapabilityQuintet *q) { + assert(q); + + struct __user_cap_header_struct hdr = { + .version = _LINUX_CAPABILITY_VERSION_3, + .pid = getpid_cached(), + }; + + struct __user_cap_data_struct data[_LINUX_CAPABILITY_U32S_3] = { + { + .effective = (uint32_t) (q->effective & UINT32_MAX), + .inheritable = (uint32_t) (q->inheritable & UINT32_MAX), + .permitted = (uint32_t) (q->permitted & UINT32_MAX), + }, + { + .effective = (uint32_t) (q->effective >> 32), + .inheritable = (uint32_t) (q->inheritable >> 32), + .permitted = (uint32_t) (q->permitted >> 32), + }, + }; + return RET_NERRNO(syscall(SYS_capset, &hdr, data)); +} + unsigned cap_last_cap(void) { static atomic_int saved = INT_MAX; int r, c; @@ -124,25 +148,7 @@ int have_inheritable_cap(unsigned cap) { return BIT_SET(q.inheritable, cap); } -int capability_update_inherited_set(cap_t caps, uint64_t set) { - /* Add capabilities in the set to the inherited caps, drops capabilities not in the set. - * Do not apply them yet. */ - - for (unsigned i = 0; i <= cap_last_cap(); i++) { - cap_flag_value_t flag = set & (UINT64_C(1) << i) ? CAP_SET : CAP_CLEAR; - cap_value_t v; - - v = (cap_value_t) i; - - if (cap_set_flag(caps, CAP_INHERITABLE, 1, &v, flag) < 0) - return -errno; - } - - return 0; -} - int capability_ambient_set_apply(uint64_t set, bool also_inherit) { - _cleanup_cap_free_ cap_t caps = NULL; int r; /* Remove capabilities requested in ambient set, but not in the bounding set */ @@ -160,16 +166,17 @@ int capability_ambient_set_apply(uint64_t set, bool also_inherit) { /* Add the capabilities to the ambient set (an possibly also the inheritable set) */ if (also_inherit) { - caps = cap_get_proc(); - if (!caps) - return -errno; + CapabilityQuintet q; - r = capability_update_inherited_set(caps, set); + r = capability_get(&q); if (r < 0) - return -errno; + return r; - if (cap_set_proc(caps) < 0) - return -errno; + q.inheritable = set; + + r = capability_apply(&q); + if (r < 0) + return r; } for (unsigned i = 0; i <= cap_last_cap(); i++) diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index 78bd75db7b..e911c7bae2 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -53,7 +53,6 @@ int capability_bounding_set_drop(uint64_t keep, bool right_now); int capability_bounding_set_drop_usermode(uint64_t keep); int capability_ambient_set_apply(uint64_t set, bool also_inherit); -int capability_update_inherited_set(cap_t caps, uint64_t ambient_set); int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities); diff --git a/src/test/test-capability-util.c b/src/test/test-capability-util.c index 67a61c7eaf..ef04e77a5c 100644 --- a/src/test/test-capability-util.c +++ b/src/test/test-capability-util.c @@ -183,23 +183,6 @@ static void test_have_effective_cap(void) { assert_se(have_effective_cap(CAP_CHOWN) == 0); } -static void test_update_inherited_set(void) { - cap_t caps; - uint64_t set = 0; - cap_flag_value_t fv; - - caps = cap_get_proc(); - assert_se(caps); - - set = (UINT64_C(1) << CAP_CHOWN); - - assert_se(!capability_update_inherited_set(caps, set)); - assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); - assert_se(fv == CAP_SET); - - cap_free(caps); -} - static void test_apply_ambient_caps(void) { cap_t caps; uint64_t set = 0; @@ -336,8 +319,6 @@ int main(int argc, char *argv[]) { if (!userns_has_single_user()) test_drop_privileges(); - test_update_inherited_set(); - if (!userns_has_single_user()) fork_test(test_have_effective_cap); From 96f2255637f49caf8e1a987ab56c9fd4058fd46f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Oct 2025 23:17:36 +0900 Subject: [PATCH 09/15] test: replace cap_to_text() with capability_get() and capability_set_to_string() --- src/test/test-capability-util.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/test/test-capability-util.c b/src/test/test-capability-util.c index ef04e77a5c..c49fadfd7b 100644 --- a/src/test/test-capability-util.c +++ b/src/test/test-capability-util.c @@ -13,6 +13,7 @@ #define TEST_CAPABILITY_C #include "alloc-util.h" +#include "capability-list.h" #include "capability-util.h" #include "errno-util.h" #include "fd-util.h" @@ -84,18 +85,15 @@ static void fork_test(void (*test_func)(void)) { } static void show_capabilities(void) { - cap_t caps; - char *text; + _cleanup_free_ char *e = NULL, *p = NULL, *i = NULL; + CapabilityQuintet q; - caps = cap_get_proc(); - assert_se(caps); + ASSERT_OK(capability_get(&q)); + ASSERT_OK(capability_set_to_string(q.effective, &e)); + ASSERT_OK(capability_set_to_string(q.permitted, &p)); + ASSERT_OK(capability_set_to_string(q.inheritable, &i)); - text = cap_to_text(caps, NULL); - assert_se(text); - - log_info("Capabilities:%s", text); - cap_free(caps); - cap_free(text); + log_info("Capabilities:e=%s p=%s, i=%s", e, p, i); } static int setup_tests(bool *run_ambient) { From 2bf880892e0163bc54147350f92f510d56deca19 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Oct 2025 23:19:11 +0900 Subject: [PATCH 10/15] test: use have_inheritable_cap() in test_apply_ambient_caps() This also make the test case use ASSERT_XYZ() macros. --- src/test/test-capability-util.c | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/test/test-capability-util.c b/src/test/test-capability-util.c index c49fadfd7b..223b2b3e8d 100644 --- a/src/test/test-capability-util.c +++ b/src/test/test-capability-util.c @@ -182,32 +182,17 @@ static void test_have_effective_cap(void) { } static void test_apply_ambient_caps(void) { - cap_t caps; - uint64_t set = 0; - cap_flag_value_t fv; + ASSERT_OK_EQ_ERRNO(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0), 0); - assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 0); + ASSERT_OK(capability_ambient_set_apply(UINT64_C(1) << CAP_CHOWN, true)); + ASSERT_OK_POSITIVE(have_inheritable_cap(CAP_CHOWN)); - set = (UINT64_C(1) << CAP_CHOWN); + ASSERT_OK_EQ_ERRNO(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0), 1); - assert_se(!capability_ambient_set_apply(set, true)); + ASSERT_OK(capability_ambient_set_apply(0, true)); + ASSERT_OK_ZERO(have_inheritable_cap(CAP_CHOWN)); - caps = cap_get_proc(); - assert_se(caps); - assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); - assert_se(fv == CAP_SET); - cap_free(caps); - - assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 1); - - assert_se(!capability_ambient_set_apply(0, true)); - caps = cap_get_proc(); - assert_se(caps); - assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); - assert_se(fv == CAP_CLEAR); - cap_free(caps); - - assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 0); + ASSERT_OK_EQ_ERRNO(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0), 0); } static void test_ensure_cap_64_bit(void) { From 256d6f3f2f25f0a624b81ec78c274bd74538f850 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Oct 2025 23:27:34 +0900 Subject: [PATCH 11/15] capability-util: rework capability_gain_cap_setpcap() and capability_bounding_set_drop() This makes the functions use CapabilityQuintet, capability_get(), and capability_apply(). --- src/basic/capability-util.c | 76 ++++++++++++++----------------------- src/basic/capability-util.h | 2 +- src/core/exec-invoke.c | 2 +- 3 files changed, 31 insertions(+), 49 deletions(-) diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index 801292ac79..b632dadf23 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -197,59 +197,48 @@ int capability_ambient_set_apply(uint64_t set, bool also_inherit) { return 0; } -int capability_gain_cap_setpcap(cap_t *ret_before_caps) { - _cleanup_cap_free_ cap_t caps = NULL; - cap_flag_value_t fv; - caps = cap_get_proc(); - if (!caps) - return -errno; +int capability_gain_cap_setpcap(void) { + CapabilityQuintet q; + int r; - if (cap_get_flag(caps, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0) - return -errno; + r = capability_get(&q); + if (r < 0) + return r; - if (fv != CAP_SET) { - _cleanup_cap_free_ cap_t temp_cap = NULL; - static const cap_value_t v = CAP_SETPCAP; + if (BIT_SET(q.effective, CAP_SETPCAP)) + return 1; /* We already have capability. */ - temp_cap = cap_dup(caps); - if (!temp_cap) - return -errno; - - if (cap_set_flag(temp_cap, CAP_EFFECTIVE, 1, &v, CAP_SET) < 0) - return -errno; - - if (cap_set_proc(temp_cap) < 0) - log_debug_errno(errno, "Can't acquire effective CAP_SETPCAP bit, ignoring: %m"); + SET_BIT(q.effective, CAP_SETPCAP); + r = capability_apply(&q); + if (r < 0) { /* If we didn't manage to acquire the CAP_SETPCAP bit, we continue anyway, after all this * just means we'll fail later, when we actually intend to drop some capabilities or try to * set securebits. */ + log_debug_errno(r, "Can't acquire effective CAP_SETPCAP bit, ignoring: %m"); + return 0; } - if (ret_before_caps) - /* Return the capabilities as they have been before setting CAP_SETPCAP */ - *ret_before_caps = TAKE_PTR(caps); - return 0; + return 1; /* acquired */ } int capability_bounding_set_drop(uint64_t keep, bool right_now) { - _cleanup_cap_free_ cap_t before_cap = NULL, after_cap = NULL; - int r; + int k, r; /* If we are run as PID 1 we will lack CAP_SETPCAP by default in the effective set (yes, the kernel * drops that when executing init!), so get it back temporarily so that we can call PR_CAPBSET_DROP. */ - r = capability_gain_cap_setpcap(&before_cap); + CapabilityQuintet q; + r = capability_get(&q); + if (r < 0) + return r; + CapabilityQuintet saved = q; + + r = capability_gain_cap_setpcap(); if (r < 0) return r; - after_cap = cap_dup(before_cap); - if (!after_cap) - return -errno; - for (unsigned i = 0; i <= cap_last_cap(); i++) { - cap_value_t v; - if (BIT_SET(keep, i)) continue; @@ -263,33 +252,26 @@ int capability_bounding_set_drop(uint64_t keep, bool right_now) { if (prctl(PR_CAPBSET_READ, i) != 0) goto finish; } - v = (cap_value_t) i; /* Also drop it from the inheritable set, so that anything we exec() loses the capability for * good. */ - if (cap_set_flag(after_cap, CAP_INHERITABLE, 1, &v, CAP_CLEAR) < 0) { - r = -errno; - goto finish; - } + CLEAR_BIT(q.inheritable, i); /* If we shall apply this right now drop it also from our own capability sets. */ if (right_now) { - if (cap_set_flag(after_cap, CAP_PERMITTED, 1, &v, CAP_CLEAR) < 0 || - cap_set_flag(after_cap, CAP_EFFECTIVE, 1, &v, CAP_CLEAR) < 0) { - r = -errno; - goto finish; - } + CLEAR_BIT(q.effective, i); + CLEAR_BIT(q.permitted, i); } } r = 0; finish: - if (cap_set_proc(after_cap) < 0) { + k = capability_apply(&q); + if (k < 0) /* If there are no actual changes anyway then let's ignore this error. */ - if (cap_compare(before_cap, after_cap) != 0) - r = -errno; - } + if (!capability_quintet_equal(&q, &saved)) + return k; return r; } diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index e911c7bae2..656b7a4a46 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -48,7 +48,7 @@ int capability_get(CapabilityQuintet *ret); unsigned cap_last_cap(void); int have_effective_cap(unsigned cap); int have_inheritable_cap(unsigned cap); -int capability_gain_cap_setpcap(cap_t *ret_before_caps); +int capability_gain_cap_setpcap(void); int capability_bounding_set_drop(uint64_t keep, bool right_now); int capability_bounding_set_drop_usermode(uint64_t keep); diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index de59e1148e..5ea22fd429 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -6165,7 +6165,7 @@ int exec_invoke( * * Hence there is no security impact to raise it in the effective set before execve */ - r = capability_gain_cap_setpcap(/* ret_before_caps = */ NULL); + r = capability_gain_cap_setpcap(); if (r < 0) { *exit_status = EXIT_CAPABILITIES; return log_error_errno(r, "Failed to gain CAP_SETPCAP for setting secure bits"); From 69eb331b6c4531bb06320908782a9d5adcde312c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Oct 2025 23:30:27 +0900 Subject: [PATCH 12/15] capability-util: use capability_apply() in drop_privileges() --- src/basic/capability-util.c | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index b632dadf23..d56991c57e 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -15,7 +15,6 @@ #include "fd-util.h" #include "fileio.h" #include "log.h" -#include "logarithm.h" #include "parse-util.h" #include "pidref.h" #include "process-util.h" @@ -351,29 +350,14 @@ int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) { /* Now upgrade the permitted caps we still kept to effective caps */ if (keep_capabilities != 0) { - cap_value_t bits[log2u64(keep_capabilities) + 1]; - _cleanup_cap_free_ cap_t d = NULL; - unsigned i, j = 0; + CapabilityQuintet q = { + .effective = keep_capabilities, + .permitted = keep_capabilities, + }; - d = cap_init(); - if (!d) - return log_oom(); - - for (i = 0; i < ELEMENTSOF(bits); i++) - if (keep_capabilities & (1ULL << i)) - bits[j++] = i; - - /* use enough bits */ - assert(i == 64 || (keep_capabilities >> i) == 0); - /* don't use too many bits */ - assert(keep_capabilities & (UINT64_C(1) << (i - 1))); - - if (cap_set_flag(d, CAP_EFFECTIVE, j, bits, CAP_SET) < 0 || - cap_set_flag(d, CAP_PERMITTED, j, bits, CAP_SET) < 0) - return log_error_errno(errno, "Failed to enable capabilities bits: %m"); - - if (cap_set_proc(d) < 0) - return log_error_errno(errno, "Failed to increase capabilities: %m"); + r = capability_apply(&q); + if (r < 0) + return log_error_errno(r, "Failed to increase capabilities: %m"); } return 0; From 6e5f07756fd7f78fc5b3df5308e4c993bf8663e6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Oct 2025 23:33:04 +0900 Subject: [PATCH 13/15] capability-util: use capability_get() and _apply() in change_capability() --- src/basic/capability-util.c | 37 +++++++++++++++++++++---------------- src/basic/capability-util.h | 4 ++-- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index d56991c57e..b15a8b462c 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -363,30 +363,35 @@ int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) { return 0; } -static int change_capability(cap_value_t cv, cap_flag_value_t flag) { - _cleanup_cap_free_ cap_t tmp_cap = NULL; +static int change_capability(unsigned cap, bool b) { + CapabilityQuintet q; + int r; - tmp_cap = cap_get_proc(); - if (!tmp_cap) - return -errno; + assert(cap <= CAP_LIMIT); - if ((cap_set_flag(tmp_cap, CAP_INHERITABLE, 1, &cv, flag) < 0) || - (cap_set_flag(tmp_cap, CAP_PERMITTED, 1, &cv, flag) < 0) || - (cap_set_flag(tmp_cap, CAP_EFFECTIVE, 1, &cv, flag) < 0)) - return -errno; + r = capability_get(&q); + if (r < 0) + return r; - if (cap_set_proc(tmp_cap) < 0) - return -errno; + if (b) { + SET_BIT(q.effective, cap); + SET_BIT(q.permitted, cap); + SET_BIT(q.inheritable, cap); + } else { + CLEAR_BIT(q.effective, cap); + CLEAR_BIT(q.permitted, cap); + CLEAR_BIT(q.inheritable, cap); + } - return 0; + return capability_apply(&q); } -int drop_capability(cap_value_t cv) { - return change_capability(cv, CAP_CLEAR); +int drop_capability(unsigned cap) { + return change_capability(cap, false); } -int keep_capability(cap_value_t cv) { - return change_capability(cv, CAP_SET); +int keep_capability(unsigned cap) { + return change_capability(cap, true); } bool capability_quintet_mangle(CapabilityQuintet *q) { diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index 656b7a4a46..64c555110c 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -56,8 +56,8 @@ int capability_ambient_set_apply(uint64_t set, bool also_inherit); int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities); -int drop_capability(cap_value_t cv); -int keep_capability(cap_value_t cv); +int drop_capability(unsigned cap); +int keep_capability(unsigned cap); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(cap_t, cap_free, NULL); #define _cleanup_cap_free_ _cleanup_(cap_freep) From a98f710fdf1a4d8206b42ea12f026840333ef1bc Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Oct 2025 23:34:31 +0900 Subject: [PATCH 14/15] capability-util: use capability_get() and _apply() in capability_quintet_enforce() --- src/basic/capability-util.c | 182 ++++++++++-------------------------- 1 file changed, 48 insertions(+), 134 deletions(-) diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index b15a8b462c..751e993667 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -423,156 +423,68 @@ bool capability_quintet_mangle(CapabilityQuintet *q) { } int capability_quintet_enforce(const CapabilityQuintet *q) { - _cleanup_cap_free_ cap_t c = NULL, modified = NULL; + CapabilityQuintet c; + bool modified = false; int r; + if (q->ambient != CAP_MASK_UNSET || + q->inheritable != CAP_MASK_UNSET || + q->permitted != CAP_MASK_UNSET || + q->effective != CAP_MASK_UNSET) { + r = capability_get(&c); + if (r < 0) + return r; + } + if (q->ambient != CAP_MASK_UNSET) { - bool changed = false; - - c = cap_get_proc(); - if (!c) - return -errno; - /* In order to raise the ambient caps set we first need to raise the matching * inheritable + permitted cap */ - for (unsigned i = 0; i <= cap_last_cap(); i++) { - uint64_t m = UINT64_C(1) << i; - cap_value_t cv = (cap_value_t) i; - cap_flag_value_t old_value_inheritable, old_value_permitted; + if (!FLAGS_SET(c.permitted, q->ambient) || + !FLAGS_SET(c.inheritable, q->ambient)) { - if ((q->ambient & m) == 0) - continue; + c.permitted |= q->ambient; + c.inheritable |= q->ambient; - if (cap_get_flag(c, cv, CAP_INHERITABLE, &old_value_inheritable) < 0) - return -errno; - if (cap_get_flag(c, cv, CAP_PERMITTED, &old_value_permitted) < 0) - return -errno; - - if (old_value_inheritable == CAP_SET && old_value_permitted == CAP_SET) - continue; - - if (cap_set_flag(c, CAP_INHERITABLE, 1, &cv, CAP_SET) < 0) - return -errno; - if (cap_set_flag(c, CAP_PERMITTED, 1, &cv, CAP_SET) < 0) - return -errno; - - changed = true; + r = capability_apply(&c); + if (r < 0) + return r; } - if (changed) - if (cap_set_proc(c) < 0) - return -errno; - - r = capability_ambient_set_apply(q->ambient, false); + r = capability_ambient_set_apply(q->ambient, /* also_inherit= */ false); if (r < 0) return r; } if (q->inheritable != CAP_MASK_UNSET || q->permitted != CAP_MASK_UNSET || q->effective != CAP_MASK_UNSET) { - bool changed = false; + if (!FLAGS_SET(c.effective, q->effective) || + !FLAGS_SET(c.permitted, q->permitted) || + !FLAGS_SET(c.inheritable, q->inheritable)) { - if (!c) { - c = cap_get_proc(); - if (!c) - return -errno; - } - - for (unsigned i = 0; i <= cap_last_cap(); i++) { - uint64_t m = UINT64_C(1) << i; - cap_value_t cv = (cap_value_t) i; - - 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) { - if (errno == EINVAL) /* If the kernel knows more caps than this - * version of libcap, then this will return - * EINVAL. In that case, simply ignore it, - * pretend it doesn't exist. */ - continue; - - return -errno; - } - - new_value = (q->inheritable & m) ? CAP_SET : CAP_CLEAR; - - if (old_value != new_value) { - changed = true; - - if (cap_set_flag(c, CAP_INHERITABLE, 1, &cv, new_value) < 0) - return -errno; - } - } - - 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) { - if (errno == EINVAL) - continue; - - return -errno; - } - - new_value = (q->permitted & m) ? CAP_SET : CAP_CLEAR; - - if (old_value != new_value) { - changed = true; - - if (cap_set_flag(c, CAP_PERMITTED, 1, &cv, new_value) < 0) - return -errno; - } - } - - 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) { - if (errno == EINVAL) - continue; - - return -errno; - } - - new_value = (q->effective & m) ? CAP_SET : CAP_CLEAR; - - if (old_value != new_value) { - changed = true; - - if (cap_set_flag(c, CAP_EFFECTIVE, 1, &cv, new_value) < 0) - return -errno; - } - } - } - - 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 != CAP_MASK_UNSET) { - cap_value_t cv = CAP_SETPCAP; - - modified = cap_dup(c); - if (!modified) - return -ENOMEM; - - if (cap_set_flag(modified, CAP_PERMITTED, 1, &cv, CAP_SET) < 0) - return -errno; - if (cap_set_flag(modified, CAP_EFFECTIVE, 1, &cv, CAP_SET) < 0) - return -errno; - - if (cap_compare(modified, c) == 0) { - /* No change? then drop this nonsense again */ - cap_free(modified); - modified = NULL; - } - } + c.effective |= q->effective; + c.permitted |= q->permitted; + c.inheritable |= q->inheritable; /* Now, let's enforce the caps for the first time. Note that this is where we acquire * caps in any of the sets we currently don't have. We have to do this before * dropping the bounding caps below, since at that point we can never acquire new - * caps in inherited/permitted/effective anymore, but only lose them. */ - if (cap_set_proc(modified ?: c) < 0) - return -errno; + * caps in inherited/permitted/effective anymore, but only lose them. + * + * 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 != CAP_MASK_UNSET && + (!BIT_SET(c.effective, CAP_SETPCAP) || !BIT_SET(c.permitted, CAP_SETPCAP))) { + CapabilityQuintet tmp = c; + + SET_BIT(c.effective, CAP_SETPCAP); + SET_BIT(c.permitted, CAP_SETPCAP); + + modified = true; + + r = capability_apply(&tmp); + } else + r = capability_apply(&c); + if (r < 0) + return r; } } @@ -586,9 +498,11 @@ int capability_quintet_enforce(const CapabilityQuintet *q) { * we have already set only in the CAP_SETPCAP bit, which we needed for dropping the bounding * bits. This call only undoes bits and doesn't acquire any which means the bounding caps don't * matter. */ - if (modified) - if (cap_set_proc(c) < 0) - return -errno; + if (modified) { + r = capability_apply(&c); + if (r < 0) + return r; + } return 0; } From 9b414a38fadb41c9ea056ed5d284ab5098251a37 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 23 Oct 2025 23:40:59 +0900 Subject: [PATCH 15/15] tree-wide: drop unused libcap dependencies --- .github/workflows/build-test.sh | 1 - README | 1 - TODO | 4 ---- docs/MINIMAL_BUILDS.md | 4 +--- meson.build | 5 +---- src/basic/capability-util.h | 5 +---- src/basic/meson.build | 3 +-- src/libsystemd/libsystemd.pc.in | 1 - src/libudev/libudev.pc.in | 1 - src/shared/meson.build | 1 - src/systemctl/meson.build | 1 - src/test/meson.build | 10 ++-------- src/test/test-capability-list.c | 24 ------------------------ tools/oss-fuzz.sh | 4 ++-- 14 files changed, 8 insertions(+), 57 deletions(-) diff --git a/.github/workflows/build-test.sh b/.github/workflows/build-test.sh index 15f8a54d91..bc23367390 100755 --- a/.github/workflows/build-test.sh +++ b/.github/workflows/build-test.sh @@ -30,7 +30,6 @@ PACKAGES=( libarchive-dev libblkid-dev libbpf-dev - libcap-dev libcurl4-gnutls-dev libfdisk-dev libfido2-dev diff --git a/README b/README index eea3a55fbb..2e3acdd907 100644 --- a/README +++ b/README @@ -212,7 +212,6 @@ REQUIREMENTS: glibc >= 2.31 libxcrypt or glibc (<= 2.38 built with --enable-crypt) - libcap libmount >= 2.30 (from util-linux) (util-linux *must* be built without --enable-libmount-support-mtab) libseccomp >= 2.3.1 (optional) diff --git a/TODO b/TODO index 5f97395188..9410eed2d7 100644 --- a/TODO +++ b/TODO @@ -1640,10 +1640,6 @@ Features: work for ECDSA keys since their signatures contain a random component, but will work for RSA and Ed25519 keys. -* drop dependency on libcap, replace by direct syscalls based on - CapabilityQuintet we already have. (This likely allows us to drop libcap - dep in the base OS image) - * userdbd: implement an additional varlink service socket that provides the host user db in restricted form, then allow this to be bind mounted into sandboxed environments that want the host database in minimal form. All diff --git a/docs/MINIMAL_BUILDS.md b/docs/MINIMAL_BUILDS.md index 1fc85e7b37..3226b812f7 100644 --- a/docs/MINIMAL_BUILDS.md +++ b/docs/MINIMAL_BUILDS.md @@ -20,8 +20,6 @@ If such modularity is required that goes beyond what we support in the configure For example: if all you want is the tmpfiles tool, then build systemd normally, and list only /usr/bin/systemd-tmpfiles in the .spec file for your RPM package. This is simple to do, allows you to pick exactly what you need, but requires a larger number of build dependencies (but not runtime dependencies). -2. If you want to reduce the build time dependencies (though only dbus and libcap are needed as build time deps) and you know the specific component you are interested in doesn't need it, then create a dummy .pc file for that dependency (i.e. basically empty), and configure systemd with PKG_CONFIG_PATH set to the path of these dummy .pc files. Then, build only the few bits you need with "make foobar", where foobar is the file you need. +2. If you want to reduce the build time dependencies (though only dbus is needed as build time deps) and you know the specific component you are interested in doesn't need it, then create a dummy .pc file for that dependency (i.e. basically empty), and configure systemd with PKG_CONFIG_PATH set to the path of these dummy .pc files. Then, build only the few bits you need with "make foobar", where foobar is the file you need. We are open to merging patches for the build system that make more "fringe" components of systemd optional. However, please be aware that in order to keep the complexity of our build system small and its readability high, and to make our lives easier, we will not accept patches that make the minimal core components optional, i.e. systemd itself, journald and udevd. - -Note that the .pc file trick mentioned above currently doesn't work for libcap, since libcap doesn't provide a .pc file. We invite you to go ahead and post a patch to libcap upstream to get this corrected. We'll happily change our build system to look for that .pc file then. (a .pc file has been sent to upstream by Bryan Kadzban). diff --git a/meson.build b/meson.build index 80ea425259..ef84bd2512 100644 --- a/meson.build +++ b/meson.build @@ -692,7 +692,6 @@ conf.set('GPERF_LEN_TYPE', gperf_len_type, foreach header : [ 'crypt.h', - 'sys/capability.h', ] if not cc.has_header(header) @@ -1001,7 +1000,6 @@ threads = dependency('threads') librt = cc.find_library('rt') libm = cc.find_library('m') libdl = cc.find_library('dl') -libcap = dependency('libcap') # On some architectures, libatomic is required. But on some installations, # it is found, but actual linking fails. So let's try to use it opportunistically. @@ -2141,8 +2139,7 @@ if static_libsystemd != 'false' install_tag: 'libsystemd', install_dir : libdir, pic : static_libsystemd_pic, - dependencies : [libcap, - libdl, + dependencies : [libdl, libgcrypt_cflags, liblz4_cflags, libm, diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index 64c555110c..3088fdb79f 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include /* IWYU pragma: export */ +#include /* IWYU pragma: export */ #include "basic-forward.h" @@ -59,9 +59,6 @@ int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities); int drop_capability(unsigned cap); int keep_capability(unsigned cap); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(cap_t, cap_free, NULL); -#define _cleanup_cap_free_ _cleanup_(cap_freep) - static inline uint64_t all_capabilities(void) { return UINT64_MAX >> (63 - cap_last_cap()); } diff --git a/src/basic/meson.build b/src/basic/meson.build index 79af468381..3ae3517a26 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -202,8 +202,7 @@ libbasic_static = static_library( fundamental_sources, include_directories : basic_includes, implicit_include_directories : false, - dependencies : [libcap, - libdl, + dependencies : [libdl, libgcrypt_cflags, liblz4_cflags, libm, diff --git a/src/libsystemd/libsystemd.pc.in b/src/libsystemd/libsystemd.pc.in index 8932eee8a3..3a43ef6071 100644 --- a/src/libsystemd/libsystemd.pc.in +++ b/src/libsystemd/libsystemd.pc.in @@ -18,4 +18,3 @@ URL: {{PROJECT_URL}} Version: {{PROJECT_VERSION}} Libs: -L${libdir} -lsystemd Cflags: -I${includedir} -Requires.private: libcap diff --git a/src/libudev/libudev.pc.in b/src/libudev/libudev.pc.in index a3f9f7b4f4..6541bcb1ab 100644 --- a/src/libudev/libudev.pc.in +++ b/src/libudev/libudev.pc.in @@ -18,4 +18,3 @@ Version: {{PROJECT_VERSION}} Libs: -L${libdir} -ludev Libs.private: -lrt -pthread Cflags: -I${includedir} -Requires.private: libcap diff --git a/src/shared/meson.build b/src/shared/meson.build index b2f8f61f23..09dd6ded0f 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -361,7 +361,6 @@ libshared_deps = [threads, libacl_cflags, libaudit_cflags, libblkid_cflags, - libcap, libcrypt, libdl, libgcrypt_cflags, diff --git a/src/systemctl/meson.build b/src/systemctl/meson.build index 21d57269eb..04af155cdc 100644 --- a/src/systemctl/meson.build +++ b/src/systemctl/meson.build @@ -56,7 +56,6 @@ executables += [ 'extract' : systemctl_extract_sources, 'link_with' : systemctl_link_with, 'dependencies' : [ - libcap, liblz4_cflags, libxz_cflags, libzstd_cflags, diff --git a/src/test/meson.build b/src/test/meson.build index 0b69fc8b9f..a1f16ae1a1 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -69,6 +69,8 @@ simple_tests += files( 'test-bus-unit-util.c', 'test-bus-util.c', 'test-calendarspec.c', + 'test-capability-list.c', + 'test-capability-util.c', 'test-cgroup-setup.c', 'test-cgroup-util.c', 'test-chase.c', @@ -255,14 +257,6 @@ executables += [ 'sources' : files('test-btrfs-physical-offset.c'), 'type' : 'manual', }, - test_template + { - 'sources' : files('test-capability-list.c'), - 'dependencies' : libcap, - }, - test_template + { - 'sources' : files('test-capability-util.c'), - 'dependencies' : libcap, - }, test_template + { 'sources' : files('test-chase-manual.c'), 'type' : 'manual', diff --git a/src/test/test-capability-list.c b/src/test/test-capability-list.c index c57b57acf8..9996196426 100644 --- a/src/test/test-capability-list.c +++ b/src/test/test-capability-list.c @@ -11,11 +11,6 @@ #include "strv.h" #include "tests.h" -static inline void cap_free_charpp(char **p) { - if (*p) - cap_free(*p); -} - /* verify the capability parser */ TEST(cap_list) { assert_se(!capability_to_name(-1)); @@ -48,25 +43,6 @@ TEST(cap_list) { assert_se(capability_from_name("63") == -EINVAL); assert_se(capability_from_name("64") == -EINVAL); assert_se(capability_from_name("-1") == -EINVAL); - - for (unsigned i = 0; i < capability_list_length(); i++) { - _cleanup_(cap_free_charpp) char *a = NULL; - const char *b; - unsigned u; - - assert_se(a = cap_to_name(i)); - - /* quit the loop as soon as libcap starts returning - * numeric ids, formatted as strings */ - if (safe_atou(a, &u) >= 0) - break; - - assert_se(b = capability_to_name(i)); - - printf("%s vs. %s\n", a, b); - - assert_se(strcasecmp(a, b) == 0); - } } static void test_capability_set_one(uint64_t c, const char *t) { diff --git a/tools/oss-fuzz.sh b/tools/oss-fuzz.sh index 6df95afcfe..39300b28db 100755 --- a/tools/oss-fuzz.sh +++ b/tools/oss-fuzz.sh @@ -42,11 +42,11 @@ else apt-get update apt-get install -y gperf m4 gettext python3-pip \ - libcap-dev libmount-dev \ + libmount-dev \ pkg-config wget python3-jinja2 zipmerge zstd if [[ "$ARCHITECTURE" == i386 ]]; then - apt-get install -y pkg-config:i386 libcap-dev:i386 libmount-dev:i386 + apt-get install -y pkg-config:i386 libmount-dev:i386 fi pip3 install -r .github/workflows/requirements.txt --require-hashes