The user keyring, the user session keyring and the persistent keyring are found by name: "_uid.", "_uid_ses." and "_persistent.", 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 --- 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