All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] kernel/futex: handle the case where we got a "late" waiter
@ 2016-04-15 12:35 Sebastian Andrzej Siewior
  2016-04-19 22:27 ` Davidlohr Bueso
  2016-04-20 11:51 ` [tip:locking/urgent] futex: Handle unlock_pi race gracefully tip-bot for Sebastian Andrzej Siewior
  0 siblings, 2 replies; 5+ messages in thread
From: Sebastian Andrzej Siewior @ 2016-04-15 12:35 UTC (permalink / raw)
  To: Thomas Gleixner; +Cc: Davidlohr Bueso, linux-kernel, Sebastian Andrzej Siewior

futex_unlock_pi() gets uval before taking the hb lock. Now imagine
someone in futex_lock_pi() took the lock. While futex_unlock_pi() waits
for the hb lock, the LOCK_PI sets FUTEX_WAITERS and drops the lock.
Now, futex_unlock_pi() figures out that there is waiter and invokes
wake_futex_pi() with the old uval which does not yet have FUTEX_WAITERS
set. This flaw lets cmpxchg_futex_value_locked() fail and return -EINVAL.

To address this race we could either use get_futex_value_locked() after
we obtained the lock but then we would need to handle the EFAULT case
(which I think means get_user() without the lock).
To avoid this I let wake_futex_pi() detect this and return -EAGAIN so it
can read the new value and try again.

As bad as this may sound: FUTEX_OWNER_DIED is not set while the owner is
unlocking the lock. FUTEX_WAITERS gets set by another task *but* this
means that the owner invoked the futex() syscall rather than doing a
cmpxchg() in userland (and libc does not do that).

Fixes: ccf9e6a80d9e ("futex: Make unlock_pi more robust")
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---
 kernel/futex.c | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/kernel/futex.c b/kernel/futex.c
