From 1be8caa6be6f5a10a7dea5ac562a0df5c5fac2e9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 18 Aug 2025 23:18:18 +0200 Subject: [PATCH] importd: support unpacking tarballs to foreign UID range When invoked unprivileged, let's use a transiently allocated userns, so that we can properly untar UIDs/GIDs so that the trees appear owned by the foreign UID/GID range. --- src/import/import-common.c | 161 ++++++++++++++++++++++++++++++++++++- src/import/import-common.h | 20 +++-- src/import/import-tar.c | 64 ++++++++++----- src/import/import.c | 3 + src/import/pull-tar.c | 148 +++++++++++++++++++++++++--------- src/import/pull.c | 3 + src/shared/dissect-image.c | 111 +++++++++++++++++++++++-- src/shared/dissect-image.h | 4 + 8 files changed, 439 insertions(+), 75 deletions(-) diff --git a/src/import/import-common.c b/src/import/import-common.c index 31bc7c95df5..d8fd6e39825 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -6,21 +6,26 @@ #include "sd-event.h" #include "capability-util.h" +#include "copy.h" #include "dirent-util.h" +#include "dissect-image.h" #include "fd-util.h" #include "fs-util.h" #include "import-common.h" #include "libarchive-util.h" #include "log.h" +#include "namespace-util.h" +#include "nsresource.h" #include "os-util.h" #include "pidref.h" #include "process-util.h" +#include "rm-rf.h" #include "selinux-util.h" #include "stat-util.h" #include "tar-util.h" #include "tmpfile-util.h" -int import_fork_tar_x(int tree_fd, PidRef *ret_pid) { +int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) { int r; assert(tree_fd >= 0); @@ -41,7 +46,7 @@ int import_fork_tar_x(int tree_fd, PidRef *ret_pid) { r = pidref_safe_fork_full( "tar-x", /* stdio_fds= */ NULL, - (int[]) { tree_fd, pipefd[0] }, 2, + (int[]) { tree_fd, pipefd[0], userns_fd }, userns_fd >= 0 ? 3 : 2, FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG, ret_pid); if (r < 0) @@ -58,6 +63,14 @@ int import_fork_tar_x(int tree_fd, PidRef *ret_pid) { /* Child */ + if (userns_fd >= 0) { + r = detach_mount_namespace_userns(userns_fd); + if (r < 0) { + log_error_errno(r, "Failed to join user namespace: %m"); + _exit(EXIT_FAILURE); + } + } + if (unshare(CLONE_NEWNET) < 0) log_warning_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); @@ -291,3 +304,147 @@ int import_allocate_event_with_signals(sd_event **ret) { *ret = TAKE_PTR(event); return 0; } + +int import_make_foreign_userns(int *userns_fd) { + assert(userns_fd); + + if (*userns_fd >= 0) + return 0; + + *userns_fd = nsresource_allocate_userns(/* name= */ NULL, NSRESOURCE_UIDS_64K); /* allocate 64K users */ + if (*userns_fd < 0) + return log_error_errno(*userns_fd, "Failed to allocate transient user namespace: %m"); + + return 1; +} + +int import_copy_foreign( + int source_fd, + int target_fd, + int *userns_fd) { + + int r; + + assert(source_fd >= 0); + assert(target_fd >= 0); + assert(userns_fd); + + /* Copies dir referenced by source_fd into dir referenced by source_fd, moves to the specified userns + * for that (allocated if needed), which should be foreign UID range */ + + r = import_make_foreign_userns(userns_fd); + if (r < 0) + return r; + + r = safe_fork_full( + "copy-tree", + /* stdio_fds= */ NULL, + (int[]) { *userns_fd, source_fd, target_fd }, 3, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT, + /* ret_pid= */ NULL); + if (r < 0) + return r; + if (r == 0) { + r = namespace_enter( + /* pidns_fd= */ -EBADF, + /* mntns_fd= */ -EBADF, + /* netns_fd= */ -EBADF, + *userns_fd, + /* root_fd= */ -EBADF); + if (r < 0) { + log_error_errno(r, "Failed to join user namespace: %m"); + _exit(EXIT_FAILURE); + } + + r = copy_tree_at( + source_fd, /* from= */ NULL, + target_fd, /* to = */ NULL, + /* override_uid= */ UID_INVALID, + /* override_gid= */ GID_INVALID, + COPY_REFLINK|COPY_HARDLINKS|COPY_MERGE_EMPTY|COPY_MERGE_APPLY_STAT|COPY_SAME_MOUNT|COPY_ALL_XATTRS, + /* denylist= */ NULL, + /* subvolumes= */ NULL); + if (r < 0) { + log_error_errno(r, "Failed to copy tree: %m"); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + return 0; +} + +int import_remove_tree_foreign(const char *path, int *userns_fd) { + int r; + + assert(path); + assert(userns_fd); + + r = import_make_foreign_userns(userns_fd); + if (r < 0) + return r; + + _cleanup_close_ int tree_fd = -EBADF; + r = mountfsd_mount_directory( + path, + *userns_fd, + DISSECT_IMAGE_FOREIGN_UID, + &tree_fd); + if (r < 0) + return r; + + r = safe_fork_full( + "rm-tree", + /* stdio_fds= */ NULL, + (int[]) { *userns_fd, tree_fd }, 2, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT, + /* ret_pid= */ NULL); + if (r < 0) + return r; + if (r == 0) { + /* child */ + + r = namespace_enter( + /* pidns_fd= */ -EBADF, + /* mntns_fd= */ -EBADF, + /* netns_fd= */ -EBADF, + *userns_fd, + /* root_fd= */ -EBADF); + if (r < 0) { + log_error_errno(r, "Failed to join user namespace: %m"); + _exit(EXIT_FAILURE); + } + + _cleanup_close_ int dfd = fd_reopen(tree_fd, O_DIRECTORY|O_CLOEXEC); + if (dfd < 0) { + log_error_errno(r, "Failed to reopen tree fd: %m"); + _exit(EXIT_FAILURE); + } + + r = rm_rf_children(dfd, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD, /* root_dev= */ NULL); + if (r < 0) + log_warning_errno(r, "Failed to empty '%s' directory in foreign UID mode, ignoring: %m", path); + + _exit(EXIT_SUCCESS); + } + + return 0; +} + +int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags) { + int r; + + assert(path); + assert(userns_fd); + + /* Try the userns dance first, to remove foreign UID range owned trees */ + if (FLAGS_SET(flags, IMPORT_FOREIGN_UID)) + (void) import_remove_tree_foreign(path, userns_fd); + + r = rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD); + if (r < 0) + return log_error_errno(r, "Failed to remove '%s': %m", path); + + return 0; +} diff --git a/src/import/import-common.h b/src/import/import-common.h index 55d00ea4668..6c6ac655b0f 100644 --- a/src/import/import-common.h +++ b/src/import/import-common.h @@ -15,15 +15,16 @@ typedef enum ImportFlags { IMPORT_CONVERT_QCOW2 = 1 << 5, /* raw: if we detect a qcow2 image, unpack it */ IMPORT_DIRECT = 1 << 6, /* import without rename games */ IMPORT_SYNC = 1 << 7, /* fsync() right before we are done */ + IMPORT_FOREIGN_UID = 1 << 8, /* tar: go via nsresourced/mountfsd and make owned by foreign UID */ /* When pulling these flags are defined too */ - IMPORT_PULL_SETTINGS = 1 << 8, /* download .nspawn settings file */ - IMPORT_PULL_ROOTHASH = 1 << 9, /* only for raw: download .roothash file for verity */ - IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 10, /* only for raw: download .roothash.p7s file for verity */ - IMPORT_PULL_VERITY = 1 << 11, /* only for raw: download .verity file for verity */ + IMPORT_PULL_SETTINGS = 1 << 9, /* download .nspawn settings file */ + IMPORT_PULL_ROOTHASH = 1 << 10, /* only for raw: download .roothash file for verity */ + IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 11, /* only for raw: download .roothash.p7s file for verity */ + IMPORT_PULL_VERITY = 1 << 12, /* only for raw: download .verity file for verity */ /* The supported flags for the tar and the raw importing */ - IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC, + IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC|IMPORT_FOREIGN_UID, IMPORT_FLAGS_MASK_RAW = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_CONVERT_QCOW2|IMPORT_DIRECT|IMPORT_SYNC, /* The supported flags for the tar and the raw pulling */ @@ -34,7 +35,7 @@ typedef enum ImportFlags { } ImportFlags; int import_fork_tar_c(const char *path, PidRef *ret); -int import_fork_tar_x(int tree_fd, PidRef *ret_pid); +int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid); int import_mangle_os_tree(const char *path); @@ -42,4 +43,11 @@ bool import_validate_local(const char *name, ImportFlags flags); int import_allocate_event_with_signals(sd_event **ret); +int import_make_foreign_userns(int *userns_fd); + +int import_copy_foreign(int source_fd, int target_fd, int *userns_fd); + +int import_remove_tree_foreign(const char *path, int *userns_fd); +int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags); + #define IMPORT_BUFFER_SIZE (128U*1024U) diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 7e5499b2b99..85fbf5abd14 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "btrfs-util.h" +#include "dissect-image.h" #include "errno-util.h" #include "fd-util.h" #include "format-util.h" @@ -46,6 +47,7 @@ typedef struct TarImport { int input_fd; int tar_fd; int tree_fd; + int userns_fd; ImportCompress compress; @@ -73,7 +75,10 @@ TarImport* tar_import_unref(TarImport *i) { pidref_done_sigkill_wait(&i->tar_pid); - rm_rf_subvolume_and_free(i->temp_path); + if (i->temp_path) { + import_remove_tree(i->temp_path, &i->userns_fd, i->flags); + free(i->temp_path); + } import_compress_free(&i->compress); @@ -81,6 +86,7 @@ TarImport* tar_import_unref(TarImport *i) { safe_close(i->tar_fd); safe_close(i->tree_fd); + safe_close(i->userns_fd); free(i->final_path); free(i->image_root); @@ -114,6 +120,7 @@ int tar_import_new( .input_fd = -EBADF, .tar_fd = -EBADF, .tree_fd = -EBADF, + .userns_fd = -EBADF, .on_finished = on_finished, .userdata = userdata, .last_percent = UINT_MAX, @@ -200,8 +207,8 @@ static int tar_import_finish(TarImport *i) { AT_FDCWD, d, AT_FDCWD, i->final_path, (i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) | - (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) | - (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0)); if (r < 0) return log_error_errno(r, "Failed to move '%s' into place: %m", i->final_path ?: i->local); @@ -244,26 +251,41 @@ static int tar_import_fork_tar(TarImport *i) { if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE)) (void) rm_rf(d, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - if (i->flags & IMPORT_BTRFS_SUBVOL) - r = btrfs_subvol_make_fallback(AT_FDCWD, d, 0755); - else - r = RET_NERRNO(mkdir(d, 0755)); - if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, - * because in that case our temporary path collided */ - r = 0; - if (r < 0) - return log_error_errno(r, "Failed to create directory/subvolume %s: %m", d); - if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */ - if (!(i->flags & IMPORT_DIRECT)) - (void) import_assign_pool_quota_and_warn(root); - (void) import_assign_pool_quota_and_warn(d); - } + if (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) { + r = import_make_foreign_userns(&i->userns_fd); + if (r < 0) + return r; + + _cleanup_close_ int directory_fd = -EBADF; + r = mountfsd_make_directory(d, /* flags= */ 0, &directory_fd); + if (r < 0) + return r; + + r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd); + if (r < 0) + return r; + } else { + if (i->flags & IMPORT_BTRFS_SUBVOL) + r = btrfs_subvol_make_fallback(AT_FDCWD, d, 0755); + else + r = RET_NERRNO(mkdir(d, 0755)); + if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, + * because in that case our temporary path collided */ + r = 0; + if (r < 0) + return log_error_errno(r, "Failed to create directory/subvolume %s: %m", d); + if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */ + if (!(i->flags & IMPORT_DIRECT)) + (void) import_assign_pool_quota_and_warn(root); + (void) import_assign_pool_quota_and_warn(d); + } - i->tree_fd = open(d, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (i->tree_fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", d); + i->tree_fd = open(d, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (i->tree_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", d); + } - i->tar_fd = import_fork_tar_x(i->tree_fd, &i->tar_pid); + i->tar_fd = import_fork_tar_x(i->tree_fd, i->userns_fd, &i->tar_pid); if (i->tar_fd < 0) return i->tar_fd; diff --git a/src/import/import.c b/src/import/import.c index 531c0820770..5276f26977a 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -471,6 +471,9 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to pick image root: %m"); } + if (arg_runtime_scope == RUNTIME_SCOPE_USER) + arg_import_flags |= IMPORT_FOREIGN_UID; + return 1; } diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index fa44216f8cf..cf9e2060da7 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -9,11 +9,10 @@ #include "btrfs-util.h" #include "copy.h" #include "curl-util.h" +#include "dissect-image.h" #include "errno-util.h" #include "fd-util.h" #include "fs-util.h" -#include "import-common.h" -#include "import-util.h" #include "install-file.h" #include "log.h" #include "mkdir-label.h" @@ -26,6 +25,7 @@ #include "rm-rf.h" #include "string-util.h" #include "tmpfile-util.h" +#include "uid-classification.h" #include "web-util.h" typedef enum TarProgress { @@ -64,6 +64,7 @@ typedef struct TarPull { char *checksum; int tree_fd; + int userns_fd; } TarPull; TarPull* tar_pull_unref(TarPull *i) { @@ -80,7 +81,10 @@ TarPull* tar_pull_unref(TarPull *i) { curl_glue_unref(i->glue); sd_event_unref(i->event); - rm_rf_subvolume_and_free(i->temp_path); + if (i->temp_path) { + import_remove_tree(i->temp_path, &i->userns_fd, i->flags); + free(i->temp_path); + } unlink_and_free(i->settings_temp_path); free(i->final_path); @@ -90,6 +94,7 @@ TarPull* tar_pull_unref(TarPull *i) { free(i->checksum); safe_close(i->tree_fd); + safe_close(i->userns_fd); return mfree(i); } @@ -138,6 +143,7 @@ int tar_pull_new( .glue = TAKE_PTR(g), .tar_pid = PIDREF_NULL, .tree_fd = -EBADF, + .userns_fd = -EBADF, }; i->glue->on_finished = pull_job_curl_on_finished; @@ -233,6 +239,9 @@ static int tar_pull_make_local_copy(TarPull *i) { if (!i->local) return 0; + /* Creates a copy/clone of the original downloaded version (which is supposed to remain untouched) + * under a local image name (which may then be modified) */ + assert(i->final_path); p = path_join(i->image_root, i->local); @@ -244,18 +253,61 @@ static int tar_pull_make_local_copy(TarPull *i) { if (r < 0) return log_error_errno(r, "Failed to generate temporary filename for %s: %m", p); - if (i->flags & IMPORT_BTRFS_SUBVOL) - r = btrfs_subvol_snapshot_at( - AT_FDCWD, i->final_path, - AT_FDCWD, t, - (i->flags & IMPORT_BTRFS_QUOTA ? BTRFS_SNAPSHOT_QUOTA : 0)| - BTRFS_SNAPSHOT_FALLBACK_COPY| - BTRFS_SNAPSHOT_FALLBACK_DIRECTORY| - BTRFS_SNAPSHOT_RECURSIVE); - else - r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to create local image: %m"); + if (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) { + /* Copy in userns */ + + r = import_make_foreign_userns(&i->userns_fd); + if (r < 0) + return r; + + /* Usually, tar_pull_job_on_open_disk_tar() would allocate ->tree_fd for us, but if + * already downloaded the image before, and are just making a copy of the original + * download, we need to open ->tree_fd now */ + if (i->tree_fd < 0) { + _cleanup_close_ int directory_fd = open(i->final_path, O_DIRECTORY|O_CLOEXEC); + if (directory_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", i->final_path); + + struct stat st; + if (fstat(directory_fd, &st) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", i->final_path); + + if (uid_is_foreign(st.st_uid)) { + r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd); + if (r < 0) + return r; + } else + i->tree_fd = TAKE_FD(directory_fd); + } + + _cleanup_close_ int directory_fd = -EBADF; + r = mountfsd_make_directory(t, /* flags= */ 0, &directory_fd); + if (r < 0) + return r; + + _cleanup_close_ int copy_fd = -EBADF; + r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, ©_fd); + if (r < 0) + return r; + + r = import_copy_foreign(i->tree_fd, copy_fd, &i->userns_fd); + if (r < 0) + return r; + } else { + /* Copy locally */ + if (i->flags & IMPORT_BTRFS_SUBVOL) + r = btrfs_subvol_snapshot_at( + AT_FDCWD, i->final_path, + AT_FDCWD, t, + (i->flags & IMPORT_BTRFS_QUOTA ? BTRFS_SNAPSHOT_QUOTA : 0)| + BTRFS_SNAPSHOT_FALLBACK_COPY| + BTRFS_SNAPSHOT_FALLBACK_DIRECTORY| + BTRFS_SNAPSHOT_RECURSIVE); + else + r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to create original download image: %m"); + } source = t; } else @@ -264,8 +316,8 @@ static int tar_pull_make_local_copy(TarPull *i) { r = install_file(AT_FDCWD, source, AT_FDCWD, p, (i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) | - (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) | - (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0)); if (r < 0) return log_error_errno(r, "Failed to install local image '%s': %m", p); @@ -427,8 +479,8 @@ static void tar_pull_job_on_finished(PullJob *j) { r = install_file( AT_FDCWD, i->local, AT_FDCWD, NULL, - (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) | - (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0)); if (r < 0) { log_error_errno(r, "Failed to finalize '%s': %m", i->local); goto finish; @@ -453,8 +505,8 @@ static void tar_pull_job_on_finished(PullJob *j) { r = install_file( AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, - (i->flags & IMPORT_PULL_KEEP_DOWNLOAD ? INSTALL_READ_ONLY : 0) | - (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_PULL_KEEP_DOWNLOAD ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0)); if (r < 0) { log_error_errno(r, "Failed to rename to final image name to %s: %m", i->final_path); goto finish; @@ -480,7 +532,7 @@ static void tar_pull_job_on_finished(PullJob *j) { r = install_file( AT_FDCWD, i->settings_temp_path, AT_FDCWD, i->settings_path, - INSTALL_READ_ONLY| + INSTALL_READ_ONLY|INSTALL_GRACEFUL| (i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0)); if (r < 0) { log_error_errno(r, "Failed to rename settings file to %s: %m", i->settings_path); @@ -537,26 +589,42 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) { if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE)) (void) rm_rf(where, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - if (i->flags & IMPORT_BTRFS_SUBVOL) - r = btrfs_subvol_make_fallback(AT_FDCWD, where, 0755); - else - r = RET_NERRNO(mkdir(where, 0755)); - if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, - * because in that case our temporary path collided */ - r = 0; - if (r < 0) - return log_error_errno(r, "Failed to create directory/subvolume %s: %m", where); - if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */ - if (!(i->flags & IMPORT_DIRECT)) - (void) import_assign_pool_quota_and_warn(i->image_root); - (void) import_assign_pool_quota_and_warn(where); - } + if (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) { + r = import_make_foreign_userns(&i->userns_fd); + if (r < 0) + return r; + + _cleanup_close_ int directory_fd = -EBADF; + r = mountfsd_make_directory(where, /* flags= */ 0, &directory_fd); + if (r < 0) + return r; + + r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd); + if (r < 0) + return r; + } else { + if (i->flags & IMPORT_BTRFS_SUBVOL) + r = btrfs_subvol_make_fallback(AT_FDCWD, where, 0755); + else + r = RET_NERRNO(mkdir(where, 0755)); + if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, + * because in that case our temporary path collided */ + r = 0; + if (r < 0) + return log_error_errno(r, "Failed to create directory/subvolume %s: %m", where); + + if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */ + if (!(i->flags & IMPORT_DIRECT)) + (void) import_assign_pool_quota_and_warn(i->image_root); + (void) import_assign_pool_quota_and_warn(where); + } - i->tree_fd = open(where, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (i->tree_fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", where); + i->tree_fd = open(where, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (i->tree_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", where); + } - j->disk_fd = import_fork_tar_x(i->tree_fd, &i->tar_pid); + j->disk_fd = import_fork_tar_x(i->tree_fd, i->userns_fd, &i->tar_pid); if (j->disk_fd < 0) return j->disk_fd; diff --git a/src/import/pull.c b/src/import/pull.c index a41b4b01555..a1b9b940fdf 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -560,6 +560,9 @@ static int parse_argv(int argc, char *argv[]) { if (auto_keep_download) SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, arg_class == IMAGE_MACHINE); + if (arg_runtime_scope == RUNTIME_SCOPE_USER) + arg_import_flags |= IMPORT_FOREIGN_UID; + return 1; } diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 2166af2e7e9..0cbb7d02cff 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -4848,14 +4848,17 @@ int mountfsd_mount_image( #endif } -int mountfsd_mount_directory( - const char *path, +int mountfsd_mount_directory_fd( + int directory_fd, int userns_fd, DissectImageFlags flags, int *ret_mount_fd) { int r; + assert(directory_fd >= 0); + assert(ret_mount_fd); + /* Pick one identity, not both, that makes no sense. */ assert(!FLAGS_SET(flags, DISSECT_IMAGE_FOREIGN_UID|DISSECT_IMAGE_IDENTITY_UID)); @@ -4872,10 +4875,6 @@ int mountfsd_mount_directory( if (r < 0) return log_error_errno(r, "Failed to enable varlink fd passing for write: %m"); - _cleanup_close_ int directory_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_PATH); - if (directory_fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", path); - r = sd_varlink_push_dup_fd(vl, directory_fd); if (r < 0) return log_error_errno(r, "Failed to push directory fd into varlink connection: %m"); @@ -4919,3 +4918,103 @@ int mountfsd_mount_directory( *ret_mount_fd = TAKE_FD(fsmount_fd); return 0; } + +int mountfsd_mount_directory( + const char *path, + int userns_fd, + DissectImageFlags flags, + int *ret_mount_fd) { + + assert(path); + assert(ret_mount_fd); + + _cleanup_close_ int directory_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_PATH); + if (directory_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", path); + + return mountfsd_mount_directory_fd(directory_fd, userns_fd, flags, ret_mount_fd); +} + +int mountfsd_make_directory_fd( + int parent_fd, + const char *name, + DissectImageFlags flags, + int *ret_directory_fd) { + + int r; + + assert(parent_fd >= 0); + assert(name); + assert(ret_directory_fd); + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.MountFileSystem"); + if (r < 0) + return log_error_errno(r, "Failed to connect to mountfsd: %m"); + + r = sd_varlink_set_allow_fd_passing_input(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable varlink fd passing for read: %m"); + + r = sd_varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable varlink fd passing for write: %m"); + + r = sd_varlink_push_dup_fd(vl, parent_fd); + if (r < 0) + return log_error_errno(r, "Failed to push parent fd into varlink connection: %m"); + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = varlink_callbo_and_log( + vl, + "io.systemd.MountFileSystem.MakeDirectory", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_UNSIGNED("parentFileDescriptor", 0), + SD_JSON_BUILD_PAIR_STRING("name", name), + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH))); + if (r < 0) + return r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "directoryFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, 0, SD_JSON_MANDATORY }, + {} + }; + + unsigned directory_fd_idx = UINT_MAX; + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &directory_fd_idx); + if (r < 0) + return log_error_errno(r, "Failed to parse MountImage() reply: %m"); + + _cleanup_close_ int directory_fd = sd_varlink_take_fd(vl, directory_fd_idx); + if (directory_fd < 0) + return log_error_errno(directory_fd, "Failed to take directory fd from Varlink connection: %m"); + + *ret_directory_fd = TAKE_FD(directory_fd); + return 0; +} + +int mountfsd_make_directory( + const char *path, + DissectImageFlags flags, + int *ret_directory_fd) { + + int r; + + _cleanup_free_ char *parent = NULL; + r = path_extract_directory(path, &parent); + if (r < 0) + return log_error_errno(r, "Failed to extract parent directory from '%s': %m", path); + + _cleanup_free_ char *dirname = NULL; + r = path_extract_filename(path, &dirname); + if (r < 0) + return log_error_errno(r, "Failed to extract directory name from '%s': %m", path); + + _cleanup_close_ int fd = open(parent, O_DIRECTORY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(r, "Failed to open '%s': %m", parent); + + return mountfsd_make_directory_fd(fd, dirname, flags, ret_directory_fd); +} diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index b4353f9c9e6..48d23599601 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -258,4 +258,8 @@ static inline const char* dissected_partition_fstype(const DissectedPartition *m int get_common_dissect_directory(char **ret); int mountfsd_mount_image(const char *path, int userns_fd, const ImagePolicy *image_policy, const VeritySettings *verity, DissectImageFlags flags, DissectedImage **ret); +int mountfsd_mount_directory_fd(int directory_fd, int userns_fd, DissectImageFlags flags, int *ret_mount_fd); int mountfsd_mount_directory(const char *path, int userns_fd, DissectImageFlags flags, int *ret_mount_fd); + +int mountfsd_make_directory_fd(int parent_fd, const char *name, DissectImageFlags flags, int *ret_directory_fd); +int mountfsd_make_directory(const char *path, DissectImageFlags flags, int *ret_directory_fd); -- 2.47.3