From 2ade8218598afba0802b1007535b5c8deaeceb58 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 27 Nov 2024 10:17:00 +0100 Subject: [PATCH] namespace-util: add process_is_owned_by_uid() helper --- src/basic/missing_fs.h | 10 ++++- src/basic/namespace-util.c | 55 +++++++++++++++++++++++ src/basic/namespace-util.h | 2 + src/test/test-namespace.c | 89 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 1 deletion(-) diff --git a/src/basic/missing_fs.h b/src/basic/missing_fs.h index d97b1901316..ed6bdf04267 100644 --- a/src/basic/missing_fs.h +++ b/src/basic/missing_fs.h @@ -95,10 +95,18 @@ assert_cc(MS_LAZYTIME == (1<<25)); #endif /* linux/nsfs.h */ -#ifndef NS_GET_NSTYPE /* d95fa3c76a66b6d76b1e109ea505c55e66360f3c (4.11) */ +#ifndef NS_GET_USERNS /* 6786741dbf99e44fb0c0ed85a37582b8a26f1c3b (4.9) */ +#define NS_GET_USERNS _IO(0xb7, 0x1) +#endif + +#ifndef NS_GET_NSTYPE /* e5ff5ce6e20ee22511398bb31fb912466cf82a36 (4.11) */ #define NS_GET_NSTYPE _IO(0xb7, 0x3) #endif +#ifndef NS_GET_OWNER_UID /* d95fa3c76a66b6d76b1e109ea505c55e66360f3c (4.11) */ +#define NS_GET_OWNER_UID _IO(0xb7, 0x4) +#endif + #ifndef FS_PROJINHERIT_FL # define FS_PROJINHERIT_FL 0x20000000 #else diff --git a/src/basic/namespace-util.c b/src/basic/namespace-util.c index 77937473778..c7b1bcca2d3 100644 --- a/src/basic/namespace-util.c +++ b/src/basic/namespace-util.c @@ -708,3 +708,58 @@ int userns_get_base_uid(int userns_fd, uid_t *ret_uid, gid_t *ret_gid) { return 0; } + +int process_is_owned_by_uid(const PidRef *pidref, uid_t uid) { + assert(uid_is_valid(uid)); + + int r; + + /* Checks if the specified process either is owned directly by the specified user, or if it is inside + * a user namespace owned by it. */ + + uid_t process_uid; + r = pidref_get_uid(pidref, &process_uid); + if (r < 0) + return r; + if (process_uid == uid) + return true; + + _cleanup_close_ int userns_fd = -EBADF; + userns_fd = pidref_namespace_open_by_type(pidref, NAMESPACE_USER); + if (userns_fd == -ENOPKG) /* If userns is not supported, then they don't matter for ownership */ + return false; + if (userns_fd < 0) + return userns_fd; + + for (unsigned iteration = 0;; iteration++) { + uid_t ns_uid; + + /* This process is in our own userns? Then we are done, in our own userns only the UIDs + * themselves matter. */ + r = is_our_namespace(userns_fd, NAMESPACE_USER); + if (r < 0) + return r; + if (r > 0) + return false; + + if (ioctl(userns_fd, NS_GET_OWNER_UID, &ns_uid) < 0) + return -errno; + if (ns_uid == uid) + return true; + + /* Paranoia check */ + if (iteration > 16) + return log_debug_errno(SYNTHETIC_ERRNO(ELOOP), "Giving up while tracing parents of user namespaces after %u steps.", iteration); + + /* Go up the tree */ + _cleanup_close_ int parent_fd = ioctl(userns_fd, NS_GET_USERNS); + if (parent_fd < 0) { + if (errno == EPERM) /* EPERM means we left our own userns */ + return false; + + return -errno; + } + + close_and_replace(userns_fd, parent_fd); + } +} diff --git a/src/basic/namespace-util.h b/src/basic/namespace-util.h index 1bd61e64457..d1ac4f6ed40 100644 --- a/src/basic/namespace-util.h +++ b/src/basic/namespace-util.h @@ -93,3 +93,5 @@ int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_r int is_idmapping_supported(const char *path); int userns_get_base_uid(int userns_fd, uid_t *ret_uid, gid_t *ret_gid); + +int process_is_owned_by_uid(const PidRef *pidref, uid_t uid); diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index 3f9c8b83de2..46350b078d9 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -7,10 +7,12 @@ #include "alloc-util.h" #include "fd-util.h" +#include "fileio.h" #include "namespace.h" #include "process-util.h" #include "string-util.h" #include "tests.h" +#include "uid-range.h" #include "user-util.h" #include "virt.h" @@ -276,6 +278,93 @@ TEST(userns_get_base_uid) { ASSERT_ERROR(userns_get_base_uid(fd, &base_uid, &base_gid), ENOMSG); } +TEST(process_is_owned_by_uid) { + int r; + + /* Test our own PID */ + _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; + ASSERT_OK(pidref_set_self(&pid)); + ASSERT_OK_POSITIVE(process_is_owned_by_uid(&pid, getuid())); + pidref_done(&pid); + + if (getuid() != 0) + return (void) log_tests_skipped("lacking userns privileges"); + + _cleanup_(uid_range_freep) UIDRange *range = NULL; + ASSERT_OK(uid_range_load_userns(/* path= */ NULL, UID_RANGE_USERNS_INSIDE, &range)); + if (!uid_range_contains(range, 1)) + return (void) log_tests_skipped("UID 1 not included in userns UID delegation, skipping test"); + + /* Test a child that runs as uid 1 */ + _cleanup_close_pair_ int p[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(p, O_CLOEXEC)); + + r = pidref_safe_fork("(child)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, &pid); + ASSERT_OK(r); + if (r == 0) { + p[0] = safe_close(p[0]); + ASSERT_OK(fully_set_uid_gid(1, 1, NULL, 0)); + ASSERT_OK_EQ_ERRNO(write(p[1], &(const char[]) { 'x' }, 1), 1); + p[1] = safe_close(p[1]); + freeze(); + } + + p[1] = safe_close(p[1]); + char x = 0; + ASSERT_OK_EQ_ERRNO(read(p[0], &x, 1), 1); + ASSERT_EQ(x, 'x'); + p[0] = safe_close(p[0]); + + ASSERT_OK_ZERO(process_is_owned_by_uid(&pid, getuid())); + + ASSERT_OK(pidref_kill(&pid, SIGKILL)); + ASSERT_OK(pidref_wait_for_terminate(&pid, /* ret= */ NULL)); + + /* Test a child that runs in a userns as uid 1, but the userns is owned by us */ + ASSERT_OK_ERRNO(pipe2(p, O_CLOEXEC)); + + _cleanup_close_pair_ int pp[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pp, O_CLOEXEC)); + + r = pidref_safe_fork("(child)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_NEW_USERNS, &pid); + ASSERT_OK(r); + if (r == 0) { + p[0] = safe_close(p[0]); + pp[1] = safe_close(pp[1]); + + x = 0; + ASSERT_OK_EQ_ERRNO(read(pp[0], &x, 1), 1); + ASSERT_EQ(x, 'x'); + pp[0] = safe_close(pp[0]); + + ASSERT_OK(reset_uid_gid()); + + ASSERT_OK_EQ_ERRNO(write(p[1], &(const char[]) { 'x' }, 1), 1); + p[1] = safe_close(p[1]); + freeze(); + } + + p[1] = safe_close(p[1]); + pp[0] = safe_close(pp[0]); + + ASSERT_OK(write_string_file(procfs_file_alloca(pid.pid, "uid_map"), "0 1 1\n", 0)); + ASSERT_OK(write_string_file(procfs_file_alloca(pid.pid, "setgroups"), "deny", 0)); + ASSERT_OK(write_string_file(procfs_file_alloca(pid.pid, "gid_map"), "0 1 1\n", 0)); + + ASSERT_OK_EQ_ERRNO(write(pp[1], &(const char[]) { 'x' }, 1), 1); + pp[1] = safe_close(pp[1]); + + x = 0; + ASSERT_OK_EQ_ERRNO(read(p[0], &x, 1), 1); + ASSERT_EQ(x, 'x'); + p[0] = safe_close(p[0]); + + ASSERT_OK_POSITIVE(process_is_owned_by_uid(&pid, getuid())); + + ASSERT_OK(pidref_kill(&pid, SIGKILL)); + ASSERT_OK(pidref_wait_for_terminate(&pid, /* ret= */ NULL)); +} + static int intro(void) { if (!have_namespaces()) return log_tests_skipped("Don't have namespace support or lacking privileges"); -- 2.47.3