Skip to content

Commit 6c67904

Browse files
authored
gh-128657: fix _hashopenssl ref/data race (GH-128886)
1 parent d3b60ff commit 6c67904

File tree

2 files changed

+28
-9
lines changed

2 files changed

+28
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix possible extra reference when using objects returned by :func:`hashlib.sha256` under :term:`free threading`.

Modules/_hashopenssl.c

+27-9
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@
2525
#include <stdbool.h>
2626
#include "Python.h"
2727
#include "pycore_hashtable.h"
28-
#include "pycore_strhex.h" // _Py_strhex()
28+
#include "pycore_strhex.h" // _Py_strhex()
29+
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_PTR_RELAXED
2930
#include "hashlib.h"
3031

3132
/* EVP is the preferred interface to hashing in OpenSSL */
3233
#include <openssl/evp.h>
3334
#include <openssl/hmac.h>
34-
#include <openssl/crypto.h> // FIPS_mode()
35+
#include <openssl/crypto.h> // FIPS_mode()
3536
/* We use the object interface to discover what hashes OpenSSL supports. */
3637
#include <openssl/objects.h>
3738
#include <openssl/err.h>
@@ -369,6 +370,7 @@ static PY_EVP_MD*
369370
py_digest_by_name(PyObject *module, const char *name, enum Py_hash_type py_ht)
370371
{
371372
PY_EVP_MD *digest = NULL;
373+
PY_EVP_MD *other_digest = NULL;
372374
_hashlibstate *state = get_hashlib_state(module);
373375
py_hashentry_t *entry = (py_hashentry_t *)_Py_hashtable_get(
374376
state->hashtable, (const void*)name
@@ -379,20 +381,36 @@ py_digest_by_name(PyObject *module, const char *name, enum Py_hash_type py_ht)
379381
case Py_ht_evp:
380382
case Py_ht_mac:
381383
case Py_ht_pbkdf2:
382-
if (entry->evp == NULL) {
383-
entry->evp = PY_EVP_MD_fetch(entry->ossl_name, NULL);
384+
digest = FT_ATOMIC_LOAD_PTR_RELAXED(entry->evp);
385+
if (digest == NULL) {
386+
digest = PY_EVP_MD_fetch(entry->ossl_name, NULL);
387+
#ifdef Py_GIL_DISABLED
388+
// exchange just in case another thread did same thing at same time
389+
other_digest = _Py_atomic_exchange_ptr(&entry->evp, digest);
390+
#else
391+
entry->evp = digest;
392+
#endif
384393
}
385-
digest = entry->evp;
386394
break;
387395
case Py_ht_evp_nosecurity:
388-
if (entry->evp_nosecurity == NULL) {
389-
entry->evp_nosecurity = PY_EVP_MD_fetch(entry->ossl_name, "-fips");
396+
digest = FT_ATOMIC_LOAD_PTR_RELAXED(entry->evp_nosecurity);
397+
if (digest == NULL) {
398+
digest = PY_EVP_MD_fetch(entry->ossl_name, "-fips");
399+
#ifdef Py_GIL_DISABLED
400+
// exchange just in case another thread did same thing at same time
401+
other_digest = _Py_atomic_exchange_ptr(&entry->evp_nosecurity, digest);
402+
#else
403+
entry->evp_nosecurity = digest;
404+
#endif
390405
}
391-
digest = entry->evp_nosecurity;
392406
break;
393407
}
408+
// if another thread same thing at same time make sure we got same ptr
409+
assert(other_digest == NULL || other_digest == digest);
394410
if (digest != NULL) {
395-
PY_EVP_MD_up_ref(digest);
411+
if (other_digest == NULL) {
412+
PY_EVP_MD_up_ref(digest);
413+
}
396414
}
397415
} else {
398416
// Fall back for looking up an unindexed OpenSSL specific name.

0 commit comments

Comments
 (0)