Skip to contentSkip to conversation

FAILED: patch "[PATCH] futex/pi: Plug private futex exec() race" failed to apply to 7.1-stable tree

PATCH In review [email protected] 2 participants 3 messages Aug 24 – Aug 25

Subsystems (4)
lore
Share & tools
lore

[email protected]

author maintainer
#0 lore raw

The patch below does not apply to the 7.1-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.

To reproduce the conflict and resubmit, you may use the following commands:

git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-7.1.y
git checkout FETCH_HEAD
git cherry-pick -x c5f0bc9fd1cec4a00400cc727fcde03e0fde17cc
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026082416-unopened-rectify-cdc8@gregkh' --subject-prefix 'PATCH 7.1.y' 'HEAD^..'

Possible dependencies:

thanks,

greg k-h

------------------ original commit in Linus's tree ------------------

From c5f0bc9fd1cec4a00400cc727fcde03e0fde17cc Mon Sep 17 00:00:00 2001
From: Thomas Gleixner <[email protected]>
Date: Fri, 7 Aug 2026 17:07:13 +0200
Subject: [PATCH] futex/pi: Plug private futex exec() race

The check for private futexes whether the waiter's mm, which is stored in the futex_key and copied into the pi_state, is the same as the owner's mm is not sufficient for exec(). exec() has a gap where the mm check fails to give the correct answer:

exec()
...

    exec_release_mm()
      futex_exec_release()
        tsk::futex::exit_state = EXITING;
        cleanup_robust_list();
1)      tsk::futex::exit_state = OK;
    ...
    old_mm = tsk::mm;

2) tsk::mm = ->mm;

Between #1 and #2 the check for the mm is wrong as that mm is about to be swapped out and eventually freed.

Plug this gap by:

1) Setting tsk::futex::exit_state to FUTEX_STATE_DEAD in futex_exec_release()

2) Setting tsk::futex::exit_state to FUTEX_STATE_OK after the mm has been switched.

From a futex point of view the task is dead after it finished the robust list cleanup up to the point where it sets the state to OK again.

Fixes 80367ad01d93 ("futex: Add basic infrastructure for local task local hash")
fs/exec.c +5 -2
index c7b8f2d6366c..d01523d0d8b4 100644
@@ -30,6 +30,7 @@
 #include <linux/mm.h>
 #include <linux/stat.h>
 #include <linux/fcntl.h>
+#include <linux/futex.h>
 #include <linux/swap.h>
 #include <linux/string.h>
 #include <linux/init.h>
@@ -854,6 +855,7 @@ static int exec_mmap(struct linux_binprm *bprm)
 	/* Notify parent that we're no longer interested in the old VM */
 	tsk = current;
 	old_mm = current->mm;
+	/* Clean up futexes and release the mm */
 	exec_mm_release(tsk, old_mm);
 
 	ret = down_write_killable(&tsk->signal->exec_update_lock);
@@ -902,9 +904,10 @@ static int exec_mmap(struct linux_binprm *bprm)
 		BUG_ON(active_mm != old_mm);
 		/* Defer teardown to setup_new_exec(), outside the exec locks. */
 		bprm->old_mm = old_mm;
-		return 0;
+	} else {
+		mmdrop_lazy_tlb(active_mm);
 	}
-	mmdrop_lazy_tlb(active_mm);
+	futex_exec_done(tsk);
 	return 0;
 }
 
include/linux/futex.h +2 -0
index 51f4ccdc9092..51d5faa1266f 100644
@@ -73,6 +73,7 @@ static inline void futex_init_task(struct task_struct *tsk)
 void futex_exit_recursive(struct task_struct *tsk);
 void futex_exit_release(struct task_struct *tsk);
 void futex_exec_release(struct task_struct *tsk);
+void futex_exec_done(struct task_struct *tsk);
 
 long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
 	      u32 __user *uaddr2, u32 val2, u32 val3);
@@ -91,6 +92,7 @@ static inline void futex_init_task(struct task_struct *tsk) { }
 static inline void futex_exit_recursive(struct task_struct *tsk) { }
 static inline void futex_exit_release(struct task_struct *tsk) { }
 static inline void futex_exec_release(struct task_struct *tsk) { }
