]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
namespace-util: add process_is_owned_by_uid() helper 35833/head
authorLennart Poettering <lennart@poettering.net>
Wed, 27 Nov 2024 09:17:00 +0000 (10:17 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 7 Jan 2025 22:55:34 +0000 (23:55 +0100)
src/basic/missing_fs.h
src/basic/namespace-util.c
src/basic/namespace-util.h
src/test/test-namespace.c

index d97b1901316b6d2e58d1240cd16d762b518d0ea7..ed6bdf04267d82655286ab66f7c3a45fbba667a9 100644 (file)
@@ -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
index 779374737785b8d77de82f404e43e63a86622d68..c7b1bcca2d32ae26ac08952d43721f404256fe0e 100644 (file)
@@ -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);
+        }
+}
index 1bd61e64457581cee7ac5da28117827ad1a1e2fc..d1ac4f6ed408e5e42237351f0f3c3147fb94309c 100644 (file)
@@ -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);
index 3f9c8b83de2cc4c0fd468c7b697acb042100f52d..46350b078d952dde49bc22ed5fe313be7b715b0b 100644 (file)
@@ -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");