Patch "futex: Prevent the reuse of stale pi_state" has been added to the 6.7-stable tree

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is a note to let you know that I've just added the patch titled

    futex: Prevent the reuse of stale pi_state

to the 6.7-stable tree which can be found at:
    http://www.kernel.org/git/?p=linux/kernel/git/stable/stable-queue.git;a=summary

The filename of the patch is:
     futex-prevent-the-reuse-of-stale-pi_state.patch
and it can be found in the queue-6.7 subdirectory.

If you, or anyone else, feels it should not be added to the stable tree,
please let <stable@xxxxxxxxxxxxxxx> know about it.



commit daed3912693f49cbe547d04b3a17f34e174810f7
Author: Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx>
Date:   Thu Jan 18 12:54:51 2024 +0100

    futex: Prevent the reuse of stale pi_state
    
    [ Upstream commit e626cb02ee8399fd42c415e542d031d185783903 ]
    
    Jiri Slaby reported a futex state inconsistency resulting in -EINVAL during
    a lock operation for a PI futex. It requires that the a lock process is
    interrupted by a timeout or signal:
    
      T1 Owns the futex in user space.
    
      T2 Tries to acquire the futex in kernel (futex_lock_pi()). Allocates a
         pi_state and attaches itself to it.
    
      T2 Times out and removes its rt_waiter from the rt_mutex. Drops the
         rtmutex lock and tries to acquire the hash bucket lock to remove
         the futex_q. The lock is contended and T2 schedules out.
    
      T1 Unlocks the futex (futex_unlock_pi()). Finds a futex_q but no
         rt_waiter. Unlocks the futex (do_uncontended) and makes it available
         to user space.
    
      T3 Acquires the futex in user space.
    
      T4 Tries to acquire the futex in kernel (futex_lock_pi()). Finds the
         existing futex_q of T2 and tries to attach itself to the existing
         pi_state.  This (attach_to_pi_state()) fails with -EINVAL because uval
         contains the TID of T3 but pi_state points to T1.
    
    It's incorrect to unlock the futex and make it available for user space to
    acquire as long as there is still an existing state attached to it in the
    kernel.
    
    T1 cannot hand over the futex to T2 because T2 already gave up and started
    to clean up and is blocked on the hash bucket lock, so T2's futex_q with
    the pi_state pointing to T1 is still queued.
    
    T2 observes the futex_q, but ignores it as there is no waiter on the
    corresponding rt_mutex and takes the uncontended path which allows the
    subsequent caller of futex_lock_pi() (T4) to observe that stale state.
    
    To prevent this the unlock path must dequeue all futex_q entries which
    point to the same pi_state when there is no waiter on the rt mutex. This
    requires obviously to make the dequeue conditional in the locking path to
    prevent a double dequeue. With that it's guaranteed that user space cannot
    observe an uncontended futex which has kernel state attached.
    
    Fixes: fbeb558b0dd0d ("futex/pi: Fix recursive rt_mutex waiter state")
    Reported-by: Jiri Slaby <jirislaby@xxxxxxxxxx>
    Signed-off-by: Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx>
    Signed-off-by: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
    Tested-by: Jiri Slaby <jirislaby@xxxxxxxxxx>
    Link: https://lore.kernel.org/r/20240118115451.0TkD_ZhB@xxxxxxxxxxxxx
    Closes: https://lore.kernel.org/all/4611bcf2-44d0-4c34-9b84-17406f881003@xxxxxxxxxx
    Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>

diff --git a/kernel/futex/core.c b/kernel/futex/core.c
index dad981a865b8..52d0bf67e715 100644
--- a/kernel/futex/core.c
+++ b/kernel/futex/core.c
@@ -626,12 +626,21 @@ int futex_unqueue(struct futex_q *q)
 }
 
 /*
- * PI futexes can not be requeued and must remove themselves from the
- * hash bucket. The hash bucket lock (i.e. lock_ptr) is held.
+ * PI futexes can not be requeued and must remove themselves from the hash
+ * bucket. The hash bucket lock (i.e. lock_ptr) is held.
  */
 void futex_unqueue_pi(struct futex_q *q)
 {
-	__futex_unqueue(q);
+	/*
+	 * If the lock was not acquired (due to timeout or signal) then the
+	 * rt_waiter is removed before futex_q is. If this is observed by
+	 * an unlocker after dropping the rtmutex wait lock and before
+	 * acquiring the hash bucket lock, then the unlocker dequeues the
+	 * futex_q from the hash bucket list to guarantee consistent state
+	 * vs. userspace. Therefore the dequeue here must be conditional.
+	 */
+	if (!plist_node_empty(&q->list))
+		__futex_unqueue(q);
 
 	BUG_ON(!q->pi_state);
 	put_pi_state(q->pi_state);
diff --git a/kernel/futex/pi.c b/kernel/futex/pi.c
index 90e5197f4e56..5722467f2737 100644
--- a/kernel/futex/pi.c
+++ b/kernel/futex/pi.c
@@ -1135,6 +1135,7 @@ int futex_unlock_pi(u32 __user *uaddr, unsigned int flags)
 
 	hb = futex_hash(&key);
 	spin_lock(&hb->lock);
+retry_hb:
 
 	/*
 	 * Check waiters first. We do not trust user space values at
@@ -1177,12 +1178,17 @@ int futex_unlock_pi(u32 __user *uaddr, unsigned int flags)
 		/*
 		 * Futex vs rt_mutex waiter state -- if there are no rt_mutex
 		 * waiters even though futex thinks there are, then the waiter
-		 * is leaving and the uncontended path is safe to take.
+		 * is leaving. The entry needs to be removed from the list so a
+		 * new futex_lock_pi() is not using this stale PI-state while
+		 * the futex is available in user space again.
+		 * There can be more than one task on its way out so it needs
+		 * to retry.
 		 */
 		rt_waiter = rt_mutex_top_waiter(&pi_state->pi_mutex);
 		if (!rt_waiter) {
+			__futex_unqueue(top_waiter);
 			raw_spin_unlock_irq(&pi_state->pi_mutex.wait_lock);
-			goto do_uncontended;
+			goto retry_hb;
 		}
 
 		get_pi_state(pi_state);
@@ -1217,7 +1223,6 @@ int futex_unlock_pi(u32 __user *uaddr, unsigned int flags)
 		return ret;
 	}
 
-do_uncontended:
 	/*
 	 * We have no kernel internal state, i.e. no waiters in the
 	 * kernel. Waiters which are about to queue themselves are stuck




[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux