From 3780a0b44622a0ba7eae530ba969cd087ff257cd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Aug 2025 11:26:17 +0200 Subject: [PATCH] export-tar: port to common libarchive tar generation code --- src/import/export-tar.c | 48 +++++++++++++++++++++++++--- src/import/export-tar.h | 5 +-- src/import/export.c | 11 ++++++- src/import/import-common.c | 65 +++++++++++++++++++------------------- src/import/import-common.h | 2 +- src/shared/tar-util.c | 1 - 6 files changed, 90 insertions(+), 42 deletions(-) diff --git a/src/import/export-tar.c b/src/import/export-tar.c index afad2460427..e945abe8c37 100644 --- a/src/import/export-tar.c +++ b/src/import/export-tar.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "btrfs-util.h" +#include "dissect-image.h" #include "export-tar.h" #include "fd-util.h" #include "format-util.h" @@ -27,11 +28,15 @@ typedef struct TarExport { TarExportFinished on_finished; void *userdata; + ImportFlags flags; + char *path; char *temp_path; - int output_fd; - int tar_fd; + int output_fd; /* compressed tar file in the fs */ + int tar_fd; /* uncompressed tar stream coming from child doing the libarchive loop */ + int tree_fd; /* directory fd of the tree to set up */ + int userns_fd; ImportCompress compress; @@ -98,6 +103,8 @@ int tar_export_new( *e = (TarExport) { .output_fd = -EBADF, .tar_fd = -EBADF, + .tree_fd = -EBADF, + .userns_fd = -EBADF, .on_finished = on_finished, .userdata = userdata, .quota_referenced = UINT64_MAX, @@ -271,7 +278,13 @@ static int tar_export_on_defer(sd_event_source *s, void *userdata) { return tar_export_process(i); } -int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType compress) { +int tar_export_start( + TarExport *e, + const char *path, + int fd, + ImportCompressType compress, + ImportFlags flags) { + _cleanup_close_ int sfd = -EBADF; int r; @@ -299,6 +312,7 @@ int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType if (r < 0) return r; + e->flags = flags; e->quota_referenced = UINT64_MAX; if (btrfs_might_be_subvol(&e->st)) { @@ -337,7 +351,33 @@ int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType if (r < 0) return r; - e->tar_fd = import_fork_tar_c(e->temp_path ?: e->path, &e->tar_pid); + const char *p = e->temp_path ?: e->path; + + if (FLAGS_SET(e->flags, IMPORT_FOREIGN_UID)) { + r = import_make_foreign_userns(&e->userns_fd); + if (r < 0) + return r; + + _cleanup_close_ int directory_fd = open(p, O_DIRECTORY|O_CLOEXEC|O_PATH); + if (directory_fd < 0) + return log_error_errno(r, "Failed to open '%s': %m", p); + + _cleanup_close_ int mapped_fd = -EBADF; + r = mountfsd_mount_directory_fd(directory_fd, e->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &mapped_fd); + if (r < 0) + return r; + + /* Drop O_PATH */ + e->tree_fd = fd_reopen(mapped_fd, O_DIRECTORY|O_CLOEXEC); + if (e->tree_fd < 0) + return log_error_errno(errno, "Failed to re-open mapped '%s': %m", p); + } else { + e->tree_fd = open(p, O_DIRECTORY|O_CLOEXEC); + if (e->tree_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", p); + } + + e->tar_fd = import_fork_tar_c(e->tree_fd, e->userns_fd, &e->tar_pid); if (e->tar_fd < 0) { e->output_event_source = sd_event_source_unref(e->output_event_source); return e->tar_fd; diff --git a/src/import/export-tar.h b/src/import/export-tar.h index 9eeec80dda3..382b5f58c0f 100644 --- a/src/import/export-tar.h +++ b/src/import/export-tar.h @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "shared-forward.h" +#include "import-common.h" #include "import-compress.h" +#include "shared-forward.h" typedef struct TarExport TarExport; @@ -13,4 +14,4 @@ TarExport* tar_export_unref(TarExport *export); DEFINE_TRIVIAL_CLEANUP_FUNC(TarExport*, tar_export_unref); -int tar_export_start(TarExport *export, const char *path, int fd, ImportCompressType compress); +int tar_export_start(TarExport *export, const char *path, int fd, ImportCompressType compress, ImportFlags flags); diff --git a/src/import/export.c b/src/import/export.c index bbea5b0343b..af9e8c15ec9 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -21,6 +21,7 @@ #include "terminal-util.h" #include "verbs.h" +static ImportFlags arg_import_flags = 0; static ImportCompressType arg_compress = IMPORT_COMPRESS_UNKNOWN; static ImageClass arg_class = IMAGE_MACHINE; static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID; @@ -111,7 +112,12 @@ static int export_tar(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to allocate exporter: %m"); - r = tar_export_start(export, local, fd, arg_compress); + r = tar_export_start( + export, + local, + fd, + arg_compress, + arg_import_flags & IMPORT_FLAGS_MASK_TAR); if (r < 0) return log_error_errno(r, "Failed to export image: %m"); @@ -283,6 +289,9 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + if (arg_runtime_scope == RUNTIME_SCOPE_USER) + arg_import_flags |= IMPORT_FOREIGN_UID; + return 1; } diff --git a/src/import/import-common.c b/src/import/import-common.c index f3d70c6f0fe..eaa68fae5a2 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -90,62 +90,61 @@ int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) { return TAKE_FD(pipefd[1]); } -int import_fork_tar_c(const char *path, PidRef *ret) { - _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; - _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; - bool use_selinux; +int import_fork_tar_c(int tree_fd, int userns_fd, PidRef *ret_pid) { int r; - assert(path); - assert(ret); + assert(tree_fd >= 0); + assert(ret_pid); + + r = dlopen_libarchive(); + if (r < 0) + return r; + + TarFlags flags = mac_selinux_use() ? TAR_SELINUX : 0; + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; if (pipe2(pipefd, O_CLOEXEC) < 0) return log_error_errno(errno, "Failed to create pipe for tar: %m"); (void) fcntl(pipefd[0], F_SETPIPE_SZ, IMPORT_BUFFER_SIZE); - use_selinux = mac_selinux_use(); - r = pidref_safe_fork_full( - "(tar)", - (int[]) { -EBADF, pipefd[1], STDERR_FILENO }, - NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG, - &pid); + "tar-c", + /* stdio_fds= */ NULL, + (int[]) { tree_fd, pipefd[1], 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) return r; if (r == 0) { - const char *cmdline[] = { - "tar", - "-C", path, - "-c", - "--xattrs", - "--xattrs-include=*", - use_selinux ? "--selinux" : "--no-selinux", - ".", - NULL - }; - - uint64_t retain = (1ULL << CAP_DAC_OVERRIDE); + static const uint64_t retain = (1ULL << CAP_DAC_OVERRIDE); /* 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_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); + log_debug_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); r = capability_bounding_set_drop(retain, true); if (r < 0) - log_error_errno(r, "Failed to drop capabilities, ignoring: %m"); + log_debug_errno(r, "Failed to drop capabilities, ignoring: %m"); + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) + log_warning_errno(errno, "Failed to enable PR_SET_NO_NEW_PRIVS, ignoring: %m"); - execvp("gtar", (char* const*) cmdline); - execvp("tar", (char* const*) cmdline); + if (tar_c(tree_fd, pipefd[1], /* filename= */ NULL, flags) < 0) + _exit(EXIT_FAILURE); - log_error_errno(errno, "Failed to execute tar: %m"); - _exit(EXIT_FAILURE); + _exit(EXIT_SUCCESS); } - *ret = TAKE_PIDREF(pid); - return TAKE_FD(pipefd[0]); } diff --git a/src/import/import-common.h b/src/import/import-common.h index 6c6ac655b0f..a922280d0ab 100644 --- a/src/import/import-common.h +++ b/src/import/import-common.h @@ -34,7 +34,7 @@ typedef enum ImportFlags { _IMPORT_FLAGS_INVALID = -EINVAL, } ImportFlags; -int import_fork_tar_c(const char *path, PidRef *ret); +int import_fork_tar_c(int tree_fd, int userns_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); diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index 3967041b6d7..c75d7eb6668 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -823,7 +823,6 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libarchive support not available."); } - int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) { assert(tree_fd >= 0); assert(output_fd >= 0); -- 2.47.3