[RFC PATCH v2 25/41] uidgid: parse and print ids in their text form

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


Add helpers for interfaces that pass ids as text:

- make_kuid_str()/make_kgid_str() parse what kuid_str() writes: a
  decimal number, a UUID, or a GUID in braces,
- kuid_str_munged()/kgid_str_munged() write an id as seen from a user
  namespace, the overflow id if it has no mapping there,
- kuid_str_signed()/kgid_str_signed() write a legacy id as a signed
  number, for output that used "%d",
- KUID_STR() and friends wrap them as expressions with a buffer of the
  enclosing block, so that printf-like calls can replace
  "%u", from_kuid(&init_user_ns, uid) by "%s", KUID_STR(uid) and print
  legacy ids exactly as before.

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

Signed-off-by: Jan Koester <jan.koester at tuxist.de>
---
 include/linux/uidgid.h |  19 ++++++
 kernel/uidgid_r.c      | 152 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 167 insertions(+), 4 deletions(-)

diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h
index ac7786f760..03ece8a4cb 100644
--- a/include/linux/uidgid.h
+++ b/include/linux/uidgid.h
@@ -190,6 +190,25 @@ static inline u32 kgid_hash(kgid_t gid)
 
 char *kuid_str(kuid_t uid, char buf[KID_STR_LEN]);
 char *kgid_str(kgid_t gid, char buf[KID_STR_LEN]);
+char *kuid_str_signed(kuid_t uid, char buf[KID_STR_LEN]);
+char *kgid_str_signed(kgid_t gid, char buf[KID_STR_LEN]);
+
+/*
+ * The text form of an id as an expression, in a buffer that lives until
+ * the end of the enclosing block -- for "%s" in place of "%u" (or "%d",
+ * with the _D variants, which write a legacy id as a signed number) of
+ * from_kuid(&init_user_ns, uid) in printf-like calls.
+ */
+#define KUID_STR(uid)	kuid_str(uid, (char [KID_STR_LEN]) {})
+#define KGID_STR(gid)	kgid_str(gid, (char [KID_STR_LEN]) {})
+#define KUID_STR_D(uid)	kuid_str_signed(uid, (char [KID_STR_LEN]) {})
+#define KGID_STR_D(gid)	kgid_str_signed(gid, (char [KID_STR_LEN]) {})
+char *kuid_str_munged(struct user_namespace *to, kuid_t uid,
+		      char buf[KID_STR_LEN]);
+char *kgid_str_munged(struct user_namespace *to, kgid_t gid,
+		      char buf[KID_STR_LEN]);
+kuid_t make_kuid_str(struct user_namespace *from, const char *str);
+kgid_t make_kgid_str(struct user_namespace *from, const char *str);
 
 #ifdef CONFIG_USER_NS
 
diff --git a/kernel/uidgid_r.c b/kernel/uidgid_r.c
index d71aecddb2..9bba60f413 100644
--- a/kernel/uidgid_r.c
+++ b/kernel/uidgid_r.c
@@ -12,6 +12,7 @@
 #include <linux/uidgid.h>
 #include <linux/uidgid_r.h>
 #include <linux/user_namespace.h>
+#include <linux/uuid.h>
 
 /*
  * Whether @id is well formed: a known type, reserved bytes zero, a UUID of
@@ -278,11 +279,12 @@ int put_kgid_r(struct user_namespace *to, kgid_t kgid,
 }
 EXPORT_SYMBOL(put_kgid_r);
 
-static char *kid_str(u32 type, u32 legacy, const uuid_t *b, char *buf)
+static char *kid_str(u32 type, u32 legacy, const uuid_t *b, char *buf,
+		     bool sgn)
 {
 	switch (type) {
 	case KID_LEGACY:
-		snprintf(buf, KID_STR_LEN, "%u", legacy);
+		snprintf(buf, KID_STR_LEN, sgn ? "%d" : "%u", legacy);
 		break;
 	case KID_UUID:
 		snprintf(buf, KID_STR_LEN, "%pUb", b);
@@ -304,7 +306,7 @@ static char *kid_str(u32 type, u32 legacy, const uuid_t *b, char *buf)
  */
 char *kuid_str(kuid_t uid, char buf[KID_STR_LEN])
 {
-	return kid_str(uid.type, __kuid_val(uid), &uid.uuid, buf);
+	return kid_str(uid.type, __kuid_val(uid), &uid.uuid, buf, false);
 }
 EXPORT_SYMBOL(kuid_str);
 
@@ -318,6 +320,148 @@ EXPORT_SYMBOL(kuid_str);
  */
 char *kgid_str(kgid_t gid, char buf[KID_STR_LEN])
 {
-	return kid_str(gid.type, __kgid_val(gid), &gid.uuid, buf);
+	return kid_str(gid.type, __kgid_val(gid), &gid.uuid, buf, false);
 }
 EXPORT_SYMBOL(kgid_str);
+
+/**
+ * kuid_str_signed - like kuid_str(), but a legacy uid as a signed number
+ * @uid: the kuid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * For callers that used to print from_kuid() with "%d".
+ *
+ * Return: @buf.
+ */
+char *kuid_str_signed(kuid_t uid, char buf[KID_STR_LEN])
+{
+	return kid_str(uid.type, __kuid_val(uid), &uid.uuid, buf, true);
+}
+EXPORT_SYMBOL(kuid_str_signed);
+
+/**
+ * kgid_str_signed - like kgid_str(), but a legacy gid as a signed number
+ * @gid: the kgid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * For callers that used to print from_kgid() with "%d".
+ *
+ * Return: @buf.
+ */
+char *kgid_str_signed(kgid_t gid, char buf[KID_STR_LEN])
+{
+	return kid_str(gid.type, __kgid_val(gid), &gid.uuid, buf, true);
+}
+EXPORT_SYMBOL(kgid_str_signed);
+
+/*
+ * Parses the text forms of kuid_str() -- a decimal number, a UUID, a GUID
+ * in braces -- with an optional trailing newline.
+ */
+static int kid_r_from_str(const char *str, struct __kernel_uid_r *id)
+{
+	char buf[KID_STR_LEN];
+	size_t len = strlen(str);
+
+	if (len && str[len - 1] == '\n')
+		len--;
+	if (!len || len >= sizeof(buf))
+		return -EINVAL;
+	memcpy(buf, str, len);
+	buf[len] = '\0';
+	memset(id, 0, sizeof(*id));
+
+	if (len == UUID_STRING_LEN) {
+		id->type = UID_R_UUID;
+		return uuid_parse(buf, (uuid_t *)id->uuid);
+	}
+	if (len == UUID_STRING_LEN + 2 && buf[0] == '{' && buf[len - 1] == '}') {
+		buf[len - 1] = '\0';
+		id->type = UID_R_GUID;
+		return guid_parse(buf + 1, (guid_t *)id->guid);
+	}
+	id->type = UID_R_LEGACY;
+	return kstrtou32(buf, 10, &id->legacy);
+}
+
+/**
+ * make_kuid_str - map the text form of a uid into a kuid
+ * @from: user namespace the uid is in
+ * @str: a decimal number, a UUID, or a GUID in braces; a trailing newline
+ *       is allowed
+ *
+ * Return: the kuid, INVALID_UID if @str is malformed or has no mapping in
+ * @from.
+ */
+kuid_t make_kuid_str(struct user_namespace *from, const char *str)
+{
+	struct __kernel_uid_r id;
+
+	if (kid_r_from_str(str, &id))
+		return INVALID_UID;
+	return make_kuid_r(from, &id);
+}
+EXPORT_SYMBOL(make_kuid_str);
+
+/**
+ * make_kgid_str - map the text form of a gid into a kgid
+ * @from: user namespace the gid is in
+ * @str: a decimal number, a UUID, or a GUID in braces; a trailing newline
+ *       is allowed
+ *
+ * Return: the kgid, INVALID_GID if @str is malformed or has no mapping in
+ * @from.
+ */
+kgid_t make_kgid_str(struct user_namespace *from, const char *str)
+{
+	struct __kernel_uid_r id;
+
+	if (kid_r_from_str(str, &id))
+		return INVALID_GID;
+	return make_kgid_r(from, &id);
+}
+EXPORT_SYMBOL(make_kgid_str);
+
+/**
+ * kuid_str_munged - text form of a kuid in a user namespace
+ * @to: user namespace the uid is wanted in
+ * @uid: the kuid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * Return: @buf, holding the uid in @to like kuid_str() does, overflowuid if
+ * @uid has no mapping there.
+ */
+char *kuid_str_munged(struct user_namespace *to, kuid_t uid,
+		      char buf[KID_STR_LEN])
+{
+	if (!kuid_has_mapping(to, uid))
+		snprintf(buf, KID_STR_LEN, "%u", overflowuid);
+	else if (uid_is_legacy(uid))
+		snprintf(buf, KID_STR_LEN, "%u", from_kuid(to, uid));
+	else
+		kuid_str(uid, buf);
+	return buf;
+}
+EXPORT_SYMBOL(kuid_str_munged);
+
+/**
+ * kgid_str_munged - text form of a kgid in a user namespace
+ * @to: user namespace the gid is wanted in
+ * @gid: the kgid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * Return: @buf, holding the gid in @to like kgid_str() does, overflowgid if
+ * @gid has no mapping there.
+ */
+char *kgid_str_munged(struct user_namespace *to, kgid_t gid,
+		      char buf[KID_STR_LEN])
+{
+	if (!kgid_has_mapping(to, gid))
+		snprintf(buf, KID_STR_LEN, "%u", overflowgid);
+	else if (gid_is_legacy(gid))
+		snprintf(buf, KID_STR_LEN, "%u", from_kgid(to, gid));
+	else
+		kgid_str(gid, buf);
+	return buf;
+}
+EXPORT_SYMBOL(kgid_str_munged);
-- 
2.56.0




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