From 54ff4f4c82be4b4a88e7f2bed436cdcbd45af9e9 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 13 Feb 2026 13:04:58 +0100 Subject: [PATCH] test-capability-util: Migrate to new test framework and macros --- src/test/test-capability-util.c | 272 ++++++++++++++++---------------- 1 file changed, 140 insertions(+), 132 deletions(-) diff --git a/src/test/test-capability-util.c b/src/test/test-capability-util.c index 6313d088ab4..cd536dd4cb6 100644 --- a/src/test/test-capability-util.c +++ b/src/test/test-capability-util.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "pidref.h" @@ -23,6 +22,7 @@ static uid_t test_uid = -1; static gid_t test_gid = -1; +static bool run_ambient = false; #if HAS_FEATURE_ADDRESS_SANITIZER /* Keep CAP_SYS_PTRACE when running under Address Sanitizer */ @@ -32,8 +32,20 @@ static const uint64_t test_flags = UINT64_C(1) << CAP_SYS_PTRACE; static const uint64_t test_flags = UINT64_C(1) << CAP_DAC_OVERRIDE; #endif +static void show_capabilities(void) { + _cleanup_free_ char *e = NULL, *p = NULL, *i = NULL; + CapabilityQuintet q; + + 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)); + + log_info("Capabilities:e=%s p=%s, i=%s", e, p, i); +} + /* verify cap_last_cap() against /proc/sys/kernel/cap_last_cap */ -static void test_last_cap_file(void) { +TEST(last_cap_file) { _cleanup_free_ char *content = NULL; unsigned long val = 0; int r; @@ -43,14 +55,13 @@ static void test_last_cap_file(void) { return (void) log_tests_skipped_errno(r, "Failed to /proc/sys/kernel/cap_last_cap"); ASSERT_OK(r); - r = safe_atolu(content, &val); - ASSERT_OK(r); - assert_se(val != 0); + ASSERT_OK(safe_atolu(content, &val)); + ASSERT_NE(val, 0UL); ASSERT_EQ(val, cap_last_cap()); } /* verify cap_last_cap() against syscall probing */ -static void test_last_cap_probe(void) { +TEST(last_cap_probe) { unsigned long p = (unsigned long)CAP_LAST_CAP; if (prctl(PR_CAPBSET_READ, p) < 0) { @@ -63,67 +74,20 @@ static void test_last_cap_probe(void) { break; } - assert_se(p != 0); + ASSERT_NE(p, 0UL); ASSERT_EQ(p, cap_last_cap()); } -static void fork_test(void (*test_func)(void)) { - pid_t pid = 0; - - pid = fork(); - assert_se(pid >= 0); - if (pid == 0) { - test_func(); - exit(EXIT_SUCCESS); - } else if (pid > 0) { - int status; - - assert_se(waitpid(pid, &status, 0) > 0); - assert_se(WIFEXITED(status) && WEXITSTATUS(status) == 0); - } -} - -static void show_capabilities(void) { - _cleanup_free_ char *e = NULL, *p = NULL, *i = NULL; - CapabilityQuintet q; - - 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)); - - log_info("Capabilities:e=%s p=%s, i=%s", e, p, i); -} - -static int setup_tests(bool *run_ambient) { - struct passwd *nobody; - int r; - - nobody = getpwnam(NOBODY_USER_NAME); - if (!nobody) - return log_warning_errno(SYNTHETIC_ERRNO(ENOENT), "Couldn't find 'nobody' user."); - - test_uid = nobody->pw_uid; - test_gid = nobody->pw_gid; - - r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); - /* There's support for PR_CAP_AMBIENT if the prctl() call succeeded or error code was something else - * than EINVAL. The EINVAL check should be good enough to rule out false positives. */ - *run_ambient = r >= 0 || errno != EINVAL; - - return 0; -} - static void test_drop_privileges_keep_net_raw(void) { int sock; sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); - assert_se(sock >= 0); + ASSERT_OK_ERRNO(sock); safe_close(sock); - assert_se(drop_privileges(test_uid, test_gid, test_flags | (1ULL << CAP_NET_RAW)) >= 0); - assert_se(getuid() == test_uid); - assert_se(getgid() == test_gid); + ASSERT_OK(drop_privileges(test_uid, test_gid, test_flags | (1ULL << CAP_NET_RAW))); + ASSERT_EQ(getuid(), test_uid); + ASSERT_EQ(getgid(), test_gid); show_capabilities(); sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); @@ -138,9 +102,9 @@ static void test_drop_privileges_dontkeep_net_raw(void) { ASSERT_OK(sock); safe_close(sock); - assert_se(drop_privileges(test_uid, test_gid, test_flags) >= 0); - assert_se(getuid() == test_uid); - assert_se(getgid() == test_gid); + ASSERT_OK(drop_privileges(test_uid, test_gid, test_flags)); + ASSERT_EQ(getuid(), test_uid); + ASSERT_EQ(getgid(), test_gid); show_capabilities(); sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); @@ -148,53 +112,103 @@ static void test_drop_privileges_dontkeep_net_raw(void) { } static void test_drop_privileges_fail(void) { - assert_se(drop_privileges(test_uid, test_gid, test_flags) >= 0); - assert_se(getuid() == test_uid); - assert_se(getgid() == test_gid); + ASSERT_OK(drop_privileges(test_uid, test_gid, test_flags)); + ASSERT_EQ(getuid(), test_uid); + ASSERT_EQ(getgid(), test_gid); - ASSERT_LT(drop_privileges(test_uid, test_gid, test_flags), 0); - ASSERT_LT(drop_privileges(0, 0, test_flags), 0); + ASSERT_FAIL(drop_privileges(test_uid, test_gid, test_flags)); + ASSERT_FAIL(drop_privileges(0, 0, test_flags)); } -static void test_drop_privileges(void) { - fork_test(test_drop_privileges_fail); +TEST(drop_privileges) { + int r; + + if (getuid() != 0) + return (void) log_tests_skipped("not running as root"); + if (userns_has_single_user()) + return (void) log_tests_skipped("running in single-user user namespace"); + + r = ASSERT_OK(pidref_safe_fork("test-cap", FORK_WAIT|FORK_DEATHSIG_SIGKILL|FORK_LOG, /* ret= */ NULL)); + if (r == 0) { + test_drop_privileges_fail(); + _exit(EXIT_SUCCESS); + } if (have_effective_cap(CAP_NET_RAW) <= 0) /* The remaining two tests only work if we have CAP_NET_RAW * in the first place. If we are run in some restricted * container environment we might not. */ return; - fork_test(test_drop_privileges_keep_net_raw); - fork_test(test_drop_privileges_dontkeep_net_raw); + r = ASSERT_OK(pidref_safe_fork("test-cap", FORK_WAIT|FORK_DEATHSIG_SIGKILL|FORK_LOG, /* ret= */ NULL)); + if (r == 0) { + test_drop_privileges_keep_net_raw(); + _exit(EXIT_SUCCESS); + } + + r = ASSERT_OK(pidref_safe_fork("test-cap", FORK_WAIT|FORK_DEATHSIG_SIGKILL|FORK_LOG, /* ret= */ NULL)); + if (r == 0) { + test_drop_privileges_dontkeep_net_raw(); + _exit(EXIT_SUCCESS); + } } -static void test_have_effective_cap(void) { +static void test_have_effective_cap_impl(void) { ASSERT_GT(have_effective_cap(CAP_KILL), 0); ASSERT_GT(have_effective_cap(CAP_CHOWN), 0); ASSERT_OK(drop_privileges(test_uid, test_gid, test_flags | (1ULL << CAP_KILL))); - assert_se(getuid() == test_uid); - assert_se(getgid() == test_gid); + ASSERT_EQ(getuid(), test_uid); + ASSERT_EQ(getgid(), test_gid); ASSERT_GT(have_effective_cap(CAP_KILL), 0); - assert_se(have_effective_cap(CAP_CHOWN) == 0); + ASSERT_EQ(have_effective_cap(CAP_CHOWN), 0); +} + +TEST(have_effective_cap) { + int r; + + if (getuid() != 0) + return (void) log_tests_skipped("not running as root"); + if (userns_has_single_user()) + return (void) log_tests_skipped("running in single-user user namespace"); + + r = ASSERT_OK(pidref_safe_fork("test-cap", FORK_WAIT|FORK_DEATHSIG_SIGKILL|FORK_LOG, /* ret= */ NULL)); + if (r == 0) { + test_have_effective_cap_impl(); + _exit(EXIT_SUCCESS); + } } -static void test_apply_ambient_caps(void) { +static void test_apply_ambient_caps_impl(void) { ASSERT_OK_EQ_ERRNO(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(capability_ambient_set_apply(UINT64_C(1) << CAP_CHOWN, /* also_inherit= */ true)); ASSERT_OK_POSITIVE(have_inheritable_cap(CAP_CHOWN)); ASSERT_OK_EQ_ERRNO(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0), 1); - ASSERT_OK(capability_ambient_set_apply(0, true)); + ASSERT_OK(capability_ambient_set_apply(0, /* also_inherit= */ true)); ASSERT_OK_ZERO(have_inheritable_cap(CAP_CHOWN)); 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) { +TEST(apply_ambient_caps) { + int r; + + if (getuid() != 0) + return (void) log_tests_skipped("not running as root"); + if (!run_ambient) + return (void) log_tests_skipped("ambient caps not supported"); + + r = ASSERT_OK(pidref_safe_fork("test-cap", FORK_WAIT|FORK_DEATHSIG_SIGKILL|FORK_LOG, /* ret= */ NULL)); + if (r == 0) { + test_apply_ambient_caps_impl(); + _exit(EXIT_SUCCESS); + } +} + +TEST(ensure_cap_64_bit) { _cleanup_free_ char *content = NULL; unsigned long p = 0; int r; @@ -208,13 +222,16 @@ static void test_ensure_cap_64_bit(void) { /* 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); + ASSERT_LE(p, (unsigned long) CAP_LIMIT); } -static void test_capability_get_ambient(void) { +TEST(capability_get_ambient) { uint64_t c; int r; + if (getuid() != 0) + return (void) log_tests_skipped("not running as root"); + ASSERT_OK(capability_get_ambient(&c)); r = prctl(PR_CAPBSET_READ, CAP_MKNOD); @@ -227,91 +244,82 @@ static void test_capability_get_ambient(void) { r = ASSERT_OK(pidref_safe_fork( "(getambient)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_LOG, - NULL)); + /* ret= */ NULL)); if (r == 0) { int x, y; /* child */ - assert_se(capability_get_ambient(&c) >= 0); + ASSERT_OK(capability_get_ambient(&c)); x = capability_ambient_set_apply( (UINT64_C(1) << CAP_MKNOD)| (UINT64_C(1) << CAP_LINUX_IMMUTABLE), /* also_inherit= */ true); - assert_se(x >= 0 || ERRNO_IS_PRIVILEGE(x)); + ASSERT_TRUE(x >= 0 || ERRNO_IS_PRIVILEGE(x)); - assert_se(capability_get_ambient(&c) >= 0); - assert_se(x < 0 || FLAGS_SET(c, UINT64_C(1) << CAP_MKNOD)); - assert_se(x < 0 || FLAGS_SET(c, UINT64_C(1) << CAP_LINUX_IMMUTABLE)); - assert_se(x < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_SETPCAP)); + ASSERT_OK(capability_get_ambient(&c)); + ASSERT_TRUE(x < 0 || FLAGS_SET(c, UINT64_C(1) << CAP_MKNOD)); + ASSERT_TRUE(x < 0 || FLAGS_SET(c, UINT64_C(1) << CAP_LINUX_IMMUTABLE)); + ASSERT_TRUE(x < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_SETPCAP)); y = capability_bounding_set_drop( ((UINT64_C(1) << CAP_LINUX_IMMUTABLE)| (UINT64_C(1) << CAP_SETPCAP)), /* right_now= */ true); - assert_se(y >= 0 || ERRNO_IS_PRIVILEGE(y)); + ASSERT_TRUE(y >= 0 || ERRNO_IS_PRIVILEGE(y)); - assert_se(capability_get_ambient(&c) >= 0); - assert_se(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_MKNOD)); - assert_se(x < 0 || y < 0 || FLAGS_SET(c, UINT64_C(1) << CAP_LINUX_IMMUTABLE)); - assert_se(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_SETPCAP)); + ASSERT_OK(capability_get_ambient(&c)); + ASSERT_TRUE(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_MKNOD)); + ASSERT_TRUE(x < 0 || y < 0 || FLAGS_SET(c, UINT64_C(1) << CAP_LINUX_IMMUTABLE)); + ASSERT_TRUE(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_SETPCAP)); y = capability_bounding_set_drop( (UINT64_C(1) << CAP_SETPCAP), /* right_now= */ true); - assert_se(y >= 0 || ERRNO_IS_PRIVILEGE(y)); + ASSERT_TRUE(y >= 0 || ERRNO_IS_PRIVILEGE(y)); - assert_se(capability_get_ambient(&c) >= 0); - assert_se(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_MKNOD)); - assert_se(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_LINUX_IMMUTABLE)); - assert_se(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_SETPCAP)); + ASSERT_OK(capability_get_ambient(&c)); + ASSERT_TRUE(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_MKNOD)); + ASSERT_TRUE(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_LINUX_IMMUTABLE)); + ASSERT_TRUE(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_SETPCAP)); _exit(EXIT_SUCCESS); } } -static void test_pidref_get_capability(void) { +TEST(pidref_get_capability) { CapabilityQuintet q = CAPABILITY_QUINTET_NULL; - assert_se(pidref_get_capability(&PIDREF_MAKE_FROM_PID(getpid_cached()), &q) >= 0); - - assert_se(q.effective != CAP_MASK_UNSET); - assert_se(q.inheritable != CAP_MASK_UNSET); - assert_se(q.permitted != CAP_MASK_UNSET); - assert_se(q.effective != CAP_MASK_UNSET); - assert_se(q.ambient != CAP_MASK_UNSET); -} - -int main(int argc, char *argv[]) { - bool run_ambient; - - test_setup_logging(LOG_DEBUG); - - test_ensure_cap_64_bit(); - - test_last_cap_file(); - test_last_cap_probe(); - if (getuid() != 0) - return log_tests_skipped("not running as root"); - - if (setup_tests(&run_ambient) < 0) - return log_tests_skipped("setup failed"); - - show_capabilities(); + return (void) log_tests_skipped("not running as root"); - if (!userns_has_single_user()) - test_drop_privileges(); + ASSERT_OK(pidref_get_capability(&PIDREF_MAKE_FROM_PID(getpid_cached()), &q)); - if (!userns_has_single_user()) - fork_test(test_have_effective_cap); + ASSERT_NE(q.effective, CAP_MASK_UNSET); + ASSERT_NE(q.inheritable, CAP_MASK_UNSET); + ASSERT_NE(q.permitted, CAP_MASK_UNSET); + ASSERT_NE(q.effective, CAP_MASK_UNSET); + ASSERT_NE(q.ambient, CAP_MASK_UNSET); +} - if (run_ambient) - fork_test(test_apply_ambient_caps); +static int intro(void) { + /* Try to set up nobody user/ambient caps for tests that need them. + * Not finding nobody is non-fatal — those tests will skip themselves. */ + struct passwd *nobody = getpwnam(NOBODY_USER_NAME); + if (nobody) { + test_uid = nobody->pw_uid; + test_gid = nobody->pw_gid; + } - test_capability_get_ambient(); + int r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); + /* There's support for PR_CAP_AMBIENT if the prctl() call succeeded or error code was something else + * than EINVAL. The EINVAL check should be good enough to rule out false positives. */ + run_ambient = r >= 0 || errno != EINVAL; - test_pidref_get_capability(); + if (getuid() == 0) + show_capabilities(); - return 0; + return EXIT_SUCCESS; } + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); -- 2.47.3