Skip to content

Commit 610aa38

Browse files
committed
[3.13] pythongh-128657: fix _hashopenssl ref/data race (pythonGH-128886)
(cherry picked from commit 6c67904) Co-authored-by: Tomasz Pytel <[email protected]>
1 parent 8a7146c commit 610aa38

File tree

2 files changed

+27
-8
lines changed

2 files changed

+27
-8
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

+26-8
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@
2727
#include "pycore_hashtable.h"
2828
#include "pycore_pyhash.h" // _Py_HashBytes()
2929
#include "pycore_strhex.h" // _Py_strhex()
30+
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_PTR_RELAXED
3031
#include "hashlib.h"
3132

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

0 commit comments

Comments
 (0)