diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 04701429ad..b7802197db 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -1588,7 +1588,7 @@ static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopD output_fd = STDOUT_FILENO; } - r = tar_c(dfd, output_fd, arg_target, /* flags= */ 0); + r = tar_c(dfd, output_fd, arg_target, /* hardlink_db_fd= */ -EBADF, /* flags= */ 0); if (r < 0) return r; diff --git a/src/import/import-common.c b/src/import/import-common.c index ef2b3e26ba..0b63828869 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -137,6 +137,15 @@ int import_fork_tar_c(int tree_fd, int userns_fd, PidRef *ret_pid) { if (unshare(CLONE_NEWNET) < 0) log_debug_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); + /* Allocate the hardlink db before dropping privileges: it lives on a tmpfs superblock of our + * own, which needs CAP_SYS_ADMIN to set up. Doing it later would leave tar_c() to fall back + * to a directory in /var/tmp/, which is refused for the transient UID range we run under + * here, and would leave transient owned inodes behind on a persistent file system if it + * weren't. */ + _cleanup_close_ int hardlink_db_fd = tar_hardlink_db_new(); + if (hardlink_db_fd < 0) + log_debug_errno(hardlink_db_fd, "Failed to allocate hardlink db, ignoring: %m"); + r = capability_bounding_set_drop(retain, true); if (r < 0) log_debug_errno(r, "Failed to drop capabilities, ignoring: %m"); @@ -145,7 +154,7 @@ int import_fork_tar_c(int tree_fd, int userns_fd, PidRef *ret_pid) { if (r < 0) log_warning_errno(r, "Failed to enable PR_SET_NO_NEW_PRIVS, ignoring: %m"); - if (tar_c(tree_fd, pipefd[1], /* filename= */ NULL, flags) < 0) + if (tar_c(tree_fd, pipefd[1], /* filename= */ NULL, hardlink_db_fd, flags) < 0) _exit(EXIT_FAILURE); _exit(EXIT_SUCCESS); diff --git a/src/import/test-tar.c b/src/import/test-tar.c index b7cfed9bf3..6fca4b89cb 100644 --- a/src/import/test-tar.c +++ b/src/import/test-tar.c @@ -46,7 +46,7 @@ static int run(int argc, char **argv) { return log_error_errno(fd2, "Cannot open %s: %m", argv[3]); if (create) - r = tar_c(fd2, fd1, argv[2], /* flags= */ TAR_SELINUX); + r = tar_c(fd2, fd1, argv[2], /* hardlink_db_fd= */ -EBADF, /* flags= */ TAR_SELINUX); else r = tar_x(fd1, fd2, /* flags= */ TAR_SELINUX); if (r < 0) diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index e2603680bd..c60054b2d1 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -1083,7 +1083,7 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { return 0; } -static int make_tmpfs(void) { +int tar_hardlink_db_new(void) { /* Creates a tmpfs superblock to store our hardlink db in. We can do this if we run in our own * userns, or if we are privileged. This is preferable, since it means the db is cleaned up * automatically once we are done. Moreover, since this is a new superblock owned by us, we do not @@ -1111,6 +1111,7 @@ struct make_archive_data { TarFlags flags; int hardlink_db_fd; + bool hardlink_db_fd_owned; /* Whether we allocated the db ourselves, i.e. whether it is ours to close */ char *hardlink_db_path; int have_unique_mount_id; }; @@ -1199,7 +1200,7 @@ static int hardlink_lookup( /* We first try to create our own superblock, which works if we are in a userns, and which * doesn't require explicit clean-up */ - d->hardlink_db_fd = make_tmpfs(); + d->hardlink_db_fd = tar_hardlink_db_new(); if (d->hardlink_db_fd < 0) { log_debug_errno(d->hardlink_db_fd, "Failed to allocate tmpfs superblock for hardlink db, falling back to temporary directory: %m"); @@ -1216,6 +1217,8 @@ static int hardlink_lookup( if (d->hardlink_db_fd < 0) return log_error_errno(d->hardlink_db_fd, "Failed to make hardlink database directory: %m"); } + + d->hardlink_db_fd_owned = true; } else { _cleanup_free_ char *p = NULL; r = readlinkat_malloc(d->hardlink_db_fd, n, &p); @@ -1585,13 +1588,17 @@ static int archive_item( static void make_archive_data_done(struct make_archive_data *d) { assert(d); - if (d->hardlink_db_fd >= 0) - (void) rm_rf_children(d->hardlink_db_fd, REMOVE_PHYSICAL, /* root_dev= */ NULL); + /* Only tear down a db we allocated ourselves: rm_rf_children() consumes the fd it is given, and one + * handed to us by the caller is theirs to close. */ + if (d->hardlink_db_fd_owned && d->hardlink_db_fd >= 0) + (void) rm_rf_children(TAKE_FD(d->hardlink_db_fd), REMOVE_PHYSICAL, /* root_dev= */ NULL); unlink_and_free(d->hardlink_db_path); } -int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) { +/* The hardlink db fd is borrowed, not consumed: tar_c() neither closes it nor empties it again, so whoever + * passed it in stays responsible for both. */ +int tar_c(int tree_fd, int output_fd, const char *filename, int hardlink_db_fd, TarFlags flags) { int r; assert(tree_fd >= 0); @@ -1617,7 +1624,7 @@ int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) { _cleanup_(make_archive_data_done) struct make_archive_data data = { .archive = a, .flags = flags, - .hardlink_db_fd = -EBADF, + .hardlink_db_fd = hardlink_db_fd, .have_unique_mount_id = -1, }; @@ -1648,11 +1655,15 @@ 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) { +int tar_c(int tree_fd, int output_fd, const char *filename, int hardlink_db_fd, TarFlags flags) { assert(tree_fd >= 0); assert(output_fd >= 0); return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libarchive support not available."); } +int tar_hardlink_db_new(void) { + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libarchive support not available."); +} + #endif diff --git a/src/shared/tar-util.h b/src/shared/tar-util.h index 4ce8c57377..3e41ef1305 100644 --- a/src/shared/tar-util.h +++ b/src/shared/tar-util.h @@ -7,5 +7,9 @@ typedef enum TarFlags { TAR_OCI_WHITEOUTS = 1 << 2, /* Turn OCI/aufs whiteout inodes into overlayfs whiteouts */ } TarFlags; +/* Allocates a tmpfs superblock for tar_c() to keep its hardlink database in. Requires CAP_SYS_ADMIN in the + * caller's user namespace, hence callers that drop privileges must do this first and pass the fd along. */ +int tar_hardlink_db_new(void); + 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); +int tar_c(int tree_fd, int output_fd, const char *filename, int hardlink_db_fd, TarFlags flags);