diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index ded69108db0d..2f72bbe2962b 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -827,26 +827,28 @@ 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 = NULL;
+	size_t key_data_len;
 
 	/* find the key first */
 	key_ref = lookup_user_key(keyid, 0, 0);
 	if (IS_ERR(key_ref)) {
 		ret = -ENOKEY;
-		goto error;
+		goto out;
 	}
 
 	key = key_ref_to_ptr(key_ref);
 
 	ret = key_read_state(key);
 	if (ret < 0)
-		goto error2; /* Negatively instantiated */
+		goto key_put_out; /* Negatively instantiated */
 
 	/* see if we can read it directly */
 	ret = key_permission(key_ref, KEY_NEED_READ);
 	if (ret == 0)
 		goto can_read_key;
 	if (ret != -EACCES)
-		goto error2;
+		goto key_put_out;
 
 	/* we can't; see if it's searchable from this process's keyrings
 	 * - we automatically take account of the fact that it may be
@@ -854,75 +856,77 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
 	 */
 	if (!is_key_possessed(key_ref)) {
 		ret = -EACCES;
-		goto error2;
+		goto key_put_out;
 	}
 
 	/* the key is probably readable - now try to read it */
 can_read_key:
 	if (!key->type->read) {
 		ret = -EOPNOTSUPP;
-		goto error2;
+		goto key_put_out;
 	}
 
 	if (!buffer || !buflen) {
 		/* Get the key length from the read method */
 		ret = __keyctl_read_key(key, NULL, 0);
-	} else {
-
-		/*
-		 * Read the data with the semaphore held (since we might sleep)
-		 * to protect against the key being updated or revoked.
-		 *
-		 * Allocating a temporary buffer to hold the keys before
-		 * transferring them to user buffer to avoid potential
-		 * deadlock involving page fault and mmap_sem.
-		 */
-		char *key_data = NULL;
-		size_t key_data_len = buflen;
+		goto key_put_out;
+	}
 
-		/*
-		 * When the user-supplied key length is larger than
-		 * PAGE_SIZE, we get the actual key length first before
-		 * allocating a right-sized key data buffer.
-		 */
-		if (buflen <= PAGE_SIZE) {
-allocbuf:
+	/*
+	 * Read the data with the semaphore held (since we might sleep)
+	 * to protect against the key being updated or revoked.
+	 *
+	 * 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_len = (buflen <= PAGE_SIZE) ? buflen : 0;
+	do {
+		if (key_data_len) {
 			key_data = kvmalloc(key_data_len, GFP_KERNEL);
 			if (!key_data) {
 				ret = -ENOMEM;
-				goto error2;
+				goto key_put_out;
 			}
 		}
+
 		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 big
-		 * enough.
+		 * Read methods will just return the required length without
+		 * any copying if the provided length isn't large enough.
 		 */
-		if (ret > 0 && ret <= buflen) {
-			/*
-			 * The key may change (unlikely) in between 2
-			 * consecutive __keyctl_read_key() calls. We will
-			 * need to allocate a larger buffer and redo the key
-			 * read when key_data_len < ret <= buflen.
-			 */
-			if (!key_data || unlikely(ret > key_data_len)) {
-				if (unlikely(key_data))
-					__kvzfree(key_data, key_data_len);
-				key_data_len = ret;
-				goto allocbuf;
-			}
+		if (ret <= 0 || ret > buflen)
+			break;
 
-			if (copy_to_user(buffer, key_data, ret))
-				ret = -EFAULT;
+		/*
+		 * 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 */
 		}
-		__kvzfree(key_data, key_data_len);
-	}
 
-error2:
+		if (copy_to_user(buffer, key_data, ret))
+			ret = -EFAULT;
+	} while (0);
+	__kvzfree(key_data, key_data_len);
+
+key_put_out:
 	key_put(key);
-error:
+out:
 	return ret;
 }
 
