[RFC PATCH v2 12/41] keys: name per-user keyrings of UUID and GUID users by the whole uid

From: Jan Koester

Date: Tue Oct 20 2026 - 23:22:03 EST


The user keyring, the user session keyring and the persistent keyring
are found by name: "_uid.<uid>", "_uid_ses.<uid>" and
"_persistent.<uid>", the uid in the caller's user namespace. A UUID or
GUID uid has none and comes out as -1, so all such users would find
and share the same keyrings -- each other's keys.

Name their keyrings after the text form of the uid (kuid_str()), which
is unique; classic uids keep their names. Hand request-key the text
form as well instead of -1.

Also add KEYCTL_CHOWN_R, KEYCTL_CHOWN with __kernel_uid_r_t ids, so keys
can be given to UUID and GUID users and groups.

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

Signed-off-by: Jan Koester <jan.koester@xxxxxxxxx>
---
include/uapi/linux/keyctl.h | 1 +
security/keys/compat.c | 3 ++
security/keys/internal.h | 3 ++
security/keys/keyctl.c | 61 ++++++++++++++++++++++++++----------
security/keys/persistent.c | 11 +++++--
security/keys/process_keys.c | 36 +++++++++++++++------
security/keys/request_key.c | 12 +++++--
7 files changed, 97 insertions(+), 30 deletions(-)

diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index 4c8884eea8..73ee0cb7b7 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -70,6 +70,7 @@
#define KEYCTL_MOVE 30 /* Move keys between keyrings */
#define KEYCTL_CAPABILITIES 31 /* Find capabilities of keyrings subsystem */
#define KEYCTL_WATCH_KEY 32 /* Watch a key or ring of keys for changes */
+#define KEYCTL_CHOWN_R 33 /* set ownership of a key, __kernel_uid_r_t ids */

