KEYS: fix cred refcount leak in request_key_auth_new()
authorEric Biggers <ebiggers@google.com>
Thu, 21 Sep 2017 20:57:40 +0000 (13:57 -0700)
committerDavid Howells <dhowells@redhat.com>
Mon, 25 Sep 2017 14:03:55 +0000 (15:03 +0100)
In request_key_auth_new(), if key_alloc() or key_instantiate_and_link()
were to fail, we would leak a reference to the 'struct cred'.  Currently
this can only happen if key_alloc() fails to allocate memory.  But it
still should be fixed, as it is a more severe bug waiting to happen.

Fix it by cleaning things up to use a helper function which frees a
'struct request_key_auth' correctly.

Fixes: d84f4f992cbd ("CRED: Inaugurate COW credentials")
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
security/keys/request_key_auth.c

index afe9d22..69d6b3b 100644 (file)
@@ -120,6 +120,18 @@ static void request_key_auth_revoke(struct key *key)
        }
 }
 
+static void free_request_key_auth(struct request_key_auth *rka)
+{
+       if (!rka)
+               return;
+       key_put(rka->target_key);
+       key_put(rka->dest_keyring);
+       if (rka->cred)
+               put_cred(rka->cred);
+       kfree(rka->callout_info);
+       kfree(rka);
+}
+
 /*
  * Destroy an instantiation authorisation token key.
  */
@@ -129,15 +141,7 @@ static void request_key_auth_destroy(struct key *key)
 
        kenter("{%d}", key->serial);
 
-       if (rka->cred) {
-               put_cred(rka->cred);
-               rka->cred = NULL;
-       }
-
-       key_put(rka->target_key);
-       key_put(rka->dest_keyring);
-       kfree(rka->callout_info);
-       kfree(rka);
+       free_request_key_auth(rka);
 }
 
 /*
@@ -151,22 +155,17 @@ struct key *request_key_auth_new(struct key *target, const void *callout_info,
        const struct cred *cred = current->cred;
        struct key *authkey = NULL;
        char desc[20];
-       int ret;
+       int ret = -ENOMEM;
 
        kenter("%d,", target->serial);
 
        /* allocate a auth record */
-       rka = kmalloc(sizeof(*rka), GFP_KERNEL);
-       if (!rka) {
-               kleave(" = -ENOMEM");
-               return ERR_PTR(-ENOMEM);
-       }
+       rka = kzalloc(sizeof(*rka), GFP_KERNEL);
+       if (!rka)
+               goto error;
        rka->callout_info = kmalloc(callout_len, GFP_KERNEL);
-       if (!rka->callout_info) {
-               kleave(" = -ENOMEM");
-               kfree(rka);
-               return ERR_PTR(-ENOMEM);
-       }
+       if (!rka->callout_info)
+               goto error_free_rka;
 
        /* see if the calling process is already servicing the key request of
         * another process */
@@ -176,8 +175,12 @@ struct key *request_key_auth_new(struct key *target, const void *callout_info,
 
                /* if the auth key has been revoked, then the key we're
                 * servicing is already instantiated */
-               if (test_bit(KEY_FLAG_REVOKED, &cred->request_key_auth->flags))
-                       goto auth_key_revoked;
+               if (test_bit(KEY_FLAG_REVOKED,
+                            &cred->request_key_auth->flags)) {
+                       up_read(&cred->request_key_auth->sem);
+                       ret = -EKEYREVOKED;
+                       goto error_free_rka;
+               }
 
                irka = cred->request_key_auth->payload.data[0];
                rka->cred = get_cred(irka->cred);
@@ -205,32 +208,23 @@ struct key *request_key_auth_new(struct key *target, const void *callout_info,
                            KEY_USR_VIEW, KEY_ALLOC_NOT_IN_QUOTA, NULL);
        if (IS_ERR(authkey)) {
                ret = PTR_ERR(authkey);
-               goto error_alloc;
+               goto error_free_rka;
        }
 
        /* construct the auth key */
        ret = key_instantiate_and_link(authkey, rka, 0, NULL, NULL);
        if (ret < 0)
-               goto error_inst;
+               goto error_put_authkey;
 
        kleave(" = {%d,%d}", authkey->serial, refcount_read(&authkey->usage));
        return authkey;
 
-auth_key_revoked:
-       up_read(&cred->request_key_auth->sem);
-       kfree(rka->callout_info);
-       kfree(rka);
-       kleave("= -EKEYREVOKED");
-       return ERR_PTR(-EKEYREVOKED);
-
-error_inst:
+error_put_authkey:
        key_revoke(authkey);
        key_put(authkey);
-error_alloc:
-       key_put(rka->target_key);
-       key_put(rka->dest_keyring);
-       kfree(rka->callout_info);
-       kfree(rka);
+error_free_rka:
+       free_request_key_auth(rka);
+error:
        kleave("= %d", ret);
        return ERR_PTR(ret);
 }