diff --git a/Misc/NEWS.d/next/Library/2025-01-15-15-45-21.gh-issue-128657.P5LNQA.rst b/Misc/NEWS.d/next/Library/2025-01-15-15-45-21.gh-issue-128657.P5LNQA.rst new file mode 100644 index 00000000000000..3b08a9fba59620 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-01-15-15-45-21.gh-issue-128657.P5LNQA.rst @@ -0,0 +1 @@ +Fix possible extra reference when using objects returned by :func:`hashlib.sha256` under :term:`free threading`. diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 082929be3c77b7..d7586feea3efcd 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -25,13 +25,14 @@ #include #include "Python.h" #include "pycore_hashtable.h" -#include "pycore_strhex.h" // _Py_strhex() +#include "pycore_strhex.h" // _Py_strhex() +#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_PTR_RELAXED #include "hashlib.h" /* EVP is the preferred interface to hashing in OpenSSL */ #include #include -#include // FIPS_mode() +#include // FIPS_mode() /* We use the object interface to discover what hashes OpenSSL supports. */ #include #include @@ -369,6 +370,7 @@ static PY_EVP_MD* py_digest_by_name(PyObject *module, const char *name, enum Py_hash_type py_ht) { PY_EVP_MD *digest = NULL; + PY_EVP_MD *other_digest = NULL; _hashlibstate *state = get_hashlib_state(module); py_hashentry_t *entry = (py_hashentry_t *)_Py_hashtable_get( state->hashtable, (const void*)name @@ -379,20 +381,36 @@ py_digest_by_name(PyObject *module, const char *name, enum Py_hash_type py_ht) case Py_ht_evp: case Py_ht_mac: case Py_ht_pbkdf2: - if (entry->evp == NULL) { - entry->evp = PY_EVP_MD_fetch(entry->ossl_name, NULL); + 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, digest); +#else + entry->evp = digest; +#endif } - digest = entry->evp; break; case Py_ht_evp_nosecurity: - if (entry->evp_nosecurity == NULL) { - entry->evp_nosecurity = PY_EVP_MD_fetch(entry->ossl_name, "-fips"); + 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, digest); +#else + entry->evp_nosecurity = digest; +#endif } - digest = entry->evp_nosecurity; break; } + // if another thread same thing at same time make sure we got same ptr + assert(other_digest == NULL || other_digest == digest); if (digest != NULL) { - PY_EVP_MD_up_ref(digest); + if (other_digest == NULL) { + PY_EVP_MD_up_ref(digest); + } } } else { // Fall back for looking up an unindexed OpenSSL specific name.