Commit f5a5e89044 for openssl.org
commit f5a5e89044174e1b9509841baa2b334b368bdadc
Author: Neil Horman <nhorman@openssl.org>
Date: Wed Jun 24 15:55:48 2026 -0400
ensure writes are syncronized on windows in CRYPTO_THREAD_run_once
We've tried to fix this properly using InitOnceExecuteOnce, but it
results in an ABI breakage, so we're doing it this way.
on windows, CRYPTO_THREAD_run_once, on weakly memory ordered systems,
may complete the write of the run once variable lock before some of the
writes made by the init callback routine complete. The result is that
on a heavily multithreaded application, other therads may see the data
that was meant to be in an initalized state, as in some erroneous
in-between state, leading to errors.
Fix it by inserting a full memory barrier after we return from the init
callback, and prior to setting the run once variable to ONCE_DONE.
Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
Reviewed-by: Norbert Pocs <norbertp@openssl.org>
MergeDate: Tue Jun 30 08:51:16 2026
(Merged from https://github.com/openssl/openssl/pull/31713)
diff --git a/crypto/threads_win.c b/crypto/threads_win.c
index b52c8dd520..d777010cd8 100644
--- a/crypto/threads_win.c
+++ b/crypto/threads_win.c
@@ -527,6 +527,20 @@ int CRYPTO_THREAD_run_once(CRYPTO_ONCE *once, void (*init)(void))
result = InterlockedCompareExchange(lock, ONCE_ININIT, ONCE_UNINITED);
if (result == ONCE_UNINITED) {
init();
+ /*
+ * On weakly ordered systems, it may happen that the write to *lock
+ * below completes prior to some writes in whatever the init()
+ * callback routine above may do. In this case, other threads
+ * entering here may see unsynchronized data in whatever the init
+ * routine initializes, leading to erroneous behavior.
+ *
+ * We should use InitOnceExecuteOnce here to implement this, but
+ * doing so requires that we modify the definition of the
+ * CRYPTO_ONCE type, which is an ABI breakage. So instead
+ * just insert a memory barrier here to ensure that any pending
+ * writes are flushed to memory prior to setting ONCE_DONE below
+ */
+ MemoryBarrier();
*lock = ONCE_DONE;
return 1;
}