linux-crypto.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] crypto: af_alg - fix use-after-free in af_alg_accept() due to bh_lock_sock()
@ 2020-06-05 16:16 Mauricio Faria de Oliveira
  2020-06-08  6:48 ` [v2 PATCH] " Herbert Xu
  0 siblings, 1 reply; 6+ messages in thread
From: Mauricio Faria de Oliveira @ 2020-06-05 16:16 UTC (permalink / raw)
  To: Herbert Xu; +Cc: David S. Miller, linux-crypto

This patch fixes a regression from commit 37f96694cf73 ("crypto: af_alg
 - Use bh_lock_sock in sk_destruct"), which allows the critical regions
of af_alg_accept() and af_alg_release_parent() to run concurrently.

With the "right" timing and ordering of events, release/free the parent
socket can happen while it is still used to accept a child socket, thus
cause an use-after-free error (usually BUG/NULL pointer dereference and
general protection fault).

It mostly happens on the callees security_sk_clone() and release_sock().
The latter indicates the socket was freed elsewhere while still being
used in the critical region / before being released.

Examples:

    BUG: unable to handle kernel NULL pointer dereference at 0000000000000000
    ...
    RIP: 0010:apparmor_sk_clone_security+0x26/0x70
    ...
    Call Trace:
     security_sk_clone+0x33/0x50
     af_alg_accept+0x81/0x1c0 [af_alg]
     alg_accept+0x15/0x20 [af_alg]
     SYSC_accept4+0xff/0x210
     SyS_accept+0x10/0x20
     do_syscall_64+0x73/0x130
     entry_SYSCALL_64_after_hwframe+0x3d/0xa2

    general protection fault: 0000 [#1] SMP PTI
    ...
    RIP: 0010:__release_sock+0x54/0xe0
    ...
    Call Trace:
     release_sock+0x30/0xa0
     af_alg_accept+0x122/0x1c0 [af_alg]
     alg_accept+0x15/0x20 [af_alg]
     SYSC_accept4+0xff/0x210
     SyS_accept+0x10/0x20
     do_syscall_64+0x73/0x130
     entry_SYSCALL_64_after_hwframe+0x3d/0xa2

Root cause:
----------

The problem happens because the patch changed the socket lock operation
from lock_sock() (mutex semantics) to bh_lock_sock() (spinlock only.)

The socket lock combines a spinlock and the 'owned' field so to provide
mutex semantics for lock_sock()/release_sock(), with the spinlock being
held just to modify the 'owned' field; later the spinlock is *released*.

That is, lock_sock() acquires the spinlock, then checks whether 'owned'
is set (i.e., mutex is held). If it's set, release spinlock, and sleep.
If not (i.e., mutex is free), set it, then release spinlock as well.
(and release_sock() clears it later, accordingly.)

Well, this works when only lock/release_sock() are used, as they honor
the 'owned' field. However, bh_lock_sock() does not honor it; just the
spinlock is acquired. (i.e. bh_lock_sock() is NOT enough to coordinate
/synchronize with lock_sock())

This allows for af_alg_accept() to lock_sock() (and believe it is safe
because 'owned' is set/no spinlock needed) AND af_alg_release_parent()
to bh_lock_sock() and succeed, as the spinlock has been released and
it doesn't check for 'owned.'

Now their critical regions are running concurrently, and with "right"
timing and event ordering, the parent socket is released/freed while
in use.

Timing/Events:
-------------

The key point is the (non-atomic) update to parent alg_sock's refcnt,
and use in-register results to sock_put() in af_alg_release_parent().

    af_alg_accept()
    ...
            if (nokey || !ask->refcnt++)
                    sock_hold(sk);
    ...

    af_alg_release_parent()
    ...
    		last = !--ask->refcnt;
    ...
    	if (last)
    		sock_put(sk);
    ...

If the (read-modify-write) updates to ask->refcnt in af_alg_accept()
and af_alg_release_parent() read at the same time (i.e., same value)
the modified value is different for both (+1/-1) and which value is
written to memory (and used for the next read) is undefined (depend
on CPU, cache, NUMA, timing etc.)

If af_alg_release_parent() finds that it decremented that to zero it
calls sock_put(); this decrements the (non-alg) sock's sk_refcnt and
might release/free the sock if it drops to zero.

And if its decremented write does not make it, af_alg_accept() skips
the sock_hold() call to make up for it.. and now the sock->sk_refcnt
value is negatively unbalanced.

If this pattern happens several times, sock->sk_refcnt drops to zero,
and sk_free() will release/free the parent socket, causing the issue.

The problem is reproducible under these timing/events, with parallel
calls to accept()/af_alg_accept() and close()/af_alg_release_parent()
happening on CPU 1 and CPU 2, respectively.
(the * symbol denotes which write to memory made it.)

    CPU 1            CPU 2        ask->refcnt   sk->sk_refcnt
    af_alg_          af_alg_      (in memory)
    accept()         release_
                     parent()         0              1 (after bind())

 Stage 1)

    0++ = 1          (not yet)        1              1
    sock_hold()                                      2

 Stage 2)

    1++ = 2 (*)      --1 = 0          2              2
                     sock_put()                      1
 Stage 3)

    2++ = 3          --2 = 1 (*)      1              1

 Stage 4)

    1++ = 2 (?)      --1 = 0 (?)      ?              1
                     sock_put()                      0 (drop to zero)
                     sk_free()
 Stage 5)

    (BUG/NULL/GPF)

Reproducers:
-----------

The issue is usually reproducible with the Varnish Cache _Plus_ 'crypto'
module [1] which uses the Linux kernel crypto API, under very high load
for several hours.

It is also reproducible with a synthetic test-case that combines a user
space tool to generate parallel calls to af_alg_accept/release_parent()
with a kernel module with kprobes to synchronize both tasks on "right"
instructions (write 'psk->refcnt' to memory) and ensure one does that
after the other (so its local value "wins.")

When either the offending patch is reverted, or this patch is applied,
the issue no longer happens.

Patch:
-----

This patch checks for the 3 possible scenarios af_alg_release_parent()
can be run (task context; BH context w/ owned clear, and w/ ownet set)
and uses the appropriate locking for the socket, scheduling work when
required (i.e. 3rd case, that needs to sleep but cannot in BH context)

In task context, it's OK to lock_sock() (pre-offending patch behavior)
to update psk.

In bottom-halves context, it's not OK (might sleep); use bh_lock_sock()
and check for 'owned' to decide:

If owned is clear (no lock_sock() holding it), it's OK to update psk.

If owned is set (lock_sock() is holding it), it's not OK; so schedule
work (runs in task context) to lock_sock() and update psk.

Testing:
-------

The patch has been tested on a v5.7 based kernel, and runs correctly
for days; previously it usually failed in less than a day. This only
exercises the task context scenario, so synthetic testing was done.

The kprobes module has been tweaked to always set SOCK_RCU_FREE on
sk_destruct() for the test program thus af_alg_release_parent() is
called in bottom halves context.

Then it has been further tweaked to wait or not in af_alg_accept()
before the lock_sock() call; i.e., so that owned is clear or set
when af_alg_release_parent() runs.

Now the 3 scenarios could be exercised, and the process/context
that __af_alg_release_parent() (note these leading underscores:
the new function that actually updates psk->refcnt) is checked
with dump_stack() -- validating the 3 scenarios work correctly:

1) task context

    [  131.653168] CPU: 2 PID: 775 Comm: cryptoops Tainted: G           O      5.7.0.rlzprt #23
    ...
    [  131.663362] RIP: 0010:__af_alg_release_parent+0x20/0x90
    ...
    [  131.675672]  __sk_destruct+0x21/0x150
    [  131.676446]  af_alg_release+0x3b/0x50
    [  131.677218]  __sock_release+0x38/0xa0
    [  131.677982]  sock_close+0xc/0x10
    [  131.678673]  __fput+0xd5/0x240
    [  131.679342]  task_work_run+0x5a/0x90
    [  131.680094]  exit_to_usermode_loop+0x98/0xa0
    [  131.680953]  do_syscall_64+0x113/0x140
    [  131.681725]  entry_SYSCALL_64_after_hwframe+0x44/0xa9

2) bottom halves context w/ owned clear

    [   29.242874] CPU: 0 PID: 0 Comm: swapper/0 Tainted: G        W  O      5.7.0.rlzprt #23
    ...
    [   29.252933] RIP: 0010:__af_alg_release_parent+0x20/0x90
    ...
    [   29.265542]  <IRQ>
    [   29.266024]  __sk_destruct+0x21/0x150
    [   29.266736]  rcu_core+0x1eb/0x6d0
    [   29.267399]  __do_softirq+0xdb/0x2d8
    [   29.268121]  irq_exit+0x9b/0xa0
    [   29.268758]  smp_apic_timer_interrupt+0x69/0x130
    [   29.269604]  apic_timer_interrupt+0xf/0x20
    [   29.270379]  </IRQ>

3) bottom halves context w/ owned set

    [   30.588811] CPU: 1 PID: 101 Comm: kworker/1:1 Tainted: G        W  O      5.7.0.rlzprt #23
    ...
    [   30.592195] Workqueue: events __af_alg_release_parent_work
    ...
    [   30.601682] RIP: 0010:__af_alg_release_parent+0x20/0x90
    ...
    [   30.614915]  __af_alg_release_parent_work+0x1e/0x30
    [   30.615717]  process_one_work+0x1d3/0x380
    [   30.616393]  worker_thread+0x45/0x3c0
    [   30.617279]  kthread+0xf6/0x130
    [   30.617847]  ? process_one_work+0x380/0x380
    [   30.618574]  ? kthread_park+0x80/0x80
    [   30.619230]  ret_from_fork+0x35/0x40

[1] https://docs.varnish-software.com/varnish-cache-plus/vmods/total-encryption/

Fixes: 37f96694cf73 ("crypto: af_alg - Use bh_lock_sock in sk_destruct")
Reported-by: Brian Moyles <bmoyles@netflix.com>
Signed-off-by: Mauricio Faria de Oliveira <mfo@canonical.com>
---
 crypto/af_alg.c | 117 +++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 105 insertions(+), 12 deletions(-)

diff --git a/crypto/af_alg.c b/crypto/af_alg.c
index b1cd3535c525..eea3993a7126 100644
--- a/crypto/af_alg.c
+++ b/crypto/af_alg.c
@@ -125,25 +125,118 @@ int af_alg_release(struct socket *sock)
 }
 EXPORT_SYMBOL_GPL(af_alg_release);
 
+void __af_alg_release_parent(struct sock *psk, unsigned int ask_refcnt,
+			     unsigned int ask_nokey_refcnt)
+{
+	struct alg_sock *pask = alg_sk(psk);
+	bool last = ask_nokey_refcnt && !ask_refcnt;
+
+	/*
+	 * The parent sock lock is held on entry (by callers),
+	 * and released on exit (by us), according to context.
+	 * (Particularly for BH context & sock not owned case.)
+	 */
+
+	pask->nokey_refcnt -= ask_nokey_refcnt;
+	if (!last)
+		last = !--pask->refcnt;
+
+	if (in_task()) {
+		release_sock(psk);
+	} else {
+		bh_unlock_sock(psk);
+		local_bh_enable();
+	}
+
+	if (last)
+		sock_put(psk);
+}
+
+struct af_alg_release_parent_work {
+	struct work_struct work;
+	struct sock *psk;
+	unsigned int ask_refcnt;
+	unsigned int ask_nokey_refcnt;
+};
+
+void __af_alg_release_parent_work(struct work_struct *_work)
+{
+	struct af_alg_release_parent_work *work =
+		(struct af_alg_release_parent_work *) _work;
+
+	lock_sock(work->psk);
+	__af_alg_release_parent(work->psk, work->ask_refcnt,
+				work->ask_nokey_refcnt);
+	kfree(work);
+}
+
 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;
-
-	sk = ask->parent;
-	ask = alg_sk(sk);
+	struct sock *psk = ask->parent;
+	struct af_alg_release_parent_work *work;
+
+	/*
+	 * This function might race with af_alg_accept() on the parent sock.
+	 *
+	 * That uses lock_sock() to set sk->sk_lock.owned (might sleep) and
+	 * then *releases* the spinlock, relying on owned to sync w/ others;
+	 * later it uses release_sock() to clear it (i.e., mutex semantics.)
+	 *
+	 * So, the sk->sk_lock spinlock alone (e.g., bh_lock_sock()) is not
+	 * sufficient for synchronizing with lock_sock() in af_alg_accept();
+	 * we do need sk->sk_lock.owned to be verified and sleep while set.
+	 *
+	 * This function might be called in non-task/bottom halves context,
+	 * which cannot sleep (i.e., call lock_sock()) so only if owned is
+	 * clear we can change the parent sock (while we hold the spinlock)
+	 * but if it is set, schedule work to do so (runs in task context.)
+	 */
+
+	/* Task context: can sleep; just release parent */
+	if (in_task()) {
+		lock_sock(psk);
+		goto release;
+	}
 
+	/* BH context: cannot sleep; only release parent if sock is not owned */
 	local_bh_disable();
-	bh_lock_sock(sk);
-	ask->nokey_refcnt -= nokey;
-	if (!last)
-		last = !--ask->refcnt;
-	bh_unlock_sock(sk);
+	bh_lock_sock(psk);
+
+	if (!sock_owned_by_user(psk)) {
+		/* No need to set sock owned as we're done when unlocking it */
+		goto release;
+	}
+
+	/* BH context: cannot sleep but have to (sock is owned); schedule work */
+
+	/* Release lock before memory allocation */
+	bh_unlock_sock(psk);
 	local_bh_enable();
 
-	if (last)
-		sock_put(sk);
+	work = kmalloc(sizeof(*work), GFP_ATOMIC);
+	if (!work) {
+		/* Cannot schedule work; log and take the risk (racy but rare) */
+		local_bh_disable();
+		bh_lock_sock(psk);
+
+		/* Check if still owned before the warning (if not, it's safe) */
+		if (sock_owned_by_user(psk))
+			pr_warn_ratelimited("af_alg: parent sock release under race");
+
+		goto release;
+	}
+
+	/* Copy the values from child socket; cannot access it after we return */
+	work->psk = psk;
+	work->ask_refcnt = ask->refcnt;
+	work->ask_nokey_refcnt = ask->nokey_refcnt;
+	INIT_WORK(&work->work, __af_alg_release_parent_work);
+	schedule_work(&work->work);
+	return;
+
+release:
+	__af_alg_release_parent(psk, ask->refcnt, ask->nokey_refcnt);
 }
 EXPORT_SYMBOL_GPL(af_alg_release_parent);
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [v2 PATCH] crypto: af_alg - fix use-after-free in af_alg_accept() due to bh_lock_sock()
  2020-06-05 16:16 [PATCH] crypto: af_alg - fix use-after-free in af_alg_accept() due to bh_lock_sock() Mauricio Faria de Oliveira
@ 2020-06-08  6:48 ` Herbert Xu
  2020-06-09 15:17   ` Mauricio Faria de Oliveira
  0 siblings, 1 reply; 6+ messages in thread
From: Herbert Xu @ 2020-06-08  6:48 UTC (permalink / raw)
  To: Mauricio Faria de Oliveira; +Cc: David S. Miller, linux-crypto

On Fri, Jun 05, 2020 at 01:16:57PM -0300, Mauricio Faria de Oliveira wrote:
> This patch fixes a regression from commit 37f96694cf73 ("crypto: af_alg
>  - Use bh_lock_sock in sk_destruct"), which allows the critical regions
> of af_alg_accept() and af_alg_release_parent() to run concurrently.

Thanks a lot for tracking this down.  I think we don't need something
as complicated as backlog processing used by TCP.  We could simply
change the ref counts to atomic_t and get rid of the locking.

---8<---
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>

diff --git a/crypto/af_alg.c b/crypto/af_alg.c
index b1cd3535c525..28fc323e3fe3 100644
--- a/crypto/af_alg.c
+++ b/crypto/af_alg.c
@@ -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;
diff --git a/crypto/algif_aead.c b/crypto/algif_aead.c
index eb1910b6d434..0ae000a61c7f 100644
--- a/crypto/algif_aead.c
+++ b/crypto/algif_aead.c
@@ -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;
 
diff --git a/crypto/algif_hash.c b/crypto/algif_hash.c
index da1ffa4f7f8d..e71727c25a7d 100644
--- a/crypto/algif_hash.c
+++ b/crypto/algif_hash.c
@@ -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;
 
diff --git a/crypto/algif_skcipher.c b/crypto/algif_skcipher.c
index e2c8ab408bed..ab97b6c7b81d 100644
--- a/crypto/algif_skcipher.c
+++ b/crypto/algif_skcipher.c
@@ -215,7 +215,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;
@@ -227,11 +227,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;
 
diff --git a/include/crypto/if_alg.h b/include/crypto/if_alg.h
index 56527c85d122..088c1ded2714 100644
--- a/include/crypto/if_alg.h
+++ b/include/crypto/if_alg.h
@@ -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;
-- 
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [v2 PATCH] crypto: af_alg - fix use-after-free in af_alg_accept() due to bh_lock_sock()
  2020-06-08  6:48 ` [v2 PATCH] " Herbert Xu
