[PATCH] futex: Avoid private hash use-after-free on final put
From: Felix Hoffmann
Date: Fri Jul 31 2026 - 11:58:52 EST
futex_private_hash_put() drops the reference to fph before evaluating
fph->mm for wake_up_var(). futex_ref_put() enables preemption again before
returning. If that put drops the final reference and the task is preempted,
another task can pivot to the replacement hash and free the old hash after
an RCU grace period. The first task then reads fph->mm from the freed
allocation when it resumes.
KASAN reports a slab-use-after-free in futex_private_hash_put(), with the
read at offset 24 in a freed kmalloc-512 allocation. The allocation and
free stacks point to futex_hash_allocate() and the RCU free path,
respectively.
Load the mm pointer while the fph reference is still held and pass the
saved value to wake_up_var(). wake_up_var() uses the pointer as a waitqueue
key and does not dereference the mm through it.
Fixes: bd54df5ea7ca ("futex: Allow to resize the private local hash")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Felix Hoffmann <f3lix.dev@xxxxxx>
---
Tested on mainline 8ba098e6b6ff with CONFIG_KASAN,
CONFIG_PROVE_LOCKING, CONFIG_PREEMPT_DYNAMIC, and
CONFIG_FUTEX_PRIVATE_HASH. The unpatched kernel reported the UAF with
KASAN recording UID 65534.
The exact signed commit completed a 60-second timing-amplified run with
1,278 hash resizes and 20,142 retained final-put trace events after
dropping to UID/GID 65534, without a KASAN, lockdep, warning, oops, or
kernel BUG report. A source reproducer is available privately on request.
kernel/futex/core.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/kernel/futex/core.c b/kernel/futex/core.c
index 179b26e9c934..90fa9d886f75 100644
--- a/kernel/futex/core.c
+++ b/kernel/futex/core.c
@@ -143,8 +143,14 @@ static bool futex_private_hash_get(struct futex_private_hash *fph)
void futex_private_hash_put(struct futex_private_hash *fph)
{
- if (fph && futex_ref_put(fph))
- wake_up_var(fph->mm);
+ struct mm_struct *mm;
+
+ if (!fph)
+ return;
+
+ mm = fph->mm;
+ if (futex_ref_put(fph))
+ wake_up_var(mm);
}
static struct futex_hash_bucket *
--
2.43.0