From 385eaf8f947c196a3f4ffd63315e83b9791f29de Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Oct 2026 09:18:43 +0200 Subject: [PATCH] import: drain trailing archive null padding in tar-x child libarchive stops reading as soon as it hits the end-of-archive marker, but tar archives are usually padded with zeroes to a full record size after that (GNU tar uses 10K records by default). Since the tar-x child then exits right away, the importer's final write of that padding can race against the child exiting and fail with EPIPE: Imported 88%. Failed to decode and write: Broken pipe The child exits successfully in this case, hence no tar error is logged at all. Seen in TEST-13-NSPAWN on "machinectl import-tar" of a "tar -czf" generated tarball. Fix this by reading the input pipe to EOF after tar_x() returns successfully, verifying that whatever follows the end-of-archive marker is all zeroes, and refusing the archive otherwise. This affects importctl import-tar as well as pull-tar and pull-oci. This addresses Ci failures such as: https://github.com/systemd/systemd/actions/runs/38738219126/job/116432869392?pr=44163 (cherry picked from commit 9c852031ec8178256196380dc9866553ce37842f) --- src/import/import-common.c | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/import/import-common.c b/src/import/import-common.c index 327da2707e..ae54383e17 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -16,6 +16,7 @@ #include "import-common.h" #include "libarchive-util.h" #include "log.h" +#include "memory-util.h" #include "namespace-util.h" #include "nsresource.h" #include "os-util.h" @@ -27,6 +28,44 @@ #include "tar-util.h" #include "tmpfile-util.h" +static int drain_null_padding(int fd) { + uint64_t n = 0; + + assert(fd >= 0); + + /* libarchive stops reading as soon as it encounters the end-of-archive marker, but tar archives are + * usually padded with zeroes to a full record size (10K for GNU tar) after it. Read and discard + * that, so that the writing side doesn't get EPIPE when it pushes the final bytes after we exited + * already. Insist that this padding is actually all zeroes though, and refuse any other trailing + * garbage. */ + + for (;;) { + uint8_t buf[64U*1024U]; + ssize_t l; + + l = read(fd, buf, sizeof(buf)); + if (l < 0) { + if (errno == EINTR) + continue; + + return log_error_errno(errno, "Failed to read trailing archive padding: %m"); + } + if (l == 0) + break; + + if (!memeqzero(buf, l)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Non-zero trailing data after end of archive, refusing."); + + n += l; + } + + if (n > 0) + log_debug("Discarded %" PRIu64 " bytes of zero padding after end of archive.", n); + + return 0; +} + int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) { int r; @@ -97,6 +136,9 @@ int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) { if (tar_x(pipefd[0], tree_fd, flags) < 0) _exit(EXIT_FAILURE); + if (drain_null_padding(pipefd[0]) < 0) + _exit(EXIT_FAILURE); + _exit(EXIT_SUCCESS); }