Skip to content

Commit 72cdb92

Browse files
picnixzmiss-islington
authored andcommitted
gh-155835: fix digest_size and block_size data races on SHA-3 objects (GH-155838)
(cherry picked from commit 90ac539) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent 5e19ff3 commit 72cdb92

3 files changed

Lines changed: 113 additions & 16 deletions

File tree

Lib/test/test_hashlib.py

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import tempfile
1919
import threading
2020
import unittest
21+
from functools import partial
22+
from operator import attrgetter
2123
from test import support
2224
from test.support import _4G, bigmemtest
2325
from test.support import hashlib_helper
@@ -52,18 +54,39 @@
5254
def get_fips_mode():
5355
return 0
5456

57+
58+
try:
59+
import _md5
60+
except ImportError:
61+
_md5 = None
62+
requires_md5 = unittest.skipUnless(_md5, 'requires _md5')
63+
64+
5565
try:
5666
import _blake2
5767
except ImportError:
5868
_blake2 = None
59-
6069
requires_blake2 = unittest.skipUnless(_blake2, 'requires _blake2')
6170

71+
72+
try:
73+
import _sha1
74+
except ImportError:
75+
_sha1 = None
76+
requires_sha1 = unittest.skipUnless(_sha1, 'requires _sha1')
77+
78+
79+
try:
80+
import _sha2
81+
except ImportError:
82+
_sha2 = None
83+
requires_sha2 = unittest.skipUnless(_sha2, 'requires _sha2')
84+
85+
6286
try:
6387
import _sha3
6488
except ImportError:
6589
_sha3 = None
66-
6790
requires_sha3 = unittest.skipUnless(_sha3, 'requires _sha3')
6891

6992

@@ -1418,5 +1441,70 @@ def scrypt(password=b"password", /, **kwargs):
14181441
self.assertRaises(numeric_exc_types, scrypt, dklen=MAX_DKLEN + 1)
14191442

14201443

1444+
@threading_helper.requires_working_threading()
1445+
class TestTSAN(unittest.TestCase):
1446+
1447+
@threading_helper.reap_threads
1448+
def check_attribute(self, write, read, expected, nthreads=8):
1449+
ready = threading.Event()
1450+
barrier = threading.Barrier(nthreads)
1451+
1452+
def writer():
1453+
barrier.wait()
1454+
while not ready.is_set():
1455+
write()
1456+
1457+
def reader():
1458+
barrier.wait()
1459+
while not ready.is_set():
1460+
self.assertEqual(read(), expected)
1461+
1462+
targets = [writer if i % 2 else reader for i in range(nthreads)]
1463+
workers = [threading.Thread(target=target) for target in targets]
1464+
with threading_helper.start_threads(workers, unlock=ready.set):
1465+
pass
1466+
1467+
def check_HACL_attribute(self, module, version, attrname):
1468+
blob = b"A" * 65536
1469+
obj = getattr(module, version)()
1470+
update = partial(obj.update, blob)
1471+
read = attrgetter(attrname)
1472+
self.check_attribute(update, partial(read, obj), read(obj))
1473+
1474+
@requires_md5
1475+
@support.subTests("attrname", ["block_size", "digest_size"])
1476+
def test_HACL_md5_attributes(self, attrname):
1477+
self.check_HACL_attribute(_md5, "md5", attrname)
1478+
1479+
@requires_sha1
1480+
@support.subTests("attrname", ["block_size", "digest_size"])
1481+
def test_HACL_sha1_attributes(self, attrname):
1482+
self.check_HACL_attribute(_sha1, "sha1", attrname)
1483+
1484+
@requires_sha2
1485+
@support.subTests("size", [224, 256, 384, 512])
1486+
@support.subTests("attrname", ["block_size", "digest_size"])
1487+
def test_HACL_sha2_attributes(self, size, attrname):
1488+
self.check_HACL_attribute(_sha2, f"sha{size}", attrname)
1489+
1490+
@requires_sha3
1491+
@support.subTests("size", [224, 256, 384, 512])
1492+
@support.subTests(
1493+
"attrname",
1494+
["block_size", "digest_size", "_capacity_bits", "_rate_bits"],
1495+
)
1496+
def test_HACL_sha3_attributes(self, size, attrname):
1497+
self.check_HACL_attribute(_sha3, f"sha3_{size}", attrname)
1498+
1499+
@requires_sha3
1500+
@support.subTests("size", [128, 256])
1501+
@support.subTests(
1502+
"attrname",
1503+
["block_size", "digest_size", "_capacity_bits", "_rate_bits"],
1504+
)
1505+
def test_HACL_shake_attributes(self, size, attrname):
1506+
self.check_HACL_attribute(_sha3, f"shake_{size}", attrname)
1507+
1508+
14211509
if __name__ == "__main__":
14221510
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:mod:`hashlib`: Fix data races when accessing
2+
:attr:`~hashlib.hash.digest_size` and :attr:`~hashlib.hash.block_size` on
3+
SHA-3 objects. Patch by Bénédikt Tran.

