crypto: af_alg - fix use-after-free in af_alg_accept() due to bh_lock_sock()
authorHerbert Xu <herbert@gondor.apana.org.au>
Mon, 8 Jun 2020 06:48:43 +0000 (16:48 +1000)
committerHerbert Xu <herbert@gondor.apana.org.au>
Thu, 18 Jun 2020 07:09:54 +0000 (17:09 +1000)
The locking in af_alg_release_parent is broken as the BH socket
lock can only be taken if there is a code-path to handle the case
where the lock is owned by process-context.  Instead of adding
such handling, we can fix this by changing the ref counts to
atomic_t.

This patch also modifies the main refcnt to include both normal
and nokey sockets.  This way we don't have to fudge the nokey
ref count when a socket changes from nokey to normal.

Credits go to Mauricio Faria de Oliveira who diagnosed this bug
and sent a patch for it:

https://lore.kernel.org/linux-crypto/20200605161657.535043-1-mfo@canonical.com/

Reported-by: Brian Moyles <bmoyles@netflix.com>
Reported-by: Mauricio Faria de Oliveira <mfo@canonical.com>
Fixes: 37f96694cf73 ("crypto: af_alg - Use bh_lock_sock in...")
Cc: <stable@vger.kernel.org>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
crypto/af_alg.c
crypto/algif_aead.c
crypto/algif_hash.c
crypto/algif_skcipher.c
include/crypto/if_alg.h

index b1cd353..28fc323 100644 (file)
@@ -128,21 +128,15 @@ EXPORT_SYMBOL_GPL(af_alg_release);
 void af_alg_release_parent(struct sock *sk)
 {
        struct alg_sock *ask = alg_sk(sk);
-       unsigned int nokey = ask->nokey_refcnt;
-       bool last = nokey && !ask->refcnt;
+       unsigned int nokey = atomic_read(&ask->nokey_refcnt);
 
        sk = ask->parent;
        ask = alg_sk(sk);
 
-       local_bh_disable();
-       bh_lock_sock(sk);
-       ask->nokey_refcnt -= nokey;
-       if (!last)
-               last = !--ask->refcnt;
-       bh_unlock_sock(sk);
-       local_bh_enable();
+       if (nokey)
+               atomic_dec(&ask->nokey_refcnt);
 
-       if (last)
+       if (atomic_dec_and_test(&ask->refcnt))
                sock_put(sk);
 }
 EXPORT_SYMBOL_GPL(af_alg_release_parent);
@@ -187,7 +181,7 @@ static int alg_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
 
        err = -EBUSY;
        lock_sock(sk);
-       if (ask->refcnt | ask->nokey_refcnt)
+       if (atomic_read(&ask->refcnt))
                goto unlock;
 
        swap(ask->type, type);
@@ -236,7 +230,7 @@ static int alg_setsockopt(struct socket *sock, int level, int optname,
        int err = -EBUSY;
 
        lock_sock(sk);
-       if (ask->refcnt)
+       if (atomic_read(&ask->refcnt) != atomic_read(&ask->nokey_refcnt))
                goto unlock;
 
        type = ask->type;
@@ -301,12 +295,14 @@ int af_alg_accept(struct sock *sk, struct socket *newsock, bool kern)
        if (err)
                goto unlock;
 
-       if (nokey || !ask->refcnt++)
+       if (atomic_inc_return_relaxed(&ask->refcnt) == 1)
                sock_hold(sk);
-       ask->nokey_refcnt += nokey;
+       if (nokey) {
+               atomic_inc(&ask->nokey_refcnt);
+               atomic_set(&alg_sk(sk2)->nokey_refcnt, 1);
+       }
        alg_sk(sk2)->parent = sk;
        alg_sk(sk2)->type = type;
-       alg_sk(sk2)->nokey_refcnt = nokey;
 
        newsock->ops = type->ops;
        newsock->state = SS_CONNECTED;
index eb1910b..0ae000a 100644 (file)
@@ -384,7 +384,7 @@ static int aead_check_key(struct socket *sock)
        struct alg_sock *ask = alg_sk(sk);
 
        lock_sock(sk);
-       if (ask->refcnt)
+       if (!atomic_read(&ask->nokey_refcnt))
                goto unlock_child;
 
        psk = ask->parent;
@@ -396,11 +396,8 @@ static int aead_check_key(struct socket *sock)
        if (crypto_aead_get_flags(tfm->aead) & CRYPTO_TFM_NEED_KEY)
                goto unlock;
 
-       if (!pask->refcnt++)
-               sock_hold(psk);
-
-       ask->refcnt = 1;
-       sock_put(psk);
+       atomic_dec(&pask->nokey_refcnt);
+       atomic_set(&ask->nokey_refcnt, 0);
 
        err = 0;
 
index da1ffa4..e71727c 100644 (file)
@@ -301,7 +301,7 @@ static int hash_check_key(struct socket *sock)
        struct alg_sock *ask = alg_sk(sk);
 
        lock_sock(sk);
-       if (ask->refcnt)
+       if (!atomic_read(&ask->nokey_refcnt))
                goto unlock_child;
 
        psk = ask->parent;
@@ -313,11 +313,8 @@ static int hash_check_key(struct socket *sock)
        if (crypto_ahash_get_flags(tfm) & CRYPTO_TFM_NEED_KEY)
                goto unlock;
 
-       if (!pask->refcnt++)
-               sock_hold(psk);
-
-       ask->refcnt = 1;
-       sock_put(psk);
+       atomic_dec(&pask->nokey_refcnt);
+       atomic_set(&ask->nokey_refcnt, 0);
 
        err = 0;
 
index 4c3bdff..ec5567c 100644 (file)
@@ -211,7 +211,7 @@ static int skcipher_check_key(struct socket *sock)
        struct alg_sock *ask = alg_sk(sk);
 
        lock_sock(sk);
-       if (ask->refcnt)
+       if (!atomic_read(&ask->nokey_refcnt))
                goto unlock_child;
 
        psk = ask->parent;
@@ -223,11 +223,8 @@ static int skcipher_check_key(struct socket *sock)
        if (crypto_skcipher_get_flags(tfm) & CRYPTO_TFM_NEED_KEY)
                goto unlock;
 
-       if (!pask->refcnt++)
-               sock_hold(psk);
-
-       ask->refcnt = 1;
-       sock_put(psk);
+       atomic_dec(&pask->nokey_refcnt);
+       atomic_set(&ask->nokey_refcnt, 0);
 
        err = 0;
 
index 56527c8..088c1de 100644 (file)
@@ -29,8 +29,8 @@ struct alg_sock {
 
        struct sock *parent;
 
-       unsigned int refcnt;
-       unsigned int nokey_refcnt;
+       atomic_t refcnt;
+       atomic_t nokey_refcnt;
 
        const struct af_alg_type *type;
        void *private;