]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
importd: support unpacking tarballs to foreign UID range
authorLennart Poettering <lennart@poettering.net>
Mon, 18 Aug 2025 21:18:18 +0000 (23:18 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 21 Oct 2025 21:09:50 +0000 (23:09 +0200)
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
src/import/import-common.h
src/import/import-tar.c
src/import/import.c
src/import/pull-tar.c
src/import/pull.c
src/shared/dissect-image.c
src/shared/dissect-image.h

index 31bc7c95df57ba7a04195ba54c31ef1a70d9dbe7..d8fd6e398256ac70b4ae573af32b33032130d2df 100644 (file)
@@ -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;
+}
index 55d00ea4668c08ef5b375643ccd3690d6b8b3ae0..6c6ac655b0fad9ef2245fee7f47560bff8ba7b04 100644 (file)
@@ -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)
index 7e5499b2b993f13b6ae843d0af62e3c199a31ff9..85fbf5abd149b1624ff30db0738cfa29aafce402 100644 (file)
@@ -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;
 
index 531c08207702d681b5f6512318c6f50ef10ca7bb..5276f26977a3c1e9269145e8bcbd52919eda2056 100644 (file)
@@ -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;
 }
 
index fa44216f8cf3d1b7935c1280aea9137a28e2b364..cf9e2060da7cc7cb2ba92f5744a1574152ed2950 100644 (file)
@@ -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, &copy_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;
 
index a41b4b01555dd6e939946b10179411ea58768b66..a1b9b940fdfbdc1b412cb2c3efbe98ee84aff43d 100644 (file)
@@ -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;
 }
 
index 2166af2e7e99688125aa9d13aa54f9f43e1aac1f..0cbb7d02cff2f7e8ba6ae61c97559546e2ee6a76 100644 (file)
@@ -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);
+}
index b4353f9c9e6364e928d99ce4c0641b06ddd7c998..48d235996018a22770a56f7b0eb41756e2d9fadc 100644 (file)
@@ -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);