]> git.ipfire.org Git - thirdparty/kernel/linux.git/commit
x86/vdso: Implement __vdso_futex_robust_try_unlock()
authorThomas Gleixner <tglx@kernel.org>
Tue, 2 Jun 2026 09:10:12 +0000 (11:10 +0200)
committerPeter Zijlstra <peterz@infradead.org>
Wed, 3 Jun 2026 09:38:52 +0000 (11:38 +0200)
commita2274cc0091ed4fdce10fad68d08c529b8d3e7dd
tree24e5d65eaf697b9d35cf7f00c2af24ff04259c1a
parent61cfc8e372d1971e0a96d3f1f8b5ee29916b3385
x86/vdso: Implement __vdso_futex_robust_try_unlock()

When the FUTEX_ROBUST_UNLOCK mechanism is used for unlocking (PI-)futexes,
then the unlock sequence in userspace looks like this:

  1) robust_list_set_op_pending(mutex);
  2) robust_list_remove(mutex);

   lval = gettid();
  3) if (atomic_try_cmpxchg(&mutex->lock, lval, 0))
  4) robust_list_clear_op_pending();
   else
  5) sys_futex(OP,...FUTEX_ROBUST_UNLOCK);

That still leaves a minimal race window between #3 and #4 where the mutex
could be acquired by some other task which observes that it is the last
user and:

  1) unmaps the mutex memory
  2) maps a different file, which ends up covering the same address

When then the original task exits before reaching #5 then the kernel robust
list handling observes the pending op entry and tries to fix up user space.

In case that the newly mapped data contains the TID of the exiting thread
at the address of the mutex/futex the kernel will set the owner died bit in
that memory and therefore corrupt unrelated data.

Provide a VDSO function which exposes the critical section window in the
VDSO symbol table. The resulting addresses are updated in the task's mm
when the VDSO is (re)map()'ed.

The core code detects when a task was interrupted within the critical
section and is about to deliver a signal. It then invokes an architecture
specific function which determines whether the pending op pointer has to be
cleared or not. The unlock assembly sequence on 64-bit is:

mov %esi,%eax // Load TID into EAX
        xor %ecx,%ecx // Set ECX to 0
lock cmpxchg %ecx,(%rdi) // Try the TID -> 0 transition
  .Lstart:
jnz     .Lend
movq %rcx,(%rdx) // Clear list_op_pending
  .Lend:
ret

So the decision can be simply based on the ZF state in regs->flags. The
pending op pointer is always in DX independent of the build mode
(32/64-bit) to make the pending op pointer retrieval uniform. The size of
the pointer is stored in the matching criticial section range struct and
the core code retrieves it from there. So the pointer retrieval function
does not have to care. It is bit-size independent:

     return regs->flags & X86_EFLAGS_ZF ? regs->dx : NULL;

There are two entry points to handle the different robust list pending op
pointer size:

__vdso_futex_robust_list64_try_unlock()
__vdso_futex_robust_list32_try_unlock()

The 32-bit VDSO provides only __vdso_futex_robust_list32_try_unlock().

The 64-bit VDSO provides always __vdso_futex_robust_list64_try_unlock() and
when COMPAT is enabled also the list32 variant, which is required to
support multi-size robust list pointers used by gaming emulators.

The unlock function is inspired by an idea from Mathieu Desnoyers.

Signed-off-by: Thomas Gleixner <tglx@kernel.org>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Reviewed-by: André Almeida <andrealmeid@igalia.com>
Acked-by: Uros Bizjak <ubizjak@gmail.com>
Link: https://lore.kernel.org/20260311185409.1988269-1-mathieu.desnoyers@efficios.com
Link: https://patch.msgid.link/20260602090535.883796247@kernel.org
arch/x86/Kconfig
arch/x86/entry/vdso/common/vfutex.c [new file with mode: 0644]
arch/x86/entry/vdso/vdso32/Makefile
arch/x86/entry/vdso/vdso32/vdso32.lds.S
arch/x86/entry/vdso/vdso32/vfutex.c [new file with mode: 0644]
arch/x86/entry/vdso/vdso64/Makefile
arch/x86/entry/vdso/vdso64/vdso64.lds.S
arch/x86/entry/vdso/vdso64/vdsox32.lds.S
arch/x86/entry/vdso/vdso64/vfutex.c [new file with mode: 0644]
arch/x86/include/asm/futex_robust.h [new file with mode: 0644]