keys: PTR_ERR return of wrong pointer in keyctl_get_security()
[safe/jmp/linux-2.6] / security / keys / keyctl.c
index dc0011b..5f830bc 100644 (file)
@@ -1,6 +1,6 @@
 /* keyctl.c: userspace keyctl operations
  *
- * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved.
+ * 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
 #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
  * - 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)
+SYSCALL_DEFINE5(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;
+       key_ref_t keyring_ref, key_ref;
        char type[32], *description;
        void *payload;
-       long dlen, ret;
+       long ret;
+       bool vm;
 
        ret = -EINVAL;
-       if (plen > 32767)
+       if (plen > 1024 * 1024 - 1)
                goto error;
 
        /* draw all the data into kernel space */
-       ret = strncpy_from_user(type, _type, sizeof(type) - 1);
+       ret = key_get_type_from_user(type, _type, sizeof(type));
        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)
+       description = strndup_user(_description, PAGE_SIZE);
+       if (IS_ERR(description)) {
+               ret = PTR_ERR(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;
 
+       vm = false;
        if (_payload) {
                ret = -ENOMEM;
                payload = kmalloc(plen, GFP_KERNEL);
-               if (!payload)
-                       goto error2;
+               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)
@@ -82,27 +103,31 @@ asmlinkage long sys_add_key(const char __user *_type,
        }
 
        /* 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);
+       keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, 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 = key_create_or_update(keyring, type, description,
-                                  payload, plen, 0);
-       if (!IS_ERR(key)) {
-               ret = key->serial;
-               key_put(key);
+       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);
+               ret = PTR_ERR(key_ref);
        }
 
-       key_put(keyring);
+       key_ref_put(keyring_ref);
  error3:
-       kfree(payload);
+       if (!vm)
+               kfree(payload);
+       else
+               vfree(payload);
  error2:
        kfree(description);
  error:
@@ -121,69 +146,49 @@ asmlinkage long sys_add_key(const char __user *_type,
  *   - 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)
+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, *dest;
+       struct key *key;
+       key_ref_t dest_ref;
+       size_t callout_len;
        char type[32], *description, *callout_info;
-       long dlen, ret;
+       long ret;
 
        /* pull the type into kernel space */
-       ret = strncpy_from_user(type, _type, sizeof(type) - 1);
+       ret = key_get_type_from_user(type, _type, sizeof(type));
        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)
+       description = strndup_user(_description, PAGE_SIZE);
+       if (IS_ERR(description)) {
+               ret = PTR_ERR(description);
                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;
+       callout_len = 0;
        if (_callout_info) {
-               ret = -EFAULT;
-               dlen = strnlen_user(_callout_info, PAGE_SIZE - 1);
-               if (dlen <= 0)
+               callout_info = strndup_user(_callout_info, PAGE_SIZE);
+               if (IS_ERR(callout_info)) {
+                       ret = PTR_ERR(callout_info);
                        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;
+               }
+               callout_len = strlen(callout_info);
        }
 
        /* get the destination keyring if specified */
-       dest = NULL;
+       dest_ref = NULL;
        if (destringid) {
-               dest = lookup_user_key(destringid, 1, 0, KEY_WRITE);
-               if (IS_ERR(dest)) {
-                       ret = PTR_ERR(dest);
+               dest_ref = lookup_user_key(destringid, KEY_LOOKUP_CREATE,
+                                          KEY_WRITE);
+               if (IS_ERR(dest_ref)) {
+                       ret = PTR_ERR(dest_ref);
                        goto error3;
                }
        }
@@ -196,27 +201,21 @@ asmlinkage long sys_request_key(const char __user *_type,
        }
 
        /* do the search */
-       key = request_key(ktype, description, callout_info);
+       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;
        }
 
-       /* 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);
+       key_put(key);
  error5:
        key_type_put(ktype);
  error4:
-       key_put(dest);
+       key_ref_put(dest_ref);
  error3:
        kfree(callout_info);
  error2:
@@ -234,17 +233,19 @@ asmlinkage long sys_request_key(const char __user *_type,
  */
 long keyctl_get_keyring_ID(key_serial_t id, int create)
 {
-       struct key *key;
+       key_ref_t key_ref;
+       unsigned long lflags;
        long ret;
 
-       key = lookup_user_key(id, create, 0, KEY_SEARCH);
-       if (IS_ERR(key)) {
-               ret = PTR_ERR(key);
+       lflags = create ? KEY_LOOKUP_CREATE : 0;
+       key_ref = lookup_user_key(id, lflags, KEY_SEARCH);
+       if (IS_ERR(key_ref)) {
+               ret = PTR_ERR(key_ref);
                goto error;
        }
 
-       ret = key->serial;
-       key_put(key);
+       ret = key_ref_to_ptr(key_ref)->serial;
+       key_ref_put(key_ref);
  error:
        return ret;
 
@@ -258,35 +259,22 @@ long keyctl_get_keyring_ID(key_serial_t id, int create)
 long keyctl_join_session_keyring(const char __user *_name)
 {
        char *name;
-       long nlen, ret;
+       long 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)
+               name = strndup_user(_name, PAGE_SIZE);
+               if (IS_ERR(name)) {
+                       ret = PTR_ERR(name);
                        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;
 
@@ -302,7 +290,7 @@ long keyctl_update_key(key_serial_t id,
                       const void __user *_payload,
                       size_t plen)
 {
-       struct key *key;
+       key_ref_t key_ref;
        void *payload;
        long ret;
 
@@ -324,16 +312,16 @@ long keyctl_update_key(key_serial_t id,
        }
 
        /* 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);
+       key_ref = lookup_user_key(id, 0, KEY_WRITE);
+       if (IS_ERR(key_ref)) {
+               ret = PTR_ERR(key_ref);
                goto error2;
        }
 
        /* update the key */
-       ret = key_update(key, payload, plen);
+       ret = key_update(key_ref, payload, plen);
 
-       key_put(key);
+       key_ref_put(key_ref);
  error2:
        kfree(payload);
  error:
@@ -349,21 +337,27 @@ long keyctl_update_key(key_serial_t id,
  */
 long keyctl_revoke_key(key_serial_t id)
 {
-       struct key *key;
+       key_ref_t key_ref;
        long ret;
 
-       key = lookup_user_key(id, 0, 0, KEY_WRITE);
-       if (IS_ERR(key)) {
-               ret = PTR_ERR(key);
-               goto error;
+       key_ref = lookup_user_key(id, 0, KEY_WRITE);
+       if (IS_ERR(key_ref)) {
+               ret = PTR_ERR(key_ref);
+               if (ret != -EACCES)
+                       goto error;
+               key_ref = lookup_user_key(id, 0, KEY_SETATTR);
+               if (IS_ERR(key_ref)) {
+                       ret = PTR_ERR(key_ref);
+                       goto error;
+               }
        }
 
-       key_revoke(key);
+       key_revoke(key_ref_to_ptr(key_ref));
        ret = 0;
 
-       key_put(key);
+       key_ref_put(key_ref);
  error:
-       return 0;
+       return ret;
 
 } /* end keyctl_revoke_key() */
 
@@ -375,18 +369,18 @@ long keyctl_revoke_key(key_serial_t id)
  */
 long keyctl_keyring_clear(key_serial_t ringid)
 {
-       struct key *keyring;
+       key_ref_t keyring_ref;
        long ret;
 
-       keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE);
-       if (IS_ERR(keyring)) {
-               ret = PTR_ERR(keyring);
+       keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE);
+       if (IS_ERR(keyring_ref)) {
+               ret = PTR_ERR(keyring_ref);
                goto error;
        }
 
-       ret = keyring_clear(keyring);
+       ret = keyring_clear(key_ref_to_ptr(keyring_ref));
 
-       key_put(keyring);
+       key_ref_put(keyring_ref);
  error:
        return ret;
 
@@ -401,26 +395,26 @@ long keyctl_keyring_clear(key_serial_t ringid)
  */
 long keyctl_keyring_link(key_serial_t id, key_serial_t ringid)
 {
-       struct key *keyring, *key;
+       key_ref_t keyring_ref, key_ref;
        long ret;
 
-       keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE);
-       if (IS_ERR(keyring)) {
-               ret = PTR_ERR(keyring);
+       keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE);
+       if (IS_ERR(keyring_ref)) {
+               ret = PTR_ERR(keyring_ref);
                goto error;
        }
 
-       key = lookup_user_key(id, 1, 0, KEY_LINK);
-       if (IS_ERR(key)) {
-               ret = PTR_ERR(key);
+       key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE, KEY_LINK);
+       if (IS_ERR(key_ref)) {
+               ret = PTR_ERR(key_ref);
                goto error2;
        }
 
-       ret = key_link(keyring, key);
+       ret = key_link(key_ref_to_ptr(keyring_ref), key_ref_to_ptr(key_ref));
 
-       key_put(key);
+       key_ref_put(key_ref);
  error2:
-       key_put(keyring);
+       key_ref_put(keyring_ref);
  error:
        return ret;
 
@@ -435,26 +429,26 @@ long keyctl_keyring_link(key_serial_t id, key_serial_t ringid)
  */
 long keyctl_keyring_unlink(key_serial_t id, key_serial_t ringid)
 {
-       struct key *keyring, *key;
+       key_ref_t keyring_ref, key_ref;
        long ret;
 
-       keyring = lookup_user_key(ringid, 0, 0, KEY_WRITE);
-       if (IS_ERR(keyring)) {
-               ret = PTR_ERR(keyring);
+       keyring_ref = lookup_user_key(ringid, 0, KEY_WRITE);
+       if (IS_ERR(keyring_ref)) {
+               ret = PTR_ERR(keyring_ref);
                goto error;
        }
 
-       key = lookup_user_key(id, 0, 0, 0);
-       if (IS_ERR(key)) {
-               ret = PTR_ERR(key);
+       key_ref = lookup_user_key(id, KEY_LOOKUP_FOR_UNLINK, 0);
+       if (IS_ERR(key_ref)) {
+               ret = PTR_ERR(key_ref);
                goto error2;
        }
 
-       ret = key_unlink(keyring, key);
+       ret = key_unlink(key_ref_to_ptr(keyring_ref), key_ref_to_ptr(key_ref));
 
-       key_put(key);
+       key_ref_put(key_ref);
  error2:
-       key_put(keyring);
+       key_ref_put(keyring_ref);
  error:
        return ret;
 
@@ -475,29 +469,48 @@ long keyctl_describe_key(key_serial_t keyid,
                         char __user *buffer,
                         size_t buflen)
 {
-       struct key *key;
+       struct key *key, *instkey;
+       key_ref_t key_ref;
        char *tmpbuf;
        long ret;
 
-       key = lookup_user_key(keyid, 0, 1, KEY_VIEW);
-       if (IS_ERR(key)) {
-               ret = PTR_ERR(key);
+       key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, 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(keyid,
+                                                         KEY_LOOKUP_PARTIAL,
+                                                         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;%06x;%s",
-                      key->type->name,
-                      key->uid,
-                      key->gid,
-                      key->perm,
-                      key->description ? key->description :""
+                      "%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 */
@@ -517,7 +530,7 @@ long keyctl_describe_key(key_serial_t keyid,
 
        kfree(tmpbuf);
  error2:
-       key_put(key);
+       key_ref_put(key_ref);
  error:
        return ret;
 
@@ -539,47 +552,35 @@ long keyctl_keyring_search(key_serial_t ringid,
                           key_serial_t destringid)
 {
        struct key_type *ktype;
-       struct key *keyring, *key, *dest;
+       key_ref_t keyring_ref, key_ref, dest_ref;
        char type[32], *description;
-       long dlen, ret;
+       long ret;
 
        /* pull the type and description into kernel space */
-       ret = strncpy_from_user(type, _type, sizeof(type) - 1);
+       ret = key_get_type_from_user(type, _type, sizeof(type));
        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)
+       description = strndup_user(_description, PAGE_SIZE);
+       if (IS_ERR(description)) {
+               ret = PTR_ERR(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);
+       keyring_ref = lookup_user_key(ringid, 0, KEY_SEARCH);
+       if (IS_ERR(keyring_ref)) {
+               ret = PTR_ERR(keyring_ref);
                goto error2;
        }
 
        /* get the destination keyring if specified */
-       dest = NULL;
+       dest_ref = NULL;
        if (destringid) {
-               dest = lookup_user_key(destringid, 1, 0, KEY_WRITE);
-               if (IS_ERR(dest)) {
-                       ret = PTR_ERR(dest);
+               dest_ref = lookup_user_key(destringid, KEY_LOOKUP_CREATE,
+                                          KEY_WRITE);
+               if (IS_ERR(dest_ref)) {
+                       ret = PTR_ERR(dest_ref);
                        goto error3;
                }
        }
@@ -592,9 +593,9 @@ long keyctl_keyring_search(key_serial_t ringid,
        }
 
        /* do the search */
-       key = keyring_search(keyring, ktype, description);
-       if (IS_ERR(key)) {
-               ret = PTR_ERR(key);
+       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)
@@ -603,26 +604,26 @@ long keyctl_keyring_search(key_serial_t ringid,
        }
 
        /* link the resulting key to the destination keyring if we can */
-       if (dest) {
-               ret = -EACCES;
-               if (!key_permission(key, KEY_LINK))
+       if (dest_ref) {
+               ret = key_permission(key_ref, KEY_LINK);
+               if (ret < 0)
                        goto error6;
 
-               ret = key_link(dest, key);
+               ret = key_link(key_ref_to_ptr(dest_ref), key_ref_to_ptr(key_ref));
                if (ret < 0)
                        goto error6;
        }
 
-       ret = key->serial;
+       ret = key_ref_to_ptr(key_ref)->serial;
 
  error6:
-       key_put(key);
+       key_ref_put(key_ref);
  error5:
        key_type_put(ktype);
  error4:
-       key_put(dest);
+       key_ref_put(dest_ref);
  error3:
-       key_put(keyring);
+       key_ref_put(keyring_ref);
  error2:
        kfree(description);
  error:
@@ -632,16 +633,6 @@ long keyctl_keyring_search(key_serial_t ringid,
 
 /*****************************************************************************/
 /*
- * 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
@@ -652,37 +643,36 @@ static int keyctl_read_key_same(const struct key *key, const void *target)
  */
 long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
 {
-       struct key *key, *skey;
+       struct key *key;
+       key_ref_t key_ref;
        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 */
+       key_ref = lookup_user_key(keyid, 0, 0);
+       if (IS_ERR(key_ref)) {
                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 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;
        }
 
-       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) {
@@ -712,40 +702,81 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
  */
 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 = lookup_user_key(id, 1, 1, 0);
-       if (IS_ERR(key)) {
-               ret = PTR_ERR(key);
+       key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
+                                 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);
-       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;
+                       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 no_access;
+                       goto error_put;
        }
 
-       /* change the UID (have to update the quotas) */
+       /* change the UID */
        if (uid != (uid_t) -1 && uid != key->uid) {
-               /* don't support UID changing yet */
-               ret = -EOPNOTSUPP;
-               goto no_access;
+               ret = -ENOMEM;
+               newowner = key_user_lookup(uid, current_user_ns());
+               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 */
@@ -754,13 +785,20 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid)
 
        ret = 0;
 
- no_access:
-       write_unlock(&key->lock);
+error_put:
        up_write(&key->sem);
        key_put(key);
- error:
+       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() */
 
 /*****************************************************************************/
@@ -772,42 +810,93 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid)
 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_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL))
+       if (perm & ~(KEY_POS_ALL | 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);
+       key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
+                                 KEY_SETATTR);
+       if (IS_ERR(key_ref)) {
+               ret = PTR_ERR(key_ref);
                goto error;
        }
 
-       /* make the changes with the locks held to prevent chown/chmod
-        * races */
+       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);
-       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;
+       /* 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;
+       }
 
- no_access:
-       write_unlock(&key->lock);
        up_write(&key->sem);
        key_put(key);
- error:
+error:
        return ret;
 
 } /* end keyctl_setperm_key() */
 
+/*
+ * get the destination keyring for instantiation
+ */
+static long get_instantiation_keyring(key_serial_t ringid,
+                                     struct request_key_auth *rka,
+                                     struct key **_dest_keyring)
+{
+       key_ref_t dkref;
+
+       *_dest_keyring = NULL;
+
+       /* just return a NULL pointer if we weren't asked to make a link */
+       if (ringid == 0)
+               return 0;
+
+       /* if a specific keyring is nominated by ID, then use that */
+       if (ringid > 0) {
+               dkref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE);
+               if (IS_ERR(dkref))
+                       return PTR_ERR(dkref);
+               *_dest_keyring = key_ref_to_ptr(dkref);
+               return 0;
+       }
+
+       if (ringid == KEY_SPEC_REQKEY_AUTH_KEY)
+               return -EINVAL;
+
+       /* otherwise specify the destination keyring recorded in the
+        * authorisation key (any KEY_SPEC_*_KEYRING) */
+       if (ringid >= KEY_SPEC_REQUESTOR_KEYRING) {
+               *_dest_keyring = key_get(rka->dest_keyring);
+               return 0;
+       }
+
+       return -ENOKEY;
+}
+
+/*
+ * change the request_key authorisation key on the current process
+ */
+static int keyctl_change_reqkey_auth(struct key *key)
+{
+       struct cred *new;
+
+       new = prepare_creds();
+       if (!new)
+               return -ENOMEM;
+
+       key_put(new->request_key_auth);
+       new->request_key_auth = key_get(key);
+
+       return commit_creds(new);
+}
+
 /*****************************************************************************/
 /*
  * instantiate the key with the specified payload, and, if one is given, link
@@ -818,12 +907,28 @@ long keyctl_instantiate_key(key_serial_t id,
                            size_t plen,
                            key_serial_t ringid)
 {
-       struct key *key, *keyring;
+       const struct cred *cred = current_cred();
+       struct request_key_auth *rka;
+       struct key *instkey, *dest_keyring;
        void *payload;
        long ret;
+       bool vm = false;
+
+       kenter("%d,,%zu,%d", id, plen, ringid);
 
        ret = -EINVAL;
-       if (plen > 32767)
+       if (plen > 1024 * 1024 - 1)
+               goto error;
+
+       /* the appropriate instantiation authorisation key must have been
+        * assumed before calling this */
+       ret = -EPERM;
+       instkey = cred->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 */
@@ -832,41 +937,43 @@ long keyctl_instantiate_key(key_serial_t id,
        if (_payload) {
                ret = -ENOMEM;
                payload = kmalloc(plen, GFP_KERNEL);
-               if (!payload)
-                       goto error;
+               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 target key (which must be writable) */
-       key = lookup_user_key(id, 0, 1, KEY_WRITE);
-       if (IS_ERR(key)) {
-               ret = PTR_ERR(key);
+       /* find the destination keyring amongst those belonging to the
+        * requesting task */
+       ret = get_instantiation_keyring(ringid, rka, &dest_keyring);
+       if (ret < 0)
                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:
+       ret = key_instantiate_and_link(rka->target_key, payload, plen,
+                                      dest_keyring, instkey);
+
+       key_put(dest_keyring);
+
+       /* discard the assumed authority if it's just been disabled by
+        * instantiation of the key */
+       if (ret == 0)
+               keyctl_change_reqkey_auth(NULL);
+
+error2:
+       if (!vm)
+               kfree(payload);
+       else
+               vfree(payload);
+error:
        return ret;
 
 } /* end keyctl_instantiate_key() */
@@ -878,44 +985,355 @@ long keyctl_instantiate_key(key_serial_t id,
  */
 long keyctl_negate_key(key_serial_t id, unsigned timeout, key_serial_t ringid)
 {
-       struct key *key, *keyring;
+       const struct cred *cred = current_cred();
+       struct request_key_auth *rka;
+       struct key *instkey, *dest_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);
+       kenter("%d,%u,%d", id, timeout, ringid);
+
+       /* the appropriate instantiation authorisation key must have been
+        * assumed before calling this */
+       ret = -EPERM;
+       instkey = cred->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 = NULL;
-       if (ringid) {
-               keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE);
-               if (IS_ERR(keyring)) {
-                       ret = PTR_ERR(keyring);
-                       goto error2;
+       ret = get_instantiation_keyring(ringid, rka, &dest_keyring);
+       if (ret < 0)
+               goto error;
+
+       /* instantiate the key and link it into a keyring */
+       ret = key_negate_and_link(rka->target_key, timeout,
+                                 dest_keyring, instkey);
+
+       key_put(dest_keyring);
+
+       /* discard the assumed authority if it's just been disabled by
+        * instantiation of the key */
+       if (ret == 0)
+               keyctl_change_reqkey_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)
+{
+       struct cred *new;
+       int ret, old_setting;
+
+       old_setting = current_cred_xxx(jit_keyring);
+
+       if (reqkey_defl == KEY_REQKEY_DEFL_NO_CHANGE)
+               return old_setting;
+
+       new = prepare_creds();
+       if (!new)
+               return -ENOMEM;
+
+       switch (reqkey_defl) {
+       case KEY_REQKEY_DEFL_THREAD_KEYRING:
+               ret = install_thread_keyring_to_cred(new);
+               if (ret < 0)
+                       goto error;
+               goto set;
+
+       case KEY_REQKEY_DEFL_PROCESS_KEYRING:
+               ret = install_process_keyring_to_cred(new);
+               if (ret < 0) {
+                       if (ret != -EEXIST)
+                               goto error;
+                       ret = 0;
                }
+               goto set;
+
+       case KEY_REQKEY_DEFL_DEFAULT:
+       case KEY_REQKEY_DEFL_SESSION_KEYRING:
+       case KEY_REQKEY_DEFL_USER_KEYRING:
+       case KEY_REQKEY_DEFL_USER_SESSION_KEYRING:
+       case KEY_REQKEY_DEFL_REQUESTOR_KEYRING:
+               goto set;
+
+       case KEY_REQKEY_DEFL_NO_CHANGE:
+       case KEY_REQKEY_DEFL_GROUP_KEYRING:
+       default:
+               ret = -EINVAL;
+               goto error;
        }
 
-       /* instantiate the key and link it into a keyring */
-       ret = key_negate_and_link(key, timeout, keyring);
+set:
+       new->jit_keyring = reqkey_defl;
+       commit_creds(new);
+       return old_setting;
+error:
+       abort_creds(new);
+       return -EINVAL;
 
-       key_put(keyring);
- error2:
+} /* 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(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
+                                 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;
+       key_schedule_gc(key->expiry + key_gc_delay);
+
+       up_write(&key->sem);
        key_put(key);
- error:
+
+       ret = 0;
+error:
        return ret;
 
-} /* end keyctl_negate_key() */
+} /* 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) {
+               ret = keyctl_change_reqkey_auth(NULL);
+               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;
+       }
+
+       ret = keyctl_change_reqkey_auth(authkey);
+       if (ret < 0)
+               goto error;
+       key_put(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(keyid, KEY_LOOKUP_PARTIAL, 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(instkey);
+               key_put(instkey);
+
+               key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, 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;
+}
+
+/*
+ * attempt to install the calling process's session keyring on the process's
+ * parent process
+ * - the keyring must exist and must grant us LINK permission
+ * - implements keyctl(KEYCTL_SESSION_TO_PARENT)
+ */
+long keyctl_session_to_parent(void)
+{
+       struct task_struct *me, *parent;
+       const struct cred *mycred, *pcred;
+       struct cred *cred, *oldcred;
+       key_ref_t keyring_r;
+       int ret;
+
+       keyring_r = lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_LINK);
+       if (IS_ERR(keyring_r))
+               return PTR_ERR(keyring_r);
+
+       /* our parent is going to need a new cred struct, a new tgcred struct
+        * and new security data, so we allocate them here to prevent ENOMEM in
+        * our parent */
+       ret = -ENOMEM;
+       cred = cred_alloc_blank();
+       if (!cred)
+               goto error_keyring;
+
+       cred->tgcred->session_keyring = key_ref_to_ptr(keyring_r);
+       keyring_r = NULL;
+
+       me = current;
+       write_lock_irq(&tasklist_lock);
+
+       parent = me->real_parent;
+       ret = -EPERM;
+
+       /* the parent mustn't be init and mustn't be a kernel thread */
+       if (parent->pid <= 1 || !parent->mm)
+               goto not_permitted;
+
+       /* the parent must be single threaded */
+       if (atomic_read(&parent->signal->count) != 1)
+               goto not_permitted;
+
+       /* the parent and the child must have different session keyrings or
+        * there's no point */
+       mycred = current_cred();
+       pcred = __task_cred(parent);
+       if (mycred == pcred ||
+           mycred->tgcred->session_keyring == pcred->tgcred->session_keyring)
+               goto already_same;
+
+       /* the parent must have the same effective ownership and mustn't be
+        * SUID/SGID */
+       if (pcred-> uid != mycred->euid ||
+           pcred->euid != mycred->euid ||
+           pcred->suid != mycred->euid ||
+           pcred-> gid != mycred->egid ||
+           pcred->egid != mycred->egid ||
+           pcred->sgid != mycred->egid)
+               goto not_permitted;
+
+       /* the keyrings must have the same UID */
+       if (pcred ->tgcred->session_keyring->uid != mycred->euid ||
+           mycred->tgcred->session_keyring->uid != mycred->euid)
+               goto not_permitted;
+
+       /* the LSM must permit the replacement of the parent's keyring with the
+        * keyring from this process */
+       ret = security_key_session_to_parent(mycred, pcred,
+                                            key_ref_to_ptr(keyring_r));
+       if (ret < 0)
+               goto not_permitted;
+
+       /* if there's an already pending keyring replacement, then we replace
+        * that */
+       oldcred = parent->replacement_session_keyring;
+
+       /* the replacement session keyring is applied just prior to userspace
+        * restarting */
+       parent->replacement_session_keyring = cred;
+       cred = NULL;
+       set_ti_thread_flag(task_thread_info(parent), TIF_NOTIFY_RESUME);
+
+       write_unlock_irq(&tasklist_lock);
+       if (oldcred)
+               put_cred(oldcred);
+       return 0;
+
+already_same:
+       ret = 0;
+not_permitted:
+       write_unlock_irq(&tasklist_lock);
+       put_cred(cred);
+       return ret;
+
+error_keyring:
+       key_ref_put(keyring_r);
+       return ret;
+}
 
 /*****************************************************************************/
 /*
  * the key control system call
  */
-asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3,
-                          unsigned long arg4, unsigned long arg5)
+SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
+               unsigned long, arg4, unsigned long, arg5)
 {
        switch (option) {
        case KEYCTL_GET_KEYRING_ID:
@@ -980,6 +1398,24 @@ asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3,
                                         (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 __user *) arg3,
+                                          (size_t) arg4);
+
+       case KEYCTL_SESSION_TO_PARENT:
+               return keyctl_session_to_parent();
+
        default:
                return -EOPNOTSUPP;
        }