[3.14] gh-148820: Fix _PyRawMutex use-after-free on spurious semaphore wakeup (gh-148852) (#148884)

_PyRawMutex_UnlockSlow CAS-removes the waiter from the list and then
calls _PySemaphore_Wakeup, with no handshake. If _PySemaphore_Wait
returns Py_PARK_INTR, the waiter can destroy its stack-allocated
semaphore before the unlocker's Wakeup runs, causing a fatal error from
ReleaseSemaphore / sem_post.

Loop in _PyRawMutex_LockSlow until _PySemaphore_Wait returns Py_PARK_OK,
which is only signalled when a matching Wakeup has been observed.

Also include GetLastError() and the handle in the Windows fatal messages
in _PySemaphore_Init, _PySemaphore_Wait, and _PySemaphore_Wakeup to make
similar races easier to diagnose in the future.

(cherry picked from commit ad3c5b7958)
This commit is contained in:
Sam Gross 2026-04-22 14:59:58 -04:00 committed by GitHub
parent 5aa8234cce
commit e5d5541683
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 23 additions and 5 deletions

View file

@ -212,7 +212,16 @@ _PyRawMutex_LockSlow(_PyRawMutex *m)
// Wait for us to be woken up. Note that we still have to lock the
// mutex ourselves: it is NOT handed off to us.
_PySemaphore_Wait(&waiter.sema, -1, /*detach=*/0);
//
// Loop until we observe an actual wakeup. A return of Py_PARK_INTR
// could otherwise let us exit _PySemaphore_Wait and destroy
// `waiter.sema` while _PyRawMutex_UnlockSlow's matching
// _PySemaphore_Wakeup is still pending, since the unlocker has
// already CAS-removed us from the waiter list without any handshake.
int res;
do {
res = _PySemaphore_Wait(&waiter.sema, -1, /*detach=*/0);
} while (res != Py_PARK_OK);
}
_PySemaphore_Destroy(&waiter.sema);