From 16d8b40c49e6ebb568186c685e303ede1de5536f Mon Sep 17 00:00:00 2001 From: Harrison Booth Date: Thu, 23 Jul 2026 12:54:14 -0500 Subject: [PATCH v1] Fix races in Windows pthread emulation The Windows mutex initialization code used InterlockedExchange to claim initialization. A waiter could overwrite the initialized state with the initializing state, causing pthread_mutex_unlock to return without leaving the critical section and deadlock. Use compare-and-exchange to claim only an uninitialized mutex. Use interlocked reads and publication for the mutex and once state so initialized objects are safely visible on ARM64. Apply the mutex fix to the duplicate ECPG and libpq implementations. --- src/interfaces/ecpg/ecpglib/misc.c | 13 +++++++------ src/interfaces/ecpg/include/ecpg-pthread-win32.h | 9 +++------ src/interfaces/libpq/pthread-win32.c | 6 +++--- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/interfaces/ecpg/ecpglib/misc.c b/src/interfaces/ecpg/ecpglib/misc.c index 354a2e731fc..6af0e61568c 100644 --- a/src/interfaces/ecpg/ecpglib/misc.c +++ b/src/interfaces/ecpg/ecpglib/misc.c @@ -437,11 +437,11 @@ int pthread_mutex_lock(pthread_mutex_t *mp) { /* Initialize the csection if not already done */ - if (mp->initstate != 1) + if (InterlockedCompareExchange(&mp->initstate, 0, 0) != 1) { LONG istate; - while ((istate = InterlockedExchange(&mp->initstate, 2)) == 2) + while ((istate = InterlockedCompareExchange(&mp->initstate, 2, 0)) == 2) Sleep(0); /* wait, another thread is doing this */ if (istate != 1) InitializeCriticalSection(&mp->csection); @@ -454,7 +454,7 @@ pthread_mutex_lock(pthread_mutex_t *mp) int pthread_mutex_unlock(pthread_mutex_t *mp) { - if (mp->initstate != 1) + if (InterlockedCompareExchange(&mp->initstate, 0, 0) != 1) return EINVAL; LeaveCriticalSection(&mp->csection); return 0; @@ -465,13 +465,14 @@ static pthread_mutex_t win32_pthread_once_lock = PTHREAD_MUTEX_INITIALIZER; void win32_pthread_once(volatile pthread_once_t *once, void (*fn) (void)) { - if (!*once) + /* Pair each read with the interlocked publication after fn(). */ + if (InterlockedCompareExchange(once, 0, 0) != 1) { pthread_mutex_lock(&win32_pthread_once_lock); - if (!*once) + if (InterlockedCompareExchange(once, 0, 0) != 1) { fn(); - *once = true; + InterlockedExchange(once, 1); } pthread_mutex_unlock(&win32_pthread_once_lock); } diff --git a/src/interfaces/ecpg/include/ecpg-pthread-win32.h b/src/interfaces/ecpg/include/ecpg-pthread-win32.h index 7b6ba46b349..46ea5f25827 100644 --- a/src/interfaces/ecpg/include/ecpg-pthread-win32.h +++ b/src/interfaces/ecpg/include/ecpg-pthread-win32.h @@ -18,10 +18,10 @@ typedef struct pthread_mutex_t } pthread_mutex_t; typedef DWORD pthread_key_t; -typedef bool pthread_once_t; +typedef LONG pthread_once_t; #define PTHREAD_MUTEX_INITIALIZER { 0 } -#define PTHREAD_ONCE_INIT false +#define PTHREAD_ONCE_INIT 0 int pthread_mutex_init(pthread_mutex_t *, void *attr); int pthread_mutex_lock(pthread_mutex_t *); @@ -40,10 +40,7 @@ void win32_pthread_once(volatile pthread_once_t *once, void (*fn) (void)); do { *(key) = TlsAlloc(); ((void)(destructor)); } while(0) #define pthread_once(once, fn) \ - do { \ - if (!*(once)) \ - win32_pthread_once((once), (fn)); \ - } while(0) + win32_pthread_once((once), (fn)) #endif /* WIN32 */ #endif /* _ECPG_PTHREAD_WIN32_H */ diff --git a/src/interfaces/libpq/pthread-win32.c b/src/interfaces/libpq/pthread-win32.c index cf66284f007..9b05d9f7071 100644 --- a/src/interfaces/libpq/pthread-win32.c +++ b/src/interfaces/libpq/pthread-win32.c @@ -42,11 +42,11 @@ int pthread_mutex_lock(pthread_mutex_t *mp) { /* Initialize the csection if not already done */ - if (mp->initstate != 1) + if (InterlockedCompareExchange(&mp->initstate, 0, 0) != 1) { LONG istate; - while ((istate = InterlockedExchange(&mp->initstate, 2)) == 2) + while ((istate = InterlockedCompareExchange(&mp->initstate, 2, 0)) == 2) Sleep(0); /* wait, another thread is doing this */ if (istate != 1) InitializeCriticalSection(&mp->csection); @@ -59,7 +59,7 @@ pthread_mutex_lock(pthread_mutex_t *mp) int pthread_mutex_unlock(pthread_mutex_t *mp) { - if (mp->initstate != 1) + if (InterlockedCompareExchange(&mp->initstate, 0, 0) != 1) return EINVAL; LeaveCriticalSection(&mp->csection); return 0; -- 2.53.0.windows.2