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 --- 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 #include #include +#include /* * 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