From 4ded7f7a434c59534f65a0f9d391c55961eb110d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Aug 2025 11:05:38 +0200 Subject: [PATCH] tar-util: add support for acls --- src/shared/acl-util.c | 4 + src/shared/acl-util.h | 2 + src/shared/libarchive-util.c | 6 + src/shared/libarchive-util.h | 3 + src/shared/tar-util.c | 279 +++++++++++++++++++++++++++++++++++ 5 files changed, 294 insertions(+) diff --git a/src/shared/acl-util.c b/src/shared/acl-util.c index 554d4738213..d16c165ed71 100644 --- a/src/shared/acl-util.c +++ b/src/shared/acl-util.c @@ -23,6 +23,7 @@ DLSYM_PROTOTYPE(acl_delete_entry); DLSYM_PROTOTYPE(acl_delete_perm); DLSYM_PROTOTYPE(acl_dup); DLSYM_PROTOTYPE(acl_entries); +DLSYM_PROTOTYPE(acl_extended_file); DLSYM_PROTOTYPE(acl_free); DLSYM_PROTOTYPE(acl_from_mode); DLSYM_PROTOTYPE(acl_from_text); @@ -36,6 +37,7 @@ DLSYM_PROTOTYPE(acl_get_tag_type); DLSYM_PROTOTYPE(acl_init); DLSYM_PROTOTYPE(acl_set_fd); DLSYM_PROTOTYPE(acl_set_file); +DLSYM_PROTOTYPE(acl_set_permset); DLSYM_PROTOTYPE(acl_set_qualifier); DLSYM_PROTOTYPE(acl_set_tag_type); DLSYM_PROTOTYPE(acl_to_any_text); @@ -58,6 +60,7 @@ int dlopen_libacl(void) { DLSYM_ARG(acl_delete_perm), DLSYM_ARG(acl_dup), DLSYM_ARG(acl_entries), + DLSYM_ARG(acl_extended_file), DLSYM_ARG(acl_free), DLSYM_ARG(acl_from_mode), DLSYM_ARG(acl_from_text), @@ -71,6 +74,7 @@ int dlopen_libacl(void) { DLSYM_ARG(acl_init), DLSYM_ARG(acl_set_fd), DLSYM_ARG(acl_set_file), + DLSYM_ARG(acl_set_permset), DLSYM_ARG(acl_set_qualifier), DLSYM_ARG(acl_set_tag_type), DLSYM_ARG(acl_to_any_text)); diff --git a/src/shared/acl-util.h b/src/shared/acl-util.h index ea4aca7bdbe..7f6650013b3 100644 --- a/src/shared/acl-util.h +++ b/src/shared/acl-util.h @@ -20,6 +20,7 @@ extern DLSYM_PROTOTYPE(acl_delete_entry); extern DLSYM_PROTOTYPE(acl_delete_perm); extern DLSYM_PROTOTYPE(acl_dup); extern DLSYM_PROTOTYPE(acl_entries); +extern DLSYM_PROTOTYPE(acl_extended_file); extern DLSYM_PROTOTYPE(acl_free); extern DLSYM_PROTOTYPE(acl_from_mode); extern DLSYM_PROTOTYPE(acl_from_text); @@ -33,6 +34,7 @@ extern DLSYM_PROTOTYPE(acl_get_tag_type); extern DLSYM_PROTOTYPE(acl_init); extern DLSYM_PROTOTYPE(acl_set_fd); extern DLSYM_PROTOTYPE(acl_set_file); +extern DLSYM_PROTOTYPE(acl_set_permset); extern DLSYM_PROTOTYPE(acl_set_qualifier); extern DLSYM_PROTOTYPE(acl_set_tag_type); extern DLSYM_PROTOTYPE(acl_to_any_text); diff --git a/src/shared/libarchive-util.c b/src/shared/libarchive-util.c index 8d8e775c517..09f036895ce 100644 --- a/src/shared/libarchive-util.c +++ b/src/shared/libarchive-util.c @@ -7,6 +7,9 @@ #if HAVE_LIBARCHIVE static void *libarchive_dl = NULL; +DLSYM_PROTOTYPE(archive_entry_acl_add_entry) = NULL; +DLSYM_PROTOTYPE(archive_entry_acl_next) = NULL; +DLSYM_PROTOTYPE(archive_entry_acl_reset) = NULL; DLSYM_PROTOTYPE(archive_entry_fflags) = NULL; DLSYM_PROTOTYPE(archive_entry_filetype) = NULL; DLSYM_PROTOTYPE(archive_entry_free) = NULL; @@ -76,6 +79,9 @@ int dlopen_libarchive(void) { &libarchive_dl, "libarchive.so.13", LOG_DEBUG, + DLSYM_ARG(archive_entry_acl_add_entry), + DLSYM_ARG(archive_entry_acl_next), + DLSYM_ARG(archive_entry_acl_reset), DLSYM_ARG(archive_entry_fflags), DLSYM_ARG(archive_entry_filetype), DLSYM_ARG(archive_entry_free), diff --git a/src/shared/libarchive-util.h b/src/shared/libarchive-util.h index 9a5d4e0b402..615cd1ec810 100644 --- a/src/shared/libarchive-util.h +++ b/src/shared/libarchive-util.h @@ -9,6 +9,9 @@ #include "dlfcn-util.h" +extern DLSYM_PROTOTYPE(archive_entry_acl_add_entry); +extern DLSYM_PROTOTYPE(archive_entry_acl_next); +extern DLSYM_PROTOTYPE(archive_entry_acl_reset); extern DLSYM_PROTOTYPE(archive_entry_fflags); extern DLSYM_PROTOTYPE(archive_entry_filetype); extern DLSYM_PROTOTYPE(archive_entry_free); diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index 54f3644ad1b..a4d0578b255 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -8,6 +8,7 @@ #include #include +#include "acl-util.h" #include "alloc-util.h" #include "chase.h" #include "chattr-util.h" @@ -57,6 +58,7 @@ typedef struct OpenInode { unsigned fflags; XAttr *xattr; size_t n_xattr; + acl_t acl_access, acl_default; } OpenInode; static void xattr_done(XAttr *xa) { @@ -85,6 +87,10 @@ static void open_inode_done(OpenInode *of) { of->path = mfree(of->path); } xattr_done_many(of->xattr, of->n_xattr); + if (of->acl_access) + sym_acl_free(of->acl_access); + if (of->acl_default) + sym_acl_free(of->acl_default); } static void open_inode_done_many(OpenInode *array, size_t n) { @@ -96,6 +102,29 @@ static void open_inode_done_many(OpenInode *array, size_t n) { free(array); } +static int open_inode_apply_acl(OpenInode *of) { + int r = 0; + + assert(of); + assert(of->fd >= 0); + + if (!inode_type_can_acl(of->filetype)) + return 0; + + if (of->acl_access) { + if (sym_acl_set_fd(of->fd, of->acl_access) < 0) + RET_GATHER(r, log_error_errno(errno, "Failed to adjust ACLs of '%s': %m", of->path)); + } + + if (of->filetype == S_IFDIR && of->acl_default) { + /* There's no API to set default ACLs by fd, hence go by /proc/self/fd/ path */ + if (sym_acl_set_file(FORMAT_PROC_FD_PATH(of->fd), ACL_TYPE_DEFAULT, of->acl_default) < 0) + RET_GATHER(r, log_error_errno(errno, "Failed to adjust default ACLs of '%s': %m", of->path)); + } + + return r; +} + static int open_inode_finalize(OpenInode *of) { int r = 0; @@ -116,6 +145,10 @@ static int open_inode_finalize(OpenInode *of) { RET_GATHER(r, log_error_errno(k, "Failed to adjust ownership/mode of '%s': %m", of->path)); } + k = open_inode_apply_acl(of); + if (k < 0) + RET_GATHER(r, log_error_errno(k, "Failed to adjust ACL of '%s': %m", of->path)); + if ((of->fflags & ~CHATTR_EARLY_FL) != 0 && inode_type_can_chattr(of->filetype)) { k = chattr_full(of->fd, /* path= */ NULL, @@ -386,6 +419,119 @@ static int archive_entry_pathname_safe(struct archive_entry *entry, const char * return 0; } +static int archive_entry_read_acl( + struct archive_entry *entry, + acl_type_t ntype, + acl_t *acl) { + + int r; + + assert(entry); + assert(acl); + + int type; + if (ntype == ACL_TYPE_ACCESS) + type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS; + else if (ntype == ACL_TYPE_DEFAULT) + type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT; + else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unexpected ACL type"); + + int c = sym_archive_entry_acl_reset(entry, type); + if (c == 0) + return 0; + assert(c > 0); + + r = dlopen_libacl(); + if (r < 0) { + log_debug_errno(r, "Not restoring ACL data on inode as libacl is not available: %m"); + return 0; + } + + _cleanup_(acl_freep) acl_t a = NULL; + a = sym_acl_init(c); + if (!a) + return log_oom(); + + for (;;) { + int rtype, permset, tag, qual; + const char *name; + r = sym_archive_entry_acl_next( + entry, + type, + &rtype, + &permset, + &tag, + &qual, + &name); + if (r == ARCHIVE_EOF) + break; + if (r != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unexpected error while iterating through ACLs."); + + assert(rtype == type); + + acl_entry_t e; + if (sym_acl_create_entry(&a, &e) < 0) + return log_error_errno(errno, "Failed to create ACL entry: %m"); + + static const struct { + int libarchive; + acl_tag_t libacl; + } tag_map[] = { + { ARCHIVE_ENTRY_ACL_USER, ACL_USER }, + { ARCHIVE_ENTRY_ACL_GROUP, ACL_GROUP }, + { ARCHIVE_ENTRY_ACL_USER_OBJ, ACL_USER_OBJ }, + { ARCHIVE_ENTRY_ACL_GROUP_OBJ, ACL_GROUP_OBJ }, + { ARCHIVE_ENTRY_ACL_MASK, ACL_MASK }, + { ARCHIVE_ENTRY_ACL_OTHER, ACL_OTHER }, + }; + + acl_tag_t ntag = ACL_UNDEFINED_TAG; + FOREACH_ELEMENT(t, tag_map) + if (t->libarchive == tag) { + ntag = t->libacl; + break; + } + if (ntag == ACL_UNDEFINED_TAG) + continue; + + if (sym_acl_set_tag_type(e, ntag) < 0) + return log_error_errno(errno, "Failed to set ACL entry tag: %m"); + + if (IN_SET(ntag, ACL_USER, ACL_GROUP)) { + id_t id = qual; + + if (sym_acl_set_qualifier(e, &id) < 0) + return log_error_errno(errno, "Failed to set ACL entry qualifier: %m"); + } + + 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"); + + r = acl_set_perm(p, ACL_READ, permset & ARCHIVE_ENTRY_ACL_READ); + if (r < 0) + return log_error_errno(r, "Failed to set ACL entry read bit: %m"); + + r = acl_set_perm(p, ACL_WRITE, permset & ARCHIVE_ENTRY_ACL_WRITE); + if (r < 0) + return log_error_errno(r, "Failed to set ACL entry write bit: %m"); + + r = acl_set_perm(p, ACL_EXECUTE, permset & ARCHIVE_ENTRY_ACL_EXECUTE); + if (r < 0) + return log_error_errno(r, "Failed to set ACL entry excute bit: %m"); + + if (sym_acl_set_permset(e, p) < 0) + return log_error_errno(errno, "Failed to set ACL entry permission set: %m"); + } + + if (*acl) + sym_acl_free(*acl); + *acl = TAKE_PTR(a); + return 0; +} + static int archive_entry_read_stat( struct archive_entry *entry, mode_t *filetype, @@ -394,6 +540,8 @@ static int archive_entry_read_stat( uid_t *uid, gid_t *gid, unsigned *fflags, + acl_t *acl_access, + acl_t *acl_default, XAttr **xa, size_t *n_xa, TarFlags flags) { @@ -468,6 +616,18 @@ static int archive_entry_read_stat( }; } + if (acl_access) { + r = archive_entry_read_acl(entry, ACL_TYPE_ACCESS, acl_access); + if (r < 0) + return r; + } + + if (acl_default) { + r = archive_entry_read_acl(entry, ACL_TYPE_DEFAULT, acl_default); + if (r < 0) + return r; + } + return 0; } @@ -543,6 +703,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { &open_inodes[0].uid, &open_inodes[0].gid, &open_inodes[0].fflags, + &open_inodes[0].acl_access, + &open_inodes[0].acl_default, &open_inodes[0].xattr, &open_inodes[0].n_xattr, flags); @@ -614,6 +776,7 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { gid_t gid = GID_INVALID; struct timespec mtime = { .tv_nsec = UTIME_OMIT }; unsigned fflags = 0; + _cleanup_(acl_freep) acl_t acl_access = NULL, acl_default = NULL; XAttr *xa = NULL; size_t n_xa = 0; CLEANUP_ARRAY(xa, n_xa, xattr_done_many); @@ -689,6 +852,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { &uid, &gid, &fflags, + &acl_access, + &acl_default, &xa, &n_xa, flags); @@ -748,6 +913,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { .uid = uid, .gid = gid, .fflags = fflags, + .acl_access = TAKE_PTR(acl_access), + .acl_default = TAKE_PTR(acl_default), .xattr = TAKE_PTR(xa), .n_xattr = n_xa, }; @@ -925,6 +1092,88 @@ static int archive_generate_sparse(struct archive_entry *entry, int fd) { return 0; } +static int archive_write_acl( + struct archive_entry *entry, + acl_type_t ntype, + acl_t acl) { + int r; + + assert(entry); + assert(acl); + + int type; + if (ntype == ACL_TYPE_ACCESS) + type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS; + else if (ntype == ACL_TYPE_DEFAULT) + type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT; + else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unexpected ACL type"); + + acl_entry_t e; + r = sym_acl_get_entry(acl, ACL_FIRST_ENTRY, &e); + for (;;) { + if (r < 0) + return log_error_errno(errno, "Failed to get ACL entry: %m"); + if (r == 0) + break; + + acl_tag_t ntag; + if (sym_acl_get_tag_type(e, &ntag) < 0) + return log_error_errno(errno, "Failed to get ACL entry tag: %m"); + + static const int tag_map[] = { + [ACL_USER] = ARCHIVE_ENTRY_ACL_USER, + [ACL_GROUP] = ARCHIVE_ENTRY_ACL_GROUP, + [ACL_USER_OBJ] = ARCHIVE_ENTRY_ACL_USER_OBJ, + [ACL_GROUP_OBJ] = ARCHIVE_ENTRY_ACL_GROUP_OBJ, + [ACL_MASK] = ARCHIVE_ENTRY_ACL_MASK, + [ACL_OTHER] = ARCHIVE_ENTRY_ACL_OTHER, + }; + assert_cc(ACL_UNDEFINED_TAG == 0); /* safety check, we assume that holes are filled with ACL_UNDEFINED_TAG */ + assert_cc(ELEMENTSOF(tag_map) <= 64); /* safety check, we assume that the tag ids are all packed and low */ + + int tag = ntag >= 0 && ntag <= (acl_tag_t) ELEMENTSOF(tag_map) ? tag_map[ntag] : ACL_UNDEFINED_TAG; + + id_t qualifier = UID_INVALID; + if (IN_SET(ntag, ACL_USER, ACL_GROUP)) { + id_t *q = sym_acl_get_qualifier(e); + if (!q) + return log_error_errno(errno, "Failed to get ACL entry qualifier: %m"); + + qualifier = *q; + sym_acl_free(q); + } + + 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); + + 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_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); + } + + return 0; +} + static int archive_item( RecurseDirEvent event, const char *path, @@ -1009,6 +1258,36 @@ static int archive_item( sym_archive_entry_set_symlink(entry, s); } + if (inode_type_can_acl(sx->stx_mode)) { + + r = dlopen_libacl(); + if (r < 0) + log_debug_errno(r, "No trying to read ACL off inode, as libacl support is not available: %m"); + else { + r = sym_acl_extended_file(FORMAT_PROC_FD_PATH(inode_fd)); + if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(errno)) + return log_error_errno(errno, "Failed check if '%s' has ACLs: %m", path); + if (r > 0) { + _cleanup_(acl_freep) acl_t acl = NULL; + acl = sym_acl_get_file(FORMAT_PROC_FD_PATH(inode_fd), ACL_TYPE_ACCESS); + if (!acl) + return log_error_errno(errno, "Failed read access ACLs of '%s': %m", path); + + archive_write_acl(entry, ACL_TYPE_ACCESS, acl); + + if (S_ISDIR(sx->stx_mode)) { + sym_acl_free(acl); + + acl = sym_acl_get_file(FORMAT_PROC_FD_PATH(inode_fd), ACL_TYPE_DEFAULT); + if (!acl) + return log_error_errno(errno, "Failed to read default ACLs of '%s': %m", path); + + archive_write_acl(entry, ACL_TYPE_DEFAULT, acl); + } + } + } + } + _cleanup_free_ char *xattrs = NULL; r = flistxattr_malloc(inode_fd, &xattrs); if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r) && r != -ENODATA) -- 2.47.3