Re: [REPORT] futex: private hash refcount corruption and UAF via CLONE_VM PR_FUTEX_HASH race
From: 钱一铭
Date: Thu Apr 30 2026 - 21:18:22 EST
> On Thu, 30 Apr 2026, ????????? wrote:
Yiming Qian wrote, please
On Fri, May 1, 2026 at 9:12 AM Davidlohr Bueso <dave@xxxxxxxxxxxx> wrote:
>
> On Thu, 30 Apr 2026, ????????? wrote:
>
> >Hello,
> >
> >I found a race in the futex private hash implementation that can
> >corrupt the private-hash reference state and free a live `struct
> >futex_private_hash`. I can trigger a KASAN slab-use-after-free in
> >`futex_hash_put()` from an unprivileged process inside QEMU.
> >
> >## Summary
> >
> >`futex_hash_allocate()` lazily initializes `current->mm->futex_ref`
> >without serialization. The code comment assumes the allocation is
> >always performed by the first thread, but `clone(CLONE_VM | SIGCHLD)`
> >creates another task that shares the same `mm_struct` while not
> >setting `CLONE_THREAD`. Such tasks bypass
> >`need_futex_hash_allocate_default()` and can concurrently call
> >`prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, ...)` while
> >`mm->futex_ref` is still `NULL`.
>
> So the code doesn't hold with that comment in futex_hash_allocate()
> when things get creative.
>
> afaict we can either assume some unlikely concurrency in futex_hash_allocate()
> and do something like this:
>
> @@ -1798,15 +1798,20 @@ static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags)
> }
> }
>
> - if (!mm->futex_ref) {
> + if (!READ_ONCE(mm->futex_ref)) {
> + unsigned int __percpu *ref;
> +
> + ref = alloc_percpu(unsigned int);
> + if (!ref)
> + return -ENOMEM;
> +
> + this_cpu_inc(*ref); /* 0 -> 1 */
> /*
> - * This will always be allocated by the first thread and
> - * therefore requires no locking.
> + * Ensure a task observing futex_ref non-nil will always
> + * see the counter with its first reference.
> */
> - mm->futex_ref = alloc_percpu(unsigned int);
> - if (!mm->futex_ref)
> - return -ENOMEM;
> - this_cpu_inc(*mm->futex_ref); /* 0 -> 1 */
> + if (unlikely(cmpxchg_release(&mm->futex_ref, NULL, ref)))
> + free_percpu(ref);
> }
>
> fph = kvzalloc(struct_size(fph, queues, hash_slots),
>
> ... or keep the current assumption and update need_futex_hash_allocate_default()
> such that we only consider CLONE_VM (maybe also CLONE_VFORK check to return false).
>
> @@ -1948,8 +1948,11 @@ static void rv_task_fork(struct task_struct *p)
>
> static bool need_futex_hash_allocate_default(u64 clone_flags)
> {
> - if ((clone_flags & (CLONE_THREAD | CLONE_VM)) != (CLONE_THREAD | CLONE_VM))
> + if (!(clone_flags & CLONE_VM))
> return false;
> return true;
> }
>
> I ran the reproducer but was unable to trigger the reported kasan splat altogether.
>
> Thanks,
> Davidlohr