Skip to content

gh-128657: fix _hashopenssl ref/data race #128886

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 8, 2025
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a possible data and PY_EVP_MD refcount race in _hashopenssl.c py_digest_by_name() under free-threading.
24 changes: 17 additions & 7 deletions Modules/_hashopenssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "Python.h"
#include "pycore_hashtable.h"
#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 */
Expand Down Expand Up @@ -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
Expand All @@ -379,20 +381,28 @@ 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);
// exchange just in case another thread did same thing at same time
other_digest = _Py_atomic_exchange_ptr(&entry->evp, digest);
}
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");
// exchange just in case another thread did same thing at same time
other_digest = _Py_atomic_exchange_ptr(&entry->evp_nosecurity, digest);
}
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.
Expand Down
Loading