8664f7c
commit cb3bd4d9775d833501826832fd1562af19f8182d
8664f7c
Author: David Howells <dhowells@redhat.com>
8664f7c
Date:   Fri Oct 18 17:30:30 2013 +0100
8664f7c
8664f7c
    KEYS: Fix keyring quota misaccounting on key replacement and unlink
8664f7c
    
8664f7c
    If a key is displaced from a keyring by a matching one, then four more bytes
8664f7c
    of quota are allocated to the keyring - despite the fact that the keyring does
8664f7c
    not change in size.
8664f7c
    
8664f7c
    Further, when a key is unlinked from a keyring, the four bytes of quota
8664f7c
    allocated the link isn't recovered and returned to the user's pool.
8664f7c
    
8664f7c
    The first can be tested by repeating:
8664f7c
    
8664f7c
    	keyctl add big_key a fred @s
8664f7c
    	cat /proc/key-users
8664f7c
    
8664f7c
    (Don't put it in a shell loop otherwise the garbage collector won't have time
8664f7c
    to clear the displaced keys, thus affecting the result).
8664f7c
    
8664f7c
    This was causing the kerberos keyring to run out of room fairly quickly.
8664f7c
    
8664f7c
    The second can be tested by:
8664f7c
    
8664f7c
    	cat /proc/key-users
8664f7c
    	a=`keyctl add user a a @s`
8664f7c
    	cat /proc/key-users
8664f7c
    	keyctl unlink $a
8664f7c
    	sleep 1 # Give RCU a chance to delete the key
8664f7c
    	cat /proc/key-users
8664f7c
    
8664f7c
    assuming no system activity that otherwise adds/removes keys, the amount of
8664f7c
    key data allocated should go up (say 40/20000 -> 47/20000) and then return to
8664f7c
    the original value at the end.
8664f7c
    
8664f7c
    Reported-by: Stephen Gallagher <sgallagh@redhat.com>
8664f7c
    Signed-off-by: David Howells <dhowells@redhat.com>
8664f7c
8664f7c
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
8664f7c
index 8c05ebd..d80311e 100644
8664f7c
--- a/security/keys/keyring.c
8664f7c
+++ b/security/keys/keyring.c
8664f7c
@@ -1063,12 +1063,6 @@ int __key_link_begin(struct key *keyring,
8664f7c
 	if (index_key->type == &key_type_keyring)
8664f7c
 		down_write(&keyring_serialise_link_sem);
8664f7c
 
8664f7c
-	/* check that we aren't going to overrun the user's quota */
8664f7c
-	ret = key_payload_reserve(keyring,
8664f7c
-				  keyring->datalen + KEYQUOTA_LINK_BYTES);
8664f7c
-	if (ret < 0)
8664f7c
-		goto error_sem;
8664f7c
-
8664f7c
 	/* Create an edit script that will insert/replace the key in the
8664f7c
 	 * keyring tree.
8664f7c
 	 */
8664f7c
@@ -1078,17 +1072,25 @@ int __key_link_begin(struct key *keyring,
8664f7c
 				  NULL);
8664f7c
 	if (IS_ERR(edit)) {
8664f7c
 		ret = PTR_ERR(edit);
8664f7c
-		goto error_quota;
8664f7c
+		goto error_sem;
8664f7c
+	}
8664f7c
+
8664f7c
+	/* If we're not replacing a link in-place then we're going to need some
8664f7c
+	 * extra quota.
8664f7c
+	 */
8664f7c
+	if (!edit->dead_leaf) {
8664f7c
+		ret = key_payload_reserve(keyring,
8664f7c
+					  keyring->datalen + KEYQUOTA_LINK_BYTES);
8664f7c
+		if (ret < 0)
8664f7c
+			goto error_cancel;
8664f7c
 	}
8664f7c
 
8664f7c
 	*_edit = edit;
8664f7c
 	kleave(" = 0");
8664f7c
 	return 0;
8664f7c
 
8664f7c
-error_quota:
8664f7c
-	/* undo the quota changes */
8664f7c
-	key_payload_reserve(keyring,
8664f7c
-			    keyring->datalen - KEYQUOTA_LINK_BYTES);
8664f7c
+error_cancel:
8664f7c
+	assoc_array_cancel_edit(edit);
8664f7c
 error_sem:
8664f7c
 	if (index_key->type == &key_type_keyring)
8664f7c
 		up_write(&keyring_serialise_link_sem);
8664f7c
@@ -1146,7 +1148,7 @@ void __key_link_end(struct key *keyring,
8664f7c
 	if (index_key->type == &key_type_keyring)
8664f7c
 		up_write(&keyring_serialise_link_sem);
8664f7c
 
8664f7c
-	if (edit) {
8664f7c
+	if (edit && !edit->dead_leaf) {
8664f7c
 		key_payload_reserve(keyring,
8664f7c
 				    keyring->datalen - KEYQUOTA_LINK_BYTES);
8664f7c
 		assoc_array_cancel_edit(edit);
8664f7c
@@ -1243,6 +1245,7 @@ int key_unlink(struct key *keyring, struct key *key)
8664f7c
 		goto error;
8664f7c
 
8664f7c
 	assoc_array_apply_edit(edit);
8664f7c
+	key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES);
8664f7c
 	ret = 0;
8664f7c
 
8664f7c
 error: