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]