Add the conversions between kuid_t/kgid_t and __kernel_uid_r_t the new
system calls need: make_kuid_r(), from_kuid_r() and the never failing
from_kuid_r_munged() (and the gid variants), and get_kuid_r() and
put_kuid_r() to copy them from and to userspace.

get_kuid_r() returns INVALID_UID for "no id" -- a NULL pointer,
UID_R_NONE, or a classic -1 -- which the set*id_r() and chown_r() calls
take as "unchanged", like the classic calls take -1. It rejects
malformed ids: unknown types, nonzero reserved bytes, UUIDs other than
RFC 9562 version 4, the nil GUID, and UUIDs or GUIDs given from outside
the initial user namespace.

Also add kuid_str() and kgid_str(), the text form of an id for logs and
procfs: a classic id in decimal, a UUID as usual, a GUID in braces.

Assisted-by: Claude:claude-opus-5-5

Signed-off-by: Jan Koester <[email protected]>
---
 include/linux/uidgid.h |  35 +++++
 kernel/Makefile        |   1 +
 kernel/uidgid_r.c      | 323 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 359 insertions(+)
 create mode 100644 kernel/uidgid_r.c

diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h
index e1adb50d5d..ac7786f760 100644
--- a/include/linux/uidgid.h
+++ b/include/linux/uidgid.h
@@ -180,6 +180,17 @@ static inline u32 kgid_hash(kgid_t gid)
        return __kid_hash(gid.type, __kgid_val(gid), gid.uuid.b);
 }
 
+/*
+ * The text form of an id, for logs and procfs: a legacy id as a decimal
+ * number, a UUID as "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" and a GUID in
+ * braces, "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}". KID_STR_LEN includes
+ * the terminating NUL.
+ */
+#define KID_STR_LEN 39
+
+char *kuid_str(kuid_t uid, char buf[KID_STR_LEN]);
+char *kgid_str(kgid_t gid, char buf[KID_STR_LEN]);
+
 #ifdef CONFIG_USER_NS
 
 extern kuid_t make_kuid(struct user_namespace *from, uid_t uid);
@@ -277,4 +288,28 @@ static inline u32 map_id_up(struct uid_gid_map *map, u32 
id)
 }
 #endif /* CONFIG_USER_NS */
 
+/*
+ * Conversion between kuid_t/kgid_t and the ids of the *_r system calls
+ * (<uapi/linux/uidgid_r.h>), in kernel/uidgid_r.c.
+ */
+struct __kernel_uid_r;
+
+kuid_t make_kuid_r(struct user_namespace *from, const struct __kernel_uid_r 
*id);
+kgid_t make_kgid_r(struct user_namespace *from, const struct __kernel_uid_r 
*id);
+bool from_kuid_r(struct user_namespace *to, kuid_t uid, struct __kernel_uid_r 
*id);
+bool from_kgid_r(struct user_namespace *to, kgid_t gid, struct __kernel_uid_r 
*id);
+void from_kuid_r_munged(struct user_namespace *to, kuid_t uid,
+                       struct __kernel_uid_r *id);
+void from_kgid_r_munged(struct user_namespace *to, kgid_t gid,
+                       struct __kernel_uid_r *id);
+
+int get_kuid_r(struct user_namespace *from,
+              const struct __kernel_uid_r __user *uid, kuid_t *kuid);
+int get_kgid_r(struct user_namespace *from,
+              const struct __kernel_uid_r __user *gid, kgid_t *kgid);
+int put_kuid_r(struct user_namespace *to, kuid_t kuid,
+              struct __kernel_uid_r __user *uid);
+int put_kgid_r(struct user_namespace *to, kgid_t kgid,
+              struct __kernel_uid_r __user *gid);
+
 #endif /* _LINUX_UIDGID_H */
diff --git a/kernel/Makefile b/kernel/Makefile
index 1e1a316735..813ddbf115 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -89,6 +89,7 @@ obj-$(CONFIG_BACKTRACE_SELF_TEST) += backtracetest.o
 obj-$(CONFIG_COMPAT) += compat.o
 obj-$(CONFIG_CGROUPS) += cgroup/
 obj-$(CONFIG_UTS_NS) += utsname.o
+obj-y += uidgid_r.o
 obj-$(CONFIG_USER_NS) += user_namespace.o
 obj-$(CONFIG_PID_NS) += pid_namespace.o
 obj-$(CONFIG_IKCONFIG) += configs.o
diff --git a/kernel/uidgid_r.c b/kernel/uidgid_r.c
new file mode 100644
index 0000000000..d71aecddb2
--- /dev/null
+++ b/kernel/uidgid_r.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Conversion between kuid_t/kgid_t and the ids of the *_r system calls
+ * (<uapi/linux/uidgid_r.h>).
+ */
+
+#include <linux/export.h>
+#include <linux/highuid.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/uidgid.h>
+#include <linux/uidgid_r.h>
+#include <linux/user_namespace.h>
+
+/*
+ * Whether @id is well formed: a known type, reserved bytes zero, a UUID of
+ * version 4 with the RFC 9562 variant, a GUID other than the nil GUID.
+ */
+static bool kid_r_valid(const struct __kernel_uid_r *id)
+{
+       switch (id->type) {
+       case UID_R_LEGACY:
+               return !memchr_inv(id->uuid + sizeof(id->legacy), 0,
+                                  UID_R_SIZE - sizeof(id->legacy));
+       case UID_R_UUID:
+               return (id->uuid[6] >> 4) == 4 && (id->uuid[8] & 0xc0) == 0x80;
+       case UID_R_GUID:
+               return memchr_inv(id->guid, 0, UID_R_SIZE);
+       }
+       return false;
+}
+
+/*
+ * The kind and 128 bit value of @id for a kuid_t or kgid_t; false if it is
+ * no UUID or GUID or has no mapping in @ns.
+ */
+static bool kid_r_to_128(struct user_namespace *ns,
+                        const struct __kernel_uid_r *id, u32 *type, uuid_t *b)
+{
+       if (!kid_r_valid(id) || ns != &init_user_ns)
+               return false;
+       if (id->type == UID_R_UUID)
+               *type = KID_UUID;
+       else if (id->type == UID_R_GUID)
+               *type = KID_GUID;
+       else
+               return false;
+       memcpy(b->b, id->uuid, UID_R_SIZE);
+       return true;
+}
+
+static bool kid_128_to_r(struct user_namespace *ns, u32 type, const uuid_t *b,
+                        struct __kernel_uid_r *id)
+{
+       if (ns != &init_user_ns)
+               return false;
+       id->type = type == KID_UUID ? UID_R_UUID : UID_R_GUID;
+       memcpy(id->uuid, b->b, UID_R_SIZE);
+       return true;
+}
+
+/**
+ * make_kuid_r - map a *_r system call uid into a kuid
+ * @from: user namespace the uid is in
+ * @id: the uid
+ *
+ * Return: the kuid, INVALID_UID if @id is malformed, UID_R_NONE or has no
+ * mapping in @from.
+ */
+kuid_t make_kuid_r(struct user_namespace *from, const struct __kernel_uid_r 
*id)
+{
+       kuid_t uid = INVALID_UID;
+
+       if (id->type == UID_R_LEGACY && kid_r_valid(id))
+               return make_kuid(from, id->legacy);
+       if (!kid_r_to_128(from, id, &uid.type, &uid.uuid))
+               return INVALID_UID;
+       return uid;
+}
+EXPORT_SYMBOL(make_kuid_r);
+
+/**
+ * make_kgid_r - map a *_r system call gid into a kgid
+ * @from: user namespace the gid is in
+ * @id: the gid
+ *
+ * Return: the kgid, INVALID_GID if @id is malformed, UID_R_NONE or has no
+ * mapping in @from.
+ */
+kgid_t make_kgid_r(struct user_namespace *from, const struct __kernel_uid_r 
*id)
+{
+       kgid_t gid = INVALID_GID;
+
+       if (id->type == UID_R_LEGACY && kid_r_valid(id))
+               return make_kgid(from, id->legacy);
+       if (!kid_r_to_128(from, id, &gid.type, &gid.uuid))
+               return INVALID_GID;
+       return gid;
+}
+EXPORT_SYMBOL(make_kgid_r);
+
+/**
+ * from_kuid_r - map a kuid into a *_r system call uid
+ * @to: user namespace the uid is wanted in
+ * @uid: the kuid
+ * @id: the uid, all zero (UID_R_NONE) if there is no mapping
+ *
+ * Return: whether @uid has a mapping in @to.
+ */
+bool from_kuid_r(struct user_namespace *to, kuid_t uid, struct __kernel_uid_r 
*id)
+{
+       memset(id, 0, sizeof(*id));
+       if (uid_is_legacy(uid)) {
+               uid_t v = from_kuid(to, uid);
+
+               if (v == (uid_t)-1)
+                       return false;
+               id->type = UID_R_LEGACY;
+               id->legacy = v;
+               return true;
+       }
+       return kid_128_to_r(to, uid.type, &uid.uuid, id);
+}
+EXPORT_SYMBOL(from_kuid_r);
+
+/**
+ * from_kgid_r - map a kgid into a *_r system call gid
+ * @to: user namespace the gid is wanted in
+ * @gid: the kgid
+ * @id: the gid, all zero (UID_R_NONE) if there is no mapping
+ *
+ * Return: whether @gid has a mapping in @to.
+ */
+bool from_kgid_r(struct user_namespace *to, kgid_t gid, struct __kernel_uid_r 
*id)
+{
+       memset(id, 0, sizeof(*id));
+       if (gid_is_legacy(gid)) {
+               gid_t v = from_kgid(to, gid);
+
+               if (v == (gid_t)-1)
+                       return false;
+               id->type = UID_R_LEGACY;
+               id->legacy = v;
+               return true;
+       }
+       return kid_128_to_r(to, gid.type, &gid.uuid, id);
+}
+EXPORT_SYMBOL(from_kgid_r);
+
+/**
+ * from_kuid_r_munged - map a kuid into a *_r system call uid, never failing
+ * @to: user namespace the uid is wanted in
+ * @uid: the kuid
+ * @id: the uid, overflowuid if @uid has no mapping in @to
+ */
+void from_kuid_r_munged(struct user_namespace *to, kuid_t uid,
+                       struct __kernel_uid_r *id)
+{
+       if (!from_kuid_r(to, uid, id)) {
+               id->type = UID_R_LEGACY;
+               id->legacy = overflowuid;
+       }
+}
+EXPORT_SYMBOL(from_kuid_r_munged);
+
+/**
+ * from_kgid_r_munged - map a kgid into a *_r system call gid, never failing
+ * @to: user namespace the gid is wanted in
+ * @gid: the kgid
+ * @id: the gid, overflowgid if @gid has no mapping in @to
+ */
+void from_kgid_r_munged(struct user_namespace *to, kgid_t gid,
+                       struct __kernel_uid_r *id)
+{
+       if (!from_kgid_r(to, gid, id)) {
+               id->type = UID_R_LEGACY;
+               id->legacy = overflowgid;
+       }
+}
+EXPORT_SYMBOL(from_kgid_r_munged);
+
+/*
+ * "No id" from userspace: no pointer, UID_R_NONE, or the classic -1. The
+ * set*id_r() and chown_r() calls take it as "leave unchanged".
+ */
+static bool kid_r_is_none(const struct __kernel_uid_r *id)
+{
+       return id->type == UID_R_NONE ||
+              (id->type == UID_R_LEGACY && id->legacy == (u32)-1);
+}
+
+/**
+ * get_kuid_r - read a uid of a *_r system call from userspace
+ * @from: user namespace the uid is in
+ * @uid: the uid, or NULL
+ * @kuid: the kuid, INVALID_UID for "no id" (NULL, UID_R_NONE or -1)
+ *
+ * Return: 0, -EFAULT, or -EINVAL if the uid is malformed or has no mapping
+ * in @from.
+ */
+int get_kuid_r(struct user_namespace *from,
+              const struct __kernel_uid_r __user *uid, kuid_t *kuid)
+{
+       struct __kernel_uid_r id;
+
+       *kuid = INVALID_UID;
+       if (!uid)
+               return 0;
+       if (copy_from_user(&id, uid, sizeof(id)))
+               return -EFAULT;
+       if (kid_r_is_none(&id))
+               return 0;
+       *kuid = make_kuid_r(from, &id);
+       return uid_valid(*kuid) ? 0 : -EINVAL;
+}
+EXPORT_SYMBOL(get_kuid_r);
+
+/**
+ * get_kgid_r - read a gid of a *_r system call from userspace
+ * @from: user namespace the gid is in
+ * @gid: the gid, or NULL
+ * @kgid: the kgid, INVALID_GID for "no id" (NULL, UID_R_NONE or -1)
+ *
+ * Return: 0, -EFAULT, or -EINVAL if the gid is malformed or has no mapping
+ * in @from.
+ */
+int get_kgid_r(struct user_namespace *from,
+              const struct __kernel_uid_r __user *gid, kgid_t *kgid)
+{
+       struct __kernel_uid_r id;
+
+       *kgid = INVALID_GID;
+       if (!gid)
+               return 0;
+       if (copy_from_user(&id, gid, sizeof(id)))
+               return -EFAULT;
+       if (kid_r_is_none(&id))
+               return 0;
+       *kgid = make_kgid_r(from, &id);
+       return gid_valid(*kgid) ? 0 : -EINVAL;
+}
+EXPORT_SYMBOL(get_kgid_r);
+
+/**
+ * put_kuid_r - write a kuid to userspace as a *_r system call uid
+ * @to: user namespace the uid is wanted in
+ * @kuid: the kuid
+ * @uid: where to put it, overflowuid if @kuid has no mapping in @to
+ *
+ * Return: 0 or -EFAULT.
+ */
+int put_kuid_r(struct user_namespace *to, kuid_t kuid,
+              struct __kernel_uid_r __user *uid)
+{
+       struct __kernel_uid_r id;
+
+       from_kuid_r_munged(to, kuid, &id);
+       return copy_to_user(uid, &id, sizeof(id)) ? -EFAULT : 0;
+}
+EXPORT_SYMBOL(put_kuid_r);
+
+/**
+ * put_kgid_r - write a kgid to userspace as a *_r system call gid
+ * @to: user namespace the gid is wanted in
+ * @kgid: the kgid
+ * @gid: where to put it, overflowgid if @kgid has no mapping in @to
+ *
+ * Return: 0 or -EFAULT.
+ */
+int put_kgid_r(struct user_namespace *to, kgid_t kgid,
+              struct __kernel_uid_r __user *gid)
+{
+       struct __kernel_uid_r id;
+
+       from_kgid_r_munged(to, kgid, &id);
+       return copy_to_user(gid, &id, sizeof(id)) ? -EFAULT : 0;
+}
+EXPORT_SYMBOL(put_kgid_r);
+
+static char *kid_str(u32 type, u32 legacy, const uuid_t *b, char *buf)
+{
+       switch (type) {
+       case KID_LEGACY:
+               snprintf(buf, KID_STR_LEN, "%u", legacy);
+               break;
+       case KID_UUID:
+               snprintf(buf, KID_STR_LEN, "%pUb", b);
+               break;
+       default:
+               snprintf(buf, KID_STR_LEN, "{%pUl}", b);
+               break;
+       }
+       return buf;
+}
+
+/**
+ * kuid_str - text form of a kuid, for logs and procfs
+ * @uid: the kuid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * Return: @buf, holding @uid in the initial user namespace: a decimal
+ * number, a UUID, or a GUID in braces.
+ */
+char *kuid_str(kuid_t uid, char buf[KID_STR_LEN])
+{
+       return kid_str(uid.type, __kuid_val(uid), &uid.uuid, buf);
+}
+EXPORT_SYMBOL(kuid_str);
+
+/**
+ * kgid_str - text form of a kgid, for logs and procfs
+ * @gid: the kgid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * Return: @buf, holding @gid in the initial user namespace: a decimal
+ * number, a UUID, or a GUID in braces.
+ */
+char *kgid_str(kgid_t gid, char buf[KID_STR_LEN])
+{
+       return kid_str(gid.type, __kgid_val(gid), &gid.uuid, buf);
+}
+EXPORT_SYMBOL(kgid_str);
-- 
2.56.0


Reply via email to