]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - security/keys/keyctl.c
security: introduce missing kfree
[karo-tx-linux.git] / security / keys / keyctl.c
index d74458522e988cbb85f07a9d55adadca10362eff..3c0f421ec07141001b66f8a6dd28cb4ccc046350 100644 (file)
@@ -19,6 +19,8 @@
 #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"
 
@@ -52,19 +54,20 @@ static int key_get_type_from_user(char *type,
  * - 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)
 {
        key_ref_t keyring_ref, key_ref;
        char type[32], *description;
        void *payload;
        long ret;
+       bool vm;
 
        ret = -EINVAL;
-       if (plen > 32767)
+       if (plen > 1024 * 1024 - 1)
                goto error;
 
        /* draw all the data into kernel space */
@@ -81,11 +84,18 @@ asmlinkage long sys_add_key(const char __user *_type,
        /* 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)
@@ -102,7 +112,8 @@ asmlinkage long sys_add_key(const char __user *_type,
        /* create or update the requested key and add it to the target
         * keyring */
        key_ref = key_create_or_update(keyring_ref, type, description,
-                                      payload, plen, KEY_ALLOC_IN_QUOTA);
+                                      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);
@@ -113,7 +124,10 @@ asmlinkage long sys_add_key(const char __user *_type,
 
        key_ref_put(keyring_ref);
  error3:
-       kfree(payload);
+       if (!vm)
+               kfree(payload);
+       else
+               vfree(payload);
  error2:
        kfree(description);
  error:
@@ -132,14 +146,15 @@ 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;
        key_ref_t dest_ref;
+       size_t callout_len;
        char type[32], *description, *callout_info;
        long ret;
 
@@ -157,12 +172,14 @@ asmlinkage long sys_request_key(const char __user *_type,
 
        /* pull the callout info into kernel space */
        callout_info = NULL;
+       callout_len = 0;
        if (_callout_info) {
                callout_info = strndup_user(_callout_info, PAGE_SIZE);
                if (IS_ERR(callout_info)) {
                        ret = PTR_ERR(callout_info);
                        goto error2;
                }
+               callout_len = strlen(callout_info);
        }
 
        /* get the destination keyring if specified */
@@ -184,7 +201,7 @@ asmlinkage long sys_request_key(const char __user *_type,
 
        /* do the search */
        key = request_key_and_link(ktype, description, callout_info,
-                                  key_ref_to_ptr(dest_ref),
+                                  callout_len, NULL, key_ref_to_ptr(dest_ref),
                                   KEY_ALLOC_IN_QUOTA);
        if (IS_ERR(key)) {
                ret = PTR_ERR(key);
@@ -253,6 +270,7 @@ long keyctl_join_session_keyring(const char __user *_name)
 
        /* join the session */
        ret = join_session_keyring(name);
+       kfree(name);
 
  error:
        return ret;
@@ -673,6 +691,7 @@ 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;
@@ -696,19 +715,56 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid)
        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);
+               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 */
@@ -717,12 +773,20 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid)
 
        ret = 0;
 
- no_access:
+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() */
 
 /*****************************************************************************/
@@ -781,9 +845,10 @@ long keyctl_instantiate_key(key_serial_t id,
        key_ref_t keyring_ref;
        void *payload;
        long ret;
+       bool vm = false;
 
        ret = -EINVAL;
-       if (plen > 32767)
+       if (plen > 1024 * 1024 - 1)
                goto error;
 
        /* the appropriate instantiation authorisation key must have been
@@ -803,8 +868,14 @@ 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)
@@ -837,7 +908,10 @@ long keyctl_instantiate_key(key_serial_t id,
        }
 
 error2:
-       kfree(payload);
+       if (!vm)
+               kfree(payload);
+       else
+               vfree(payload);
 error:
        return ret;
 
@@ -1015,12 +1089,72 @@ error:
 
 } /* end keyctl_assume_authority() */
 
+/*
+ * get the security label of a key
+ * - the key must grant us view permission
+ * - if there's a buffer, we place up to buflen bytes of data into it
+ * - unless there's an error, we return the amount of information available,
+ *   irrespective of how much we may have copied (including the terminal NUL)
+ * - implements keyctl(KEYCTL_GET_SECURITY)
+ */
+long keyctl_get_security(key_serial_t keyid,
+                        char __user *buffer,
+                        size_t buflen)
+{
+       struct key *key, *instkey;
+       key_ref_t key_ref;
+       char *context;
+       long ret;
+
+       key_ref = lookup_user_key(NULL, keyid, 0, 1, KEY_VIEW);
+       if (IS_ERR(key_ref)) {
+               if (PTR_ERR(key_ref) != -EACCES)
+                       return PTR_ERR(key_ref);
+
+               /* viewing a key under construction is also permitted if we
+                * have the authorisation token handy */
+               instkey = key_get_instantiation_authkey(keyid);
+               if (IS_ERR(instkey))
+                       return PTR_ERR(key_ref);
+               key_put(instkey);
+
+               key_ref = lookup_user_key(NULL, keyid, 0, 1, 0);
+               if (IS_ERR(key_ref))
+                       return PTR_ERR(key_ref);
+       }
+
+       key = key_ref_to_ptr(key_ref);
+       ret = security_key_getsecurity(key, &context);
+       if (ret == 0) {
+               /* if no information was returned, give userspace an empty
+                * string */
+               ret = 1;
+               if (buffer && buflen > 0 &&
+                   copy_to_user(buffer, "", 1) != 0)
+                       ret = -EFAULT;
+       } else if (ret > 0) {
+               /* return as much data as there's room for */
+               if (buffer && buflen > 0) {
+                       if (buflen > ret)
+                               buflen = ret;
+
+                       if (copy_to_user(buffer, context, buflen) != 0)
+                               ret = -EFAULT;
+               }
+
+               kfree(context);
+       }
+
+       key_ref_put(key_ref);
+       return ret;
+}
+
 /*****************************************************************************/
 /*
  * the key control system call
  */
-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:
@@ -1095,6 +1229,11 @@ asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3,
        case KEYCTL_ASSUME_AUTHORITY:
                return keyctl_assume_authority((key_serial_t) arg2);
 
+       case KEYCTL_GET_SECURITY:
+               return keyctl_get_security((key_serial_t) arg2,
+                                          (char *) arg3,
+                                          (size_t) arg4);
+
        default:
                return -EOPNOTSUPP;
        }