From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from galois.linutronix.de (Galois.linutronix.de [193.142.43.55]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 589C71C6FF5 for ; Sat, 4 Apr 2026 09:39:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=193.142.43.55 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775295585; cv=none; b=FjmGKs9Oi9iT2xbt2lT1jQd84sdY16xcEoOyEHQ9mJgocnP0/jpob9wIrzX24sEnkLYHi4DNXGV8bApmm60diETaViaSKIe/jXLtAQaQWwPspHx+8rapu8n5JeCWVvGFof3DDNBxiuPgv8bx6sc+47KRYMQuCbrbbxv7xgqtqG0= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775295585; c=relaxed/simple; bh=JF+9W3C8ywAVX19ksfjIc2zakiG6Y1in0sR2caYhpRk=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=WJJ8eXA/NtLAtm3Yd85Rx0ZKplGm/1I99puyylRaWXlqkS0ubNiXfRuAPV8yd9BU9O6k3M/sRiaYnDQEd+/nZZyskDLwe1H23EdTM6u8sufSr/DultIIMjwxFMgLbMcdN0P1MqyyzAoJSN+18tM7GXlzmb6iSb5fDRGgQxY+15c= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linutronix.de; spf=pass smtp.mailfrom=linutronix.de; dkim=pass (2048-bit key) header.d=linutronix.de header.i=@linutronix.de header.b=wgc0fZpK; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b=ZwcntWg2; arc=none smtp.client-ip=193.142.43.55 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linutronix.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=linutronix.de header.i=@linutronix.de header.b="wgc0fZpK"; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b="ZwcntWg2" Date: Sat, 4 Apr 2026 11:39:39 +0200 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020; t=1775295581; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=TYlGYLNEhUvZvW9BE71KR3RjcLYgxbjrlSwwL9skmyU=; b=wgc0fZpKLQOczrCr2Fiwiqr4VDivDoMWI9df1c6TKqjXGXvl61kIWyLPxQxaijAlB7B1aw eMPYuFG9LJzDxnlTz4fiWuEeSisIuatxJ64XLGpTomEULVuzKYBxXEzNbv8v6sQEP3wpeR s9XbLXwZSISrL9klW4VHk23ytmuTQsAv8I4FIHu41CAUxrgTx8aepYmkYkDh55NztwEdoF NEujEM8yrvxWSJ7WjOXj/GRx6Fu59s0AtNCzC4yEasaIu9ehzsprHr1vqG+yXFn5uPtQ8F UxUecZ3vdQ1Sn2InsNKdPjj/9FkhSiUpE++RhnIueWEeiS/QUIArFayG/GVIEA== DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020e; t=1775295581; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=TYlGYLNEhUvZvW9BE71KR3RjcLYgxbjrlSwwL9skmyU=; b=ZwcntWg2dG2ewNjCeiOWp7gGipBu7w54+LeZRvrSg/NmTEYeaJKz58yPQJVo04eAuos0VP zSqKJ0NDazXCwTBA== From: Sebastian Andrzej Siewior To: Thomas Gleixner Cc: LKML , Mathieu Desnoyers , =?utf-8?B?QW5kcsOo?= Almeida , Carlos O'Donell , Peter Zijlstra , Florian Weimer , Rich Felker , Torvald Riegel , Darren Hart , Ingo Molnar , Davidlohr Bueso , Arnd Bergmann , "Liam R . Howlett" , Uros Bizjak , Thomas =?utf-8?Q?Wei=C3=9Fschuh?= Subject: [PATCH 15/14] selftests: futex: Add tests for robust unlock within the critical section. Message-ID: <20260404093939.7XgeW_54@linutronix.de> References: <20260402151131.876492985@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable In-Reply-To: <20260402151131.876492985@kernel.org> =46rom: Sebastian Andrzej Siewior I took Thomas=E2=80=99 initial test case from the cover letter and reworked= it so that it uses ptrace() to single=E2=80=91step 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 --- .../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_cr= itical.c diff --git a/tools/testing/selftests/futex/functional/Makefile b/tools/test= ing/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 =3D $(shell sh -c "$(PKG_CONFIG) numa --atleas= t-version 2.0.16 > /dev =20 INCLUDES :=3D -I../include -I../../ $(KHDR_INCLUDES) CFLAGS :=3D $(CFLAGS) -g -O2 -Wall -pthread -D_FILE_OFFSET_BITS=3D64 -D_TI= ME_BITS=3D64 $(INCLUDES) $(KHDR_INCLUDES) -DLIBNUMA_VER_$(LIBNUMA_TEST)=3D1 -LDLIBS :=3D -lpthread -lrt -lnuma +LDLIBS :=3D -lpthread -lrt -lnuma -ldw =20 LOCAL_HDRS :=3D \ ../include/futextest.h \ @@ -23,6 +23,7 @@ TEST_GEN_PROGS :=3D \ futex_numa_mpol \ futex_waitv \ futex_numa \ + robust_list_critical \ robust_list =20 TEST_PROGS :=3D 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 + * + * Testing the VDSO robust=E2=80=91list unlock critical section. Specifica= lly, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +static char *vdso_dbg_file =3D +#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, ui= nt64_t end) +{ + unsigned long pc; + +#if defined(__x86_64__) + pc =3D regs->rip; +#elif defined(__riscv) + pc =3D reg->pc; +# error Missing ptrace support +#endif + if (pc >=3D (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 =3D dwfl_module_getsymtab(mod); + if (vdso_syms < 0) + err(1, "dwfl_module_getsymtab: %s", dwfl_errmsg (-1)); + + for (i =3D 0; i < vdso_syms; i++) { + + sname =3D dwfl_module_getsym_info(mod, i, &sym, &addr, NULL, NULL, NULL); + if (!strcmp("__vdso_futex_robust_list64_try_unlock", sname)) { + frtu64 =3D (frtu64_t) addr; + frtu64_end =3D addr + sym.st_size; + } else if (!strcmp("__futex_list64_try_unlock_cs_start", sname)) { + __futex_list64_try_unlock_cs_start =3D addr; + } else if (!strcmp("__futex_list64_try_unlock_cs_end", sname)) { + __futex_list64_try_unlock_cs_end =3D addr; + } else if (!strcmp("__futex_list64_try_unlock_cs_success", sname)) { + __futex_list64_try_unlock_cs_success =3D addr; + + } else if (!strcmp("__vdso_futex_robust_list32_try_unlock", sname)) { + frtu32 =3D (frtu32_t) addr; + frtu32_end =3D addr + sym.st_size; + } else if (!strcmp("__futex_list32_try_unlock_cs_start", sname)) { + __futex_list32_try_unlock_cs_start =3D addr; + } else if (!strcmp("__futex_list32_try_unlock_cs_end", sname)) { + __futex_list32_try_unlock_cs_end =3D addr; + } else if (!strcmp("__futex_list32_try_unlock_cs_success", sname)) { + __futex_list32_try_unlock_cs_success =3D addr; + } + + } + return DWARF_CB_OK; +} + +static int find_dbg_local_def(Dwfl_Module *mod, void **userdata, const cha= r *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 =3D open(vdso_dbg_file, O_RDONLY); + if (fd >=3D 0) + return fd; + } + + return dwfl_standard_find_debuginfo(mod, userdata, modname, base, file_na= me, + debuglink_file, debuglink_crc, debuginfo_file_name); +} + +static void get_symbols(void) +{ + char *debuginfo_path; + const Dwfl_Callbacks proc_callbacks =3D { + .find_debuginfo =3D find_dbg_local_def, + .debuginfo_path =3D &debuginfo_path, + .find_elf =3D dwfl_linux_proc_find_elf, + }; + int result; + pid_t pid; + Dwfl *dwfl; + + dwfl =3D dwfl_begin(&proc_callbacks); + if (!dwfl) + errx(1, "dwfl_begin: %s", dwfl_errmsg(-1)); + + pid =3D getpid(); + result =3D 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 =3D 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) !=3D 0) + err (1, "dwfl_report_end: %s", dwfl_errmsg(-1)); + + if (dwfl_getmodules(dwfl, module_callback, NULL, 0) !=3D 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_3= 2bit) +{ + struct robust_list_head *rhead; + uint32_t *lock; + pid_t lock_tid; + int error =3D 1; + pid_t tid; + size_t sz; + + syscall(SYS_get_robust_list, 0, &rhead, &sz); + pthread_mutex_init(&test_mutex, NULL); + + tid =3D gettid(); + lock =3D (uint32_t *)&test_mutex.__data.__lock; + *lock =3D tid; + /* Set the pending prior unlocking */ + rhead->list_op_pending =3D (struct robust_list *)&test_mutex.__data.__lis= t.__next; + + raise(SIGTRAP); + if (is_32bit) + lock_tid =3D frtu32(lock, tid, (uint32_t *)&rhead->list_op_pending); + else + lock_tid =3D frtu64(lock, tid, (uint64_t *)&rhead->list_op_pending); + + if (lock_tid !=3D tid) + TH_LOG("Non contended unlock failed. Return: %08x expected %08x\n", + lock_tid, tid); + else + error =3D 0; + return error; +} + +enum trace_state { + STATE_WAIT =3D 0, + STATE_ENTER_VDSO, + STATE_IN_CS, + STATE_OUT_CS, + STATE_LEAVE_VDSO, +}; + +static void trace_child(struct __test_metadata *_metadata, pid_t child, bo= ol is_32bit) +{ + int state =3D STATE_WAIT; + struct robust_list_head *rhead; + size_t sz; + bool do_end =3D 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 =3D waitpid(child, &wstatus, 0); + if (rpid !=3D child) + errx(1, "waitpid"); + if (!do_end) { + if (WSTOPSIG(wstatus) !=3D 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, ®s) !=3D 0) + errx(1, "PTRACE_GETREGS"); + + if (is_32bit) { + in_vdso =3D pc_is_within(®s, (long)frtu32, frtu32_end); + } else { + in_vdso =3D pc_is_within(®s, (long)frtu64, frtu64_end); + } + if (in_vdso) { + unsigned long rhead_val; + + if (state =3D=3D STATE_WAIT) { + state =3D STATE_ENTER_VDSO; + + } else { + if (is_32bit) { + if (pc_is_within(®s, __futex_list32_try_unlock_cs_start, + __futex_list32_try_unlock_cs_end)) + state =3D STATE_IN_CS; + else + state =3D STATE_OUT_CS; + } else { + if (pc_is_within(®s, __futex_list64_try_unlock_cs_start, + __futex_list64_try_unlock_cs_end)) + state =3D STATE_IN_CS; + else + state =3D STATE_OUT_CS; + } + } + + errno =3D 0; + rhead_val =3D ptrace(PTRACE_PEEKDATA, child, &rhead->list_op_pending, 0= ); + if (rhead_val =3D=3D -1 && errno !=3D 0) + err(1, "PTRACE_PEEKDATA"); + + lock_val =3D ptrace(PTRACE_PEEKDATA, child, &test_mutex.__data.__lock, = 0); + if (lock_val =3D=3D -1 && errno !=3D 0) + err(1, "PTRACE_PEEKDATA"); + + if (state =3D=3D STATE_ENTER_VDSO) { + /* Entering VDSO, it supposed to be locked */ + ASSERT_NE(rhead_val, 0); + ASSERT_EQ(lock_val, child); + + } else if (state =3D=3D 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 &=3D 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 =3D=3D STATE_WAIT) { + ret =3D ptrace(PTRACE_SINGLESTEP, child, 0, 0); + } else if (state =3D=3D STATE_OUT_CS) { + state =3D STATE_LEAVE_VDSO; + ret =3D ptrace(PTRACE_SINGLESTEP, child, 0, 0); + } else if (state =3D=3D STATE_LEAVE_VDSO) { + state =3D STATE_WAIT; + do_end =3D true; + ret =3D ptrace(PTRACE_CONT, child, 0, 0); + } + if (ret !=3D 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 =3D=3D 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=3DPATH/TO/vdso64.so.db= g.\n"); + } + + child =3D fork(); + if (child < 0) + err(1, "fork()"); + + if (!child) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) !=3D 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 =3D=3D 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=3DPATH/TO/vdso32.so.db= g.\n"); + } + + child =3D fork(); + if (child < 0) + err(1, "fork()"); + + if (!child) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) !=3D 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 =3D getenv("VDSO_DBG"); + if (vdso_dbg) { + vdso_dbg_file =3D vdso_dbg; + ksft_print_msg("Using %s as VDSO debug symbols\n", vdso_dbg_file); + } + + get_symbols(); + + return test_harness_run(argc, argv); +} --=20 2.53.0