Skip to content
1 change: 1 addition & 0 deletions Lib/test/libregrtest/tsan.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
'test_code',
'test_enum',
'test_functools',
'test_hashlib',
'test_httpservers',
'test_imaplib',
'test_importlib',
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,29 @@ def test_file_digest(self):
with open(os_helper.TESTFN, "wb") as f:
hashlib.file_digest(f, "sha256")

@unittest.skipUnless(support.check_sanitizer(thread=True), "only meaningful on free-threading")
def test_gh_128657(self):
def test_hashlib_sha256():
hash_obj = hashlib.sha256()

def closure(barrier):
barrier.wait()
test_hashlib_sha256()

num_workers = 40
num_runs = 20

for i in range(num_runs):
barrier = threading.Barrier(num_workers)
thrds = []

for i in range(num_workers):
thrds.append(thrd := threading.Thread(target=closure, args=(barrier,)))
thrd.start()

for thrd in thrds:
thrd.join()


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix possible extra reference when using objects returned by :func:`hashlib.sha256` under :term:`free threading`.
36 changes: 27 additions & 9 deletions Modules/_hashopenssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@
#include <stdbool.h>
#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 <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/crypto.h> // FIPS_mode()
#include <openssl/crypto.h> // FIPS_mode()
/* We use the object interface to discover what hashes OpenSSL supports. */
#include <openssl/objects.h>
#include <openssl/err.h>
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,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.
Expand Down
Loading