Re: [PATCH v5 2/4] arm64: vdso: Implement __vdso_futex_robust_try_unlock()

From: Mark Rutland

Date: Fri Jul 17 2026 - 14:32:05 EST


On Fri, Jul 17, 2026 at 11:41:41AM -0300, André Almeida wrote:
> Based on the x86 implementation, implement the vDSO function for unlocking
> a robust futex correctly.

Hi Andre,

As mentioned on the prior patch, it would be easier to review this if
all of the compat VDSO additions were in this patch, rather than half of
those being in the prior patch. I left some suggestions there as to how
to split that.

> Commit a2274cc0091e ("x86/vdso: Implement __vdso_futex_robust_try_unlock()")
> has the full explanation about why this mechanism is needed.

Can we please have a short explanation here? IIUC there's a race where
one thread has a dangling pending op for a VA, but another thread has
re-allocated that VA, and the kernel can corrupt the memory at that VA.

TBH, from reading a2274cc0091e I'm struggling to follow the race, and
how this mechanism helps, but that might just be due to my brain not
working at the end of the week.

> The unlock assembly sequence for arm64 is:
>
> __vdso_futex_robust_list64_try_unlock:
> retry:
> ldxr w8, [x0] // Load the value from *futex
> cmp w1, w8 // Compare with TID
> b.ne __vdso_futex_list64_try_unlock_cs_end
> stlxr w3, wzr, [x0] // Try to zero *futex
> __vdso_futex_list64_try_unlock_cs_start:
> cbnz w3, retry
> str xzr, [x2] // After zeroing *futex, zero *op_pending
> __vdso_futex_list64_try_unlock_cs_end>:
>
> The decision regarding if the pointer should be cleared or not lies on
> checking the w3 register:
>
> return (regs->user_regs[3]) ? NULL : (void __user *)
> regs->user_regs.regs[2];

I don't think the description of the assembly helps without a complete
description of the problem, and it'd be best to just remove the assembly
from the commit message, and just describe what the kernel does
depending on whether the userspace cmpxchg (using LDXR + STLXR) succeded
or failed.

