From 194cdab0180b4fde7266e755c9160b236bc3202b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:20:07 +0200 Subject: [PATCH 1/4] gh-155835: fix `digest_size` data race on BLAKE-2 objects --- .../Library/2026-08-15-13-18-01.gh-issue-155835.mlyLWp.rst | 2 ++ Modules/blake2module.c | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-15-13-18-01.gh-issue-155835.mlyLWp.rst diff --git a/Misc/NEWS.d/next/Library/2026-08-15-13-18-01.gh-issue-155835.mlyLWp.rst b/Misc/NEWS.d/next/Library/2026-08-15-13-18-01.gh-issue-155835.mlyLWp.rst new file mode 100644 index 000000000000000..9ef7d0694e66c31 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-15-13-18-01.gh-issue-155835.mlyLWp.rst @@ -0,0 +1,2 @@ +:mod:`hashlib`: Fix a data race when accessing :attr:`~hashlib.hash.digest_size` +on BLAKE-2 objects. Patch by Bénédikt Tran. diff --git a/Modules/blake2module.c b/Modules/blake2module.c index ac7265bb9d6836c..be62192e05e38cc 100644 --- a/Modules/blake2module.c +++ b/Modules/blake2module.c @@ -950,7 +950,10 @@ static PyObject * py_blake2b_get_digest_size(PyObject *op, void *Py_UNUSED(closure)) { Blake2Object *self = _Blake2Object_CAST(op); - Hacl_Hash_Blake2b_index info = hacl_get_blake2_info(self); + Hacl_Hash_Blake2b_index info; + HASHLIB_ACQUIRE_LOCK(self); + info = hacl_get_blake2_info(self); + HASHLIB_RELEASE_LOCK(self); return PyLong_FromLong(info.digest_length); } From 374bfeffcff7b5cb452907c4fb9aec6482fc7b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:38:15 +0200 Subject: [PATCH 2/4] Update blake2module.c --- Modules/blake2module.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/blake2module.c b/Modules/blake2module.c index be62192e05e38cc..c69c4259b12666f 100644 --- a/Modules/blake2module.c +++ b/Modules/blake2module.c @@ -950,9 +950,8 @@ static PyObject * py_blake2b_get_digest_size(PyObject *op, void *Py_UNUSED(closure)) { Blake2Object *self = _Blake2Object_CAST(op); - Hacl_Hash_Blake2b_index info; HASHLIB_ACQUIRE_LOCK(self); - info = hacl_get_blake2_info(self); + Hacl_Hash_Blake2b_index info = hacl_get_blake2_info(self); HASHLIB_RELEASE_LOCK(self); return PyLong_FromLong(info.digest_length); } From 0565277c2420909d9d31d10b5ff20f4ab54c2462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 16 Aug 2026 11:05:15 +0200 Subject: [PATCH 3/4] add TSAN tests for BLAKE2 --- Lib/test/test_hashlib.py | 41 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 253a8f455853f58..6d417b09ef76cc4 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -18,6 +18,8 @@ import tempfile import threading import unittest +from functools import partial +from operator import attrgetter from test import support from test.support import _4G, bigmemtest from test.support import hashlib_helper @@ -56,14 +58,12 @@ def get_fips_mode(): import _blake2 except ImportError: _blake2 = None - requires_blake2 = unittest.skipUnless(_blake2, 'requires _blake2') try: import _sha3 except ImportError: _sha3 = None - requires_sha3 = unittest.skipUnless(_sha3, 'requires _sha3') @@ -1418,5 +1418,42 @@ def scrypt(password=b"password", /, **kwargs): self.assertRaises(numeric_exc_types, scrypt, dklen=MAX_DKLEN + 1) +@threading_helper.requires_working_threading() +class TestTSAN(unittest.TestCase): + + @threading_helper.reap_threads + def check_attribute(self, write, read, expected, nthreads=8): + ready = threading.Event() + barrier = threading.Barrier(nthreads) + + def writer(): + barrier.wait() + while not ready.is_set(): + write() + + def reader(): + barrier.wait() + while not ready.is_set(): + self.assertEqual(read(), expected) + + targets = [writer if i % 2 else reader for i in range(nthreads)] + workers = [threading.Thread(target=target) for target in targets] + with threading_helper.start_threads(workers, unlock=ready.set): + pass + + def check_HACL_attribute(self, module, version, attrname): + blob = b"A" * 65536 + obj = getattr(module, version)() + update = partial(obj.update, blob) + read = attrgetter(attrname) + self.check_attribute(update, partial(read, obj), read(obj)) + + @requires_blake2 + @support.subTests("version", ["blake2s", "blake2b"]) + @support.subTests("attrname", ["block_size", "digest_size"]) + def test_HACL_blake2_attributes(self, version, attrname): + self.check_HACL_attribute(module, version, attrname) + + if __name__ == "__main__": unittest.main() From 2693e046571adf7299b022cbd01f799bb1ecc999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 16 Aug 2026 11:20:30 +0200 Subject: [PATCH 4/4] Update Lib/test/test_hashlib.py --- Lib/test/test_hashlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 6d417b09ef76cc4..ddfacdd14e0a533 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -1452,7 +1452,7 @@ def check_HACL_attribute(self, module, version, attrname): @support.subTests("version", ["blake2s", "blake2b"]) @support.subTests("attrname", ["block_size", "digest_size"]) def test_HACL_blake2_attributes(self, version, attrname): - self.check_HACL_attribute(module, version, attrname) + self.check_HACL_attribute(_blake2, version, attrname) if __name__ == "__main__":