diff options
Diffstat (limited to 'security/keys/keyctl.c')
-rw-r--r-- | security/keys/keyctl.c | 987 |
1 files changed, 987 insertions, 0 deletions
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c new file mode 100644 index 000000000000..dc0011b3fac9 --- /dev/null +++ b/security/keys/keyctl.c @@ -0,0 +1,987 @@ +/* keyctl.c: userspace keyctl operations + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/syscalls.h> +#include <linux/keyctl.h> +#include <linux/fs.h> +#include <linux/err.h> +#include <asm/uaccess.h> +#include "internal.h" + +/*****************************************************************************/ +/* + * extract the description of a new key from userspace and either add it as a + * new key to the specified keyring or update a matching key in that keyring + * - the keyring must be writable + * - returns the new key's serial number + * - implements add_key() + */ +asmlinkage long sys_add_key(const char __user *_type, + const char __user *_description, + const void __user *_payload, + size_t plen, + key_serial_t ringid) +{ + struct key *keyring, *key; + char type[32], *description; + void *payload; + long dlen, ret; + + ret = -EINVAL; + if (plen > 32767) + goto error; + + /* draw all the data into kernel space */ + ret = strncpy_from_user(type, _type, sizeof(type) - 1); + if (ret < 0) + goto error; + type[31] = '\0'; + + ret = -EFAULT; + dlen = strnlen_user(_description, PAGE_SIZE - 1); + if (dlen <= 0) + goto error; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + description = kmalloc(dlen + 1, GFP_KERNEL); + if (!description) + goto error; + + ret = -EFAULT; + if (copy_from_user(description, _description, dlen + 1) != 0) + goto error2; + + /* pull the payload in if one was supplied */ + payload = NULL; + + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error2; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error3; + } + + /* find the target keyring (which must be writable) */ + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error3; + } + + /* create or update the requested key and add it to the target + * keyring */ + key = key_create_or_update(keyring, type, description, + payload, plen, 0); + if (!IS_ERR(key)) { + ret = key->serial; + key_put(key); + } + else { + ret = PTR_ERR(key); + } + + key_put(keyring); + error3: + kfree(payload); + error2: + kfree(description); + error: + return ret; + +} /* end sys_add_key() */ + +/*****************************************************************************/ +/* + * search the process keyrings for a matching key + * - nested keyrings may also be searched if they have Search permission + * - if a key is found, it will be attached to the destination keyring if + * there's one specified + * - /sbin/request-key will be invoked if _callout_info is non-NULL + * - the _callout_info string will be passed to /sbin/request-key + * - if the _callout_info string is empty, it will be rendered as "-" + * - implements request_key() + */ +asmlinkage long sys_request_key(const char __user *_type, + const char __user *_description, + const char __user *_callout_info, + key_serial_t destringid) +{ + struct key_type *ktype; + struct key *key, *dest; + char type[32], *description, *callout_info; + long dlen, ret; + + /* pull the type into kernel space */ + ret = strncpy_from_user(type, _type, sizeof(type) - 1); + if (ret < 0) + goto error; + type[31] = '\0'; + + /* pull the description into kernel space */ + ret = -EFAULT; + dlen = strnlen_user(_description, PAGE_SIZE - 1); + if (dlen <= 0) + goto error; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + description = kmalloc(dlen + 1, GFP_KERNEL); + if (!description) + goto error; + + ret = -EFAULT; + if (copy_from_user(description, _description, dlen + 1) != 0) + goto error2; + + /* pull the callout info into kernel space */ + callout_info = NULL; + if (_callout_info) { + ret = -EFAULT; + dlen = strnlen_user(_callout_info, PAGE_SIZE - 1); + if (dlen <= 0) + goto error2; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error2; + + ret = -ENOMEM; + callout_info = kmalloc(dlen + 1, GFP_KERNEL); + if (!callout_info) + goto error2; + + ret = -EFAULT; + if (copy_from_user(callout_info, _callout_info, dlen + 1) != 0) + goto error3; + } + + /* get the destination keyring if specified */ + dest = NULL; + if (destringid) { + dest = lookup_user_key(destringid, 1, 0, KEY_WRITE); + if (IS_ERR(dest)) { + ret = PTR_ERR(dest); + goto error3; + } + } + + /* find the key type */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + ret = PTR_ERR(ktype); + goto error4; + } + + /* do the search */ + key = request_key(ktype, description, callout_info); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error5; + } + + /* link the resulting key to the destination keyring */ + if (dest) { + ret = key_link(dest, key); + if (ret < 0) + goto error6; + } + + ret = key->serial; + + error6: + key_put(key); + error5: + key_type_put(ktype); + error4: + key_put(dest); + error3: + kfree(callout_info); + error2: + kfree(description); + error: + return ret; + +} /* end sys_request_key() */ + +/*****************************************************************************/ +/* + * get the ID of the specified process keyring + * - the keyring must have search permission to be found + * - implements keyctl(KEYCTL_GET_KEYRING_ID) + */ +long keyctl_get_keyring_ID(key_serial_t id, int create) +{ + struct key *key; + long ret; + + key = lookup_user_key(id, create, 0, KEY_SEARCH); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + ret = key->serial; + key_put(key); + error: + return ret; + +} /* end keyctl_get_keyring_ID() */ + +/*****************************************************************************/ +/* + * join the session keyring + * - implements keyctl(KEYCTL_JOIN_SESSION_KEYRING) + */ +long keyctl_join_session_keyring(const char __user *_name) +{ + char *name; + long nlen, ret; + + /* fetch the name from userspace */ + name = NULL; + if (_name) { + ret = -EFAULT; + nlen = strnlen_user(_name, PAGE_SIZE - 1); + if (nlen <= 0) + goto error; + + ret = -EINVAL; + if (nlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + name = kmalloc(nlen + 1, GFP_KERNEL); + if (!name) + goto error; + + ret = -EFAULT; + if (copy_from_user(name, _name, nlen + 1) != 0) + goto error2; + } + + /* join the session */ + ret = join_session_keyring(name); + + error2: + kfree(name); + error: + return ret; + +} /* end keyctl_join_session_keyring() */ + +/*****************************************************************************/ +/* + * update a key's data payload + * - the key must be writable + * - implements keyctl(KEYCTL_UPDATE) + */ +long keyctl_update_key(key_serial_t id, + const void __user *_payload, + size_t plen) +{ + struct key *key; + void *payload; + long ret; + + ret = -EINVAL; + if (plen > PAGE_SIZE) + goto error; + + /* pull the payload in if one was supplied */ + payload = NULL; + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error2; + } + + /* find the target key (which must be writable) */ + key = lookup_user_key(id, 0, 0, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + /* update the key */ + ret = key_update(key, payload, plen); + + key_put(key); + error2: + kfree(payload); + error: + return ret; + +} /* end keyctl_update_key() */ + +/*****************************************************************************/ +/* + * revoke a key + * - the key must be writable + * - implements keyctl(KEYCTL_REVOKE) + */ +long keyctl_revoke_key(key_serial_t id) +{ + struct key *key; + long ret; + + key = lookup_user_key(id, 0, 0, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + key_revoke(key); + ret = 0; + + key_put(key); + error: + return 0; + +} /* end keyctl_revoke_key() */ + +/*****************************************************************************/ +/* + * clear the specified process keyring + * - the keyring must be writable + * - implements keyctl(KEYCTL_CLEAR) + */ +long keyctl_keyring_clear(key_serial_t ringid) +{ + struct key *keyring; + long ret; + + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + ret = keyring_clear(keyring); + + key_put(keyring); + error: + return ret; + +} /* end keyctl_keyring_clear() */ + +/*****************************************************************************/ +/* + * link a key into a keyring + * - the keyring must be writable + * - the key must be linkable + * - implements keyctl(KEYCTL_LINK) + */ +long keyctl_keyring_link(key_serial_t id, key_serial_t ringid) +{ + struct key *keyring, *key; + long ret; + + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + key = lookup_user_key(id, 1, 0, KEY_LINK); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + ret = key_link(keyring, key); + + key_put(key); + error2: + key_put(keyring); + error: + return ret; + +} /* end keyctl_keyring_link() */ + +/*****************************************************************************/ +/* + * unlink the first attachment of a key from a keyring + * - the keyring must be writable + * - we don't need any permissions on the key + * - implements keyctl(KEYCTL_UNLINK) + */ +long keyctl_keyring_unlink(key_serial_t id, key_serial_t ringid) +{ + struct key *keyring, *key; + long ret; + + keyring = lookup_user_key(ringid, 0, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error; + } + + key = lookup_user_key(id, 0, 0, 0); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + ret = key_unlink(keyring, key); + + key_put(key); + error2: + key_put(keyring); + error: + return ret; + +} /* end keyctl_keyring_unlink() */ + +/*****************************************************************************/ +/* + * describe a user key + * - the key must have view permission + * - if there's a buffer, we place up to buflen bytes of data into it + * - unless there's an error, we return the amount of description available, + * irrespective of how much we may have copied + * - the description is formatted thus: + * type;uid;gid;perm;description<NUL> + * - implements keyctl(KEYCTL_DESCRIBE) + */ +long keyctl_describe_key(key_serial_t keyid, + char __user *buffer, + size_t buflen) +{ + struct key *key; + char *tmpbuf; + long ret; + + key = lookup_user_key(keyid, 0, 1, KEY_VIEW); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* calculate how much description we're going to return */ + ret = -ENOMEM; + tmpbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!tmpbuf) + goto error2; + + ret = snprintf(tmpbuf, PAGE_SIZE - 1, + "%s;%d;%d;%06x;%s", + key->type->name, + key->uid, + key->gid, + key->perm, + key->description ? key->description :"" + ); + + /* include a NUL char at the end of the data */ + if (ret > PAGE_SIZE - 1) + ret = PAGE_SIZE - 1; + tmpbuf[ret] = 0; + ret++; + + /* consider returning the data */ + if (buffer && buflen > 0) { + if (buflen > ret) + buflen = ret; + + if (copy_to_user(buffer, tmpbuf, buflen) != 0) + ret = -EFAULT; + } + + kfree(tmpbuf); + error2: + key_put(key); + error: + return ret; + +} /* end keyctl_describe_key() */ + +/*****************************************************************************/ +/* + * search the specified keyring for a matching key + * - the start keyring must be searchable + * - nested keyrings may also be searched if they are searchable + * - only keys with search permission may be found + * - if a key is found, it will be attached to the destination keyring if + * there's one specified + * - implements keyctl(KEYCTL_SEARCH) + */ +long keyctl_keyring_search(key_serial_t ringid, + const char __user *_type, + const char __user *_description, + key_serial_t destringid) +{ + struct key_type *ktype; + struct key *keyring, *key, *dest; + char type[32], *description; + long dlen, ret; + + /* pull the type and description into kernel space */ + ret = strncpy_from_user(type, _type, sizeof(type) - 1); + if (ret < 0) + goto error; + type[31] = '\0'; + + ret = -EFAULT; + dlen = strnlen_user(_description, PAGE_SIZE - 1); + if (dlen <= 0) + goto error; + + ret = -EINVAL; + if (dlen > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + description = kmalloc(dlen + 1, GFP_KERNEL); + if (!description) + goto error; + + ret = -EFAULT; + if (copy_from_user(description, _description, dlen + 1) != 0) + goto error2; + + /* get the keyring at which to begin the search */ + keyring = lookup_user_key(ringid, 0, 0, KEY_SEARCH); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + + /* get the destination keyring if specified */ + dest = NULL; + if (destringid) { + dest = lookup_user_key(destringid, 1, 0, KEY_WRITE); + if (IS_ERR(dest)) { + ret = PTR_ERR(dest); + goto error3; + } + } + + /* find the key type */ + ktype = key_type_lookup(type); + if (IS_ERR(ktype)) { + ret = PTR_ERR(ktype); + goto error4; + } + + /* do the search */ + key = keyring_search(keyring, ktype, description); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + + /* treat lack or presence of a negative key the same */ + if (ret == -EAGAIN) + ret = -ENOKEY; + goto error5; + } + + /* link the resulting key to the destination keyring if we can */ + if (dest) { + ret = -EACCES; + if (!key_permission(key, KEY_LINK)) + goto error6; + + ret = key_link(dest, key); + if (ret < 0) + goto error6; + } + + ret = key->serial; + + error6: + key_put(key); + error5: + key_type_put(ktype); + error4: + key_put(dest); + error3: + key_put(keyring); + error2: + kfree(description); + error: + return ret; + +} /* end keyctl_keyring_search() */ + +/*****************************************************************************/ +/* + * see if the key we're looking at is the target key + */ +static int keyctl_read_key_same(const struct key *key, const void *target) +{ + return key == target; + +} /* end keyctl_read_key_same() */ + +/*****************************************************************************/ +/* + * read a user key's payload + * - the keyring must be readable or the key must be searchable from the + * process's keyrings + * - if there's a buffer, we place up to buflen bytes of data into it + * - unless there's an error, we return the amount of data in the key, + * irrespective of how much we may have copied + * - implements keyctl(KEYCTL_READ) + */ +long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen) +{ + struct key *key, *skey; + long ret; + + /* find the key first */ + key = lookup_user_key(keyid, 0, 0, 0); + if (!IS_ERR(key)) { + /* see if we can read it directly */ + if (key_permission(key, KEY_READ)) + goto can_read_key; + + /* can't; see if it's searchable from this process's + * keyrings */ + ret = -ENOKEY; + if (key_permission(key, KEY_SEARCH)) { + /* okay - we do have search permission on the key + * itself, but do we have the key? */ + skey = search_process_keyrings_aux(key->type, key, + keyctl_read_key_same); + if (!IS_ERR(skey)) + goto can_read_key2; + } + + goto error2; + } + + ret = -ENOKEY; + goto error; + + /* the key is probably readable - now try to read it */ + can_read_key2: + key_put(skey); + can_read_key: + ret = key_validate(key); + if (ret == 0) { + ret = -EOPNOTSUPP; + if (key->type->read) { + /* read the data with the semaphore held (since we + * might sleep) */ + down_read(&key->sem); + ret = key->type->read(key, buffer, buflen); + up_read(&key->sem); + } + } + + error2: + key_put(key); + error: + return ret; + +} /* end keyctl_read_key() */ + +/*****************************************************************************/ +/* + * change the ownership of a key + * - the keyring owned by the changer + * - if the uid or gid is -1, then that parameter is not changed + * - implements keyctl(KEYCTL_CHOWN) + */ +long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid) +{ + struct key *key; + long ret; + + ret = 0; + if (uid == (uid_t) -1 && gid == (gid_t) -1) + goto error; + + key = lookup_user_key(id, 1, 1, 0); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* make the changes with the locks held to prevent chown/chown races */ + ret = -EACCES; + down_write(&key->sem); + write_lock(&key->lock); + + if (!capable(CAP_SYS_ADMIN)) { + /* only the sysadmin can chown a key to some other UID */ + if (uid != (uid_t) -1 && key->uid != uid) + goto no_access; + + /* only the sysadmin can set the key's GID to a group other + * than one of those that the current process subscribes to */ + if (gid != (gid_t) -1 && gid != key->gid && !in_group_p(gid)) + goto no_access; + } + + /* change the UID (have to update the quotas) */ + if (uid != (uid_t) -1 && uid != key->uid) { + /* don't support UID changing yet */ + ret = -EOPNOTSUPP; + goto no_access; + } + + /* change the GID */ + if (gid != (gid_t) -1) + key->gid = gid; + + ret = 0; + + no_access: + write_unlock(&key->lock); + up_write(&key->sem); + key_put(key); + error: + return ret; + +} /* end keyctl_chown_key() */ + +/*****************************************************************************/ +/* + * change the permission mask on a key + * - the keyring owned by the changer + * - implements keyctl(KEYCTL_SETPERM) + */ +long keyctl_setperm_key(key_serial_t id, key_perm_t perm) +{ + struct key *key; + long ret; + + ret = -EINVAL; + if (perm & ~(KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL)) + goto error; + + key = lookup_user_key(id, 1, 1, 0); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* make the changes with the locks held to prevent chown/chmod + * races */ + ret = -EACCES; + down_write(&key->sem); + write_lock(&key->lock); + + /* if we're not the sysadmin, we can only chmod a key that we + * own */ + if (!capable(CAP_SYS_ADMIN) && key->uid != current->fsuid) + goto no_access; + + /* changing the permissions mask */ + key->perm = perm; + ret = 0; + + no_access: + write_unlock(&key->lock); + up_write(&key->sem); + key_put(key); + error: + return ret; + +} /* end keyctl_setperm_key() */ + +/*****************************************************************************/ +/* + * instantiate the key with the specified payload, and, if one is given, link + * the key into the keyring + */ +long keyctl_instantiate_key(key_serial_t id, + const void __user *_payload, + size_t plen, + key_serial_t ringid) +{ + struct key *key, *keyring; + void *payload; + long ret; + + ret = -EINVAL; + if (plen > 32767) + goto error; + + /* pull the payload in if one was supplied */ + payload = NULL; + + if (_payload) { + ret = -ENOMEM; + payload = kmalloc(plen, GFP_KERNEL); + if (!payload) + goto error; + + ret = -EFAULT; + if (copy_from_user(payload, _payload, plen) != 0) + goto error2; + } + + /* find the target key (which must be writable) */ + key = lookup_user_key(id, 0, 1, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error2; + } + + /* find the destination keyring if present (which must also be + * writable) */ + keyring = NULL; + if (ringid) { + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error3; + } + } + + /* instantiate the key and link it into a keyring */ + ret = key_instantiate_and_link(key, payload, plen, keyring); + + key_put(keyring); + error3: + key_put(key); + error2: + kfree(payload); + error: + return ret; + +} /* end keyctl_instantiate_key() */ + +/*****************************************************************************/ +/* + * negatively instantiate the key with the given timeout (in seconds), and, if + * one is given, link the key into the keyring + */ +long keyctl_negate_key(key_serial_t id, unsigned timeout, key_serial_t ringid) +{ + struct key *key, *keyring; + long ret; + + /* find the target key (which must be writable) */ + key = lookup_user_key(id, 0, 1, KEY_WRITE); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto error; + } + + /* find the destination keyring if present (which must also be + * writable) */ + keyring = NULL; + if (ringid) { + keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE); + if (IS_ERR(keyring)) { + ret = PTR_ERR(keyring); + goto error2; + } + } + + /* instantiate the key and link it into a keyring */ + ret = key_negate_and_link(key, timeout, keyring); + + key_put(keyring); + error2: + key_put(key); + error: + return ret; + +} /* end keyctl_negate_key() */ + +/*****************************************************************************/ +/* + * the key control system call + */ +asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + switch (option) { + case KEYCTL_GET_KEYRING_ID: + return keyctl_get_keyring_ID((key_serial_t) arg2, + (int) arg3); + + case KEYCTL_JOIN_SESSION_KEYRING: + return keyctl_join_session_keyring((const char __user *) arg2); + + case KEYCTL_UPDATE: + return keyctl_update_key((key_serial_t) arg2, + (const void __user *) arg3, + (size_t) arg4); + + case KEYCTL_REVOKE: + return keyctl_revoke_key((key_serial_t) arg2); + + case KEYCTL_DESCRIBE: + return keyctl_describe_key((key_serial_t) arg2, + (char __user *) arg3, + (unsigned) arg4); + + case KEYCTL_CLEAR: + return keyctl_keyring_clear((key_serial_t) arg2); + + case KEYCTL_LINK: + return keyctl_keyring_link((key_serial_t) arg2, + (key_serial_t) arg3); + + case KEYCTL_UNLINK: + return keyctl_keyring_unlink((key_serial_t) arg2, + (key_serial_t) arg3); + + case KEYCTL_SEARCH: + return keyctl_keyring_search((key_serial_t) arg2, + (const char __user *) arg3, + (const char __user *) arg4, + (key_serial_t) arg5); + + case KEYCTL_READ: + return keyctl_read_key((key_serial_t) arg2, + (char __user *) arg3, + (size_t) arg4); + + case KEYCTL_CHOWN: + return keyctl_chown_key((key_serial_t) arg2, + (uid_t) arg3, + (gid_t) arg4); + + case KEYCTL_SETPERM: + return keyctl_setperm_key((key_serial_t) arg2, + (key_perm_t) arg3); + + case KEYCTL_INSTANTIATE: + return keyctl_instantiate_key((key_serial_t) arg2, + (const void __user *) arg3, + (size_t) arg4, + (key_serial_t) arg5); + + case KEYCTL_NEGATE: + return keyctl_negate_key((key_serial_t) arg2, + (unsigned) arg3, + (key_serial_t) arg4); + + default: + return -EOPNOTSUPP; + } + +} /* end sys_keyctl() */ |