mirror of
https://github.com/torvalds/linux.git
synced 2026-10-26 00:56:03 +01:00
When no keyring exists for the requested UID, KEYCTL_GET_PERSISTENT
creates and registers one before linking it to the requested destination.
The configured timeout is set only after the destination link succeeds.
A destination restricted with KEYCTL_RESTRICT_KEYRING makes that link fail
with -EPERM. With persistent_keyring_expiry set to 60 seconds, /proc/keys
still reports the registered keyring's expiry as "perm".
The failed call therefore leaves a quota-exempt keyring in the namespace's
hidden register, where it may remain until namespace teardown.
Rename the write-locked helper to key_get_or_create_persistent() and return
the key reference through a result parameter. Return 0 when it creates a
keyring and 1 when the retry finds an existing one. Return a negative error
on failure. Another caller may create the keyring between the initial
read-locked lookup and the retry under the write lock.
Set the timeout after permission checking and linking. Do this on success,
or on failure if the call allocated the keyring. This leaves a new keyring
collectible after failure without starting its timeout while linking is
still in progress.
Cc: stable@vger.kernel.org # v5.10+
Fixes: f36f8c75ae ("KEYS: Add per-user_namespace registers for persistent per-UID kerberos caches")
Assisted-by: LLM
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
Link: https://lore.kernel.org/r/20260912095440.79864-1-kmehltretter@gmail.com
Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org>
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
177 lines
4.5 KiB
C
177 lines
4.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/* General persistent per-UID keyrings register
|
|
*
|
|
* Copyright (C) 2013 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*/
|
|
|
|
#include <linux/user_namespace.h>
|
|
#include <linux/cred.h>
|
|
|
|
#include "internal.h"
|
|
|
|
unsigned persistent_keyring_expiry = 3 * 24 * 3600; /* Expire after 3 days of non-use */
|
|
|
|
/*
|
|
* Create the persistent keyring register for the current user namespace.
|
|
*
|
|
* Called with the namespace's sem locked for writing.
|
|
*/
|
|
static int key_create_persistent_register(struct user_namespace *ns)
|
|
{
|
|
struct key *reg = keyring_alloc(".persistent_register",
|
|
KUIDT_INIT(0), KGIDT_INIT(0),
|
|
current_cred(),
|
|
((KEY_POS_ALL & ~KEY_POS_SETATTR) |
|
|
KEY_USR_VIEW | KEY_USR_READ),
|
|
KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
|
|
if (IS_ERR(reg))
|
|
return PTR_ERR(reg);
|
|
|
|
ns->persistent_keyring_register = reg;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Get or create the persistent keyring for the specified user.
|
|
*
|
|
* Called with the namespace's sem locked for writing.
|
|
*
|
|
* Returns a boolean indicating whether the keyring already existed,
|
|
* or a negative error. On success, *persistent_ref holds a reference
|
|
* to the keyring.
|
|
*/
|
|
static int key_get_or_create_persistent(struct user_namespace *ns, kuid_t uid,
|
|
struct keyring_index_key *index_key,
|
|
key_ref_t *persistent_ref)
|
|
{
|
|
struct key *persistent;
|
|
key_ref_t reg_ref;
|
|
|
|
if (!ns->persistent_keyring_register) {
|
|
int err = key_create_persistent_register(ns);
|
|
|
|
if (err < 0)
|
|
return err;
|
|
} else {
|
|
reg_ref = make_key_ref(ns->persistent_keyring_register, true);
|
|
*persistent_ref = find_key_to_update(reg_ref, index_key);
|
|
if (*persistent_ref)
|
|
return 1;
|
|
}
|
|
|
|
persistent = keyring_alloc(index_key->description,
|
|
uid, INVALID_GID, current_cred(),
|
|
((KEY_POS_ALL & ~KEY_POS_SETATTR) |
|
|
KEY_USR_VIEW | KEY_USR_READ),
|
|
KEY_ALLOC_NOT_IN_QUOTA, NULL,
|
|
ns->persistent_keyring_register);
|
|
if (IS_ERR(persistent))
|
|
return PTR_ERR(persistent);
|
|
|
|
*persistent_ref = make_key_ref(persistent, true);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Get the persistent keyring for a specific UID and link it to the nominated
|
|
* keyring.
|
|
*/
|
|
static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
|
|
key_ref_t dest_ref)
|
|
{
|
|
struct keyring_index_key index_key;
|
|
struct key *persistent;
|
|
key_ref_t reg_ref, persistent_ref;
|
|
char buf[32];
|
|
long ret;
|
|
bool created = false;
|
|
|
|
/* Look in the register if it exists */
|
|
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));
|
|
key_set_index_key(&index_key);
|
|
|
|
if (ns->persistent_keyring_register) {
|
|
reg_ref = make_key_ref(ns->persistent_keyring_register, true);
|
|
down_read(&ns->keyring_sem);
|
|
persistent_ref = find_key_to_update(reg_ref, &index_key);
|
|
up_read(&ns->keyring_sem);
|
|
|
|
if (persistent_ref)
|
|
goto found;
|
|
}
|
|
|
|
/* It wasn't in the register, so we'll need to create it. We might
|
|
* also need to create the register.
|
|
*/
|
|
down_write(&ns->keyring_sem);
|
|
ret = key_get_or_create_persistent(ns, uid, &index_key,
|
|
&persistent_ref);
|
|
up_write(&ns->keyring_sem);
|
|
if (ret < 0)
|
|
return ret;
|
|
created = ret == 0;
|
|
|
|
found:
|
|
persistent = key_ref_to_ptr(persistent_ref);
|
|
ret = key_task_permission(persistent_ref, current_cred(), KEY_NEED_LINK);
|
|
if (ret == 0)
|
|
ret = key_link(key_ref_to_ptr(dest_ref), persistent);
|
|
|
|
if (ret == 0 || created)
|
|
key_set_timeout(persistent, persistent_keyring_expiry);
|
|
|
|
if (ret == 0)
|
|
ret = persistent->serial;
|
|
|
|
key_ref_put(persistent_ref);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Get the persistent keyring for a specific UID and link it to the nominated
|
|
* keyring.
|
|
*/
|
|
long keyctl_get_persistent(uid_t _uid, key_serial_t destid)
|
|
{
|
|
struct user_namespace *ns = current_user_ns();
|
|
key_ref_t dest_ref;
|
|
kuid_t uid;
|
|
long ret;
|
|
|
|
/* -1 indicates the current user */
|
|
if (_uid == (uid_t)-1) {
|
|
uid = current_uid();
|
|
} else {
|
|
uid = make_kuid(ns, _uid);
|
|
if (!uid_valid(uid))
|
|
return -EINVAL;
|
|
|
|
/* You can only see your own persistent cache if you're not
|
|
* sufficiently privileged.
|
|
*/
|
|
if (!uid_eq(uid, current_uid()) &&
|
|
!uid_eq(uid, current_euid()) &&
|
|
!ns_capable(ns, CAP_SETUID))
|
|
return -EPERM;
|
|
}
|
|
|
|
/* There must be a destination keyring */
|
|
dest_ref = lookup_user_key(destid, KEY_LOOKUP_CREATE, KEY_NEED_WRITE);
|
|
if (IS_ERR(dest_ref))
|
|
return PTR_ERR(dest_ref);
|
|
if (key_ref_to_ptr(dest_ref)->type != &key_type_keyring) {
|
|
ret = -ENOTDIR;
|
|
goto out_put_dest;
|
|
}
|
|
|
|
ret = key_get_persistent(ns, uid, dest_ref);
|
|
|
|
out_put_dest:
|
|
key_ref_put(dest_ref);
|
|
return ret;
|
|
}
|