diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index 5af0896ad6..6a07f1da2c 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -674,6 +674,21 @@ bool mount_propagation_flag_is_valid(unsigned long flag) { return IN_SET(flag, 0, MS_SHARED, MS_PRIVATE, MS_SLAVE); } +bool mount_new_api_supported(void) { + static int cache = -1; + int r; + + if (cache >= 0) + return cache; + + /* This is the newer API among the ones we use, so use it as boundary */ + r = RET_NERRNO(mount_setattr(-EBADF, NULL, 0, NULL, 0)); + if (r == 0 || ERRNO_IS_NOT_SUPPORTED(r)) /* This should return an error if it is working properly */ + return (cache = false); + + return (cache = true); +} + unsigned long ms_nosymfollow_supported(void) { _cleanup_close_ int fsfd = -EBADF, mntfd = -EBADF; static int cache = -1; @@ -683,6 +698,9 @@ unsigned long ms_nosymfollow_supported(void) { if (cache >= 0) return cache ? MS_NOSYMFOLLOW : 0; + if (!mount_new_api_supported()) + goto not_supported; + /* Checks if MS_NOSYMFOLLOW is supported (which was added in 5.10). We use the new mount API's * mount_setattr() call for that, which was added in 5.12, which is close enough. */ diff --git a/src/basic/mountpoint-util.h b/src/basic/mountpoint-util.h index 846015c43c..499403a4d8 100644 --- a/src/basic/mountpoint-util.h +++ b/src/basic/mountpoint-util.h @@ -65,6 +65,7 @@ const char *mount_propagation_flag_to_string(unsigned long flags); int mount_propagation_flag_from_string(const char *name, unsigned long *ret); bool mount_propagation_flag_is_valid(unsigned long flag); +bool mount_new_api_supported(void); unsigned long ms_nosymfollow_supported(void); int mount_option_supported(const char *fstype, const char *key, const char *value); diff --git a/src/core/namespace.c b/src/core/namespace.c index 206453f30f..feedb283cd 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -1311,7 +1311,8 @@ static int mount_image( host_os_release_id, host_os_release_version_id, host_os_release_level, - NULL); + /* required_sysext_scope= */ NULL, + /* ret_image= */ NULL); if (r == -ENOENT && m->ignore) return 0; if (r == -ESTALE && host_os_release_id) @@ -2494,7 +2495,13 @@ int setup_namespace( if (root_image) { /* A root image is specified, mount it to the right place */ - r = dissected_image_mount(dissected_image, root, UID_INVALID, UID_INVALID, dissect_image_flags); + r = dissected_image_mount( + dissected_image, + root, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, + dissect_image_flags); if (r < 0) { log_debug_errno(r, "Failed to mount root image: %m"); goto finish; diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 291cb66375..0b4c5ba22f 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -1063,7 +1063,13 @@ static int action_mount(DissectedImage *m, LoopDevice *d) { assert(d); assert(arg_action == ACTION_MOUNT); - r = dissected_image_mount_and_warn(m, arg_path, UID_INVALID, UID_INVALID, arg_flags); + r = dissected_image_mount_and_warn( + m, + arg_path, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, + arg_flags); if (r < 0) return r; @@ -1298,7 +1304,13 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) { created_dir = TAKE_PTR(temp); - r = dissected_image_mount_and_warn(m, created_dir, UID_INVALID, UID_INVALID, arg_flags); + r = dissected_image_mount_and_warn( + m, + created_dir, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, + arg_flags); if (r < 0) return r; @@ -1549,7 +1561,13 @@ static int action_with(DissectedImage *m, LoopDevice *d) { created_dir = TAKE_PTR(temp); - r = dissected_image_mount_and_warn(m, created_dir, UID_INVALID, UID_INVALID, arg_flags); + r = dissected_image_mount_and_warn( + m, + created_dir, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, + arg_flags); if (r < 0) return r; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 3674f6b023..05d04dce13 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3681,6 +3681,7 @@ static int outer_child( directory, arg_uid_shift, arg_uid_range, + /* userns_fd= */ -EBADF, DISSECT_IMAGE_MOUNT_ROOT_ONLY| DISSECT_IMAGE_DISCARD_ON_LOOP| DISSECT_IMAGE_USR_NO_ROOT| @@ -3845,6 +3846,7 @@ static int outer_child( directory, arg_uid_shift, arg_uid_range, + /* userns_fd= */ -EBADF, DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY| DISSECT_IMAGE_DISCARD_ON_LOOP| DISSECT_IMAGE_USR_NO_ROOT| diff --git a/src/portable/portable.c b/src/portable/portable.c index 52f38dc83e..a672bff3c5 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -424,7 +424,13 @@ static int portable_extract_by_path( else flags |= DISSECT_IMAGE_VALIDATE_OS; - r = dissected_image_mount(m, tmpdir, UID_INVALID, UID_INVALID, flags); + r = dissected_image_mount( + m, + tmpdir, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, + flags); if (r < 0) { log_debug_errno(r, "Failed to mount dissected image: %m"); goto child_finish; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index be4a3d1738..5c30e4f0af 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -51,6 +51,7 @@ #include "import-util.h" #include "io-util.h" #include "missing_mount.h" +#include "missing_syscall.h" #include "mkdir-label.h" #include "mount-util.h" #include "mountpoint-util.h" @@ -515,6 +516,7 @@ static void dissected_partition_done(DissectedPartition *p) { free(p->decrypted_node); free(p->mount_options); safe_close(p->mount_node_fd); + safe_close(p->fsmount_fd); *p = DISSECTED_PARTITION_NULL; } @@ -836,6 +838,7 @@ static int dissect_image( .mount_node_fd = TAKE_FD(mount_node_fd), .offset = 0, .size = UINT64_MAX, + .fsmount_fd = -EBADF, }; return 0; @@ -1252,6 +1255,7 @@ static int dissect_image( .offset = (uint64_t) start * 512, .size = (uint64_t) size * 512, .gpt_flags = pflags, + .fsmount_fd = -EBADF, }; } @@ -1326,6 +1330,7 @@ static int dissect_image( .mount_node_fd = TAKE_FD(mount_node_fd), .offset = (uint64_t) start * 512, .size = (uint64_t) size * 512, + .fsmount_fd = -EBADF, }; break; @@ -1416,6 +1421,7 @@ static int dissect_image( .mount_node_fd = TAKE_FD(mount_node_fd), .offset = UINT64_MAX, .size = UINT64_MAX, + .fsmount_fd = -EBADF, }; } } @@ -1739,11 +1745,15 @@ static int run_fsck(int node_fd, const char *fstype) { return 0; } -static int fs_grow(const char *node_path, const char *mount_path) { - _cleanup_close_ int mount_fd = -EBADF, node_fd = -EBADF; +static int fs_grow(const char *node_path, int mount_fd, const char *mount_path) { + _cleanup_close_ int _mount_fd = -EBADF, node_fd = -EBADF; uint64_t size, newsize; + const char *id; int r; + assert(node_path); + assert(mount_fd >= 0 || mount_path); + node_fd = open(node_path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); if (node_fd < 0) return log_debug_errno(errno, "Failed to open node device %s: %m", node_path); @@ -1751,22 +1761,34 @@ static int fs_grow(const char *node_path, const char *mount_path) { if (ioctl(node_fd, BLKGETSIZE64, &size) != 0) return log_debug_errno(errno, "Failed to get block device size of %s: %m", node_path); - mount_fd = open(mount_path, O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (mount_fd < 0) - return log_debug_errno(errno, "Failed to open mountd file system %s: %m", mount_path); + if (mount_fd < 0) { + assert(mount_path); - log_debug("Resizing \"%s\" to %"PRIu64" bytes...", mount_path, size); + _mount_fd = open(mount_path, O_RDONLY|O_DIRECTORY|O_CLOEXEC); + if (_mount_fd < 0) + return log_debug_errno(errno, "Failed to open mounted file system %s: %m", mount_path); + + mount_fd = _mount_fd; + } else { + mount_fd = fd_reopen_condition(mount_fd, O_RDONLY|O_DIRECTORY|O_CLOEXEC, O_RDONLY|O_DIRECTORY|O_CLOEXEC, &_mount_fd); + if (mount_fd < 0) + return log_debug_errno(errno, "Failed to reopen mount node: %m"); + } + + id = mount_path ?: node_path; + + log_debug("Resizing \"%s\" to %"PRIu64" bytes...", id, size); r = resize_fs(mount_fd, size, &newsize); if (r < 0) - return log_debug_errno(r, "Failed to resize \"%s\" to %"PRIu64" bytes: %m", mount_path, size); + return log_debug_errno(r, "Failed to resize \"%s\" to %"PRIu64" bytes: %m", id, size); if (newsize == size) log_debug("Successfully resized \"%s\" to %s bytes.", - mount_path, FORMAT_BYTES(newsize)); + id, FORMAT_BYTES(newsize)); else { assert(newsize < size); log_debug("Successfully resized \"%s\" to %s bytes (%"PRIu64" bytes lost due to blocksize).", - mount_path, FORMAT_BYTES(newsize), size - newsize); + id, FORMAT_BYTES(newsize), size - newsize); } return 0; @@ -1863,6 +1885,14 @@ int partition_pick_mount_options( return 0; } +static bool need_user_mapping(uid_t uid_shift, uid_t uid_range) { + + if (!uid_is_valid(uid_shift)) + return false; + + return uid_shift != 0 || uid_range != UINT32_MAX; +} + static int mount_partition( PartitionDesignator d, DissectedPartition *m, @@ -1870,81 +1900,92 @@ static int mount_partition( const char *directory, uid_t uid_shift, uid_t uid_range, + int userns_fd, DissectImageFlags flags) { _cleanup_free_ char *chased = NULL, *options = NULL; - bool rw, discard, remap_uid_gid = false; - const char *p, *node, *fstype; + const char *p = NULL, *node, *fstype = NULL; + bool rw, discard, grow; unsigned long ms_flags; int r; assert(m); - assert(where); - if (m->mount_node_fd < 0) + if (!m->found) return 0; - /* Use decrypted node and matching fstype if available, otherwise use the original device */ - node = FORMAT_PROC_FD_PATH(m->mount_node_fd); - fstype = dissected_partition_fstype(m); + /* Check the various combinations when we can't do anything anymore */ + if (m->fsmount_fd < 0 && m->mount_node_fd < 0) + return 0; + if (m->fsmount_fd >= 0 && !where) + return 0; + if (!where && m->mount_node_fd < 0) + return 0; - if (!fstype) - return -EAFNOSUPPORT; + if (m->fsmount_fd < 0) { + fstype = dissected_partition_fstype(m); + if (!fstype) + return -EAFNOSUPPORT; - /* We are looking at an encrypted partition? This either means stacked encryption, or the caller - * didn't call dissected_image_decrypt() beforehand. Let's return a recognizable error for this - * case. */ - if (streq(fstype, "crypto_LUKS")) - return -EUNATCH; + /* We are looking at an encrypted partition? This either means stacked encryption, or the + * caller didn't call dissected_image_decrypt() beforehand. Let's return a recognizable error + * for this case. */ + if (streq(fstype, "crypto_LUKS")) + return -EUNATCH; - r = dissect_fstype_ok(fstype); - if (r < 0) - return r; - if (!r) - return -EIDRM; /* Recognizable error */ + r = dissect_fstype_ok(fstype); + if (r < 0) + return r; + if (!r) + return -EIDRM; /* Recognizable error */ + } + node = m->mount_node_fd < 0 ? NULL : FORMAT_PROC_FD_PATH(m->mount_node_fd); rw = m->rw && !(flags & DISSECT_IMAGE_MOUNT_READ_ONLY); discard = ((flags & DISSECT_IMAGE_DISCARD) || - ((flags & DISSECT_IMAGE_DISCARD_ON_LOOP) && is_loop_device(m->node) > 0)); + ((flags & DISSECT_IMAGE_DISCARD_ON_LOOP) && (m->node && is_loop_device(m->node) > 0))); - if (FLAGS_SET(flags, DISSECT_IMAGE_FSCK) && rw) { + grow = rw && m->growfs && FLAGS_SET(flags, DISSECT_IMAGE_GROWFS); + + if (FLAGS_SET(flags, DISSECT_IMAGE_FSCK) && rw && m->mount_node_fd >= 0 && m->fsmount_fd < 0) { r = run_fsck(m->mount_node_fd, fstype); if (r < 0) return r; } - if (directory) { - /* Automatically create missing mount points inside the image, if necessary. */ - r = mkdir_p_root(where, directory, uid_shift, (gid_t) uid_shift, 0755, NULL); - if (r < 0 && r != -EROFS) - return r; + if (where) { + if (directory) { + /* Automatically create missing mount points inside the image, if necessary. */ + r = mkdir_p_root(where, directory, uid_shift, (gid_t) uid_shift, 0755, NULL); + if (r < 0 && r != -EROFS) + return r; - r = chase(directory, where, CHASE_PREFIX_ROOT, &chased, NULL); + r = chase(directory, where, CHASE_PREFIX_ROOT, &chased, NULL); + if (r < 0) + return r; + + p = chased; + } else { + /* Create top-level mount if missing – but only if this is asked for. This won't modify the + * image (as the branch above does) but the host hierarchy, and the created directory might + * survive our mount in the host hierarchy hence. */ + if (FLAGS_SET(flags, DISSECT_IMAGE_MKDIR)) { + r = mkdir_p(where, 0755); + if (r < 0) + return r; + } + + p = where; + } + } + + if (m->fsmount_fd < 0) { + r = partition_pick_mount_options(d, fstype, rw, discard, &options, &ms_flags); if (r < 0) return r; - p = chased; - } else { - /* Create top-level mount if missing – but only if this is asked for. This won't modify the - * image (as the branch above does) but the host hierarchy, and the created directory might - * survive our mount in the host hierarchy hence. */ - if (FLAGS_SET(flags, DISSECT_IMAGE_MKDIR)) { - r = mkdir_p(where, 0755); - if (r < 0) - return r; - } - - p = where; - } - - r = partition_pick_mount_options(d, dissected_partition_fstype(m), rw, discard, &options, &ms_flags); - if (r < 0) - return r; - - if (uid_is_valid(uid_shift) && uid_shift != 0) { - - if (fstype_can_uid_gid(fstype)) { + if (need_user_mapping(uid_shift, uid_range) && fstype_can_uid_gid(fstype)) { _cleanup_free_ char *uid_option = NULL; if (asprintf(&uid_option, "uid=" UID_FMT ",gid=" GID_FMT, uid_shift, (gid_t) uid_shift) < 0) @@ -1952,31 +1993,56 @@ static int mount_partition( if (!strextend_with_separator(&options, ",", uid_option)) return -ENOMEM; - } else if (FLAGS_SET(flags, DISSECT_IMAGE_MOUNT_IDMAPPED)) - remap_uid_gid = true; + + userns_fd = -EBADF; /* Not needed */ + } + + if (!isempty(m->mount_options)) + if (!strextend_with_separator(&options, ",", m->mount_options)) + return -ENOMEM; } - if (!isempty(m->mount_options)) - if (!strextend_with_separator(&options, ",", m->mount_options)) - return -ENOMEM; + if (p) { + if (m->fsmount_fd >= 0) { + /* Case #1: Attach existing fsmount fd to the file system */ - r = mount_nofollow_verbose(LOG_DEBUG, node, p, fstype, ms_flags, options); - if (r < 0) - return r; + if (move_mount(m->fsmount_fd, "", -EBADF, p, MOVE_MOUNT_F_EMPTY_PATH) < 0) + return -errno; - if (rw && m->growfs && FLAGS_SET(flags, DISSECT_IMAGE_GROWFS)) - (void) fs_grow(node, p); + } else { + assert(node); - if (remap_uid_gid) { - r = remount_idmap(p, uid_shift, uid_range, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT); - if (r < 0) - return r; + /* Case #2: Mount directly into place */ + r = mount_nofollow_verbose(LOG_DEBUG, node, p, fstype, ms_flags, options); + if (r < 0) + return r; + + if (grow) + (void) fs_grow(node, -EBADF, p); + + if (userns_fd >= 0) { + r = remount_idmap_fd(p, userns_fd); + if (r < 0) + return r; + } + } + } else { + assert(node); + + /* Case #3: Create fsmount fd */ + + m->fsmount_fd = make_fsmount(LOG_DEBUG, node, fstype, ms_flags, options, userns_fd); + if (m->fsmount_fd < 0) + return m->fsmount_fd; + + if (grow) + (void) fs_grow(node, m->fsmount_fd, NULL); } return 1; } -static int mount_root_tmpfs(const char *where, uid_t uid_shift, DissectImageFlags flags) { +static int mount_root_tmpfs(const char *where, uid_t uid_shift, uid_t uid_range, DissectImageFlags flags) { _cleanup_free_ char *options = NULL; int r; @@ -1990,7 +2056,7 @@ static int mount_root_tmpfs(const char *where, uid_t uid_shift, DissectImageFlag return r; } - if (uid_is_valid(uid_shift)) { + if (need_user_mapping(uid_shift, uid_range)) { if (asprintf(&options, "uid=" UID_FMT ",gid=" GID_FMT, uid_shift, (gid_t) uid_shift) < 0) return -ENOMEM; } @@ -2028,14 +2094,25 @@ int dissected_image_mount( const char *where, uid_t uid_shift, uid_t uid_range, + int userns_fd, DissectImageFlags flags) { + _cleanup_close_ int my_userns_fd = -EBADF; int r; assert(m); - assert(where); - /* Returns: + /* If 'where' is NULL then we'll use the new mount API to create fsmount() fds for the mounts and + * store them in DissectedPartition.fsmount_fd. + * + * If 'where' is not NULL then we'll either mount the partitions to the right places ourselves, + * or use DissectedPartition.fsmount_fd and bind it to the right places. + * + * This allows splitting the setting up up the superblocks and the binding to file systems paths into + * two distinct and differently privileged components: one that gets the fsmount fds, and the other + * that then applies them. + * + * Returns: * * -ENXIO → No root partition found * -EMEDIUMTYPE → DISSECT_IMAGE_VALIDATE_OS set but no os-release/extension-release file found @@ -2046,82 +2123,103 @@ int dissected_image_mount( * -EIDRM → File system is not among allowlisted "common" file systems */ + if (!where && (flags & (DISSECT_IMAGE_VALIDATE_OS|DISSECT_IMAGE_VALIDATE_OS_EXT)) != 0) + return -EOPNOTSUPP; /* for now, not supported */ + if (!(m->partitions[PARTITION_ROOT].found || (m->partitions[PARTITION_USR].found && FLAGS_SET(flags, DISSECT_IMAGE_USR_NO_ROOT)))) return -ENXIO; /* Require a root fs or at least a /usr/ fs (the latter is subject to a flag of its own) */ + if (userns_fd < 0 && need_user_mapping(uid_shift, uid_range) && FLAGS_SET(flags, DISSECT_IMAGE_MOUNT_IDMAPPED)) { + + my_userns_fd = make_userns(uid_shift, uid_range, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT); + if (my_userns_fd < 0) + return my_userns_fd; + + userns_fd = my_userns_fd; + } + if ((flags & DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY) == 0) { /* First mount the root fs. If there's none we use a tmpfs. */ - if (m->partitions[PARTITION_ROOT].found) - r = mount_partition(PARTITION_ROOT, m->partitions + PARTITION_ROOT, where, NULL, uid_shift, uid_range, flags); - else - r = mount_root_tmpfs(where, uid_shift, flags); - if (r < 0) - return r; + if (m->partitions[PARTITION_ROOT].found) { + r = mount_partition(PARTITION_ROOT, m->partitions + PARTITION_ROOT, where, NULL, uid_shift, uid_range, userns_fd, flags); + if (r < 0) + return r; + + } else if (where) { + r = mount_root_tmpfs(where, uid_shift, uid_range, flags); + if (r < 0) + return r; + } /* For us mounting root always means mounting /usr as well */ - r = mount_partition(PARTITION_USR, m->partitions + PARTITION_USR, where, "/usr", uid_shift, uid_range, flags); + r = mount_partition(PARTITION_USR, m->partitions + PARTITION_USR, where, "/usr", uid_shift, uid_range, userns_fd, flags); if (r < 0) return r; + } - if ((flags & (DISSECT_IMAGE_VALIDATE_OS|DISSECT_IMAGE_VALIDATE_OS_EXT)) != 0) { - /* If either one of the validation flags are set, ensure that the image qualifies - * as one or the other (or both). */ - bool ok = false; + if ((flags & DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY) == 0 && + (flags & (DISSECT_IMAGE_VALIDATE_OS|DISSECT_IMAGE_VALIDATE_OS_EXT)) != 0) { + /* If either one of the validation flags are set, ensure that the image qualifies as + * one or the other (or both). */ + bool ok = false; - if (FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS)) { - r = path_is_os_tree(where); + assert(where); + + if (FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS)) { + r = path_is_os_tree(where); + if (r < 0) + return r; + if (r > 0) + ok = true; + } + if (!ok && FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS_EXT)) { + r = extension_has_forbidden_content(where); + if (r < 0) + return r; + if (r == 0) { + r = path_is_extension_tree(IMAGE_SYSEXT, where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_EXTENSION_CHECK)); + if (r == 0) + r = path_is_extension_tree(IMAGE_CONFEXT, where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_EXTENSION_CHECK)); if (r < 0) return r; if (r > 0) ok = true; } - if (!ok && FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS_EXT)) { - r = extension_has_forbidden_content(where); - if (r < 0) - return r; - if (r == 0) { - r = path_is_extension_tree(IMAGE_SYSEXT, where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_EXTENSION_CHECK)); - if (r == 0) - r = path_is_extension_tree(IMAGE_CONFEXT, where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_EXTENSION_CHECK)); - if (r < 0) - return r; - if (r > 0) - ok = true; - } - } - - if (!ok) - return -ENOMEDIUM; } + + if (!ok) + return -ENOMEDIUM; } if (flags & DISSECT_IMAGE_MOUNT_ROOT_ONLY) return 0; - r = mount_partition(PARTITION_HOME, m->partitions + PARTITION_HOME, where, "/home", uid_shift, uid_range, flags); + r = mount_partition(PARTITION_HOME, m->partitions + PARTITION_HOME, where, "/home", uid_shift, uid_range, userns_fd, flags); if (r < 0) return r; - r = mount_partition(PARTITION_SRV, m->partitions + PARTITION_SRV, where, "/srv", uid_shift, uid_range, flags); + r = mount_partition(PARTITION_SRV, m->partitions + PARTITION_SRV, where, "/srv", uid_shift, uid_range, userns_fd, flags); if (r < 0) return r; - r = mount_partition(PARTITION_VAR, m->partitions + PARTITION_VAR, where, "/var", uid_shift, uid_range, flags); + r = mount_partition(PARTITION_VAR, m->partitions + PARTITION_VAR, where, "/var", uid_shift, uid_range, userns_fd, flags); if (r < 0) return r; - r = mount_partition(PARTITION_TMP, m->partitions + PARTITION_TMP, where, "/var/tmp", uid_shift, uid_range, flags); + r = mount_partition(PARTITION_TMP, m->partitions + PARTITION_TMP, where, "/var/tmp", uid_shift, uid_range, userns_fd, flags); if (r < 0) return r; - int slash_boot_is_available; - r = slash_boot_is_available = mount_point_is_available(where, "/boot", /* missing_ok = */ true); - if (r < 0) - return r; - if (r > 0) { - r = mount_partition(PARTITION_XBOOTLDR, m->partitions + PARTITION_XBOOTLDR, where, "/boot", uid_shift, uid_range, flags); + int slash_boot_is_available = 0; + if (where) { + r = slash_boot_is_available = mount_point_is_available(where, "/boot", /* missing_ok = */ true); + if (r < 0) + return r; + } + if (!where || slash_boot_is_available) { + r = mount_partition(PARTITION_XBOOTLDR, m->partitions + PARTITION_XBOOTLDR, where, "/boot", uid_shift, uid_range, userns_fd, flags); if (r < 0) return r; slash_boot_is_available = !r; @@ -2130,31 +2228,32 @@ int dissected_image_mount( if (m->partitions[PARTITION_ESP].found) { const char *esp_path = NULL; - /* Mount the ESP to /boot/ if it exists and is empty and we didn't already mount the XBOOTLDR - * partition into it. Otherwise, use /efi instead, but only if it exists and is empty. */ + if (where) { + /* Mount the ESP to /boot/ if it exists and is empty and we didn't already mount the + * XBOOTLDR partition into it. Otherwise, use /efi instead, but only if it exists + * and is empty. */ - if (slash_boot_is_available) { - r = mount_point_is_available(where, "/boot", /* missing_ok = */ false); - if (r < 0) - return r; - if (r > 0) - esp_path = "/boot"; + if (slash_boot_is_available) { + r = mount_point_is_available(where, "/boot", /* missing_ok = */ false); + if (r < 0) + return r; + if (r > 0) + esp_path = "/boot"; + } + + if (!esp_path) { + r = mount_point_is_available(where, "/efi", /* missing_ok = */ true); + if (r < 0) + return r; + if (r > 0) + esp_path = "/efi"; + } } - if (!esp_path) { - r = mount_point_is_available(where, "/efi", /* missing_ok = */ true); - if (r < 0) - return r; - if (r > 0) - esp_path = "/efi"; - } - - if (esp_path) { - /* OK, let's mount the ESP now (possibly creating the dir if missing) */ - r = mount_partition(PARTITION_ESP, m->partitions + PARTITION_ESP, where, esp_path, uid_shift, uid_range, flags); - if (r < 0) - return r; - } + /* OK, let's mount the ESP now (possibly creating the dir if missing) */ + r = mount_partition(PARTITION_ESP, m->partitions + PARTITION_ESP, where, esp_path, uid_shift, uid_range, userns_fd, flags); + if (r < 0) + return r; } return 0; @@ -2165,14 +2264,14 @@ int dissected_image_mount_and_warn( const char *where, uid_t uid_shift, uid_t uid_range, + int userns_fd, DissectImageFlags flags) { int r; assert(m); - assert(where); - r = dissected_image_mount(m, where, uid_shift, uid_range, flags); + r = dissected_image_mount(m, where, uid_shift, uid_range, userns_fd, flags); if (r == -ENXIO) return log_error_errno(r, "Not root file system found in image."); if (r == -EMEDIUMTYPE) @@ -3315,8 +3414,9 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ r = dissected_image_mount( m, t, - UID_INVALID, - UID_INVALID, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, extra_flags | DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_MOUNT_ROOT_ONLY | @@ -3755,6 +3855,7 @@ int mount_image_privately_interactively( "/run/systemd/mount-rootfs", /* uid_shift= */ UID_INVALID, /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, flags); if (r < 0) return r; @@ -3808,7 +3909,8 @@ int verity_dissect_and_mount( const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, - const char *required_sysext_scope) { + const char *required_sysext_scope, + DissectedImage **ret_image) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; @@ -3818,7 +3920,9 @@ int verity_dissect_and_mount( int r; assert(src); - assert(dest); + /* Verifying release metadata requires mounted image for now, so ensure the check is skipped when + * opening an image without mounting it immediately (i.e.: 'dest' is NULL). */ + assert(!required_host_os_release_id || dest); relax_extension_release_check = mount_options_relax_extension_release_checks(options); @@ -3875,14 +3979,22 @@ int verity_dissect_and_mount( if (r < 0) return log_debug_errno(r, "Failed to decrypt dissected image: %m"); - r = mkdir_p_label(dest, 0755); - if (r < 0) - return log_debug_errno(r, "Failed to create destination directory %s: %m", dest); - r = umount_recursive(dest, 0); - if (r < 0) - return log_debug_errno(r, "Failed to umount under destination directory %s: %m", dest); + if (dest) { + r = mkdir_p_label(dest, 0755); + if (r < 0) + return log_debug_errno(r, "Failed to create destination directory %s: %m", dest); + r = umount_recursive(dest, 0); + if (r < 0) + return log_debug_errno(r, "Failed to umount under destination directory %s: %m", dest); + } - r = dissected_image_mount(dissected_image, dest, UID_INVALID, UID_INVALID, dissect_image_flags); + r = dissected_image_mount( + dissected_image, + dest, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, + dissect_image_flags); if (r < 0) return log_debug_errno(r, "Failed to mount image: %m"); @@ -3928,5 +4040,8 @@ int verity_dissect_and_mount( if (r < 0) return log_debug_errno(r, "Failed to relinquish dissected image: %m"); + if (ret_image) + *ret_image = TAKE_PTR(dissected_image); + return 0; } diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 508085fd1a..eb0841bd2e 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -36,6 +36,7 @@ struct DissectedPartition { uint64_t size; uint64_t offset; uint64_t gpt_flags; + int fsmount_fd; }; #define DISSECTED_PARTITION_NULL \ @@ -43,6 +44,7 @@ struct DissectedPartition { .partno = -1, \ .architecture = _ARCHITECTURE_INVALID, \ .mount_node_fd = -EBADF, \ + .fsmount_fd = -EBADF, \ }) #define TAKE_PARTITION(p) \ ({ \ @@ -160,8 +162,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref); int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const VeritySettings *verity, DissectImageFlags flags); int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const VeritySettings *verity, DissectImageFlags flags); -int dissected_image_mount(DissectedImage *m, const char *dest, uid_t uid_shift, uid_t uid_range, DissectImageFlags flags); -int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, DissectImageFlags flags); +int dissected_image_mount(DissectedImage *m, const char *dest, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags); +int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags); int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_flags); @@ -192,7 +194,7 @@ bool dissected_image_verity_sig_ready(const DissectedImage *image, PartitionDesi int mount_image_privately_interactively(const char *path, const ImagePolicy *image_policy, DissectImageFlags flags, char **ret_directory, int *ret_dir_fd, LoopDevice **ret_loop_device); -int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const ImagePolicy *image_policy, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope); +int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const ImagePolicy *image_policy, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope, DissectedImage **ret_image); int dissect_fstype_ok(const char *fstype); diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 20f93b7c89..93e5af7e34 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -797,12 +797,16 @@ int mount_option_mangle( return 0; } -static int mount_in_namespace( - pid_t target, +static int mount_in_namespace_legacy( + const char *chased_src_path, + int chased_src_fd, + struct stat *chased_src_st, const char *propagate_path, const char *incoming_path, - const char *src, const char *dest, + int pidns_fd, + int mntns_fd, + int root_fd, bool read_only, bool make_file_or_directory, const MountOptions *options, @@ -810,52 +814,29 @@ static int mount_in_namespace( bool is_image) { _cleanup_close_pair_ int errno_pipe_fd[2] = PIPE_EBADF; - _cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF, pidns_fd = -EBADF, chased_src_fd = -EBADF; char mount_slave[] = "/tmp/propagate.XXXXXX", *mount_tmp, *mount_outside, *p; bool mount_slave_created = false, mount_slave_mounted = false, mount_tmp_created = false, mount_tmp_mounted = false, mount_outside_created = false, mount_outside_mounted = false; - _cleanup_free_ char *chased_src_path = NULL; - struct stat st; pid_t child; int r; - assert(target > 0); + assert(chased_src_path); + assert(chased_src_fd >= 0); + assert(chased_src_st); assert(propagate_path); assert(incoming_path); - assert(src); assert(dest); + assert(pidns_fd >= 0); + assert(mntns_fd >= 0); + assert(root_fd >= 0); assert(!options || is_image); - r = namespace_open(target, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd); - if (r < 0) - return log_debug_errno(r, "Failed to retrieve FDs of the target process' namespace: %m"); - - r = in_same_namespace(target, 0, NAMESPACE_MOUNT); - if (r < 0) - return log_debug_errno(r, "Failed to determine if mount namespaces are equal: %m"); - /* We can't add new mounts at runtime if the process wasn't started in a namespace */ - if (r > 0) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to activate bind mount in target, not running in a mount namespace"); - - /* One day, when bind mounting /proc/self/fd/n works across namespace boundaries we should rework - * this logic to make use of it... */ - p = strjoina(propagate_path, "/"); r = laccess(p, F_OK); if (r < 0) return log_debug_errno(r == -ENOENT ? SYNTHETIC_ERRNO(EOPNOTSUPP) : r, "Target does not allow propagation of mount points"); - r = chase(src, NULL, 0, &chased_src_path, &chased_src_fd); - if (r < 0) - return log_debug_errno(r, "Failed to resolve source path of %s: %m", src); - log_debug("Chased source path of %s to %s", src, chased_src_path); - - if (fstat(chased_src_fd, &st) < 0) - return log_debug_errno(errno, "Failed to stat() resolved source path %s: %m", src); - if (S_ISLNK(st.st_mode)) /* This shouldn't really happen, given that we just chased the symlinks above, but let's better be safe… */ - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Source directory %s can't be a symbolic link", src); - /* Our goal is to install a new bind mount into the container, possibly read-only. This is irritatingly complex unfortunately, currently. @@ -885,7 +866,7 @@ static int mount_in_namespace( if (is_image) r = mkdir_p(mount_tmp, 0700); else - r = make_mount_point_inode_from_stat(&st, mount_tmp, 0700); + r = make_mount_point_inode_from_stat(chased_src_st, mount_tmp, 0700); if (r < 0) { log_debug_errno(r, "Failed to create temporary mount point %s: %m", mount_tmp); goto finish; @@ -894,7 +875,17 @@ static int mount_in_namespace( mount_tmp_created = true; if (is_image) - r = verity_dissect_and_mount(chased_src_fd, chased_src_path, mount_tmp, options, image_policy, NULL, NULL, NULL, NULL); + r = verity_dissect_and_mount( + chased_src_fd, + chased_src_path, + mount_tmp, + options, + image_policy, + /* required_host_os_release_id= */ NULL, + /* required_host_os_release_version_id= */ NULL, + /* required_host_os_release_sysext_level= */ NULL, + /* required_sysext_scope= */ NULL, + /* ret_image= */ NULL); else r = mount_follow_verbose(LOG_DEBUG, FORMAT_PROC_FD_PATH(chased_src_fd), mount_tmp, NULL, MS_BIND, NULL); if (r < 0) @@ -913,7 +904,7 @@ static int mount_in_namespace( * right-away. */ mount_outside = strjoina(propagate_path, "/XXXXXX"); - if (is_image || S_ISDIR(st.st_mode)) + if (is_image || S_ISDIR(chased_src_st->st_mode)) r = mkdtemp(mount_outside) ? 0 : -errno; else { r = mkostemp_safe(mount_outside); @@ -933,7 +924,7 @@ static int mount_in_namespace( mount_outside_mounted = true; mount_tmp_mounted = false; - if (is_image || S_ISDIR(st.st_mode)) + if (is_image || S_ISDIR(chased_src_st->st_mode)) (void) rmdir(mount_tmp); else (void) unlink(mount_tmp); @@ -962,7 +953,7 @@ static int mount_in_namespace( if (make_file_or_directory) { if (!is_image) { (void) mkdir_parents(dest, 0755); - (void) make_mount_point_inode_from_stat(&st, dest, 0700); + (void) make_mount_point_inode_from_stat(chased_src_st, dest, 0700); } else (void) mkdir_p(dest, 0755); } @@ -1012,7 +1003,7 @@ finish: if (mount_outside_mounted) (void) umount_verbose(LOG_DEBUG, mount_outside, UMOUNT_NOFOLLOW); if (mount_outside_created) { - if (is_image || S_ISDIR(st.st_mode)) + if (is_image || S_ISDIR(chased_src_st->st_mode)) (void) rmdir(mount_outside); else (void) unlink(mount_outside); @@ -1021,7 +1012,7 @@ finish: if (mount_tmp_mounted) (void) umount_verbose(LOG_DEBUG, mount_tmp, UMOUNT_NOFOLLOW); if (mount_tmp_created) { - if (is_image || S_ISDIR(st.st_mode)) + if (is_image || S_ISDIR(chased_src_st->st_mode)) (void) rmdir(mount_tmp); else (void) unlink(mount_tmp); @@ -1035,6 +1026,183 @@ finish: return r; } +static int mount_in_namespace( + pid_t target, + const char *propagate_path, + const char *incoming_path, + const char *src, + const char *dest, + bool read_only, + bool make_file_or_directory, + const MountOptions *options, + const ImagePolicy *image_policy, + bool is_image) { + + _cleanup_(dissected_image_unrefp) DissectedImage *img = NULL; + _cleanup_close_pair_ int errno_pipe_fd[2] = PIPE_EBADF; + _cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF, pidns_fd = -EBADF, chased_src_fd = -EBADF, + new_mount_fd = -EBADF; + _cleanup_free_ char *chased_src_path = NULL; + struct stat st; + pid_t child; + int r; + + assert(target > 0); + assert(propagate_path); + assert(incoming_path); + assert(src); + assert(dest); + assert(!options || is_image); + + r = namespace_open(target, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd); + if (r < 0) + return log_debug_errno(r, "Failed to retrieve FDs of the target process' namespace: %m"); + + r = in_same_namespace(target, 0, NAMESPACE_MOUNT); + if (r < 0) + return log_debug_errno(r, "Failed to determine if mount namespaces are equal: %m"); + /* We can't add new mounts at runtime if the process wasn't started in a namespace */ + if (r > 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to activate bind mount in target, not running in a mount namespace"); + + r = chase(src, NULL, 0, &chased_src_path, &chased_src_fd); + if (r < 0) + return log_debug_errno(r, "Failed to resolve source path of %s: %m", src); + log_debug("Chased source path of %s to %s", src, chased_src_path); + + if (fstat(chased_src_fd, &st) < 0) + return log_debug_errno(errno, "Failed to stat() resolved source path %s: %m", src); + if (S_ISLNK(st.st_mode)) /* This shouldn't really happen, given that we just chased the symlinks above, but let's better be safe… */ + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Source directory %s can't be a symbolic link", src); + + if (!mount_new_api_supported()) /* Fallback if we can't use the new mount API */ + return mount_in_namespace_legacy( + chased_src_path, + chased_src_fd, + &st, + propagate_path, + incoming_path, + dest, + pidns_fd, + mntns_fd, + root_fd, + read_only, + make_file_or_directory, + options, + image_policy, + is_image); + + if (is_image) { + r = verity_dissect_and_mount( + chased_src_fd, + chased_src_path, + /* dest= */ NULL, + options, + image_policy, + /* required_host_os_release_id= */ NULL, + /* required_host_os_release_version_id= */ NULL, + /* required_host_os_release_sysext_level= */ NULL, + /* required_sysext_scope= */ NULL, + &img); + if (r < 0) + return log_debug_errno( + r, + "Failed to dissect and mount image %s: %m", + chased_src_path); + } else { + new_mount_fd = open_tree( + chased_src_fd, + "", + OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH); + if (new_mount_fd < 0) + return log_debug_errno( + errno, + "Failed to open mount point \"%s\": %m", + chased_src_path); + + if (read_only && mount_setattr(new_mount_fd, "", AT_EMPTY_PATH, + &(struct mount_attr) { + .attr_set = MOUNT_ATTR_RDONLY, + }, MOUNT_ATTR_SIZE_VER0) < 0) + return log_debug_errno( + errno, + "Failed to set mount flags for \"%s\": %m", + chased_src_path); + } + + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) + return log_debug_errno(errno, "Failed to create pipe: %m"); + + r = namespace_fork("(sd-bindmnt)", + "(sd-bindmnt-inner)", + /* except_fds= */ NULL, + /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_DEATHSIG, + pidns_fd, + mntns_fd, + /* netns_fd= */ -1, + /* userns_fd= */ -1, + root_fd, + &child); + if (r < 0) + return log_debug_errno(r, "Failed to fork off: %m"); + if (r == 0) { + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + if (make_file_or_directory) + (void) mkdir_parents(dest, 0755); + + if (img) { + DissectImageFlags f = 0; + + if (make_file_or_directory) + f |= DISSECT_IMAGE_MKDIR; + + if (read_only) + f |= DISSECT_IMAGE_READ_ONLY; + + r = dissected_image_mount( + img, + dest, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, + f); + } else { + if (make_file_or_directory) + (void) make_mount_point_inode_from_stat(&st, dest, 0700); + + r = RET_NERRNO(move_mount(new_mount_fd, + "", + -EBADF, + dest, + MOVE_MOUNT_F_EMPTY_PATH)); + } + if (r < 0) { + (void) write(errno_pipe_fd[1], &r, sizeof(r)); + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + r = wait_for_terminate_and_check("(sd-bindmnt)", child, 0); + if (r < 0) + return log_debug_errno(r, "Failed to wait for child: %m"); + if (r != EXIT_SUCCESS) { + if (read(errno_pipe_fd[0], &r, sizeof(r)) == sizeof(r)) + return log_debug_errno(r, "Failed to mount: %m"); + + return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Child failed."); + } + + return 0; +} + int bind_mount_in_namespace( pid_t target, const char *propagate_path, @@ -1460,3 +1628,88 @@ int mount_credentials_fs(const char *path, size_t size, bool ro) { credentials_fs_mount_flags(ro), opts); } + +int make_fsmount( + int error_log_level, + const char *what, + const char *type, + unsigned long flags, + const char *options, + int userns_fd) { + + _cleanup_close_ int fs_fd = -EBADF, mnt_fd = -EBADF; + _cleanup_free_ char *o = NULL; + unsigned long f; + int r; + + assert(type); + assert(what); + + r = mount_option_mangle(options, flags, &f, &o); + if (r < 0) + return log_full_errno( + error_log_level, r, "Failed to mangle mount options %s: %m", + strempty(options)); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *fl = NULL; + (void) mount_flags_to_string(f, &fl); + + log_debug("Creating mount fd for %s (%s) (%s \"%s\")...", + strna(what), strna(type), strnull(fl), strempty(o)); + } + + fs_fd = fsopen(type, FSOPEN_CLOEXEC); + if (fs_fd < 0) + return log_full_errno(error_log_level, errno, "Failed to open superblock for \"%s\": %m", type); + + if (fsconfig(fs_fd, FSCONFIG_SET_STRING, "source", what, 0) < 0) + return log_full_errno(error_log_level, errno, "Failed to set mount source for \"%s\" to \"%s\": %m", type, what); + + if (FLAGS_SET(f, MS_RDONLY)) + if (fsconfig(fs_fd, FSCONFIG_SET_FLAG, "ro", NULL, 0) < 0) + return log_full_errno(error_log_level, errno, "Failed to set read only mount flag for \"%s\": %m", type); + + for (const char *p = o;;) { + _cleanup_free_ char *word = NULL; + char *eq; + + r = extract_first_word(&p, &word, ",", EXTRACT_KEEP_QUOTE); + if (r < 0) + return log_full_errno(error_log_level, r, "Failed to parse mount option string \"%s\": %m", o); + if (r == 0) + break; + + eq = strchr(word, '='); + if (eq) { + *eq = 0; + eq++; + + if (fsconfig(fs_fd, FSCONFIG_SET_STRING, word, eq, 0) < 0) + return log_full_errno(error_log_level, errno, "Failed to set mount option \"%s=%s\" for \"%s\": %m", word, eq, type); + } else { + if (fsconfig(fs_fd, FSCONFIG_SET_FLAG, word, NULL, 0) < 0) + return log_full_errno(error_log_level, errno, "Failed to set mount flag \"%s\" for \"%s\": %m", word, type); + } + } + + if (fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) + return log_full_errno(error_log_level, errno, "Failed to realize fs fd for \"%s\" (\"%s\"): %m", what, type); + + mnt_fd = fsmount(fs_fd, FSMOUNT_CLOEXEC, 0); + if (mnt_fd < 0) + return log_full_errno(error_log_level, errno, "Failed to create mount fd for \"%s\" (\"%s\"): %m", what, type); + + if (mount_setattr(mnt_fd, "", AT_EMPTY_PATH|AT_RECURSIVE, + &(struct mount_attr) { + .attr_set = ms_flags_to_mount_attr(f) | (userns_fd >= 0 ? MOUNT_ATTR_IDMAP : 0), + .userns_fd = userns_fd, + }, MOUNT_ATTR_SIZE_VER0) < 0) + return log_full_errno(error_log_level, + errno, + "Failed to set mount flags for \"%s\" (\"%s\"): %m", + what, + type); + + return TAKE_FD(mnt_fd); +} diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h index fb496e156b..a56530c42e 100644 --- a/src/shared/mount-util.h +++ b/src/shared/mount-util.h @@ -136,3 +136,5 @@ int trigger_automount_at(int dir_fd, const char *path); unsigned long credentials_fs_mount_flags(bool ro); int mount_credentials_fs(const char *path, size_t size, bool ro); + +int make_fsmount(int error_log_level, const char *what, const char *type, unsigned long flags, const char *options, int userns_fd); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 5099bdcb4e..33bfb3dc80 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -712,8 +712,9 @@ static int merge_subprocess(Hashmap *images, const char *workspace) { r = dissected_image_mount_and_warn( m, p, - UID_INVALID, - UID_INVALID, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, flags); if (r < 0 && r != -ENOMEDIUM) return r; diff --git a/src/test/test-loop-block.c b/src/test/test-loop-block.c index f76d3543eb..1bd00d1af1 100644 --- a/src/test/test-loop-block.c +++ b/src/test/test-loop-block.c @@ -101,7 +101,13 @@ static void* thread_func(void *ptr) { verify_dissected_image(dissected); - r = dissected_image_mount(dissected, mounted, UID_INVALID, UID_INVALID, DISSECT_IMAGE_READ_ONLY); + r = dissected_image_mount( + dissected, + mounted, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, + DISSECT_IMAGE_READ_ONLY); log_notice_errno(r, "Mounted %s → %s: %m", loop->node, mounted); assert_se(r >= 0); @@ -291,7 +297,13 @@ static int run(int argc, char *argv[]) { assert_se(detach_mount_namespace() >= 0); /* This first (writable) mount will initialize the mount point dirs, so that the subsequent read-only ones can work */ - assert_se(dissected_image_mount(dissected, mounted, UID_INVALID, UID_INVALID, 0) >= 0); + assert_se(dissected_image_mount( + dissected, + mounted, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* usernfs_fd= */ -EBADF, + 0) >= 0); /* Now we mounted everything, the partitions are pinned. Now it's fine to release the lock * fully. This means udev could now issue BLKRRPART again, but that's OK given this will fail because diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index 44889779cc..7bede7da1a 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -351,7 +351,7 @@ MountAPIVFS=yes PrivateTmp=yes ExecStart=/bin/sh -c ' \\ systemd-notify --ready; \\ - while [[ ! -f /tmp/img/usr/lib/os-release ]] || ! grep -q -F MARKER /tmp/img/usr/lib/os-release; do \\ + while [ ! -f /tmp/img/usr/lib/os-release ] || ! grep -q -F MARKER /tmp/img/usr/lib/os-release; do \\ sleep 0.1; \\ done; \\ mount; \\