|
|
Message-ID: <b78eff8d05538224665e71a2fd985c13@ispras.ru>
Date: Wed, 07 Sep 2022 03:46:53 +0300
From: Alexey Izbyshev <izbyshev@...ras.ru>
To: musl@...ts.openwall.com
Subject: Illegal killlock skipping when transitioning to single-threaded state
Hi,
While reading pthread_exit() implementation I noticed that it can set
"libc.need_locks" to -1 while still holding the killlock of the exiting
thread:
if (!--libc.threads_minus_1) libc.need_locks = -1;
If the remaining thread attempts to acquire the same killlock
concurrently (which is valid for joinable threads), it works fine
because LOCK() resets "libc.need_locks" only after a_cas():
int need_locks = libc.need_locks;
if (!need_locks) return;
/* fast path: INT_MIN for the lock, +1 for the congestion */
int current = a_cas(l, 0, INT_MIN + 1);
if (need_locks < 0) libc.need_locks = 0;
if (!current) return;
However, because "libc.need_locks" is reset when using LOCK() for any
other lock too, the following could happen (E is the exiting thread, R
is the remaining thread):
E: libc.need_locks = -1
R: LOCK(unrelated_lock)
R: libc.need_locks = 0
R: UNLOCK(unrelated_lock)
R: LOCK(E->killlock) // skips locking, now both R and E think they are
holding the lock
R: UNLOCK(E->killlock)
E: UNLOCK(E->killlock)
The lack of mutual exclusion means that the tid reuse problem that
killlock is supposed to prevent might occur.
Moreover, when UNLOCK(E->killlock) is called concurrently by R and E,
a_fetch_add() could be done twice if timing is such that both threads
notice that "l[0] < 0":
/* Check l[0] to see if we are multi-threaded. */
if (l[0] < 0) {
if (a_fetch_add(l, -(INT_MIN + 1)) != (INT_MIN + 1)) {
__wake(l, 1, 1);
}
}
In that case E->killlock will be assigned to INT_MAX (i.e. "unlocked
with INT_MAX waiters"). This is a bad state because the next LOCK() will
wrap it to "unlocked" state instead of locking. Also, if more than two
threads attempt to use it, a deadlock will occur if two
supposedly-owners execute a_fetch_add() concurrently in UNLOCK() after a
third thread registered itself as a waiter because the lock will wrap to
INT_MIN.
Reordering the "libc.need_locks = -1" assignment and UNLOCK(E->killlock)
and providing a store barrier between them should fix the issue.
Thanks,
Alexey
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.