[PATCH v2 2/3] nptl: Do not always assume set_robust_list availability (BZ 33225)
Adhemerval Zanella <[email protected]> Wed, 29 Jul 2026 14:46:37 -0300
| Newsgroups | gmane.comp.lib.glibc.alpha |
|---|---|
| Message-ID | <[email protected]> |
The __ASSUME_SET_ROBUST_LIST macro gates the definition of
__nptl_set_robust_list_avail, which advertises process-shared robust
mutex support and is set by __tls_init_tp if the initial set_robust_list
call succeeds.
Some kernel configurations, and qemu-user (for all ABIs), do not
implement set_robust_list. With __ASSUME_SET_ROBUST_LIST defined the
missing support is not detected, and pthread_mutex_init succeeds where
it should fail. For instance, the sequence below returns 0 on qemu-user
even though set_robust_list fails with ENOSYS:
pthread_mutexattr_init (&attr);
pthread_mutexattr_setpshared (&attr, PTHREAD_PROCESS_SHARED);
pthread_mutexattr_setrobust (&attr, PTHREAD_MUTEX_ROBUST);
pthread_mutex_init (&mutex, &attr);
Remove __ASSUME_SET_ROBUST_LIST and always rely on the runtime
__nptl_set_robust_list_avail detection.
The userspace robust list cleanup in start_thread is now built for all
targets, so it also needs a fix: it assumed that a target without
set_robust_list also lacks priority-inheritance futexes, and dereferenced
the list entries directly. qemu-user implements the PI futex operations
while returning ENOSYS for set_robust_list, and bit 0 of an entry is set
for PI mutexes.
Checked on x86_64-linux-gnu, and on x86_64-linux-gnu under qemu-user
where set_robust_list returns ENOSYS.
---
nptl/Makefile | 1 +
nptl/pthread_create.c | 57 +++++++++++------
nptl/pthread_mutex_init.c | 3 -
nptl/tst-mutexpi10.c | 7 ++-
nptl/tst-robust-fork.c | 21 +++++++
nptl/tst-robust-pshared.c | 63 +++++++++++++++++++
sysdeps/nptl/dl-tls_init_tp.c | 8 +--
sysdeps/nptl/pthreadP.h | 2 -
sysdeps/pthread/tst-robust8.c | 6 ++
sysdeps/unix/sysv/linux/arm/kernel-features.h | 7 ---
.../unix/sysv/linux/hppa/kernel-features.h | 3 -
sysdeps/unix/sysv/linux/kernel-features.h | 5 --
.../unix/sysv/linux/m68k/kernel-features.h | 5 --
.../unix/sysv/linux/mips/kernel-features.h | 6 --
.../unix/sysv/linux/riscv/kernel-features.h | 5 --
.../unix/sysv/linux/sparc/kernel-features.h | 6 --
16 files changed, 135 insertions(+), 70 deletions(-)
create mode 100644 nptl/tst-robust-pshared.c
diff --git a/nptl/Makefile b/nptl/Makefile
index 97a0fb7f658..01aa3619932 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -326,6 +326,7 @@ tests = \
tst-pthread_exit-nothreads-static \
tst-pthread_gettid_np \
tst-robust-fork \
+ tst-robust-pshared \
tst-robustpi1 \
tst-robustpi2 \
tst-robustpi3 \
diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
index fcb06c68c71..f8220c22208 100644
--- a/nptl/pthread_create.c
+++ b/nptl/pthread_create.c
@@ -333,6 +333,21 @@ static int create_thread (struct pthread *pd, const struct pthread_attr *attr,
return 0;
}
+/* Bit 0 of a robust list entry marks a priority-inheritance mutex, so it has
+ to be masked off before ENTRY is dereferenced. */
+static inline void *
+robust_list_entry (void *entry)
+{
+ return (void *) ((uintptr_t) entry & ~1ul);
+}
+
+/* Return true if ENTRY refers to a mutex on the robust list within HEAD. */
+static inline bool
+robust_list_has_entry (void *entry, void *head)
+{
+ return robust_list_entry (entry) != head;
+}
+
/* Local function to start thread and handle cleanup. */
static int _Noreturn
start_thread (void *arg)
@@ -386,9 +401,7 @@ start_thread (void *arg)
__libc_fatal ("Fatal glibc error: rseq registration failed\n");
}
-#ifndef __ASSUME_SET_ROBUST_LIST
if (__nptl_set_robust_list_avail)
-#endif
{
/* This call should never fail because the initial call in init.c
succeeded. */
@@ -536,38 +549,42 @@ start_thread (void *arg)
pd->exiting = true;
__libc_lock_unlock (pd->exit_lock);
-#ifndef __ASSUME_SET_ROBUST_LIST
/* If this thread has any robust mutexes locked, handle them now. */
-# if __PTHREAD_MUTEX_HAVE_PREV
+#if __PTHREAD_MUTEX_HAVE_PREV
void *robust = pd->robust_head.list;
-# else
+#else
__pthread_slist_t *robust = pd->robust_list.__next;
-# endif
- /* We let the kernel do the notification if it is able to do so.
- If we have to do it here there for sure are no PI mutexes involved
- since the kernel support for them is even more recent. */
+#endif
+ /* We let the kernel do the notification if it is able to do so. */
if (!__nptl_set_robust_list_avail
- && __builtin_expect (robust != (void *) &pd->robust_head, 0))
+ && __glibc_unlikely (robust_list_has_entry (robust, &pd->robust_head)))
{
do
{
+ void *entry = robust_list_entry (robust);
+ bool is_pi = ((uintptr_t) robust & 1) != 0;
struct __pthread_mutex_s *this = (struct __pthread_mutex_s *)
- ((char *) robust - offsetof (struct __pthread_mutex_s,
- __list.__next));
- robust = *((void **) robust);
+ ((char *) entry - offsetof (struct __pthread_mutex_s,
+ __list.__next));
+ robust = *((void **) entry);
-# if __PTHREAD_MUTEX_HAVE_PREV
+#if __PTHREAD_MUTEX_HAVE_PREV
this->__list.__prev = NULL;
-# endif
+#endif
this->__list.__next = NULL;
- atomic_fetch_or_acquire (&this->__lock, FUTEX_OWNER_DIED);
- futex_wake ((unsigned int *) &this->__lock, 1,
- /* XYZ */ FUTEX_SHARED);
+ /* PI mutexes are handled by the kernel even without the robust
+ list, FUTEX_LOCK_PI reports EOWNERDEAD once the recorded owner
+ is gone, and a plain FUTEX_WAKE on a PI futex is invalid. */
+ if (!is_pi)
+ {
+ atomic_fetch_or_acquire (&this->__lock, FUTEX_OWNER_DIED);
+ futex_wake ((unsigned int *) &this->__lock, 1,
+ /* XYZ */ FUTEX_SHARED);
+ }
}
- while (robust != (void *) &pd->robust_head);
+ while (robust_list_has_entry (robust, &pd->robust_head));
}
-#endif
/* Release the vDSO getrandom per-thread buffer with all signal blocked,
to avoid creating a new free-state block during thread release. */
diff --git a/nptl/pthread_mutex_init.c b/nptl/pthread_mutex_init.c
index 8a5450bcb08..9be08332f1f 100644
--- a/nptl/pthread_mutex_init.c
+++ b/nptl/pthread_mutex_init.c
@@ -93,12 +93,9 @@ ___pthread_mutex_init (pthread_mutex_t *mutex,
if ((imutexattr->mutexkind & PTHREAD_MUTEXATTR_FLAG_ROBUST) != 0)
{
-#ifndef __ASSUME_SET_ROBUST_LIST
if ((imutexattr->mutexkind & PTHREAD_MUTEXATTR_FLAG_PSHARED) != 0
&& !__nptl_set_robust_list_avail)
return ENOTSUP;
-#endif
-
mutex_kind |= PTHREAD_MUTEX_ROBUST_NORMAL_NP;
}
diff --git a/nptl/tst-mutexpi10.c b/nptl/tst-mutexpi10.c
index 3978fb25fdf..c7db0ba1be8 100644
--- a/nptl/tst-mutexpi10.c
+++ b/nptl/tst-mutexpi10.c
@@ -58,7 +58,12 @@ do_test (void)
xpthread_mutexattr_setrobust (&attr, robust[r]);
pthread_mutex_t mtx;
- xpthread_mutex_init (&mtx, &attr);
+ /* ENOTSUP is returned if the kernel does not support priority
+ inheritance futexes, in which case there is nothing to check. */
+ int ret = pthread_mutex_init (&mtx, &attr);
+ if (ret == ENOTSUP)
+ continue;
+ TEST_COMPARE (ret, 0);
/* Uncontended case does not trigger any futex call. */
struct timespec tmo = timespec_add (xclock_now (clocks[c].clk),
diff --git a/nptl/tst-robust-fork.c b/nptl/tst-robust-fork.c
index be9c6d3cc41..6e235bbd979 100644
--- a/nptl/tst-robust-fork.c
+++ b/nptl/tst-robust-fork.c
@@ -159,9 +159,20 @@ one_test (int parent_bits, int child_bits, int nonshared_bits,
xmunmap (shared, sizeof (*shared));
}
+static inline bool
+is_robust_pshared (int bits)
+{
+ return (bits & (mutex_robust | mutex_pshared))
+ == (mutex_robust | mutex_pshared);
+}
+
static int
do_test (void)
{
+ bool robust_support = support_process_shared_robust_mutex ();
+ if (test_verbose)
+ printf ("info: robust_support=%d\n", robust_support);
+
for (int parent_bits = 0; parent_bits <= mutex_all_bits; ++parent_bits)
for (int child_bits = 0; child_bits <= mutex_all_bits; ++child_bits)
for (int nonshared_bits = 0; nonshared_bits <= mutex_all_bits;
@@ -175,6 +186,16 @@ do_test (void)
parent_bits, child_bits, nonshared_bits,
lock_nonshared ? " lock_nonshared" : "",
lock_child ? " lock_child" : "");
+ if (!robust_support
+ && (is_robust_pshared (parent_bits)
+ || is_robust_pshared (child_bits)
+ || is_robust_pshared (nonshared_bits)))
+ {
+ if (test_verbose)
+ printf ("info: skipping test due to missing"
+ " process-shared robust mutex support\n");
+ continue;
+ }
one_test (parent_bits, child_bits, nonshared_bits,
lock_nonshared, lock_child);
}
diff --git a/nptl/tst-robust-pshared.c b/nptl/tst-robust-pshared.c
new file mode 100644
index 00000000000..3edbabadf91
--- /dev/null
+++ b/nptl/tst-robust-pshared.c
@@ -0,0 +1,63 @@
+/* Check that process-shared robust mutex creation follows kernel
+ support (BZ #33225).
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdbool.h>
+
+#include <support/check.h>
+#include <support/xthread.h>
+
+static int
+do_test (void)
+{
+ bool robust_support = support_process_shared_robust_mutex ();
+
+ for (int pshared = 0; pshared < 2; pshared++)
+ for (int robust = 0; robust < 2; robust++)
+ {
+ pthread_mutexattr_t attr;
+ xpthread_mutexattr_init (&attr);
+ if (pshared)
+ xpthread_mutexattr_setpshared (&attr, PTHREAD_PROCESS_SHARED);
+ if (robust)
+ xpthread_mutexattr_setrobust (&attr, PTHREAD_MUTEX_ROBUST);
+
+ /* Only process-shared robust mutexes require the kernel to walk the
+ robust list on process exit, robust mutexes private to the process
+ are handled by pthread_create itself. Non-robust mutexes do not
+ need the robust list at all. */
+ int expected = pshared && robust && !robust_support ? ENOTSUP : 0;
+
+ pthread_mutex_t mtx;
+ TEST_COMPARE (pthread_mutex_init (&mtx, &attr), expected);
+ if (expected == 0)
+ {
+ TEST_COMPARE (pthread_mutex_lock (&mtx), 0);
+ TEST_COMPARE (pthread_mutex_unlock (&mtx), 0);
+ xpthread_mutex_destroy (&mtx);
+ }
+
+ xpthread_mutexattr_destroy (&attr);
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/nptl/dl-tls_init_tp.c b/sysdeps/nptl/dl-tls_init_tp.c
index 72cc4087c91..75e3712a6d8 100644
--- a/sysdeps/nptl/dl-tls_init_tp.c
+++ b/sysdeps/nptl/dl-tls_init_tp.c
@@ -28,10 +28,8 @@
#define TUNABLE_NAMESPACE pthread
#include <dl-tunables.h>
-#ifndef __ASSUME_SET_ROBUST_LIST
bool __nptl_set_robust_list_avail;
rtld_hidden_data_def (__nptl_set_robust_list_avail)
-#endif
bool __nptl_initial_report_events;
rtld_hidden_def (__nptl_initial_report_events)
@@ -95,11 +93,7 @@ __tls_init_tp (void)
int res = INTERNAL_SYSCALL_CALL (set_robust_list, &pd->robust_head,
sizeof (struct robust_list_head));
if (!INTERNAL_SYSCALL_ERROR_P (res))
- {
-#ifndef __ASSUME_SET_ROBUST_LIST
- __nptl_set_robust_list_avail = true;
-#endif
- }
+ __nptl_set_robust_list_avail = true;
}
{
diff --git a/sysdeps/nptl/pthreadP.h b/sysdeps/nptl/pthreadP.h
index de432d40324..c62c8982905 100644
--- a/sysdeps/nptl/pthreadP.h
+++ b/sysdeps/nptl/pthreadP.h
@@ -192,12 +192,10 @@ libc_hidden_proto (__pthread_keys)
extern unsigned int __nptl_nthreads;
libc_hidden_proto (__nptl_nthreads)
-#ifndef __ASSUME_SET_ROBUST_LIST
/* True if the set_robust_list system call works. Initialized in
__tls_init_tp. */
extern bool __nptl_set_robust_list_avail;
rtld_hidden_proto (__nptl_set_robust_list_avail)
-#endif
/* Thread Priority Protection. */
extern int __sched_fifo_min_prio;
diff --git a/sysdeps/pthread/tst-robust8.c b/sysdeps/pthread/tst-robust8.c
index 65f37feddfa..3b3ca161905 100644
--- a/sysdeps/pthread/tst-robust8.c
+++ b/sysdeps/pthread/tst-robust8.c
@@ -10,6 +10,8 @@
#include <pthreadP.h>
+#include <support/check.h>
+#include <support/xthread.h>
static void prepare (void);
@@ -143,6 +145,10 @@ child (int round)
static int
do_test (void)
{
+ /* Process shared robust mutexes requires kernel support. */
+ if (!support_process_shared_robust_mutex ())
+ FAIL_UNSUPPORTED ("process-shared robust mutexes not supported");
+
if (ftruncate (fd, N * sizeof (pthread_mutex_t)) != 0)
{
puts ("cannot size new file");
diff --git a/sysdeps/unix/sysv/linux/arm/kernel-features.h b/sysdeps/unix/sysv/linux/arm/kernel-features.h
index d169bf58946..1511de1c843 100644
--- a/sysdeps/unix/sysv/linux/arm/kernel-features.h
+++ b/sysdeps/unix/sysv/linux/arm/kernel-features.h
@@ -20,13 +20,6 @@
#include <endian.h>
#include_next <kernel-features.h>
-/* The ARM kernel before 3.14.3 may or may not support
- futex_atomic_cmpxchg_inatomic, depending on kernel
- configuration. */
-#if __LINUX_KERNEL_VERSION < 0x030E03
-# undef __ASSUME_SET_ROBUST_LIST
-#endif
-
/* ARM fadvise64_64 reorganize the syscall arguments. */
#define __ASSUME_FADVISE64_64_6ARG 1
diff --git a/sysdeps/unix/sysv/linux/hppa/kernel-features.h b/sysdeps/unix/sysv/linux/hppa/kernel-features.h
index 1b46336e4a0..45ef64bb1f9 100644
--- a/sysdeps/unix/sysv/linux/hppa/kernel-features.h
+++ b/sysdeps/unix/sysv/linux/hppa/kernel-features.h
@@ -30,6 +30,3 @@
#undef __ASSUME_CLONE_DEFAULT
#define __ASSUME_CLONE_BACKWARDS 1
-
-/* QEMU does not support set_robust_list. */
-#undef __ASSUME_SET_ROBUST_LIST
diff --git a/sysdeps/unix/sysv/linux/kernel-features.h b/sysdeps/unix/sysv/linux/kernel-features.h
index 85f6e888dba..33f3c2d2c58 100644
--- a/sysdeps/unix/sysv/linux/kernel-features.h
+++ b/sysdeps/unix/sysv/linux/kernel-features.h
@@ -49,11 +49,6 @@
SH this appeared first in 2.6.19-rc1. */
#define __ASSUME_PSELECT 1
-/* Support for inter-process robust mutexes was added in 2.6.17 (but
- some architectures lack futex_atomic_cmpxchg_inatomic in some
- configurations). */
-#define __ASSUME_SET_ROBUST_LIST 1
-
/* The termios2 interface was introduced across all architectures except
Alpha in kernel 2.6.22. */
#define __ASSUME_TERMIOS2 1
diff --git a/sysdeps/unix/sysv/linux/m68k/kernel-features.h b/sysdeps/unix/sysv/linux/m68k/kernel-features.h
index d66fe16fa8d..db90d8a8c07 100644
--- a/sysdeps/unix/sysv/linux/m68k/kernel-features.h
+++ b/sysdeps/unix/sysv/linux/m68k/kernel-features.h
@@ -42,11 +42,6 @@
# undef __ASSUME_GETPEERNAME_SYSCALL
#endif
-/* No support for PI futexes or robust mutexes before 3.10 for m68k. */
-#if __LINUX_KERNEL_VERSION < 0x030a00
-# undef __ASSUME_SET_ROBUST_LIST
-#endif
-
/* m68k only supports ipc syscall before 5.1. */
#if __LINUX_KERNEL_VERSION < 0x050100
# undef __ASSUME_DIRECT_SYSVIPC_SYSCALLS
diff --git a/sysdeps/unix/sysv/linux/mips/kernel-features.h b/sysdeps/unix/sysv/linux/mips/kernel-features.h
index 7790f0d14b5..0d30d4d8284 100644
--- a/sysdeps/unix/sysv/linux/mips/kernel-features.h
+++ b/sysdeps/unix/sysv/linux/mips/kernel-features.h
@@ -21,12 +21,6 @@
#include_next <kernel-features.h>
-/* The MIPS kernel does not support futex_atomic_cmpxchg_inatomic if
- emulating LL/SC. */
-#if __mips == 1 || defined _MIPS_ARCH_R5900
-# undef __ASSUME_SET_ROBUST_LIST
-#endif
-
/* Define this if your 32-bit syscall API requires 64-bit register
pairs to start with an even-number register. */
#if _MIPS_SIM == _ABIO32
diff --git a/sysdeps/unix/sysv/linux/riscv/kernel-features.h b/sysdeps/unix/sysv/linux/riscv/kernel-features.h
index 32087c0602c..9172373e989 100644
--- a/sysdeps/unix/sysv/linux/riscv/kernel-features.h
+++ b/sysdeps/unix/sysv/linux/riscv/kernel-features.h
@@ -21,8 +21,3 @@
#undef __ASSUME_CLONE_DEFAULT
#define __ASSUME_CLONE_BACKWARDS 1
-
-/* No support for PI mutexes or robust futexes before 4.20. */
-#if __LINUX_KERNEL_VERSION < 0x041400
-# undef __ASSUME_SET_ROBUST_LIST
-#endif
diff --git a/sysdeps/unix/sysv/linux/sparc/kernel-features.h b/sysdeps/unix/sysv/linux/sparc/kernel-features.h
index eb293411135..dad512cec35 100644
--- a/sysdeps/unix/sysv/linux/sparc/kernel-features.h
+++ b/sysdeps/unix/sysv/linux/sparc/kernel-features.h
@@ -19,12 +19,6 @@
#include_next <kernel-features.h>
-/* 32-bit SPARC kernels do not support
- futex_atomic_cmpxchg_inatomic. */
-#if !defined __arch64__ && !defined __sparc_v9__
-# undef __ASSUME_SET_ROBUST_LIST
-#endif
-
/* These syscalls were added for 32-bit in 4.4 (but present for 64-bit
in all supported kernel versions); the architecture-independent
kernel-features.h assumes some of them to be present by default.
--
2.53.0