This commit was manufactured by cvs2svn to create branch 'vserver'.
[linux-2.6.git] / security / keys / keyctl.c
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
new file mode 100644 (file)
index 0000000..4d95fdb
--- /dev/null
@@ -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 *) arg3);
+
+       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() */