]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests: futex: Add tests for robust release operations
authorAndré Almeida <andrealmeid@igalia.com>
Tue, 2 Jun 2026 09:10:21 +0000 (11:10 +0200)
committerPeter Zijlstra <peterz@infradead.org>
Wed, 3 Jun 2026 09:38:53 +0000 (11:38 +0200)
Add tests for __vdso_futex_robust_listXX_try_unlock() and for the futex()
op FUTEX_ROBUST_UNLOCK.

Test the contended and uncontended cases for the vDSO functions and all
ops combinations for FUTEX_ROBUST_UNLOCK.

[ tglx: Replace the VDSO function lookup ]

Signed-off-by: André Almeida <andrealmeid@igalia.com>
Signed-off-by: Thomas Gleixner <tglx@kernel.org>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20260329-tonyk-vdso_test-v2-2-b7db810e44a1@igalia.com
Link: https://patch.msgid.link/20260602090535.988101541@kernel.org
tools/testing/selftests/futex/functional/robust_list.c
tools/testing/selftests/futex/include/futextest.h

index e7d1254e18ca48e792bea840c5d955e010292939..b3fab60181d5e206222d9369a695293d46b88322 100644 (file)
 #include "futextest.h"
 #include "../../kselftest_harness.h"
 
+#include <dlfcn.h>
 #include <errno.h>
 #include <pthread.h>
 #include <signal.h>
+#include <stdint.h>
 #include <stdatomic.h>
 #include <stdbool.h>
 #include <stddef.h>
+#include <sys/auxv.h>
 #include <sys/mman.h>
 #include <sys/wait.h>
 
 
 #define SLEEP_US 100
 
+#if __SIZEOF_LONG__ == 8
+# define BUILD_64
+#endif
+
 static pthread_barrier_t barrier, barrier2;
 
 static int set_robust_list(struct robust_list_head *head, size_t len)
@@ -54,6 +61,12 @@ static int get_robust_list(int pid, struct robust_list_head **head, size_t *len_
        return syscall(SYS_get_robust_list, pid, head, len_ptr);
 }
 