/* keyctl structures */
struct keyctl_dh_params {
diff --git a/security/keys/compat.c b/security/keys/compat.c
index 1545efdca5..c7acf7db59 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -52,6 +52,9 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
case KEYCTL_CHOWN:
return keyctl_chown_key(arg2, arg3, arg4);

+ case KEYCTL_CHOWN_R:
+ return keyctl_chown_key_r(arg2, compat_ptr(arg3), compat_ptr(arg4));
+
case KEYCTL_SETPERM:
return keyctl_setperm_key(arg2, arg3);

diff --git a/security/keys/internal.h b/security/keys/internal.h
index b7b622bc36..63874fa531 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -249,6 +249,9 @@ extern long keyctl_keyring_search(key_serial_t, const char __user *,
const char __user *, key_serial_t);
extern long keyctl_read_key(key_serial_t, char __user *, size_t);
extern long keyctl_chown_key(key_serial_t, uid_t, gid_t);
+extern long keyctl_chown_key_r(key_serial_t id,
+ const struct __kernel_uid_r __user *user,
+ const struct __kernel_uid_r __user *group);
extern long keyctl_setperm_key(key_serial_t, key_perm_t);
extern long keyctl_instantiate_key(key_serial_t, const void __user *,
size_t, key_serial_t);
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index d14ace88e5..984c9c54a5 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -946,26 +946,17 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
*
* If successful, 0 will be returned.
*/
-long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
+static long keyctl_chown_kid(key_serial_t id, kuid_t uid, kgid_t gid)
{
struct key_user *newowner, *zapowner = NULL;
struct key *key;
key_ref_t key_ref;
long ret;
- kuid_t uid;
- kgid_t gid;
unsigned long flags;

- uid = make_kuid(current_user_ns(), user);
- gid = make_kgid(current_user_ns(), group);
- ret = -EINVAL;
- if ((user != (uid_t) -1) && !uid_valid(uid))
- goto error;
- if ((group != (gid_t) -1) && !gid_valid(gid))
- goto error;
-
+ /* invalid ids leave the owner or group unchanged */
ret = 0;
- if (user == (uid_t) -1 && group == (gid_t) -1)
+ if (!uid_valid(uid) && !gid_valid(gid))
goto error;

key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
@@ -985,12 +976,12 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
bool is_privileged_op = false;

/* only the sysadmin can chown a key to some other UID */
- if (user != (uid_t) -1 && !uid_eq(key->uid, uid))
+ if (uid_valid(uid) && !uid_eq(key->uid, uid))
is_privileged_op = true;

/* only the sysadmin can set the key's GID to a group other
* than one of those that the current process subscribes to */
- if (group != (gid_t) -1 && !gid_eq(gid, key->gid) && !in_group_p(gid))
+ if (gid_valid(gid) && !gid_eq(gid, key->gid) && !in_group_p(gid))
is_privileged_op = true;

if (is_privileged_op && !capable(CAP_SYS_ADMIN))
@@ -998,7 +989,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
}

/* change the UID */
- if (user != (uid_t) -1 && !uid_eq(uid, key->uid)) {
+ if (uid_valid(uid) && !uid_eq(uid, key->uid)) {
ret = -ENOMEM;
newowner = key_user_lookup(uid);
if (!newowner)
@@ -1042,7 +1033,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
}

/* change the GID */
- if (group != (gid_t) -1)
+ if (gid_valid(gid))
key->gid = gid;

notify_key(key, NOTIFY_KEY_SETATTR, 0);
@@ -1063,6 +1054,39 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
goto error_put;
}

+long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
+{
+ kuid_t uid = make_kuid(current_user_ns(), user);
+ kgid_t gid = make_kgid(current_user_ns(), group);
+
+ if ((user != (uid_t) -1) && !uid_valid(uid))
+ return -EINVAL;
+ if ((group != (gid_t) -1) && !gid_valid(gid))
+ return -EINVAL;
+ return keyctl_chown_kid(id, uid, gid);
+}
+
+/*
+ * KEYCTL_CHOWN with __kernel_uid_r_t ids, which can be UUIDs and GUIDs. A
+ * NULL pointer, UID_R_NONE or a classic -1 leave the owner or group
+ * unchanged.
+ */
+long keyctl_chown_key_r(key_serial_t id,
+ const struct __kernel_uid_r __user *user,
+ const struct __kernel_uid_r __user *group)
+{
+ kuid_t uid;
+ kgid_t gid;
+ long ret;
+
+ ret = get_kuid_r(current_user_ns(), user, &uid);
+ if (!ret)
+ ret = get_kgid_r(current_user_ns(), group, &gid);
+ if (ret)
+ return ret;
+ return keyctl_chown_kid(id, uid, gid);
+}
+
/*
* Change the permission mask on a key.
*
@@ -1934,6 +1958,11 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
(uid_t) arg3,
(gid_t) arg4);

+ case KEYCTL_CHOWN_R:
+ return keyctl_chown_key_r((key_serial_t) arg2,
+ (const struct __kernel_uid_r __user *) arg3,
+ (const struct __kernel_uid_r __user *) arg4);
+
case KEYCTL_SETPERM:
return keyctl_setperm_key((key_serial_t) arg2,
(key_perm_t) arg3);
diff --git a/security/keys/persistent.c b/security/keys/persistent.c
index bdab995393..2c4b32cd2f 100644
--- a/security/keys/persistent.c
+++ b/security/keys/persistent.c
@@ -83,7 +83,8 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
struct keyring_index_key index_key;
struct key *persistent;
key_ref_t reg_ref, persistent_ref;
- char buf[32];
+ char buf[sizeof("_persistent.") + KID_STR_LEN];
+ char idbuf[KID_STR_LEN];
long ret;
bool created = false;

@@ -91,7 +92,13 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
memset(&index_key, 0, sizeof(index_key));
index_key.type = &key_type_keyring;
index_key.description = buf;
- index_key.desc_len = sprintf(buf, "_persistent.%u", from_kuid(ns, uid));
+ /* a UUID or GUID has no uid in @ns: name it by its text form */
+ if (uid_is_legacy(uid))
+ index_key.desc_len = sprintf(buf, "_persistent.%u",
+ from_kuid(ns, uid));
+ else
+ index_key.desc_len = sprintf(buf, "_persistent.%s",
+ kuid_str(uid, idbuf));
key_set_index_key(&index_key);

if (ns->persistent_keyring_register) {
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index a63c46bb2d..6f275899f3 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -67,6 +67,25 @@ static struct key *get_user_register(struct user_namespace *user_ns)
return reg_keyring;
}

+/*
+ * The name of a user's keyring or user session keyring: @prefix and the uid
+ * in @user_ns, or for a UUID or GUID uid its text form. Those have no uid
+ * in any namespace, and as -1 all of them would share the keyrings.
+ */
+#define USER_KEYRING_NAME_SIZE (sizeof("_uid_ses.") + KID_STR_LEN)
+
+static int user_keyring_name(char *buf, const char *prefix,
+ struct user_namespace *user_ns, kuid_t kuid)
+{
+ char idbuf[KID_STR_LEN];
+
+ if (!uid_is_legacy(kuid))
+ return snprintf(buf, USER_KEYRING_NAME_SIZE, "%s%s", prefix,
+ kuid_str(kuid, idbuf));
+ return snprintf(buf, USER_KEYRING_NAME_SIZE, "%s%u", prefix,
+ from_kuid(user_ns, kuid));
+}
+
/*
* Look up the user and user session keyrings for the current process's UID,
* creating them if they don't exist.
@@ -79,13 +98,12 @@ int look_up_user_keyrings(struct key **_user_keyring,
struct key *reg_keyring, *uid_keyring, *session_keyring;
key_perm_t user_keyring_perm;
key_ref_t uid_keyring_r, session_keyring_r;
- uid_t uid = from_kuid(user_ns, cred->user->uid);
- char buf[20];
+ char buf[USER_KEYRING_NAME_SIZE];
int ret;

user_keyring_perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL;

- kenter("%u", uid);
+ kenter("%u", from_kuid(user_ns, cred->user->uid));

reg_keyring = get_user_register(user_ns);
if (IS_ERR(reg_keyring))
@@ -98,7 +116,7 @@ int look_up_user_keyrings(struct key **_user_keyring,
* already as it may have been pinned by a session, but the user_struct
* pointing to it may have been destroyed by setuid.
*/
- snprintf(buf, sizeof(buf), "_uid.%u", uid);
+ user_keyring_name(buf, "_uid.", user_ns, cred->user->uid);
uid_keyring_r = keyring_search(make_key_ref(reg_keyring, true),
&key_type_keyring, buf, false);
kdebug("_uid %p", uid_keyring_r);
@@ -120,7 +138,7 @@ int look_up_user_keyrings(struct key **_user_keyring,
}

/* Get a default session keyring (which might also exist already) */
- snprintf(buf, sizeof(buf), "_uid_ses.%u", uid);
+ user_keyring_name(buf, "_uid_ses.", user_ns, cred->user->uid);
session_keyring_r = keyring_search(make_key_ref(reg_keyring, true),
&key_type_keyring, buf, false);
kdebug("_uid_ses %p", session_keyring_r);
@@ -186,7 +204,7 @@ struct key *get_user_session_keyring_rcu(const struct cred *cred)
{
struct key *reg_keyring = READ_ONCE(cred->user_ns->user_keyring_register);
key_ref_t session_keyring_r;
- char buf[20];
+ char buf[USER_KEYRING_NAME_SIZE];

struct keyring_search_context ctx = {
.index_key.type = &key_type_keyring,
@@ -201,9 +219,9 @@ struct key *get_user_session_keyring_rcu(const struct cred *cred)
if (!reg_keyring)
return NULL;

- ctx.index_key.desc_len = snprintf(buf, sizeof(buf), "_uid_ses.%u",
- from_kuid(cred->user_ns,
- cred->user->uid));
+ ctx.index_key.desc_len = user_keyring_name(buf, "_uid_ses.",
+ cred->user_ns,
+ cred->user->uid);

session_keyring_r = keyring_search_rcu(make_key_ref(reg_keyring, true),
&ctx);
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index fa2bb9f2f5..48067bb73a 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -122,7 +122,7 @@ static int call_sbin_request_key(struct key *authkey, void *aux)
const struct cred *cred = current_cred();
key_serial_t prkey, sskey;
struct key *key = rka->target_key, *keyring, *session, *user_session;
- char *argv[9], *envp[3], uid_str[12], gid_str[12];
+ char *argv[9], *envp[3], uid_str[KID_STR_LEN], gid_str[KID_STR_LEN];
char key_str[12], keyring_str[3][12];
char desc[20];
int ret, i;
@@ -152,8 +152,14 @@ static int call_sbin_request_key(struct key *authkey, void *aux)
goto error_link;

/* record the UID and GID */
- sprintf(uid_str, "%d", from_kuid(&init_user_ns, cred->fsuid));
- sprintf(gid_str, "%d", from_kgid(&init_user_ns, cred->fsgid));
+ if (uid_is_legacy(cred->fsuid))
+ sprintf(uid_str, "%d", from_kuid(&init_user_ns, cred->fsuid));
+ else
+ kuid_str(cred->fsuid, uid_str);
+ if (gid_is_legacy(cred->fsgid))
+ sprintf(gid_str, "%d", from_kgid(&init_user_ns, cred->fsgid));
+ else
+ kgid_str(cred->fsgid, gid_str);

/* we say which key is under construction */
sprintf(key_str, "%d", key->serial);
--
2.56.0