Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > linux.kernel > #1379787 > unrolled thread

[PATCH] kernel/futex: handle the case where we got a "late" waiter

Started bySebastian Andrzej Siewior <bigeasy@linutronix.de>
First post2016-04-15 14:40 +0200
Last post2016-04-20 14:00 +0200
Articles 5 — 4 participants

Back to article view | Back to linux.kernel


Contents

  [PATCH] kernel/futex: handle the case where we got a "late" waiter Sebastian Andrzej Siewior <bigeasy@linutronix.de> - 2016-04-15 14:40 +0200
    Re: [PATCH] kernel/futex: handle the case where we got a "late"  waiter Davidlohr Bueso <dave@stgolabs.net> - 2016-04-20 00:30 +0200
      Re: [PATCH] kernel/futex: handle the case where we got a "late"  waiter Sebastian Andrzej Siewior <bigeasy@linutronix.de> - 2016-04-20 09:10 +0200
      Re: [PATCH] kernel/futex: handle the case where we got a "late"  waiter Thomas Gleixner <tglx@linutronix.de> - 2016-04-20 09:40 +0200
    [tip:locking/urgent] futex: Handle unlock_pi race gracefully tip-bot for Sebastian Andrzej Siewior <tipbot@zytor.com> - 2016-04-20 14:00 +0200

#1379787 — [PATCH] kernel/futex: handle the case where we got a "late" waiter

FromSebastian Andrzej Siewior <bigeasy@linutronix.de>
Date2016-04-15 14:40 +0200
Subject[PATCH] kernel/futex: handle the case where we got a "late" waiter
Message-ID<robct-32n-3@gated-at.bofh.it>
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

[toc] | [next] | [standalone]


#1382866 — Re: [PATCH] kernel/futex: handle the case where we got a "late" waiter

FromDavidlohr Bueso <dave@stgolabs.net>
Date2016-04-20 00:30 +0200
SubjectRe: [PATCH] kernel/futex: handle the case where we got a "late" waiter
Message-ID<rpMjE-5XD-1@gated-at.bofh.it>
In reply to#1379787
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

[toc] | [prev] | [next] | [standalone]


#1383088 — Re: [PATCH] kernel/futex: handle the case where we got a "late" waiter

FromSebastian Andrzej Siewior <bigeasy@linutronix.de>
Date2016-04-20 09:10 +0200
SubjectRe: [PATCH] kernel/futex: handle the case where we got a "late" waiter
Message-ID<rpUqR-3QU-1@gated-at.bofh.it>
In reply to#1382866
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

[toc] | [prev] | [next] | [standalone]


#1383110 — Re: [PATCH] kernel/futex: handle the case where we got a "late" waiter

FromThomas Gleixner <tglx@linutronix.de>
Date2016-04-20 09:40 +0200
SubjectRe: [PATCH] kernel/futex: handle the case where we got a "late" waiter
Message-ID<rpUTT-43d-5@gated-at.bofh.it>
In reply to#1382866
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

[toc] | [prev] | [next] | [standalone]


#1383327 — [tip:locking/urgent] futex: Handle unlock_pi race gracefully

Fromtip-bot for Sebastian Andrzej Siewior <tipbot@zytor.com>
Date2016-04-20 14:00 +0200
Subject[tip:locking/urgent] futex: Handle unlock_pi race gracefully
Message-ID<rpYXw-7jN-9@gated-at.bofh.it>
In reply to#1379787
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.
 		 */

[toc] | [prev] | [standalone]


Back to top | Article view | linux.kernel


csiph-web