[RFC PATCH v2 33/41] posix_acl: support UUID and GUID entries

Jan Koester jan.koester at tuxist.de
Wed Oct 21 03:15:53 UTC 2026


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 <jan.koester at tuxist.de>
---
 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 <linux/cred.h>
 #include <linux/posix_acl.h>
 #include <linux/posix_acl_xattr.h>
+#include <linux/uidgid_r.h>
+#include <linux/unaligned.h>
 #include <linux/xattr.h>
 #include <linux/export.h>
 #include <linux/user_namespace.h>
@@ -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 (<linux/uidgid_r.h>); 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




More information about the Linux-security-module-archive mailing list