FAILED: patch "[PATCH] futex: Fix race on the initial mm->futex.phash.ref allocation" failed to apply to 7.1-stable tree
Subsystems (2)
[email protected]
maintainerThe 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 bde0238083647381d4747355c5a19115a3422b96
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026082406-engaged-statistic-82bb@gregkh' --subject-prefix 'PATCH 7.1.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From bde0238083647381d4747355c5a19115a3422b96 Mon Sep 17 00:00:00 2001
From: Hyunwoo Kim <[email protected]>
Date: Tue, 11 Aug 2026 23:03:16 +0900
Subject: [PATCH] futex: Fix race on the initial mm->futex.phash.ref allocation
futex_hash_allocate() allocates mm->futex.phash.ref without any locking. Commit d9b05321e21e ("futex: Move futex_hash_free() back to __mmput()") moved the allocation here and assumed that the process has just a single thread at this point.
Commit ee9dce44362b ("futex: Drop CLONE_THREAD requirement for private default hash alloc") widened need_futex_hash_allocate_default() to cover any CLONE_VM clone, but left out vfork because the parent is suspended and cannot race.
That no longer holds once vfork is nested. If a vfork child calls vfork again and is then killed with SIGKILL, the parent is released from its vfork wait and runs concurrently with the grandchild in the same mm. Neither of them went through futex_hash_allocate_default().
When both call prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS) at the same time, each one sees mm->futex.phash.ref as NULL and stores its own percpu counter. Only the last store survives. The counter stored first is no longer reachable from the mm, so the references on it are not seen by __futex_ref_atomic_end(). A private hash that still has references is then considered dead and freed, and a task that still holds one of its buckets writes into freed memory in futex_q_lock().
Store the counter once with cmpxchg() and let the loser free_percpu() its own. The initial reference has to be taken before the store, otherwise another task can install a private hash while the counter is still 0.
kernel/futex/core.c +11 -7
@@ -1857,14 +1857,18 @@ static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags) }if (!mm->futex.phash.ref) { -/* -* This will always be allocated by the first thread and -* therefore requires no locking. -*/ - mm->futex.phash.ref = alloc_percpu(unsigned int); -if (!mm->futex.phash.ref) + unsigned int __percpu *ref = alloc_percpu(unsigned int); + +if (!ref)return -ENOMEM ; -this_cpu_inc (*mm->futex.phash.ref);/* 0 -> 1 */ + +/* +* Tasks sharing the mm can run this concurrently, so take the +* initial reference before publishing the counter. +*/ +this_cpu_inc (*ref);/* 0 -> 1 */ +if (cmpxchg (&mm->futex.phash.ref,NULL , ref)) +free_percpu (ref); } fph = kvzalloc(struct_size(fph, queues, hash_slots),
Sasha Levin
maintainerPATCH 1/3 futex: Make futex_mm_init() void
From: Thomas Gleixner <[email protected]>
[ Upstream commit d7b3f52c861f54ba2fff15696d3798277fb4c19f ]
Nothing fails there. Mop up the leftovers of the early version of this, which did an allocation.
While at it clean up the stubs and the #ifdef comments to make the header file readable.
Stable-dep-of: bde023808364 ("futex: Fix race on the initial mm->futex.phash.ref allocation")
--- include/linux/futex.h | 28 +++++++++++----------------- kernel/fork.c | 8 ++------ kernel/futex/core.c | 3 +-- 3 files changed, 14 insertions(+), 25 deletions(-)
include/linux/futex.h +11 -17
@@ -85,22 +85,20 @@ int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4)#ifdef CONFIG_FUTEX_PRIVATE_HASH int futex_hash_allocate_default (void );void futex_hash_free (struct mm_struct *mm); -int futex_mm_init (struct mm_struct *mm); - -#else /* !CONFIG_FUTEX_PRIVATE_HASH */ +void futex_mm_init (struct mm_struct *mm); +#else /* CONFIG_FUTEX_PRIVATE_HASH */ static inline int futex_hash_allocate_default (void ) {return 0 ; }static inline int futex_hash_free (struct mm_struct *mm) {return 0 ; } -static inline int futex_mm_init (struct mm_struct *mm) {return 0 ; } -#endif /* CONFIG_FUTEX_PRIVATE_HASH */ +static inline void futex_mm_init (struct mm_struct *mm) { } +#endif /* !CONFIG_FUTEX_PRIVATE_HASH */ -#else /* !CONFIG_FUTEX */ +#else /* CONFIG_FUTEX */ 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 long do_futex (u32 __user *uaddr,int op,u32 val, -ktime_t *timeout,u32 __user *uaddr2, -u32 val2,u32 val3) +static inline long do_futex (u32 __user *uaddr,int op,u32 val,ktime_t *timeout, +u32 __user *uaddr2,u32 val2,u32 val3) {return -EINVAL ; }
@@ -108,13 +106,9 @@ static inline int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsig {return -EINVAL ; } -static inline int futex_hash_allocate_default (void ) -{ -return 0 ; -} +static inline int futex_hash_allocate_default (void ) {return 0 ; }static inline int futex_hash_free (struct mm_struct *mm) {return 0 ; } -static inline int futex_mm_init (struct mm_struct *mm) {return 0 ; } +static inline void futex_mm_init (struct mm_struct *mm) { } +#endif /* !CONFIG_FUTEX */ -#endif - -#endif +#endif /* _LINUX_FUTEX_H */
kernel/fork.c +2 -6
@@ -1106,6 +1106,7 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,#endif mm_init_uprobes_state (mm);hugetlb_count_init (mm); +futex_mm_init (mm);mm_flags_clear_all (mm);if (current->mm) {
@@ -1118,11 +1119,8 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p, mm->def_flags =0 ; } -if (futex_mm_init (mm)) - goto fail_mm_init; -if (mm_alloc_pgd (mm)) - goto fail_nopgd; + goto fail_mm_init;if (mm_alloc_id (mm)) goto fail_noid;
@@ -1149,8 +1147,6 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,mm_free_id (mm); fail_noid:mm_free_pgd (mm); -fail_nopgd: -futex_hash_free (mm); fail_mm_init:free_mm (mm);return NULL ;
kernel/futex/core.c +1 -2
@@ -1750,7 +1750,7 @@ static bool futex_ref_is_dead(struct futex_private_hash *fph)return atomic_long_read (&mm->futex_atomic) ==0 ; } -int futex_mm_init (struct mm_struct *mm) +void futex_mm_init (struct mm_struct *mm) {mutex_init (&mm->futex_hash_lock);RCU_INIT_POINTER (mm->futex_phash,NULL );
@@ -1759,7 +1759,6 @@ int futex_mm_init(struct mm_struct *mm) mm->futex_ref =NULL ;atomic_long_set (&mm->futex_atomic,0 ); mm->futex_batches =get_state_synchronize_rcu (); -return 0 ; }void futex_hash_free (struct mm_struct *mm)
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/3 futex: Move futex related mm_struct data into a struct
From: Thomas Gleixner <[email protected]>
[ Upstream commit 1f7f4816b9b05e5110bc1c8a05c3c478e2dae11b ]
Having all these members in mm_struct along with the required #ifdeffery is annoying, does not allow efficient initializing of the data with memset() and makes extending it tedious.
Move it into a data structure and fix up all usage sites.
The extra struct for the private hash is intentional to make integration of other conditional mechanisms easier in terms of initialization and separation.
Stable-dep-of: bde023808364 ("futex: Fix race on the initial mm->futex.phash.ref allocation")
--- include/linux/futex_types.h | 45 ++++++++++++ include/linux/mm_types.h | 12 +--- kernel/futex/core.c | 133 +++++++++++++++++------------------- 3 files changed, 108 insertions(+), 82 deletions(-) create mode 100644 include/linux/futex_types.h
include/linux/futex_types.h new file +45 -0
@@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_FUTEX_TYPES_H +#define _LINUX_FUTEX_TYPES_H + +#ifdef CONFIG_FUTEX +#include <linux/compiler_types.h> +#include <linux/mutex_types.h> +#include <linux/types.h> + +#ifdef CONFIG_FUTEX_PRIVATE_HASH +/** +* struct futex_mm_phash - Futex private hash related per MM data +* @lock: Mutex to protect the private hash operations +* @hash: RCU managed pointer to the private hash +* @hash_new: Pointer to a newly allocated private hash +* @batches: Batch state for RCU synchronization +* @rcu: RCU head for call_rcu() +* @atomic: Aggregate value for @hash_ref +* @ref: Per CPU reference counter for a private hash +*/ +struct futex_mm_phash { +struct mutex lock; +struct futex_private_hash __rcu *hash; +struct futex_private_hash *hash_new; +unsigned long batches; +struct rcu_head rcu; +atomic_long_t atomic; +unsigned int __percpu *ref; +}; +#else /* CONFIG_FUTEX_ROBUST_UNLOCK */ +struct futex_mm_phash { }; +#endif /* !CONFIG_FUTEX_ROBUST_UNLOCK */ + +/** +* struct futex_mm_data - Futex related per MM data +* @phash: Futex private hash related data +*/ +struct futex_mm_data { +struct futex_mm_phash phash; +}; +#else /* CONFIG_FUTEX */ +struct futex_mm_data { }; +#endif /* !CONFIG_FUTEX */ + +#endif /* _LINUX_FUTEX_TYPES_H */
include/linux/mm_types.h +2 -10
@@ -20,6 +20,7 @@#include <linux/seqlock.h> #include <linux/percpu_counter.h> #include <linux/types.h> +#include <linux/futex_types.h> #include <linux/rseq_types.h> #include <linux/bitmap.h>
@@ -1270,16 +1271,7 @@ struct mm_struct {*/ seqcount_t mm_lock_seq;#endif -#ifdef CONFIG_FUTEX_PRIVATE_HASH -struct mutex futex_hash_lock; -struct futex_private_hash __rcu *futex_phash; -struct futex_private_hash *futex_phash_new; -/* futex-ref */ -unsigned long futex_batches; -struct rcu_head futex_rcu; -atomic_long_t futex_atomic; -unsigned int __percpu *futex_ref; -#endif +struct futex_mm_data futex;unsigned long hiwater_rss;/* High-watermark of RSS usage */ unsigned long hiwater_vm;/* High-water virtual memory usage */
kernel/futex/core.c +61 -72
355 lines not on this page · raw message
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/3 futex: Fix race on the initial mm->futex.phash.ref allocation
In reply to Sasha LevinFrom: Hyunwoo Kim <[email protected]>
[ Upstream commit bde0238083647381d4747355c5a19115a3422b96 ]
futex_hash_allocate() allocates mm->futex.phash.ref without any locking. Commit d9b05321e21e ("futex: Move futex_hash_free() back to __mmput()") moved the allocation here and assumed that the process has just a single thread at this point.
Commit ee9dce44362b ("futex: Drop CLONE_THREAD requirement for private default hash alloc") widened need_futex_hash_allocate_default() to cover any CLONE_VM clone, but left out vfork because the parent is suspended and cannot race.
That no longer holds once vfork is nested. If a vfork child calls vfork again and is then killed with SIGKILL, the parent is released from its vfork wait and runs concurrently with the grandchild in the same mm. Neither of them went through futex_hash_allocate_default().
When both call prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS) at the same time, each one sees mm->futex.phash.ref as NULL and stores its own percpu counter. Only the last store survives. The counter stored first is no longer reachable from the mm, so the references on it are not seen by __futex_ref_atomic_end(). A private hash that still has references is then considered dead and freed, and a task that still holds one of its buckets writes into freed memory in futex_q_lock().
Store the counter once with cmpxchg() and let the loser free_percpu() its own. The initial reference has to be taken before the store, otherwise another task can install a private hash while the counter is still 0.
--- kernel/futex/core.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-)
kernel/futex/core.c +10 -6
@@ -1823,14 +1823,18 @@ static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags) }if (!mm->futex.phash.ref) { + unsigned int __percpu *ref = alloc_percpu(unsigned int); + +if (!ref) +return -ENOMEM ; +/* -* This will always be allocated by the first thread and -* therefore requires no locking. +* Tasks sharing the mm can run this concurrently, so take the +* initial reference before publishing the counter. */ - mm->futex.phash.ref = alloc_percpu(unsigned int); -if (!mm->futex.phash.ref) -return -ENOMEM ; -this_cpu_inc (*mm->futex.phash.ref);/* 0 -> 1 */ +this_cpu_inc (*ref);/* 0 -> 1 */ +if (cmpxchg (&mm->futex.phash.ref,NULL , ref)) +free_percpu (ref); } fph = kvzalloc(struct_size(fph, queues, hash_slots),
Signature · 1 line
2.53.0