nptl: Fix lost wake-up in robust mutex ENOTRECOVERABLE path (BZ 34724)

pthread_mutex_lock, pthread_mutex_timedlock, and pthread_mutex_trylock
acquire a not recoverable robust mutex and release it with lll_unlock
before returning ENOTRECOVERABLE.  lll_unlock only wakes a waiter if the
lock value is larger than 1, which is never true for a robust lock with
FUTEX_WAITERS (bit 31) set.

Since pthread_mutex_unlock wakes only one waiter when the mutex becomes
not recoverable, and the woken thread acquires the lock with FUTEX_WAITERS
set, the remaining waiters block forever.

Release the lock as __pthread_mutex_unlock_full does, waking a waiter
if FUTEX_WAITERS was set.

Checked on x86_64-linux-gnu and aarch64-linux-gnu.
This commit is contained in:
Adhemerval Zanella committed 2026-10-19 15:56:50 -03:00
1 parent fd79535e41
commit c8041a1ec8
5 files changed
+142 -4

No files matched your search

+1
View File
@@ -328,6 +328,7 @@ tests = \
tst-pthread_exit-nothreads-static \
tst-pthread_gettid_np \
tst-robust-fork \
tst-robust-notrecoverable-wake \
tst-robust-trylock-notrecoverable \
tst-robust12 \
tst-robustpi1 \
+6 -1
View File
@@ -320,7 +320,12 @@ __pthread_mutex_lock_full (pthread_mutex_t *mutex)
/* This mutex is now not recoverable. */
mutex->__data.__count = 0;
int private = PTHREAD_ROBUST_MUTEX_PSHARED (mutex);
lll_unlock (mutex->__data.__lock, private);
/* lll_unlock cannot be used here, since it only wakes if the lock
value is larger than 1, and a robust lock with FUTEX_WAITERS set
is negative. */
if ((atomic_exchange_release (&mutex->__data.__lock, 0)
& FUTEX_WAITERS) != 0)
futex_wake ((unsigned int *) &mutex->__data.__lock, 1, private);
/* FIXME This violates the mutex destruction requirements. See
__pthread_mutex_unlock_full. */
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL);
+5 -1
View File
@@ -257,7 +257,11 @@ __pthread_mutex_clocklock_common (pthread_mutex_t *mutex,
/* This mutex is now not recoverable. */
mutex->__data.__count = 0;
int private = PTHREAD_ROBUST_MUTEX_PSHARED (mutex);
lll_unlock (mutex->__data.__lock, private);
/* See __pthread_mutex_lock_full for why lll_unlock cannot be used
here. */
if ((atomic_exchange_release (&mutex->__data.__lock, 0)
& FUTEX_WAITERS) != 0)
futex_wake ((unsigned int *) &mutex->__data.__lock, 1, private);
/* FIXME This violates the mutex destruction requirements. See
__pthread_mutex_unlock_full. */
return __pthread_mutex_robust_error (ENOTRECOVERABLE);
+6 -2
View File
@@ -170,8 +170,12 @@ ___pthread_mutex_trylock (pthread_mutex_t *mutex)
{
/* This mutex is now not recoverable. */
mutex->__data.__count = 0;
if (oldval == 0)
lll_unlock (mutex->__data.__lock,
/* See __pthread_mutex_lock_full for why lll_unlock cannot be
used here. */
if (oldval == 0
&& (atomic_exchange_release (&mutex->__data.__lock, 0)
& FUTEX_WAITERS) != 0)
futex_wake ((unsigned int *) &mutex->__data.__lock, 1,
PTHREAD_ROBUST_MUTEX_PSHARED (mutex));
/* FIXME This violates the mutex destruction requirements. See
__pthread_mutex_unlock_full. */
+124
View File
@@ -0,0 +1,124 @@
/* Test that all waiters wake up when a robust mutex becomes not recoverable.
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/>. */
/* The owner of a robust mutex dies, the next owner gets EOWNERDEAD, and while
it holds the mutex several other threads block on it. The owner then
unlocks without calling pthread_mutex_consistent, so the mutex becomes not
recoverable and every waiter has to return ENOTRECOVERABLE.
The unlock wakes a single waiter, which acquires the lock with
FUTEX_WAITERS set and has to wake the next one when it releases it again. */
#include <errno.h>
#include <pthread.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <support/check.h>
#include <support/process_state.h>
#include <support/timespec.h>
#include <support/xthread.h>
#include <support/xtime.h>
enum { nwaiters = 3 };
static pthread_mutex_t mutex;
static bool use_timedlock;
static void *
lock_and_exit (void *arg)
{
xpthread_mutex_lock (&mutex);
/* Exit while holding the mutex, so that the next owner sees EOWNERDEAD. */
return NULL;
}
static void *
waiter (void *arg)
{
pid_t *tid = arg;
__atomic_store_n (tid, gettid (), __ATOMIC_RELEASE);
int r;
if (use_timedlock)
{
struct timespec timeout = timespec_add (xclock_now (CLOCK_REALTIME),
make_timespec (3600, 0));
r = pthread_mutex_timedlock (&mutex, &timeout);
}
else
r = pthread_mutex_lock (&mutex);
TEST_COMPARE (r, ENOTRECOVERABLE);
return NULL;
}
static void
run_test (bool pshared, bool timedlock)
{
printf ("info: pshared=%d timedlock=%d\n", pshared, timedlock);
use_timedlock = timedlock;
pthread_mutexattr_t attr;
xpthread_mutexattr_init (&attr);
xpthread_mutexattr_setrobust (&attr, PTHREAD_MUTEX_ROBUST);
if (pshared)
xpthread_mutexattr_setpshared (&attr, PTHREAD_PROCESS_SHARED);
xpthread_mutex_init (&mutex, &attr);
xpthread_mutexattr_destroy (&attr);
xpthread_join (xpthread_create (NULL, lock_and_exit, NULL));
TEST_COMPARE (pthread_mutex_lock (&mutex), EOWNERDEAD);
pid_t tids[nwaiters] = { 0 };
pthread_t threads[nwaiters];
for (int i = 0; i < nwaiters; i++)
threads[i] = xpthread_create (NULL, waiter, &tids[i]);
/* Wait until all waiters are blocked on the mutex. A waiter that has
published its TID does not sleep anywhere else before blocking. */
for (int i = 0; i < nwaiters; i++)
{
pid_t tid;
while ((tid = __atomic_load_n (&tids[i], __ATOMIC_ACQUIRE)) == 0)
sched_yield ();
support_thread_state_wait (tid, support_process_state_sleeping);
}
/* Make the mutex not recoverable. */
TEST_COMPARE (pthread_mutex_unlock (&mutex), 0);
/* If the wake-up is lost, the remaining waiters block forever and the
test times out here. */
for (int i = 0; i < nwaiters; i++)
xpthread_join (threads[i]);
TEST_COMPARE (pthread_mutex_destroy (&mutex), 0);
}
static int
do_test (void)
{
run_test (false, false);
run_test (false, true);
run_test (true, false);
run_test (true, true);
return 0;
}
#include <support/test-driver.c>