diff options
Diffstat (limited to 'security/keys')
-rw-r--r-- | security/keys/Makefile | 14 | ||||
-rw-r--r-- | security/keys/compat.c | 78 | ||||
-rw-r--r-- | security/keys/internal.h | 123 | ||||
-rw-r--r-- | security/keys/key.c | 1040 | ||||
-rw-r--r-- | security/keys/keyctl.c | 987 | ||||
-rw-r--r-- | security/keys/keyring.c | 895 | ||||
-rw-r--r-- | security/keys/proc.c | 251 | ||||
-rw-r--r-- | security/keys/process_keys.c | 665 | ||||
-rw-r--r-- | security/keys/request_key.c | 359 | ||||
-rw-r--r-- | security/keys/user_defined.c | 191 |
10 files changed, 4603 insertions, 0 deletions
diff --git a/security/keys/Makefile b/security/keys/Makefile new file mode 100644 index 00000000000..ddb495d6506 --- /dev/null +++ b/security/keys/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for key management +# + +obj-y := \ + key.o \ + keyring.o \ + keyctl.o \ + process_keys.o \ + user_defined.o \ + request_key.o + +obj-$(CONFIG_KEYS_COMPAT) += compat.o +obj-$(CONFIG_PROC_FS) += proc.o diff --git a/security/keys/compat.c b/security/keys/compat.c new file mode 100644 index 00000000000..aff8b22dcb5 --- /dev/null +++ b/security/keys/compat.c @@ -0,0 +1,78 @@ +/* compat.c: 32-bit compatibility syscall for 64-bit systems + * + * Copyright (C) 2004 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/sched.h> +#include <linux/syscalls.h> +#include <linux/keyctl.h> +#include <linux/compat.h> +#include "internal.h" + +/*****************************************************************************/ +/* + * the key control system call, 32-bit compatibility version for 64-bit archs + * - this should only be called if the 64-bit arch uses weird pointers in + * 32-bit mode or doesn't guarantee that the top 32-bits of the argument + * registers on taking a 32-bit syscall are zero + * - if you can, you should call sys_keyctl directly + */ +asmlinkage long compat_sys_keyctl(u32 option, + u32 arg2, u32 arg3, u32 arg4, u32 arg5) +{ + switch (option) { + case KEYCTL_GET_KEYRING_ID: + return keyctl_get_keyring_ID(arg2, arg3); + + case KEYCTL_JOIN_SESSION_KEYRING: + return keyctl_join_session_keyring(compat_ptr(arg2)); + + case KEYCTL_UPDATE: + return keyctl_update_key(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_REVOKE: + return keyctl_revoke_key(arg2); + + case KEYCTL_DESCRIBE: + return keyctl_describe_key(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_CLEAR: + return keyctl_keyring_clear(arg2); + + case KEYCTL_LINK: + return keyctl_keyring_link(arg2, arg3); + + case KEYCTL_UNLINK: + return keyctl_keyring_unlink(arg2, arg3); + + case KEYCTL_SEARCH: + return keyctl_keyring_search(arg2, compat_ptr(arg3), + compat_ptr(arg4), arg5); + + case KEYCTL_READ: + return keyctl_read_key(arg2, compat_ptr(arg3), arg4); + + case KEYCTL_CHOWN: + return keyctl_chown_key(arg2, arg3, arg4); + + case KEYCTL_SETPERM: + return keyctl_setperm_key(arg2, arg3); + + case KEYCTL_INSTANTIATE: + return keyctl_instantiate_key(arg2, compat_ptr(arg3), arg4, + arg5); + + case KEYCTL_NEGATE: + return keyctl_negate_key(arg2, arg3, arg4); + + default: + return -EOPNOTSUPP; + } + +} /* end compat_sys_keyctl() */ diff --git a/security/keys/internal.h b/security/keys/internal.h new file mode 100644 index 00000000000..67b2b93a748 --- /dev/null +++ b/security/keys/internal.h @@ -0,0 +1,123 @@ +/* internal.h: authentication token and access key management internal defs + * + * Copyright (C) 2003 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. + */ + +#ifndef _INTERNAL_H +#define _INTERNAL_H + +#include <linux/key.h> +#include <linux/key-ui.h> + +extern struct key_type key_type_dead; +extern struct key_type key_type_user; + +/*****************************************************************************/ +/* + * keep track of keys for a user + * - this needs to be separate to user_struct to avoid a refcount-loop + * (user_struct pins some keyrings which pin this struct) + * - this also keeps track of keys under request from userspace for this UID + */ +struct key_user { + struct rb_node node; + struct list_head consq; /* construction queue */ + spinlock_t lock; + atomic_t usage; /* for accessing qnkeys & qnbytes */ + atomic_t nkeys; /* number of keys */ + atomic_t nikeys; /* number of instantiated keys */ + uid_t uid; + int qnkeys; /* number of keys allocated to this user */ + int qnbytes; /* number of bytes allocated to this user */ +}; + +#define KEYQUOTA_MAX_KEYS 100 +#define KEYQUOTA_MAX_BYTES 10000 +#define KEYQUOTA_LINK_BYTES 4 /* a link in a keyring is worth 4 bytes */ + +extern struct rb_root key_user_tree; +extern spinlock_t key_user_lock; +extern struct key_user root_key_user; + +extern struct key_user *key_user_lookup(uid_t uid); +extern void key_user_put(struct key_user *user); + + + +extern struct rb_root key_serial_tree; +extern spinlock_t key_serial_lock; +extern struct semaphore key_alloc_sem; +extern struct rw_semaphore key_construction_sem; +extern wait_queue_head_t request_key_conswq; + + +extern void keyring_publish_name(struct key *keyring); + +extern int __key_link(struct key *keyring, struct key *key); + +extern struct key *__keyring_search_one(struct key *keyring, + const struct key_type *type, + const char *description, + key_perm_t perm); + +typedef int (*key_match_func_t)(const struct key *, const void *); + +extern struct key *keyring_search_aux(struct key *keyring, + struct key_type *type, + const void *description, + key_match_func_t match); + +extern struct key *search_process_keyrings_aux(struct key_type *type, + const void *description, + key_match_func_t match); + +extern struct key *find_keyring_by_name(const char *name, key_serial_t bound); + +extern int install_thread_keyring(struct task_struct *tsk); + +/* + * keyctl functions + */ +extern long keyctl_get_keyring_ID(key_serial_t, int); +extern long keyctl_join_session_keyring(const char __user *); +extern long keyctl_update_key(key_serial_t, const void __user *, size_t); +extern long keyctl_revoke_key(key_serial_t); +extern long keyctl_keyring_clear(key_serial_t); +extern long keyctl_keyring_link(key_serial_t, key_serial_t); +extern long keyctl_keyring_unlink(key_serial_t, key_serial_t); +extern long keyctl_describe_key(key_serial_t, char __user *, size_t); +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_setperm_key(key_serial_t, key_perm_t); +extern long keyctl_instantiate_key(key_serial_t, const void __user *, + size_t, key_serial_t); +extern long keyctl_negate_key(key_serial_t, unsigned, key_serial_t); + + +/* + * debugging key validation + */ +#ifdef KEY_DEBUGGING +extern void __key_check(const struct key *); + +static inline void key_check(const struct key *key) +{ + if (key && (IS_ERR(key) || key->magic != KEY_DEBUG_MAGIC)) + __key_check(key); +} + +#else + +#define key_check(key) do {} while(0) + +#endif + +#endif /* _INTERNAL_H */ diff --git a/security/keys/key.c b/security/keys/key.c new file mode 100644 index 00000000000..59402c84320 --- /dev/null +++ b/security/keys/key.c @@ -0,0 +1,1040 @@ +/* key.c: basic authentication token and access key management + * + * Copyright (C) 2004 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/sched.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/err.h> +#include "internal.h" + +static kmem_cache_t *key_jar; +static key_serial_t key_serial_next = 3; +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); + +static LIST_HEAD(key_types_list); +static DECLARE_RWSEM(key_types_sem); + +static void key_cleanup(void *data); +static DECLARE_WORK(key_cleanup_task, key_cleanup, NULL); + +/* we serialise key instantiation and link */ +DECLARE_RWSEM(key_construction_sem); + +/* any key who's type gets unegistered will be re-typed to this */ +struct key_type key_type_dead = { + .name = "dead", +}; + +#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(uid_t uid) +{ + struct key_user *candidate = NULL, *user; + struct rb_node *parent = NULL; + struct rb_node **p; + + try_again: + 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 < user->uid) + p = &(*p)->rb_left; + else if (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 */ + atomic_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); + INIT_LIST_HEAD(&candidate->consq); + + 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: + atomic_inc(&user->usage); + spin_unlock(&key_user_lock); + if (candidate) + kfree(candidate); + out: + return user; + +} /* end key_user_lookup() */ + +/*****************************************************************************/ +/* + * dispose of a user structure + */ +void key_user_put(struct key_user *user) +{ + if (atomic_dec_and_lock(&user->usage, &key_user_lock)) { + rb_erase(&user->node, &key_user_tree); + spin_unlock(&key_user_lock); + + kfree(user); + } + +} /* end key_user_put() */ + +/*****************************************************************************/ +/* + * insert a key with a fixed serial number + */ +static void __init __key_insert_serial(struct key *key) +{ + struct rb_node *parent, **p; + struct key *xkey; + + 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 + BUG(); + } + + /* 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); + +} /* end __key_insert_serial() */ + +/*****************************************************************************/ +/* + * assign a key the next unique serial number + * - we work through all the serial numbers between 2 and 2^31-1 in turn and + * then wrap + */ +static inline void key_alloc_serial(struct key *key) +{ + struct rb_node *parent, **p; + struct key *xkey; + + spin_lock(&key_serial_lock); + + /* propose a likely serial number and look for a hole for it in the + * serial number tree */ + key->serial = key_serial_next; + if (key->serial < 3) + key->serial = 3; + key_serial_next = key->serial + 1; + + 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; + } + goto insert_here; + + /* 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 = key_serial_next; + if (key->serial < 2) + key->serial = 2; + key_serial_next = key->serial + 1; + + if (!parent->rb_parent) + p = &key_serial_tree.rb_node; + else if (parent->rb_parent->rb_left == parent) + p = &parent->rb_parent->rb_left; + else + p = &parent->rb_parent->rb_right; + + parent = rb_next(parent); + if (!parent) + break; + + xkey = rb_entry(parent, struct key, serial_node); + if (key->serial < xkey->serial) + goto insert_here; + } + + /* we've found a suitable hole - arrange for this key to occupy it */ + insert_here: + rb_link_node(&key->serial_node, parent, p); + rb_insert_color(&key->serial_node, &key_serial_tree); + + spin_unlock(&key_serial_lock); + +} /* end key_alloc_serial() */ + +/*****************************************************************************/ +/* + * allocate a key of the specified type + * - update the user's quota to reflect the existence of the key + * - called from a key-type operation with key_types_sem read-locked by either + * key_create_or_update() or by key_duplicate(); this prevents unregistration + * of the key type + * - upon return the key is as yet uninstantiated; the caller needs to either + * instantiate the key or discard it before returning + */ +struct key *key_alloc(struct key_type *type, const char *desc, + uid_t uid, gid_t gid, key_perm_t perm, + int not_in_quota) +{ + struct key_user *user = NULL; + struct key *key; + size_t desclen, quotalen; + + key = ERR_PTR(-EINVAL); + if (!desc || !*desc) + goto error; + + desclen = strlen(desc) + 1; + quotalen = desclen + 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 (!not_in_quota) { + spin_lock(&user->lock); + if (user->qnkeys + 1 >= KEYQUOTA_MAX_KEYS && + user->qnbytes + quotalen >= KEYQUOTA_MAX_BYTES + ) + goto no_quota; + + user->qnkeys++; + user->qnbytes += quotalen; + spin_unlock(&user->lock); + } + + /* allocate and initialise the key and its description */ + key = kmem_cache_alloc(key_jar, SLAB_KERNEL); + if (!key) + goto no_memory_2; + + if (desc) { + key->description = kmalloc(desclen, GFP_KERNEL); + if (!key->description) + goto no_memory_3; + + memcpy(key->description, desc, desclen); + } + + atomic_set(&key->usage, 1); + rwlock_init(&key->lock); + init_rwsem(&key->sem); + key->type = type; + key->user = user; + key->quotalen = quotalen; + key->datalen = type->def_datalen; + key->uid = uid; + key->gid = gid; + key->perm = perm; + key->flags = 0; + key->expiry = 0; + key->payload.data = NULL; + + if (!not_in_quota) + key->flags |= KEY_FLAG_IN_QUOTA; + + memset(&key->type_data, 0, sizeof(key->type_data)); + +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC; +#endif + + /* publish the key by giving it a serial number */ + atomic_inc(&user->nkeys); + key_alloc_serial(key); + + error: + return key; + + no_memory_3: + kmem_cache_free(key_jar, key); + no_memory_2: + if (!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; + +} /* end key_alloc() */ + +EXPORT_SYMBOL(key_alloc); + +/*****************************************************************************/ +/* + * reserve an amount of quota for the key's payload + */ +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 && key->flags & KEY_FLAG_IN_QUOTA) { + spin_lock(&key->user->lock); + + if (delta > 0 && + key->user->qnbytes + delta > KEYQUOTA_MAX_BYTES + ) { + 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; + +} /* end key_payload_reserve() */ + +EXPORT_SYMBOL(key_payload_reserve); + +/*****************************************************************************/ +/* + * instantiate a key and link it into the target keyring atomically + * - called with the target keyring's semaphore writelocked + */ +static int __key_instantiate_and_link(struct key *key, + const void *data, + size_t datalen, + struct key *keyring) +{ + int ret, awaken; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + down_write(&key_construction_sem); + + /* can't instantiate twice */ + if (!(key->flags & KEY_FLAG_INSTANTIATED)) { + /* instantiate the key */ + ret = key->type->instantiate(key, data, datalen); + + if (ret == 0) { + /* mark the key as being instantiated */ + write_lock(&key->lock); + + atomic_inc(&key->user->nikeys); + key->flags |= KEY_FLAG_INSTANTIATED; + + if (key->flags & KEY_FLAG_USER_CONSTRUCT) { + key->flags &= ~KEY_FLAG_USER_CONSTRUCT; + awaken = 1; + } + + write_unlock(&key->lock); + + /* and link it into the destination keyring */ + if (keyring) + ret = __key_link(keyring, key); + } + } + + up_write(&key_construction_sem); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_all(&request_key_conswq); + + return ret; + +} /* end __key_instantiate_and_link() */ + +/*****************************************************************************/ +/* + * instantiate a key and link it into the target keyring atomically + */ +int key_instantiate_and_link(struct key *key, + const void *data, + size_t datalen, + struct key *keyring) +{ + int ret; + + if (keyring) + down_write(&keyring->sem); + + ret = __key_instantiate_and_link(key, data, datalen, keyring); + + if (keyring) + up_write(&keyring->sem); + + return ret; +} /* end key_instantiate_and_link() */ + +EXPORT_SYMBOL(key_instantiate_and_link); + +/*****************************************************************************/ +/* + * negatively instantiate a key and link it into the target keyring atomically + */ +int key_negate_and_link(struct key *key, + unsigned timeout, + struct key *keyring) +{ + struct timespec now; + int ret, awaken; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + if (keyring) + down_write(&keyring->sem); + + down_write(&key_construction_sem); + + /* can't instantiate twice */ + if (!(key->flags & KEY_FLAG_INSTANTIATED)) { + /* mark the key as being negatively instantiated */ + write_lock(&key->lock); + + atomic_inc(&key->user->nikeys); + key->flags |= KEY_FLAG_INSTANTIATED | KEY_FLAG_NEGATIVE; + now = current_kernel_time(); + key->expiry = now.tv_sec + timeout; + + if (key->flags & KEY_FLAG_USER_CONSTRUCT) { + key->flags &= ~KEY_FLAG_USER_CONSTRUCT; + awaken = 1; + } + + write_unlock(&key->lock); + ret = 0; + + /* and link it into the destination keyring */ + if (keyring) + ret = __key_link(keyring, key); + } + + up_write(&key_construction_sem); + + if (keyring) + up_write(&keyring->sem); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_all(&request_key_conswq); + + return ret; + +} /* end key_negate_and_link() */ + +EXPORT_SYMBOL(key_negate_and_link); + +/*****************************************************************************/ +/* + * do cleaning up in process context so that we don't have to disable + * interrupts all over the place + */ +static void key_cleanup(void *data) +{ + struct rb_node *_n; + struct key *key; + + go_again: + /* look for a dead key in the tree */ + spin_lock(&key_serial_lock); + + for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { + key = rb_entry(_n, struct key, serial_node); + + if (atomic_read(&key->usage) == 0) + goto found_dead_key; + } + + spin_unlock(&key_serial_lock); + return; + + found_dead_key: + /* we found a dead key - once we've removed it from the tree, we can + * drop the lock */ + rb_erase(&key->serial_node, &key_serial_tree); + spin_unlock(&key_serial_lock); + + /* deal with the user's key tracking and quota */ + if (key->flags & KEY_FLAG_IN_QUOTA) { + spin_lock(&key->user->lock); + key->user->qnkeys--; + key->user->qnbytes -= key->quotalen; + spin_unlock(&key->user->lock); + } + + atomic_dec(&key->user->nkeys); + if (key->flags & KEY_FLAG_INSTANTIATED) + atomic_dec(&key->user->nikeys); + + key_user_put(key->user); + + /* now throw away the key memory */ + if (key->type->destroy) + key->type->destroy(key); + + kfree(key->description); + +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC_X; +#endif + kmem_cache_free(key_jar, key); + + /* there may, of course, be more than one key to destroy */ + goto go_again; + +} /* end key_cleanup() */ + +/*****************************************************************************/ +/* + * dispose of a reference to a key + * - when all the references are gone, we schedule the cleanup task to come and + * pull it out of the tree in definite process context + */ +void key_put(struct key *key) +{ + if (key) { + key_check(key); + + if (atomic_dec_and_test(&key->usage)) + schedule_work(&key_cleanup_task); + } + +} /* end key_put() */ + +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: + /* pretent doesn't exist if it's dead */ + if (atomic_read(&key->usage) == 0 || + (key->flags & KEY_FLAG_DEAD) || + key->type == &key_type_dead) + goto not_found; + + /* this races with key_put(), but that doesn't matter since key_put() + * doesn't actually change the key + */ + atomic_inc(&key->usage); + + error: + spin_unlock(&key_serial_lock); + return key; + +} /* end key_lookup() */ + +/*****************************************************************************/ +/* + * find and lock the specified key type against removal + * - we return with the sem readlocked + */ +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; + +} /* end key_type_lookup() */ + +/*****************************************************************************/ +/* + * unlock a key type + */ +void key_type_put(struct key_type *ktype) +{ + up_read(&key_types_sem); + +} /* end key_type_put() */ + +/*****************************************************************************/ +/* + * attempt to update an existing key + * - the key has an incremented refcount + * - we need to put the key if we get an error + */ +static inline struct key *__key_update(struct key *key, const void *payload, + size_t plen) +{ + int ret; + + /* need write permission on the key to update it */ + ret = -EACCES; + if (!key_permission(key, KEY_WRITE)) + goto error; + + ret = -EEXIST; + if (!key->type->update) + goto error; + + down_write(&key->sem); + + ret = key->type->update(key, payload, plen); + + if (ret == 0) { + /* updating a negative key instantiates it */ + write_lock(&key->lock); + key->flags &= ~KEY_FLAG_NEGATIVE; + write_unlock(&key->lock); + } + + up_write(&key->sem); + + if (ret < 0) + goto error; + out: + return key; + + error: + key_put(key); + key = ERR_PTR(ret); + goto out; + +} /* end __key_update() */ + +/*****************************************************************************/ +/* + * search the specified keyring for a key of the same description; if one is + * found, update it, otherwise add a new one + */ +struct key *key_create_or_update(struct key *keyring, + const char *type, + const char *description, + const void *payload, + size_t plen, + int not_in_quota) +{ + struct key_type *ktype; + struct key *key = NULL; + key_perm_t perm; + int ret; + + key_check(keyring); + + /* look up the key type to see if it's one of the registered kernel + * types */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + key = ERR_PTR(-ENODEV); + goto error; + } + + ret = -EINVAL; + if (!ktype->match || !ktype->instantiate) + goto error_2; + + /* search for an existing key of the same type and description in the + * destination keyring + */ + down_write(&keyring->sem); + + key = __keyring_search_one(keyring, ktype, description, 0); + if (!IS_ERR(key)) + goto found_matching_key; + + /* if we're going to allocate a new key, we're going to have to modify + * the keyring */ + ret = -EACCES; + if (!key_permission(keyring, KEY_WRITE)) + goto error_3; + + /* decide on the permissions we want */ + perm = KEY_USR_VIEW | KEY_USR_SEARCH | KEY_USR_LINK; + + if (ktype->read) + perm |= KEY_USR_READ; + + if (ktype == &key_type_keyring || ktype->update) + perm |= KEY_USR_WRITE; + + /* allocate a new key */ + key = key_alloc(ktype, description, current->fsuid, current->fsgid, + perm, not_in_quota); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error_3; + } + + /* instantiate it and link it into the target keyring */ + ret = __key_instantiate_and_link(key, payload, plen, keyring); + if (ret < 0) { + key_put(key); + key = ERR_PTR(ret); + } + + error_3: + up_write(&keyring->sem); + error_2: + key_type_put(ktype); + error: + return key; + + 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 + */ + up_write(&keyring->sem); + key_type_put(ktype); + + key = __key_update(key, payload, plen); + goto error; + +} /* end key_create_or_update() */ + +EXPORT_SYMBOL(key_create_or_update); + +/*****************************************************************************/ +/* + * update a key + */ +int key_update(struct key *key, const void *payload, size_t plen) +{ + int ret; + + key_check(key); + + /* the key must be writable */ + ret = -EACCES; + if (!key_permission(key, KEY_WRITE)) + goto error; + + /* attempt to update it if supported */ + ret = -EOPNOTSUPP; + if (key->type->update) { + down_write(&key->sem); + ret = key->type->update(key, payload, plen); + + if (ret == 0) { + /* updating a negative key instantiates it */ + write_lock(&key->lock); + key->flags &= ~KEY_FLAG_NEGATIVE; + write_unlock(&key->lock); + } + + up_write(&key->sem); + } + + error: + return ret; + +} /* end key_update() */ + +EXPORT_SYMBOL(key_update); + +/*****************************************************************************/ +/* + * duplicate a key, potentially with a revised description + * - must be supported by the keytype (keyrings for instance can be duplicated) + */ +struct key *key_duplicate(struct key *source, const char *desc) +{ + struct key *key; + int ret; + + key_check(source); + + if (!desc) + desc = source->description; + + down_read(&key_types_sem); + + ret = -EINVAL; + if (!source->type->duplicate) + goto error; + + /* allocate and instantiate a key */ + key = key_alloc(source->type, desc, current->fsuid, current->fsgid, + source->perm, 0); + if (IS_ERR(key)) + goto error_k; + + down_read(&source->sem); + ret = key->type->duplicate(key, source); + up_read(&source->sem); + if (ret < 0) + goto error2; + + atomic_inc(&key->user->nikeys); + + write_lock(&key->lock); + key->flags |= KEY_FLAG_INSTANTIATED; + write_unlock(&key->lock); + + error_k: + up_read(&key_types_sem); + out: + return key; + + error2: + key_put(key); + error: + up_read(&key_types_sem); + key = ERR_PTR(ret); + goto out; + +} /* end key_duplicate() */ + +/*****************************************************************************/ +/* + * revoke a key + */ +void key_revoke(struct key *key) +{ + key_check(key); + + /* make sure no one's trying to change or use the key when we mark + * it */ + down_write(&key->sem); + write_lock(&key->lock); + key->flags |= KEY_FLAG_REVOKED; + write_unlock(&key->lock); + up_write(&key->sem); + +} /* end key_revoke() */ + +EXPORT_SYMBOL(key_revoke); + +/*****************************************************************************/ +/* + * register a type of key + */ +int register_key_type(struct key_type *ktype) +{ + struct key_type *p; + int ret; + + 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); + ret = 0; + + out: + up_write(&key_types_sem); + return ret; + +} /* end register_key_type() */ + +EXPORT_SYMBOL(register_key_type); + +/*****************************************************************************/ +/* + * unregister a type of key + */ +void unregister_key_type(struct key_type *ktype) +{ + struct rb_node *_n; + struct key *key; + + down_write(&key_types_sem); + + /* withdraw the key type */ + list_del_init(&ktype->link); + + /* need to withdraw all keys of this type */ + spin_lock(&key_serial_lock); + + for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { + key = rb_entry(_n, struct key, serial_node); + + if (key->type != ktype) + continue; + + write_lock(&key->lock); + key->type = &key_type_dead; + write_unlock(&key->lock); + + /* there shouldn't be anyone looking at the description or + * payload now */ + if (ktype->destroy) + ktype->destroy(key); + memset(&key->payload, 0xbd, sizeof(key->payload)); + } + + spin_unlock(&key_serial_lock); + up_write(&key_types_sem); + +} /* end unregister_key_type() */ + +EXPORT_SYMBOL(unregister_key_type); + +/*****************************************************************************/ +/* + * initialise the key management stuff + */ +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, 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); + + /* 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); + + /* record root's user standard keyrings */ + key_check(&root_user_keyring); + key_check(&root_session_keyring); + + __key_insert_serial(&root_user_keyring); + __key_insert_serial(&root_session_keyring); + + keyring_publish_name(&root_user_keyring); + keyring_publish_name(&root_session_keyring); + + /* link the two root keyrings together */ + key_link(&root_session_keyring, &root_user_keyring); +} /* end key_init() */ diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c new file mode 100644 index 00000000000..dc0011b3fac --- /dev/null +++ b/security/keys/keyctl.c @@ -0,0 +1,987 @@ +/* keyctl.c: userspace keyctl operations + * + * Copyright (C) 2004 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/sched.h> +#include <linux/slab.h> +#include <linux/syscalls.h> +#include <linux/keyctl.h> +#include <linux/fs.h> +#include <linux/err.h> +#include <asm/uaccess.h> +#include "internal.h" + +/*****************************************************************************/ +/* + * extract the description of a new key from userspace and either add it as a + * new key to the specified keyring or update a matching key in that keyring + * - the keyring must be writable + * - returns the new key's serial number + * - implements add_key() + */ +asmlinkage long sys_add_key(const char __user *_type, + const char __user *_description, + const void __user *_payload, + size_t plen, + key_serial_t ringid) +{ + struct key *keyring, *key; + char type[32], *description; + void *payload; + long dlen, ret; + + ret = -EINVAL; + if (plen > 32767) + goto error; + + /* draw all the data into kernel space */ + ret = strncpy_from_user(type, _type, sizeof(type) - 1); + if (ret < 0) + goto error; + type[31] = '\0'; + + ret = -EFAULT; + dlen = strnlen_user(_description, PAGE_SIZE - 1); + if (dlen <= 0) + goto error; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + description = kmalloc(dlen + 1, GFP_KERNEL); + if (!description) + goto error; + + ret = -EFAULT; + if (copy_from_user(description, _description, dlen + 1) != 0) + goto error2; + + /* pull the payload in if one was supplied */ + payload = NULL; + + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error2; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error3; + } + + /* find the target keyring (which must be writable) */ + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error3; + } + + /* create or update the requested key and add it to the target + * keyring */ + key = key_create_or_update(keyring, type, description, + payload, plen, 0); + if (!IS_ERR(key)) { + ret = key->serial; + key_put(key); + } + else { + ret = PTR_ERR(key); + } + + key_put(keyring); + error3: + kfree(payload); + error2: + kfree(description); + error: + return ret; + +} /* end sys_add_key() */ + +/*****************************************************************************/ +/* + * search the process keyrings for a matching key + * - nested keyrings may also be searched if they have Search permission + * - if a key is found, it will be attached to the destination keyring if + * there's one specified + * - /sbin/request-key will be invoked if _callout_info is non-NULL + * - the _callout_info string will be passed to /sbin/request-key + * - if the _callout_info string is empty, it will be rendered as "-" + * - implements request_key() + */ +asmlinkage long sys_request_key(const char __user *_type, + const char __user *_description, + const char __user *_callout_info, + key_serial_t destringid) +{ + struct key_type *ktype; + struct key *key, *dest; + char type[32], *description, *callout_info; + long dlen, ret; + + /* pull the type into kernel space */ + ret = strncpy_from_user(type, _type, sizeof(type) - 1); + if (ret < 0) + goto error; + type[31] = '\0'; + + /* pull the description into kernel space */ + ret = -EFAULT; + dlen = strnlen_user(_description, PAGE_SIZE - 1); + if (dlen <= 0) + goto error; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + description = kmalloc(dlen + 1, GFP_KERNEL); + if (!description) + goto error; + + ret = -EFAULT; + if (copy_from_user(description, _description, dlen + 1) != 0) + goto error2; + + /* pull the callout info into kernel space */ + callout_info = NULL; + if (_callout_info) { + ret = -EFAULT; + dlen = strnlen_user(_callout_info, PAGE_SIZE - 1); + if (dlen <= 0) + goto error2; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error2; + + ret = -ENOMEM; + callout_info = kmalloc(dlen + 1, GFP_KERNEL); + if (!callout_info) + goto error2; + + ret = -EFAULT; + if (copy_from_user(callout_info, _callout_info, dlen + 1) != 0) + goto error3; + } + + /* get the destination keyring if specified */ + dest = NULL; + if (destringid) { + dest = lookup_user_key(destringid, 1, 0, KEY_WRITE); + if (IS_ERR(dest)) { + ret = PTR_ERR(dest); + goto error3; + } + } + + /* find the key type */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + ret = PTR_ERR(ktype); + goto error4; + } + + /* do the search */ + key = request_key(ktype, description, callout_info); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error5; + } + + /* link the resulting key to the destination keyring */ + if (dest) { + ret = key_link(dest, key); + if (ret < 0) + goto error6; + } + + ret = key->serial; + + error6: + key_put(key); + error5: + key_type_put(ktype); + error4: + key_put(dest); + error3: + kfree(callout_info); + error2: + kfree(description); + error: + return ret; + +} /* end sys_request_key() */ + +/*****************************************************************************/ +/* + * get the ID of the specified process keyring + * - the keyring must have search permission to be found + * - implements keyctl(KEYCTL_GET_KEYRING_ID) + */ +long keyctl_get_keyring_ID(key_serial_t id, int create) +{ + struct key *key; + long ret; + + key = lookup_user_key(id, create, 0, KEY_SEARCH); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + ret = key->serial; + key_put(key); + error: + return ret; + +} /* end keyctl_get_keyring_ID() */ + +/*****************************************************************************/ +/* + * join the session keyring + * - implements keyctl(KEYCTL_JOIN_SESSION_KEYRING) + */ +long keyctl_join_session_keyring(const char __user *_name) +{ + char *name; + long nlen, ret; + + /* fetch the name from userspace */ + name = NULL; + if (_name) { + ret = -EFAULT; + nlen = strnlen_user(_name, PAGE_SIZE - 1); + if (nlen <= 0) + goto error; + + ret = -EINVAL; + if (nlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + name = kmalloc(nlen + 1, GFP_KERNEL); + if (!name) + goto error; + + ret = -EFAULT; + if (copy_from_user(name, _name, nlen + 1) != 0) + goto error2; + } + + /* join the session */ + ret = join_session_keyring(name); + + error2: + kfree(name); + error: + return ret; + +} /* end keyctl_join_session_keyring() */ + +/*****************************************************************************/ +/* + * update a key's data payload + * - the key must be writable + * - implements keyctl(KEYCTL_UPDATE) + */ +long keyctl_update_key(key_serial_t id, + const void __user *_payload, + size_t plen) +{ + struct key *key; + void *payload; + long ret; + + ret = -EINVAL; + if (plen > PAGE_SIZE) + goto error; + + /* pull the payload in if one was supplied */ + payload = NULL; + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error2; + } + + /* find the target key (which must be writable) */ + key = lookup_user_key(id, 0, 0, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + /* update the key */ + ret = key_update(key, payload, plen); + + key_put(key); + error2: + kfree(payload); + error: + return ret; + +} /* end keyctl_update_key() */ + +/*****************************************************************************/ +/* + * revoke a key + * - the key must be writable + * - implements keyctl(KEYCTL_REVOKE) + */ +long keyctl_revoke_key(key_serial_t id) +{ + struct key *key; + long ret; + + key = lookup_user_key(id, 0, 0, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + key_revoke(key); + ret = 0; + + key_put(key); + error: + return 0; + +} /* end keyctl_revoke_key() */ + +/*****************************************************************************/ +/* + * clear the specified process keyring + * - the keyring must be writable + * - implements keyctl(KEYCTL_CLEAR) + */ +long keyctl_keyring_clear(key_serial_t ringid) +{ + struct key *keyring; + long ret; + + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + ret = keyring_clear(keyring); + + key_put(keyring); + error: + return ret; + +} /* end keyctl_keyring_clear() */ + +/*****************************************************************************/ +/* + * link a key into a keyring + * - the keyring must be writable + * - the key must be linkable + * - implements keyctl(KEYCTL_LINK) + */ +long keyctl_keyring_link(key_serial_t id, key_serial_t ringid) +{ + struct key *keyring, *key; + long ret; + + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + key = lookup_user_key(id, 1, 0, KEY_LINK); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + ret = key_link(keyring, key); + + key_put(key); + error2: + key_put(keyring); + error: + return ret; + +} /* end keyctl_keyring_link() */ + +/*****************************************************************************/ +/* + * unlink the first attachment of a key from a keyring + * - the keyring must be writable + * - we don't need any permissions on the key + * - implements keyctl(KEYCTL_UNLINK) + */ +long keyctl_keyring_unlink(key_serial_t id, key_serial_t ringid) +{ + struct key *keyring, *key; + long ret; + + keyring = lookup_user_key(ringid, 0, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + key = lookup_user_key(id, 0, 0, 0); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + ret = key_unlink(keyring, key); + + key_put(key); + error2: + key_put(keyring); + error: + return ret; + +} /* end keyctl_keyring_unlink() */ + +/*****************************************************************************/ +/* + * describe a user key + * - the key must have view permission + * - if there's a buffer, we place up to buflen bytes of data into it + * - unless there's an error, we return the amount of description available, + * irrespective of how much we may have copied + * - the description is formatted thus: + * type;uid;gid;perm;description<NUL> + * - implements keyctl(KEYCTL_DESCRIBE) + */ +long keyctl_describe_key(key_serial_t keyid, + char __user *buffer, + size_t buflen) +{ + struct key *key; + char *tmpbuf; + long ret; + + key = lookup_user_key(keyid, 0, 1, KEY_VIEW); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* calculate how much description we're going to return */ + ret = -ENOMEM; + tmpbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!tmpbuf) + goto error2; + + ret = snprintf(tmpbuf, PAGE_SIZE - 1, + "%s;%d;%d;%06x;%s", + key->type->name, + key->uid, + key->gid, + key->perm, + key->description ? key->description :"" + ); + + /* include a NUL char at the end of the data */ + if (ret > PAGE_SIZE - 1) + ret = PAGE_SIZE - 1; + tmpbuf[ret] = 0; + ret++; + + /* consider returning the data */ + if (buffer && buflen > 0) { + if (buflen > ret) + buflen = ret; + + if (copy_to_user(buffer, tmpbuf, buflen) != 0) + ret = -EFAULT; + } + + kfree(tmpbuf); + error2: + key_put(key); + error: + return ret; + +} /* end keyctl_describe_key() */ + +/*****************************************************************************/ +/* + * search the specified keyring for a matching key + * - the start keyring must be searchable + * - nested keyrings may also be searched if they are searchable + * - only keys with search permission may be found + * - if a key is found, it will be attached to the destination keyring if + * there's one specified + * - implements keyctl(KEYCTL_SEARCH) + */ +long keyctl_keyring_search(key_serial_t ringid, + const char __user *_type, + const char __user *_description, + key_serial_t destringid) +{ + struct key_type *ktype; + struct key *keyring, *key, *dest; + char type[32], *description; + long dlen, ret; + + /* pull the type and description into kernel space */ + ret = strncpy_from_user(type, _type, sizeof(type) - 1); + if (ret < 0) + goto error; + type[31] = '\0'; + + ret = -EFAULT; + dlen = strnlen_user(_description, PAGE_SIZE - 1); + if (dlen <= 0) + goto error; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + description = kmalloc(dlen + 1, GFP_KERNEL); + if (!description) + goto error; + + ret = -EFAULT; + if (copy_from_user(description, _description, dlen + 1) != 0) + goto error2; + + /* get the keyring at which to begin the search */ + keyring = lookup_user_key(ringid, 0, 0, KEY_SEARCH); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + + /* get the destination keyring if specified */ + dest = NULL; + if (destringid) { + dest = lookup_user_key(destringid, 1, 0, KEY_WRITE); + if (IS_ERR(dest)) { + ret = PTR_ERR(dest); + goto error3; + } + } + + /* find the key type */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + ret = PTR_ERR(ktype); + goto error4; + } + + /* do the search */ + key = keyring_search(keyring, ktype, description); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + + /* treat lack or presence of a negative key the same */ + if (ret == -EAGAIN) + ret = -ENOKEY; + goto error5; + } + + /* link the resulting key to the destination keyring if we can */ + if (dest) { + ret = -EACCES; + if (!key_permission(key, KEY_LINK)) + goto error6; + + ret = key_link(dest, key); + if (ret < 0) + goto error6; + } + + ret = key->serial; + + error6: + key_put(key); + error5: + key_type_put(ktype); + error4: + key_put(dest); + error3: + key_put(keyring); + error2: + kfree(description); + error: + return ret; + +} /* end keyctl_keyring_search() */ + +/*****************************************************************************/ +/* + * see if the key we're looking at is the target key + */ +static int keyctl_read_key_same(const struct key *key, const void *target) +{ + return key == target; + +} /* end keyctl_read_key_same() */ + +/*****************************************************************************/ +/* + * read a user key's payload + * - the keyring must be readable or the key must be searchable from the + * process's keyrings + * - if there's a buffer, we place up to buflen bytes of data into it + * - unless there's an error, we return the amount of data in the key, + * irrespective of how much we may have copied + * - implements keyctl(KEYCTL_READ) + */ +long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen) +{ + struct key *key, *skey; + long ret; + + /* find the key first */ + key = lookup_user_key(keyid, 0, 0, 0); + if (!IS_ERR(key)) { + /* see if we can read it directly */ + if (key_permission(key, KEY_READ)) + goto can_read_key; + + /* can't; see if it's searchable from this process's + * keyrings */ + ret = -ENOKEY; + if (key_permission(key, KEY_SEARCH)) { + /* okay - we do have search permission on the key + * itself, but do we have the key? */ + skey = search_process_keyrings_aux(key->type, key, + keyctl_read_key_same); + if (!IS_ERR(skey)) + goto can_read_key2; + } + + goto error2; + } + + ret = -ENOKEY; + goto error; + + /* the key is probably readable - now try to read it */ + can_read_key2: + key_put(skey); + can_read_key: + ret = key_validate(key); + if (ret == 0) { + ret = -EOPNOTSUPP; + if (key->type->read) { + /* read the data with the semaphore held (since we + * might sleep) */ + down_read(&key->sem); + ret = key->type->read(key, buffer, buflen); + up_read(&key->sem); + } + } + + error2: + key_put(key); + error: + return ret; + +} /* end keyctl_read_key() */ + +/*****************************************************************************/ +/* + * change the ownership of a key + * - the keyring owned by the changer + * - if the uid or gid is -1, then that parameter is not changed + * - implements keyctl(KEYCTL_CHOWN) + */ +long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid) +{ + struct key *key; + long ret; + + ret = 0; + if (uid == (uid_t) -1 && gid == (gid_t) -1) + goto error; + + key = lookup_user_key(id, 1, 1, 0); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* make the changes with the locks held to prevent chown/chown races */ + ret = -EACCES; + down_write(&key->sem); + write_lock(&key->lock); + + if (!capable(CAP_SYS_ADMIN)) { + /* only the sysadmin can chown a key to some other UID */ + if (uid != (uid_t) -1 && key->uid != uid) + goto no_access; + + /* only the sysadmin can set the key's GID to a group other + * than one of those that the current process subscribes to */ + if (gid != (gid_t) -1 && gid != key->gid && !in_group_p(gid)) + goto no_access; + } + + /* change the UID (have to update the quotas) */ + if (uid != (uid_t) -1 && uid != key->uid) { + /* don't support UID changing yet */ + ret = -EOPNOTSUPP; + goto no_access; + } + + /* change the GID */ + if (gid != (gid_t) -1) + key->gid = gid; + + ret = 0; + + no_access: + write_unlock(&key->lock); + up_write(&key->sem); + key_put(key); + error: + return ret; + +} /* end keyctl_chown_key() */ + +/*****************************************************************************/ +/* + * change the permission mask on a key + * - the keyring owned by the changer + * - implements keyctl(KEYCTL_SETPERM) + */ +long keyctl_setperm_key(key_serial_t id, key_perm_t perm) +{ + struct key *key; + long ret; + + ret = -EINVAL; + if (perm & ~(KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL)) + goto error; + + key = lookup_user_key(id, 1, 1, 0); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* make the changes with the locks held to prevent chown/chmod + * races */ + ret = -EACCES; + down_write(&key->sem); + write_lock(&key->lock); + + /* if we're not the sysadmin, we can only chmod a key that we + * own */ + if (!capable(CAP_SYS_ADMIN) && key->uid != current->fsuid) + goto no_access; + + /* changing the permissions mask */ + key->perm = perm; + ret = 0; + + no_access: + write_unlock(&key->lock); + up_write(&key->sem); + key_put(key); + error: + return ret; + +} /* end keyctl_setperm_key() */ + +/*****************************************************************************/ +/* + * instantiate the key with the specified payload, and, if one is given, link + * the key into the keyring + */ +long keyctl_instantiate_key(key_serial_t id, + const void __user *_payload, + size_t plen, + key_serial_t ringid) +{ + struct key *key, *keyring; + void *payload; + long ret; + + ret = -EINVAL; + if (plen > 32767) + goto error; + + /* pull the payload in if one was supplied */ + payload = NULL; + + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error2; + } + + /* find the target key (which must be writable) */ + key = lookup_user_key(id, 0, 1, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + /* find the destination keyring if present (which must also be + * writable) */ + keyring = NULL; + if (ringid) { + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error3; + } + } + + /* instantiate the key and link it into a keyring */ + ret = key_instantiate_and_link(key, payload, plen, keyring); + + key_put(keyring); + error3: + key_put(key); + error2: + kfree(payload); + error: + return ret; + +} /* end keyctl_instantiate_key() */ + +/*****************************************************************************/ +/* + * negatively instantiate the key with the given timeout (in seconds), and, if + * one is given, link the key into the keyring + */ +long keyctl_negate_key(key_serial_t id, unsigned timeout, key_serial_t ringid) +{ + struct key *key, *keyring; + long ret; + + /* find the target key (which must be writable) */ + key = lookup_user_key(id, 0, 1, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* find the destination keyring if present (which must also be + * writable) */ + keyring = NULL; + if (ringid) { + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + } + + /* instantiate the key and link it into a keyring */ + ret = key_negate_and_link(key, timeout, keyring); + + key_put(keyring); + error2: + key_put(key); + error: + return ret; + +} /* end keyctl_negate_key() */ + +/*****************************************************************************/ +/* + * the key control system call + */ +asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + switch (option) { + case KEYCTL_GET_KEYRING_ID: + return keyctl_get_keyring_ID((key_serial_t) arg2, + (int) arg3); + + case KEYCTL_JOIN_SESSION_KEYRING: + return keyctl_join_session_keyring((const char __user *) arg2); + + case KEYCTL_UPDATE: + return keyctl_update_key((key_serial_t) arg2, + (const void __user *) arg3, + (size_t) arg4); + + case KEYCTL_REVOKE: + return keyctl_revoke_key((key_serial_t) arg2); + + case KEYCTL_DESCRIBE: + return keyctl_describe_key((key_serial_t) arg2, + (char __user *) arg3, + (unsigned) arg4); + + case KEYCTL_CLEAR: + return keyctl_keyring_clear((key_serial_t) arg2); + + case KEYCTL_LINK: + return keyctl_keyring_link((key_serial_t) arg2, + (key_serial_t) arg3); + + case KEYCTL_UNLINK: + return keyctl_keyring_unlink((key_serial_t) arg2, + (key_serial_t) arg3); + + case KEYCTL_SEARCH: + return keyctl_keyring_search((key_serial_t) arg2, + (const char __user *) arg3, + (const char __user *) arg4, + (key_serial_t) arg5); + + case KEYCTL_READ: + return keyctl_read_key((key_serial_t) arg2, + (char __user *) arg3, + (size_t) arg4); + + case KEYCTL_CHOWN: + return keyctl_chown_key((key_serial_t) arg2, + (uid_t) arg3, + (gid_t) arg4); + + case KEYCTL_SETPERM: + return keyctl_setperm_key((key_serial_t) arg2, + (key_perm_t) arg3); + + case KEYCTL_INSTANTIATE: + return keyctl_instantiate_key((key_serial_t) arg2, + (const void __user *) arg3, + (size_t) arg4, + (key_serial_t) arg5); + + case KEYCTL_NEGATE: + return keyctl_negate_key((key_serial_t) arg2, + (unsigned) arg3, + (key_serial_t) arg4); + + default: + return -EOPNOTSUPP; + } + +} /* end sys_keyctl() */ diff --git a/security/keys/keyring.c b/security/keys/keyring.c new file mode 100644 index 00000000000..e2ab4f8e748 --- /dev/null +++ b/security/keys/keyring.c @@ -0,0 +1,895 @@ +/* keyring.c: keyring handling + * + * Copyright (C) 2004 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/sched.h> +#include <linux/slab.h> +#include <linux/seq_file.h> +#include <linux/err.h> +#include <asm/uaccess.h> +#include "internal.h" + +/* + * when plumbing the depths of the key tree, this sets a hard limit set on how + * deep we're willing to go + */ +#define KEYRING_SEARCH_MAX_DEPTH 6 + +/* + * we keep all named keyrings in a hash to speed looking them up + */ +#define KEYRING_NAME_HASH_SIZE (1 << 5) + +static struct list_head keyring_name_hash[KEYRING_NAME_HASH_SIZE]; +static DEFINE_RWLOCK(keyring_name_lock); + +static inline unsigned keyring_hash(const char *desc) +{ + unsigned bucket = 0; + + for (; *desc; desc++) + bucket += (unsigned char) *desc; + + return bucket & (KEYRING_NAME_HASH_SIZE - 1); +} + +/* + * the keyring type definition + */ +static int keyring_instantiate(struct key *keyring, + const void *data, size_t datalen); +static int keyring_duplicate(struct key *keyring, const struct key *source); +static int keyring_match(const struct key *keyring, const void *criterion); +static void keyring_destroy(struct key *keyring); +static void keyring_describe(const struct key *keyring, struct seq_file *m); +static long keyring_read(const struct key *keyring, + char __user *buffer, size_t buflen); + +struct key_type key_type_keyring = { + .name = "keyring", + .def_datalen = sizeof(struct keyring_list), + .instantiate = keyring_instantiate, + .duplicate = keyring_duplicate, + .match = keyring_match, + .destroy = keyring_destroy, + .describe = keyring_describe, + .read = keyring_read, +}; + +/* + * semaphore to serialise link/link calls to prevent two link calls in parallel + * introducing a cycle + */ +DECLARE_RWSEM(keyring_serialise_link_sem); + +/*****************************************************************************/ +/* + * publish the name of a keyring so that it can be found by name (if it has + * one) + */ +void keyring_publish_name(struct key *keyring) +{ + int bucket; + + if (keyring->description) { + bucket = keyring_hash(keyring->description); + + write_lock(&keyring_name_lock); + + if (!keyring_name_hash[bucket].next) + INIT_LIST_HEAD(&keyring_name_hash[bucket]); + + list_add_tail(&keyring->type_data.link, + &keyring_name_hash[bucket]); + + write_unlock(&keyring_name_lock); + } + +} /* end keyring_publish_name() */ + +/*****************************************************************************/ +/* + * initialise a keyring + * - we object if we were given any data + */ +static int keyring_instantiate(struct key *keyring, + const void *data, size_t datalen) +{ + int ret; + + ret = -EINVAL; + if (datalen == 0) { + /* make the keyring available by name if it has one */ + keyring_publish_name(keyring); + ret = 0; + } + + return ret; + +} /* end keyring_instantiate() */ + +/*****************************************************************************/ +/* + * duplicate the list of subscribed keys from a source keyring into this one + */ +static int keyring_duplicate(struct key *keyring, const struct key *source) +{ + struct keyring_list *sklist, *klist; + unsigned max; + size_t size; + int loop, ret; + + const unsigned limit = + (PAGE_SIZE - sizeof(*klist)) / sizeof(struct key); + + ret = 0; + sklist = source->payload.subscriptions; + + if (sklist && sklist->nkeys > 0) { + max = sklist->nkeys; + BUG_ON(max > limit); + + max = (max + 3) & ~3; + if (max > limit) + max = limit; + + ret = -ENOMEM; + size = sizeof(*klist) + sizeof(struct key) * max; + klist = kmalloc(size, GFP_KERNEL); + if (!klist) + goto error; + + klist->maxkeys = max; + klist->nkeys = sklist->nkeys; + memcpy(klist->keys, + sklist->keys, + sklist->nkeys * sizeof(struct key)); + + for (loop = klist->nkeys - 1; loop >= 0; loop--) + atomic_inc(&klist->keys[loop]->usage); + + keyring->payload.subscriptions = klist; + ret = 0; + } + + error: + return ret; + +} /* end keyring_duplicate() */ + +/*****************************************************************************/ +/* + * match keyrings on their name + */ +static int keyring_match(const struct key *keyring, const void *description) +{ + return keyring->description && + strcmp(keyring->description, description) == 0; + +} /* end keyring_match() */ + +/*****************************************************************************/ +/* + * dispose of the data dangling from the corpse of a keyring + */ +static void keyring_destroy(struct key *keyring) +{ + struct keyring_list *klist; + int loop; + + if (keyring->description) { + write_lock(&keyring_name_lock); + list_del(&keyring->type_data.link); + write_unlock(&keyring_name_lock); + } + + klist = keyring->payload.subscriptions; + if (klist) { + for (loop = klist->nkeys - 1; loop >= 0; loop--) + key_put(klist->keys[loop]); + kfree(klist); + } + +} /* end keyring_destroy() */ + +/*****************************************************************************/ +/* + * describe the keyring + */ +static void keyring_describe(const struct key *keyring, struct seq_file *m) +{ + struct keyring_list *klist; + + if (keyring->description) { + seq_puts(m, keyring->description); + } + else { + seq_puts(m, "[anon]"); + } + + klist = keyring->payload.subscriptions; + if (klist) + seq_printf(m, ": %u/%u", klist->nkeys, klist->maxkeys); + else + seq_puts(m, ": empty"); + +} /* end keyring_describe() */ + +/*****************************************************************************/ +/* + * read a list of key IDs from the keyring's contents + */ +static long keyring_read(const struct key *keyring, + char __user *buffer, size_t buflen) +{ + struct keyring_list *klist; + struct key *key; + size_t qty, tmp; + int loop, ret; + + ret = 0; + klist = keyring->payload.subscriptions; + + if (klist) { + /* calculate how much data we could return */ + qty = klist->nkeys * sizeof(key_serial_t); + + if (buffer && buflen > 0) { + if (buflen > qty) + buflen = qty; + + /* copy the IDs of the subscribed keys into the + * buffer */ + ret = -EFAULT; + + for (loop = 0; loop < klist->nkeys; loop++) { + key = klist->keys[loop]; + + tmp = sizeof(key_serial_t); + if (tmp > buflen) + tmp = buflen; + + if (copy_to_user(buffer, + &key->serial, + tmp) != 0) + goto error; + + buflen -= tmp; + if (buflen == 0) + break; + buffer += tmp; + } + } + + ret = qty; + } + + error: + return ret; + +} /* end keyring_read() */ + +/*****************************************************************************/ +/* + * allocate a keyring and link into the destination keyring + */ +struct key *keyring_alloc(const char *description, uid_t uid, gid_t gid, + int not_in_quota, struct key *dest) +{ + struct key *keyring; + int ret; + + keyring = key_alloc(&key_type_keyring, description, + uid, gid, KEY_USR_ALL, not_in_quota); + + if (!IS_ERR(keyring)) { + ret = key_instantiate_and_link(keyring, NULL, 0, dest); + if (ret < 0) { + key_put(keyring); + keyring = ERR_PTR(ret); + } + } + + return keyring; + +} /* end keyring_alloc() */ + +/*****************************************************************************/ +/* + * search the supplied keyring tree for a key that matches the criterion + * - perform a breadth-then-depth search up to the prescribed limit + * - we only find keys on which we have search permission + * - we use the supplied match function to see if the description (or other + * feature of interest) matches + * - we readlock the keyrings as we search down the tree + * - we return -EAGAIN if we didn't find any matching key + * - we return -ENOKEY if we only found negative matching keys + */ +struct key *keyring_search_aux(struct key *keyring, + struct key_type *type, + const void *description, + key_match_func_t match) +{ + struct { + struct key *keyring; + int kix; + } stack[KEYRING_SEARCH_MAX_DEPTH]; + + struct keyring_list *keylist; + struct timespec now; + struct key *key; + long err; + int sp, psp, kix; + + key_check(keyring); + + /* top keyring must have search permission to begin the search */ + key = ERR_PTR(-EACCES); + if (!key_permission(keyring, KEY_SEARCH)) + goto error; + + key = ERR_PTR(-ENOTDIR); + if (keyring->type != &key_type_keyring) + goto error; + + now = current_kernel_time(); + err = -EAGAIN; + sp = 0; + + /* start processing a new keyring */ + descend: + read_lock(&keyring->lock); + if (keyring->flags & KEY_FLAG_REVOKED) + goto not_this_keyring; + + keylist = keyring->payload.subscriptions; + if (!keylist) + goto not_this_keyring; + + /* iterate through the keys in this keyring first */ + for (kix = 0; kix < keylist->nkeys; kix++) { + key = keylist->keys[kix]; + + /* ignore keys not of this type */ + if (key->type != type) + continue; + + /* skip revoked keys and expired keys */ + if (key->flags & KEY_FLAG_REVOKED) + continue; + + if (key->expiry && now.tv_sec >= key->expiry) + continue; + + /* keys that don't match */ + if (!match(key, description)) + continue; + + /* key must have search permissions */ + if (!key_permission(key, KEY_SEARCH)) + continue; + + /* we set a different error code if we find a negative key */ + if (key->flags & KEY_FLAG_NEGATIVE) { + err = -ENOKEY; + continue; + } + + goto found; + } + + /* search through the keyrings nested in this one */ + kix = 0; + ascend: + while (kix < keylist->nkeys) { + key = keylist->keys[kix]; + if (key->type != &key_type_keyring) + goto next; + + /* recursively search nested keyrings + * - only search keyrings for which we have search permission + */ + if (sp >= KEYRING_SEARCH_MAX_DEPTH) + goto next; + + if (!key_permission(key, KEY_SEARCH)) + goto next; + + /* evade loops in the keyring tree */ + for (psp = 0; psp < sp; psp++) + if (stack[psp].keyring == keyring) + goto next; + + /* stack the current position */ + stack[sp].keyring = keyring; + stack[sp].kix = kix; + sp++; + + /* begin again with the new keyring */ + keyring = key; + goto descend; + + next: + kix++; + } + + /* the keyring we're looking at was disqualified or didn't contain a + * matching key */ + not_this_keyring: + read_unlock(&keyring->lock); + + if (sp > 0) { + /* resume the processing of a keyring higher up in the tree */ + sp--; + keyring = stack[sp].keyring; + keylist = keyring->payload.subscriptions; + kix = stack[sp].kix + 1; + goto ascend; + } + + key = ERR_PTR(err); + goto error; + + /* we found a viable match */ + found: + atomic_inc(&key->usage); + read_unlock(&keyring->lock); + + /* unwind the keyring stack */ + while (sp > 0) { + sp--; + read_unlock(&stack[sp].keyring->lock); + } + + key_check(key); + error: + return key; + +} /* end keyring_search_aux() */ + +/*****************************************************************************/ +/* + * search the supplied keyring tree for a key that matches the criterion + * - perform a breadth-then-depth search up to the prescribed limit + * - we only find keys on which we have search permission + * - we readlock the keyrings as we search down the tree + * - we return -EAGAIN if we didn't find any matching key + * - we return -ENOKEY if we only found negative matching keys + */ +struct key *keyring_search(struct key *keyring, + struct key_type *type, + const char *description) +{ + return keyring_search_aux(keyring, type, description, type->match); + +} /* end keyring_search() */ + +EXPORT_SYMBOL(keyring_search); + +/*****************************************************************************/ +/* + * search the given keyring only (no recursion) + * - keyring must be locked by caller + */ +struct key *__keyring_search_one(struct key *keyring, + const struct key_type *ktype, + const char *description, + key_perm_t perm) +{ + struct keyring_list *klist; + struct key *key; + int loop; + + klist = keyring->payload.subscriptions; + if (klist) { + for (loop = 0; loop < klist->nkeys; loop++) { + key = klist->keys[loop]; + + if (key->type == ktype && + key->type->match(key, description) && + key_permission(key, perm) && + !(key->flags & KEY_FLAG_REVOKED) + ) + goto found; + } + } + + key = ERR_PTR(-ENOKEY); + goto error; + + found: + atomic_inc(&key->usage); + error: + return key; + +} /* end __keyring_search_one() */ + +/*****************************************************************************/ +/* + * find a keyring with the specified name + * - all named keyrings are searched + * - only find keyrings with search permission for the process + * - only find keyrings with a serial number greater than the one specified + */ +struct key *find_keyring_by_name(const char *name, key_serial_t bound) +{ + struct key *keyring; + int bucket; + + keyring = ERR_PTR(-EINVAL); + if (!name) + goto error; + + bucket = keyring_hash(name); + + read_lock(&keyring_name_lock); + + if (keyring_name_hash[bucket].next) { + /* search this hash bucket for a keyring with a matching name + * that's readable and that hasn't been revoked */ + list_for_each_entry(keyring, + &keyring_name_hash[bucket], + type_data.link + ) { + if (keyring->flags & KEY_FLAG_REVOKED) + continue; + + if (strcmp(keyring->description, name) != 0) + continue; + + if (!key_permission(keyring, KEY_SEARCH)) + continue; + + /* found a potential candidate, but we still need to + * check the serial number */ + if (keyring->serial <= bound) + continue; + + /* we've got a match */ + atomic_inc(&keyring->usage); + read_unlock(&keyring_name_lock); + goto error; + } + } + + read_unlock(&keyring_name_lock); + keyring = ERR_PTR(-ENOKEY); + + error: + return keyring; + +} /* end find_keyring_by_name() */ + +/*****************************************************************************/ +/* + * see if a cycle will will be created by inserting acyclic tree B in acyclic + * tree A at the topmost level (ie: as a direct child of A) + * - since we are adding B to A at the top level, checking for cycles should + * just be a matter of seeing if node A is somewhere in tree B + */ +static int keyring_detect_cycle(struct key *A, struct key *B) +{ + struct { + struct key *subtree; + int kix; + } stack[KEYRING_SEARCH_MAX_DEPTH]; + + struct keyring_list *keylist; + struct key *subtree, *key; + int sp, kix, ret; + + ret = -EDEADLK; + if (A == B) + goto error; + + subtree = B; + sp = 0; + + /* start processing a new keyring */ + descend: + read_lock(&subtree->lock); + if (subtree->flags & KEY_FLAG_REVOKED) + goto not_this_keyring; + + keylist = subtree->payload.subscriptions; + if (!keylist) + goto not_this_keyring; + kix = 0; + + ascend: + /* iterate through the remaining keys in this keyring */ + for (; kix < keylist->nkeys; kix++) { + key = keylist->keys[kix]; + + if (key == A) + goto cycle_detected; + + /* recursively check nested keyrings */ + if (key->type == &key_type_keyring) { + if (sp >= KEYRING_SEARCH_MAX_DEPTH) + goto too_deep; + + /* stack the current position */ + stack[sp].subtree = subtree; + stack[sp].kix = kix; + sp++; + + /* begin again with the new keyring */ + subtree = key; + goto descend; + } + } + + /* the keyring we're looking at was disqualified or didn't contain a + * matching key */ + not_this_keyring: + read_unlock(&subtree->lock); + + if (sp > 0) { + /* resume the checking of a keyring higher up in the tree */ + sp--; + subtree = stack[sp].subtree; + keylist = subtree->payload.subscriptions; + kix = stack[sp].kix + 1; + goto ascend; + } + + ret = 0; /* no cycles detected */ + + error: + return ret; + + too_deep: + ret = -ELOOP; + goto error_unwind; + cycle_detected: + ret = -EDEADLK; + error_unwind: + read_unlock(&subtree->lock); + + /* unwind the keyring stack */ + while (sp > 0) { + sp--; + read_unlock(&stack[sp].subtree->lock); + } + + goto error; + +} /* end keyring_detect_cycle() */ + +/*****************************************************************************/ +/* + * link a key into to a keyring + * - must be called with the keyring's semaphore held + */ +int __key_link(struct key *keyring, struct key *key) +{ + struct keyring_list *klist, *nklist; + unsigned max; + size_t size; + int ret; + + ret = -EKEYREVOKED; + if (keyring->flags & KEY_FLAG_REVOKED) + goto error; + + ret = -ENOTDIR; + if (keyring->type != &key_type_keyring) + goto error; + + /* serialise link/link calls to prevent parallel calls causing a + * cycle when applied to two keyring in opposite orders */ + down_write(&keyring_serialise_link_sem); + + /* check that we aren't going to create a cycle adding one keyring to + * another */ + if (key->type == &key_type_keyring) { + ret = keyring_detect_cycle(keyring, key); + if (ret < 0) + goto error2; + } + + /* check that we aren't going to overrun the user's quota */ + ret = key_payload_reserve(keyring, + keyring->datalen + KEYQUOTA_LINK_BYTES); + if (ret < 0) + goto error2; + + klist = keyring->payload.subscriptions; + + if (klist && klist->nkeys < klist->maxkeys) { + /* there's sufficient slack space to add directly */ + atomic_inc(&key->usage); + + write_lock(&keyring->lock); + klist->keys[klist->nkeys++] = key; + write_unlock(&keyring->lock); + + ret = 0; + } + else { + /* grow the key list */ + max = 4; + if (klist) + max += klist->maxkeys; + + ret = -ENFILE; + size = sizeof(*klist) + sizeof(*key) * max; + if (size > PAGE_SIZE) + goto error3; + + ret = -ENOMEM; + nklist = kmalloc(size, GFP_KERNEL); + if (!nklist) + goto error3; + nklist->maxkeys = max; + nklist->nkeys = 0; + + if (klist) { + nklist->nkeys = klist->nkeys; + memcpy(nklist->keys, + klist->keys, + sizeof(struct key *) * klist->nkeys); + } + + /* add the key into the new space */ + atomic_inc(&key->usage); + + write_lock(&keyring->lock); + keyring->payload.subscriptions = nklist; + nklist->keys[nklist->nkeys++] = key; + write_unlock(&keyring->lock); + + /* dispose of the old keyring list */ + kfree(klist); + + ret = 0; + } + + error2: + up_write(&keyring_serialise_link_sem); + error: + return ret; + + error3: + /* undo the quota changes */ + key_payload_reserve(keyring, + keyring->datalen - KEYQUOTA_LINK_BYTES); + goto error2; + +} /* end __key_link() */ + +/*****************************************************************************/ +/* + * link a key to a keyring + */ +int key_link(struct key *keyring, struct key *key) +{ + int ret; + + key_check(keyring); + key_check(key); + + down_write(&keyring->sem); + ret = __key_link(keyring, key); + up_write(&keyring->sem); + + return ret; + +} /* end key_link() */ + +EXPORT_SYMBOL(key_link); + +/*****************************************************************************/ +/* + * unlink the first link to a key from a keyring + */ +int key_unlink(struct key *keyring, struct key *key) +{ + struct keyring_list *klist; + int loop, ret; + + key_check(keyring); + key_check(key); + + ret = -ENOTDIR; + if (keyring->type != &key_type_keyring) + goto error; + + down_write(&keyring->sem); + + klist = keyring->payload.subscriptions; + if (klist) { + /* search the keyring for the key */ + for (loop = 0; loop < klist->nkeys; loop++) + if (klist->keys[loop] == key) + goto key_is_present; + } + + up_write(&keyring->sem); + ret = -ENOENT; + goto error; + + key_is_present: + /* adjust the user's quota */ + key_payload_reserve(keyring, + keyring->datalen - KEYQUOTA_LINK_BYTES); + + /* shuffle down the key pointers + * - it might be worth shrinking the allocated memory, but that runs + * the risk of ENOMEM as we would have to copy + */ + write_lock(&keyring->lock); + + klist->nkeys--; + if (loop < klist->nkeys) + memcpy(&klist->keys[loop], + &klist->keys[loop + 1], + (klist->nkeys - loop) * sizeof(struct key *)); + + write_unlock(&keyring->lock); + + up_write(&keyring->sem); + key_put(key); + ret = 0; + + error: + return ret; + +} /* end key_unlink() */ + +EXPORT_SYMBOL(key_unlink); + +/*****************************************************************************/ +/* + * clear the specified process keyring + * - implements keyctl(KEYCTL_CLEAR) + */ +int keyring_clear(struct key *keyring) +{ + struct keyring_list *klist; + int loop, ret; + + ret = -ENOTDIR; + if (keyring->type == &key_type_keyring) { + /* detach the pointer block with the locks held */ + down_write(&keyring->sem); + + klist = keyring->payload.subscriptions; + if (klist) { + /* adjust the quota */ + key_payload_reserve(keyring, + sizeof(struct keyring_list)); + + write_lock(&keyring->lock); + keyring->payload.subscriptions = NULL; + write_unlock(&keyring->lock); + } + + up_write(&keyring->sem); + + /* free the keys after the locks have been dropped */ + if (klist) { + for (loop = klist->nkeys - 1; loop >= 0; loop--) + key_put(klist->keys[loop]); + + kfree(klist); + } + + ret = 0; + } + + return ret; + +} /* end keyring_clear() */ + +EXPORT_SYMBOL(keyring_clear); diff --git a/security/keys/proc.c b/security/keys/proc.c new file mode 100644 index 00000000000..91343b85c39 --- /dev/null +++ b/security/keys/proc.c @@ -0,0 +1,251 @@ +/* proc.c: proc files for key database enumeration + * + * Copyright (C) 2004 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/sched.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <asm/errno.h> +#include "internal.h" + +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS +static int proc_keys_open(struct inode *inode, struct file *file); +static void *proc_keys_start(struct seq_file *p, loff_t *_pos); +static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos); +static void proc_keys_stop(struct seq_file *p, void *v); +static int proc_keys_show(struct seq_file *m, void *v); + +static struct seq_operations proc_keys_ops = { + .start = proc_keys_start, + .next = proc_keys_next, + .stop = proc_keys_stop, + .show = proc_keys_show, +}; + +static struct file_operations proc_keys_fops = { + .open = proc_keys_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; +#endif + +static int proc_key_users_open(struct inode *inode, struct file *file); +static void *proc_key_users_start(struct seq_file *p, loff_t *_pos); +static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos); +static void proc_key_users_stop(struct seq_file *p, void *v); +static int proc_key_users_show(struct seq_file *m, void *v); + +static struct seq_operations proc_key_users_ops = { + .start = proc_key_users_start, + .next = proc_key_users_next, + .stop = proc_key_users_stop, + .show = proc_key_users_show, +}; + +static struct file_operations proc_key_users_fops = { + .open = proc_key_users_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +/*****************************************************************************/ +/* + * declare the /proc files + */ +static int __init key_proc_init(void) +{ + struct proc_dir_entry *p; + +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS + p = create_proc_entry("keys", 0, NULL); + if (!p) + panic("Cannot create /proc/keys\n"); + + p->proc_fops = &proc_keys_fops; +#endif + + p = create_proc_entry("key-users", 0, NULL); + if (!p) + panic("Cannot create /proc/key-users\n"); + + p->proc_fops = &proc_key_users_fops; + + return 0; + +} /* end key_proc_init() */ + +__initcall(key_proc_init); + +/*****************************************************************************/ +/* + * implement "/proc/keys" to provides a list of the keys on the system + */ +#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS + +static int proc_keys_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &proc_keys_ops); + +} + +static void *proc_keys_start(struct seq_file *p, loff_t *_pos) +{ + struct rb_node *_p; + loff_t pos = *_pos; + + spin_lock(&key_serial_lock); + + _p = rb_first(&key_serial_tree); + while (pos > 0 && _p) { + pos--; + _p = rb_next(_p); + } + + return _p; + +} + +static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos) +{ + (*_pos)++; + return rb_next((struct rb_node *) v); + +} + +static void proc_keys_stop(struct seq_file *p, void *v) +{ + spin_unlock(&key_serial_lock); +} + +static int proc_keys_show(struct seq_file *m, void *v) +{ + struct rb_node *_p = v; + struct key *key = rb_entry(_p, struct key, serial_node); + struct timespec now; + unsigned long timo; + char xbuf[12]; + + now = current_kernel_time(); + + read_lock(&key->lock); + + /* come up with a suitable timeout value */ + if (key->expiry == 0) { + memcpy(xbuf, "perm", 5); + } + else if (now.tv_sec >= key->expiry) { + memcpy(xbuf, "expd", 5); + } + else { + timo = key->expiry - now.tv_sec; + + if (timo < 60) + sprintf(xbuf, "%lus", timo); + else if (timo < 60*60) + sprintf(xbuf, "%lum", timo / 60); + else if (timo < 60*60*24) + sprintf(xbuf, "%luh", timo / (60*60)); + else if (timo < 60*60*24*7) + sprintf(xbuf, "%lud", timo / (60*60*24)); + else + sprintf(xbuf, "%luw", timo / (60*60*24*7)); + } + + seq_printf(m, "%08x %c%c%c%c%c%c %5d %4s %06x %5d %5d %-9.9s ", + key->serial, + key->flags & KEY_FLAG_INSTANTIATED ? 'I' : '-', + key->flags & KEY_FLAG_REVOKED ? 'R' : '-', + key->flags & KEY_FLAG_DEAD ? 'D' : '-', + key->flags & KEY_FLAG_IN_QUOTA ? 'Q' : '-', + key->flags & KEY_FLAG_USER_CONSTRUCT ? 'U' : '-', + key->flags & KEY_FLAG_NEGATIVE ? 'N' : '-', + atomic_read(&key->usage), + xbuf, + key->perm, + key->uid, + key->gid, + key->type->name); + + if (key->type->describe) + key->type->describe(key, m); + seq_putc(m, '\n'); + + read_unlock(&key->lock); + + return 0; + +} + +#endif /* CONFIG_KEYS_DEBUG_PROC_KEYS */ + +/*****************************************************************************/ +/* + * implement "/proc/key-users" to provides a list of the key users + */ +static int proc_key_users_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &proc_key_users_ops); + +} + +static void *proc_key_users_start(struct seq_file *p, loff_t *_pos) +{ + struct rb_node *_p; + loff_t pos = *_pos; + + spin_lock(&key_user_lock); + + _p = rb_first(&key_user_tree); + while (pos > 0 && _p) { + pos--; + _p = rb_next(_p); + } + + return _p; + +} + +static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos) +{ + (*_pos)++; + return rb_next((struct rb_node *) v); + +} + +static void proc_key_users_stop(struct seq_file *p, void *v) +{ + spin_unlock(&key_user_lock); +} + +static int proc_key_users_show(struct seq_file *m, void *v) +{ + struct rb_node *_p = v; + struct key_user *user = rb_entry(_p, struct key_user, node); + + seq_printf(m, "%5u: %5d %d/%d %d/%d %d/%d\n", + user->uid, + atomic_read(&user->usage), + atomic_read(&user->nkeys), + atomic_read(&user->nikeys), + user->qnkeys, + KEYQUOTA_MAX_KEYS, + user->qnbytes, + KEYQUOTA_MAX_BYTES + ); + + return 0; + +} diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c new file mode 100644 index 00000000000..2eb0e471cd4 --- /dev/null +++ b/security/keys/process_keys.c @@ -0,0 +1,665 @@ +/* process_keys.c: management of a process's keyrings + * + * Copyright (C) 2004 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/sched.h> +#include <linux/slab.h> +#include <linux/keyctl.h> +#include <linux/fs.h> +#include <linux/err.h> +#include <asm/uaccess.h> +#include "internal.h" + +/* session keyring create vs join semaphore */ +static DECLARE_MUTEX(key_session_sem); + +/* the root user's tracking struct */ +struct key_user root_key_user = { + .usage = ATOMIC_INIT(3), + .consq = LIST_HEAD_INIT(root_key_user.consq), + .lock = SPIN_LOCK_UNLOCKED, + .nkeys = ATOMIC_INIT(2), + .nikeys = ATOMIC_INIT(2), + .uid = 0, +}; + +/* the root user's UID keyring */ +struct key root_user_keyring = { + .usage = ATOMIC_INIT(1), + .serial = 2, + .type = &key_type_keyring, + .user = &root_key_user, + .lock = RW_LOCK_UNLOCKED, + .sem = __RWSEM_INITIALIZER(root_user_keyring.sem), + .perm = KEY_USR_ALL, + .flags = KEY_FLAG_INSTANTIATED, + .description = "_uid.0", +#ifdef KEY_DEBUGGING + .magic = KEY_DEBUG_MAGIC, +#endif +}; + +/* the root user's default session keyring */ +struct key root_session_keyring = { + .usage = ATOMIC_INIT(1), + .serial = 1, + .type = &key_type_keyring, + .user = &root_key_user, + .lock = RW_LOCK_UNLOCKED, + .sem = __RWSEM_INITIALIZER(root_session_keyring.sem), + .perm = KEY_USR_ALL, + .flags = KEY_FLAG_INSTANTIATED, + .description = "_uid_ses.0", +#ifdef KEY_DEBUGGING + .magic = KEY_DEBUG_MAGIC, +#endif +}; + +/*****************************************************************************/ +/* + * allocate the keyrings to be associated with a UID + */ +int alloc_uid_keyring(struct user_struct *user) +{ + struct key *uid_keyring, *session_keyring; + char buf[20]; + int ret; + + /* concoct a default session keyring */ + sprintf(buf, "_uid_ses.%u", user->uid); + + session_keyring = keyring_alloc(buf, user->uid, (gid_t) -1, 0, NULL); + if (IS_ERR(session_keyring)) { + ret = PTR_ERR(session_keyring); + goto error; + } + + /* and a UID specific keyring, pointed to by the default session + * keyring */ + sprintf(buf, "_uid.%u", user->uid); + + uid_keyring = keyring_alloc(buf, user->uid, (gid_t) -1, 0, + session_keyring); + if (IS_ERR(uid_keyring)) { + key_put(session_keyring); + ret = PTR_ERR(uid_keyring); + goto error; + } + + /* install the keyrings */ + user->uid_keyring = uid_keyring; + user->session_keyring = session_keyring; + ret = 0; + + error: + return ret; + +} /* end alloc_uid_keyring() */ + +/*****************************************************************************/ +/* + * deal with the UID changing + */ +void switch_uid_keyring(struct user_struct *new_user) +{ +#if 0 /* do nothing for now */ + struct key *old; + + /* switch to the new user's session keyring if we were running under + * root's default session keyring */ + if (new_user->uid != 0 && + current->session_keyring == &root_session_keyring + ) { + atomic_inc(&new_user->session_keyring->usage); + + task_lock(current); + old = current->session_keyring; + current->session_keyring = new_user->session_keyring; + task_unlock(current); + + key_put(old); + } +#endif + +} /* end switch_uid_keyring() */ + +/*****************************************************************************/ +/* + * install a fresh thread keyring, discarding the old one + */ +int install_thread_keyring(struct task_struct *tsk) +{ + struct key *keyring, *old; + char buf[20]; + int ret; + + sprintf(buf, "_tid.%u", tsk->pid); + + keyring = keyring_alloc(buf, tsk->uid, tsk->gid, 1, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + task_lock(tsk); + old = tsk->thread_keyring; + tsk->thread_keyring = keyring; + task_unlock(tsk); + + ret = 0; + + key_put(old); + error: + return ret; + +} /* end install_thread_keyring() */ + +/*****************************************************************************/ +/* + * make sure a process keyring is installed + */ +static int install_process_keyring(struct task_struct *tsk) +{ + unsigned long flags; + struct key *keyring; + char buf[20]; + int ret; + + if (!tsk->signal->process_keyring) { + sprintf(buf, "_pid.%u", tsk->tgid); + + keyring = keyring_alloc(buf, tsk->uid, tsk->gid, 1, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + /* attach or swap keyrings */ + spin_lock_irqsave(&tsk->sighand->siglock, flags); + if (!tsk->signal->process_keyring) { + tsk->signal->process_keyring = keyring; + keyring = NULL; + } + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + + key_put(keyring); + } + + ret = 0; + error: + return ret; + +} /* end install_process_keyring() */ + +/*****************************************************************************/ +/* + * install a session keyring, discarding the old one + * - if a keyring is not supplied, an empty one is invented + */ +static int install_session_keyring(struct task_struct *tsk, + struct key *keyring) +{ + unsigned long flags; + struct key *old; + char buf[20]; + int ret; + + /* create an empty session keyring */ + if (!keyring) { + sprintf(buf, "_ses.%u", tsk->tgid); + + keyring = keyring_alloc(buf, tsk->uid, tsk->gid, 1, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + } + else { + atomic_inc(&keyring->usage); + } + + /* install the keyring */ + spin_lock_irqsave(&tsk->sighand->siglock, flags); + old = tsk->signal->session_keyring; + tsk->signal->session_keyring = keyring; + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + + ret = 0; + + key_put(old); + error: + return ret; + +} /* end install_session_keyring() */ + +/*****************************************************************************/ +/* + * copy the keys in a thread group for fork without CLONE_THREAD + */ +int copy_thread_group_keys(struct task_struct *tsk) +{ + unsigned long flags; + + key_check(current->thread_group->session_keyring); + key_check(current->thread_group->process_keyring); + + /* no process keyring yet */ + tsk->signal->process_keyring = NULL; + + /* same session keyring */ + spin_lock_irqsave(¤t->sighand->siglock, flags); + tsk->signal->session_keyring = + key_get(current->signal->session_keyring); + spin_unlock_irqrestore(¤t->sighand->siglock, flags); + + return 0; + +} /* end copy_thread_group_keys() */ + +/*****************************************************************************/ +/* + * copy the keys for fork + */ +int copy_keys(unsigned long clone_flags, struct task_struct *tsk) +{ + key_check(tsk->thread_keyring); + + /* no thread keyring yet */ + tsk->thread_keyring = NULL; + return 0; + +} /* end copy_keys() */ + +/*****************************************************************************/ +/* + * dispose of thread group keys upon thread group destruction + */ +void exit_thread_group_keys(struct signal_struct *tg) +{ + key_put(tg->session_keyring); + key_put(tg->process_keyring); + +} /* end exit_thread_group_keys() */ + +/*****************************************************************************/ +/* + * dispose of keys upon thread exit + */ +void exit_keys(struct task_struct *tsk) +{ + key_put(tsk->thread_keyring); + +} /* end exit_keys() */ + +/*****************************************************************************/ +/* + * deal with execve() + */ +int exec_keys(struct task_struct *tsk) +{ + unsigned long flags; + struct key *old; + + /* newly exec'd tasks don't get a thread keyring */ + task_lock(tsk); + old = tsk->thread_keyring; + tsk->thread_keyring = NULL; + task_unlock(tsk); + + key_put(old); + + /* discard the process keyring from a newly exec'd task */ + spin_lock_irqsave(&tsk->sighand->siglock, flags); + old = tsk->signal->process_keyring; + tsk->signal->process_keyring = NULL; + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + + key_put(old); + + return 0; + +} /* end exec_keys() */ + +/*****************************************************************************/ +/* + * deal with SUID programs + * - we might want to make this invent a new session keyring + */ +int suid_keys(struct task_struct *tsk) +{ + return 0; + +} /* end suid_keys() */ + +/*****************************************************************************/ +/* + * the filesystem user ID changed + */ +void key_fsuid_changed(struct task_struct *tsk) +{ + /* update the ownership of the thread keyring */ + if (tsk->thread_keyring) { + down_write(&tsk->thread_keyring->sem); + write_lock(&tsk->thread_keyring->lock); + tsk->thread_keyring->uid = tsk->fsuid; + write_unlock(&tsk->thread_keyring->lock); + up_write(&tsk->thread_keyring->sem); + } + +} /* end key_fsuid_changed() */ + +/*****************************************************************************/ +/* + * the filesystem group ID changed + */ +void key_fsgid_changed(struct task_struct *tsk) +{ + /* update the ownership of the thread keyring */ + if (tsk->thread_keyring) { + down_write(&tsk->thread_keyring->sem); + write_lock(&tsk->thread_keyring->lock); + tsk->thread_keyring->gid = tsk->fsgid; + write_unlock(&tsk->thread_keyring->lock); + up_write(&tsk->thread_keyring->sem); + } + +} /* end key_fsgid_changed() */ + +/*****************************************************************************/ +/* + * search the process keyrings for the first matching key + * - we use the supplied match function to see if the description (or other + * feature of interest) matches + * - we return -EAGAIN if we didn't find any matching key + * - we return -ENOKEY if we found only negative matching keys + */ +struct key *search_process_keyrings_aux(struct key_type *type, + const void *description, + key_match_func_t match) +{ + struct task_struct *tsk = current; + unsigned long flags; + struct key *key, *ret, *err, *tmp; + + /* we want to return -EAGAIN or -ENOKEY if any of the keyrings were + * searchable, but we failed to find a key or we found a negative key; + * otherwise we want to return a sample error (probably -EACCES) if + * none of the keyrings were searchable + * + * in terms of priority: success > -ENOKEY > -EAGAIN > other error + */ + key = NULL; + ret = NULL; + err = ERR_PTR(-EAGAIN); + + /* search the thread keyring first */ + if (tsk->thread_keyring) { + key = keyring_search_aux(tsk->thread_keyring, type, + description, match); + if (!IS_ERR(key)) + goto found; + + switch (PTR_ERR(key)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key; + break; + default: + err = key; + break; + } + } + + /* search the process keyring second */ + if (tsk->signal->process_keyring) { + key = keyring_search_aux(tsk->signal->process_keyring, + type, description, match); + if (!IS_ERR(key)) + goto found; + + switch (PTR_ERR(key)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key; + break; + default: + err = key; + break; + } + } + + /* search the session keyring last */ + spin_lock_irqsave(&tsk->sighand->siglock, flags); + + tmp = tsk->signal->session_keyring; + if (!tmp) + tmp = tsk->user->session_keyring; + atomic_inc(&tmp->usage); + + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + + key = keyring_search_aux(tmp, type, description, match); + key_put(tmp); + if (!IS_ERR(key)) + goto found; + + switch (PTR_ERR(key)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key; + break; + default: + err = key; + break; + } + + /* no key - decide on the error we're going to go for */ + key = ret ? ret : err; + + found: + return key; + +} /* end search_process_keyrings_aux() */ + +/*****************************************************************************/ +/* + * search the process keyrings for the first matching key + * - we return -EAGAIN if we didn't find any matching key + * - we return -ENOKEY if we found only negative matching keys + */ +struct key *search_process_keyrings(struct key_type *type, + const char *description) +{ + return search_process_keyrings_aux(type, description, type->match); + +} /* end search_process_keyrings() */ + +/*****************************************************************************/ +/* + * lookup a key given a key ID from userspace with a given permissions mask + * - don't create special keyrings unless so requested + * - partially constructed keys aren't found unless requested + */ +struct key *lookup_user_key(key_serial_t id, int create, int partial, + key_perm_t perm) +{ + struct task_struct *tsk = current; + unsigned long flags; + struct key *key; + int ret; + + key = ERR_PTR(-ENOKEY); + + switch (id) { + case KEY_SPEC_THREAD_KEYRING: + if (!tsk->thread_keyring) { + if (!create) + goto error; + + ret = install_thread_keyring(tsk); + if (ret < 0) { + key = ERR_PTR(ret); + goto error; + } + } + + key = tsk->thread_keyring; + atomic_inc(&key->usage); + break; + + case KEY_SPEC_PROCESS_KEYRING: + if (!tsk->signal->process_keyring) { + if (!create) + goto error; + + ret = install_process_keyring(tsk); + if (ret < 0) { + key = ERR_PTR(ret); + goto error; + } + } + + key = tsk->signal->process_keyring; + atomic_inc(&key->usage); + break; + + case KEY_SPEC_SESSION_KEYRING: + if (!tsk->signal->session_keyring) { + /* always install a session keyring upon access if one + * doesn't exist yet */ + ret = install_session_keyring( + tsk, tsk->user->session_keyring); + if (ret < 0) + goto error; + } + + spin_lock_irqsave(&tsk->sighand->siglock, flags); + key = tsk->signal->session_keyring; + atomic_inc(&key->usage); + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + break; + + case KEY_SPEC_USER_KEYRING: + key = tsk->user->uid_keyring; + atomic_inc(&key->usage); + break; + + case KEY_SPEC_USER_SESSION_KEYRING: + key = tsk->user->session_keyring; + atomic_inc(&key->usage); + break; + + case KEY_SPEC_GROUP_KEYRING: + /* group keyrings are not yet supported */ + key = ERR_PTR(-EINVAL); + goto error; + + default: + key = ERR_PTR(-EINVAL); + if (id < 1) + goto error; + + key = key_lookup(id); + if (IS_ERR(key)) + goto error; + break; + } + + /* check the status and permissions */ + if (perm) { + ret = key_validate(key); + if (ret < 0) + goto invalid_key; + } + + ret = -EIO; + if (!partial && !(key->flags & KEY_FLAG_INSTANTIATED)) + goto invalid_key; + + ret = -EACCES; + if (!key_permission(key, perm)) + goto invalid_key; + + error: + return key; + + invalid_key: + key_put(key); + key = ERR_PTR(ret); + goto error; + +} /* end lookup_user_key() */ + +/*****************************************************************************/ +/* + * join the named keyring as the session keyring if possible, or attempt to + * create a new one of that name if not + * - if the name is NULL, an empty anonymous keyring is installed instead + * - named session keyring joining is done with a semaphore held + */ +long join_session_keyring(const char *name) +{ + struct task_struct *tsk = current; + unsigned long flags; + struct key *keyring; + long ret; + + /* if no name is provided, install an anonymous keyring */ + if (!name) { + ret = install_session_keyring(tsk, NULL); + if (ret < 0) + goto error; + + spin_lock_irqsave(&tsk->sighand->siglock, flags); + ret = tsk->signal->session_keyring->serial; + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + goto error; + } + + /* allow the user to join or create a named keyring */ + down(&key_session_sem); + + /* look for an existing keyring of this name */ + keyring = find_keyring_by_name(name, 0); + if (PTR_ERR(keyring) == -ENOKEY) { + /* not found - try and create a new one */ + keyring = keyring_alloc(name, tsk->uid, tsk->gid, 0, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + } + else if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + + /* we've got a keyring - now to install it */ + ret = install_session_keyring(tsk, keyring); + if (ret < 0) + goto error2; + + ret = keyring->serial; + key_put(keyring); + + error2: + up(&key_session_sem); + error: + return ret; + +} /* end join_session_keyring() */ diff --git a/security/keys/request_key.c b/security/keys/request_key.c new file mode 100644 index 00000000000..9705b1aeba5 --- /dev/null +++ b/security/keys/request_key.c @@ -0,0 +1,359 @@ +/* request_key.c: request a key from userspace + * + * Copyright (C) 2004 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/sched.h> +#include <linux/kmod.h> +#include <linux/err.h> +#include "internal.h" + +struct key_construction { + struct list_head link; /* link in construction queue */ + struct key *key; /* key being constructed */ +}; + +/* when waiting for someone else's keys, you get added to this */ +DECLARE_WAIT_QUEUE_HEAD(request_key_conswq); + +/*****************************************************************************/ +/* + * request userspace finish the construction of a key + * - execute "/sbin/request-key <op> <key> <uid> <gid> <keyring> <keyring> <keyring> <info>" + * - if callout_info is an empty string, it'll be rendered as a "-" instead + */ +static int call_request_key(struct key *key, + const char *op, + const char *callout_info) +{ + struct task_struct *tsk = current; + unsigned long flags; + key_serial_t prkey, sskey; + char *argv[10], *envp[3], uid_str[12], gid_str[12]; + char key_str[12], keyring_str[3][12]; + int i; + + /* record the UID and GID */ + sprintf(uid_str, "%d", current->fsuid); + sprintf(gid_str, "%d", current->fsgid); + + /* we say which key is under construction */ + sprintf(key_str, "%d", key->serial); + + /* we specify the process's default keyrings */ + sprintf(keyring_str[0], "%d", + tsk->thread_keyring ? tsk->thread_keyring->serial : 0); + + prkey = 0; + if (tsk->signal->process_keyring) + prkey = tsk->signal->process_keyring->serial; + + sskey = 0; + spin_lock_irqsave(&tsk->sighand->siglock, flags); + if (tsk->signal->session_keyring) + sskey = tsk->signal->session_keyring->serial; + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); + + + if (!sskey) + sskey = tsk->user->session_keyring->serial; + + sprintf(keyring_str[1], "%d", prkey); + sprintf(keyring_str[2], "%d", sskey); + + /* set up a minimal environment */ + i = 0; + envp[i++] = "HOME=/"; + envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + envp[i] = NULL; + + /* set up the argument list */ + i = 0; + argv[i++] = "/sbin/request-key"; + argv[i++] = (char *) op; + argv[i++] = key_str; + argv[i++] = uid_str; + argv[i++] = gid_str; + argv[i++] = keyring_str[0]; + argv[i++] = keyring_str[1]; + argv[i++] = keyring_str[2]; + argv[i++] = callout_info[0] ? (char *) callout_info : "-"; + argv[i] = NULL; + + /* do it */ + return call_usermodehelper(argv[0], argv, envp, 1); + +} /* end call_request_key() */ + +/*****************************************************************************/ +/* + * call out to userspace for the key + * - called with the construction sem held, but the sem is dropped here + * - we ignore program failure and go on key status instead + */ +static struct key *__request_key_construction(struct key_type *type, + const char *description, + const char *callout_info) +{ + struct key_construction cons; + struct timespec now; + struct key *key; + int ret, negative; + + /* create a key and add it to the queue */ + key = key_alloc(type, description, + current->fsuid, current->fsgid, KEY_USR_ALL, 0); + if (IS_ERR(key)) + goto alloc_failed; + + write_lock(&key->lock); + key->flags |= KEY_FLAG_USER_CONSTRUCT; + write_unlock(&key->lock); + + cons.key = key; + list_add_tail(&cons.link, &key->user->consq); + + /* we drop the construction sem here on behalf of the caller */ + up_write(&key_construction_sem); + + /* make the call */ + ret = call_request_key(key, "create", callout_info); + if (ret < 0) + goto request_failed; + + /* if the key wasn't instantiated, then we want to give an error */ + ret = -ENOKEY; + if (!(key->flags & KEY_FLAG_INSTANTIATED)) + goto request_failed; + + down_write(&key_construction_sem); + list_del(&cons.link); + up_write(&key_construction_sem); + + /* also give an error if the key was negatively instantiated */ + check_not_negative: + if (key->flags & KEY_FLAG_NEGATIVE) { + key_put(key); + key = ERR_PTR(-ENOKEY); + } + + out: + return key; + + request_failed: + /* it wasn't instantiated + * - remove from construction queue + * - mark the key as dead + */ + negative = 0; + down_write(&key_construction_sem); + + list_del(&cons.link); + + write_lock(&key->lock); + key->flags &= ~KEY_FLAG_USER_CONSTRUCT; + + /* check it didn't get instantiated between the check and the down */ + if (!(key->flags & KEY_FLAG_INSTANTIATED)) { + key->flags |= KEY_FLAG_INSTANTIATED | KEY_FLAG_NEGATIVE; + negative = 1; + } + + write_unlock(&key->lock); + up_write(&key_construction_sem); + + if (!negative) + goto check_not_negative; /* surprisingly, the key got + * instantiated */ + + /* set the timeout and store in the session keyring if we can */ + now = current_kernel_time(); + key->expiry = now.tv_sec + key_negative_timeout; + + if (current->signal->session_keyring) { + unsigned long flags; + struct key *keyring; + + spin_lock_irqsave(¤t->sighand->siglock, flags); + keyring = current->signal->session_keyring; + atomic_inc(&keyring->usage); + spin_unlock_irqrestore(¤t->sighand->siglock, flags); + + key_link(keyring, key); + key_put(keyring); + } + + key_put(key); + + /* notify anyone who was waiting */ + wake_up_all(&request_key_conswq); + + key = ERR_PTR(ret); + goto out; + + alloc_failed: + up_write(&key_construction_sem); + goto out; + +} /* end __request_key_construction() */ + +/*****************************************************************************/ +/* + * call out to userspace to request the key + * - we check the construction queue first to see if an appropriate key is + * already being constructed by userspace + */ +static struct key *request_key_construction(struct key_type *type, + const char *description, + struct key_user *user, + const char *callout_info) +{ + struct key_construction *pcons; + struct key *key, *ckey; + + DECLARE_WAITQUEUE(myself, current); + + /* see if there's such a key under construction already */ + down_write(&key_construction_sem); + + list_for_each_entry(pcons, &user->consq, link) { + ckey = pcons->key; + + if (ckey->type != type) + continue; + + if (type->match(ckey, description)) + goto found_key_under_construction; + } + + /* see about getting userspace to construct the key */ + key = __request_key_construction(type, description, callout_info); + error: + return key; + + /* someone else has the same key under construction + * - we want to keep an eye on their key + */ + found_key_under_construction: + atomic_inc(&ckey->usage); + up_write(&key_construction_sem); + + /* wait for the key to be completed one way or another */ + add_wait_queue(&request_key_conswq, &myself); + + for (;;) { + set_current_state(TASK_UNINTERRUPTIBLE); + if (!(ckey->flags & KEY_FLAG_USER_CONSTRUCT)) + break; + schedule(); + } + + set_current_state(TASK_RUNNING); + remove_wait_queue(&request_key_conswq, &myself); + + /* we'll need to search this process's keyrings to see if the key is + * now there since we can't automatically assume it's also available + * there */ + key_put(ckey); + ckey = NULL; + + key = NULL; /* request a retry */ + goto error; + +} /* end request_key_construction() */ + +/*****************************************************************************/ +/* + * request a key + * - search the process's keyrings + * - check the list of keys being created or updated + * - call out to userspace for a key if requested (supplementary info can be + * passed) + */ +struct key *request_key(struct key_type *type, + const char *description, + const char *callout_info) +{ + struct key_user *user; + struct key *key; + + /* search all the process keyrings for a key */ + key = search_process_keyrings_aux(type, description, type->match); + + if (PTR_ERR(key) == -EAGAIN) { + /* the search failed, but the keyrings were searchable, so we + * should consult userspace if we can */ + key = ERR_PTR(-ENOKEY); + if (!callout_info) + goto error; + + /* - get hold of the user's construction queue */ + user = key_user_lookup(current->fsuid); + if (!user) { + key = ERR_PTR(-ENOMEM); + goto error; + } + + for (;;) { + /* ask userspace (returns NULL if it waited on a key + * being constructed) */ + key = request_key_construction(type, description, + user, callout_info); + if (key) + break; + + /* someone else made the key we want, so we need to + * search again as it might now be available to us */ + key = search_process_keyrings_aux(type, description, + type->match); + if (PTR_ERR(key) != -EAGAIN) + break; + } + + key_user_put(user); + } + + error: + return key; + +} /* end request_key() */ + +EXPORT_SYMBOL(request_key); + +/*****************************************************************************/ +/* + * validate a key + */ +int key_validate(struct key *key) +{ + struct timespec now; + int ret = 0; + + if (key) { + /* check it's still accessible */ + ret = -EKEYREVOKED; + if (key->flags & (KEY_FLAG_REVOKED | KEY_FLAG_DEAD)) + goto error; + + /* check it hasn't expired */ + ret = 0; + if (key->expiry) { + now = current_kernel_time(); + if (now.tv_sec >= key->expiry) + ret = -EKEYEXPIRED; + } + } + + error: + return ret; + +} /* end key_validate() */ + +EXPORT_SYMBOL(key_validate); diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c new file mode 100644 index 00000000000..8d65b3a2812 --- /dev/null +++ b/security/keys/user_defined.c @@ -0,0 +1,191 @@ +/* user_defined.c: user defined key type + * + * Copyright (C) 2004 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/sched.h> +#include <linux/slab.h> +#include <linux/seq_file.h> +#include <linux/err.h> +#include <asm/uaccess.h> +#include "internal.h" + +static int user_instantiate(struct key *key, const void *data, size_t datalen); +static int user_duplicate(struct key *key, const struct key *source); +static int user_update(struct key *key, const void *data, size_t datalen); +static int user_match(const struct key *key, const void *criterion); +static void user_destroy(struct key *key); +static void user_describe(const struct key *user, struct seq_file *m); +static long user_read(const struct key *key, + char __user *buffer, size_t buflen); + +/* + * user defined keys take an arbitrary string as the description and an + * arbitrary blob of data as the payload + */ +struct key_type key_type_user = { + .name = "user", + .instantiate = user_instantiate, + .duplicate = user_duplicate, + .update = user_update, + .match = user_match, + .destroy = user_destroy, + .describe = user_describe, + .read = user_read, +}; + +/*****************************************************************************/ +/* + * instantiate a user defined key + */ +static int user_instantiate(struct key *key, const void *data, size_t datalen) +{ + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > 32767 || !data) + goto error; + + ret = key_payload_reserve(key, datalen); + if (ret < 0) + goto error; + + /* attach the data */ + ret = -ENOMEM; + key->payload.data = kmalloc(datalen, GFP_KERNEL); + if (!key->payload.data) + goto error; + + memcpy(key->payload.data, data, datalen); + ret = 0; + + error: + return ret; + +} /* end user_instantiate() */ + +/*****************************************************************************/ +/* + * duplicate a user defined key + */ +static int user_duplicate(struct key *key, const struct key *source) +{ + int ret; + + /* just copy the payload */ + ret = -ENOMEM; + key->payload.data = kmalloc(source->datalen, GFP_KERNEL); + + if (key->payload.data) { + key->datalen = source->datalen; + memcpy(key->payload.data, source->payload.data, source->datalen); + ret = 0; + } + + return ret; + +} /* end user_duplicate() */ + +/*****************************************************************************/ +/* + * update a user defined key + */ +static int user_update(struct key *key, const void *data, size_t datalen) +{ + void *new, *zap; + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > 32767 || !data) + goto error; + + /* copy the data */ + ret = -ENOMEM; + new = kmalloc(datalen, GFP_KERNEL); + if (!new) + goto error; + + memcpy(new, data, datalen); + + /* check the quota and attach the new data */ + zap = new; + write_lock(&key->lock); + + ret = key_payload_reserve(key, datalen); + + if (ret == 0) { + /* attach the new data, displacing the old */ + zap = key->payload.data; + key->payload.data = new; + key->expiry = 0; + } + + write_unlock(&key->lock); + kfree(zap); + + error: + return ret; + +} /* end user_update() */ + +/*****************************************************************************/ +/* + * match users on their name + */ +static int user_match(const struct key *key, const void *description) +{ + return strcmp(key->description, description) == 0; + +} /* end user_match() */ + +/*****************************************************************************/ +/* + * dispose of the data dangling from the corpse of a user + */ +static void user_destroy(struct key *key) +{ + kfree(key->payload.data); + +} /* end user_destroy() */ + +/*****************************************************************************/ +/* + * describe the user + */ +static void user_describe(const struct key *key, struct seq_file *m) +{ + seq_puts(m, key->description); + + seq_printf(m, ": %u", key->datalen); + +} /* end user_describe() */ + +/*****************************************************************************/ +/* + * read the key data + */ +static long user_read(const struct key *key, + char __user *buffer, size_t buflen) +{ + long ret = key->datalen; + + /* we can return the data as is */ + if (buffer && buflen > 0) { + if (buflen > key->datalen) + buflen = key->datalen; + + if (copy_to_user(buffer, key->payload.data, buflen) != 0) + ret = -EFAULT; + } + + return ret; + +} /* end user_read() */ |