1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-03 05:18:09 +03:00

namespace-util: add process_is_owned_by_uid() helper

This commit is contained in:
Lennart Poettering 2024-11-27 10:17:00 +01:00
parent a23142a361
commit 02ab91301c
4 changed files with 164 additions and 1 deletions

View 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

View File

@ -692,3 +692,67 @@ int userns_get_base_uid(int userns_fd, uid_t *ret_uid, gid_t *ret_gid) {
return 0;
}
int process_is_owned_by_uid(PidRef *pidref, uid_t 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;
r = pidref_namespace_open(
pidref,
/* ret_pidns_fd= */ NULL,
/* ret_mntns_fd= */ NULL,
/* ret_netns_fd= */ NULL,
&userns_fd,
/* ret_root_fd= */ NULL);
if (ERRNO_IS_NOT_SUPPORTED(r)) /* If userns is not supported, then they don't matter for ownership */
return false;
if (r < 0)
return r;
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) {
/* kernel too old? then we cannot make the determination, let's hence say no */
if (ERRNO_IS_NOT_SUPPORTED(errno))
return false;
return -errno;
}
if (ns_uid == uid)
return true;
/* Paranoia check */
if (iteration > 16)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "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);
}
}

View File

@ -82,3 +82,5 @@ int is_our_namespace(int fd, NamespaceType type);
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(PidRef *pidref, uid_t uid);

View 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"
@ -249,6 +251,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");