@ 2020-06-09 15:17   ` Mauricio Faria de Oliveira
  2020-06-10  0:21     ` Herbert Xu
  0 siblings, 1 reply; 6+ messages in thread
From: Mauricio Faria de Oliveira @ 2020-06-09 15:17 UTC (permalink / raw)
  To: Herbert Xu; +Cc: David S. Miller, linux-crypto

Hi Herbert,

On Mon, Jun 8, 2020 at 3:49 AM Herbert Xu <herbert@gondor.apana.org.au> wrote:
>
> On Fri, Jun 05, 2020 at 01:16:57PM -0300, Mauricio Faria de Oliveira wrote:
> > This patch fixes a regression from commit 37f96694cf73 ("crypto: af_alg
> >  - Use bh_lock_sock in sk_destruct"), which allows the critical regions
> > of af_alg_accept() and af_alg_release_parent() to run concurrently.
>
> Thanks a lot for tracking this down.  I think we don't need something
> as complicated as backlog processing used by TCP.  We could simply
> change the ref counts to atomic_t and get rid of the locking.
>

Thanks for reviewing and providing an alternative solution.

I had considered a change to atomic refcounts, but it was more pervasive
than I would be comfortable with, being for the first time in the crypto code.
Well, that's certainly not a problem for you! :-)

One other thing I had in mind back then, is that the usage of atomic operations
are often associated with an incurred performance penalty, and since the code
already had locking in place, it also seemed (to me) as another reason not to
change them. (And I wouldn't think of the nokey refcount idea you had.)

Per your knowledge/experience with the crypto subsystem, the changed code
paths are not hot enough to suffer from such implications?

Thanks again!

Kind regards,
Mauricio

> ---8<---
> 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>
>
> diff --git a/crypto/af_alg.c b/crypto/af_alg.c
> index b1cd3535c525..28fc323e3fe3 100644
> --- a/crypto/af_alg.c
> +++ b/crypto/af_alg.c
> @@ -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;
> diff --git a/crypto/algif_aead.c b/crypto/algif_aead.c
> index eb1910b6d434..0ae000a61c7f 100644
> --- a/crypto/algif_aead.c
> +++ b/crypto/algif_aead.c
> @@ -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;
>
> diff --git a/crypto/algif_hash.c b/crypto/algif_hash.c
> index da1ffa4f7f8d..e71727c25a7d 100644
> --- a/crypto/algif_hash.c
> +++ b/crypto/algif_hash.c
> @@ -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;
>
> diff --git a/crypto/algif_skcipher.c b/crypto/algif_skcipher.c
> index e2c8ab408bed..ab97b6c7b81d 100644
> --- a/crypto/algif_skcipher.c
> +++ b/crypto/algif_skcipher.c
> @@ -215,7 +215,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;
> @@ -227,11 +227,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;
>
> diff --git a/include/crypto/if_alg.h b/include/crypto/if_alg.h
> index 56527c85d122..088c1ded2714 100644
> --- a/include/crypto/if_alg.h
> +++ b/include/crypto/if_alg.h
> @@ -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;
> --
> Email: Herbert Xu <herbert@gondor.apana.org.au>
> Home Page: http://gondor.apana.org.au/~herbert/
> PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt



-- 
Mauricio Faria de Oliveira

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [v2 PATCH] crypto: af_alg - fix use-after-free in af_alg_accept() due to bh_lock_sock()
  2020-06-09 15:17   ` Mauricio Faria de Oliveira
@ 2020-06-10  0:21     ` Herbert Xu
  2020-06-10 14:28       ` Mauricio Faria de Oliveira
  0 siblings, 1 reply; 6+ messages in thread
From: Herbert Xu @ 2020-06-10  0:21 UTC (permalink / raw)
  To: Mauricio Faria de Oliveira; +Cc: David S. Miller, linux-crypto

On Tue, Jun 09, 2020 at 12:17:32PM -0300, Mauricio Faria de Oliveira wrote:
>
> Per your knowledge/experience with the crypto subsystem, the changed code
> paths are not hot enough to suffer from such implications?

I don't think replacing a spin-lock with a pair of atomic ops is
going to be too much of an issue.  We can always look at this again
if someone comes up with real numbers of course.

Cheers,
-- 
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [v2 PATCH] crypto: af_alg - fix use-after-free in af_alg_accept() due to bh_lock_sock()
  2020-06-10  0:21     ` Herbert Xu
@ 2020-06-10 14:28       ` Mauricio Faria de Oliveira
  2020-06-12 18:32         ` Mauricio Faria de Oliveira
  0 siblings, 1 reply; 6+ messages in thread
From: Mauricio Faria de Oliveira @ 2020-06-10 14:28 UTC (permalink / raw)
  To: Herbert Xu; +Cc: David S. Miller, linux-crypto

On Tue, Jun 9, 2020 at 9:21 PM Herbert Xu <herbert@gondor.apana.org.au> wrote:
>
> On Tue, Jun 09, 2020 at 12:17:32PM -0300, Mauricio Faria de Oliveira wrote:
> >
> > Per your knowledge/experience with the crypto subsystem, the changed code
> > paths are not hot enough to suffer from such implications?
>
> I don't think replacing a spin-lock with a pair of atomic ops is
> going to be too much of an issue.  We can always look at this again
> if someone comes up with real numbers of course.

Right; I meant the other places as well, where atomic ops were added
(in addition to the existing spinlocks.)

But indeed, real numbers would be great and tell whether or not
there's performance differences.

We're working on that -- Brian (bug reporter) has access to detailed
metrics/stats from the workload, and kindly agreed to set up two
identical instances to compare the numbers.  I'll keep you posted.

Thank you,
Mauricio



>
> Cheers,
> --
> Email: Herbert Xu <herbert@gondor.apana.org.au>
> Home Page: http://gondor.apana.org.au/~herbert/
> PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt



-- 
Mauricio Faria de Oliveira

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [v2 PATCH] crypto: af_alg - fix use-after-free in af_alg_accept() due to bh_lock_sock()
  2020-06-10 14:28       ` Mauricio Faria de Oliveira
@ 2020-06-12 18:32         ` Mauricio Faria de Oliveira
  0 siblings, 0 replies; 6+ messages in thread
From: Mauricio Faria de Oliveira @ 2020-06-12 18:32 UTC (permalink / raw)
  To: Herbert Xu; +Cc: David S. Miller, linux-crypto

Hi Herbert,

On Wed, Jun 10, 2020 at 11:28 AM Mauricio Faria de Oliveira
<mfo@canonical.com> wrote:
>
> On Tue, Jun 9, 2020 at 9:21 PM Herbert Xu <herbert@gondor.apana.org.au> wrote:
> >
> > On Tue, Jun 09, 2020 at 12:17:32PM -0300, Mauricio Faria de Oliveira wrote:
> > >
> > > Per your knowledge/experience with the crypto subsystem, the changed code
> > > paths are not hot enough to suffer from such implications?
> >
> > I don't think replacing a spin-lock with a pair of atomic ops is
> > going to be too much of an issue.  We can always look at this again
> > if someone comes up with real numbers of course.
>
> Right; I meant the other places as well, where atomic ops were added
> (in addition to the existing spinlocks.)
>
> But indeed, real numbers would be great and tell whether or not
> there's performance differences.
>
> We're working on that -- Brian (bug reporter) has access to detailed
> metrics/stats from the workload, and kindly agreed to set up two
> identical instances to compare the numbers.  I'll keep you posted.
>

Just wanted to let you know that the performance is really close for
the two patches,
both on the CPU utilization profile and also on workload results
(#requests over time).

And the tests ran for two days, so it  looks stable.

Thanks!

> Thank you,
> Mauricio
>
>
>
> >
> > Cheers,
> > --
> > Email: Herbert Xu <herbert@gondor.apana.org.au>
> > Home Page: http://gondor.apana.org.au/~herbert/
> > PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
>
>
>
> --
> Mauricio Faria de Oliveira



-- 
Mauricio Faria de Oliveira

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2020-06-12 18:32 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-06-05 16:16 [PATCH] crypto: af_alg - fix use-after-free in af_alg_accept() due to bh_lock_sock() Mauricio Faria de Oliveira
2020-06-08  6:48 ` [v2 PATCH] " Herbert Xu
2020-06-09 15:17   ` Mauricio Faria de Oliveira
2020-06-10  0:21     ` Herbert Xu
2020-06-10 14:28       ` Mauricio Faria de Oliveira
2020-06-12 18:32         ` Mauricio Faria de Oliveira

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).