+static int sys_futex_robust_unlock(_Atomic(uint32_t) *uaddr, unsigned int op, int val,
+                                  void *list_op_pending, unsigned int val3)
+{
+       return syscall(SYS_futex, uaddr, op, val, NULL, list_op_pending, val3, 0);
+}
+
 /*
  * Basic lock struct, contains just the futex word and the robust list element
  * Real implementations have also a *prev to easily walk in the list
@@ -549,4 +562,230 @@ TEST(test_circular_list)
                ksft_test_result_pass("%s\n", __func__);
 }
 
+/*
+ * Below are tests for the fix of robust release race condition. Please read the following
+ * thread to learn more about the issue in the first place and why the following functions fix it:
+ * https://lore.kernel.org/lkml/20260316162316.356674433@kernel.org/
+ */
+
+/*
+ * Auxiliary code for binding the vDSO functions
+ */
+static void *get_vdso_func_addr(const char *function)
+{
+       const char *vdso_names[] = {
+               "linux-vdso.so.1", "linux-gate.so.1", "linux-vdso32.so.1", "linux-vdso64.so.1",
+       };
+
+       for (int i = 0; i < ARRAY_SIZE(vdso_names); i++) {
+               void *vdso = dlopen(vdso_names[i], RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
+
+               if (vdso)
+                       return dlsym(vdso, function);
+       }
+       return NULL;
+}
+
+/*
+ * These are the real vDSO function signatures:
+ *
+ *     __vdso_futex_robust_list64_try_unlock(__u32 *lock, __u32 tid, __u64 *pop)
+ *     __vdso_futex_robust_list32_try_unlock(__u32 *lock, __u32 tid, __u32 *pop)
+ *
+ * So for the generic entry point we need to use a void pointer as the last argument
+ */
+FIXTURE(vdso_unlock)
+{
+       uint32_t (*vdso)(_Atomic(uint32_t) *lock, uint32_t tid, void *pop);
+};
+
+FIXTURE_VARIANT(vdso_unlock)
+{
+       bool is_32;
+       char func_name[];
+};
+
+FIXTURE_SETUP(vdso_unlock)
+{
+       self->vdso = get_vdso_func_addr(variant->func_name);
+}
+
+FIXTURE_TEARDOWN(vdso_unlock) {}
+
+FIXTURE_VARIANT_ADD(vdso_unlock, 32)
+{
+       .func_name = "__vdso_futex_robust_list32_try_unlock",
+       .is_32 = true,
+};
+
+FIXTURE_VARIANT_ADD(vdso_unlock, 64)
+{
+       .func_name = "__vdso_futex_robust_list64_try_unlock",
+       .is_32 = false,
+};
+
+/*
+ * Test the vDSO robust_listXX_try_unlock() for the uncontended case. The virtual syscall should
+ * return the thread ID of the lock owner, the lock word must be 0 and the list_op_pending should
+ * be NULL.
+ */
+TEST_F(vdso_unlock, test_robust_try_unlock_uncontended)
+{
+       struct lock_struct lock = { .futex = 0 };
+       _Atomic(unsigned int) *futex = &lock.futex;
+       struct robust_list_head head;
+       uintptr_t exp = (uintptr_t) NULL;
+       pid_t tid = gettid();
+       int ret;
+
+       if (!self->vdso) {
+               ksft_test_result_skip("%s not found\n", variant->func_name);
+               return;
+       }
+
+       *futex = tid;
+
+       ret = set_list(&head);
+       if (ret)
+               ksft_test_result_fail("set_robust_list error\n");
+
+       head.list_op_pending = &lock.list;
+
+       ret = self->vdso(futex, tid, &head.list_op_pending);
+
+       ASSERT_EQ(ret, tid);
+       ASSERT_EQ(*futex, 0);
+
+       /* Check only the lower 32 bits for the 32-bit entry point */
+       if (variant->is_32) {
+               exp = (uintptr_t)(unsigned long)&lock.list;
+               exp &= ~0xFFFFFFFFULL;
+       }
+
+       ASSERT_EQ((uintptr_t)(unsigned long)head.list_op_pending, exp);
+}
+
+/*
+ * If the lock is contended, the operation fails. The return value is the value found at the
+ * futex word (tid | FUTEX_WAITERS), the futex word is not modified and the list_op_pending is_32
+ * not cleared.
+ */
+TEST_F(vdso_unlock, test_robust_try_unlock_contended)
+{
+       struct lock_struct lock = { .futex = 0 };
+       _Atomic(unsigned int) *futex = &lock.futex;
+       struct robust_list_head head;
+       pid_t tid = gettid();
+       int ret;
+
+       if (!self->vdso) {
+               ksft_test_result_skip("%s not found\n", variant->func_name);
+               return;
+       }
+
+       *futex = tid | FUTEX_WAITERS;
+
+       ret = set_list(&head);
+       if (ret)
+               ksft_test_result_fail("set_robust_list error\n");
+
+       head.list_op_pending = &lock.list;
+
+       ret = self->vdso(futex, tid, &head.list_op_pending);
+
+       ASSERT_EQ(ret, tid | FUTEX_WAITERS);
+       ASSERT_EQ(*futex, tid | FUTEX_WAITERS);
+       ASSERT_EQ(head.list_op_pending, &lock.list);
+}
+
+FIXTURE(futex_op) {};
+
+FIXTURE_VARIANT(futex_op)
+{
+       unsigned int op;
+       unsigned int val3;
+};
+
+FIXTURE_SETUP(futex_op) {}
+
+FIXTURE_TEARDOWN(futex_op) {}
+
+FIXTURE_VARIANT_ADD(futex_op, wake)
+{
+       .op = FUTEX_WAKE,
+       .val3 = 0,
+};
+
+FIXTURE_VARIANT_ADD(futex_op, wake_bitset)
+{
+       .op = FUTEX_WAKE_BITSET,
+       .val3 = FUTEX_BITSET_MATCH_ANY,
+};
+
+FIXTURE_VARIANT_ADD(futex_op, unlock_pi)
+{
+       .op = FUTEX_UNLOCK_PI,
+       .val3 = 0,
+};
+
+FIXTURE_VARIANT_ADD(futex_op, wake32)
+{
+       .op = FUTEX_WAKE | FUTEX_ROBUST_LIST32,
+       .val3 = 0,
+};
+
+FIXTURE_VARIANT_ADD(futex_op, wake_bitset32)
+{
+       .op = FUTEX_WAKE_BITSET | FUTEX_ROBUST_LIST32,
+       .val3 = FUTEX_BITSET_MATCH_ANY,
+};
+
+FIXTURE_VARIANT_ADD(futex_op, unlock_pi32)
+{
+       .op = FUTEX_UNLOCK_PI | FUTEX_ROBUST_LIST32,
+       .val3 = 0,
+};
+
+/*
+ * The syscall should return the number of tasks waken (for this test, 0), clear the futex word and
+ * clear list_op_pending
+ */
+TEST_F(futex_op, test_futex_robust_unlock)
+{
+       struct lock_struct lock = { .futex = 0 };
+       _Atomic(unsigned int) *futex = &lock.futex;
+       uintptr_t exp = (uintptr_t) NULL;
+       struct robust_list_head head;
+       pid_t tid = gettid();
+       int ret;
+
+#ifndef BUILD_64
+       if (!(variant->op & FUTEX_ROBUST_LIST32)) {
+               ksft_test_result_skip("Not supported for 32 bit build\n");
+               return;
+       }
+#endif
+
+       *futex = tid | FUTEX_WAITERS;
+
+       ret = set_list(&head);
+       if (ret)
+               ksft_test_result_fail("set_robust_list error\n");
+
+       head.list_op_pending = &lock.list;
+
+       ret = sys_futex_robust_unlock(futex, FUTEX_ROBUST_UNLOCK | variant->op, tid,
+                                     &head.list_op_pending, variant->val3);
+
+       ASSERT_EQ(ret, 0);
+       ASSERT_EQ(*futex, 0);
+
+       if (variant->op & FUTEX_ROBUST_LIST32) {
+               exp = (uint64_t)(unsigned long)&lock.list;
+               exp &= ~0xFFFFFFFFULL;
+       }
+
+       ASSERT_EQ((uintptr_t)(unsigned long)head.list_op_pending, exp);
+}
+
 TEST_HARNESS_MAIN
index 3d48e9789d9fe6c7565066260678d6d6061e1516..df33f31d699405dc9b5c8d1a6d0b3154d9cf4cb2 100644 (file)
@@ -38,6 +38,12 @@ typedef volatile u_int32_t futex_t;
 #ifndef FUTEX_CMP_REQUEUE_PI
 #define FUTEX_CMP_REQUEUE_PI           12
 #endif
+#ifndef FUTEX_ROBUST_UNLOCK
+#define FUTEX_ROBUST_UNLOCK            512
+#endif
+#ifndef FUTEX_ROBUST_LIST32
+#define FUTEX_ROBUST_LIST32            1024
+#endif
 #ifndef FUTEX_WAIT_REQUEUE_PI_PRIVATE
 #define FUTEX_WAIT_REQUEUE_PI_PRIVATE  (FUTEX_WAIT_REQUEUE_PI | \
                                         FUTEX_PRIVATE_FLAG)