> If it's zero, that means that the exclusive store worked and the kernel
> should clear op_pending (if userspace didn't managed to) stored at x2.
>
> Signed-off-by: André Almeida <andrealmeid@xxxxxxxxxx>
> ---
> Notes:
> - Only LL/SC for now but I can add LSE later if this looks good
>
> v4:
> - Guard makefile for vfutex.o with ifdef
> - Moved _start label one instruction above
> - Use results register (w3) to check for store success instead of using zero
> flag
>
> v3:
> - Managed to get pop to always be stored at x2
> ---
> arch/arm64/Kconfig | 1 +
> arch/arm64/include/asm/futex_robust.h | 19 +++++++++++++++++++
> arch/arm64/kernel/vdso/Makefile | 10 ++++++++++
> arch/arm64/kernel/vdso/vfutex.c | 35 +++++++++++++++++++++++++++++++++++
> 4 files changed, 65 insertions(+)
>
> diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
> index b3afe0688919..0582172811d9 100644
> --- a/arch/arm64/Kconfig
> +++ b/arch/arm64/Kconfig
> @@ -221,6 +221,7 @@ config ARM64
> select HAVE_RELIABLE_STACKTRACE
> select HAVE_POSIX_CPU_TIMERS_TASK_WORK
> select HAVE_FUNCTION_ARG_ACCESS_API
> + select HAVE_FUTEX_ROBUST_UNLOCK
> select MMU_GATHER_RCU_TABLE_FREE
> select HAVE_RSEQ
> select HAVE_RUST if RUSTC_SUPPORTS_ARM64
> diff --git a/arch/arm64/include/asm/futex_robust.h b/arch/arm64/include/asm/futex_robust.h
> new file mode 100644
> index 000000000000..64f22166756a
> --- /dev/null
> +++ b/arch/arm64/include/asm/futex_robust.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef _ASM_ARM64_FUTEX_ROBUST_H
> +#define _ASM_ARM64_FUTEX_ROBUST_H
> +
> +#include <asm/ptrace.h>
> +
> +static __always_inline void __user *arm64_futex_robust_unlock_get_pop(struct pt_regs *regs)
> +{
> + /*
> + * w3 is stores the result of the stlxr instruction. If it's zero, the then
> + * the ll/sc cmpxchg succeeded and the pending op pointer needs to be cleared.
> + */
> + return (regs->user_regs.regs[3]) ? NULL : (void __user *) regs->user_regs.regs[2];
> +}
> +
> +#define arch_futex_robust_unlock_get_pop(regs) \
> + arm64_futex_robust_unlock_get_pop(regs)
> +
> +#endif /* _ASM_ARM64_FUTEX_ROBUST_H */
> diff --git a/arch/arm64/kernel/vdso/Makefile b/arch/arm64/kernel/vdso/Makefile
> index 7dec05dd33b7..985346c7a0bb 100644
> --- a/arch/arm64/kernel/vdso/Makefile
> +++ b/arch/arm64/kernel/vdso/Makefile
> @@ -11,6 +11,10 @@ include $(srctree)/lib/vdso/Makefile.include
>
> obj-vdso := vgettimeofday.o note.o sigreturn.o vgetrandom.o vgetrandom-chacha.o
>
> +ifdef CONFIG_FUTEX_ROBUST_UNLOCK
> + obj-vdso += vfutex.o
> +endif
> +
> # Build rules
> targets := $(obj-vdso) vdso.so vdso.so.dbg
> obj-vdso := $(addprefix $(obj)/, $(obj-vdso))
> @@ -45,9 +49,11 @@ CC_FLAGS_ADD_VDSO := -O2 -mcmodel=tiny -fasynchronous-unwind-tables
>
> CFLAGS_REMOVE_vgettimeofday.o = $(CC_FLAGS_REMOVE_VDSO)
> CFLAGS_REMOVE_vgetrandom.o = $(CC_FLAGS_REMOVE_VDSO)
> +CFLAGS_REMOVE_vfutex.o = $(CC_FLAGS_REMOVE_VDSO)
>
> CFLAGS_vgettimeofday.o = $(CC_FLAGS_ADD_VDSO)
> CFLAGS_vgetrandom.o = $(CC_FLAGS_ADD_VDSO)
> +CFLAGS_vfutex.o = $(CC_FLAGS_ADD_VDSO)
>
> ifneq ($(c-gettimeofday-y),)
> CFLAGS_vgettimeofday.o += -include $(c-gettimeofday-y)
> @@ -57,6 +63,10 @@ ifneq ($(c-getrandom-y),)
> CFLAGS_vgetrandom.o += -include $(c-getrandom-y)
> endif
>
> +ifneq ($(c-futex-y),)
> + CFLAGS_vfutex.o += -include $(c-futex-y)
> +endif
> +
> targets += vdso.lds
> CPPFLAGS_vdso.lds += -P -C -U$(ARCH)
>
> diff --git a/arch/arm64/kernel/vdso/vfutex.c b/arch/arm64/kernel/vdso/vfutex.c
> new file mode 100644
> index 000000000000..4c69d92426fd
> --- /dev/null
> +++ b/arch/arm64/kernel/vdso/vfutex.c
> @@ -0,0 +1,35 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +#include <linux/stringify.h>
> +#include <vdso/futex.h>
> +
> +#define LABEL(name, sz) __stringify(__futex_list##sz##_try_unlock_cs_##name)
> +
> +#define GLOBLS(sz) ".globl " LABEL(start, sz) ", " LABEL(success, sz) ", " LABEL(end, sz) "\n"

Since we don't share the assembly between native and compat, it would be
clearer to have these inline within the assembly block, and without the
'sz' parameter.

Using the full names would make this easier to grep for.

Mark.

> +
> +__u32 __vdso_futex_robust_list64_try_unlock(__u32 *lock, __u32 tid, __u64 *pop)
> +{
> + register __u64 *pop_reg asm("x2") = pop;
> + register __u32 result_reg asm("w3") = 0;
> + __u32 val;
> +
> + asm volatile (
> + GLOBLS(64)
> + " prfm pstl1strm, %[lock] \n"
> + "retry: \n"
> + " ldxr %w[val], %[lock] \n"
> + " cmp %w[tid], %w[val] \n"
> + " bne " LABEL(end, 64)" \n"
> + " stlxr %w[result], wzr, %[lock] \n"
> + LABEL(start, 64)": \n"
> + " cbnz %w[result], retry \n"
> + LABEL(success, 64)": \n"
> + " str xzr, %[pop_reg] \n"
> + LABEL(end, 64)": \n"
> +
> + : [val] "=&r" (val), [result] "=&r" (result_reg)
> + : [tid] "r" (tid), [lock] "Q" (*lock), [pop_reg] "Q" (*pop_reg)
> + : "cc", "memory"
> + );
> +
> + return val;
> +}
>
> --
> 2.55.0
>