index 0d557e13823a..2911f54a6dde 100644
--- a/kernel/futex.c
+++ b/kernel/futex.c
@@ -1496,8 +1496,12 @@ static int wake_futex_pi(u32 __user *uaddr, u32 uval, struct futex_q *this,
 
 	if (cmpxchg_futex_value_locked(&curval, uaddr, uval, newval))
 		ret = -EFAULT;
-	else if (curval != uval)
-		ret = -EINVAL;
+	else if (curval != uval) {
+		if ((FUTEX_TID_MASK & curval) == uval)
+			ret = -EAGAIN;
+		else
+			ret = -EINVAL;
+	}
 	if (ret) {
 		raw_spin_unlock_irq(&pi_state->pi_mutex.wait_lock);
 		return ret;
@@ -2852,6 +2856,12 @@ static int futex_unlock_pi(u32 __user *uaddr, unsigned int flags)
 		if (ret == -EFAULT)
 			goto pi_faulted;
 		/*
+		 * Between get_user() and obtaining the hb->lock the uval
+		 * gained a flag. Retry with the new value.
+		 */
+		if (ret == -EAGAIN)
+			goto pi_faulted;
+		/*
 		 * wake_futex_pi has detected invalid state. Tell user
 		 * space.
 		 */
@@ -2883,6 +2893,8 @@ static int futex_unlock_pi(u32 __user *uaddr, unsigned int flags)
 	spin_unlock(&hb->lock);
 	put_futex_key(&key);
 
+	if (ret == -EAGAIN)
+		goto retry;
 	ret = fault_in_user_writeable(uaddr);
 	if (!ret)
 		goto retry;
-- 
2.8.0.rc3

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

* Re: [PATCH] kernel/futex: handle the case where we got a "late" waiter
  2016-04-15 12:35 [PATCH] kernel/futex: handle the case where we got a "late" waiter Sebastian Andrzej Siewior
@ 2016-04-19 22:27 ` Davidlohr Bueso
  2016-04-20  7:09   ` Sebastian Andrzej Siewior
  2016-04-20  7:36   ` Thomas Gleixner
  2016-04-20 11:51 ` [tip:locking/urgent] futex: Handle unlock_pi race gracefully tip-bot for Sebastian Andrzej Siewior
  1 sibling, 2 replies; 5+ messages in thread
From: Davidlohr Bueso @ 2016-04-19 22:27 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior; +Cc: Thomas Gleixner, linux-kernel

On Fri, 15 Apr 2016, Sebastian Andrzej Siewior wrote:

>futex_unlock_pi() gets uval before taking the hb lock. Now imagine
>someone in futex_lock_pi() took the lock. While futex_unlock_pi() waits
>for the hb lock, the LOCK_PI sets FUTEX_WAITERS and drops the lock.
>Now, futex_unlock_pi() figures out that there is waiter and invokes
>wake_futex_pi() with the old uval which does not yet have FUTEX_WAITERS
>set. This flaw lets cmpxchg_futex_value_locked() fail and return -EINVAL.

Hmm but if we're calling futex_unlock_pi() in the first place, doesn't that
indicate that the uval already has FUTEX_WAITERS and therefore failed the
TID->0 transition in userland? That or the thread is bogusly unlocking a
lock that it doesn't own.

This is of course different than the requeue_pi case which can specify
set_waiters but also gets the value via get_futex_value_locked().

Is this a real issue or did you find it by code inspection?

Thanks,
Davidlohr

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

* Re: [PATCH] kernel/futex: handle the case where we got a "late" waiter
  2016-04-19 22:27 ` Davidlohr Bueso
@ 2016-04-20  7:09   ` Sebastian Andrzej Siewior
  2016-04-20  7:36   ` Thomas Gleixner
  1 sibling, 0 replies; 5+ messages in thread
From: Sebastian Andrzej Siewior @ 2016-04-20  7:09 UTC (permalink / raw)
  To: Davidlohr Bueso; +Cc: Thomas Gleixner, linux-kernel

On 04/20/2016 12:27 AM, Davidlohr Bueso wrote:
> On Fri, 15 Apr 2016, Sebastian Andrzej Siewior wrote:
> 
>> futex_unlock_pi() gets uval before taking the hb lock. Now imagine
>> someone in futex_lock_pi() took the lock. While futex_unlock_pi() waits
>> for the hb lock, the LOCK_PI sets FUTEX_WAITERS and drops the lock.
>> Now, futex_unlock_pi() figures out that there is waiter and invokes
>> wake_futex_pi() with the old uval which does not yet have FUTEX_WAITERS
>> set. This flaw lets cmpxchg_futex_value_locked() fail and return -EINVAL.
> 
> Hmm but if we're calling futex_unlock_pi() in the first place, doesn't that
> indicate that the uval already has FUTEX_WAITERS and therefore failed the
> TID->0 transition in userland? That or the thread is bogusly unlocking a
> lock that it doesn't own.

I hacked a testing tool which always did the syscall for LOCK_PI /
UNLOCK_PI and this popped up.

> 
> This is of course different than the requeue_pi case which can specify
> set_waiters but also gets the value via get_futex_value_locked().
> 
> Is this a real issue or did you find it by code inspection?

real issue.
	https://breakpoint.cc/mass-futex2-rl.c

> Thanks,
> Davidlohr

Sebastian

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

* Re: [PATCH] kernel/futex: handle the case where we got a "late" waiter
  2016-04-19 22:27 ` Davidlohr Bueso
  2016-04-20  7:09   ` Sebastian Andrzej Siewior
@ 2016-04-20  7:36   ` Thomas Gleixner
  1 sibling, 0 replies; 5+ messages in thread
From: Thomas Gleixner @ 2016-04-20  7:36 UTC (permalink / raw)
  To: Davidlohr Bueso; +Cc: Sebastian Andrzej Siewior, linux-kernel

On Tue, 19 Apr 2016, Davidlohr Bueso wrote:
> On Fri, 15 Apr 2016, Sebastian Andrzej Siewior wrote:
> 
> > futex_unlock_pi() gets uval before taking the hb lock. Now imagine
> > someone in futex_lock_pi() took the lock. While futex_unlock_pi() waits
> > for the hb lock, the LOCK_PI sets FUTEX_WAITERS and drops the lock.
> > Now, futex_unlock_pi() figures out that there is waiter and invokes
> > wake_futex_pi() with the old uval which does not yet have FUTEX_WAITERS
> > set. This flaw lets cmpxchg_futex_value_locked() fail and return -EINVAL.
> 
> Hmm but if we're calling futex_unlock_pi() in the first place, doesn't that
> indicate that the uval already has FUTEX_WAITERS and therefore failed the
> TID->0 transition in userland? That or the thread is bogusly unlocking a
> lock that it doesn't own.

It can be called unconditionally w/o trying the TID->0 transition in user
space first and we should handle that case.
 
Thanks,

	tglx

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

* [tip:locking/urgent] futex: Handle unlock_pi race gracefully
  2016-04-15 12:35 [PATCH] kernel/futex: handle the case where we got a "late" waiter Sebastian Andrzej Siewior
  2016-04-19 22:27 ` Davidlohr Bueso
@ 2016-04-20 11:51 ` tip-bot for Sebastian Andrzej Siewior
  1 sibling, 0 replies; 5+ messages in thread
From: tip-bot for Sebastian Andrzej Siewior @ 2016-04-20 11:51 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: dave, bigeasy, tglx, hpa, dvhart, peterz, mingo, linux-kernel

Commit-ID:  89e9e66ba1b3bde9d8ea90566c2aee20697ad681
Gitweb:     http://git.kernel.org/tip/89e9e66ba1b3bde9d8ea90566c2aee20697ad681
Author:     Sebastian Andrzej Siewior <bigeasy@linutronix.de>
AuthorDate: Fri, 15 Apr 2016 14:35:39 +0200
Committer:  Thomas Gleixner <tglx@linutronix.de>
CommitDate: Wed, 20 Apr 2016 12:33:13 +0200

futex: Handle unlock_pi race gracefully

If userspace calls UNLOCK_PI unconditionally without trying the TID -> 0
transition in user space first then the user space value might not have the
waiters bit set. This opens the following race:

CPU0	    	      	    CPU1
uval = get_user(futex)
			    lock(hb)
lock(hb)
			    futex |= FUTEX_WAITERS
			    ....
			    unlock(hb)

cmpxchg(futex, uval, newval)

So the cmpxchg fails and returns -EINVAL to user space, which is wrong because
the futex value is valid.

To handle this (yes, yet another) corner case gracefully, check for a flag
change and retry.

[ tglx: Massaged changelog and slightly reworked implementation ]

Fixes: ccf9e6a80d9e ("futex: Make unlock_pi more robust")
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Cc: stable@vger.kernel.org
Cc: Davidlohr Bueso <dave@stgolabs.net>
Cc: Darren Hart <dvhart@linux.intel.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: http://lkml.kernel.org/r/1460723739-5195-1-git-send-email-bigeasy@linutronix.de
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
---
 kernel/futex.c | 25 ++++++++++++++++++++++---
 1 file changed, 22 insertions(+), 3 deletions(-)

diff --git a/kernel/futex.c b/kernel/futex.c
index a5d2e74..fd204e1 100644
--- a/kernel/futex.c
+++ b/kernel/futex.c
@@ -1295,10 +1295,20 @@ static int wake_futex_pi(u32 __user *uaddr, u32 uval, struct futex_q *this,
 	if (unlikely(should_fail_futex(true)))
 		ret = -EFAULT;
 
-	if (cmpxchg_futex_value_locked(&curval, uaddr, uval, newval))
+	if (cmpxchg_futex_value_locked(&curval, uaddr, uval, newval)) {
 		ret = -EFAULT;
-	else if (curval != uval)
-		ret = -EINVAL;
+	} else if (curval != uval) {
+		/*
+		 * If a unconditional UNLOCK_PI operation (user space did not
+		 * try the TID->0 transition) raced with a waiter setting the
+		 * FUTEX_WAITERS flag between get_user() and locking the hash
+		 * bucket lock, retry the operation.
+		 */
+		if ((FUTEX_TID_MASK & curval) == uval)
+			ret = -EAGAIN;
+		else
+			ret = -EINVAL;
+	}
 	if (ret) {
 		raw_spin_unlock_irq(&pi_state->pi_mutex.wait_lock);
 		return ret;
@@ -2623,6 +2633,15 @@ retry:
 		if (ret == -EFAULT)
 			goto pi_faulted;
 		/*
+		 * A unconditional UNLOCK_PI op raced against a waiter
+		 * setting the FUTEX_WAITERS bit. Try again.
+		 */
+		if (ret == -EAGAIN) {
+			spin_unlock(&hb->lock);
+			put_futex_key(&key);
+			goto retry;
+		}
+		/*
 		 * wake_futex_pi has detected invalid state. Tell user
 		 * space.
 		 */

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

end of thread, other threads:[~2016-04-20 11:52 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-04-15 12:35 [PATCH] kernel/futex: handle the case where we got a "late" waiter Sebastian Andrzej Siewior
2016-04-19 22:27 ` Davidlohr Bueso
2016-04-20  7:09   ` Sebastian Andrzej Siewior
2016-04-20  7:36   ` Thomas Gleixner
2016-04-20 11:51 ` [tip:locking/urgent] futex: Handle unlock_pi race gracefully tip-bot for Sebastian Andrzej Siewior

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.