mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
To: Thomas Gleixner <tglx@kernel.org>
Cc: LKML <linux-kernel@vger.kernel.org>,
	"Mathieu Desnoyers" <mathieu.desnoyers@efficios.com>,
	"Andrè Almeida" <andrealmeid@igalia.com>,
	"Carlos O'Donell" <carlos@redhat.com>,
	"Peter Zijlstra" <peterz@infradead.org>,
	"Florian Weimer" <fweimer@redhat.com>,
	"Rich Felker" <dalias@aerifal.cx>,
	"Torvald Riegel" <triegel@redhat.com>,
	"Darren Hart" <dvhart@infradead.org>,
	"Ingo Molnar" <mingo@kernel.org>,
	"Davidlohr Bueso" <dave@stgolabs.net>,
	"Arnd Bergmann" <arnd@arndb.de>,
	"Liam R . Howlett" <Liam.Howlett@oracle.com>,
	"Uros Bizjak" <ubizjak@gmail.com>,
	"Thomas Weißschuh" <linux@weissschuh.net>
Subject: [PATCH 15/14] selftests: futex: Add tests for robust unlock within the critical section.
Date: Sat, 4 Apr 2026 11:39:39 +0200	[thread overview]
Message-ID: <20260404093939.7XgeW_54@linutronix.de> (raw)
In-Reply-To: <20260402151131.876492985@kernel.org>

From: Sebastian Andrzej Siewior <sebastian@breakpoint.cc>

I took Thomas’ initial test case from the cover letter and reworked it
so that it uses ptrace() to single‑step through the VDSO unlock
operation. The test expects the lock to remain locked, with
`list_op_pending' pointing somewhere, when entering the VDSO unlock
path. Once execution steps into the critical section, it expects the
kernel to perform the fixup that is, to unlock the lock and clear
`list_op_pending'.

The test requires VDSO debug symbols, typically provided by
vdso64.so.dbg or vdso32.so.dbg. It attempts to locate the appropriate
file automatically, but the user may override this by setting the
VDSO_DBG environment variable. If neither method succeeds, libelf falls
back to its usual lookup mechanism under /usr/lib/debug/.build-id/

Signed-off-by: Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
---
 .../selftests/futex/functional/Makefile       |   3 +-
 .../futex/functional/robust_list_critical.c   | 402 ++++++++++++++++++
 2 files changed, 404 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/futex/functional/robust_list_critical.c

