futex: Fix potential use-after-free in FUTEX_REQUEUE_PI
PATCH 1/1 futex: Fix potential use-after-free in FUTEX_REQUEUE_PI
While working on the futex code, I stumbled over this potential use-after-free scenario.
pi_mutex is a pointer into pi_state, which we drop the reference on in unqueue_me_pi(). So any access to that pointer after that is bad.
Since other sites already do rt_mutex_unlock() with hb->lock held, see for example futex_lock_pi(), simply move the unlock before unqueue_me_pi().
--- kernel/futex.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-)
kernel/futex.c +13 -9
@@ -2813,7 +2813,6 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, {struct hrtimer_sleeper timeout, *to =NULL ;struct rt_mutex_waiter rt_waiter; -struct rt_mutex *pi_mutex =NULL ;struct futex_hash_bucket *hb;union futex_key key2 =FUTEX_KEY_INIT ;struct futex_q q = futex_q_init;
@@ -2905,6 +2904,8 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags,spin_unlock (q.lock_ptr); } }else { +struct rt_mutex *pi_mutex; +/* * We have been woken up by futex_unlock_pi(), a timeout, or a * signal. futex_unlock_pi() will not destroy the lock_ptr nor
@@ -2928,18 +2929,21 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags,if (res) ret = (res <0 ) ? res :0 ; +/* +* If fixup_pi_state_owner() faulted and was unable to handle +* the fault, unlock the rt_mutex and return the fault to +* userspace. +*/ +if (ret == -EFAULT ) { +if (rt_mutex_owner (pi_mutex) == current) +rt_mutex_unlock (pi_mutex); + } +/* Unqueue and drop the lock. */ unqueue_me_pi (&q); } -/* -* If fixup_pi_state_owner() faulted and was unable to handle the -* fault, unlock the rt_mutex and return the fault to userspace. -*/ -if (ret == -EFAULT ) { -if (pi_mutex &&rt_mutex_owner (pi_mutex) == current) -rt_mutex_unlock (pi_mutex); - }else if (ret == -EINTR ) { +if (ret == -EINTR ) {/* * We've already been requeued, but cannot restart by calling * futex_lock_pi() directly. We could restart this syscall, but
Quoted 29 lines · Peter Zijlstra
While working on the futex code, I stumbled over this potential use-after-free scenario.
pi_mutexis a pointer intopi_state, which we drop the reference on inunqueue_me_pi(). So any access to that pointer after that is bad.Since other sites already do
rt_mutex_unlock()withhb->lockheld, see for examplefutex_lock_pi(), simply move the unlock beforeunqueue_me_pi().Signed-off-by Peter Zijlstra (Intel) <[email protected]>--- kernel/futex.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-)kernel/futex.c +0
-1@@ -2813,7 +2813,6 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, {struct hrtimer_sleeper timeout, *to =NULL ;struct rt_mutex_waiter rt_waiter; -struct rt_mutex *pi_mutex =NULL ;struct futex_hash_bucket *hb;union futex_key key2 =FUTEX_KEY_INIT ;struct futex_q q = futex_q_init;
kernel/futex.c static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, · line 2904
Quoted 1 line
kernel/futex.c +0
-0@@ -2905,6 +2904,8 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags,spin_unlock (q.lock_ptr);
In this path the fixup can return -EFAIL as well, so it should drop rtmutex too if it owns it. We should move the rtmutex drop into the fixup functions...
Quoted 27 lines · Thomas Gleixner
Quoted 25 lines · Peter Zijlstra
While working on the futex code, I stumbled over this potential use-after-free scenario.
pi_mutexis a pointer intopi_state, which we drop the reference on inunqueue_me_pi(). So any access to that pointer after that is bad.Since other sites already do
rt_mutex_unlock()withhb->lockheld, see for examplefutex_lock_pi(), simply move the unlock beforeunqueue_me_pi().Signed-off-by Peter Zijlstra (Intel) <[email protected]>--- kernel/futex.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-)kernel/futex.c +0
-1@@ -2813,7 +2813,6 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, {struct hrtimer_sleeper timeout, *to =NULL ;struct rt_mutex_waiter rt_waiter; -struct rt_mutex *pi_mutex =NULL ;
Quoted 8 lines
kernel/futex.c static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, · line 2904
Quoted 5 lines
kernel/futex.c +0
-0struct futex_hash_bucket *hb;union futex_key key2 =FUTEX_KEY_INIT ;struct futex_q q = futex_q_init;@@ -2905,6 +2904,8 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags,spin_unlock (q.lock_ptr);In this path the fixup can return
-EFAILas well, so it should drop rtmutex too if it owns it. We should move the rtmutex drop into the fixup functions...
Urgh, so would really like to avoid doing that, I'll have to instantly drag it back out again :/
Also, the fixup_owner() fail in futex_lock_pi() will unlock the rt_mutex on _any_ fail, not only -EFAULT, should we not do the same?
---
Subject: futex: Fix potential use-after-free in FUTEX_REQUEUE_PI
From: Peter Zijlstra <[email protected]>
Date: Thu, 24 Nov 2016 15:42:35 +0100
While working on the futex code, I stumbled over this potential use-after-free scenario.
pi_mutex is a pointer into pi_state, which we drop the reference on in unqueue_me_pi(). So any access to that pointer after that is bad.
Since other sites already do rt_mutex_unlock() with hb->lock held, see for example futex_lock_pi(), simply move the unlock before unqueue_me_pi().
--- kernel/futex.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-)
kernel/futex.c +13 -9
@@ -2813,7 +2813,6 @@ static int futex_wait_requeue_pi(u32 __u {struct hrtimer_sleeper timeout, *to =NULL ;struct rt_mutex_waiter rt_waiter; -struct rt_mutex *pi_mutex =NULL ;struct futex_hash_bucket *hb;union futex_key key2 =FUTEX_KEY_INIT ;struct futex_q q = futex_q_init;
@@ -2897,6 +2896,8 @@ static int futex_wait_requeue_pi(u32 __uif (q.pi_state && (q.pi_state->owner != current)) {spin_lock (q.lock_ptr); ret =fixup_pi_state_owner (uaddr2, &q, current); +if (ret &&rt_mutex_owner (&q.pi_state->pi_mutex) == current) +rt_mutex_unlock (&q.pi_state->pi_mutex);/* * Drop the reference to the pi state which * the requeue_pi() code acquired for us.
@@ -2905,6 +2906,8 @@ static int futex_wait_requeue_pi(u32 __uspin_unlock (q.lock_ptr); } }else { +struct rt_mutex *pi_mutex; +/* * We have been woken up by futex_unlock_pi(), a timeout, or a * signal. futex_unlock_pi() will not destroy the lock_ptr nor
@@ -2928,18 +2931,19 @@ static int futex_wait_requeue_pi(u32 __uif (res) ret = (res <0 ) ? res :0 ; +/* +* If fixup_pi_state_owner() faulted and was unable to handle +* the fault, unlock the rt_mutex and return the fault to +* userspace. +*/ +if (ret &&rt_mutex_owner (pi_mutex) == current) +rt_mutex_unlock (pi_mutex); +/* Unqueue and drop the lock. */ unqueue_me_pi (&q); } -/* -* If fixup_pi_state_owner() faulted and was unable to handle the -* fault, unlock the rt_mutex and return the fault to userspace. -*/ -if (ret == -EFAULT ) { -if (pi_mutex &&rt_mutex_owner (pi_mutex) == current) -rt_mutex_unlock (pi_mutex); - }else if (ret == -EINTR ) { +if (ret == -EINTR ) {/* * We've already been requeued, but cannot restart by calling * futex_lock_pi() directly. We could restart this syscall, but
Quoted 37 lines · Peter Zijlstra
Quoted 27 lines · Thomas Gleixner
Quoted 25 lines · Peter Zijlstra
While working on the futex code, I stumbled over this potential use-after-free scenario.
pi_mutexis a pointer intopi_state, which we drop the reference on inunqueue_me_pi(). So any access to that pointer after that is bad.Since other sites already do
rt_mutex_unlock()withhb->lockheld, see for examplefutex_lock_pi(), simply move the unlock beforeunqueue_me_pi().Signed-off-by Peter Zijlstra (Intel) <[email protected]>--- kernel/futex.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-)kernel/futex.c +0
-1@@ -2813,7 +2813,6 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, {struct hrtimer_sleeper timeout, *to =NULL ;struct rt_mutex_waiter rt_waiter; -struct rt_mutex *pi_mutex =NULL ;Quoted 8 lines
kernel/futex.c static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, · line 2904
Quoted 5 lines
kernel/futex.c +0
-0struct futex_hash_bucket *hb;union futex_key key2 =FUTEX_KEY_INIT ;struct futex_q q = futex_q_init;@@ -2905,6 +2904,8 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags,spin_unlock (q.lock_ptr);In this path the fixup can return
-EFAILas well, so it should drop rtmutex too if it owns it. We should move the rtmutex drop into the fixup functions...
Quoted 2 lines
Urgh, so would really like to avoid doing that, I'll have to instantly drag it back out again :/
Why would you have to drag it back out again? Something else you're working on?
Signature · 2 lines
Darren Hart Intel Open Source Technology Center
Quoted 9 lines · Darren Hart
Quoted 5 lines · Peter Zijlstra
Quoted 2 lines
In this path the fixup can return
-EFAILas well, so it should drop rtmutex too if it owns it. We should move the rtmutex drop into the fixup functions...Urgh, so would really like to avoid doing that, I'll have to instantly drag it back out again :/
Why would you have to drag it back out again? Something else you're working on?
Yeah, the very reason I've been staring at this mess in the first place :-)
So I could point you at the patches; and I will, see:
https://lkml.kernel.org/r/[email protected]
but the TL;DR version is that we must not rt_mutex_unlock() while holding hb->lock, because on RT hb->lock is itself a rt_mutex which gives rise to some very fun prio inversions.
Quoted 28 lines · Peter Zijlstra
Quoted 27 lines · Thomas Gleixner
Quoted 25 lines · Peter Zijlstra
While working on the futex code, I stumbled over this potential use-after-free scenario.
pi_mutexis a pointer intopi_state, which we drop the reference on inunqueue_me_pi(). So any access to that pointer after that is bad.Since other sites already do
rt_mutex_unlock()withhb->lockheld, see for examplefutex_lock_pi(), simply move the unlock beforeunqueue_me_pi().Signed-off-by Peter Zijlstra (Intel) <[email protected]>--- kernel/futex.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-)kernel/futex.c +0
-1@@ -2813,7 +2813,6 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, {struct hrtimer_sleeper timeout, *to =NULL ;struct rt_mutex_waiter rt_waiter; -struct rt_mutex *pi_mutex =NULL ;
Quoted 8 lines
Quoted 8 lines
kernel/futex.c static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, · line 2904
Quoted 5 lines
kernel/futex.c +0
-0struct futex_hash_bucket *hb;union futex_key key2 =FUTEX_KEY_INIT ;struct futex_q q = futex_q_init;@@ -2905,6 +2904,8 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags,spin_unlock (q.lock_ptr);In this path the fixup can return
-EFAILas well, so it should drop rtmutex too if it owns it. We should move the rtmutex drop into the fixup functions...
I traced through the possible return codes and found:
fixup_pi_state_owner
see below
rt_mutex_finish_proxy_lock
__rt_mutex_slowlock
EINTR
ETIMEDOUT
(ignored if fixup_owner fails)
fixup_owner
fixup_pi_state_owner
fault_in_user_writeable
fixup_user_fault
EFAULT
ENOMEM
EHWPOISON
Quoted 7 lines
Urgh, so would really like to avoid doing that, I'll have to instantly drag it back out again :/
Also, the
fixup_owner()fail infutex_lock_pi()will unlock thert_mutexon _any_ fail, not only-EFAULT, should we not do the same?
I don't see why we should treat ENOMEM or EHWPOISON any differently from EFAULT in this situation.
Quoted 52 lines
---
Subject: futex: Fix potential use-after-free inFUTEX_REQUEUE_PI
From: Peter Zijlstra <[email protected]>
Date: Thu, 24 Nov 2016 15:42:35 +0100While working on the futex code, I stumbled over this potential use-after-free scenario.
pi_mutexis a pointer intopi_state, which we drop the reference on inunqueue_me_pi(). So any access to that pointer after that is bad.Since other sites already do
rt_mutex_unlock()withhb->lockheld, see for examplefutex_lock_pi(), simply move the unlock beforeunqueue_me_pi().Cc Ingo Molnar <[email protected]>Cc Thomas Gleixner <[email protected]>Signed-off-by Peter Zijlstra (Intel) <[email protected]>--- kernel/futex.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-)kernel/futex.c +4
-1@@ -2813,7 +2813,6 @@ static int futex_wait_requeue_pi(u32 __u {struct hrtimer_sleeper timeout, *to =NULL ;struct rt_mutex_waiter rt_waiter; -struct rt_mutex *pi_mutex =NULL ;struct futex_hash_bucket *hb;union futex_key key2 =FUTEX_KEY_INIT ;struct futex_q q = futex_q_init;@@ -2897,6 +2896,8 @@ static int futex_wait_requeue_pi(u32 __uif (q.pi_state && (q.pi_state->owner != current)) {spin_lock (q.lock_ptr); ret =fixup_pi_state_owner (uaddr2, &q, current); +if (ret &&rt_mutex_owner (&q.pi_state->pi_mutex) == current) +rt_mutex_unlock (&q.pi_state->pi_mutex);/* * Drop the reference to the pi state which * the requeue_pi() code acquired for us. @@ -2905,6 +2906,8 @@ static int futex_wait_requeue_pi(u32 __uspin_unlock (q.lock_ptr); } }else { +struct rt_mutex *pi_mutex; +/* * We have been woken up by futex_unlock_pi(), a timeout, or a * signal. futex_unlock_pi() will not destroy the lock_ptr nor
kernel/futex.c static int futex_wait_requeue_pi(u32 __u · line 2935
Quoted 5 lines
kernel/futex.c +2
-0@@ -2928,18 +2931,19 @@ static int futex_wait_requeue_pi(u32 __uif (res) ret = (res <0 ) ? res :0 ; +/* +* If fixup_pi_state_owner() faulted and was unable to handle
faulted or failed ?
kernel/futex.c static int futex_wait_requeue_pi(u32 __u · line 2936
Quoted 1 line
kernel/futex.c +1
-0+* the fault, unlock the rt_mutex and return the fault to
propagate the error to userspace
Quoted 22 lines
kernel/futex.c +6
-8+* userspace. +*/ +if (ret &&rt_mutex_owner (pi_mutex) == current) +rt_mutex_unlock (pi_mutex); +/* Unqueue and drop the lock. */ unqueue_me_pi (&q); } -/* -* If fixup_pi_state_owner() faulted and was unable to handle the -* fault, unlock the rt_mutex and return the fault to userspace. -*/ -if (ret == -EFAULT ) { -if (pi_mutex &&rt_mutex_owner (pi_mutex) == current) -rt_mutex_unlock (pi_mutex); - }else if (ret == -EINTR ) { +if (ret == -EINTR ) {/* * We've already been requeued, but cannot restart by calling * futex_lock_pi() directly. We could restart this syscall, but
Signature · 2 lines
Darren Hart Intel Open Source Technology Center