diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index afc83f8072..f4c02737f5 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -53,6 +53,7 @@ #include "stat-util.h" #include "string-util.h" #include "strv.h" +#include "tar-util.h" #include "terminal-util.h" #include "tmpfile-util.h" #include "uid-classification.h" @@ -1432,109 +1433,6 @@ static int mtree_print_item( return RECURSE_DIR_CONTINUE; } -#if HAVE_LIBARCHIVE -static int archive_item( - RecurseDirEvent event, - const char *path, - int dir_fd, - int inode_fd, - const struct dirent *de, - const struct statx *sx, - void *userdata) { - - struct archive *a = ASSERT_PTR(userdata); - int r; - - assert(path); - - if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY)) - return RECURSE_DIR_CONTINUE; - - assert(inode_fd >= 0); - assert(sx); - - log_debug("Archiving %s\n", path); - - _cleanup_(archive_entry_freep) struct archive_entry *entry = NULL; - entry = sym_archive_entry_new(); - if (!entry) - return log_oom(); - - assert(FLAGS_SET(sx->stx_mask, STATX_TYPE|STATX_MODE)); - sym_archive_entry_set_pathname(entry, path); - sym_archive_entry_set_filetype(entry, sx->stx_mode); - - if (!S_ISLNK(sx->stx_mode)) - sym_archive_entry_set_perm(entry, sx->stx_mode); - - if (FLAGS_SET(sx->stx_mask, STATX_UID)) - sym_archive_entry_set_uid(entry, sx->stx_uid); - if (FLAGS_SET(sx->stx_mask, STATX_GID)) - sym_archive_entry_set_gid(entry, sx->stx_gid); - - if (S_ISREG(sx->stx_mode)) { - if (!FLAGS_SET(sx->stx_mask, STATX_SIZE)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Unable to determine file size of '%s'.", path); - - sym_archive_entry_set_size(entry, sx->stx_size); - } - - if (S_ISCHR(sx->stx_mode) || S_ISBLK(sx->stx_mode)) { - sym_archive_entry_set_rdevmajor(entry, sx->stx_rdev_major); - sym_archive_entry_set_rdevminor(entry, sx->stx_rdev_minor); - } - - /* We care about a modicum of reproducibility here, hence we don't save atime/btime here */ - if (FLAGS_SET(sx->stx_mask, STATX_MTIME)) - sym_archive_entry_set_mtime(entry, sx->stx_mtime.tv_sec, sx->stx_mtime.tv_nsec); - if (FLAGS_SET(sx->stx_mask, STATX_CTIME)) - sym_archive_entry_set_ctime(entry, sx->stx_ctime.tv_sec, sx->stx_ctime.tv_nsec); - - if (S_ISLNK(sx->stx_mode)) { - _cleanup_free_ char *s = NULL; - - assert(dir_fd >= 0); - assert(de); - - r = readlinkat_malloc(dir_fd, de->d_name, &s); - if (r < 0) - return log_error_errno(r, "Failed to read symlink target of '%s': %m", path); - - sym_archive_entry_set_symlink(entry, s); - } - - if (sym_archive_write_header(a, entry) != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(a)); - - if (S_ISREG(sx->stx_mode)) { - _cleanup_close_ int data_fd = -EBADF; - - /* Convert the O_PATH fd in a proper fd */ - data_fd = fd_reopen(inode_fd, O_RDONLY|O_CLOEXEC); - if (data_fd < 0) - return log_error_errno(data_fd, "Failed to open '%s': %m", path); - - for (;;) { - char buffer[64*1024]; - ssize_t l; - - l = read(data_fd, buffer, sizeof(buffer)); - if (l < 0) - return log_error_errno(errno, "Failed to read '%s': %m", path); - if (l == 0) - break; - - la_ssize_t k; - k = sym_archive_write_data(a, buffer, l); - if (k < 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive data: %s", sym_archive_error_string(a)); - } - } - - return RECURSE_DIR_CONTINUE; -} -#endif - static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopDevice *d, int userns_fd) { _cleanup_(umount_and_freep) char *mounted_dir = NULL; _cleanup_free_ char *t = NULL; @@ -1736,56 +1634,34 @@ static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopD case ACTION_MAKE_ARCHIVE: { #if HAVE_LIBARCHIVE - _cleanup_(unlink_and_freep) char *tar = NULL; _cleanup_close_ int dfd = -EBADF; - _cleanup_fclose_ FILE *f = NULL; dfd = open(root, O_DIRECTORY|O_CLOEXEC|O_RDONLY); if (dfd < 0) return log_error_errno(errno, "Failed to open mount directory: %m"); - _cleanup_(archive_write_freep) struct archive *a = sym_archive_write_new(); - if (!a) - return log_oom(); - - if (arg_target) - r = sym_archive_write_set_format_filter_by_ext(a, arg_target); - else - r = sym_archive_write_set_format_gnutar(a); - if (r != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output format: %s", sym_archive_error_string(a)); - + _cleanup_(unlink_and_freep) char *tar = NULL; + _cleanup_close_ int tmp_fd = -EBADF; + int output_fd; if (arg_target) { - r = fopen_tmpfile_linkable(arg_target, O_WRONLY|O_CLOEXEC, &tar, &f); - if (r < 0) - return log_error_errno(r, "Failed to create target file '%s': %m", arg_target); + tmp_fd = open_tmpfile_linkable(arg_target, O_WRONLY|O_CLOEXEC, &tar); + if (tmp_fd < 0) + return log_error_errno(tmp_fd, "Failed to create target file '%s': %m", arg_target); - r = sym_archive_write_open_FILE(a, f); + output_fd = tmp_fd; } else { if (isatty_safe(STDOUT_FILENO)) return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Refusing to write archive to TTY."); - r = sym_archive_write_open_fd(a, STDOUT_FILENO); + output_fd = STDOUT_FILENO; } - if (r != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output file: %s", sym_archive_error_string(a)); - r = recurse_dir(dfd, - ".", - STATX_TYPE|STATX_MODE|STATX_UID|STATX_GID|STATX_SIZE|STATX_ATIME|STATX_CTIME, - UINT_MAX, - RECURSE_DIR_SORT|RECURSE_DIR_INODE_FD|RECURSE_DIR_TOPLEVEL, - archive_item, - a); + r = tar_c(dfd, output_fd, arg_target, /* flags= */ 0); if (r < 0) - return log_error_errno(r, "Failed to make archive: %m"); - - r = sym_archive_write_close(a); - if (r != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to finish writing archive: %s", sym_archive_error_string(a)); + return r; if (arg_target) { - r = flink_tmpfile(f, tar, arg_target, LINK_TMPFILE_REPLACE); + r = link_tmpfile(tmp_fd, tar, arg_target, LINK_TMPFILE_REPLACE); if (r < 0) return log_error_errno(r, "Failed to move archive file into place: %m"); diff --git a/src/import/export-tar.c b/src/import/export-tar.c index afad246042..e945abe8c3 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 9eeec80dda..382b5f58c0 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 bbea5b0343..af9e8c15ec 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 f3d70c6f0f..eaa68fae5a 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"); - execvp("gtar", (char* const*) cmdline); - execvp("tar", (char* const*) cmdline); + 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"); - log_error_errno(errno, "Failed to execute tar: %m"); - _exit(EXIT_FAILURE); + if (tar_c(tree_fd, pipefd[1], /* filename= */ NULL, flags) < 0) + _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 6c6ac655b0..a922280d0a 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 135ff28a11..c75d7eb666 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -14,6 +14,7 @@ #include "iovec-util.h" #include "libarchive-util.h" #include "path-util.h" +#include "recurse-dir.h" #include "stat-util.h" #include "string-util.h" #include "tmpfile-util.h" @@ -674,6 +675,145 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { return 0; } +static int archive_item( + RecurseDirEvent event, + const char *path, + int dir_fd, + int inode_fd, + const struct dirent *de, + const struct statx *sx, + void *userdata) { + + struct archive *a = ASSERT_PTR(userdata); + int r; + + assert(path); + + if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY)) + return RECURSE_DIR_CONTINUE; + + assert(inode_fd >= 0); + assert(sx); + + log_debug("Archiving %s\n", path); + + _cleanup_(archive_entry_freep) struct archive_entry *entry = NULL; + entry = sym_archive_entry_new(); + if (!entry) + return log_oom(); + + assert(FLAGS_SET(sx->stx_mask, STATX_TYPE|STATX_MODE)); + sym_archive_entry_set_pathname(entry, path); + sym_archive_entry_set_filetype(entry, sx->stx_mode); + + if (!S_ISLNK(sx->stx_mode)) + sym_archive_entry_set_perm(entry, sx->stx_mode); + + if (FLAGS_SET(sx->stx_mask, STATX_UID)) + sym_archive_entry_set_uid(entry, sx->stx_uid); + if (FLAGS_SET(sx->stx_mask, STATX_GID)) + sym_archive_entry_set_gid(entry, sx->stx_gid); + + if (S_ISREG(sx->stx_mode)) { + if (!FLAGS_SET(sx->stx_mask, STATX_SIZE)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Unable to determine file size of '%s'.", path); + + sym_archive_entry_set_size(entry, sx->stx_size); + } + + if (S_ISCHR(sx->stx_mode) || S_ISBLK(sx->stx_mode)) { + sym_archive_entry_set_rdevmajor(entry, sx->stx_rdev_major); + sym_archive_entry_set_rdevminor(entry, sx->stx_rdev_minor); + } + + /* We care about a modicum of reproducibility here, hence we don't save atime/btime here */ + if (FLAGS_SET(sx->stx_mask, STATX_MTIME)) + sym_archive_entry_set_mtime(entry, sx->stx_mtime.tv_sec, sx->stx_mtime.tv_nsec); + if (FLAGS_SET(sx->stx_mask, STATX_CTIME)) + sym_archive_entry_set_ctime(entry, sx->stx_ctime.tv_sec, sx->stx_ctime.tv_nsec); + + if (S_ISLNK(sx->stx_mode)) { + _cleanup_free_ char *s = NULL; + + assert(dir_fd >= 0); + assert(de); + + r = readlinkat_malloc(dir_fd, de->d_name, &s); + if (r < 0) + return log_error_errno(r, "Failed to read symlink target of '%s': %m", path); + + sym_archive_entry_set_symlink(entry, s); + } + + if (sym_archive_write_header(a, entry) != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(a)); + + if (S_ISREG(sx->stx_mode)) { + _cleanup_close_ int data_fd = -EBADF; + + /* Convert the O_PATH fd in a proper fd */ + data_fd = fd_reopen(inode_fd, O_RDONLY|O_CLOEXEC); + if (data_fd < 0) + return log_error_errno(data_fd, "Failed to open '%s': %m", path); + + for (;;) { + char buffer[64*1024]; + ssize_t l; + + l = read(data_fd, buffer, sizeof(buffer)); + if (l < 0) + return log_error_errno(errno, "Failed to read '%s': %m", path); + if (l == 0) + break; + + la_ssize_t k; + k = sym_archive_write_data(a, buffer, l); + if (k < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive data: %s", sym_archive_error_string(a)); + } + } + + return RECURSE_DIR_CONTINUE; +} + +int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) { + int r; + + assert(tree_fd >= 0); + assert(output_fd >= 0); + + _cleanup_(archive_write_freep) struct archive *a = sym_archive_write_new(); + if (!a) + return log_oom(); + + if (filename) + r = sym_archive_write_set_format_filter_by_ext(a, filename); + else + r = sym_archive_write_set_format_gnutar(a); + if (r != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output format: %s", sym_archive_error_string(a)); + + r = sym_archive_write_open_fd(a, output_fd); + if (r != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output file: %s", sym_archive_error_string(a)); + + r = recurse_dir(tree_fd, + ".", + STATX_TYPE|STATX_MODE|STATX_UID|STATX_GID|STATX_SIZE|STATX_ATIME|STATX_CTIME, + UINT_MAX, + RECURSE_DIR_SORT|RECURSE_DIR_INODE_FD|RECURSE_DIR_TOPLEVEL, + archive_item, + a); + if (r < 0) + return log_error_errno(r, "Failed to make archive: %m"); + + r = sym_archive_write_close(a); + if (r != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to finish writing archive: %s", sym_archive_error_string(a)); + + return 0; +} + #else int tar_x(int input_fd, int tree_fd, TarFlags flags) { @@ -683,4 +823,11 @@ 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); + + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libarchive support not available."); +} + #endif diff --git a/src/shared/tar-util.h b/src/shared/tar-util.h index 4fb00d9389..06e2dc88fe 100644 --- a/src/shared/tar-util.h +++ b/src/shared/tar-util.h @@ -6,3 +6,4 @@ typedef enum TarFlags { } TarFlags; int tar_x(int input_fd, int tree_fd, TarFlags flags); +int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags);