The ACL xattrs carry 32 bit ids, so ACL_USER and ACL_GROUP entries for UUID and GUID users and groups could neither be set nor shown. Add version 3 of the xattr format, whose entries hold an id kind and 16 bytes of id. setxattr() takes version 2 and 3; getxattr() returns version 3 only if the ACL has such an entry the caller can see, so existing tools keep getting version 2 for everything else. set_posix_acl() refuses UUID and GUID entries with -EOVERFLOW on filesystems without SB_I_UID_R, whose ACL formats can't store them. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Jan Koester --- fs/posix_acl.c | 123 ++++++++++++++++++++++++++- include/linux/posix_acl_xattr.h | 31 +++++++ include/uapi/linux/posix_acl_xattr.h | 19 +++++ 3 files changed, 171 insertions(+), 2 deletions(-) diff --git a/fs/posix_acl.c b/fs/posix_acl.c index 18b302f941..bcb0663fe2 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include #include @@ -741,16 +743,62 @@ static int posix_acl_fix_xattr_common(const void *value, size_t size) return -EINVAL; if (size < sizeof(struct posix_acl_xattr_header)) return -EINVAL; - if (header->a_version != cpu_to_le32(POSIX_ACL_XATTR_VERSION)) + if (header->a_version == cpu_to_le32(POSIX_ACL_XATTR_VERSION_R)) + count = posix_acl_xattr_count_r(size); + else if (header->a_version == cpu_to_le32(POSIX_ACL_XATTR_VERSION)) + count = posix_acl_xattr_count(size); + else return -EOPNOTSUPP; - count = posix_acl_xattr_count(size); if (count < 0) return -EINVAL; return count; } +/* The entries of a version 3 ACL xattr into @acl; -EINVAL if malformed */ +static int posix_acl_from_xattr_r(struct user_namespace *userns, + const struct posix_acl_xattr_entry_r *entry, + struct posix_acl *acl) +{ + struct __kernel_uid_r id; + + for (int n = 0; n < acl->a_count; n++, entry++) { + struct posix_acl_entry *acl_e = &acl->a_entries[n]; + + acl_e->e_tag = le16_to_cpu(entry->e_tag); + acl_e->e_perm = le16_to_cpu(entry->e_perm); + + memset(&id, 0, sizeof(id)); + id.type = le32_to_cpu(entry->e_type); + if (id.type == UID_R_LEGACY) + id.legacy = get_unaligned_le32(entry->e_id); + else + memcpy(id.uuid, entry->e_id, sizeof(id.uuid)); + + switch (acl_e->e_tag) { + case ACL_USER_OBJ: + case ACL_GROUP_OBJ: + case ACL_MASK: + case ACL_OTHER: + break; + case ACL_USER: + acl_e->e_uid = make_kuid_r(userns, &id); + if (!uid_valid(acl_e->e_uid)) + return -EINVAL; + break; + case ACL_GROUP: + acl_e->e_gid = make_kgid_r(userns, &id); + if (!gid_valid(acl_e->e_gid)) + return -EINVAL; + break; + default: + return -EINVAL; + } + } + return 0; +} + /** * posix_acl_from_xattr - convert POSIX ACLs from backing store to VFS format * @userns: the filesystem's idmapping @@ -795,6 +843,13 @@ struct posix_acl *posix_acl_from_xattr(struct user_namespace *userns, return ERR_PTR(-ENOMEM); acl_e = acl->a_entries; + if (header->a_version == cpu_to_le32(POSIX_ACL_XATTR_VERSION_R)) { + if (posix_acl_from_xattr_r(userns, (const void *)(header + 1), + acl)) + goto fail; + return acl; + } + for (end = entry + count; entry != end; acl_e++, entry++) { acl_e->e_tag = le16_to_cpu(entry->e_tag); acl_e->e_perm = le16_to_cpu(entry->e_perm); @@ -874,6 +929,61 @@ posix_acl_to_xattr(struct user_namespace *user_ns, const struct posix_acl *acl, } EXPORT_SYMBOL (posix_acl_to_xattr); +/* Like vfs_posix_acl_to_xattr(), in version 3 */ +static ssize_t vfs_posix_acl_to_xattr_r(struct mnt_idmap *idmap, + struct inode *inode, + const struct posix_acl *acl, + void *buffer, size_t size) +{ + struct posix_acl_xattr_header *ext_acl = buffer; + struct posix_acl_xattr_entry_r *ext_entry; + struct user_namespace *fs_userns = i_user_ns(inode); + struct user_namespace *caller_userns = current_user_ns(); + struct __kernel_uid_r id; + ssize_t real_size; + bool mapped; + + real_size = posix_acl_xattr_size_r(acl->a_count); + if (!buffer) + return real_size; + if (real_size > size) + return -ERANGE; + + ext_entry = (void *)(ext_acl + 1); + ext_acl->a_version = cpu_to_le32(POSIX_ACL_XATTR_VERSION_R); + + for (int n = 0; n < acl->a_count; n++, ext_entry++) { + const struct posix_acl_entry *acl_e = &acl->a_entries[n]; + + ext_entry->e_tag = cpu_to_le16(acl_e->e_tag); + ext_entry->e_perm = cpu_to_le16(acl_e->e_perm); + memset(&id, 0, sizeof(id)); + mapped = true; + switch (acl_e->e_tag) { + case ACL_USER: + mapped = from_kuid_r(caller_userns, vfsuid_into_kuid( + make_vfsuid(idmap, fs_userns, acl_e->e_uid)), &id); + break; + case ACL_GROUP: + mapped = from_kgid_r(caller_userns, vfsgid_into_kgid( + make_vfsgid(idmap, fs_userns, acl_e->e_gid)), &id); + break; + } + /* no mapping: the classic -1, as in version 2 */ + if (!mapped) { + id.type = UID_R_LEGACY; + id.legacy = ACL_UNDEFINED_ID; + } + ext_entry->e_type = cpu_to_le32(id.type); + memset(ext_entry->e_id, 0, sizeof(ext_entry->e_id)); + if (id.type == UID_R_LEGACY) + put_unaligned_le32(id.legacy, ext_entry->e_id); + else if (id.type != UID_R_NONE) + memcpy(ext_entry->e_id, id.uuid, sizeof(ext_entry->e_id)); + } + return real_size; +} + /** * vfs_posix_acl_to_xattr - convert from kernel to userspace representation * @idmap: idmap of the mount @@ -902,6 +1012,11 @@ static ssize_t vfs_posix_acl_to_xattr(struct mnt_idmap *idmap, vfsuid_t vfsuid; vfsgid_t vfsgid; + /* UUIDs and GUIDs the caller can see need version 3 */ + if (posix_acl_has_id_r(acl) && current_user_ns() == &init_user_ns) + return vfs_posix_acl_to_xattr_r(idmap, inode, acl, buffer, + size); + real_size = posix_acl_xattr_size(acl->a_count); if (!buffer) return real_size; @@ -956,6 +1071,10 @@ set_posix_acl(struct mnt_idmap *idmap, struct dentry *dentry, int ret = posix_acl_valid(inode->i_sb->s_user_ns, acl); if (ret) return ret; + /* UUID and GUID entries only where the filesystem stores them */ + if (posix_acl_has_id_r(acl) && + !(inode->i_sb->s_iflags & SB_I_UID_R)) + return -EOVERFLOW; } return inode->i_op->set_acl(idmap, dentry, acl, type); } diff --git a/include/linux/posix_acl_xattr.h b/include/linux/posix_acl_xattr.h index 9e1892525e..307d543bce 100644 --- a/include/linux/posix_acl_xattr.h +++ b/include/linux/posix_acl_xattr.h @@ -32,6 +32,37 @@ posix_acl_xattr_count(size_t size) return size / sizeof(struct posix_acl_xattr_entry); } +static inline size_t +posix_acl_xattr_size_r(int count) +{ + return (sizeof(struct posix_acl_xattr_header) + + (count * sizeof(struct posix_acl_xattr_entry_r))); +} + +static inline int +posix_acl_xattr_count_r(size_t size) +{ + if (size < sizeof(struct posix_acl_xattr_header)) + return -1; + size -= sizeof(struct posix_acl_xattr_header); + if (size % sizeof(struct posix_acl_xattr_entry_r)) + return -1; + return size / sizeof(struct posix_acl_xattr_entry_r); +} + +/* Whether an ACL_USER or ACL_GROUP entry of @acl is a UUID or GUID */ +static inline bool posix_acl_has_id_r(const struct posix_acl *acl) +{ + for (int n = 0; n < acl->a_count; n++) { + const struct posix_acl_entry *e = &acl->a_entries[n]; + + if ((e->e_tag == ACL_USER && !uid_is_legacy(e->e_uid)) || + (e->e_tag == ACL_GROUP && !gid_is_legacy(e->e_gid))) + return true; + } + return false; +} + #ifdef CONFIG_FS_POSIX_ACL struct posix_acl *posix_acl_from_xattr(struct user_namespace *user_ns, const void *value, size_t size); diff --git a/include/uapi/linux/posix_acl_xattr.h b/include/uapi/linux/posix_acl_xattr.h index 506380f0b9..a44a515079 100644 --- a/include/uapi/linux/posix_acl_xattr.h +++ b/include/uapi/linux/posix_acl_xattr.h @@ -36,4 +36,23 @@ struct posix_acl_xattr_header { __le32 a_version; }; +/* + * Version 3: the ids of ACL_USER and ACL_GROUP entries can also be UUIDs + * and GUIDs. e_type is a UID_R_* kind (); a classic id + * is in the first four bytes of e_id, little endian, a UUID or GUID takes + * all of it in the byte order of __kernel_uid_r_t. Other entries have + * e_type UID_R_NONE. + * + * getxattr() returns version 2 unless an entry has a UUID or GUID; + * setxattr() takes both. + */ +#define POSIX_ACL_XATTR_VERSION_R 0x0003 + +struct posix_acl_xattr_entry_r { + __le16 e_tag; + __le16 e_perm; + __le32 e_type; + __u8 e_id[16]; +}; + #endif /* __UAPI_POSIX_ACL_XATTR_H */ -- 2.56.0