Re: [RFC PATCH] futex: Introduce __vdso_robust_futex_unlock
From: Thomas Gleixner
Date: Mon Mar 16 2026 - 13:13:37 EST
On Thu, Mar 12 2026 at 18:52, Mathieu Desnoyers wrote:
> On 2026-03-12 18:23, Thomas Gleixner wrote:
>> xchg(futex->uval, h->unlock_val);
>
> Here is the problem with your proposed approach:
>
> "XCHG — Exchange Register/Memory With Register"
> ^^^^^^^^
>
> So only one of the xchg arguments can be a memory location.
> Therefore, you will end up needing an extra store after xchg
> to store the content of the result register into h->unlock_val.
Indeed.
> If the process dies between those two instructions, your proposed
> robust list code will be fooled and fall into the same bug that's
> been lingering for 14 years.
s/lingering/ignored/
To fix this for correctness sake it needs more than a hack in the kernel
without even looking at the overall larger picture. I sat down and did a
full analysis and here are the most important questions:
Q: Have non-PI and PI to be treated differently?
A: No.
That's just historical evolution. While PI can't use XCHG because that
would create inconsistent state, there is absolutely no reason why
non-PI can't use try_cmpxchg().
Q: Is it required to unlock in user space first and then go into the kernel
to wake up waiters?
A: No.
That's again a historical leftover from the 1st generation futexes which
preceeded both robust and PI. There is no technical reason to keep it
this way.
So both can do:
if (cmpxchg(lock, tid, 0) != tid)
sys_futex(UNLOCK,....);
which then allows for both non-PI and PI to hand the pending op pointer
into the syscall and let the kernel deal with the unlock, the op pointer
and the wake up in one go.
That reduces the problem space to take care of the non-contended unlock
case, where the pending op is cleared after the cmpxchg() succeeded.
And yes, that part can be done in the VDSO and a fixup mechanism in the
kernel.
Q: Are robust list pointers guaranteed to be 64-bit when running as a
64-bit task?
A: No.
The gaming emulators use both the native 64-bit robust list and the
32-bit robust list from the same 64-bit application to make the
emulation work.
So both the UNLOCK syscall and the fixup need to have means to figure
out the to be cleared size for that pointer.
Sure, this can be done with a boat load of different functions and flags
and whatever, but that makes the actual fixup handling in the kernel
more complicated than necessary.
Q: Have regular signal delivery and process exit in case of crash or being
killed by a external signal to be treated differently?
A: No.
A task always goes through the same signal code path for both cases so
all of this can be handled in _one_ place without even touching the
robust list cleanup code.
sys_exit() is different because there a task voluntarily exits and if
it does so between the unlock and the clearing of the op pointer,
then so be it. That'd be wilfull ignorance or malice and not any
different from the task doing the corruption itself in user space
right away.
Q: Are exception tables a good idea?
A: No.
This is not an exception handling case. It's a fixup similar to RSEQ
critical section fixups and so it has to be handled with dedicated
mechanisms which are performant and not glued onto something which has a
completely different purpose.
>> This fixes a long standing data corruption race condition with robust
>> futexes, as pointed out here:
>>
>> "File corruption race condition in robust mutex unlocking"
>> https://sourceware.org/bugzilla/show_bug.cgi?id=14485
No comment.
Thanks,
tglx