diff --git a/README b/README index 75072382d1..3aba52921a 100644 --- a/README +++ b/README @@ -58,6 +58,7 @@ REQUIREMENTS: Linux kernel ≥ 5.8 for LOOP_CONFIGURE and STATX_ATTR_MOUNT_ROOT ≥ 5.9 for close_range() + ≥ 5.12 for idmapped mount ≥ 5.14 for cgroup.kill ≥ 6.3 for MFD_EXEC/MFD_NOEXEC_SEAL and tmpfs noswap option ≥ 6.5 for name_to_handle_at() AT_HANDLE_FID, SO_PEERPIDFD/SO_PASSPIDFD, diff --git a/src/basic/missing_fs.h b/src/basic/missing_fs.h index ed6bdf0426..60b0a3a2a7 100644 --- a/src/basic/missing_fs.h +++ b/src/basic/missing_fs.h @@ -89,30 +89,17 @@ assert_cc(MS_STRICTATIME == (1 << 24)); assert_cc(MS_LAZYTIME == (1<<25)); #endif -/* Not exposed yet. Defined at fs/ext4/ext4.h */ -#ifndef EXT4_IOC_RESIZE_FS -#define EXT4_IOC_RESIZE_FS _IOW('f', 16, __u64) -#endif - -/* linux/nsfs.h */ -#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 assert_cc(FS_PROJINHERIT_FL == 0x20000000); #endif +/* Not exposed yet. Defined at fs/ext4/ext4.h */ +#ifndef EXT4_IOC_RESIZE_FS +#define EXT4_IOC_RESIZE_FS _IOW('f', 16, __u64) +#endif + /* linux/fscrypt.h */ #ifndef FS_KEY_DESCRIPTOR_SIZE # define FS_KEY_DESCRIPTOR_SIZE 8 diff --git a/src/basic/missing_namespace.h b/src/basic/missing_namespace.h index 318c0143b8..1c7285151e 100644 --- a/src/basic/missing_namespace.h +++ b/src/basic/missing_namespace.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + /* Root namespace inode numbers, as per include/linux/proc_ns.h in the kernel source tree, since v3.8: * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=98f842e675f96ffac96e6c50315790912b2812be */ @@ -10,3 +12,16 @@ #define PROC_PID_INIT_INO ((ino_t) UINT32_C(0xEFFFFFFC)) #define PROC_CGROUP_INIT_INO ((ino_t) UINT32_C(0xEFFFFFFB)) #define PROC_TIME_INIT_INO ((ino_t) UINT32_C(0xEFFFFFFA)) + +/* linux/nsfs.h */ +#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 diff --git a/src/basic/namespace-util.c b/src/basic/namespace-util.c index da6ef37493..060c540f8a 100644 --- a/src/basic/namespace-util.c +++ b/src/basic/namespace-util.c @@ -474,94 +474,6 @@ int detach_mount_namespace_userns(int userns_fd) { return detach_mount_namespace(); } -int userns_acquire_empty(void) { - _cleanup_(sigkill_waitp) pid_t pid = 0; - _cleanup_close_ int userns_fd = -EBADF; - int r; - - r = safe_fork("(sd-mkuserns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_USERNS, &pid); - if (r < 0) - return r; - if (r == 0) - /* Child. We do nothing here, just freeze until somebody kills us. */ - freeze(); - - r = namespace_open(pid, NULL, NULL, NULL, &userns_fd, NULL); - if (r < 0) - return log_error_errno(r, "Failed to open userns fd: %m"); - - return TAKE_FD(userns_fd); -} - -int userns_acquire(const char *uid_map, const char *gid_map) { - char path[STRLEN("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1]; - _cleanup_(sigkill_waitp) pid_t pid = 0; - _cleanup_close_ int userns_fd = -EBADF; - int r; - - assert(uid_map); - assert(gid_map); - - /* Forks off a process in a new userns, configures the specified uidmap/gidmap, acquires an fd to it, - * and then kills the process again. This way we have a userns fd that is not bound to any - * process. We can use that for file system mounts and similar. */ - - r = safe_fork("(sd-mkuserns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_USERNS, &pid); - if (r < 0) - return r; - if (r == 0) - /* Child. We do nothing here, just freeze until somebody kills us. */ - freeze(); - - xsprintf(path, "/proc/" PID_FMT "/uid_map", pid); - r = write_string_file(path, uid_map, WRITE_STRING_FILE_DISABLE_BUFFER); - if (r < 0) - return log_debug_errno(r, "Failed to write UID map: %m"); - - xsprintf(path, "/proc/" PID_FMT "/gid_map", pid); - r = write_string_file(path, gid_map, WRITE_STRING_FILE_DISABLE_BUFFER); - if (r < 0) - return log_debug_errno(r, "Failed to write GID map: %m"); - - r = namespace_open(pid, - /* ret_pidns_fd = */ NULL, - /* ret_mntns_fd = */ NULL, - /* ret_netns_fd = */ NULL, - &userns_fd, - /* ret_root_fd = */ NULL); - if (r < 0) - return log_debug_errno(r, "Failed to open userns fd: %m"); - - return TAKE_FD(userns_fd); -} - -int netns_acquire(void) { - _cleanup_(sigkill_waitp) pid_t pid = 0; - _cleanup_close_ int netns_fd = -EBADF; - int r; - - /* Forks off a process in a new network namespace, acquires a network namespace fd, and then kills - * the process again. This way we have a netns fd that is not bound to any process. */ - - r = safe_fork("(sd-mknetns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_NETNS, &pid); - if (r < 0) - return log_debug_errno(r, "Failed to fork process (sd-mknetns): %m"); - if (r == 0) - /* Child. We do nothing here, just freeze until somebody kills us. */ - freeze(); - - r = namespace_open(pid, - /* ret_pidns_fd = */ NULL, - /* ret_mntns_fd = */ NULL, - &netns_fd, - /* ret_userns_fd = */ NULL, - /* ret_root_fd = */ NULL); - if (r < 0) - return log_debug_errno(r, "Failed to open netns fd: %m"); - - return TAKE_FD(netns_fd); -} - int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_range) { _cleanup_free_ char *buffer = NULL; const char *range, *shift; @@ -600,6 +512,193 @@ int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_r return 0; } +int userns_acquire_empty(void) { + _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL; + int r; + + r = pidref_safe_fork("(sd-mkuserns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_USERNS, &pid); + if (r < 0) + return r; + if (r == 0) + /* Child. We do nothing here, just freeze until somebody kills us. */ + freeze(); + + return pidref_namespace_open_by_type(&pid, NAMESPACE_USER); +} + +int userns_acquire(const char *uid_map, const char *gid_map) { + char path[STRLEN("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1]; + _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL; + int r; + + assert(uid_map); + assert(gid_map); + + /* Forks off a process in a new userns, configures the specified uidmap/gidmap, acquires an fd to it, + * and then kills the process again. This way we have a userns fd that is not bound to any + * process. We can use that for file system mounts and similar. */ + + r = pidref_safe_fork("(sd-mkuserns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_USERNS, &pid); + if (r < 0) + return r; + if (r == 0) + /* Child. We do nothing here, just freeze until somebody kills us. */ + freeze(); + + xsprintf(path, "/proc/" PID_FMT "/uid_map", pid.pid); + r = write_string_file(path, uid_map, WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return log_debug_errno(r, "Failed to write UID map: %m"); + + xsprintf(path, "/proc/" PID_FMT "/gid_map", pid.pid); + r = write_string_file(path, gid_map, WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return log_debug_errno(r, "Failed to write GID map: %m"); + + return pidref_namespace_open_by_type(&pid, NAMESPACE_USER); +} + +int userns_enter_and_pin(int userns_fd, pid_t *ret_pid) { + _cleanup_(close_pairp) int pfd[2] = EBADF_PAIR; + _cleanup_(sigkill_waitp) pid_t pid = 0; + ssize_t n; + char x; + int r; + + assert(userns_fd >= 0); + assert(ret_pid); + + if (pipe2(pfd, O_CLOEXEC) < 0) + return -errno; + + r = safe_fork_full( + "(sd-pinuserns)", + /* stdio_fds= */ NULL, + (int[]) { pfd[1], userns_fd }, 2, + FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL, + &pid); + if (r < 0) + return r; + if (r == 0) { + /* Child. */ + + if (setns(userns_fd, CLONE_NEWUSER) < 0) { + log_debug_errno(errno, "Failed to join userns: %m"); + _exit(EXIT_FAILURE); + } + + userns_fd = safe_close(userns_fd); + + n = write(pfd[1], &(const char) { 'x' }, 1); + if (n < 0) { + log_debug_errno(errno, "Failed to write to pipe: %m"); + _exit(EXIT_FAILURE); + } + assert(n == 1); + + freeze(); + } + + pfd[1] = safe_close(pfd[1]); + + n = read(pfd[0], &x, 1); + if (n < 0) + return -errno; + if (n == 0) + return -EPROTO; + assert(n == 1); + assert(x == 'x'); + + *ret_pid = TAKE_PID(pid); + return 0; +} + +int userns_get_base_uid(int userns_fd, uid_t *ret_uid, gid_t *ret_gid) { + _cleanup_(sigkill_waitp) pid_t pid = 0; + int r; + + assert(userns_fd >= 0); + + r = userns_enter_and_pin(userns_fd, &pid); + if (r < 0) + return r; + + uid_t uid; + r = uid_map_search_root(pid, UID_RANGE_USERNS_OUTSIDE, &uid); + if (r < 0) + return r; + + gid_t gid; + r = uid_map_search_root(pid, GID_RANGE_USERNS_OUTSIDE, &gid); + if (r < 0) + return r; + + if (!ret_gid && uid != gid) + return -EUCLEAN; + + if (ret_uid) + *ret_uid = uid; + if (ret_gid) + *ret_gid = gid; + + return 0; +} + +int process_is_owned_by_uid(const 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. */ + + assert(uid_is_valid(uid)); + + 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); + } +} + int is_idmapping_supported(const char *path) { _cleanup_close_ int mount_fd = -EBADF, userns_fd = -EBADF, dir_fd = -EBADF; _cleanup_free_ char *uid_map = NULL, *gid_map = NULL; @@ -653,128 +752,20 @@ int is_idmapping_supported(const char *path) { return true; } -int userns_get_base_uid(int userns_fd, uid_t *ret_uid, gid_t *ret_gid) { - _cleanup_(close_pairp) int pfd[2] = EBADF_PAIR; - _cleanup_(sigkill_waitp) pid_t pid = 0; - ssize_t n; - char x; +int netns_acquire(void) { + _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL; int r; - assert(userns_fd >= 0); + /* Forks off a process in a new network namespace, acquires a network namespace fd, and then kills + * the process again. This way we have a netns fd that is not bound to any process. */ - if (pipe2(pfd, O_CLOEXEC) < 0) - return -errno; - - r = safe_fork_full( - "(sd-baseuns)", - /* stdio_fds= */ NULL, - (int[]) { pfd[1], userns_fd }, 2, - FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL, - &pid); + r = pidref_safe_fork("(sd-mknetns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_NETNS, &pid); if (r < 0) - return r; - if (r == 0) { - /* Child. */ - - if (setns(userns_fd, CLONE_NEWUSER) < 0) { - log_debug_errno(errno, "Failed to join userns: %m"); - _exit(EXIT_FAILURE); - } - - userns_fd = safe_close(userns_fd); - - n = write(pfd[1], &(const char) { 'x' }, 1); - if (n < 0) { - log_debug_errno(errno, "Failed to write to pipe: %m"); - _exit(EXIT_FAILURE); - } - assert(n == 1); - + return log_debug_errno(r, "Failed to fork process into new netns: %m"); + if (r == 0) + /* Child. We do nothing here, just freeze until somebody kills us. */ freeze(); - } - pfd[1] = safe_close(pfd[1]); - - n = read(pfd[0], &x, 1); - if (n < 0) - return -errno; - if (n == 0) - return -EPROTO; - assert(n == 1); - assert(x == 'x'); - - uid_t uid; - r = uid_map_search_root(pid, "uid_map", &uid); - if (r < 0) - return r; - - gid_t gid; - r = uid_map_search_root(pid, "gid_map", &gid); - if (r < 0) - return r; - - if (!ret_gid && uid != gid) - return -EUCLEAN; - - if (ret_uid) - *ret_uid = uid; - if (ret_gid) - *ret_gid = gid; - - return 0; + return pidref_namespace_open_by_type(&pid, NAMESPACE_NET); } -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 ea2488a432..3d40a515e7 100644 --- a/src/basic/namespace-util.h +++ b/src/basic/namespace-util.h @@ -83,15 +83,16 @@ static inline bool userns_shift_range_valid(uid_t shift, uid_t range) { return true; } -int userns_acquire_empty(void); -int userns_acquire(const char *uid_map, const char *gid_map); - -int netns_acquire(void); - int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_range); -int is_idmapping_supported(const char *path); +int userns_acquire_empty(void); +int userns_acquire(const char *uid_map, const char *gid_map); +int userns_enter_and_pin(int userns_fd, pid_t *ret_pid); 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); + +int is_idmapping_supported(const char *path); + +int netns_acquire(void); diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 59c0fdd0f9..a13e2d5f28 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -1542,9 +1542,6 @@ int safe_fork_full( } if (FLAGS_SET(flags, FORK_DETACH)) { - assert(!FLAGS_SET(flags, FORK_WAIT)); - assert(!ret_pid); - /* Fork off intermediary child if needed */ r = is_reaper_process(); diff --git a/src/basic/uid-range.c b/src/basic/uid-range.c index 687ddacc57..99984d8a4e 100644 --- a/src/basic/uid-range.c +++ b/src/basic/uid-range.c @@ -9,6 +9,7 @@ #include "fd-util.h" #include "format-util.h" #include "macro.h" +#include "namespace-util.h" #include "path-util.h" #include "process-util.h" #include "sort-util.h" @@ -264,10 +265,7 @@ int uid_range_load_userns(const char *path, UIDRangeUsernsMode mode, UIDRange ** } int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange **ret) { - _cleanup_(close_pairp) int pfd[2] = EBADF_PAIR; _cleanup_(sigkill_waitp) pid_t pid = 0; - ssize_t n; - char x; int r; assert(userns_fd >= 0); @@ -275,46 +273,9 @@ int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange assert(mode < _UID_RANGE_USERNS_MODE_MAX); assert(ret); - if (pipe2(pfd, O_CLOEXEC) < 0) - return -errno; - - r = safe_fork_full( - "(sd-mkuserns)", - /* stdio_fds= */ NULL, - (int[]) { pfd[1], userns_fd }, 2, - FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL, - &pid); + r = userns_enter_and_pin(userns_fd, &pid); if (r < 0) return r; - if (r == 0) { - /* Child. */ - - if (setns(userns_fd, CLONE_NEWUSER) < 0) { - log_debug_errno(errno, "Failed to join userns: %m"); - _exit(EXIT_FAILURE); - } - - userns_fd = safe_close(userns_fd); - - n = write(pfd[1], &(const char) { 'x' }, 1); - if (n < 0) { - log_debug_errno(errno, "Failed to write to fifo: %m"); - _exit(EXIT_FAILURE); - } - assert(n == 1); - - freeze(); - } - - pfd[1] = safe_close(pfd[1]); - - n = read(pfd[0], &x, 1); - if (n < 0) - return -errno; - if (n == 0) - return -EPROTO; - assert(n == 1); - assert(x == 'x'); const char *p = procfs_file_alloca( pid, @@ -363,13 +324,13 @@ bool uid_range_equal(const UIDRange *a, const UIDRange *b) { return true; } -int uid_map_search_root(pid_t pid, const char *filename, uid_t *ret) { +int uid_map_search_root(pid_t pid, UIDRangeUsernsMode mode, uid_t *ret) { int r; assert(pid_is_valid(pid)); - assert(filename); + assert(IN_SET(mode, UID_RANGE_USERNS_OUTSIDE, GID_RANGE_USERNS_OUTSIDE)); - const char *p = procfs_file_alloca(pid, filename); + const char *p = procfs_file_alloca(pid, mode == UID_RANGE_USERNS_OUTSIDE ? "uid_map" : "gid_map"); _cleanup_fclose_ FILE *f = fopen(p, "re"); if (!f) { if (errno != ENOENT) diff --git a/src/basic/uid-range.h b/src/basic/uid-range.h index 41ca671dd6..21e5c7a17b 100644 --- a/src/basic/uid-range.h +++ b/src/basic/uid-range.h @@ -15,7 +15,7 @@ typedef struct UIDRange { size_t n_entries; } UIDRange; -UIDRange *uid_range_free(UIDRange *range); +UIDRange* uid_range_free(UIDRange *range); DEFINE_TRIVIAL_CLEANUP_FUNC(UIDRange*, uid_range_free); int uid_range_add_internal(UIDRange **range, uid_t start, uid_t nr, bool coalesce); @@ -77,4 +77,4 @@ int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange bool uid_range_overlaps(const UIDRange *range, uid_t start, uid_t nr); -int uid_map_search_root(pid_t pid, const char *filename, uid_t *ret); +int uid_map_search_root(pid_t pid, UIDRangeUsernsMode mode, uid_t *ret);