KEYS: Avoid false positive ENOMEM error on key read
[sfrench/cifs-2.6.git] / security / keys / keyctl.c
index 434ed9defd3ad6b8ea67e9b3496b3cf535d795c2..0062e422e0fdd0c3c71481c677c71650e7bbd3f0 100644 (file)
@@ -339,7 +339,7 @@ long keyctl_update_key(key_serial_t id,
        payload = NULL;
        if (plen) {
                ret = -ENOMEM;
-               payload = kmalloc(plen, GFP_KERNEL);
+               payload = kvmalloc(plen, GFP_KERNEL);
                if (!payload)
                        goto error;
 
@@ -360,7 +360,7 @@ long keyctl_update_key(key_serial_t id,
 
        key_ref_put(key_ref);
 error2:
-       kzfree(payload);
+       __kvzfree(payload, plen);
 error:
        return ret;
 }
@@ -827,7 +827,8 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
        struct key *key;
        key_ref_t key_ref;
        long ret;
-       char *key_data;
+       char *key_data = NULL;
+       size_t key_data_len;
 
        /* find the key first */
        key_ref = lookup_user_key(keyid, 0, 0);
@@ -878,24 +879,51 @@ can_read_key:
         * Allocating a temporary buffer to hold the keys before
         * transferring them to user buffer to avoid potential
         * deadlock involving page fault and mmap_sem.
+        *
+        * key_data_len = (buflen <= PAGE_SIZE)
+        *              ? buflen : actual length of key data
+        *
+        * This prevents allocating arbitrary large buffer which can
+        * be much larger than the actual key length. In the latter case,
+        * at least 2 passes of this loop is required.
         */
-       key_data = kmalloc(buflen, GFP_KERNEL);
+       key_data_len = (buflen <= PAGE_SIZE) ? buflen : 0;
+       for (;;) {
+               if (key_data_len) {
+                       key_data = kvmalloc(key_data_len, GFP_KERNEL);
+                       if (!key_data) {
+                               ret = -ENOMEM;
+                               goto key_put_out;
+                       }
+               }
 
-       if (!key_data) {
-               ret = -ENOMEM;
-               goto key_put_out;
-       }
-       ret = __keyctl_read_key(key, key_data, buflen);
+               ret = __keyctl_read_key(key, key_data, key_data_len);
+
+               /*
+                * Read methods will just return the required length without
+                * any copying if the provided length isn't large enough.
+                */
+               if (ret <= 0 || ret > buflen)
+                       break;
+
+               /*
+                * The key may change (unlikely) in between 2 consecutive
+                * __keyctl_read_key() calls. In this case, we reallocate
+                * a larger buffer and redo the key read when
+                * key_data_len < ret <= buflen.
+                */
+               if (ret > key_data_len) {
+                       if (unlikely(key_data))
+                               __kvzfree(key_data, key_data_len);
+                       key_data_len = ret;
+                       continue;       /* Allocate buffer */
+               }
 
-       /*
-        * Read methods will just return the required length without
-        * any copying if the provided length isn't large enough.
-        */
-       if (ret > 0 && ret <= buflen) {
                if (copy_to_user(buffer, key_data, ret))
                        ret = -EFAULT;
+               break;
        }
-       kzfree(key_data);
+       __kvzfree(key_data, key_data_len);
 
 key_put_out:
        key_put(key);