diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /security/keys/key.c | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/keys/key.c')
-rw-r--r-- | security/keys/key.c | 1184 |
1 files changed, 1184 insertions, 0 deletions
diff --git a/security/keys/key.c b/security/keys/key.c new file mode 100644 index 000000000..d3ebc0533 --- /dev/null +++ b/security/keys/key.c @@ -0,0 +1,1184 @@ +/* Basic authentication token and access key management + * + * Copyright (C) 2004-2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poison.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/security.h> +#include <linux/workqueue.h> +#include <linux/random.h> +#include <linux/err.h> +#include "internal.h" + +struct kmem_cache *key_jar; +struct rb_root key_serial_tree; /* tree of keys indexed by serial */ +DEFINE_SPINLOCK(key_serial_lock); + +struct rb_root key_user_tree; /* tree of quota records indexed by UID */ +DEFINE_SPINLOCK(key_user_lock); + +unsigned int key_quota_root_maxkeys = 1000000; /* root's key count quota */ +unsigned int key_quota_root_maxbytes = 25000000; /* root's key space quota */ +unsigned int key_quota_maxkeys = 200; /* general key count quota */ +unsigned int key_quota_maxbytes = 20000; /* general key space quota */ + +static LIST_HEAD(key_types_list); +static DECLARE_RWSEM(key_types_sem); + +/* We serialise key instantiation and link */ +DEFINE_MUTEX(key_construction_mutex); + +#ifdef KEY_DEBUGGING +void __key_check(const struct key *key) +{ + printk("__key_check: key %p {%08x} should be {%08x}\n", + key, key->magic, KEY_DEBUG_MAGIC); + BUG(); +} +#endif + +/* + * Get the key quota record for a user, allocating a new record if one doesn't + * already exist. + */ +struct key_user *key_user_lookup(kuid_t uid) +{ + struct key_user *candidate = NULL, *user; + struct rb_node *parent, **p; + +try_again: + parent = NULL; + p = &key_user_tree.rb_node; + spin_lock(&key_user_lock); + + /* search the tree for a user record with a matching UID */ + while (*p) { + parent = *p; + user = rb_entry(parent, struct key_user, node); + + if (uid_lt(uid, user->uid)) + p = &(*p)->rb_left; + else if (uid_gt(uid, user->uid)) + p = &(*p)->rb_right; + else + goto found; + } + + /* if we get here, we failed to find a match in the tree */ + if (!candidate) { + /* allocate a candidate user record if we don't already have + * one */ + spin_unlock(&key_user_lock); + + user = NULL; + candidate = kmalloc(sizeof(struct key_user), GFP_KERNEL); + if (unlikely(!candidate)) + goto out; + + /* the allocation may have scheduled, so we need to repeat the + * search lest someone else added the record whilst we were + * asleep */ + goto try_again; + } + + /* if we get here, then the user record still hadn't appeared on the + * second pass - so we use the candidate record */ + refcount_set(&candidate->usage, 1); + atomic_set(&candidate->nkeys, 0); + atomic_set(&candidate->nikeys, 0); + candidate->uid = uid; + candidate->qnkeys = 0; + candidate->qnbytes = 0; + spin_lock_init(&candidate->lock); + mutex_init(&candidate->cons_lock); + + rb_link_node(&candidate->node, parent, p); + rb_insert_color(&candidate->node, &key_user_tree); + spin_unlock(&key_user_lock); + user = candidate; + goto out; + + /* okay - we found a user record for this UID */ +found: + refcount_inc(&user->usage); + spin_unlock(&key_user_lock); + kfree(candidate); +out: + return user; +} + +/* + * Dispose of a user structure + */ +void key_user_put(struct key_user *user) +{ + if (refcount_dec_and_lock(&user->usage, &key_user_lock)) { + rb_erase(&user->node, &key_user_tree); + spin_unlock(&key_user_lock); + + kfree(user); + } +} + +/* + * Allocate a serial number for a key. These are assigned randomly to avoid + * security issues through covert channel problems. + */ +static inline void key_alloc_serial(struct key *key) +{ + struct rb_node *parent, **p; + struct key *xkey; + + /* propose a random serial number and look for a hole for it in the + * serial number tree */ + do { + get_random_bytes(&key->serial, sizeof(key->serial)); + + key->serial >>= 1; /* negative numbers are not permitted */ + } while (key->serial < 3); + + spin_lock(&key_serial_lock); + +attempt_insertion: + parent = NULL; + p = &key_serial_tree.rb_node; + + while (*p) { + parent = *p; + xkey = rb_entry(parent, struct key, serial_node); + + if (key->serial < xkey->serial) + p = &(*p)->rb_left; + else if (key->serial > xkey->serial) + p = &(*p)->rb_right; + else + goto serial_exists; + } + + /* we've found a suitable hole - arrange for this key to occupy it */ + rb_link_node(&key->serial_node, parent, p); + rb_insert_color(&key->serial_node, &key_serial_tree); + + spin_unlock(&key_serial_lock); + return; + + /* we found a key with the proposed serial number - walk the tree from + * that point looking for the next unused serial number */ +serial_exists: + for (;;) { + key->serial++; + if (key->serial < 3) { + key->serial = 3; + goto attempt_insertion; + } + + parent = rb_next(parent); + if (!parent) + goto attempt_insertion; + + xkey = rb_entry(parent, struct key, serial_node); + if (key->serial < xkey->serial) + goto attempt_insertion; + } +} + +/** + * key_alloc - Allocate a key of the specified type. + * @type: The type of key to allocate. + * @desc: The key description to allow the key to be searched out. + * @uid: The owner of the new key. + * @gid: The group ID for the new key's group permissions. + * @cred: The credentials specifying UID namespace. + * @perm: The permissions mask of the new key. + * @flags: Flags specifying quota properties. + * @restrict_link: Optional link restriction for new keyrings. + * + * Allocate a key of the specified type with the attributes given. The key is + * returned in an uninstantiated state and the caller needs to instantiate the + * key before returning. + * + * The restrict_link structure (if not NULL) will be freed when the + * keyring is destroyed, so it must be dynamically allocated. + * + * The user's key count quota is updated to reflect the creation of the key and + * the user's key data quota has the default for the key type reserved. The + * instantiation function should amend this as necessary. If insufficient + * quota is available, -EDQUOT will be returned. + * + * The LSM security modules can prevent a key being created, in which case + * -EACCES will be returned. + * + * Returns a pointer to the new key if successful and an error code otherwise. + * + * Note that the caller needs to ensure the key type isn't uninstantiated. + * Internally this can be done by locking key_types_sem. Externally, this can + * be done by either never unregistering the key type, or making sure + * key_alloc() calls don't race with module unloading. + */ +struct key *key_alloc(struct key_type *type, const char *desc, + kuid_t uid, kgid_t gid, const struct cred *cred, + key_perm_t perm, unsigned long flags, + struct key_restriction *restrict_link) +{ + struct key_user *user = NULL; + struct key *key; + size_t desclen, quotalen; + int ret; + + key = ERR_PTR(-EINVAL); + if (!desc || !*desc) + goto error; + + if (type->vet_description) { + ret = type->vet_description(desc); + if (ret < 0) { + key = ERR_PTR(ret); + goto error; + } + } + + desclen = strlen(desc); + quotalen = desclen + 1 + type->def_datalen; + + /* get hold of the key tracking for this user */ + user = key_user_lookup(uid); + if (!user) + goto no_memory_1; + + /* check that the user's quota permits allocation of another key and + * its description */ + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + unsigned maxkeys = uid_eq(uid, GLOBAL_ROOT_UID) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = uid_eq(uid, GLOBAL_ROOT_UID) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + spin_lock(&user->lock); + if (!(flags & KEY_ALLOC_QUOTA_OVERRUN)) { + if (user->qnkeys + 1 > maxkeys || + user->qnbytes + quotalen > maxbytes || + user->qnbytes + quotalen < user->qnbytes) + goto no_quota; + } + + user->qnkeys++; + user->qnbytes += quotalen; + spin_unlock(&user->lock); + } + + /* allocate and initialise the key and its description */ + key = kmem_cache_zalloc(key_jar, GFP_KERNEL); + if (!key) + goto no_memory_2; + + key->index_key.desc_len = desclen; + key->index_key.description = kmemdup(desc, desclen + 1, GFP_KERNEL); + if (!key->index_key.description) + goto no_memory_3; + + refcount_set(&key->usage, 1); + init_rwsem(&key->sem); + lockdep_set_class(&key->sem, &type->lock_class); + key->index_key.type = type; + key->user = user; + key->quotalen = quotalen; + key->datalen = type->def_datalen; + key->uid = uid; + key->gid = gid; + key->perm = perm; + key->restrict_link = restrict_link; + key->last_used_at = ktime_get_real_seconds(); + + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) + key->flags |= 1 << KEY_FLAG_IN_QUOTA; + if (flags & KEY_ALLOC_BUILT_IN) + key->flags |= 1 << KEY_FLAG_BUILTIN; + if (flags & KEY_ALLOC_UID_KEYRING) + key->flags |= 1 << KEY_FLAG_UID_KEYRING; + if (flags & KEY_ALLOC_SET_KEEP) + key->flags |= 1 << KEY_FLAG_KEEP; + +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC; +#endif + + /* let the security module know about the key */ + ret = security_key_alloc(key, cred, flags); + if (ret < 0) + goto security_error; + + /* publish the key by giving it a serial number */ + atomic_inc(&user->nkeys); + key_alloc_serial(key); + +error: + return key; + +security_error: + kfree(key->description); + kmem_cache_free(key_jar, key); + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + spin_lock(&user->lock); + user->qnkeys--; + user->qnbytes -= quotalen; + spin_unlock(&user->lock); + } + key_user_put(user); + key = ERR_PTR(ret); + goto error; + +no_memory_3: + kmem_cache_free(key_jar, key); +no_memory_2: + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + spin_lock(&user->lock); + user->qnkeys--; + user->qnbytes -= quotalen; + spin_unlock(&user->lock); + } + key_user_put(user); +no_memory_1: + key = ERR_PTR(-ENOMEM); + goto error; + +no_quota: + spin_unlock(&user->lock); + key_user_put(user); + key = ERR_PTR(-EDQUOT); + goto error; +} +EXPORT_SYMBOL(key_alloc); + +/** + * key_payload_reserve - Adjust data quota reservation for the key's payload + * @key: The key to make the reservation for. + * @datalen: The amount of data payload the caller now wants. + * + * Adjust the amount of the owning user's key data quota that a key reserves. + * If the amount is increased, then -EDQUOT may be returned if there isn't + * enough free quota available. + * + * If successful, 0 is returned. + */ +int key_payload_reserve(struct key *key, size_t datalen) +{ + int delta = (int)datalen - key->datalen; + int ret = 0; + + key_check(key); + + /* contemplate the quota adjustment */ + if (delta != 0 && test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + unsigned maxbytes = uid_eq(key->user->uid, GLOBAL_ROOT_UID) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + spin_lock(&key->user->lock); + + if (delta > 0 && + (key->user->qnbytes + delta > maxbytes || + key->user->qnbytes + delta < key->user->qnbytes)) { + ret = -EDQUOT; + } + else { + key->user->qnbytes += delta; + key->quotalen += delta; + } + spin_unlock(&key->user->lock); + } + + /* change the recorded data length if that didn't generate an error */ + if (ret == 0) + key->datalen = datalen; + + return ret; +} +EXPORT_SYMBOL(key_payload_reserve); + +/* + * Change the key state to being instantiated. + */ +static void mark_key_instantiated(struct key *key, int reject_error) +{ + /* Commit the payload before setting the state; barrier versus + * key_read_state(). + */ + smp_store_release(&key->state, + (reject_error < 0) ? reject_error : KEY_IS_POSITIVE); +} + +/* + * Instantiate a key and link it into the target keyring atomically. Must be + * called with the target keyring's semaphore writelocked. The target key's + * semaphore need not be locked as instantiation is serialised by + * key_construction_mutex. + */ +static int __key_instantiate_and_link(struct key *key, + struct key_preparsed_payload *prep, + struct key *keyring, + struct key *authkey, + struct assoc_array_edit **_edit) +{ + int ret, awaken; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + mutex_lock(&key_construction_mutex); + + /* can't instantiate twice */ + if (key->state == KEY_IS_UNINSTANTIATED) { + /* instantiate the key */ + ret = key->type->instantiate(key, prep); + + if (ret == 0) { + /* mark the key as being instantiated */ + atomic_inc(&key->user->nikeys); + mark_key_instantiated(key, 0); + + if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) + awaken = 1; + + /* and link it into the destination keyring */ + if (keyring) { + if (test_bit(KEY_FLAG_KEEP, &keyring->flags)) + set_bit(KEY_FLAG_KEEP, &key->flags); + + __key_link(key, _edit); + } + + /* disable the authorisation key */ + if (authkey) + key_revoke(authkey); + + if (prep->expiry != TIME64_MAX) { + key->expiry = prep->expiry; + key_schedule_gc(prep->expiry + key_gc_delay); + } + } + } + + mutex_unlock(&key_construction_mutex); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_bit(&key->flags, KEY_FLAG_USER_CONSTRUCT); + + return ret; +} + +/** + * key_instantiate_and_link - Instantiate a key and link it into the keyring. + * @key: The key to instantiate. + * @data: The data to use to instantiate the keyring. + * @datalen: The length of @data. + * @keyring: Keyring to create a link in on success (or NULL). + * @authkey: The authorisation token permitting instantiation. + * + * Instantiate a key that's in the uninstantiated state using the provided data + * and, if successful, link it in to the destination keyring if one is + * supplied. + * + * If successful, 0 is returned, the authorisation token is revoked and anyone + * waiting for the key is woken up. If the key was already instantiated, + * -EBUSY will be returned. + */ +int key_instantiate_and_link(struct key *key, + const void *data, + size_t datalen, + struct key *keyring, + struct key *authkey) +{ + struct key_preparsed_payload prep; + struct assoc_array_edit *edit; + int ret; + + memset(&prep, 0, sizeof(prep)); + prep.data = data; + prep.datalen = datalen; + prep.quotalen = key->type->def_datalen; + prep.expiry = TIME64_MAX; + if (key->type->preparse) { + ret = key->type->preparse(&prep); + if (ret < 0) + goto error; + } + + if (keyring) { + ret = __key_link_begin(keyring, &key->index_key, &edit); + if (ret < 0) + goto error; + + if (keyring->restrict_link && keyring->restrict_link->check) { + struct key_restriction *keyres = keyring->restrict_link; + + ret = keyres->check(keyring, key->type, &prep.payload, + keyres->key); + if (ret < 0) + goto error_link_end; + } + } + + ret = __key_instantiate_and_link(key, &prep, keyring, authkey, &edit); + +error_link_end: + if (keyring) + __key_link_end(keyring, &key->index_key, edit); + +error: + if (key->type->preparse) + key->type->free_preparse(&prep); + return ret; +} + +EXPORT_SYMBOL(key_instantiate_and_link); + +/** + * key_reject_and_link - Negatively instantiate a key and link it into the keyring. + * @key: The key to instantiate. + * @timeout: The timeout on the negative key. + * @error: The error to return when the key is hit. + * @keyring: Keyring to create a link in on success (or NULL). + * @authkey: The authorisation token permitting instantiation. + * + * Negatively instantiate a key that's in the uninstantiated state and, if + * successful, set its timeout and stored error and link it in to the + * destination keyring if one is supplied. The key and any links to the key + * will be automatically garbage collected after the timeout expires. + * + * Negative keys are used to rate limit repeated request_key() calls by causing + * them to return the stored error code (typically ENOKEY) until the negative + * key expires. + * + * If successful, 0 is returned, the authorisation token is revoked and anyone + * waiting for the key is woken up. If the key was already instantiated, + * -EBUSY will be returned. + */ +int key_reject_and_link(struct key *key, + unsigned timeout, + unsigned error, + struct key *keyring, + struct key *authkey) +{ + struct assoc_array_edit *edit; + int ret, awaken, link_ret = 0; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + if (keyring) { + if (keyring->restrict_link) + return -EPERM; + + link_ret = __key_link_begin(keyring, &key->index_key, &edit); + } + + mutex_lock(&key_construction_mutex); + + /* can't instantiate twice */ + if (key->state == KEY_IS_UNINSTANTIATED) { + /* mark the key as being negatively instantiated */ + atomic_inc(&key->user->nikeys); + mark_key_instantiated(key, -error); + key->expiry = ktime_get_real_seconds() + timeout; + key_schedule_gc(key->expiry + key_gc_delay); + + if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) + awaken = 1; + + ret = 0; + + /* and link it into the destination keyring */ + if (keyring && link_ret == 0) + __key_link(key, &edit); + + /* disable the authorisation key */ + if (authkey) + key_revoke(authkey); + } + + mutex_unlock(&key_construction_mutex); + + if (keyring && link_ret == 0) + __key_link_end(keyring, &key->index_key, edit); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_bit(&key->flags, KEY_FLAG_USER_CONSTRUCT); + + return ret == 0 ? link_ret : ret; +} +EXPORT_SYMBOL(key_reject_and_link); + +/** + * key_put - Discard a reference to a key. + * @key: The key to discard a reference from. + * + * Discard a reference to a key, and when all the references are gone, we + * schedule the cleanup task to come and pull it out of the tree in process + * context at some later time. + */ +void key_put(struct key *key) +{ + if (key) { + key_check(key); + + if (refcount_dec_and_test(&key->usage)) + schedule_work(&key_gc_work); + } +} +EXPORT_SYMBOL(key_put); + +/* + * Find a key by its serial number. + */ +struct key *key_lookup(key_serial_t id) +{ + struct rb_node *n; + struct key *key; + + spin_lock(&key_serial_lock); + + /* search the tree for the specified key */ + n = key_serial_tree.rb_node; + while (n) { + key = rb_entry(n, struct key, serial_node); + + if (id < key->serial) + n = n->rb_left; + else if (id > key->serial) + n = n->rb_right; + else + goto found; + } + +not_found: + key = ERR_PTR(-ENOKEY); + goto error; + +found: + /* A key is allowed to be looked up only if someone still owns a + * reference to it - otherwise it's awaiting the gc. + */ + if (!refcount_inc_not_zero(&key->usage)) + goto not_found; + +error: + spin_unlock(&key_serial_lock); + return key; +} + +/* + * Find and lock the specified key type against removal. + * + * We return with the sem read-locked if successful. If the type wasn't + * available -ENOKEY is returned instead. + */ +struct key_type *key_type_lookup(const char *type) +{ + struct key_type *ktype; + + down_read(&key_types_sem); + + /* look up the key type to see if it's one of the registered kernel + * types */ + list_for_each_entry(ktype, &key_types_list, link) { + if (strcmp(ktype->name, type) == 0) + goto found_kernel_type; + } + + up_read(&key_types_sem); + ktype = ERR_PTR(-ENOKEY); + +found_kernel_type: + return ktype; +} + +void key_set_timeout(struct key *key, unsigned timeout) +{ + time64_t expiry = 0; + + /* make the changes with the locks held to prevent races */ + down_write(&key->sem); + + if (timeout > 0) + expiry = ktime_get_real_seconds() + timeout; + + key->expiry = expiry; + key_schedule_gc(key->expiry + key_gc_delay); + + up_write(&key->sem); +} +EXPORT_SYMBOL_GPL(key_set_timeout); + +/* + * Unlock a key type locked by key_type_lookup(). + */ +void key_type_put(struct key_type *ktype) +{ + up_read(&key_types_sem); +} + +/* + * Attempt to update an existing key. + * + * The key is given to us with an incremented refcount that we need to discard + * if we get an error. + */ +static inline key_ref_t __key_update(key_ref_t key_ref, + struct key_preparsed_payload *prep) +{ + struct key *key = key_ref_to_ptr(key_ref); + int ret; + + /* need write permission on the key to update it */ + ret = key_permission(key_ref, KEY_NEED_WRITE); + if (ret < 0) + goto error; + + ret = -EEXIST; + if (!key->type->update) + goto error; + + down_write(&key->sem); + + ret = key->type->update(key, prep); + if (ret == 0) + /* Updating a negative key positively instantiates it */ + mark_key_instantiated(key, 0); + + up_write(&key->sem); + + if (ret < 0) + goto error; +out: + return key_ref; + +error: + key_put(key); + key_ref = ERR_PTR(ret); + goto out; +} + +/** + * key_create_or_update - Update or create and instantiate a key. + * @keyring_ref: A pointer to the destination keyring with possession flag. + * @type: The type of key. + * @description: The searchable description for the key. + * @payload: The data to use to instantiate or update the key. + * @plen: The length of @payload. + * @perm: The permissions mask for a new key. + * @flags: The quota flags for a new key. + * + * Search the destination keyring for a key of the same description and if one + * is found, update it, otherwise create and instantiate a new one and create a + * link to it from that keyring. + * + * If perm is KEY_PERM_UNDEF then an appropriate key permissions mask will be + * concocted. + * + * Returns a pointer to the new key if successful, -ENODEV if the key type + * wasn't available, -ENOTDIR if the keyring wasn't a keyring, -EACCES if the + * caller isn't permitted to modify the keyring or the LSM did not permit + * creation of the key. + * + * On success, the possession flag from the keyring ref will be tacked on to + * the key ref before it is returned. + */ +key_ref_t key_create_or_update(key_ref_t keyring_ref, + const char *type, + const char *description, + const void *payload, + size_t plen, + key_perm_t perm, + unsigned long flags) +{ + struct keyring_index_key index_key = { + .description = description, + }; + struct key_preparsed_payload prep; + struct assoc_array_edit *edit; + const struct cred *cred = current_cred(); + struct key *keyring, *key = NULL; + key_ref_t key_ref; + int ret; + struct key_restriction *restrict_link = NULL; + + /* look up the key type to see if it's one of the registered kernel + * types */ + index_key.type = key_type_lookup(type); + if (IS_ERR(index_key.type)) { + key_ref = ERR_PTR(-ENODEV); + goto error; + } + + key_ref = ERR_PTR(-EINVAL); + if (!index_key.type->instantiate || + (!index_key.description && !index_key.type->preparse)) + goto error_put_type; + + keyring = key_ref_to_ptr(keyring_ref); + + key_check(keyring); + + if (!(flags & KEY_ALLOC_BYPASS_RESTRICTION)) + restrict_link = keyring->restrict_link; + + key_ref = ERR_PTR(-ENOTDIR); + if (keyring->type != &key_type_keyring) + goto error_put_type; + + memset(&prep, 0, sizeof(prep)); + prep.data = payload; + prep.datalen = plen; + prep.quotalen = index_key.type->def_datalen; + prep.expiry = TIME64_MAX; + if (index_key.type->preparse) { + ret = index_key.type->preparse(&prep); + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error_free_prep; + } + if (!index_key.description) + index_key.description = prep.description; + key_ref = ERR_PTR(-EINVAL); + if (!index_key.description) + goto error_free_prep; + } + index_key.desc_len = strlen(index_key.description); + + ret = __key_link_begin(keyring, &index_key, &edit); + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error_free_prep; + } + + if (restrict_link && restrict_link->check) { + ret = restrict_link->check(keyring, index_key.type, + &prep.payload, restrict_link->key); + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error_link_end; + } + } + + /* if we're going to allocate a new key, we're going to have + * to modify the keyring */ + ret = key_permission(keyring_ref, KEY_NEED_WRITE); + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error_link_end; + } + + /* if it's possible to update this type of key, search for an existing + * key of the same type and description in the destination keyring and + * update that instead if possible + */ + if (index_key.type->update) { + key_ref = find_key_to_update(keyring_ref, &index_key); + if (key_ref) + goto found_matching_key; + } + + /* if the client doesn't provide, decide on the permissions we want */ + if (perm == KEY_PERM_UNDEF) { + perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR; + perm |= KEY_USR_VIEW; + + if (index_key.type->read) + perm |= KEY_POS_READ; + + if (index_key.type == &key_type_keyring || + index_key.type->update) + perm |= KEY_POS_WRITE; + } + + /* allocate a new key */ + key = key_alloc(index_key.type, index_key.description, + cred->fsuid, cred->fsgid, cred, perm, flags, NULL); + if (IS_ERR(key)) { + key_ref = ERR_CAST(key); + goto error_link_end; + } + + /* instantiate it and link it into the target keyring */ + ret = __key_instantiate_and_link(key, &prep, keyring, NULL, &edit); + if (ret < 0) { + key_put(key); + key_ref = ERR_PTR(ret); + goto error_link_end; + } + + key_ref = make_key_ref(key, is_key_possessed(keyring_ref)); + +error_link_end: + __key_link_end(keyring, &index_key, edit); +error_free_prep: + if (index_key.type->preparse) + index_key.type->free_preparse(&prep); +error_put_type: + key_type_put(index_key.type); +error: + return key_ref; + + found_matching_key: + /* we found a matching key, so we're going to try to update it + * - we can drop the locks first as we have the key pinned + */ + __key_link_end(keyring, &index_key, edit); + + key = key_ref_to_ptr(key_ref); + if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) { + ret = wait_for_key_construction(key, true); + if (ret < 0) { + key_ref_put(key_ref); + key_ref = ERR_PTR(ret); + goto error_free_prep; + } + } + + key_ref = __key_update(key_ref, &prep); + goto error_free_prep; +} +EXPORT_SYMBOL(key_create_or_update); + +/** + * key_update - Update a key's contents. + * @key_ref: The pointer (plus possession flag) to the key. + * @payload: The data to be used to update the key. + * @plen: The length of @payload. + * + * Attempt to update the contents of a key with the given payload data. The + * caller must be granted Write permission on the key. Negative keys can be + * instantiated by this method. + * + * Returns 0 on success, -EACCES if not permitted and -EOPNOTSUPP if the key + * type does not support updating. The key type may return other errors. + */ +int key_update(key_ref_t key_ref, const void *payload, size_t plen) +{ + struct key_preparsed_payload prep; + struct key *key = key_ref_to_ptr(key_ref); + int ret; + + key_check(key); + + /* the key must be writable */ + ret = key_permission(key_ref, KEY_NEED_WRITE); + if (ret < 0) + return ret; + + /* attempt to update it if supported */ + if (!key->type->update) + return -EOPNOTSUPP; + + memset(&prep, 0, sizeof(prep)); + prep.data = payload; + prep.datalen = plen; + prep.quotalen = key->type->def_datalen; + prep.expiry = TIME64_MAX; + if (key->type->preparse) { + ret = key->type->preparse(&prep); + if (ret < 0) + goto error; + } + + down_write(&key->sem); + + ret = key->type->update(key, &prep); + if (ret == 0) + /* Updating a negative key positively instantiates it */ + mark_key_instantiated(key, 0); + + up_write(&key->sem); + +error: + if (key->type->preparse) + key->type->free_preparse(&prep); + return ret; +} +EXPORT_SYMBOL(key_update); + +/** + * key_revoke - Revoke a key. + * @key: The key to be revoked. + * + * Mark a key as being revoked and ask the type to free up its resources. The + * revocation timeout is set and the key and all its links will be + * automatically garbage collected after key_gc_delay amount of time if they + * are not manually dealt with first. + */ +void key_revoke(struct key *key) +{ + time64_t time; + + key_check(key); + + /* make sure no one's trying to change or use the key when we mark it + * - we tell lockdep that we might nest because we might be revoking an + * authorisation key whilst holding the sem on a key we've just + * instantiated + */ + down_write_nested(&key->sem, 1); + if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags) && + key->type->revoke) + key->type->revoke(key); + + /* set the death time to no more than the expiry time */ + time = ktime_get_real_seconds(); + if (key->revoked_at == 0 || key->revoked_at > time) { + key->revoked_at = time; + key_schedule_gc(key->revoked_at + key_gc_delay); + } + + up_write(&key->sem); +} +EXPORT_SYMBOL(key_revoke); + +/** + * key_invalidate - Invalidate a key. + * @key: The key to be invalidated. + * + * Mark a key as being invalidated and have it cleaned up immediately. The key + * is ignored by all searches and other operations from this point. + */ +void key_invalidate(struct key *key) +{ + kenter("%d", key_serial(key)); + + key_check(key); + + if (!test_bit(KEY_FLAG_INVALIDATED, &key->flags)) { + down_write_nested(&key->sem, 1); + if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags)) + key_schedule_gc_links(); + up_write(&key->sem); + } +} +EXPORT_SYMBOL(key_invalidate); + +/** + * generic_key_instantiate - Simple instantiation of a key from preparsed data + * @key: The key to be instantiated + * @prep: The preparsed data to load. + * + * Instantiate a key from preparsed data. We assume we can just copy the data + * in directly and clear the old pointers. + * + * This can be pointed to directly by the key type instantiate op pointer. + */ +int generic_key_instantiate(struct key *key, struct key_preparsed_payload *prep) +{ + int ret; + + pr_devel("==>%s()\n", __func__); + + ret = key_payload_reserve(key, prep->quotalen); + if (ret == 0) { + rcu_assign_keypointer(key, prep->payload.data[0]); + key->payload.data[1] = prep->payload.data[1]; + key->payload.data[2] = prep->payload.data[2]; + key->payload.data[3] = prep->payload.data[3]; + prep->payload.data[0] = NULL; + prep->payload.data[1] = NULL; + prep->payload.data[2] = NULL; + prep->payload.data[3] = NULL; + } + pr_devel("<==%s() = %d\n", __func__, ret); + return ret; +} +EXPORT_SYMBOL(generic_key_instantiate); + +/** + * register_key_type - Register a type of key. + * @ktype: The new key type. + * + * Register a new key type. + * + * Returns 0 on success or -EEXIST if a type of this name already exists. + */ +int register_key_type(struct key_type *ktype) +{ + struct key_type *p; + int ret; + + memset(&ktype->lock_class, 0, sizeof(ktype->lock_class)); + + ret = -EEXIST; + down_write(&key_types_sem); + + /* disallow key types with the same name */ + list_for_each_entry(p, &key_types_list, link) { + if (strcmp(p->name, ktype->name) == 0) + goto out; + } + + /* store the type */ + list_add(&ktype->link, &key_types_list); + + pr_notice("Key type %s registered\n", ktype->name); + ret = 0; + +out: + up_write(&key_types_sem); + return ret; +} +EXPORT_SYMBOL(register_key_type); + +/** + * unregister_key_type - Unregister a type of key. + * @ktype: The key type. + * + * Unregister a key type and mark all the extant keys of this type as dead. + * Those keys of this type are then destroyed to get rid of their payloads and + * they and their links will be garbage collected as soon as possible. + */ +void unregister_key_type(struct key_type *ktype) +{ + down_write(&key_types_sem); + list_del_init(&ktype->link); + downgrade_write(&key_types_sem); + key_gc_keytype(ktype); + pr_notice("Key type %s unregistered\n", ktype->name); + up_read(&key_types_sem); +} +EXPORT_SYMBOL(unregister_key_type); + +/* + * Initialise the key management state. + */ +void __init key_init(void) +{ + /* allocate a slab in which we can store keys */ + key_jar = kmem_cache_create("key_jar", sizeof(struct key), + 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL); + + /* add the special key types */ + list_add_tail(&key_type_keyring.link, &key_types_list); + list_add_tail(&key_type_dead.link, &key_types_list); + list_add_tail(&key_type_user.link, &key_types_list); + list_add_tail(&key_type_logon.link, &key_types_list); + + /* record the root user tracking */ + rb_link_node(&root_key_user.node, + NULL, + &key_user_tree.rb_node); + + rb_insert_color(&root_key_user.node, + &key_user_tree); +} |