From a440d138a5b9065f262bdfe7a22d3e6c9c37a6e9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 13 Nov 2024 12:13:30 +0100 Subject: [PATCH] nspawn: support foreign mappings also when nspawn doing the mapping itself --- src/nspawn/nspawn-settings.c | 9 +++---- src/nspawn/nspawn-settings.h | 7 +++--- src/nspawn/nspawn.c | 41 ++++++++++++++++++++++++++++++-- src/shared/mount-util.c | 46 ++++++++++++++++++++++++++---------- src/shared/mount-util.h | 3 +++ 5 files changed, 85 insertions(+), 21 deletions(-) diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index 7842d93c34a..2d883e21968 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -930,10 +930,11 @@ DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(timezone_mode, TimezoneMode, TIMEZONE_AU DEFINE_CONFIG_PARSE_ENUM(config_parse_userns_ownership, user_namespace_ownership, UserNamespaceOwnership); static const char *const user_namespace_ownership_table[_USER_NAMESPACE_OWNERSHIP_MAX] = { - [USER_NAMESPACE_OWNERSHIP_OFF] = "off", - [USER_NAMESPACE_OWNERSHIP_CHOWN] = "chown", - [USER_NAMESPACE_OWNERSHIP_MAP] = "map", - [USER_NAMESPACE_OWNERSHIP_AUTO] = "auto", + [USER_NAMESPACE_OWNERSHIP_OFF] = "off", + [USER_NAMESPACE_OWNERSHIP_CHOWN] = "chown", + [USER_NAMESPACE_OWNERSHIP_MAP] = "map", + [USER_NAMESPACE_OWNERSHIP_FOREIGN] = "foreign", + [USER_NAMESPACE_OWNERSHIP_AUTO] = "auto", }; /* Note: while "yes" maps to "auto" here, we don't really document that, in order to make things clearer and less confusing to users. */ diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 135b3dbb0a6..767057eeb40 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -34,9 +34,10 @@ typedef enum UserNamespaceMode { } UserNamespaceMode; typedef enum UserNamespaceOwnership { - USER_NAMESPACE_OWNERSHIP_OFF, - USER_NAMESPACE_OWNERSHIP_CHOWN, - USER_NAMESPACE_OWNERSHIP_MAP, + USER_NAMESPACE_OWNERSHIP_OFF, /* do not change ownership */ + USER_NAMESPACE_OWNERSHIP_CHOWN, /* chown to target range */ + USER_NAMESPACE_OWNERSHIP_MAP, /* map from 0x00000000…0x0000FFFF range to target range */ + USER_NAMESPACE_OWNERSHIP_FOREIGN, /* map from 0x7FFE0000…0x7FFEFFFF range to target range */ USER_NAMESPACE_OWNERSHIP_AUTO, _USER_NAMESPACE_OWNERSHIP_MAX, _USER_NAMESPACE_OWNERSHIP_INVALID = -1, diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index aba4238d1a9..c6b116a78d8 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -107,6 +107,7 @@ #include "sysctl-util.h" #include "terminal-util.h" #include "tmpfile-util.h" +#include "uid-classification.h" #include "umask-util.h" #include "unit-name.h" #include "user-util.h" @@ -4145,9 +4146,39 @@ static int outer_child( return r; if (arg_userns_mode != USER_NAMESPACE_NO && - IN_SET(arg_userns_ownership, USER_NAMESPACE_OWNERSHIP_MAP, USER_NAMESPACE_OWNERSHIP_AUTO) && + IN_SET(arg_userns_ownership, USER_NAMESPACE_OWNERSHIP_MAP, USER_NAMESPACE_OWNERSHIP_FOREIGN, USER_NAMESPACE_OWNERSHIP_AUTO) && arg_uid_shift != 0) { _cleanup_strv_free_ char **dirs = NULL; + RemountIdmapping mapping; + + switch (arg_userns_ownership) { + case USER_NAMESPACE_OWNERSHIP_MAP: + mapping = REMOUNT_IDMAPPING_HOST_ROOT; + break; + + case USER_NAMESPACE_OWNERSHIP_FOREIGN: + mapping = REMOUNT_IDMAPPING_FOREIGN_WITH_HOST_ROOT; + break; + + case USER_NAMESPACE_OWNERSHIP_AUTO: { + struct stat st; + + if (lstat(directory, &st) < 0) + return log_error_errno(errno, "Failed to stat() container root directory '%s': %m", directory); + + r = stat_verify_directory(&st); + if (r < 0) + return log_error_errno(r, "Container root directory '%s' is not a directory: %m", directory); + + mapping = uid_is_foreign(st.st_uid) ? + REMOUNT_IDMAPPING_FOREIGN_WITH_HOST_ROOT : + REMOUNT_IDMAPPING_HOST_ROOT; + break; + } + + default: + assert_not_reached(); + } if (arg_volatile_mode != VOLATILE_YES) { r = strv_extend(&dirs, directory); @@ -4166,7 +4197,13 @@ static int outer_child( return log_oom(); } - r = remount_idmap(dirs, arg_uid_shift, arg_uid_range, UID_INVALID, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT); + r = remount_idmap( + dirs, + arg_uid_shift, + arg_uid_range, + /* host_owner= */ UID_INVALID, + /* dest_owner= */ UID_INVALID, + mapping); if (r == -EINVAL || ERRNO_IS_NEG_NOT_SUPPORTED(r)) { /* This might fail because the kernel or file system doesn't support idmapping. We * can't really distinguish this nicely, nor do we have any guarantees about the diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 756fb74c354..7e9f5846e23 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -1313,9 +1313,15 @@ int fd_make_mount_point(int fd) { return 1; } -int make_userns(uid_t uid_shift, uid_t uid_range, uid_t source_owner, uid_t dest_owner, RemountIdmapping idmapping) { +int make_userns(uid_t uid_shift, + uid_t uid_range, + uid_t source_owner, + uid_t dest_owner, + RemountIdmapping idmapping) { + _cleanup_close_ int userns_fd = -EBADF; _cleanup_free_ char *line = NULL; + uid_t source_base = 0; /* Allocates a userns file descriptor with the mapping we need. For this we'll fork off a child * process whose only purpose is to give us a new user namespace. It's killed when we got it. */ @@ -1323,8 +1329,18 @@ int make_userns(uid_t uid_shift, uid_t uid_range, uid_t source_owner, uid_t dest if (!userns_shift_range_valid(uid_shift, uid_range)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid UID range for user namespace."); - if (IN_SET(idmapping, REMOUNT_IDMAPPING_NONE, REMOUNT_IDMAPPING_HOST_ROOT)) { - if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", 0u, uid_shift, uid_range) < 0) + switch (idmapping) { + + case REMOUNT_IDMAPPING_FOREIGN_WITH_HOST_ROOT: + source_base = FOREIGN_UID_BASE; + _fallthrough_; + + case REMOUNT_IDMAPPING_NONE: + case REMOUNT_IDMAPPING_HOST_ROOT: + + if (asprintf(&line, + UID_FMT " " UID_FMT " " UID_FMT "\n", + source_base, uid_shift, uid_range) < 0) return log_oom_debug(); /* If requested we'll include an entry in the mapping so that the host root user can make @@ -1341,28 +1357,34 @@ int make_userns(uid_t uid_shift, uid_t uid_range, uid_t source_owner, uid_t dest if (idmapping == REMOUNT_IDMAPPING_HOST_ROOT) if (strextendf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", - UID_MAPPED_ROOT, 0u, 1u) < 0) + UID_MAPPED_ROOT, (uid_t) 0u, (uid_t) 1u) < 0) return log_oom_debug(); - } - if (idmapping == REMOUNT_IDMAPPING_HOST_OWNER) { + break; + + case REMOUNT_IDMAPPING_HOST_OWNER: /* Remap the owner of the bind mounted directory to the root user within the container. This * way every file written by root within the container to the bind-mounted directory will * be owned by the original user from the host. All other users will remain unmapped. */ - if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", source_owner, uid_shift, 1u) < 0) + if (asprintf(&line, + UID_FMT " " UID_FMT " " UID_FMT "\n", + source_owner, uid_shift, (uid_t) 1u) < 0) return log_oom_debug(); - } + break; - if (idmapping == REMOUNT_IDMAPPING_HOST_OWNER_TO_TARGET_OWNER) { + case REMOUNT_IDMAPPING_HOST_OWNER_TO_TARGET_OWNER: /* Remap the owner of the bind mounted directory to the owner of the target directory * within the container. This way every file written by target directory owner within the * container to the bind-mounted directory will be owned by the original host user. * All other users will remain unmapped. */ - if (asprintf( - &line, + if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", - source_owner, dest_owner, 1u) < 0) + source_owner, dest_owner, (uid_t) 1u) < 0) return log_oom_debug(); + break; + + default: + assert_not_reached(); } /* We always assign the same UID and GID ranges */ diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h index 496a95ab050..4d6ecd6e093 100644 --- a/src/shared/mount-util.h +++ b/src/shared/mount-util.h @@ -151,6 +151,9 @@ typedef enum RemountIdmapping { * to add inodes to file systems mapped this way should set this flag, but given it comes with * certain security implications defaults to off, and requires explicit opt-in. */ REMOUNT_IDMAPPING_HOST_ROOT, + /* Much like REMOUNT_IDMAPPING_HOST_ROOT, but the source mapping is not from 0…65535 but from the + * foreign UID range. */ + REMOUNT_IDMAPPING_FOREIGN_WITH_HOST_ROOT, /* Define a mapping from root user within the container to the owner of the bind mounted directory. * This ensures no root-owned files will be written in a bind-mounted directory owned by a different * user. No other users are mapped. */