]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-148820: Fix _PyRawMutex use-after-free on spurious semaphore wakeup (gh...
authorSam Gross <colesbury@gmail.com>
Wed, 22 Apr 2026 18:59:58 +0000 (14:59 -0400)
committerGitHub <noreply@github.com>
Wed, 22 Apr 2026 18:59:58 +0000 (18:59 +0000)
_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 ad3c5b7958b890382f431a53349320cb7c84d405)

Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-14-36-44.gh-issue-148820.XhOGhA.rst [new file with mode: 0644]
Python/lock.c
Python/parking_lot.c

diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-14-36-44.gh-issue-148820.XhOGhA.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-14-36-44.gh-issue-148820.XhOGhA.rst
new file mode 100644 (file)
index 0000000..392beca
--- /dev/null
@@ -0,0 +1,5 @@
+Fix a race in :c:type:`!_PyRawMutex` on the free-threaded build where a
+``Py_PARK_INTR`` return from ``_PySemaphore_Wait`` could let the waiter
+destroy its semaphore before the unlocking thread's
+``_PySemaphore_Wakeup`` completed, causing a fatal ``ReleaseSemaphore``
+error.
index ca269bd2cb4b5188d8460260a2b0ef0adbca8f9a..62f2637bcfb45313a033b9942f85426d9618686b 100644 (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);
index 0ffea97b079943a064663c27843cc074c99df5e4..01901f5c09be1b5d86fc40434cdd9f8c39b3d185 100644 (file)
@@ -61,7 +61,9 @@ _PySemaphore_Init(_PySemaphore *sema)
         NULL    //  unnamed
     );
     if (!sema->platform_sem) {
-        Py_FatalError("parking_lot: CreateSemaphore failed");
+        _Py_FatalErrorFormat(__func__,
+            "parking_lot: CreateSemaphore failed (error: %u)",
+            GetLastError());
     }
 #elif defined(_Py_USE_SEMAPHORES)
     if (sem_init(&sema->platform_sem, /*pshared=*/0, /*value=*/0) < 0) {
@@ -141,8 +143,8 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, PyTime_t timeout)
     }
     else {
         _Py_FatalErrorFormat(__func__,
-            "unexpected error from semaphore: %u (error: %u)",
-            wait, GetLastError());
+            "unexpected error from semaphore: %u (error: %u, handle: %p)",
+            wait, GetLastError(), sema->platform_sem);
     }
 #elif defined(_Py_USE_SEMAPHORES)
     int err;
@@ -251,7 +253,9 @@ _PySemaphore_Wakeup(_PySemaphore *sema)
 {
 #if defined(MS_WINDOWS)
     if (!ReleaseSemaphore(sema->platform_sem, 1, NULL)) {
-        Py_FatalError("parking_lot: ReleaseSemaphore failed");
+        _Py_FatalErrorFormat(__func__,
+            "parking_lot: ReleaseSemaphore failed (error: %u, handle: %p)",
+            GetLastError(), sema->platform_sem);
     }
 #elif defined(_Py_USE_SEMAPHORES)
     int err = sem_post(&sema->platform_sem);