diff options
author | Timothy Pearson <tpearson@raptorengineering.com> | 2017-08-23 14:45:25 -0500 |
---|---|---|
committer | Timothy Pearson <tpearson@raptorengineering.com> | 2017-08-23 14:45:25 -0500 |
commit | fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204 (patch) | |
tree | 22962a4387943edc841c72a4e636a068c66d58fd /security/keys | |
download | ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.zip ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.tar.gz |
Initial import of modified Linux 2.6.28 tree
Original upstream URL:
git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git | branch linux-2.6.28.y
Diffstat (limited to 'security/keys')
-rw-r--r-- | security/keys/Makefile | 17 | ||||
-rw-r--r-- | security/keys/compat.c | 89 | ||||
-rw-r--r-- | security/keys/internal.h | 183 | ||||
-rw-r--r-- | security/keys/key.c | 1006 | ||||
-rw-r--r-- | security/keys/keyctl.c | 1241 | ||||
-rw-r--r-- | security/keys/keyring.c | 998 | ||||
-rw-r--r-- | security/keys/permission.c | 107 | ||||
-rw-r--r-- | security/keys/proc.c | 262 | ||||
-rw-r--r-- | security/keys/process_keys.c | 799 | ||||
-rw-r--r-- | security/keys/request_key.c | 525 | ||||
-rw-r--r-- | security/keys/request_key_auth.c | 280 | ||||
-rw-r--r-- | security/keys/sysctl.c | 50 | ||||
-rw-r--r-- | security/keys/user_defined.c | 218 |
13 files changed, 5775 insertions, 0 deletions
diff --git a/security/keys/Makefile b/security/keys/Makefile new file mode 100644 index 0000000..747a464 --- /dev/null +++ b/security/keys/Makefile @@ -0,0 +1,17 @@ +# +# Makefile for key management +# + +obj-y := \ + key.o \ + keyring.o \ + keyctl.o \ + permission.o \ + process_keys.o \ + request_key.o \ + request_key_auth.o \ + user_defined.o + +obj-$(CONFIG_KEYS_COMPAT) += compat.o +obj-$(CONFIG_PROC_FS) += proc.o +obj-$(CONFIG_SYSCTL) += sysctl.o diff --git a/security/keys/compat.c b/security/keys/compat.c new file mode 100644 index 0000000..c766c68 --- /dev/null +++ b/security/keys/compat.c @@ -0,0 +1,89 @@ +/* compat.c: 32-bit compatibility syscall for 64-bit systems + * + * Copyright (C) 2004-5 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/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); + + case KEYCTL_SET_REQKEY_KEYRING: + return keyctl_set_reqkey_keyring(arg2); + + case KEYCTL_SET_TIMEOUT: + return keyctl_set_timeout(arg2, arg3); + + case KEYCTL_ASSUME_AUTHORITY: + return keyctl_assume_authority(arg2); + + case KEYCTL_GET_SECURITY: + return keyctl_get_security(arg2, compat_ptr(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 0000000..239098f --- /dev/null +++ b/security/keys/internal.h @@ -0,0 +1,183 @@ +/* internal.h: authentication token and access key management internal defs + * + * Copyright (C) 2003-5, 2007 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-type.h> +#include <linux/key-ui.h> + +static inline __attribute__((format(printf, 1, 2))) +void no_printk(const char *fmt, ...) +{ +} + +#ifdef __KDEBUG +#define kenter(FMT, ...) \ + printk(KERN_DEBUG "==> %s("FMT")\n", __func__, ##__VA_ARGS__) +#define kleave(FMT, ...) \ + printk(KERN_DEBUG "<== %s()"FMT"\n", __func__, ##__VA_ARGS__) +#define kdebug(FMT, ...) \ + printk(KERN_DEBUG "xxx" FMT"yyy\n", ##__VA_ARGS__) +#else +#define kenter(FMT, ...) \ + no_printk(KERN_DEBUG "==> %s("FMT")\n", __func__, ##__VA_ARGS__) +#define kleave(FMT, ...) \ + no_printk(KERN_DEBUG "<== %s()"FMT"\n", __func__, ##__VA_ARGS__) +#define kdebug(FMT, ...) \ + no_printk(KERN_DEBUG FMT"\n", ##__VA_ARGS__) +#endif + +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 mutex cons_lock; /* construction initiation lock */ + 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 */ +}; + +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); + +/* + * key quota limits + * - root has its own separate limits to everyone else + */ +extern unsigned key_quota_root_maxkeys; +extern unsigned key_quota_root_maxbytes; +extern unsigned key_quota_maxkeys; +extern unsigned key_quota_maxbytes; + +#define KEYQUOTA_LINK_BYTES 4 /* a link in a keyring is worth 4 bytes */ + + +extern struct rb_root key_serial_tree; +extern spinlock_t key_serial_lock; +extern struct mutex key_construction_mutex; +extern wait_queue_head_t request_key_conswq; + + +extern int __key_link(struct key *keyring, struct key *key); + +extern key_ref_t __keyring_search_one(key_ref_t keyring_ref, + const struct key_type *type, + const char *description, + key_perm_t perm); + +extern struct key *keyring_search_instkey(struct key *keyring, + key_serial_t target_id); + +typedef int (*key_match_func_t)(const struct key *, const void *); + +extern key_ref_t keyring_search_aux(key_ref_t keyring_ref, + struct task_struct *tsk, + struct key_type *type, + const void *description, + key_match_func_t match); + +extern key_ref_t search_process_keyrings(struct key_type *type, + const void *description, + key_match_func_t match, + struct task_struct *tsk); + +extern struct key *find_keyring_by_name(const char *name, bool skip_perm_check); + +extern int install_user_keyrings(struct task_struct *tsk); +extern int install_thread_keyring(struct task_struct *tsk); +extern int install_process_keyring(struct task_struct *tsk); + +extern struct key *request_key_and_link(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len, + void *aux, + struct key *dest_keyring, + unsigned long flags); + +/* + * request_key authorisation + */ +struct request_key_auth { + struct key *target_key; + struct task_struct *context; + void *callout_info; + size_t callout_len; + pid_t pid; +}; + +extern struct key_type key_type_request_key_auth; +extern struct key *request_key_auth_new(struct key *target, + const void *callout_info, + size_t callout_len); + +extern struct key *key_get_instantiation_authkey(key_serial_t target_id); + +/* + * 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); +extern long keyctl_set_reqkey_keyring(int); +extern long keyctl_set_timeout(key_serial_t, unsigned); +extern long keyctl_assume_authority(key_serial_t); +extern long keyctl_get_security(key_serial_t keyid, char __user *buffer, + size_t buflen); + +/* + * 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 0000000..14948cf --- /dev/null +++ b/security/keys/key.c @@ -0,0 +1,1006 @@ +/* Basic authentication token and access key management + * + * Copyright (C) 2004-2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poison.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/security.h> +#include <linux/workqueue.h> +#include <linux/random.h> +#include <linux/err.h> +#include "internal.h" + +static struct kmem_cache *key_jar; +struct rb_root key_serial_tree; /* tree of keys indexed by serial */ +DEFINE_SPINLOCK(key_serial_lock); + +struct rb_root key_user_tree; /* tree of quota records indexed by UID */ +DEFINE_SPINLOCK(key_user_lock); + +unsigned int key_quota_root_maxkeys = 200; /* root's key count quota */ +unsigned int key_quota_root_maxbytes = 20000; /* root's key space quota */ +unsigned int key_quota_maxkeys = 200; /* general key count quota */ +unsigned int key_quota_maxbytes = 20000; /* general key space quota */ + +static LIST_HEAD(key_types_list); +static DECLARE_RWSEM(key_types_sem); + +static void key_cleanup(struct work_struct *work); +static DECLARE_WORK(key_cleanup_task, key_cleanup); + +/* we serialise key instantiation and link */ +DEFINE_MUTEX(key_construction_mutex); + +/* any key who's type gets unegistered will be re-typed to this */ +static 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); + mutex_init(&candidate->cons_lock); + + rb_link_node(&candidate->node, parent, p); + rb_insert_color(&candidate->node, &key_user_tree); + spin_unlock(&key_user_lock); + user = candidate; + goto out; + + /* okay - we found a user record for this UID */ + found: + atomic_inc(&user->usage); + spin_unlock(&key_user_lock); + 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() */ + +/*****************************************************************************/ +/* + * assign a key the next unique serial number + * - these are assigned randomly to avoid security issues through covert + * channel problems + */ +static inline void key_alloc_serial(struct key *key) +{ + struct rb_node *parent, **p; + struct key *xkey; + + /* propose a random serial number and look for a hole for it in the + * serial number tree */ + do { + get_random_bytes(&key->serial, sizeof(key->serial)); + + key->serial >>= 1; /* negative numbers are not permitted */ + } while (key->serial < 3); + + spin_lock(&key_serial_lock); + +attempt_insertion: + parent = NULL; + p = &key_serial_tree.rb_node; + + while (*p) { + parent = *p; + xkey = rb_entry(parent, struct key, serial_node); + + if (key->serial < xkey->serial) + p = &(*p)->rb_left; + else if (key->serial > xkey->serial) + p = &(*p)->rb_right; + else + goto serial_exists; + } + + /* we've found a suitable hole - arrange for this key to occupy it */ + rb_link_node(&key->serial_node, parent, p); + rb_insert_color(&key->serial_node, &key_serial_tree); + + spin_unlock(&key_serial_lock); + return; + + /* we found a key with the proposed serial number - walk the tree from + * that point looking for the next unused serial number */ +serial_exists: + for (;;) { + key->serial++; + if (key->serial < 3) { + key->serial = 3; + goto attempt_insertion; + } + + parent = rb_next(parent); + if (!parent) + goto attempt_insertion; + + xkey = rb_entry(parent, struct key, serial_node); + if (key->serial < xkey->serial) + goto attempt_insertion; + } + +} /* 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 + * key_create_or_update() + * - 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, struct task_struct *ctx, + key_perm_t perm, unsigned long flags) +{ + struct key_user *user = NULL; + struct key *key; + size_t desclen, quotalen; + int ret; + + 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 (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + unsigned maxkeys = (uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + spin_lock(&user->lock); + if (!(flags & KEY_ALLOC_QUOTA_OVERRUN)) { + if (user->qnkeys + 1 >= maxkeys || + user->qnbytes + quotalen >= maxbytes || + user->qnbytes + quotalen < user->qnbytes) + goto no_quota; + } + + user->qnkeys++; + user->qnbytes += quotalen; + spin_unlock(&user->lock); + } + + /* allocate and initialise the key and its description */ + key = kmem_cache_alloc(key_jar, GFP_KERNEL); + if (!key) + goto no_memory_2; + + if (desc) { + key->description = kmemdup(desc, desclen, GFP_KERNEL); + if (!key->description) + goto no_memory_3; + } + + atomic_set(&key->usage, 1); + 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; + key->security = NULL; + + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) + key->flags |= 1 << KEY_FLAG_IN_QUOTA; + + memset(&key->type_data, 0, sizeof(key->type_data)); + +#ifdef KEY_DEBUGGING + key->magic = KEY_DEBUG_MAGIC; +#endif + + /* let the security module know about the key */ + ret = security_key_alloc(key, ctx, flags); + if (ret < 0) + goto security_error; + + /* publish the key by giving it a serial number */ + atomic_inc(&user->nkeys); + key_alloc_serial(key); + +error: + return key; + +security_error: + kfree(key->description); + kmem_cache_free(key_jar, key); + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + spin_lock(&user->lock); + user->qnkeys--; + user->qnbytes -= quotalen; + spin_unlock(&user->lock); + } + key_user_put(user); + key = ERR_PTR(ret); + goto error; + +no_memory_3: + kmem_cache_free(key_jar, key); +no_memory_2: + if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { + spin_lock(&user->lock); + user->qnkeys--; + user->qnbytes -= quotalen; + spin_unlock(&user->lock); + } + key_user_put(user); +no_memory_1: + key = ERR_PTR(-ENOMEM); + goto error; + +no_quota: + spin_unlock(&user->lock); + key_user_put(user); + key = ERR_PTR(-EDQUOT); + goto error; + +} /* 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 && test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + unsigned maxbytes = (key->user->uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + spin_lock(&key->user->lock); + + if (delta > 0 && + (key->user->qnbytes + delta >= maxbytes || + key->user->qnbytes + delta < key->user->qnbytes)) { + ret = -EDQUOT; + } + else { + key->user->qnbytes += delta; + key->quotalen += delta; + } + spin_unlock(&key->user->lock); + } + + /* change the recorded data length if that didn't generate an error */ + if (ret == 0) + key->datalen = datalen; + + return ret; + +} /* 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, + struct key *instkey) +{ + int ret, awaken; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + mutex_lock(&key_construction_mutex); + + /* can't instantiate twice */ + if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) { + /* instantiate the key */ + ret = key->type->instantiate(key, data, datalen); + + if (ret == 0) { + /* mark the key as being instantiated */ + atomic_inc(&key->user->nikeys); + set_bit(KEY_FLAG_INSTANTIATED, &key->flags); + + if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) + awaken = 1; + + /* and link it into the destination keyring */ + if (keyring) + ret = __key_link(keyring, key); + + /* disable the authorisation key */ + if (instkey) + key_revoke(instkey); + } + } + + mutex_unlock(&key_construction_mutex); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_bit(&key->flags, KEY_FLAG_USER_CONSTRUCT); + + return ret; + +} /* 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, + struct key *instkey) +{ + int ret; + + if (keyring) + down_write(&keyring->sem); + + ret = __key_instantiate_and_link(key, data, datalen, keyring, instkey); + + 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 key *instkey) +{ + struct timespec now; + int ret, awaken; + + key_check(key); + key_check(keyring); + + awaken = 0; + ret = -EBUSY; + + if (keyring) + down_write(&keyring->sem); + + mutex_lock(&key_construction_mutex); + + /* can't instantiate twice */ + if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) { + /* mark the key as being negatively instantiated */ + atomic_inc(&key->user->nikeys); + set_bit(KEY_FLAG_NEGATIVE, &key->flags); + set_bit(KEY_FLAG_INSTANTIATED, &key->flags); + now = current_kernel_time(); + key->expiry = now.tv_sec + timeout; + + if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) + awaken = 1; + + ret = 0; + + /* and link it into the destination keyring */ + if (keyring) + ret = __key_link(keyring, key); + + /* disable the authorisation key */ + if (instkey) + key_revoke(instkey); + } + + mutex_unlock(&key_construction_mutex); + + if (keyring) + up_write(&keyring->sem); + + /* wake up anyone waiting for a key to be constructed */ + if (awaken) + wake_up_bit(&key->flags, KEY_FLAG_USER_CONSTRUCT); + + 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(struct work_struct *work) +{ + 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); + + key_check(key); + + security_key_free(key); + + /* deal with the user's key tracking and quota */ + if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + spin_lock(&key->user->lock); + key->user->qnkeys--; + key->user->qnbytes -= key->quotalen; + spin_unlock(&key->user->lock); + } + + atomic_dec(&key->user->nkeys); + if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) + 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: + /* pretend it doesn't exist if it's dead */ + if (atomic_read(&key->usage) == 0 || + test_bit(KEY_FLAG_DEAD, &key->flags) || + 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 key_ref_t __key_update(key_ref_t key_ref, + const void *payload, size_t plen) +{ + struct key *key = key_ref_to_ptr(key_ref); + int ret; + + /* need write permission on the key to update it */ + ret = key_permission(key_ref, KEY_WRITE); + if (ret < 0) + 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 */ + clear_bit(KEY_FLAG_NEGATIVE, &key->flags); + + up_write(&key->sem); + + if (ret < 0) + goto error; +out: + return key_ref; + +error: + key_put(key); + key_ref = 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 + */ +key_ref_t key_create_or_update(key_ref_t keyring_ref, + const char *type, + const char *description, + const void *payload, + size_t plen, + key_perm_t perm, + unsigned long flags) +{ + struct key_type *ktype; + struct key *keyring, *key = NULL; + key_ref_t key_ref; + int ret; + + /* 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_ref = ERR_PTR(-ENODEV); + goto error; + } + + key_ref = ERR_PTR(-EINVAL); + if (!ktype->match || !ktype->instantiate) + goto error_2; + + keyring = key_ref_to_ptr(keyring_ref); + + key_check(keyring); + + key_ref = ERR_PTR(-ENOTDIR); + if (keyring->type != &key_type_keyring) + goto error_2; + + down_write(&keyring->sem); + + /* if we're going to allocate a new key, we're going to have + * to modify the keyring */ + ret = key_permission(keyring_ref, KEY_WRITE); + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error_3; + } + + /* if it's possible to update this type of key, search for an existing + * key of the same type and description in the destination keyring and + * update that instead if possible + */ + if (ktype->update) { + key_ref = __keyring_search_one(keyring_ref, ktype, description, + 0); + if (!IS_ERR(key_ref)) + goto found_matching_key; + } + + /* if the client doesn't provide, decide on the permissions we want */ + if (perm == KEY_PERM_UNDEF) { + perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR; + perm |= KEY_USR_VIEW | KEY_USR_SEARCH | KEY_USR_LINK | KEY_USR_SETATTR; + + if (ktype->read) + perm |= KEY_POS_READ | 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, + current, perm, flags); + if (IS_ERR(key)) { + key_ref = ERR_CAST(key); + goto error_3; + } + + /* instantiate it and link it into the target keyring */ + ret = __key_instantiate_and_link(key, payload, plen, keyring, NULL); + if (ret < 0) { + key_put(key); + key_ref = ERR_PTR(ret); + goto error_3; + } + + key_ref = make_key_ref(key, is_key_possessed(keyring_ref)); + + error_3: + up_write(&keyring->sem); + error_2: + key_type_put(ktype); + error: + return key_ref; + + found_matching_key: + /* we found a matching key, so we're going to try to update it + * - we can drop the locks first as we have the key pinned + */ + up_write(&keyring->sem); + key_type_put(ktype); + + key_ref = __key_update(key_ref, payload, plen); + goto error; + +} /* end key_create_or_update() */ + +EXPORT_SYMBOL(key_create_or_update); + +/*****************************************************************************/ +/* + * update a key + */ +int key_update(key_ref_t key_ref, const void *payload, size_t plen) +{ + struct key *key = key_ref_to_ptr(key_ref); + int ret; + + key_check(key); + + /* the key must be writable */ + ret = key_permission(key_ref, KEY_WRITE); + if (ret < 0) + 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 */ + clear_bit(KEY_FLAG_NEGATIVE, &key->flags); + + up_write(&key->sem); + } + + error: + return ret; + +} /* end key_update() */ + +EXPORT_SYMBOL(key_update); + +/*****************************************************************************/ +/* + * 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 + * - we tell lockdep that we might nest because we might be revoking an + * authorisation key whilst holding the sem on a key we've just + * instantiated + */ + down_write_nested(&key->sem, 1); + if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags) && + key->type->revoke) + key->type->revoke(key); + + 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); + + /* mark all the keys of this type dead */ + 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) + key->type = &key_type_dead; + } + + spin_unlock(&key_serial_lock); + + /* make sure everyone revalidates their keys */ + synchronize_rcu(); + + /* we should now be able to destroy the payloads of all the keys of + * this type with impunity */ + 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) { + if (ktype->destroy) + ktype->destroy(key); + memset(&key->payload, KEY_DESTROY, 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); + + /* 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); + +} /* end key_init() */ diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c new file mode 100644 index 0000000..3c0f421 --- /dev/null +++ b/security/keys/keyctl.c @@ -0,0 +1,1241 @@ +/* keyctl.c: userspace keyctl operations + * + * Copyright (C) 2004-5 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/capability.h> +#include <linux/string.h> +#include <linux/err.h> +#include <linux/vmalloc.h> +#include <linux/security.h> +#include <asm/uaccess.h> +#include "internal.h" + +static int key_get_type_from_user(char *type, + const char __user *_type, + unsigned len) +{ + int ret; + + ret = strncpy_from_user(type, _type, len); + + if (ret < 0) + return -EFAULT; + + if (ret == 0 || ret >= len) + return -EINVAL; + + if (type[0] == '.') + return -EPERM; + + type[len - 1] = '\0'; + + return 0; +} + +/*****************************************************************************/ +/* + * 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() + */ +SYSCALL_DEFINE5(add_key, const char __user *, _type, + const char __user *, _description, + const void __user *, _payload, + size_t, plen, + key_serial_t, ringid) +{ + key_ref_t keyring_ref, key_ref; + char type[32], *description; + void *payload; + long ret; + bool vm; + + ret = -EINVAL; + if (plen > 1024 * 1024 - 1) + goto error; + + /* draw all the data into kernel space */ + ret = key_get_type_from_user(type, _type, sizeof(type)); + if (ret < 0) + goto error; + + description = strndup_user(_description, PAGE_SIZE); + if (IS_ERR(description)) { + ret = PTR_ERR(description); + goto error; + } + + /* pull the payload in if one was supplied */ + payload = NULL; + + vm = false; + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) { + if (plen <= PAGE_SIZE) + goto error2; + vm = true; + payload = vmalloc(plen); + 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_ref = lookup_user_key(NULL, ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error3; + } + + /* create or update the requested key and add it to the target + * keyring */ + key_ref = key_create_or_update(keyring_ref, type, description, + payload, plen, KEY_PERM_UNDEF, + KEY_ALLOC_IN_QUOTA); + if (!IS_ERR(key_ref)) { + ret = key_ref_to_ptr(key_ref)->serial; + key_ref_put(key_ref); + } + else { + ret = PTR_ERR(key_ref); + } + + key_ref_put(keyring_ref); + error3: + if (!vm) + kfree(payload); + else + vfree(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() + */ +SYSCALL_DEFINE4(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; + key_ref_t dest_ref; + size_t callout_len; + char type[32], *description, *callout_info; + long ret; + + /* pull the type into kernel space */ + ret = key_get_type_from_user(type, _type, sizeof(type)); + if (ret < 0) + goto error; + + /* pull the description into kernel space */ + description = strndup_user(_description, PAGE_SIZE); + if (IS_ERR(description)) { + ret = PTR_ERR(description); + goto error; + } + + /* pull the callout info into kernel space */ + callout_info = NULL; + callout_len = 0; + if (_callout_info) { + callout_info = strndup_user(_callout_info, PAGE_SIZE); + if (IS_ERR(callout_info)) { + ret = PTR_ERR(callout_info); + goto error2; + } + callout_len = strlen(callout_info); + } + + /* get the destination keyring if specified */ + dest_ref = NULL; + if (destringid) { + dest_ref = lookup_user_key(NULL, destringid, 1, 0, KEY_WRITE); + if (IS_ERR(dest_ref)) { + ret = PTR_ERR(dest_ref); + 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_and_link(ktype, description, callout_info, + callout_len, NULL, key_ref_to_ptr(dest_ref), + KEY_ALLOC_IN_QUOTA); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error5; + } + + ret = key->serial; + + key_put(key); + error5: + key_type_put(ktype); + error4: + key_ref_put(dest_ref); + 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) +{ + key_ref_t key_ref; + long ret; + + key_ref = lookup_user_key(NULL, id, create, 0, KEY_SEARCH); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + + ret = key_ref_to_ptr(key_ref)->serial; + key_ref_put(key_ref); + 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 ret; + + /* fetch the name from userspace */ + name = NULL; + if (_name) { + name = strndup_user(_name, PAGE_SIZE); + if (IS_ERR(name)) { + ret = PTR_ERR(name); + goto error; + } + } + + /* join the session */ + ret = join_session_keyring(name); + 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) +{ + key_ref_t key_ref; + 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_ref = lookup_user_key(NULL, id, 0, 0, KEY_WRITE); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error2; + } + + /* update the key */ + ret = key_update(key_ref, payload, plen); + + key_ref_put(key_ref); + 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) +{ + key_ref_t key_ref; + long ret; + + key_ref = lookup_user_key(NULL, id, 0, 0, KEY_WRITE); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + + key_revoke(key_ref_to_ptr(key_ref)); + ret = 0; + + key_ref_put(key_ref); + error: + return ret; + +} /* 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) +{ + key_ref_t keyring_ref; + long ret; + + keyring_ref = lookup_user_key(NULL, ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error; + } + + ret = keyring_clear(key_ref_to_ptr(keyring_ref)); + + key_ref_put(keyring_ref); + 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) +{ + key_ref_t keyring_ref, key_ref; + long ret; + + keyring_ref = lookup_user_key(NULL, ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error; + } + + key_ref = lookup_user_key(NULL, id, 1, 0, KEY_LINK); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error2; + } + + ret = key_link(key_ref_to_ptr(keyring_ref), key_ref_to_ptr(key_ref)); + + key_ref_put(key_ref); + error2: + key_ref_put(keyring_ref); + 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) +{ + key_ref_t keyring_ref, key_ref; + long ret; + + keyring_ref = lookup_user_key(NULL, ringid, 0, 0, KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error; + } + + key_ref = lookup_user_key(NULL, id, 0, 0, 0); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error2; + } + + ret = key_unlink(key_ref_to_ptr(keyring_ref), key_ref_to_ptr(key_ref)); + + key_ref_put(key_ref); + error2: + key_ref_put(keyring_ref); + 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, *instkey; + key_ref_t key_ref; + char *tmpbuf; + long ret; + + key_ref = lookup_user_key(NULL, keyid, 0, 1, KEY_VIEW); + if (IS_ERR(key_ref)) { + /* viewing a key under construction is permitted if we have the + * authorisation token handy */ + if (PTR_ERR(key_ref) == -EACCES) { + instkey = key_get_instantiation_authkey(keyid); + if (!IS_ERR(instkey)) { + key_put(instkey); + key_ref = lookup_user_key(NULL, keyid, + 0, 1, 0); + if (!IS_ERR(key_ref)) + goto okay; + } + } + + ret = PTR_ERR(key_ref); + goto error; + } + +okay: + /* calculate how much description we're going to return */ + ret = -ENOMEM; + tmpbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!tmpbuf) + goto error2; + + key = key_ref_to_ptr(key_ref); + + ret = snprintf(tmpbuf, PAGE_SIZE - 1, + "%s;%d;%d;%08x;%s", + key_ref_to_ptr(key_ref)->type->name, + key_ref_to_ptr(key_ref)->uid, + key_ref_to_ptr(key_ref)->gid, + key_ref_to_ptr(key_ref)->perm, + key_ref_to_ptr(key_ref)->description ? + key_ref_to_ptr(key_ref)->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_ref_put(key_ref); + 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; + key_ref_t keyring_ref, key_ref, dest_ref; + char type[32], *description; + long ret; + + /* pull the type and description into kernel space */ + ret = key_get_type_from_user(type, _type, sizeof(type)); + if (ret < 0) + goto error; + + description = strndup_user(_description, PAGE_SIZE); + if (IS_ERR(description)) { + ret = PTR_ERR(description); + goto error; + } + + /* get the keyring at which to begin the search */ + keyring_ref = lookup_user_key(NULL, ringid, 0, 0, KEY_SEARCH); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error2; + } + + /* get the destination keyring if specified */ + dest_ref = NULL; + if (destringid) { + dest_ref = lookup_user_key(NULL, destringid, 1, 0, KEY_WRITE); + if (IS_ERR(dest_ref)) { + ret = PTR_ERR(dest_ref); + 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_ref = keyring_search(keyring_ref, ktype, description); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + + /* 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_ref) { + ret = key_permission(key_ref, KEY_LINK); + if (ret < 0) + goto error6; + + ret = key_link(key_ref_to_ptr(dest_ref), key_ref_to_ptr(key_ref)); + if (ret < 0) + goto error6; + } + + ret = key_ref_to_ptr(key_ref)->serial; + + error6: + key_ref_put(key_ref); + error5: + key_type_put(ktype); + error4: + key_ref_put(dest_ref); + error3: + key_ref_put(keyring_ref); + error2: + kfree(description); + error: + return ret; + +} /* end keyctl_keyring_search() */ + +/*****************************************************************************/ +/* + * 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; + key_ref_t key_ref; + long ret; + + /* find the key first */ + key_ref = lookup_user_key(NULL, keyid, 0, 0, 0); + if (IS_ERR(key_ref)) { + ret = -ENOKEY; + goto error; + } + + key = key_ref_to_ptr(key_ref); + + /* see if we can read it directly */ + ret = key_permission(key_ref, KEY_READ); + if (ret == 0) + goto can_read_key; + if (ret != -EACCES) + goto error; + + /* we can't; see if it's searchable from this process's keyrings + * - we automatically take account of the fact that it may be + * dangling off an instantiation key + */ + if (!is_key_possessed(key_ref)) { + ret = -EACCES; + goto error2; + } + + /* the key is probably readable - now try to read it */ + 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_user *newowner, *zapowner = NULL; + struct key *key; + key_ref_t key_ref; + long ret; + + ret = 0; + if (uid == (uid_t) -1 && gid == (gid_t) -1) + goto error; + + key_ref = lookup_user_key(NULL, id, 1, 1, KEY_SETATTR); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + + key = key_ref_to_ptr(key_ref); + + /* make the changes with the locks held to prevent chown/chown races */ + ret = -EACCES; + down_write(&key->sem); + + 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 error_put; + + /* 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 error_put; + } + + /* change the UID */ + if (uid != (uid_t) -1 && uid != key->uid) { + ret = -ENOMEM; + newowner = key_user_lookup(uid); + if (!newowner) + goto error_put; + + /* transfer the quota burden to the new user */ + if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + unsigned maxkeys = (uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + spin_lock(&newowner->lock); + if (newowner->qnkeys + 1 >= maxkeys || + newowner->qnbytes + key->quotalen >= maxbytes || + newowner->qnbytes + key->quotalen < + newowner->qnbytes) + goto quota_overrun; + + newowner->qnkeys++; + newowner->qnbytes += key->quotalen; + spin_unlock(&newowner->lock); + + spin_lock(&key->user->lock); + key->user->qnkeys--; + key->user->qnbytes -= key->quotalen; + spin_unlock(&key->user->lock); + } + + atomic_dec(&key->user->nkeys); + atomic_inc(&newowner->nkeys); + + if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) { + atomic_dec(&key->user->nikeys); + atomic_inc(&newowner->nikeys); + } + + zapowner = key->user; + key->user = newowner; + key->uid = uid; + } + + /* change the GID */ + if (gid != (gid_t) -1) + key->gid = gid; + + ret = 0; + +error_put: + up_write(&key->sem); + key_put(key); + if (zapowner) + key_user_put(zapowner); +error: + return ret; + +quota_overrun: + spin_unlock(&newowner->lock); + zapowner = newowner; + ret = -EDQUOT; + goto error_put; + +} /* 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; + key_ref_t key_ref; + long ret; + + ret = -EINVAL; + if (perm & ~(KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL)) + goto error; + + key_ref = lookup_user_key(NULL, id, 1, 1, KEY_SETATTR); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + + key = key_ref_to_ptr(key_ref); + + /* make the changes with the locks held to prevent chown/chmod races */ + ret = -EACCES; + down_write(&key->sem); + + /* if we're not the sysadmin, we can only change a key that we own */ + if (capable(CAP_SYS_ADMIN) || key->uid == current->fsuid) { + key->perm = perm; + ret = 0; + } + + 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 request_key_auth *rka; + struct key *instkey; + key_ref_t keyring_ref; + void *payload; + long ret; + bool vm = false; + + ret = -EINVAL; + if (plen > 1024 * 1024 - 1) + goto error; + + /* the appropriate instantiation authorisation key must have been + * assumed before calling this */ + ret = -EPERM; + instkey = current->request_key_auth; + if (!instkey) + goto error; + + rka = instkey->payload.data; + if (rka->target_key->serial != id) + goto error; + + /* pull the payload in if one was supplied */ + payload = NULL; + + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) { + if (plen <= PAGE_SIZE) + goto error; + vm = true; + payload = vmalloc(plen); + if (!payload) + goto error; + } + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error2; + } + + /* find the destination keyring amongst those belonging to the + * requesting task */ + keyring_ref = NULL; + if (ringid) { + keyring_ref = lookup_user_key(rka->context, ringid, 1, 0, + KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error2; + } + } + + /* instantiate the key and link it into a keyring */ + ret = key_instantiate_and_link(rka->target_key, payload, plen, + key_ref_to_ptr(keyring_ref), instkey); + + key_ref_put(keyring_ref); + + /* discard the assumed authority if it's just been disabled by + * instantiation of the key */ + if (ret == 0) { + key_put(current->request_key_auth); + current->request_key_auth = NULL; + } + +error2: + if (!vm) + kfree(payload); + else + vfree(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 request_key_auth *rka; + struct key *instkey; + key_ref_t keyring_ref; + long ret; + + /* the appropriate instantiation authorisation key must have been + * assumed before calling this */ + ret = -EPERM; + instkey = current->request_key_auth; + if (!instkey) + goto error; + + rka = instkey->payload.data; + if (rka->target_key->serial != id) + goto error; + + /* find the destination keyring if present (which must also be + * writable) */ + keyring_ref = NULL; + if (ringid) { + keyring_ref = lookup_user_key(NULL, ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring_ref)) { + ret = PTR_ERR(keyring_ref); + goto error; + } + } + + /* instantiate the key and link it into a keyring */ + ret = key_negate_and_link(rka->target_key, timeout, + key_ref_to_ptr(keyring_ref), instkey); + + key_ref_put(keyring_ref); + + /* discard the assumed authority if it's just been disabled by + * instantiation of the key */ + if (ret == 0) { + key_put(current->request_key_auth); + current->request_key_auth = NULL; + } + +error: + return ret; + +} /* end keyctl_negate_key() */ + +/*****************************************************************************/ +/* + * set the default keyring in which request_key() will cache keys + * - return the old setting + */ +long keyctl_set_reqkey_keyring(int reqkey_defl) +{ + int ret; + + switch (reqkey_defl) { + case KEY_REQKEY_DEFL_THREAD_KEYRING: + ret = install_thread_keyring(current); + if (ret < 0) + return ret; + goto set; + + case KEY_REQKEY_DEFL_PROCESS_KEYRING: + ret = install_process_keyring(current); + if (ret < 0) + return ret; + + case KEY_REQKEY_DEFL_DEFAULT: + case KEY_REQKEY_DEFL_SESSION_KEYRING: + case KEY_REQKEY_DEFL_USER_KEYRING: + case KEY_REQKEY_DEFL_USER_SESSION_KEYRING: + set: + current->jit_keyring = reqkey_defl; + + case KEY_REQKEY_DEFL_NO_CHANGE: + return current->jit_keyring; + + case KEY_REQKEY_DEFL_GROUP_KEYRING: + default: + return -EINVAL; + } + +} /* end keyctl_set_reqkey_keyring() */ + +/*****************************************************************************/ +/* + * set or clear the timeout for a key + */ +long keyctl_set_timeout(key_serial_t id, unsigned timeout) +{ + struct timespec now; + struct key *key; + key_ref_t key_ref; + time_t expiry; + long ret; + + key_ref = lookup_user_key(NULL, id, 1, 1, KEY_SETATTR); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + + key = key_ref_to_ptr(key_ref); + + /* make the changes with the locks held to prevent races */ + down_write(&key->sem); + + expiry = 0; + if (timeout > 0) { + now = current_kernel_time(); + expiry = now.tv_sec + timeout; + } + + key->expiry = expiry; + + up_write(&key->sem); + key_put(key); + + ret = 0; +error: + return ret; + +} /* end keyctl_set_timeout() */ + +/*****************************************************************************/ +/* + * assume the authority to instantiate the specified key + */ +long keyctl_assume_authority(key_serial_t id) +{ + struct key *authkey; + long ret; + + /* special key IDs aren't permitted */ + ret = -EINVAL; + if (id < 0) + goto error; + + /* we divest ourselves of authority if given an ID of 0 */ + if (id == 0) { + key_put(current->request_key_auth); + current->request_key_auth = NULL; + ret = 0; + goto error; + } + + /* attempt to assume the authority temporarily granted to us whilst we + * instantiate the specified key + * - the authorisation key must be in the current task's keyrings + * somewhere + */ + authkey = key_get_instantiation_authkey(id); + if (IS_ERR(authkey)) { + ret = PTR_ERR(authkey); + goto error; + } + + key_put(current->request_key_auth); + current->request_key_auth = authkey; + ret = authkey->serial; + +error: + return ret; + +} /* end keyctl_assume_authority() */ + +/* + * get the security label of a key + * - the key must grant us 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 information available, + * irrespective of how much we may have copied (including the terminal NUL) + * - implements keyctl(KEYCTL_GET_SECURITY) + */ +long keyctl_get_security(key_serial_t keyid, + char __user *buffer, + size_t buflen) +{ + struct key *key, *instkey; + key_ref_t key_ref; + char *context; + long ret; + + key_ref = lookup_user_key(NULL, keyid, 0, 1, KEY_VIEW); + if (IS_ERR(key_ref)) { + if (PTR_ERR(key_ref) != -EACCES) + return PTR_ERR(key_ref); + + /* viewing a key under construction is also permitted if we + * have the authorisation token handy */ + instkey = key_get_instantiation_authkey(keyid); + if (IS_ERR(instkey)) + return PTR_ERR(key_ref); + key_put(instkey); + + key_ref = lookup_user_key(NULL, keyid, 0, 1, 0); + if (IS_ERR(key_ref)) + return PTR_ERR(key_ref); + } + + key = key_ref_to_ptr(key_ref); + ret = security_key_getsecurity(key, &context); + if (ret == 0) { + /* if no information was returned, give userspace an empty + * string */ + ret = 1; + if (buffer && buflen > 0 && + copy_to_user(buffer, "", 1) != 0) + ret = -EFAULT; + } else if (ret > 0) { + /* return as much data as there's room for */ + if (buffer && buflen > 0) { + if (buflen > ret) + buflen = ret; + + if (copy_to_user(buffer, context, buflen) != 0) + ret = -EFAULT; + } + + kfree(context); + } + + key_ref_put(key_ref); + return ret; +} + +/*****************************************************************************/ +/* + * the key control system call + */ +SYSCALL_DEFINE5(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); + + case KEYCTL_SET_REQKEY_KEYRING: + return keyctl_set_reqkey_keyring(arg2); + + case KEYCTL_SET_TIMEOUT: + return keyctl_set_timeout((key_serial_t) arg2, + (unsigned) arg3); + + case KEYCTL_ASSUME_AUTHORITY: + return keyctl_assume_authority((key_serial_t) arg2); + + case KEYCTL_GET_SECURITY: + return keyctl_get_security((key_serial_t) arg2, + (char *) arg3, + (size_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 0000000..a9ab8af --- /dev/null +++ b/security/keys/keyring.c @@ -0,0 +1,998 @@ +/* Keyring handling + * + * Copyright (C) 2004-2005, 2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/security.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_match(const struct key *keyring, const void *criterion); +static void keyring_revoke(struct key *keyring); +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, + .match = keyring_match, + .revoke = keyring_revoke, + .destroy = keyring_destroy, + .describe = keyring_describe, + .read = keyring_read, +}; + +EXPORT_SYMBOL(key_type_keyring); + +/* + * semaphore to serialise link/link calls to prevent two link calls in parallel + * introducing a cycle + */ +static DECLARE_RWSEM(keyring_serialise_link_sem); + +/*****************************************************************************/ +/* + * publish the name of a keyring so that it can be found by name (if it has + * one) + */ +static 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() */ + +/*****************************************************************************/ +/* + * 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); + + if (keyring->type_data.link.next != NULL && + !list_empty(&keyring->type_data.link)) + list_del(&keyring->type_data.link); + + write_unlock(&keyring_name_lock); + } + + klist = rcu_dereference(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]"); + } + + rcu_read_lock(); + klist = rcu_dereference(keyring->payload.subscriptions); + if (klist) + seq_printf(m, ": %u/%u", klist->nkeys, klist->maxkeys); + else + seq_puts(m, ": empty"); + rcu_read_unlock(); + +} /* end keyring_describe() */ + +/*****************************************************************************/ +/* + * read a list of key IDs from the keyring's contents + * - the keyring's semaphore is read-locked + */ +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 = rcu_dereference(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, + struct task_struct *ctx, unsigned long flags, + struct key *dest) +{ + struct key *keyring; + int ret; + + keyring = key_alloc(&key_type_keyring, description, + uid, gid, ctx, + (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL, + flags); + + if (!IS_ERR(keyring)) { + ret = key_instantiate_and_link(keyring, NULL, 0, dest, NULL); + 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 rely on RCU to prevent the keyring lists from disappearing on us + * - we return -EAGAIN if we didn't find any matching key + * - we return -ENOKEY if we only found negative matching keys + * - we propagate the possession attribute from the keyring ref to the key ref + */ +key_ref_t keyring_search_aux(key_ref_t keyring_ref, + struct task_struct *context, + struct key_type *type, + const void *description, + key_match_func_t match) +{ + struct { + struct keyring_list *keylist; + int kix; + } stack[KEYRING_SEARCH_MAX_DEPTH]; + + struct keyring_list *keylist; + struct timespec now; + unsigned long possessed, kflags; + struct key *keyring, *key; + key_ref_t key_ref; + long err; + int sp, kix; + + keyring = key_ref_to_ptr(keyring_ref); + possessed = is_key_possessed(keyring_ref); + key_check(keyring); + + /* top keyring must have search permission to begin the search */ + err = key_task_permission(keyring_ref, context, KEY_SEARCH); + if (err < 0) { + key_ref = ERR_PTR(err); + goto error; + } + + key_ref = ERR_PTR(-ENOTDIR); + if (keyring->type != &key_type_keyring) + goto error; + + rcu_read_lock(); + + now = current_kernel_time(); + err = -EAGAIN; + sp = 0; + + /* firstly we should check to see if this top-level keyring is what we + * are looking for */ + key_ref = ERR_PTR(-EAGAIN); + kflags = keyring->flags; + if (keyring->type == type && match(keyring, description)) { + key = keyring; + + /* check it isn't negative and hasn't expired or been + * revoked */ + if (kflags & (1 << KEY_FLAG_REVOKED)) + goto error_2; + if (key->expiry && now.tv_sec >= key->expiry) + goto error_2; + key_ref = ERR_PTR(-ENOKEY); + if (kflags & (1 << KEY_FLAG_NEGATIVE)) + goto error_2; + goto found; + } + + /* otherwise, the top keyring must not be revoked, expired, or + * negatively instantiated if we are to search it */ + key_ref = ERR_PTR(-EAGAIN); + if (kflags & ((1 << KEY_FLAG_REVOKED) | (1 << KEY_FLAG_NEGATIVE)) || + (keyring->expiry && now.tv_sec >= keyring->expiry)) + goto error_2; + + /* start processing a new keyring */ +descend: + if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + goto not_this_keyring; + + keylist = rcu_dereference(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]; + kflags = key->flags; + + /* ignore keys not of this type */ + if (key->type != type) + continue; + + /* skip revoked keys and expired keys */ + if (kflags & (1 << 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_task_permission(make_key_ref(key, possessed), + context, KEY_SEARCH) < 0) + continue; + + /* we set a different error code if we pass a negative key */ + if (kflags & (1 << KEY_FLAG_NEGATIVE)) { + err = -ENOKEY; + continue; + } + + goto found; + } + + /* search through the keyrings nested in this one */ + kix = 0; +ascend: + for (; kix < keylist->nkeys; kix++) { + key = keylist->keys[kix]; + if (key->type != &key_type_keyring) + continue; + + /* recursively search nested keyrings + * - only search keyrings for which we have search permission + */ + if (sp >= KEYRING_SEARCH_MAX_DEPTH) + continue; + + if (key_task_permission(make_key_ref(key, possessed), + context, KEY_SEARCH) < 0) + continue; + + /* stack the current position */ + stack[sp].keylist = keylist; + stack[sp].kix = kix; + sp++; + + /* begin again with the new keyring */ + keyring = key; + goto descend; + } + + /* the keyring we're looking at was disqualified or didn't contain a + * matching key */ +not_this_keyring: + if (sp > 0) { + /* resume the processing of a keyring higher up in the tree */ + sp--; + keylist = stack[sp].keylist; + kix = stack[sp].kix + 1; + goto ascend; + } + + key_ref = ERR_PTR(err); + goto error_2; + + /* we found a viable match */ +found: + atomic_inc(&key->usage); + key_check(key); + key_ref = make_key_ref(key, possessed); +error_2: + rcu_read_unlock(); +error: + return key_ref; + +} /* 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 + */ +key_ref_t keyring_search(key_ref_t keyring, + struct key_type *type, + const char *description) +{ + if (!type->match) + return ERR_PTR(-ENOKEY); + + return keyring_search_aux(keyring, current, + type, description, type->match); + +} /* end keyring_search() */ + +EXPORT_SYMBOL(keyring_search); + +/*****************************************************************************/ +/* + * search the given keyring only (no recursion) + * - keyring must be locked by caller + * - caller must guarantee that the keyring is a keyring + */ +key_ref_t __keyring_search_one(key_ref_t keyring_ref, + const struct key_type *ktype, + const char *description, + key_perm_t perm) +{ + struct keyring_list *klist; + unsigned long possessed; + struct key *keyring, *key; + int loop; + + keyring = key_ref_to_ptr(keyring_ref); + possessed = is_key_possessed(keyring_ref); + + rcu_read_lock(); + + klist = rcu_dereference(keyring->payload.subscriptions); + if (klist) { + for (loop = 0; loop < klist->nkeys; loop++) { + key = klist->keys[loop]; + + if (key->type == ktype && + (!key->type->match || + key->type->match(key, description)) && + key_permission(make_key_ref(key, possessed), + perm) == 0 && + !test_bit(KEY_FLAG_REVOKED, &key->flags) + ) + goto found; + } + } + + rcu_read_unlock(); + return ERR_PTR(-ENOKEY); + + found: + atomic_inc(&key->usage); + rcu_read_unlock(); + return make_key_ref(key, possessed); + +} /* end __keyring_search_one() */ + +/*****************************************************************************/ +/* + * find a keyring with the specified name + * - all named keyrings are searched + * - normally only finds keyrings with search permission for the current process + */ +struct key *find_keyring_by_name(const char *name, bool skip_perm_check) +{ + 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 (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + continue; + + if (strcmp(keyring->description, name) != 0) + continue; + + if (!skip_perm_check && + key_permission(make_key_ref(keyring, 0), + KEY_SEARCH) < 0) + 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 keyring_list *keylist; + int kix; + } stack[KEYRING_SEARCH_MAX_DEPTH]; + + struct keyring_list *keylist; + struct key *subtree, *key; + int sp, kix, ret; + + rcu_read_lock(); + + ret = -EDEADLK; + if (A == B) + goto cycle_detected; + + subtree = B; + sp = 0; + + /* start processing a new keyring */ + descend: + if (test_bit(KEY_FLAG_REVOKED, &subtree->flags)) + goto not_this_keyring; + + keylist = rcu_dereference(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].keylist = keylist; + 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: + if (sp > 0) { + /* resume the checking of a keyring higher up in the tree */ + sp--; + keylist = stack[sp].keylist; + kix = stack[sp].kix + 1; + goto ascend; + } + + ret = 0; /* no cycles detected */ + + error: + rcu_read_unlock(); + return ret; + + too_deep: + ret = -ELOOP; + goto error; + + cycle_detected: + ret = -EDEADLK; + goto error; + +} /* end keyring_detect_cycle() */ + +/*****************************************************************************/ +/* + * dispose of a keyring list after the RCU grace period + */ +static void keyring_link_rcu_disposal(struct rcu_head *rcu) +{ + struct keyring_list *klist = + container_of(rcu, struct keyring_list, rcu); + + kfree(klist); + +} /* end keyring_link_rcu_disposal() */ + +/*****************************************************************************/ +/* + * dispose of a keyring list after the RCU grace period, freeing the unlinked + * key + */ +static void keyring_unlink_rcu_disposal(struct rcu_head *rcu) +{ + struct keyring_list *klist = + container_of(rcu, struct keyring_list, rcu); + + key_put(klist->keys[klist->delkey]); + kfree(klist); + +} /* end keyring_unlink_rcu_disposal() */ + +/*****************************************************************************/ +/* + * link a key into to a keyring + * - must be called with the keyring's semaphore write-locked + * - discard already extant link to matching key if there is one + */ +int __key_link(struct key *keyring, struct key *key) +{ + struct keyring_list *klist, *nklist; + unsigned max; + size_t size; + int loop, ret; + + ret = -EKEYREVOKED; + if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + 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; + } + + /* see if there's a matching key we can displace */ + klist = keyring->payload.subscriptions; + + if (klist && klist->nkeys > 0) { + struct key_type *type = key->type; + + for (loop = klist->nkeys - 1; loop >= 0; loop--) { + if (klist->keys[loop]->type == type && + strcmp(klist->keys[loop]->description, + key->description) == 0 + ) { + /* found a match - replace with new key */ + size = sizeof(struct key *) * klist->maxkeys; + size += sizeof(*klist); + BUG_ON(size > PAGE_SIZE); + + ret = -ENOMEM; + nklist = kmemdup(klist, size, GFP_KERNEL); + if (!nklist) + goto error2; + + /* replace matched key */ + atomic_inc(&key->usage); + nklist->keys[loop] = key; + + rcu_assign_pointer( + keyring->payload.subscriptions, + nklist); + + /* dispose of the old keyring list and the + * displaced key */ + klist->delkey = loop; + call_rcu(&klist->rcu, + keyring_unlink_rcu_disposal); + + goto done; + } + } + } + + /* 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); + + klist->keys[klist->nkeys] = key; + smp_wmb(); + klist->nkeys++; + smp_wmb(); + } + else { + /* grow the key list */ + max = 4; + if (klist) + max += klist->maxkeys; + + ret = -ENFILE; + if (max > 65535) + goto error3; + size = sizeof(*klist) + sizeof(struct 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); + nklist->keys[nklist->nkeys++] = key; + + rcu_assign_pointer(keyring->payload.subscriptions, nklist); + + /* dispose of the old keyring list */ + if (klist) + call_rcu(&klist->rcu, keyring_link_rcu_disposal); + } + +done: + 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, *nklist; + 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: + /* we need to copy the key list for RCU purposes */ + nklist = kmalloc(sizeof(*klist) + + sizeof(struct key *) * klist->maxkeys, + GFP_KERNEL); + if (!nklist) + goto nomem; + nklist->maxkeys = klist->maxkeys; + nklist->nkeys = klist->nkeys - 1; + + if (loop > 0) + memcpy(&nklist->keys[0], + &klist->keys[0], + loop * sizeof(struct key *)); + + if (loop < nklist->nkeys) + memcpy(&nklist->keys[loop], + &klist->keys[loop + 1], + (nklist->nkeys - loop) * sizeof(struct key *)); + + /* adjust the user's quota */ + key_payload_reserve(keyring, + keyring->datalen - KEYQUOTA_LINK_BYTES); + + rcu_assign_pointer(keyring->payload.subscriptions, nklist); + + up_write(&keyring->sem); + + /* schedule for later cleanup */ + klist->delkey = loop; + call_rcu(&klist->rcu, keyring_unlink_rcu_disposal); + + ret = 0; + +error: + return ret; +nomem: + ret = -ENOMEM; + up_write(&keyring->sem); + goto error; + +} /* end key_unlink() */ + +EXPORT_SYMBOL(key_unlink); + +/*****************************************************************************/ +/* + * dispose of a keyring list after the RCU grace period, releasing the keys it + * links to + */ +static void keyring_clear_rcu_disposal(struct rcu_head *rcu) +{ + struct keyring_list *klist; + int loop; + + klist = container_of(rcu, struct keyring_list, rcu); + + for (loop = klist->nkeys - 1; loop >= 0; loop--) + key_put(klist->keys[loop]); + + kfree(klist); + +} /* end keyring_clear_rcu_disposal() */ + +/*****************************************************************************/ +/* + * clear the specified process keyring + * - implements keyctl(KEYCTL_CLEAR) + */ +int keyring_clear(struct key *keyring) +{ + struct keyring_list *klist; + int 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)); + + rcu_assign_pointer(keyring->payload.subscriptions, + NULL); + } + + up_write(&keyring->sem); + + /* free the keys after the locks have been dropped */ + if (klist) + call_rcu(&klist->rcu, keyring_clear_rcu_disposal); + + ret = 0; + } + + return ret; + +} /* end keyring_clear() */ + +EXPORT_SYMBOL(keyring_clear); + +/*****************************************************************************/ +/* + * dispose of the links from a revoked keyring + * - called with the key sem write-locked + */ +static void keyring_revoke(struct key *keyring) +{ + struct keyring_list *klist = keyring->payload.subscriptions; + + /* adjust the quota */ + key_payload_reserve(keyring, 0); + + if (klist) { + rcu_assign_pointer(keyring->payload.subscriptions, NULL); + call_rcu(&klist->rcu, keyring_clear_rcu_disposal); + } + +} /* end keyring_revoke() */ diff --git a/security/keys/permission.c b/security/keys/permission.c new file mode 100644 index 0000000..3b41f9b --- /dev/null +++ b/security/keys/permission.c @@ -0,0 +1,107 @@ +/* permission.c: key permission determination + * + * Copyright (C) 2005 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/security.h> +#include "internal.h" + +/*****************************************************************************/ +/* + * check to see whether permission is granted to use a key in the desired way, + * but permit the security modules to override + */ +int key_task_permission(const key_ref_t key_ref, + struct task_struct *context, + key_perm_t perm) +{ + struct key *key; + key_perm_t kperm; + int ret; + + key = key_ref_to_ptr(key_ref); + + /* use the second 8-bits of permissions for keys the caller owns */ + if (key->uid == context->fsuid) { + kperm = key->perm >> 16; + goto use_these_perms; + } + + /* use the third 8-bits of permissions for keys the caller has a group + * membership in common with */ + if (key->gid != -1 && key->perm & KEY_GRP_ALL) { + if (key->gid == context->fsgid) { + kperm = key->perm >> 8; + goto use_these_perms; + } + + task_lock(context); + ret = groups_search(context->group_info, key->gid); + task_unlock(context); + + if (ret) { + kperm = key->perm >> 8; + goto use_these_perms; + } + } + + /* otherwise use the least-significant 8-bits */ + kperm = key->perm; + +use_these_perms: + /* use the top 8-bits of permissions for keys the caller possesses + * - possessor permissions are additive with other permissions + */ + if (is_key_possessed(key_ref)) + kperm |= key->perm >> 24; + + kperm = kperm & perm & KEY_ALL; + + if (kperm != perm) + return -EACCES; + + /* let LSM be the final arbiter */ + return security_key_permission(key_ref, context, perm); + +} /* end key_task_permission() */ + +EXPORT_SYMBOL(key_task_permission); + +/*****************************************************************************/ +/* + * 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 (test_bit(KEY_FLAG_REVOKED, &key->flags) || + test_bit(KEY_FLAG_DEAD, &key->flags)) + 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/proc.c b/security/keys/proc.c new file mode 100644 index 0000000..f619170 --- /dev/null +++ b/security/keys/proc.c @@ -0,0 +1,262 @@ +/* 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 const struct seq_operations proc_keys_ops = { + .start = proc_keys_start, + .next = proc_keys_next, + .stop = proc_keys_stop, + .show = proc_keys_show, +}; + +static const 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 const 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 const 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 = proc_create("keys", 0, NULL, &proc_keys_fops); + if (!p) + panic("Cannot create /proc/keys\n"); +#endif + + p = proc_create("key-users", 0, NULL, &proc_key_users_fops); + if (!p) + panic("Cannot create /proc/key-users\n"); + + 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]; + int rc; + + /* check whether the current task is allowed to view the key (assuming + * non-possession) */ + rc = key_task_permission(make_key_ref(key, 0), current, KEY_VIEW); + if (rc < 0) + return 0; + + now = current_kernel_time(); + + rcu_read_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)); + } + +#define showflag(KEY, LETTER, FLAG) \ + (test_bit(FLAG, &(KEY)->flags) ? LETTER : '-') + + seq_printf(m, "%08x %c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ", + key->serial, + showflag(key, 'I', KEY_FLAG_INSTANTIATED), + showflag(key, 'R', KEY_FLAG_REVOKED), + showflag(key, 'D', KEY_FLAG_DEAD), + showflag(key, 'Q', KEY_FLAG_IN_QUOTA), + showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT), + showflag(key, 'N', KEY_FLAG_NEGATIVE), + atomic_read(&key->usage), + xbuf, + key->perm, + key->uid, + key->gid, + key->type->name); + +#undef showflag + + if (key->type->describe) + key->type->describe(key, m); + seq_putc(m, '\n'); + + rcu_read_unlock(); + + 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); + unsigned maxkeys = (user->uid == 0) ? + key_quota_root_maxkeys : key_quota_maxkeys; + unsigned maxbytes = (user->uid == 0) ? + key_quota_root_maxbytes : key_quota_maxbytes; + + 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, + maxkeys, + user->qnbytes, + maxbytes); + + return 0; + +} diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c new file mode 100644 index 0000000..45b240a --- /dev/null +++ b/security/keys/process_keys.c @@ -0,0 +1,799 @@ +/* Management of a process's keyrings + * + * Copyright (C) 2004-2005, 2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/keyctl.h> +#include <linux/fs.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <asm/uaccess.h> +#include "internal.h" + +/* session keyring create vs join semaphore */ +static DEFINE_MUTEX(key_session_mutex); + +/* user keyring creation semaphore */ +static DEFINE_MUTEX(key_user_keyring_mutex); + +/* the root user's tracking struct */ +struct key_user root_key_user = { + .usage = ATOMIC_INIT(3), + .cons_lock = __MUTEX_INITIALIZER(root_key_user.cons_lock), + .lock = __SPIN_LOCK_UNLOCKED(root_key_user.lock), + .nkeys = ATOMIC_INIT(2), + .nikeys = ATOMIC_INIT(2), + .uid = 0, +}; + +/*****************************************************************************/ +/* + * install user and user session keyrings for a particular UID + */ +int install_user_keyrings(struct task_struct *tsk) +{ + struct user_struct *user = tsk->user; + struct key *uid_keyring, *session_keyring; + char buf[20]; + int ret; + + kenter("%p{%u}", user, user->uid); + + if (user->uid_keyring) { + kleave(" = 0 [exist]"); + return 0; + } + + mutex_lock(&key_user_keyring_mutex); + ret = 0; + + if (!user->uid_keyring) { + /* get the UID-specific keyring + * - there may be one in existence already as it may have been + * pinned by a session, but the user_struct pointing to it + * may have been destroyed by setuid */ + sprintf(buf, "_uid.%u", user->uid); + + uid_keyring = find_keyring_by_name(buf, true); + if (IS_ERR(uid_keyring)) { + uid_keyring = keyring_alloc(buf, user->uid, (gid_t) -1, + tsk, KEY_ALLOC_IN_QUOTA, + NULL); + if (IS_ERR(uid_keyring)) { + ret = PTR_ERR(uid_keyring); + goto error; + } + } + + /* get a default session keyring (which might also exist + * already) */ + sprintf(buf, "_uid_ses.%u", user->uid); + + session_keyring = find_keyring_by_name(buf, true); + if (IS_ERR(session_keyring)) { + session_keyring = + keyring_alloc(buf, user->uid, (gid_t) -1, + tsk, KEY_ALLOC_IN_QUOTA, NULL); + if (IS_ERR(session_keyring)) { + ret = PTR_ERR(session_keyring); + goto error_release; + } + + /* we install a link from the user session keyring to + * the user keyring */ + ret = key_link(session_keyring, uid_keyring); + if (ret < 0) + goto error_release_both; + } + + /* install the keyrings */ + user->uid_keyring = uid_keyring; + user->session_keyring = session_keyring; + } + + mutex_unlock(&key_user_keyring_mutex); + kleave(" = 0"); + return 0; + +error_release_both: + key_put(session_keyring); +error_release: + key_put(uid_keyring); +error: + mutex_unlock(&key_user_keyring_mutex); + kleave(" = %d", ret); + return ret; +} + +/*****************************************************************************/ +/* + * 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, tsk, + KEY_ALLOC_QUOTA_OVERRUN, 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 + */ +int install_process_keyring(struct task_struct *tsk) +{ + struct key *keyring; + char buf[20]; + int ret; + + might_sleep(); + + if (!tsk->signal->process_keyring) { + sprintf(buf, "_pid.%u", tsk->tgid); + + keyring = keyring_alloc(buf, tsk->uid, tsk->gid, tsk, + KEY_ALLOC_QUOTA_OVERRUN, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + /* attach keyring */ + spin_lock_irq(&tsk->sighand->siglock); + if (!tsk->signal->process_keyring) { + tsk->signal->process_keyring = keyring; + keyring = NULL; + } + spin_unlock_irq(&tsk->sighand->siglock); + + 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]; + + might_sleep(); + + /* create an empty session keyring */ + if (!keyring) { + sprintf(buf, "_ses.%u", tsk->tgid); + + flags = KEY_ALLOC_QUOTA_OVERRUN; + if (tsk->signal->session_keyring) + flags = KEY_ALLOC_IN_QUOTA; + + keyring = keyring_alloc(buf, tsk->uid, tsk->gid, tsk, + flags, NULL); + if (IS_ERR(keyring)) + return PTR_ERR(keyring); + } + else { + atomic_inc(&keyring->usage); + } + + /* install the keyring */ + spin_lock_irq(&tsk->sighand->siglock); + old = tsk->signal->session_keyring; + rcu_assign_pointer(tsk->signal->session_keyring, keyring); + spin_unlock_irq(&tsk->sighand->siglock); + + /* we're using RCU on the pointer, but there's no point synchronising + * on it if it didn't previously point to anything */ + if (old) { + synchronize_rcu(); + key_put(old); + } + + return 0; + +} /* 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) +{ + 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 */ + rcu_read_lock(); + tsk->signal->session_keyring = + key_get(rcu_dereference(current->signal->session_keyring)); + rcu_read_unlock(); + + 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); + key_check(tsk->request_key_auth); + + /* no thread keyring yet */ + tsk->thread_keyring = NULL; + + /* copy the request_key() authorisation for this thread */ + key_get(tsk->request_key_auth); + + 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 per-thread keys upon thread exit + */ +void exit_keys(struct task_struct *tsk) +{ + key_put(tsk->thread_keyring); + key_put(tsk->request_key_auth); + +} /* end exit_keys() */ + +/*****************************************************************************/ +/* + * deal with execve() + */ +int exec_keys(struct task_struct *tsk) +{ + 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_irq(&tsk->sighand->siglock); + old = tsk->signal->process_keyring; + tsk->signal->process_keyring = NULL; + spin_unlock_irq(&tsk->sighand->siglock); + + 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); + tsk->thread_keyring->uid = tsk->fsuid; + 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); + tsk->thread_keyring->gid = tsk->fsgid; + 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 + */ +key_ref_t search_process_keyrings(struct key_type *type, + const void *description, + key_match_func_t match, + struct task_struct *context) +{ + struct request_key_auth *rka; + key_ref_t key_ref, ret, err; + + might_sleep(); + + /* 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_ref = NULL; + ret = NULL; + err = ERR_PTR(-EAGAIN); + + /* search the thread keyring first */ + if (context->thread_keyring) { + key_ref = keyring_search_aux( + make_key_ref(context->thread_keyring, 1), + context, type, description, match); + if (!IS_ERR(key_ref)) + goto found; + + switch (PTR_ERR(key_ref)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key_ref; + break; + default: + err = key_ref; + break; + } + } + + /* search the process keyring second */ + if (context->signal->process_keyring) { + key_ref = keyring_search_aux( + make_key_ref(context->signal->process_keyring, 1), + context, type, description, match); + if (!IS_ERR(key_ref)) + goto found; + + switch (PTR_ERR(key_ref)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key_ref; + break; + default: + err = key_ref; + break; + } + } + + /* search the session keyring */ + if (context->signal->session_keyring) { + rcu_read_lock(); + key_ref = keyring_search_aux( + make_key_ref(rcu_dereference( + context->signal->session_keyring), + 1), + context, type, description, match); + rcu_read_unlock(); + + if (!IS_ERR(key_ref)) + goto found; + + switch (PTR_ERR(key_ref)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key_ref; + break; + default: + err = key_ref; + break; + } + } + /* or search the user-session keyring */ + else if (context->user->session_keyring) { + key_ref = keyring_search_aux( + make_key_ref(context->user->session_keyring, 1), + context, type, description, match); + if (!IS_ERR(key_ref)) + goto found; + + switch (PTR_ERR(key_ref)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key_ref; + break; + default: + err = key_ref; + break; + } + } + + /* if this process has an instantiation authorisation key, then we also + * search the keyrings of the process mentioned there + * - we don't permit access to request_key auth keys via this method + */ + if (context->request_key_auth && + context == current && + type != &key_type_request_key_auth + ) { + /* defend against the auth key being revoked */ + down_read(&context->request_key_auth->sem); + + if (key_validate(context->request_key_auth) == 0) { + rka = context->request_key_auth->payload.data; + + key_ref = search_process_keyrings(type, description, + match, rka->context); + + up_read(&context->request_key_auth->sem); + + if (!IS_ERR(key_ref)) + goto found; + + switch (PTR_ERR(key_ref)) { + case -EAGAIN: /* no key */ + if (ret) + break; + case -ENOKEY: /* negative key */ + ret = key_ref; + break; + default: + err = key_ref; + break; + } + } else { + up_read(&context->request_key_auth->sem); + } + } + + /* no key - decide on the error we're going to go for */ + key_ref = ret ? ret : err; + +found: + return key_ref; + +} /* end search_process_keyrings() */ + +/*****************************************************************************/ +/* + * see if the key we're looking at is the target key + */ +static int lookup_user_key_possessed(const struct key *key, const void *target) +{ + return key == target; + +} /* end lookup_user_key_possessed() */ + +/*****************************************************************************/ +/* + * 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 + */ +key_ref_t lookup_user_key(struct task_struct *context, key_serial_t id, + int create, int partial, key_perm_t perm) +{ + key_ref_t key_ref, skey_ref; + struct key *key; + int ret; + + if (!context) + context = current; + + key_ref = ERR_PTR(-ENOKEY); + + switch (id) { + case KEY_SPEC_THREAD_KEYRING: + if (!context->thread_keyring) { + if (!create) + goto error; + + ret = install_thread_keyring(context); + if (ret < 0) { + key = ERR_PTR(ret); + goto error; + } + } + + key = context->thread_keyring; + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_PROCESS_KEYRING: + if (!context->signal->process_keyring) { + if (!create) + goto error; + + ret = install_process_keyring(context); + if (ret < 0) { + key = ERR_PTR(ret); + goto error; + } + } + + key = context->signal->process_keyring; + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_SESSION_KEYRING: + if (!context->signal->session_keyring) { + /* always install a session keyring upon access if one + * doesn't exist yet */ + ret = install_user_keyrings(context); + if (ret < 0) + goto error; + ret = install_session_keyring( + context, context->user->session_keyring); + if (ret < 0) + goto error; + } + + rcu_read_lock(); + key = rcu_dereference(context->signal->session_keyring); + atomic_inc(&key->usage); + rcu_read_unlock(); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_USER_KEYRING: + if (!context->user->uid_keyring) { + ret = install_user_keyrings(context); + if (ret < 0) + goto error; + } + + key = context->user->uid_keyring; + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_USER_SESSION_KEYRING: + if (!context->user->session_keyring) { + ret = install_user_keyrings(context); + if (ret < 0) + goto error; + } + + key = context->user->session_keyring; + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + case KEY_SPEC_GROUP_KEYRING: + /* group keyrings are not yet supported */ + key = ERR_PTR(-EINVAL); + goto error; + + case KEY_SPEC_REQKEY_AUTH_KEY: + key = context->request_key_auth; + if (!key) + goto error; + + atomic_inc(&key->usage); + key_ref = make_key_ref(key, 1); + break; + + default: + key_ref = ERR_PTR(-EINVAL); + if (id < 1) + goto error; + + key = key_lookup(id); + if (IS_ERR(key)) { + key_ref = ERR_CAST(key); + goto error; + } + + key_ref = make_key_ref(key, 0); + + /* check to see if we possess the key */ + skey_ref = search_process_keyrings(key->type, key, + lookup_user_key_possessed, + current); + + if (!IS_ERR(skey_ref)) { + key_put(key); + key_ref = skey_ref; + } + + break; + } + + if (!partial) { + ret = wait_for_key_construction(key, true); + switch (ret) { + case -ERESTARTSYS: + goto invalid_key; + default: + if (perm) + goto invalid_key; + case 0: + break; + } + } else if (perm) { + ret = key_validate(key); + if (ret < 0) + goto invalid_key; + } + + ret = -EIO; + if (!partial && !test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) + goto invalid_key; + + /* check the permissions */ + ret = key_task_permission(key_ref, context, perm); + if (ret < 0) + goto invalid_key; + +error: + return key_ref; + +invalid_key: + key_ref_put(key_ref); + key_ref = 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; + 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; + + rcu_read_lock(); + ret = rcu_dereference(tsk->signal->session_keyring)->serial; + rcu_read_unlock(); + goto error; + } + + /* allow the user to join or create a named keyring */ + mutex_lock(&key_session_mutex); + + /* look for an existing keyring of this name */ + keyring = find_keyring_by_name(name, false); + if (PTR_ERR(keyring) == -ENOKEY) { + /* not found - try and create a new one */ + keyring = keyring_alloc(name, tsk->uid, tsk->gid, tsk, + KEY_ALLOC_IN_QUOTA, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + } + 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: + mutex_unlock(&key_session_mutex); +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 0000000..abea08f --- /dev/null +++ b/security/keys/request_key.c @@ -0,0 +1,525 @@ +/* Request a key from userspace + * + * Copyright (C) 2004-2007 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. + * + * See Documentation/keys-request-key.txt + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/kmod.h> +#include <linux/err.h> +#include <linux/keyctl.h> +#include <linux/slab.h> +#include "internal.h" + +/* + * wait_on_bit() sleep function for uninterruptible waiting + */ +static int key_wait_bit(void *flags) +{ + schedule(); + return 0; +} + +/* + * wait_on_bit() sleep function for interruptible waiting + */ +static int key_wait_bit_intr(void *flags) +{ + schedule(); + return signal_pending(current) ? -ERESTARTSYS : 0; +} + +/* + * call to complete the construction of a key + */ +void complete_request_key(struct key_construction *cons, int error) +{ + kenter("{%d,%d},%d", cons->key->serial, cons->authkey->serial, error); + + if (error < 0) + key_negate_and_link(cons->key, key_negative_timeout, NULL, + cons->authkey); + else + key_revoke(cons->authkey); + + key_put(cons->key); + key_put(cons->authkey); + kfree(cons); +} +EXPORT_SYMBOL(complete_request_key); + +/* + * request userspace finish the construction of a key + * - execute "/sbin/request-key <op> <key> <uid> <gid> <keyring> <keyring> <keyring>" + */ +static int call_sbin_request_key(struct key_construction *cons, + const char *op, + void *aux) +{ + struct task_struct *tsk = current; + key_serial_t prkey, sskey; + struct key *key = cons->key, *authkey = cons->authkey, *keyring; + char *argv[9], *envp[3], uid_str[12], gid_str[12]; + char key_str[12], keyring_str[3][12]; + char desc[20]; + int ret, i; + + kenter("{%d},{%d},%s", key->serial, authkey->serial, op); + + ret = install_user_keyrings(tsk); + if (ret < 0) + goto error_alloc; + + /* allocate a new session keyring */ + sprintf(desc, "_req.%u", key->serial); + + keyring = keyring_alloc(desc, current->fsuid, current->fsgid, current, + KEY_ALLOC_QUOTA_OVERRUN, NULL); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error_alloc; + } + + /* attach the auth key to the session keyring */ + ret = __key_link(keyring, authkey); + if (ret < 0) + goto error_link; + + /* 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; + + sprintf(keyring_str[1], "%d", prkey); + + if (tsk->signal->session_keyring) { + rcu_read_lock(); + sskey = rcu_dereference(tsk->signal->session_keyring)->serial; + rcu_read_unlock(); + } else { + sskey = tsk->user->session_keyring->serial; + } + + 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] = NULL; + + /* do it */ + ret = call_usermodehelper_keys(argv[0], argv, envp, keyring, + UMH_WAIT_PROC); + kdebug("usermode -> 0x%x", ret); + if (ret >= 0) { + /* ret is the exit/wait code */ + if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags) || + key_validate(key) < 0) + ret = -ENOKEY; + else + /* ignore any errors from userspace if the key was + * instantiated */ + ret = 0; + } + +error_link: + key_put(keyring); + +error_alloc: + kleave(" = %d", ret); + complete_request_key(cons, ret); + return ret; +} + +/* + * call out to userspace for key construction + * - we ignore program failure and go on key status instead + */ +static int construct_key(struct key *key, const void *callout_info, + size_t callout_len, void *aux) +{ + struct key_construction *cons; + request_key_actor_t actor; + struct key *authkey; + int ret; + + kenter("%d,%p,%zu,%p", key->serial, callout_info, callout_len, aux); + + cons = kmalloc(sizeof(*cons), GFP_KERNEL); + if (!cons) + return -ENOMEM; + + /* allocate an authorisation key */ + authkey = request_key_auth_new(key, callout_info, callout_len); + if (IS_ERR(authkey)) { + kfree(cons); + ret = PTR_ERR(authkey); + authkey = NULL; + } else { + cons->authkey = key_get(authkey); + cons->key = key_get(key); + + /* make the call */ + actor = call_sbin_request_key; + if (key->type->request_key) + actor = key->type->request_key; + + ret = actor(cons, "create", aux); + + /* check that the actor called complete_request_key() prior to + * returning an error */ + WARN_ON(ret < 0 && + !test_bit(KEY_FLAG_REVOKED, &authkey->flags)); + key_put(authkey); + } + + kleave(" = %d", ret); + return ret; +} + +/* + * link a key to the appropriate destination keyring + * - the caller must hold a write lock on the destination keyring + */ +static void construct_key_make_link(struct key *key, struct key *dest_keyring) +{ + struct task_struct *tsk = current; + struct key *drop = NULL; + + kenter("{%d},%p", key->serial, dest_keyring); + + /* find the appropriate keyring */ + if (!dest_keyring) { + switch (tsk->jit_keyring) { + case KEY_REQKEY_DEFL_DEFAULT: + case KEY_REQKEY_DEFL_THREAD_KEYRING: + dest_keyring = tsk->thread_keyring; + if (dest_keyring) + break; + + case KEY_REQKEY_DEFL_PROCESS_KEYRING: + dest_keyring = tsk->signal->process_keyring; + if (dest_keyring) + break; + + case KEY_REQKEY_DEFL_SESSION_KEYRING: + rcu_read_lock(); + dest_keyring = key_get( + rcu_dereference(tsk->signal->session_keyring)); + rcu_read_unlock(); + drop = dest_keyring; + + if (dest_keyring) + break; + + case KEY_REQKEY_DEFL_USER_SESSION_KEYRING: + dest_keyring = tsk->user->session_keyring; + break; + + case KEY_REQKEY_DEFL_USER_KEYRING: + dest_keyring = tsk->user->uid_keyring; + break; + + case KEY_REQKEY_DEFL_GROUP_KEYRING: + default: + BUG(); + } + } + + /* and attach the key to it */ + __key_link(dest_keyring, key); + key_put(drop); + kleave(""); +} + +/* + * allocate a new key in under-construction state and attempt to link it in to + * the requested place + * - may return a key that's already under construction instead + */ +static int construct_alloc_key(struct key_type *type, + const char *description, + struct key *dest_keyring, + unsigned long flags, + struct key_user *user, + struct key **_key) +{ + struct key *key; + key_ref_t key_ref; + + kenter("%s,%s,,,", type->name, description); + + mutex_lock(&user->cons_lock); + + key = key_alloc(type, description, + current->fsuid, current->fsgid, current, KEY_POS_ALL, + flags); + if (IS_ERR(key)) + goto alloc_failed; + + set_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags); + + if (dest_keyring) + down_write(&dest_keyring->sem); + + /* attach the key to the destination keyring under lock, but we do need + * to do another check just in case someone beat us to it whilst we + * waited for locks */ + mutex_lock(&key_construction_mutex); + + key_ref = search_process_keyrings(type, description, type->match, + current); + if (!IS_ERR(key_ref)) + goto key_already_present; + + if (dest_keyring) + construct_key_make_link(key, dest_keyring); + + mutex_unlock(&key_construction_mutex); + if (dest_keyring) + up_write(&dest_keyring->sem); + mutex_unlock(&user->cons_lock); + *_key = key; + kleave(" = 0 [%d]", key_serial(key)); + return 0; + +key_already_present: + mutex_unlock(&key_construction_mutex); + if (dest_keyring) + up_write(&dest_keyring->sem); + mutex_unlock(&user->cons_lock); + key_put(key); + *_key = key = key_ref_to_ptr(key_ref); + kleave(" = -EINPROGRESS [%d]", key_serial(key)); + return -EINPROGRESS; + +alloc_failed: + mutex_unlock(&user->cons_lock); + *_key = NULL; + kleave(" = %ld", PTR_ERR(key)); + return PTR_ERR(key); +} + +/* + * commence key construction + */ +static struct key *construct_key_and_link(struct key_type *type, + const char *description, + const char *callout_info, + size_t callout_len, + void *aux, + struct key *dest_keyring, + unsigned long flags) +{ + struct key_user *user; + struct key *key; + int ret; + + user = key_user_lookup(current->fsuid); + if (!user) + return ERR_PTR(-ENOMEM); + + ret = construct_alloc_key(type, description, dest_keyring, flags, user, + &key); + key_user_put(user); + + if (ret == 0) { + ret = construct_key(key, callout_info, callout_len, aux); + if (ret < 0) + goto construction_failed; + } + + return key; + +construction_failed: + key_negate_and_link(key, key_negative_timeout, NULL, NULL); + key_put(key); + return ERR_PTR(ret); +} + +/* + * 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 supplementary info was provided + * - cache the key in an appropriate keyring + */ +struct key *request_key_and_link(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len, + void *aux, + struct key *dest_keyring, + unsigned long flags) +{ + struct key *key; + key_ref_t key_ref; + + kenter("%s,%s,%p,%zu,%p,%p,%lx", + type->name, description, callout_info, callout_len, aux, + dest_keyring, flags); + + /* search all the process keyrings for a key */ + key_ref = search_process_keyrings(type, description, type->match, + current); + + if (!IS_ERR(key_ref)) { + key = key_ref_to_ptr(key_ref); + } else if (PTR_ERR(key_ref) != -EAGAIN) { + key = ERR_CAST(key_ref); + } else { + /* 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; + + key = construct_key_and_link(type, description, callout_info, + callout_len, aux, dest_keyring, + flags); + } + +error: + kleave(" = %p", key); + return key; +} + +/* + * wait for construction of a key to complete + */ +int wait_for_key_construction(struct key *key, bool intr) +{ + int ret; + + ret = wait_on_bit(&key->flags, KEY_FLAG_USER_CONSTRUCT, + intr ? key_wait_bit_intr : key_wait_bit, + intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE); + if (ret < 0) + return ret; + return key_validate(key); +} +EXPORT_SYMBOL(wait_for_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 supplementary info was provided + * - waits uninterruptible for creation to complete + */ +struct key *request_key(struct key_type *type, + const char *description, + const char *callout_info) +{ + struct key *key; + size_t callout_len = 0; + int ret; + + if (callout_info) + callout_len = strlen(callout_info); + key = request_key_and_link(type, description, callout_info, callout_len, + NULL, NULL, KEY_ALLOC_IN_QUOTA); + if (!IS_ERR(key)) { + ret = wait_for_key_construction(key, false); + if (ret < 0) { + key_put(key); + return ERR_PTR(ret); + } + } + return key; +} +EXPORT_SYMBOL(request_key); + +/* + * request a key with auxiliary data for the upcaller + * - search the process's keyrings + * - check the list of keys being created or updated + * - call out to userspace for a key if supplementary info was provided + * - waits uninterruptible for creation to complete + */ +struct key *request_key_with_auxdata(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len, + void *aux) +{ + struct key *key; + int ret; + + key = request_key_and_link(type, description, callout_info, callout_len, + aux, NULL, KEY_ALLOC_IN_QUOTA); + if (!IS_ERR(key)) { + ret = wait_for_key_construction(key, false); + if (ret < 0) { + key_put(key); + return ERR_PTR(ret); + } + } + return key; +} +EXPORT_SYMBOL(request_key_with_auxdata); + +/* + * request a key (allow async construction) + * - search the process's keyrings + * - check the list of keys being created or updated + * - call out to userspace for a key if supplementary info was provided + */ +struct key *request_key_async(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len) +{ + return request_key_and_link(type, description, callout_info, + callout_len, NULL, NULL, + KEY_ALLOC_IN_QUOTA); +} +EXPORT_SYMBOL(request_key_async); + +/* + * request a key with auxiliary data for the upcaller (allow async construction) + * - search the process's keyrings + * - check the list of keys being created or updated + * - call out to userspace for a key if supplementary info was provided + */ +struct key *request_key_async_with_auxdata(struct key_type *type, + const char *description, + const void *callout_info, + size_t callout_len, + void *aux) +{ + return request_key_and_link(type, description, callout_info, + callout_len, aux, NULL, KEY_ALLOC_IN_QUOTA); +} +EXPORT_SYMBOL(request_key_async_with_auxdata); diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c new file mode 100644 index 0000000..bd237b0 --- /dev/null +++ b/security/keys/request_key_auth.c @@ -0,0 +1,280 @@ +/* request_key_auth.c: request key authorisation controlling key def + * + * Copyright (C) 2005 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. + * + * See Documentation/keys-request-key.txt + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/err.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <asm/uaccess.h> +#include "internal.h" + +static int request_key_auth_instantiate(struct key *, const void *, size_t); +static void request_key_auth_describe(const struct key *, struct seq_file *); +static void request_key_auth_revoke(struct key *); +static void request_key_auth_destroy(struct key *); +static long request_key_auth_read(const struct key *, char __user *, size_t); + +/* + * the request-key authorisation key type definition + */ +struct key_type key_type_request_key_auth = { + .name = ".request_key_auth", + .def_datalen = sizeof(struct request_key_auth), + .instantiate = request_key_auth_instantiate, + .describe = request_key_auth_describe, + .revoke = request_key_auth_revoke, + .destroy = request_key_auth_destroy, + .read = request_key_auth_read, +}; + +/*****************************************************************************/ +/* + * instantiate a request-key authorisation key + */ +static int request_key_auth_instantiate(struct key *key, + const void *data, + size_t datalen) +{ + key->payload.data = (struct request_key_auth *) data; + return 0; + +} /* end request_key_auth_instantiate() */ + +/*****************************************************************************/ +/* + * reading a request-key authorisation key retrieves the callout information + */ +static void request_key_auth_describe(const struct key *key, + struct seq_file *m) +{ + struct request_key_auth *rka = key->payload.data; + + seq_puts(m, "key:"); + seq_puts(m, key->description); + seq_printf(m, " pid:%d ci:%zu", rka->pid, rka->callout_len); + +} /* end request_key_auth_describe() */ + +/*****************************************************************************/ +/* + * read the callout_info data + * - the key's semaphore is read-locked + */ +static long request_key_auth_read(const struct key *key, + char __user *buffer, size_t buflen) +{ + struct request_key_auth *rka = key->payload.data; + size_t datalen; + long ret; + + datalen = rka->callout_len; + ret = datalen; + + /* we can return the data as is */ + if (buffer && buflen > 0) { + if (buflen > datalen) + buflen = datalen; + + if (copy_to_user(buffer, rka->callout_info, buflen) != 0) + ret = -EFAULT; + } + + return ret; + +} /* end request_key_auth_read() */ + +/*****************************************************************************/ +/* + * handle revocation of an authorisation token key + * - called with the key sem write-locked + */ +static void request_key_auth_revoke(struct key *key) +{ + struct request_key_auth *rka = key->payload.data; + + kenter("{%d}", key->serial); + + if (rka->context) { + put_task_struct(rka->context); + rka->context = NULL; + } + +} /* end request_key_auth_revoke() */ + +/*****************************************************************************/ +/* + * destroy an instantiation authorisation token key + */ +static void request_key_auth_destroy(struct key *key) +{ + struct request_key_auth *rka = key->payload.data; + + kenter("{%d}", key->serial); + + if (rka->context) { + put_task_struct(rka->context); + rka->context = NULL; + } + + key_put(rka->target_key); + kfree(rka->callout_info); + kfree(rka); + +} /* end request_key_auth_destroy() */ + +/*****************************************************************************/ +/* + * create an authorisation token for /sbin/request-key or whoever to gain + * access to the caller's security data + */ +struct key *request_key_auth_new(struct key *target, const void *callout_info, + size_t callout_len) +{ + struct request_key_auth *rka, *irka; + struct key *authkey = NULL; + char desc[20]; + int ret; + + kenter("%d,", target->serial); + + /* allocate a auth record */ + rka = kmalloc(sizeof(*rka), GFP_KERNEL); + if (!rka) { + kleave(" = -ENOMEM"); + return ERR_PTR(-ENOMEM); + } + rka->callout_info = kmalloc(callout_len, GFP_KERNEL); + if (!rka->callout_info) { + kleave(" = -ENOMEM"); + kfree(rka); + return ERR_PTR(-ENOMEM); + } + + /* see if the calling process is already servicing the key request of + * another process */ + if (current->request_key_auth) { + /* it is - use that instantiation context here too */ + down_read(¤t->request_key_auth->sem); + + /* if the auth key has been revoked, then the key we're + * servicing is already instantiated */ + if (test_bit(KEY_FLAG_REVOKED, + ¤t->request_key_auth->flags)) + goto auth_key_revoked; + + irka = current->request_key_auth->payload.data; + rka->context = irka->context; + rka->pid = irka->pid; + get_task_struct(rka->context); + + up_read(¤t->request_key_auth->sem); + } + else { + /* it isn't - use this process as the context */ + rka->context = current; + rka->pid = current->pid; + get_task_struct(rka->context); + } + + rka->target_key = key_get(target); + memcpy(rka->callout_info, callout_info, callout_len); + rka->callout_len = callout_len; + + /* allocate the auth key */ + sprintf(desc, "%x", target->serial); + + authkey = key_alloc(&key_type_request_key_auth, desc, + current->fsuid, current->fsgid, current, + KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH | + KEY_USR_VIEW, KEY_ALLOC_NOT_IN_QUOTA); + if (IS_ERR(authkey)) { + ret = PTR_ERR(authkey); + goto error_alloc; + } + + /* construct and attach to the keyring */ + ret = key_instantiate_and_link(authkey, rka, 0, NULL, NULL); + if (ret < 0) + goto error_inst; + + kleave(" = {%d}", authkey->serial); + return authkey; + +auth_key_revoked: + up_read(¤t->request_key_auth->sem); + kfree(rka->callout_info); + kfree(rka); + kleave("= -EKEYREVOKED"); + return ERR_PTR(-EKEYREVOKED); + +error_inst: + key_revoke(authkey); + key_put(authkey); +error_alloc: + key_put(rka->target_key); + kfree(rka->callout_info); + kfree(rka); + kleave("= %d", ret); + return ERR_PTR(ret); + +} /* end request_key_auth_new() */ + +/*****************************************************************************/ +/* + * see if an authorisation key is associated with a particular key + */ +static int key_get_instantiation_authkey_match(const struct key *key, + const void *_id) +{ + struct request_key_auth *rka = key->payload.data; + key_serial_t id = (key_serial_t)(unsigned long) _id; + + return rka->target_key->serial == id; + +} /* end key_get_instantiation_authkey_match() */ + +/*****************************************************************************/ +/* + * get the authorisation key for instantiation of a specific key if attached to + * the current process's keyrings + * - this key is inserted into a keyring and that is set as /sbin/request-key's + * session keyring + * - a target_id of zero specifies any valid token + */ +struct key *key_get_instantiation_authkey(key_serial_t target_id) +{ + struct key *authkey; + key_ref_t authkey_ref; + + authkey_ref = search_process_keyrings( + &key_type_request_key_auth, + (void *) (unsigned long) target_id, + key_get_instantiation_authkey_match, + current); + + if (IS_ERR(authkey_ref)) { + authkey = ERR_CAST(authkey_ref); + goto error; + } + + authkey = key_ref_to_ptr(authkey_ref); + if (test_bit(KEY_FLAG_REVOKED, &authkey->flags)) { + key_put(authkey); + authkey = ERR_PTR(-EKEYREVOKED); + } + +error: + return authkey; + +} /* end key_get_instantiation_authkey() */ diff --git a/security/keys/sysctl.c b/security/keys/sysctl.c new file mode 100644 index 0000000..b611d49 --- /dev/null +++ b/security/keys/sysctl.c @@ -0,0 +1,50 @@ +/* Key management controls + * + * Copyright (C) 2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/key.h> +#include <linux/sysctl.h> +#include "internal.h" + +ctl_table key_sysctls[] = { + { + .ctl_name = CTL_UNNUMBERED, + .procname = "maxkeys", + .data = &key_quota_maxkeys, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = &proc_dointvec, + }, + { + .ctl_name = CTL_UNNUMBERED, + .procname = "maxbytes", + .data = &key_quota_maxbytes, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = &proc_dointvec, + }, + { + .ctl_name = CTL_UNNUMBERED, + .procname = "root_maxkeys", + .data = &key_quota_root_maxkeys, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = &proc_dointvec, + }, + { + .ctl_name = CTL_UNNUMBERED, + .procname = "root_maxbytes", + .data = &key_quota_root_maxbytes, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = &proc_dointvec, + }, + { .ctl_name = 0 } +}; diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c new file mode 100644 index 0000000..7c687d5 --- /dev/null +++ b/security/keys/user_defined.c @@ -0,0 +1,218 @@ +/* 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/slab.h> +#include <linux/seq_file.h> +#include <linux/err.h> +#include <keys/user-type.h> +#include <asm/uaccess.h> +#include "internal.h" + +/* + * 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, + .update = user_update, + .match = user_match, + .revoke = user_revoke, + .destroy = user_destroy, + .describe = user_describe, + .read = user_read, +}; + +EXPORT_SYMBOL_GPL(key_type_user); + +/*****************************************************************************/ +/* + * instantiate a user defined key + */ +int user_instantiate(struct key *key, const void *data, size_t datalen) +{ + struct user_key_payload *upayload; + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > 32767 || !data) + goto error; + + ret = key_payload_reserve(key, datalen); + if (ret < 0) + goto error; + + ret = -ENOMEM; + upayload = kmalloc(sizeof(*upayload) + datalen, GFP_KERNEL); + if (!upayload) + goto error; + + /* attach the data */ + upayload->datalen = datalen; + memcpy(upayload->data, data, datalen); + rcu_assign_pointer(key->payload.data, upayload); + ret = 0; + +error: + return ret; + +} /* end user_instantiate() */ + +EXPORT_SYMBOL_GPL(user_instantiate); + +/*****************************************************************************/ +/* + * dispose of the old data from an updated user defined key + */ +static void user_update_rcu_disposal(struct rcu_head *rcu) +{ + struct user_key_payload *upayload; + + upayload = container_of(rcu, struct user_key_payload, rcu); + + kfree(upayload); + +} /* end user_update_rcu_disposal() */ + +/*****************************************************************************/ +/* + * update a user defined key + * - the key's semaphore is write-locked + */ +int user_update(struct key *key, const void *data, size_t datalen) +{ + struct user_key_payload *upayload, *zap; + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > 32767 || !data) + goto error; + + /* construct a replacement payload */ + ret = -ENOMEM; + upayload = kmalloc(sizeof(*upayload) + datalen, GFP_KERNEL); + if (!upayload) + goto error; + + upayload->datalen = datalen; + memcpy(upayload->data, data, datalen); + + /* check the quota and attach the new data */ + zap = upayload; + + ret = key_payload_reserve(key, datalen); + + if (ret == 0) { + /* attach the new data, displacing the old */ + zap = key->payload.data; + rcu_assign_pointer(key->payload.data, upayload); + key->expiry = 0; + } + + call_rcu(&zap->rcu, user_update_rcu_disposal); + +error: + return ret; + +} /* end user_update() */ + +EXPORT_SYMBOL_GPL(user_update); + +/*****************************************************************************/ +/* + * match users on their name + */ +int user_match(const struct key *key, const void *description) +{ + return strcmp(key->description, description) == 0; + +} /* end user_match() */ + +EXPORT_SYMBOL_GPL(user_match); + +/*****************************************************************************/ +/* + * dispose of the links from a revoked keyring + * - called with the key sem write-locked + */ +void user_revoke(struct key *key) +{ + struct user_key_payload *upayload = key->payload.data; + + /* clear the quota */ + key_payload_reserve(key, 0); + + if (upayload) { + rcu_assign_pointer(key->payload.data, NULL); + call_rcu(&upayload->rcu, user_update_rcu_disposal); + } + +} /* end user_revoke() */ + +EXPORT_SYMBOL(user_revoke); + +/*****************************************************************************/ +/* + * dispose of the data dangling from the corpse of a user key + */ +void user_destroy(struct key *key) +{ + struct user_key_payload *upayload = key->payload.data; + + kfree(upayload); + +} /* end user_destroy() */ + +EXPORT_SYMBOL_GPL(user_destroy); + +/*****************************************************************************/ +/* + * describe the user key + */ +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() */ + +EXPORT_SYMBOL_GPL(user_describe); + +/*****************************************************************************/ +/* + * read the key data + * - the key's semaphore is read-locked + */ +long user_read(const struct key *key, char __user *buffer, size_t buflen) +{ + struct user_key_payload *upayload; + long ret; + + upayload = rcu_dereference(key->payload.data); + ret = upayload->datalen; + + /* we can return the data as is */ + if (buffer && buflen > 0) { + if (buflen > upayload->datalen) + buflen = upayload->datalen; + + if (copy_to_user(buffer, upayload->data, buflen) != 0) + ret = -EFAULT; + } + + return ret; + +} /* end user_read() */ + +EXPORT_SYMBOL_GPL(user_read); |