diff --git a/tools/testing/selftests/futex/functional/Makefile b/tools/testing/selftests/futex/functional/Makefile
index af7ec309ea78d..40d7256477a40 100644
--- a/tools/testing/selftests/futex/functional/Makefile
+++ b/tools/testing/selftests/futex/functional/Makefile
@@ -4,7 +4,7 @@ LIBNUMA_TEST = $(shell sh -c "$(PKG_CONFIG) numa --atleast-version 2.0.16 > /dev
 
 INCLUDES := -I../include -I../../ $(KHDR_INCLUDES)
 CFLAGS := $(CFLAGS) -g -O2 -Wall -pthread -D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64 $(INCLUDES) $(KHDR_INCLUDES) -DLIBNUMA_VER_$(LIBNUMA_TEST)=1
-LDLIBS := -lpthread -lrt -lnuma
+LDLIBS := -lpthread -lrt -lnuma -ldw
 
 LOCAL_HDRS := \
 	../include/futextest.h \
@@ -23,6 +23,7 @@ TEST_GEN_PROGS := \
 	futex_numa_mpol \
 	futex_waitv \
 	futex_numa \
+	robust_list_critical \
 	robust_list
 
 TEST_PROGS := run.sh
diff --git a/tools/testing/selftests/futex/functional/robust_list_critical.c b/tools/testing/selftests/futex/functional/robust_list_critical.c
new file mode 100644
index 0000000000000..b9490d24eb10a
--- /dev/null
+++ b/tools/testing/selftests/futex/functional/robust_list_critical.c
@@ -0,0 +1,402 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026, Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
+ *
+ * Testing the VDSO robust‑list unlock critical section. Specifically, when the
+ * kernel detects that user space attempts to unlock a robust lock and is
+ * preempted inside the VDSO critical section, the kernel must complete the
+ * unlock operation and reset the `list_op_pending' field.
+ */
+
+#define _GNU_SOURCE
+#include <err.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include <linux/futex.h>
+#include <linux/prctl.h>
+
+#include <link.h>
+#include <elfutils/libdwfl.h>
+
+#include <sys/prctl.h>
+#include <sys/ptrace.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/user.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "../../kselftest_harness.h"
+
+static char *vdso_dbg_file =
+#ifdef __LP64__
+	"vdso64.so.dbg";
+#else
+	"vdso32.so.dbg";
+#endif
+
+typedef uint32_t (*frtu64_t)(uint32_t *, uint32_t, uint64_t *);
+typedef uint32_t (*frtu32_t)(uint32_t *, uint32_t, uint32_t *);
+
+static frtu64_t frtu64;
+static frtu32_t frtu32;
+static uint64_t frtu64_end;
+static uint64_t frtu32_end;
+
+static uint64_t __futex_list64_try_unlock_cs_start;
+static uint64_t __futex_list64_try_unlock_cs_end;
+static uint64_t __futex_list64_try_unlock_cs_success;
+static uint64_t __futex_list32_try_unlock_cs_start;
+static uint64_t __futex_list32_try_unlock_cs_end;
+static uint64_t __futex_list32_try_unlock_cs_success;
+
+static bool pc_is_within(struct user_regs_struct *regs, uint64_t start, uint64_t end)
+{
+	unsigned long pc;
+
+#if defined(__x86_64__)
+	pc = regs->rip;
+#elif defined(__riscv)
+	pc = reg->pc;
+# error Missing ptrace support
+#endif
+	if (pc >= (long) start && pc < end)
+		return true;
+	return false;
+}
+
+static int is_vdso_mod(const char *mod)
+{
+	return !strncmp(mod, "[vdso: ", 7);
+}
+
+static int module_callback(Dwfl_Module *mod, void **userdata, const char *name,
+			   Dwarf_Addr start, void *arg)
+{
+	const char *sname;
+	GElf_Addr addr;
+	int vdso_syms;
+	GElf_Sym sym;
+	int i;
+
+	/* We can only recognize the vdso by inspecting the "magic name".  */
+	if (!is_vdso_mod(name))
+		return DWARF_CB_OK;
+
+	vdso_syms = dwfl_module_getsymtab(mod);
+	if (vdso_syms < 0)
+		err(1, "dwfl_module_getsymtab: %s", dwfl_errmsg (-1));
+
+	for (i = 0; i < vdso_syms; i++) {
+
+		sname = dwfl_module_getsym_info(mod, i, &sym, &addr, NULL, NULL, NULL);
+		if (!strcmp("__vdso_futex_robust_list64_try_unlock", sname)) {
+			frtu64 = (frtu64_t) addr;
+			frtu64_end = addr + sym.st_size;
+		} else if (!strcmp("__futex_list64_try_unlock_cs_start", sname)) {
+			__futex_list64_try_unlock_cs_start = addr;
+		} else if (!strcmp("__futex_list64_try_unlock_cs_end", sname)) {
+			__futex_list64_try_unlock_cs_end = addr;
+		} else if (!strcmp("__futex_list64_try_unlock_cs_success", sname)) {
+			__futex_list64_try_unlock_cs_success = addr;
+
+		} else if (!strcmp("__vdso_futex_robust_list32_try_unlock", sname)) {
+			frtu32 = (frtu32_t) addr;
+			frtu32_end = addr + sym.st_size;
+		} else if (!strcmp("__futex_list32_try_unlock_cs_start", sname)) {
+			__futex_list32_try_unlock_cs_start = addr;
+		} else if (!strcmp("__futex_list32_try_unlock_cs_end", sname)) {
+			__futex_list32_try_unlock_cs_end = addr;
+		} else if (!strcmp("__futex_list32_try_unlock_cs_success", sname)) {
+			__futex_list32_try_unlock_cs_success = addr;
+		}
+
+	}
+	return DWARF_CB_OK;
+}
+
+static int find_dbg_local_def(Dwfl_Module *mod, void **userdata, const char *modname,
+			      GElf_Addr base, const char *file_name,
+			      const char *debuglink_file, GElf_Word debuglink_crc,
+			      char **debuginfo_file_name)
+{
+	int fd;
+
+	if (is_vdso_mod(modname)) {
+		fd = open(vdso_dbg_file, O_RDONLY);
+		if (fd >= 0)
+			return fd;
+	}
+
+	return dwfl_standard_find_debuginfo(mod, userdata, modname, base, file_name,
+					    debuglink_file, debuglink_crc, debuginfo_file_name);
+}
+
+static void get_symbols(void)
+{
+	char *debuginfo_path;
+	const Dwfl_Callbacks proc_callbacks = {
+		.find_debuginfo = find_dbg_local_def,
+		.debuginfo_path = &debuginfo_path,
+		.find_elf = dwfl_linux_proc_find_elf,
+	};
+	int result;
+	pid_t pid;
+	Dwfl *dwfl;
+
+	dwfl = dwfl_begin(&proc_callbacks);
+	if (!dwfl)
+		errx(1, "dwfl_begin: %s", dwfl_errmsg(-1));
+
+	pid = getpid();
+	result = dwfl_linux_proc_report(dwfl, pid);
+	if (result < 0)
+		errx(1, "dwfl_linux_proc_report: %s", dwfl_errmsg(-1));
+	else if (result > 0)
+		errx(1, "dwfl_linux_proc_report");
+
+	result = dwfl_linux_proc_attach(dwfl, pid, false);
+	if (result < 0)
+		err (1, "dwfl_linux_proc_attach: %s", dwfl_errmsg(-1));
+	else if (result > 0)
+		err (1, "dwfl_linux_proc_attach");
+
+	if (dwfl_report_end(dwfl, NULL, NULL) != 0)
+		err (1, "dwfl_report_end: %s", dwfl_errmsg(-1));
+
+	if (dwfl_getmodules(dwfl, module_callback, NULL, 0) != 0)
+		err (1, "dwfl_getmodules: %s", dwfl_errmsg(-1));
+	dwfl_end(dwfl);
+}
+
+static pthread_mutex_t test_mutex;
+static int unlock_uncontended(struct __test_metadata *_metadata, bool is_32bit)
+{
+	struct robust_list_head *rhead;
+	uint32_t *lock;
+	pid_t lock_tid;
+	int error = 1;
+	pid_t tid;
+	size_t sz;
+
+	syscall(SYS_get_robust_list, 0, &rhead, &sz);
+	pthread_mutex_init(&test_mutex, NULL);
+
+	tid = gettid();
+	lock = (uint32_t *)&test_mutex.__data.__lock;
+	*lock = tid;
+	/* Set the pending prior unlocking */
+	rhead->list_op_pending = (struct robust_list *)&test_mutex.__data.__list.__next;
+
+	raise(SIGTRAP);
+	if (is_32bit)
+		lock_tid = frtu32(lock, tid, (uint32_t *)&rhead->list_op_pending);
+	else
+		lock_tid = frtu64(lock, tid, (uint64_t *)&rhead->list_op_pending);
+
+	if (lock_tid != tid)
+		TH_LOG("Non contended unlock failed. Return: %08x expected %08x\n",
+		       lock_tid, tid);
+	else
+		error = 0;
+	return error;
+}
+
+enum trace_state {
+	STATE_WAIT = 0,
+	STATE_ENTER_VDSO,
+	STATE_IN_CS,
+	STATE_OUT_CS,
+	STATE_LEAVE_VDSO,
+};
+
+static void trace_child(struct __test_metadata *_metadata, pid_t child, bool is_32bit)
+{
+	int state = STATE_WAIT;
+	struct robust_list_head *rhead;
+	size_t sz;
+	bool do_end = false;
+
+	syscall(SYS_get_robust_list, 0, &rhead, &sz);
+	do {
+		struct user_regs_struct regs;
+		uint64_t lock_val;
+		bool in_vdso;
+		int wstatus;
+		pid_t rpid;
+
+		rpid = waitpid(child, &wstatus, 0);
+		if (rpid != child)
+			errx(1, "waitpid");
+		if (!do_end) {
+			if (WSTOPSIG(wstatus) != SIGTRAP)
+				errx(1, "NOT SIGTRAP");
+		} else {
+			if (!WIFEXITED(wstatus))
+				errx(1, "Did not exit, but we are done");
+			ASSERT_EQ(WEXITSTATUS(wstatus), 0);
+			return;
+		}
+
+		if (ptrace(PTRACE_GETREGS, child, 0, &regs) != 0)
+			errx(1, "PTRACE_GETREGS");
+
+		if (is_32bit) {
+			in_vdso = pc_is_within(&regs, (long)frtu32, frtu32_end);
+		} else {
+			in_vdso = pc_is_within(&regs, (long)frtu64, frtu64_end);
+		}
+		if (in_vdso) {
+			unsigned long rhead_val;
+
+			if (state == STATE_WAIT) {
+				state = STATE_ENTER_VDSO;
+
+			} else {
+				if (is_32bit) {
+					if (pc_is_within(&regs, __futex_list32_try_unlock_cs_start,
+							 __futex_list32_try_unlock_cs_end))
+						state = STATE_IN_CS;
+					else
+						state = STATE_OUT_CS;
+				} else {
+					if (pc_is_within(&regs, __futex_list64_try_unlock_cs_start,
+							 __futex_list64_try_unlock_cs_end))
+						state = STATE_IN_CS;
+					else
+						state = STATE_OUT_CS;
+				}
+			}
+
+			errno = 0;
+			rhead_val = ptrace(PTRACE_PEEKDATA, child, &rhead->list_op_pending, 0);
+			if (rhead_val == -1 && errno != 0)
+				err(1, "PTRACE_PEEKDATA");
+
+			lock_val = ptrace(PTRACE_PEEKDATA, child, &test_mutex.__data.__lock, 0);
+			if (lock_val == -1 && errno != 0)
+				err(1, "PTRACE_PEEKDATA");
+
+			if (state == STATE_ENTER_VDSO) {
+				/* Entering VDSO, it supposed to be locked */
+				ASSERT_NE(rhead_val, 0);
+				ASSERT_EQ(lock_val, child);
+
+			} else if (state == STATE_IN_CS) {
+				/*
+				 * If the critical section has been entered then
+				 * the kernel has to unlock and clean list_op_pending.
+				 * On 32bit the pointer is just 32bit wide, the
+				 * upper 32bit are cleaned on 64bit.
+				 */
+				if (is_32bit)
+					rhead_val &= 0xffffffff;
+
+				ASSERT_EQ(rhead_val, 0);
+				ASSERT_EQ(lock_val, 0);
+			}
+
+			if (ptrace(PTRACE_SINGLESTEP, child, 0, 0))
+				err(1, "PTRACE_SINGLESTEP");
+		} else {
+			int ret;
+
+			if (state == STATE_WAIT) {
+				ret = ptrace(PTRACE_SINGLESTEP, child, 0, 0);
+			} else if (state == STATE_OUT_CS) {
+				state = STATE_LEAVE_VDSO;
+				ret = ptrace(PTRACE_SINGLESTEP, child, 0, 0);
+			} else if (state == STATE_LEAVE_VDSO) {
+				state = STATE_WAIT;
+				do_end = true;
+				ret = ptrace(PTRACE_CONT, child, 0, 0);
+			}
+			if (ret != 0)
+				err(1, "ptrace continue failed");
+		}
+	} while (1);
+}
+
+TEST(robust_unlock_64)
+{
+	pid_t child;
+
+	if (!frtu64)
+		SKIP(return, "Missing __vdso_futex_robust_list64_try_unlock() in VDSO\n");
+
+	if ((long)frtu64 == frtu64_end)
+		SKIP(return, "size for __vdso_futex_robust_list64_try_unlock\n");
+
+	if (!__futex_list64_try_unlock_cs_start ||
+	    !__futex_list64_try_unlock_cs_end ||
+	    !__futex_list64_try_unlock_cs_success) {
+		SKIP(return, "Missing debug symbols, use VDSO_DBG=PATH/TO/vdso64.so.dbg.\n");
+	}
+
+	child = fork();
+	if (child < 0)
+		err(1, "fork()");
+
+	if (!child) {
+		if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0)
+			err(1, "PTRACE_TRACEME");
+
+		exit(unlock_uncontended(_metadata, false));
+	} else {
+		trace_child(_metadata, child, false);
+	}
+}
+
+TEST(robust_unlock_32)
+{
+	pid_t child;
+
+	if (!frtu32)
+		SKIP(return, "Missing __vdso_futex_robust_list32_try_unlock() in VDSO\n");
+
+	if ((long)frtu32 == frtu32_end)
+		SKIP(return, "size for __vdso_futex_robust_list32_try_unlock\n");
+
+	if (!__futex_list32_try_unlock_cs_start ||
+	    !__futex_list32_try_unlock_cs_end ||
+	    !__futex_list32_try_unlock_cs_success) {
+		SKIP(return, "Missing debug symbols, use VDSO_DBG=PATH/TO/vdso32.so.dbg.\n");
+	}
+
+	child = fork();
+	if (child < 0)
+		err(1, "fork()");
+
+	if (!child) {
+		if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0)
+			err(1, "PTRACE_TRACEME");
+
+		exit(unlock_uncontended(_metadata, true));
+	} else {
+		trace_child(_metadata, child, true);
+	}
+}
+
+int main(int argc, char **argv)
+{
+	char *vdso_dbg;
+
+	vdso_dbg = getenv("VDSO_DBG");
+	if (vdso_dbg) {
+		vdso_dbg_file = vdso_dbg;
+		ksft_print_msg("Using %s as VDSO debug symbols\n", vdso_dbg_file);
+	}
+
+	get_symbols();
+
+	return test_harness_run(argc, argv);
+}
-- 
2.53.0


  parent reply	other threads:[~2026-04-04  9:39 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-02 15:21 [patch V4 00/14] futex: Address the robust futex unlock race for real Thomas Gleixner
2026-04-02 15:21 ` [patch V4 01/14] futex: Move futex task related data into a struct Thomas Gleixner
2026-04-02 15:21 ` [patch V4 02/14] futex: Make futex_mm_init() void Thomas Gleixner
2026-04-02 15:21 ` [patch V4 03/14] futex: Move futex related mm_struct data into a struct Thomas Gleixner
2026-04-09 11:11   ` Nam Cao
2026-04-10 14:20     ` Thomas Gleixner
2026-04-02 15:21 ` [patch V4 04/14] futex: Provide UABI defines for robust list entry modifiers Thomas Gleixner
2026-04-02 15:21 ` [patch V4 05/14] uaccess: Provide unsafe_atomic_store_release_user() Thomas Gleixner
2026-04-02 15:21 ` [patch V4 06/14] x86: Select ARCH_MEMORY_ORDER_TSO Thomas Gleixner
2026-04-02 15:21 ` [patch V4 07/14] futex: Cleanup UAPI defines Thomas Gleixner
2026-04-02 15:21 ` [patch V4 08/14] futex: Add support for unlocking robust futexes Thomas Gleixner
2026-04-02 15:21 ` [patch V4 09/14] futex: Add robust futex unlock IP range Thomas Gleixner
2026-05-28  1:02   ` André Almeida
2026-05-29 21:30     ` Thomas Gleixner
2026-04-02 15:21 ` [patch V4 10/14] futex: Provide infrastructure to plug the non contended robust futex unlock race Thomas Gleixner
2026-05-28  1:08   ` André Almeida
2026-05-29 21:14     ` Thomas Gleixner
2026-04-02 15:21 ` [patch V4 11/14] x86/vdso: Prepare for robust futex unlock support Thomas Gleixner
2026-05-28  1:14   ` André Almeida
2026-04-02 15:22 ` [patch V4 12/14] x86/vdso: Implement __vdso_futex_robust_try_unlock() Thomas Gleixner
2026-04-29  8:44   ` Thomas Weißschuh
2026-05-07  9:29     ` Thomas Gleixner
2026-05-07  9:48       ` Thomas Weißschuh
2026-05-07 16:51         ` Thomas Gleixner
2026-05-29 15:36   ` André Almeida
2026-04-02 15:22 ` [patch V4 13/14] Documentation: futex: Add a note about robust list race condition Thomas Gleixner
2026-04-02 15:22 ` [patch V4 14/14] selftests: futex: Add tests for robust release operations Thomas Gleixner
2026-04-04  9:39 ` Sebastian Andrzej Siewior [this message]
2026-04-04 20:13   ` [PATCH 15/14] selftests: futex: Add tests for robust unlock within the critical section Thomas Gleixner
2026-05-22 22:16   ` André Almeida
2026-05-28  2:55   ` André Almeida
2026-05-29 21:27     ` Thomas Gleixner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260404093939.7XgeW_54@linutronix.de \
    --to=bigeasy@linutronix.de \
    --cc=Liam.Howlett@oracle.com \
    --cc=andrealmeid@igalia.com \
    --cc=arnd@arndb.de \
    --cc=carlos@redhat.com \
    --cc=dalias@aerifal.cx \
    --cc=dave@stgolabs.net \
    --cc=dvhart@infradead.org \
    --cc=fweimer@redhat.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@weissschuh.net \
    --cc=mathieu.desnoyers@efficios.com \
    --cc=mingo@kernel.org \
    --cc=peterz@infradead.org \
    --cc=tglx@kernel.org \
    --cc=triegel@redhat.com \
    --cc=ubizjak@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®