]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
tar-util: squash high UIDs in user mode
authorLennart Poettering <lennart@poettering.net>
Fri, 22 Aug 2025 15:28:50 +0000 (17:28 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 4 Nov 2025 13:12:39 +0000 (14:12 +0100)
src/import/import-common.c
src/shared/tar-util.c
src/shared/tar-util.h

index 11a6c060bb43efd0f894c5525a1dd9d547c1514b..2fb781442a18ef7c6b3f91cf546763ce07e576bf 100644 (file)
@@ -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)
index a4d0578b255e45fab048ca36f953afd8e6bbb0aa..05dd4cd9fc9de873962d3f0e6273d222563fedec 100644 (file)
@@ -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);
                                 }
                         }
                 }
index 06e2dc88feac483ae31e118879153d04675063f7..10c60be8a80dc7cab3ad2a754d27111bbe014644 100644 (file)
@@ -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);