Modules/sha3module.c

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ sha3_get_state(PyObject *module)
6767
typedef struct {
6868
HASHLIB_OBJECT_HEAD
6969
Hacl_Hash_SHA3_state_t *hash_state;
70+
// HACL* update functions entirely replace the state, which can lead
71+
// to races on the free-threaded build. Since the kind of hash is static,
72+
// we can store its corresponding metadata once.
73+
uint32_t digest_size;
74+
uint32_t block_size;
75+
int is_shake;
7076
} SHA3object;
7177

7278
#define _SHA3object_CAST(op) ((SHA3object *)(op))
@@ -96,7 +102,7 @@ newSHA3object(PyTypeObject *type)
96102
return NULL;
97103
}
98104
HASHLIB_INIT_MUTEX(newobj);
99-
105+
newobj->digest_size = newobj->block_size = 0;
100106
PyObject_GC_Track(newobj);
101107
return newobj;
102108
}
@@ -179,6 +185,11 @@ py_sha3_new_impl(PyTypeObject *type, PyObject *data_obj, int usedforsecurity,
179185
goto error;
180186
}
181187

188+
// set the metadata once we know that the state is valid
189+
int is_shake = Hacl_Hash_SHA3_is_shake(self->hash_state);
190+
self->digest_size = is_shake ? 0 : Hacl_Hash_SHA3_hash_len(self->hash_state);
191+
self->block_size = Hacl_Hash_SHA3_block_len(self->hash_state);
192+
182193
if (data) {
183194
GET_BUFFER_VIEW_OR_ERROR(data, &buf, goto error);
184195
/* Do not use self->mutex here as this is the constructor
@@ -253,6 +264,8 @@ _sha3_sha3_224_copy_impl(SHA3object *self, PyTypeObject *cls)
253264
Py_DECREF(newobj);
254265
return PyErr_NoMemory();
255266
}
267+
newobj->digest_size = self->digest_size;
268+
newobj->block_size = self->block_size;
256269
return (PyObject *)newobj;
257270
}
258271

@@ -273,8 +286,7 @@ _sha3_sha3_224_digest_impl(SHA3object *self)
273286
HASHLIB_ACQUIRE_LOCK(self);
274287
(void)Hacl_Hash_SHA3_digest(self->hash_state, digest);
275288
HASHLIB_RELEASE_LOCK(self);
276-
return PyBytes_FromStringAndSize((const char *)digest,
277-
Hacl_Hash_SHA3_hash_len(self->hash_state));
289+
return PyBytes_FromStringAndSize((const char *)digest, self->digest_size);
278290
}
279291

280292

@@ -292,8 +304,7 @@ _sha3_sha3_224_hexdigest_impl(SHA3object *self)
292304
HASHLIB_ACQUIRE_LOCK(self);
293305
(void)Hacl_Hash_SHA3_digest(self->hash_state, digest);
294306
HASHLIB_RELEASE_LOCK(self);
295-
return _Py_strhex((const char *)digest,
296-
Hacl_Hash_SHA3_hash_len(self->hash_state));
307+
return _Py_strhex((const char *)digest, self->digest_size);
297308
}
298309

299310

@@ -334,8 +345,7 @@ static PyObject *
334345
SHA3_get_block_size(PyObject *op, void *Py_UNUSED(closure))
335346
{
336347
SHA3object *self = _SHA3object_CAST(op);
337-
uint32_t rate = Hacl_Hash_SHA3_block_len(self->hash_state);
338-
return PyLong_FromLong(rate);
348+
return PyLong_FromLong(self->block_size);
339349
}
340350

341351

@@ -371,18 +381,15 @@ SHA3_get_digest_size(PyObject *op, void *Py_UNUSED(closure))
371381
{
372382
// Preserving previous behavior: variable-length algorithms return 0
373383
SHA3object *self = _SHA3object_CAST(op);
374-
if (Hacl_Hash_SHA3_is_shake(self->hash_state))
375-
return PyLong_FromLong(0);
376-
else
377-
return PyLong_FromLong(Hacl_Hash_SHA3_hash_len(self->hash_state));
384+
return PyLong_FromLong(self->digest_size);
378385
}
379386

380387

381388
static PyObject *
382389
SHA3_get_capacity_bits(PyObject *op, void *Py_UNUSED(closure))
383390
{
384391
SHA3object *self = _SHA3object_CAST(op);
385-
uint32_t rate = Hacl_Hash_SHA3_block_len(self->hash_state) * 8;
392+
uint32_t rate = self->block_size * 8;
386393
assert(rate <= 1600);
387394
int capacity = 1600 - rate;
388395
return PyLong_FromLong(capacity);
@@ -393,8 +400,7 @@ static PyObject *
393400
SHA3_get_rate_bits(PyObject *op, void *Py_UNUSED(closure))
394401
{
395402
SHA3object *self = _SHA3object_CAST(op);
396-
uint32_t rate = Hacl_Hash_SHA3_block_len(self->hash_state) * 8;
397-
return PyLong_FromLong(rate);
403+
return PyLong_FromLong(self->block_size * 8);
398404
}
399405

400406
static PyObject *

0 commit comments

Comments
 (0)