From: Sasha Levin <sashal@kernel.org>
To: stable@vger.kernel.org
Cc: Jann Horn <jannh@google.com>,
"Peter Zijlstra (Intel)" <peterz@infradead.org>,
Sasha Levin <sashal@kernel.org>
Subject: [PATCH 6.18.y] futex: Fix mm reuse handling for FUT_OFF_MMSHARED
Date: Wed, 21 Oct 2026 11:48:29 -0400 [thread overview]
Message-ID: <20261021154743.296641-1-sashal@kernel.org> (raw)
In-Reply-To: <2026102123-unrated-shading-f030@gregkh>
From: Jann Horn <jannh@google.com>
[ Upstream commit 3267512ad1c2cc0c7768c5b014dc2cceb398a801 ]
A FUT_OFF_MMSHARED futex is a shared futex that refers to an MM.
It is possible for a process to wait on a shared futex with a different MM
because a FUT_OFF_INODE waiter can be requeued onto a FUT_OFF_MMSHARED
futex by another process.
This can cause FUT_OFF_MMSHARED waiters on a freed MM to consume
wakeups intended for a newly allocated MM at the same address.
Fix it by keying FUT_OFF_MMSHARED using a unique 64-bit per-MM ID.
Leave private futexes as before to avoid influencing the performance of the
hotpath.
(Multi-threaded processes typically implicitly use FUT_OFF_MMSHARED by
setting clear_child_tid such that it points into anonymous memory, which
causes mm_release() in a multi-threaded mm to perform FUTEX_WAKE.)
Fixes: 222993395ed3 ("futex: Remove pointless mmgrap() + mmdrop()")
Closes: https://lore.kernel.org/r/CAG48ez0dLBpc3QtbAhMMVNHwHL94iHh2G+h-=BVFR4dDuzZr1g@mail.gmail.com/
Signed-off-by: Jann Horn <jannh@google.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20260929-futex-mmshared-fix-v1-1-262b1ffeb3d0@google.com
Cc: stable@vger.kernel.org
[ sashal: adapted mm->futex.unique_id access to mm->futex_unique_id because struct futex_mm_data is absent. ]
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
include/linux/futex.h | 18 ++++++----
include/linux/mm_types.h | 3 ++
kernel/futex/core.c | 75 ++++++++++++++++++++++------------------
3 files changed, 56 insertions(+), 40 deletions(-)
diff --git a/include/linux/futex.h b/include/linux/futex.h
index e93f2be22779f..01febbc9ef1ee 100644
--- a/include/linux/futex.h
+++ b/include/linux/futex.h
@@ -23,28 +23,32 @@ struct task_struct;
* 01 : Shared futex (PTHREAD_PROCESS_SHARED)
* mapped on a file (reference on the underlying inode)
* 10 : Shared futex (PTHREAD_PROCESS_SHARED)
- * (but private mapping on an mm, and reference taken on it)
+ * (but private mapping on an mm)
*/
-#define FUT_OFF_INODE 1 /* We set bit 0 if key has a reference on inode */
-#define FUT_OFF_MMSHARED 2 /* We set bit 1 if key has a reference on mm */
+#define FUT_OFF_INODE 1 /* We set bit 0 if shared key identifies an inode */
+#define FUT_OFF_MMSHARED 2 /* We set bit 1 if shared key identifies an mm */
union futex_key {
+ /* For FUT_OFF_INODE */
struct {
u64 i_seq;
unsigned long pgoff;
unsigned int offset;
/* unsigned int node; */
} shared;
+
+ /* For FUT_OFF_MMSHARED or private */
struct {
union {
- struct mm_struct *mm;
- u64 __tmp;
+ struct mm_struct *mm; /* for private */
+ u64 mm_seq; /* for FUT_OFF_MMSHARED */
};
unsigned long address;
unsigned int offset;
/* unsigned int node; */
} private;
+
struct {
u64 ptr;
unsigned long word;
@@ -83,15 +87,15 @@ long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
u32 __user *uaddr2, u32 val2, u32 val3);
int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4);
+int futex_mm_init(struct mm_struct *mm);
+
#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 */
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 */
#else /* !CONFIG_FUTEX */
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index f9c552744b71a..08495d38572f3 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -1079,6 +1079,9 @@ struct mm_struct {
*/
seqcount_t mm_lock_seq;
#endif
+#ifdef CONFIG_FUTEX
+ atomic64_t futex_unique_id;
+#endif
#ifdef CONFIG_FUTEX_PRIVATE_HASH
struct mutex futex_hash_lock;
struct futex_private_hash __rcu *futex_phash;
diff --git a/kernel/futex/core.c b/kernel/futex/core.c
index 69f340cddfd1f..e6fbeb2ac1901 100644
--- a/kernel/futex/core.c
+++ b/kernel/futex/core.c
@@ -441,45 +441,51 @@ futex_setup_timer(ktime_t *time, struct hrtimer_sleeper *timeout,
}
/*
- * Generate a machine wide unique identifier for this inode.
+ * Generate a machine wide unique identifier for this object (inode or mm).
*
* This relies on u64 not wrapping in the life-time of the machine; which with
* 1ns resolution means almost 585 years.
- *
- * This further relies on the fact that a well formed program will not unmap
- * the file while it has a (shared) futex waiting on it. This mapping will have
- * a file reference which pins the mount and inode.
- *
- * If for some reason an inode gets evicted and read back in again, it will get
- * a new sequence number and will _NOT_ match, even though it is the exact same
- * file.
- *
- * It is important that futex_match() will never have a false-positive, esp.
- * for PI futexes that can mess up the state. The above argues that false-negatives
- * are only possible for malformed programs.
*/
-static u64 get_inode_sequence_number(struct inode *inode)
+static u64 get_object_id(atomic64_t *object_seq)
{
- static atomic64_t i_seq;
+ static atomic64_t last_assigned_seq;
u64 old;
- /* Does the inode already have a sequence number? */
- old = atomic64_read(&inode->i_sequence);
+ /* Does the object already have a sequence number? */
+ old = atomic64_read(object_seq);
if (likely(old))
return old;
for (;;) {
- u64 new = atomic64_inc_return(&i_seq);
+ u64 new = atomic64_inc_return(&last_assigned_seq);
if (WARN_ON_ONCE(!new))
continue;
old = 0;
- if (!atomic64_try_cmpxchg_relaxed(&inode->i_sequence, &old, new))
+ if (!atomic64_try_cmpxchg_relaxed(object_seq, &old, new))
return old;
return new;
}
}
+/*
+ * This relies on the fact that a well formed program will not unmap
+ * the file while it has a (shared) futex waiting on it. This mapping will have
+ * a file reference which pins the mount and inode.
+ *
+ * If for some reason an inode gets evicted and read back in again, it will get
+ * a new sequence number and will _NOT_ match, even though it is the exact same
+ * file.
+ *
+ * It is important that futex_match() will never have a false-positive, esp.
+ * for PI futexes that can mess up the state. The above argues that false-negatives
+ * are only possible for malformed programs.
+ */
+static u64 get_inode_sequence_number(struct inode *inode)
+{
+ return get_object_id(&inode->i_sequence);
+}
+
/**
* get_futex_key() - Get parameters which are the keys for a futex
* @uaddr: virtual address of the futex
@@ -685,8 +691,8 @@ int get_futex_key(u32 __user *uaddr, unsigned int flags, union futex_key *key,
goto out;
}
- key->both.offset |= FUT_OFF_MMSHARED; /* ref taken on mm */
- key->private.mm = mm;
+ key->both.offset |= FUT_OFF_MMSHARED;
+ key->private.mm_seq = get_object_id(&mm->futex_unique_id);
key->private.address = address;
} else {
@@ -1724,18 +1730,6 @@ 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)
-{
- mutex_init(&mm->futex_hash_lock);
- RCU_INIT_POINTER(mm->futex_phash, NULL);
- mm->futex_phash_new = NULL;
- /* futex-ref */
- 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)
{
struct futex_private_hash *fph;
@@ -1982,6 +1976,21 @@ static int futex_hash_get_slots(void)
#endif
+int futex_mm_init(struct mm_struct *mm)
+{
+ atomic64_set(&mm->futex_unique_id, 0);
+#ifdef CONFIG_FUTEX_PRIVATE_HASH
+ mutex_init(&mm->futex_hash_lock);
+ RCU_INIT_POINTER(mm->futex_phash, NULL);
+ mm->futex_phash_new = NULL;
+ /* futex-ref */
+ mm->futex_ref = NULL;
+ atomic_long_set(&mm->futex_atomic, 0);
+ mm->futex_batches = get_state_synchronize_rcu();
+#endif
+ return 0;
+}
+
int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4)
{
unsigned int flags = FH_CUSTOM;
--
2.53.0
prev parent reply other threads:[~2026-10-21 15:48 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-21 14:40 FAILED: patch "[PATCH] futex: Fix mm reuse handling for FUT_OFF_MMSHARED" failed to apply to 6.18-stable tree gregkh
2026-10-21 15:48 ` Sasha Levin [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261021154743.296641-1-sashal@kernel.org \
--to=sashal@kernel.org \
--cc=jannh@google.com \
--cc=peterz@infradead.org \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox