https://github.com/python/cpython/commit/23469818538cf39108535b8f182f3527639d519b
commit: 23469818538cf39108535b8f182f3527639d519b
branch: main
author: Bénédikt Tran <[email protected]>
committer: picnixz <[email protected]>
date: 2026-10-20T16:55:40-07:00
summary:

gh-149816: fix a race when fetching OpenSSL digests (#153019)

files:
A Misc/NEWS.d/next/Library/2026-07-04-12-52-22.gh-issue-149816.XQOutB.rst
M Modules/_hashopenssl.c

diff --git 
a/Misc/NEWS.d/next/Library/2026-07-04-12-52-22.gh-issue-149816.XQOutB.rst 
b/Misc/NEWS.d/next/Library/2026-07-04-12-52-22.gh-issue-149816.XQOutB.rst
new file mode 100644
index 00000000000000..eb273865894aae
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-04-12-52-22.gh-issue-149816.XQOutB.rst
@@ -0,0 +1,3 @@
+:mod:`hashlib`: fix a crash arising from a race condition when looking
+up an OpenSSL hash algorithm by name the first time. Patch by Bénédikt Tran.
+
diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
index 00ed87e7e3a36c..9d7602b39d7b93 100644
--- a/Modules/_hashopenssl.c
+++ b/Modules/_hashopenssl.c
@@ -693,43 +693,46 @@ static PY_EVP_MD *
 get_openssl_evp_md_by_utf8name(_hashlibstate *state, const char *name,
                                Py_hash_type py_ht)
 {
-    PY_EVP_MD *digest = NULL, *other_digest = NULL;
+    PY_EVP_MD *digest = NULL, *fresh = NULL;
     py_hashentry_t *entry = _Py_hashtable_get(state->hashtable, name);
+    int fips = !disable_fips_property(py_ht);
 
     if (entry != NULL) {
-        if (!disable_fips_property(py_ht)) {
-            digest = FT_ATOMIC_LOAD_PTR_RELAXED(entry->evp);
-            if (digest == NULL) {
-                digest = PY_EVP_MD_fetch(entry->ossl_name, NULL);
-#ifdef Py_GIL_DISABLED
-                // exchange just in case another thread did same thing at same 
time
-                other_digest = _Py_atomic_exchange_ptr(&entry->evp, (void 
*)digest);
-#else
-                entry->evp = digest;
-#endif
-            }
-        }
-        else {
-            digest = FT_ATOMIC_LOAD_PTR_RELAXED(entry->evp_nosecurity);
-            if (digest == NULL) {
-                digest = PY_EVP_MD_fetch(entry->ossl_name, "-fips");
-#ifdef Py_GIL_DISABLED
-                // exchange just in case another thread did same thing at same 
time
-                other_digest = _Py_atomic_exchange_ptr(&entry->evp_nosecurity, 
(void *)digest);
-#else
-                entry->evp_nosecurity = digest;
-#endif
+        PY_EVP_MD **cached = fips ? &entry->evp : &entry->evp_nosecurity;
+        digest = FT_ATOMIC_LOAD_PTR_RELAXED(*cached);
+        if (digest == NULL) {
+            fresh = PY_EVP_MD_fetch(entry->ossl_name, fips ? NULL : "-fips");
+            if (fresh != NULL) {
+                /*
+                 * Publish a freshly fetched digest into the cache exactly 
once.
+                 *
+                 * On the first publication, 'cached' adopts the 'fresh' ref.
+                 * If another thread wins the race, 'fresh' is released and
+                 * the cached digest is used instead.
+                 *
+                 * See https://github.com/python/cpython/issues/128657
+                 * and https://github.com/python/cpython/issues/149816.
+                 */
+                PY_EVP_MD *expected = NULL;
+                if (_Py_atomic_compare_exchange_ptr(cached, &expected, (void 
*)fresh)) {
+                    digest = fresh;         // we won: 'cached' now owns the 
fresh digest
+                }
+                else {
+                    PY_EVP_MD_free(fresh);  // we lost: discard the fresh 
digest
+                    digest = expected;      // reuse the winner's digest
+                }
             }
         }
-        // if another thread same thing at same time make sure we got same ptr
-        assert(other_digest == NULL || other_digest == digest);
-        if (digest != NULL && other_digest == NULL) {
+        /* Hand the caller its own reference; the cache keeps its own. Valid
+         * whether `digest` was already cached, freshly published by this
+         * thread, or published by a thread that won the race. */
+        if (digest != NULL) {
             PY_EVP_MD_up_ref(digest);
         }
     }
     else {
         // Fall back for looking up an unindexed OpenSSL specific name.
-        const char *props = disable_fips_property(py_ht) ? "-fips" : NULL;
+        const char *props = fips ? NULL : "-fips";
         (void)props;  // will only be used in OpenSSL 3.0 and later
         digest = PY_EVP_MD_fetch(name, props);
     }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to