From c7986bc9b64e095399c3e380441b4de26d1276a1 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 27 Sep 2023 01:37:59 +0100 Subject: [PATCH 1/7] test: fix check in test unit in TEST-50-DISSECT '[[ not found' --- test/units/testsuite-50.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; \\ From f273c09c5138d73697a5d8c6e6dc6974a43ae4a7 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 28 Sep 2023 13:19:45 +0100 Subject: [PATCH 2/7] mountpoint-util: add bool mount_new_api_supported() helper --- src/basic/mountpoint-util.c | 18 ++++++++++++++++++ src/basic/mountpoint-util.h | 1 + 2 files changed, 19 insertions(+) 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); From a7e0199ecef8c0f8c413c069306cf6cc85a445ff Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 27 Sep 2023 00:01:45 +0100 Subject: [PATCH 3/7] mount-util: use new mount API for bind mount tunnel If we have the new mount API we can simplify bind mounting into a namespace a lot --- src/shared/mount-util.c | 190 ++++++++++++++++++++++++++++++++-------- 1 file changed, 152 insertions(+), 38 deletions(-) diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 20f93b7c89..8acbadcaf9 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; @@ -913,7 +894,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 +914,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 +943,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 +993,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 +1002,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 +1016,139 @@ 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_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 (is_image || !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); + + 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); + (void) make_mount_point_inode_from_stat(&st, dest, 0700); + } + + if (move_mount(new_mount_fd, "", -EBADF, dest, MOVE_MOUNT_F_EMPTY_PATH) < 0) { + (void) write(errno_pipe_fd[1], &errno, sizeof(errno)); + 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, From 44cc82bfbf160bba2562bec08ffacbd587771210 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 14 Mar 2023 11:09:57 +0100 Subject: [PATCH 4/7] mount-util: add make_fsmount() --- src/shared/mount-util.c | 85 +++++++++++++++++++++++++++++++++++++++++ src/shared/mount-util.h | 2 + 2 files changed, 87 insertions(+) diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 8acbadcaf9..8ba054471f 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -1574,3 +1574,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); From 8d9a1d5979a9290f23925f482690fee680990a8e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 14 Mar 2023 11:09:46 +0100 Subject: [PATCH 5/7] dissect-image: optionally allow mounting via new kernel mount API in two steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for the new fsmount() logic of the kernel: we'll first create an unattached fsmount fd, and then in a second step attach this to some real file system inode – as opposed to attaching file system directly. The benefit of this is that we can pass the open fsmount fds over some sockets if need be, to isolate the mounting code from the attaching code. --- src/core/namespace.c | 8 +- src/dissect/dissect.c | 24 ++- src/nspawn/nspawn.c | 2 + src/portable/portable.c | 8 +- src/shared/dissect-image.c | 391 +++++++++++++++++++++++-------------- src/shared/dissect-image.h | 6 +- src/sysext/sysext.c | 5 +- src/test/test-loop-block.c | 16 +- 8 files changed, 307 insertions(+), 153 deletions(-) diff --git a/src/core/namespace.c b/src/core/namespace.c index 206453f30f..dc3fa8d4a2 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -2494,7 +2494,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..2036b5adba 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; @@ -3882,7 +3983,13 @@ int verity_dissect_and_mount( 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"); diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 508085fd1a..7a8d5ff81d 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); 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 From 3e1072726b47329c438b0abd8e7641c7baf35115 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 27 Sep 2023 01:04:44 +0100 Subject: [PATCH 6/7] dissect-image: support verity_dissect_and_mount() in two steps too With the new mount API first the image is opened, and later mounted --- src/core/namespace.c | 3 ++- src/shared/dissect-image.c | 24 ++++++++++++++++-------- src/shared/dissect-image.h | 2 +- src/shared/mount-util.c | 12 +++++++++++- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/core/namespace.c b/src/core/namespace.c index dc3fa8d4a2..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) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 2036b5adba..5c30e4f0af 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -3909,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; @@ -3919,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); @@ -3976,12 +3979,14 @@ 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, @@ -4035,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 7a8d5ff81d..eb0841bd2e 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -194,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 8ba054471f..813c2bc519 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -875,7 +875,17 @@ static int mount_in_namespace_legacy( 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) From ddba5a1ad6e6340970f7ec46db8c323ff9645576 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 27 Sep 2023 01:37:25 +0100 Subject: [PATCH 7/7] mount-util: use new mount API for image mount tunnel --- src/shared/mount-util.c | 90 ++++++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 813c2bc519..93e5af7e34 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -1038,6 +1038,7 @@ static int mount_in_namespace( 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; @@ -1074,7 +1075,7 @@ static int mount_in_namespace( 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 (is_image || !mount_new_api_supported()) /* Fallback if we can't use the new mount API */ + 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, @@ -1091,24 +1092,43 @@ static int mount_in_namespace( image_policy, is_image); - 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 (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 (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"); @@ -1129,13 +1149,37 @@ static int mount_in_namespace( if (r == 0) { errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); - if (make_file_or_directory) { + if (make_file_or_directory) (void) mkdir_parents(dest, 0755); - (void) make_mount_point_inode_from_stat(&st, dest, 0700); - } - if (move_mount(new_mount_fd, "", -EBADF, dest, MOVE_MOUNT_F_EMPTY_PATH) < 0) { - (void) write(errno_pipe_fd[1], &errno, sizeof(errno)); + 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);