+static inline void futex_exec_done(struct task_struct *tsk) { }
 static inline long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
 			    u32 __user *uaddr2, u32 val2, u32 val3)
 {
kernel/futex/core.c +41 -21

81 lines not on this page · raw message

kernel/futex/pi.c +16 -6
index 2731e55f99e5..88788e584ec8 100644
@@ -200,15 +200,25 @@ void put_pi_state(struct futex_pi_state *pi_state)
  *
  * - FUTEX_STATE_OK when the task is alive and waiters can be attached
  *
- * - FUTEX_STATE_EXITING when the task cleans up the robust list and pi
+ * - FUTEX_STATE_EXITING when the task cleans up the robust list and PI
  *   state. Concurrent waiters cannot attach anymore and have to wait until the
- *   cleanup is finished to re-evaluate the potential changes of robust list and
- *   pi state cleanups.
+ *   cleanup is finished to re-evaluate the potential changes caused by the
+ *   robust list and PI state cleanups.
  *
- * - FUTEX_STATE_DEAD when the task has cleaned up the robust list and
- *   is about to fully exit.
+ * - FUTEX_STATE_DEAD when the task has cleaned up the robust list. This state
+ *   is set independent of exit() or exec(). In the exit() case the task is
+ *   gone. In the exec() case this ensures that nothing can attach to the task
+ *   after cleaning up the robust list and PI state before it has switched to
+ *   the new mm. From a futex point of view the task is dead until it sets the
+ *   state to FUTEX_STATE_OK again after switching to the new mm.
  *
- * exec() switches back to FUTEX_STATE_OK after the cleanup.
+ * The valid state transitions for exit():
+ *
+ *   FUTEX_STATE_OK -> FUTEX_STATE_EXITING -> FUTEX_STATE_DEAD
+ *
+ * The valid state transitions for exec():
+ *
+ *   FUTEX_STATE_OK -> FUTEX_STATE_EXITING -> FUTEX_STATE_DEAD -> FUTEX_STATE_OK
  *
  * The state has two related locks:
  *
maintainer
#1 lore raw

PATCH 1/1 futex/pi: Plug private futex exec() race

From: Thomas Gleixner <[email protected]>

[ Upstream commit c5f0bc9fd1cec4a00400cc727fcde03e0fde17cc ]

The check for private futexes whether the waiter's mm, which is stored in the futex_key and copied into the pi_state, is the same as the owner's mm is not sufficient for exec(). exec() has a gap where the mm check fails to give the correct answer:

exec()
...

    exec_release_mm()
      futex_exec_release()
        tsk::futex::exit_state = EXITING;
        cleanup_robust_list();
1)      tsk::futex::exit_state = OK;
    ...
    old_mm = tsk::mm;

2) tsk::mm = ->mm;

Between #1 and #2 the check for the mm is wrong as that mm is about to be swapped out and eventually freed.

Plug this gap by:

1) Setting tsk::futex::exit_state to FUTEX_STATE_DEAD in futex_exec_release()

2) Setting tsk::futex::exit_state to FUTEX_STATE_OK after the mm has been switched.

From a futex point of view the task is dead after it finished the robust list cleanup up to the point where it sets the state to OK again.

Fixes 80367ad01d93 ("futex: Add basic infrastructure for local task local hash")
[ Dropped the comment-only kernel/futex/pi.c hunk and renamed `tsk->futex.state` to `tsk->futex_state`, keeping 7.1's lockless DEAD store in `futex_cleanup_end()`. ]
---
 fs/exec.c             |  7 ++++--
 include/linux/futex.h |  2 ++
 kernel/futex/core.c   | 54 +++++++++++++++++++++++++++++--------------
 3 files changed, 44 insertions(+), 19 deletions(-)
fs/exec.c +5 -2
index 4e156bb783217..48409410b15d2 100644
@@ -30,6 +30,7 @@
 #include <linux/mm.h>
 #include <linux/stat.h>
 #include <linux/fcntl.h>
+#include <linux/futex.h>
 #include <linux/swap.h>
 #include <linux/string.h>
 #include <linux/init.h>
@@ -843,6 +844,7 @@ static int exec_mmap(struct mm_struct *mm)
 	/* Notify parent that we're no longer interested in the old VM */
 	tsk = current;
 	old_mm = current->mm;
+	/* Clean up futexes and release the mm */
 	exec_mm_release(tsk, old_mm);
 
 	ret = down_write_killable(&tsk->signal->exec_update_lock);
@@ -891,9 +893,10 @@ static int exec_mmap(struct mm_struct *mm)
 		setmax_mm_hiwater_rss(&tsk->signal->maxrss, old_mm);
 		mm_update_next_owner(old_mm);
 		mmput(old_mm);
-		return 0;
+	} else {
+		mmdrop_lazy_tlb(active_mm);
 	}
-	mmdrop_lazy_tlb(active_mm);
+	futex_exec_done(tsk);
 	return 0;
 }
 
include/linux/futex.h +2 -0
index 9e9750f049805..e93f2be22779f 100644
@@ -77,6 +77,7 @@ static inline void futex_init_task(struct task_struct *tsk)
 void futex_exit_recursive(struct task_struct *tsk);
 void futex_exit_release(struct task_struct *tsk);
 void futex_exec_release(struct task_struct *tsk);
+void futex_exec_done(struct task_struct *tsk);
 
 long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
 	      u32 __user *uaddr2, u32 val2, u32 val3);
@@ -98,6 +99,7 @@ static inline void futex_init_task(struct task_struct *tsk) { }
 static inline void futex_exit_recursive(struct task_struct *tsk) { }
 static inline void futex_exit_release(struct task_struct *tsk) { }
 static inline void futex_exec_release(struct task_struct *tsk) { }
+static inline void futex_exec_done(struct task_struct *tsk) { }
 static inline long do_futex(u32 __user *uaddr, int op, u32 val,
 			    ktime_t *timeout, u32 __user *uaddr2,
 			    u32 val2, u32 val3)
kernel/futex/core.c +37 -17

80 lines not on this page · raw message

Signature · 1 line
2.53.0
author maintainer
#2 lore raw

Re: [PATCH 7.1.y] futex/pi: Plug private futex exec() race

Quoted 3 lines · Sasha Levin

From: Thomas Gleixner <[email protected]>

[ Upstream commit c5f0bc9fd1cec4a00400cc727fcde03e0fde17cc ]

Already queued up :)