[RFC PATCH v2 26/41] proc: show UUID and GUID ids in status, accept them for loginuid
sashiko-bot at kernel.org
sashiko-bot at kernel.org
Thu Oct 22 03:17:51 UTC 2026
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] proc: off-by-one TMPBUFLEN in proc_loginuid_write() rejects GUID with newline
- [Medium] proc: torn read of 20-byte loginuid in proc_loginuid_read()
--
commit 272e06b9ffc63c454062912295a6fc0f1ae20fab
Author: Jan Koester <jan.koester at tuxist.de>
proc: show UUID and GUID ids in status, accept them for loginuid
This commit adds Uid_r, Gid_r, and Groups_r lines to /proc/<pid>/status to
display user and group ids in text form via kuid_str_munged() and
kgid_str_munged(). It also updates /proc/<pid>/loginuid to accept and
display text-formatted uuid and guid ids alongside classic numeric uids.
> diff --git a/fs/proc/base.c b/fs/proc/base.c
> index 6a39de424f62a..ec3147e7ab05e 100644
> --- a/fs/proc/base.c
> +++ b/fs/proc/base.c
> @@ -1290,20 +1290,26 @@ static const struct file_operations proc_oom_score_adj_operations = {
> };
>
> #ifdef CONFIG_AUDIT
> -#define TMPBUFLEN 11
> +#define TMPBUFLEN KID_STR_LEN
> static ssize_t proc_loginuid_read(struct file * file, char __user * buf,
> size_t count, loff_t *ppos)
> {
> struct inode * inode = file_inode(file);
> struct task_struct *task = get_proc_task(inode);
> + struct user_namespace *ns = file->f_cred->user_ns;
> ssize_t length;
> char tmpbuf[TMPBUFLEN];
> + kuid_t loginuid;
>
> if (!task)
> return -ESRCH;
> - length = scnprintf(tmpbuf, TMPBUFLEN, "%u",
> - from_kuid(file->f_cred->user_ns,
> - audit_get_loginuid(task)));
> + loginuid = audit_get_loginuid(task);
> + /* a UUID or GUID loginuid as text, if @ns can see it */
> + if (!uid_is_legacy(loginuid) && kuid_has_mapping(ns, loginuid))
> + length = strlen(kuid_str(loginuid, tmpbuf));
> + else
> + length = scnprintf(tmpbuf, TMPBUFLEN, "%u",
> + from_kuid(ns, loginuid));
[Severity: Medium]
Can proc_loginuid_read() observe a torn 20-byte kuid_t when reading
task->loginuid concurrently with proc_loginuid_write() ->
audit_set_loginuid()?
In kernel/audit.c:audit_set_loginuid():
current->sessionid = sessionid;
current->loginuid = loginuid;
Updating current->loginuid is an unsynchronized 20-byte struct store, and
audit_get_loginuid(task) in proc_loginuid_read() copies task->loginuid from
a potentially running target task without a lock or seqcount retry loop.
When transitioning between a legacy id (such as INVALID_UID) and a uuid or
guid id, a concurrent reader of /proc/<pid>/loginuid could see the new
.type before all 16 bytes of .uuid/.guid are written (or after .legacy is
overwritten when unsetting), causing kuid_str() to format a corrupted uuid
or guid string. Conversely, seeing the old KID_LEGACY .type with the first
4 bytes of the new .uuid/.guid already stored would cause from_kuid() to
return a bogus numeric uid to userspace.
Would a seqcount or lock around task->loginuid be needed here to prevent
torn reads?
> put_task_struct(task);
> return simple_read_from_buffer(buf, count, ppos, tmpbuf, length);
> }
> @@ -1312,7 +1318,7 @@ static ssize_t proc_loginuid_write(struct file * file, const char __user * buf,
> size_t count, loff_t *ppos)
> {
> struct inode * inode = file_inode(file);
> - uid_t loginuid;
> + char tmpbuf[TMPBUFLEN];
> kuid_t kloginuid;
> int rv;
>
> @@ -1332,16 +1338,20 @@ static ssize_t proc_loginuid_write(struct file * file, const char __user * buf,
> return -EINVAL;
> }
>
> - rv = kstrtou32_from_user(buf, count, 10, &loginuid);
> - if (rv < 0)
> - return rv;
> + /* a decimal uid, a UUID or a GUID in braces */
> + if (count >= sizeof(tmpbuf))
> + return -EINVAL;
> + if (copy_from_user(tmpbuf, buf, count))
> + return -EFAULT;
> + tmpbuf[count] = '\0';
>
> - /* is userspace tring to explicitly UNSET the loginuid? */
> - if (loginuid == AUDIT_UID_UNSET) {
> - kloginuid = INVALID_UID;
> - } else {
> - kloginuid = make_kuid(file->f_cred->user_ns, loginuid);
> - if (!uid_valid(kloginuid))
> + kloginuid = make_kuid_str(file->f_cred->user_ns, tmpbuf);
[Severity: Medium]
Does this size check in proc_loginuid_write() reject a guid in braces when
written with a trailing newline?
In include/linux/uidgid.h, KID_STR_LEN is 39, which is the exact size of a
38-character guid in braces ("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}") plus
the terminating nul byte.
Meanwhile, make_kuid_str() -> kid_r_from_str() in kernel/uidgid_r.c is
designed to accept an optional trailing newline before checking the length
against KID_STR_LEN:
if (len && str[len - 1] == '\n')
len--;
if (!len || len >= sizeof(buf))
return -EINVAL;
Because TMPBUFLEN is defined as KID_STR_LEN (39), writing a 38-byte guid in
braces followed by '\n' passes count == 39 to proc_loginuid_write(), which
hits count >= sizeof(tmpbuf) and returns -EINVAL before reaching
make_kuid_str().
Should tmpbuf in proc_loginuid_write() (or TMPBUFLEN) be sized to
KID_STR_LEN + 1 so that a guid in braces with a trailing newline is
accepted just like decimal uids and uuids with a trailing newline?
> + if (!uid_valid(kloginuid)) {
> + u32 loginuid;
> +
> + /* is userspace tring to explicitly UNSET the loginuid? */
> + if (kstrtou32(tmpbuf, 10, &loginuid) ||
> + loginuid != AUDIT_UID_UNSET)
> return -EINVAL;
> }
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1792552161.git.jan.koester@tuxist.de?part=26
More information about the Linux-security-module-archive
mailing list