From 78a738f4cf98ee7731b173ac0b4993ec9a88489a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Aug 2025 17:28:50 +0200 Subject: [PATCH] tar-util: squash high UIDs in user mode --- src/import/import-common.c | 8 +++- src/shared/tar-util.c | 93 ++++++++++++++++++++++++++------------ src/shared/tar-util.h | 3 +- 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/src/import/import-common.c b/src/import/import-common.c index 11a6c060bb4..2fb781442a1 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -35,7 +35,9 @@ int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) { if (r < 0) return r; - TarFlags flags = mac_selinux_use() ? TAR_SELINUX : 0; + TarFlags flags = + (userns_fd >= 0 ? TAR_SQUASH_UIDS_ABOVE_64K : 0) | + (mac_selinux_use() ? TAR_SELINUX : 0); _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; if (pipe2(pipefd, O_CLOEXEC) < 0) @@ -100,7 +102,9 @@ int import_fork_tar_c(int tree_fd, int userns_fd, PidRef *ret_pid) { if (r < 0) return r; - TarFlags flags = mac_selinux_use() ? TAR_SELINUX : 0; + TarFlags flags = + (userns_fd >= 0 ? TAR_SQUASH_UIDS_ABOVE_64K : 0) | + (mac_selinux_use() ? TAR_SELINUX : 0); _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; if (pipe2(pipefd, O_CLOEXEC) < 0) diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index a4d0578b255..05dd4cd9fc9 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -18,6 +18,7 @@ #include "iovec-util.h" #include "libarchive-util.h" #include "mountpoint-util.h" +#include "nsresource.h" #include "nulstr-util.h" #include "path-util.h" #include "recurse-dir.h" @@ -422,7 +423,8 @@ static int archive_entry_pathname_safe(struct archive_entry *entry, const char * static int archive_entry_read_acl( struct archive_entry *entry, acl_type_t ntype, - acl_t *acl) { + acl_t *acl, + TarFlags flags) { int r; @@ -501,6 +503,13 @@ static int archive_entry_read_acl( if (IN_SET(ntag, ACL_USER, ACL_GROUP)) { id_t id = qual; + /* Suppress ACL entries for invalid UIDs/GIDS */ + if (!uid_is_valid(id)) + continue; + + /* Suppress ACL entries for UIDs/GIDs to squash */ + if (FLAGS_SET(flags, TAR_SQUASH_UIDS_ABOVE_64K) && id >= NSRESOURCE_UIDS_64K) + continue; if (sym_acl_set_qualifier(e, &id) < 0) return log_error_errno(errno, "Failed to set ACL entry qualifier: %m"); @@ -532,6 +541,24 @@ static int archive_entry_read_acl( return 0; } +static uid_t maybe_squash_uid(uid_t uid, TarFlags flags) { + if (FLAGS_SET(flags, TAR_SQUASH_UIDS_ABOVE_64K) && + uid_is_valid(uid) && + uid >= NSRESOURCE_UIDS_64K) + return UID_NOBODY; + + return uid; +} + +static uid_t maybe_squash_gid(uid_t gid, TarFlags flags) { + if (FLAGS_SET(flags, TAR_SQUASH_UIDS_ABOVE_64K) && + gid_is_valid(gid) && + gid >= NSRESOURCE_UIDS_64K) + return GID_NOBODY; + + return gid; +} + static int archive_entry_read_stat( struct archive_entry *entry, mode_t *filetype, @@ -565,9 +592,9 @@ static int archive_entry_read_stat( sym_archive_entry_mtime_nsec(entry), }; if (uid && sym_archive_entry_uid_is_set(entry)) - *uid = sym_archive_entry_uid(entry); + *uid = maybe_squash_uid(sym_archive_entry_uid(entry), flags); if (gid && sym_archive_entry_gid_is_set(entry)) - *gid = sym_archive_entry_gid(entry); + *gid = maybe_squash_gid(sym_archive_entry_gid(entry), flags); if (fflags) { unsigned long fs = 0, fc = 0; @@ -617,13 +644,13 @@ static int archive_entry_read_stat( } if (acl_access) { - r = archive_entry_read_acl(entry, ACL_TYPE_ACCESS, acl_access); + r = archive_entry_read_acl(entry, ACL_TYPE_ACCESS, acl_access, flags); if (r < 0) return r; } if (acl_default) { - r = archive_entry_read_acl(entry, ACL_TYPE_DEFAULT, acl_default); + r = archive_entry_read_acl(entry, ACL_TYPE_DEFAULT, acl_default, flags); if (r < 0) return r; } @@ -1095,7 +1122,8 @@ static int archive_generate_sparse(struct archive_entry *entry, int fd) { static int archive_write_acl( struct archive_entry *entry, acl_type_t ntype, - acl_t acl) { + acl_t acl, + TarFlags flags) { int r; assert(entry); @@ -1134,6 +1162,7 @@ static int archive_write_acl( int tag = ntag >= 0 && ntag <= (acl_tag_t) ELEMENTSOF(tag_map) ? tag_map[ntag] : ACL_UNDEFINED_TAG; + bool skip = false; id_t qualifier = UID_INVALID; if (IN_SET(ntag, ACL_USER, ACL_GROUP)) { id_t *q = sym_acl_get_qualifier(e); @@ -1142,31 +1171,37 @@ static int archive_write_acl( qualifier = *q; sym_acl_free(q); + + /* Suppress invalid UIDs or those that shall be squashed */ + skip = !(uid_is_valid(qualifier) && + (!FLAGS_SET(flags, TAR_SQUASH_UIDS_ABOVE_64K) || qualifier < NSRESOURCE_UIDS_64K)); } - acl_permset_t p; - if (sym_acl_get_permset(e, &p) < 0) - return log_error_errno(errno, "Failed to get ACL entry permission set: %m"); + if (!skip) { + acl_permset_t p; + if (sym_acl_get_permset(e, &p) < 0) + return log_error_errno(errno, "Failed to get ACL entry permission set: %m"); - int permset = 0; - r = sym_acl_get_perm(p, ACL_READ); - if (r < 0) - return log_error_errno(r, "Failed to get ACL entry read bit: %m"); - SET_FLAG(permset, ARCHIVE_ENTRY_ACL_READ, r); + int permset = 0; + r = sym_acl_get_perm(p, ACL_READ); + if (r < 0) + return log_error_errno(r, "Failed to get ACL entry read bit: %m"); + SET_FLAG(permset, ARCHIVE_ENTRY_ACL_READ, r); - r = sym_acl_get_perm(p, ACL_WRITE); - if (r < 0) - return log_error_errno(r, "Failed to get ACL entry write bit: %m"); - SET_FLAG(permset, ARCHIVE_ENTRY_ACL_WRITE, r); + r = sym_acl_get_perm(p, ACL_WRITE); + if (r < 0) + return log_error_errno(r, "Failed to get ACL entry write bit: %m"); + SET_FLAG(permset, ARCHIVE_ENTRY_ACL_WRITE, r); - r = sym_acl_get_perm(p, ACL_EXECUTE); - if (r < 0) - return log_error_errno(r, "Failed to get ACL entry execute bit: %m"); - SET_FLAG(permset, ARCHIVE_ENTRY_ACL_EXECUTE, r); + r = sym_acl_get_perm(p, ACL_EXECUTE); + if (r < 0) + return log_error_errno(r, "Failed to get ACL entry execute bit: %m"); + SET_FLAG(permset, ARCHIVE_ENTRY_ACL_EXECUTE, r); - r = sym_archive_entry_acl_add_entry(entry, type, permset, tag, qualifier, /* name= */ NULL); - if (r != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to add ACL entry."); + r = sym_archive_entry_acl_add_entry(entry, type, permset, tag, qualifier, /* name= */ NULL); + if (r != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to add ACL entry."); + } r = sym_acl_get_entry(acl, ACL_NEXT_ENTRY, &e); } @@ -1223,9 +1258,9 @@ static int archive_item( 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); + sym_archive_entry_set_uid(entry, maybe_squash_uid(sx->stx_uid, d->flags)); if (FLAGS_SET(sx->stx_mask, STATX_GID)) - sym_archive_entry_set_gid(entry, sx->stx_gid); + sym_archive_entry_set_gid(entry, maybe_squash_gid(sx->stx_gid, d->flags)); if (S_ISREG(sx->stx_mode)) { if (!FLAGS_SET(sx->stx_mask, STATX_SIZE)) @@ -1273,7 +1308,7 @@ static int archive_item( if (!acl) return log_error_errno(errno, "Failed read access ACLs of '%s': %m", path); - archive_write_acl(entry, ACL_TYPE_ACCESS, acl); + archive_write_acl(entry, ACL_TYPE_ACCESS, acl, d->flags); if (S_ISDIR(sx->stx_mode)) { sym_acl_free(acl); @@ -1282,7 +1317,7 @@ static int archive_item( if (!acl) return log_error_errno(errno, "Failed to read default ACLs of '%s': %m", path); - archive_write_acl(entry, ACL_TYPE_DEFAULT, acl); + archive_write_acl(entry, ACL_TYPE_DEFAULT, acl, d->flags); } } } diff --git a/src/shared/tar-util.h b/src/shared/tar-util.h index 06e2dc88fea..10c60be8a80 100644 --- a/src/shared/tar-util.h +++ b/src/shared/tar-util.h @@ -2,7 +2,8 @@ #pragma once typedef enum TarFlags { - TAR_SELINUX = 1 << 0, + TAR_SELINUX = 1 << 0, /* Include SELinux xattr in tarball, or unpack it */ + TAR_SQUASH_UIDS_ABOVE_64K = 1 << 1, /* Squash UIDs/GIDs above 64K when packing/unpacking to the nobody user */ } TarFlags; int tar_x(int input_fd, int tree_fd, TarFlags flags); -- 2.47.3