Skip to content

Commit b3965e5

Browse files
committed
[3.13] 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 19693af commit b3965e5

3 files changed

Lines changed: 131 additions & 19 deletions

File tree

Lib/test/test_hashlib.py

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import threading
1919
import unittest
2020
import warnings
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_helper import import_fresh_module
@@ -52,17 +54,43 @@
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+
86+
try:
87+
import _sha3
88+
except ImportError:
89+
_sha3 = None
6290
# bpo-46913: Don't test the _sha3 extension on a Python UBSAN build
6391
# TODO(gh-99108): Revisit this after _sha3 uses HACL*.
64-
SKIP_SHA3 = support.check_sanitizer(ub=True)
65-
requires_sha3 = unittest.skipUnless(not SKIP_SHA3, 'requires _sha3')
92+
SKIP_SHA3 = _sha3 is None or support.check_sanitizer(ub=True)
93+
requires_sha3 = unittest.skipUnkess(not SKIP_SHA3, 'requires _sha3')
6694

6795

6896
def hexstr(s):
@@ -1273,5 +1301,70 @@ def readable(self):
12731301
hashlib.file_digest(NonBlocking(), hashlib.sha256)
12741302

12751303

1304+
@threading_helper.requires_working_threading()
1305+
class TestTSAN(unittest.TestCase):
1306+
1307+
@threading_helper.reap_threads
1308+
def check_attribute(self, write, read, expected, nthreads=8):
1309+
ready = threading.Event()
1310+
barrier = threading.Barrier(nthreads)
1311+
1312+
def writer():
1313+
barrier.wait()
1314+
while not ready.is_set():
1315+
write()
1316+
1317+
def reader():
1318+
barrier.wait()
1319+
while not ready.is_set():
1320+
self.assertEqual(read(), expected)
1321+
1322+
targets = [writer if i % 2 else reader for i in range(nthreads)]
1323+
workers = [threading.Thread(target=target) for target in targets]
1324+
with threading_helper.start_threads(workers, unlock=ready.set):
1325+
pass
1326+
1327+
def check_HACL_attribute(self, module, version, attrname):
1328+
blob = b"A" * 65536
1329+
obj = getattr(module, version)()
1330+
update = partial(obj.update, blob)
1331+
read = attrgetter(attrname)
1332+
self.check_attribute(update, partial(read, obj), read(obj))
1333+
1334+
@requires_md5
1335+
@support.subTests("attrname", ["block_size", "digest_size"])
1336+
def test_HACL_md5_attributes(self, attrname):
1337+
self.check_HACL_attribute(_md5, "md5", attrname)
1338+
1339+
@requires_sha1
1340+
@support.subTests("attrname", ["block_size", "digest_size"])
1341+
def test_HACL_sha1_attributes(self, attrname):
1342+
self.check_HACL_attribute(_sha1, "sha1", attrname)
1343+
1344+
@requires_sha2
1345+
@support.subTests("size", [224, 256, 384, 512])
1346+
@support.subTests("attrname", ["block_size", "digest_size"])
1347+
def test_HACL_sha2_attributes(self, size, attrname):
1348+
self.check_HACL_attribute(_sha2, f"sha{size}", attrname)
1349+
1350+
@requires_sha3
1351+
@support.subTests("size", [224, 256, 384, 512])
1352+
@support.subTests(
1353+
"attrname",
1354+
["block_size", "digest_size", "_capacity_bits", "_rate_bits"],
1355+
)
1356+
def test_HACL_sha3_attributes(self, size, attrname):
1357+
self.check_HACL_attribute(_sha3, f"sha3_{size}", attrname)
1358+
1359+
@requires_sha3
1360+
@support.subTests("size", [128, 256])
1361+
@support.subTests(
1362+
"attrname",
1363+
["block_size", "digest_size", "_capacity_bits", "_rate_bits"],
1364+
)
1365+
def test_HACL_shake_attributes(self, size, attrname):
1366+
self.check_HACL_attribute(_sha3, f"shake_{size}", attrname)
1367+
1368+
12761369
if __name__ == "__main__":
12771370
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: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ typedef struct {
6464
bool use_mutex;
6565
PyMutex mutex;
6666
Hacl_Hash_SHA3_state_t *hash_state;
67+
// HACL* update functions entirely replace the state, which can lead
68+
// to races on the free-threaded build. Since the kind of hash is static,
69+
// we can store its corresponding metadata once.
70+
uint32_t digest_size;
71+
uint32_t block_size;
72+
int is_shake;
6773
} SHA3object;
6874

6975
#include "clinic/sha3module.c.h"
@@ -77,7 +83,7 @@ newSHA3object(PyTypeObject *type)
7783
return NULL;
7884
}
7985
HASHLIB_INIT_MUTEX(newobj);
80-
86+
newobj->digest_size = newobj->block_size = 0;
8187
return newobj;
8288
}
8389

@@ -143,6 +149,16 @@ py_sha3_new_impl(PyTypeObject *type, PyObject *data_obj, int usedforsecurity,
143149
goto error;
144150
}
145151

152+
if (self->hash_state == NULL) {
153+
(void)PyErr_NoMemory();
154+
goto error;
155+
}
156+
157+
// set the metadata once we know that the state is valid
158+
int is_shake = Hacl_Hash_SHA3_is_shake(self->hash_state);
159+
self->digest_size = is_shake ? 0 : Hacl_Hash_SHA3_hash_len(self->hash_state);
160+
self->block_size = Hacl_Hash_SHA3_block_len(self->hash_state);
161+
146162
if (data) {
147163
GET_BUFFER_VIEW_OR_ERROR(data, &buf, goto error);
148164
if (buf.len >= HASHLIB_GIL_MINSIZE) {
@@ -204,6 +220,12 @@ _sha3_sha3_224_copy_impl(SHA3object *self)
204220
ENTER_HASHLIB(self);
205221
newobj->hash_state = Hacl_Hash_SHA3_copy(self->hash_state);
206222
LEAVE_HASHLIB(self);
223+
if (newobj->hash_state == NULL) {
224+
Py_DECREF(newobj);
225+
return PyErr_NoMemory();
226+
}
227+
newobj->digest_size = self->digest_size;
228+
newobj->block_size = self->block_size;
207229
return (PyObject *)newobj;
208230
}
209231

@@ -222,10 +244,9 @@ _sha3_sha3_224_digest_impl(SHA3object *self)
222244
// This function errors out if the algorithm is Shake. Here, we know this
223245
// not to be the case, and therefore do not perform error checking.
224246
ENTER_HASHLIB(self);
225-
Hacl_Hash_SHA3_digest(self->hash_state, digest);
247+
(void)Hacl_Hash_SHA3_digest(self->hash_state, digest);
226248
LEAVE_HASHLIB(self);
227-
return PyBytes_FromStringAndSize((const char *)digest,
228-
Hacl_Hash_SHA3_hash_len(self->hash_state));
249+
return PyBytes_FromStringAndSize((const char *)digest, self->digest_size);
229250
}
230251

231252

@@ -241,10 +262,9 @@ _sha3_sha3_224_hexdigest_impl(SHA3object *self)
241262
{
242263
unsigned char digest[SHA3_MAX_DIGESTSIZE];
243264
ENTER_HASHLIB(self);
244-
Hacl_Hash_SHA3_digest(self->hash_state, digest);
265+
(void)Hacl_Hash_SHA3_digest(self->hash_state, digest);
245266
LEAVE_HASHLIB(self);
246-
return _Py_strhex((const char *)digest,
247-
Hacl_Hash_SHA3_hash_len(self->hash_state));
267+
return _Py_strhex((const char *)digest, self->digest_size);
248268
}
249269

250270

@@ -295,8 +315,7 @@ static PyMethodDef SHA3_methods[] = {
295315
static PyObject *
296316
SHA3_get_block_size(SHA3object *self, void *closure)
297317
{
298-
uint32_t rate = Hacl_Hash_SHA3_block_len(self->hash_state);
299-
return PyLong_FromLong(rate);
318+
return PyLong_FromLong(self->block_size);
300319
}
301320

302321

@@ -331,17 +350,15 @@ static PyObject *
331350
SHA3_get_digest_size(SHA3object *self, void *closure)
332351
{
333352
// Preserving previous behavior: variable-length algorithms return 0
334-
if (Hacl_Hash_SHA3_is_shake(self->hash_state))
335-
return PyLong_FromLong(0);
336-
else
337-
return PyLong_FromLong(Hacl_Hash_SHA3_hash_len(self->hash_state));
353+
return PyLong_FromLong(self->digest_size);
338354
}
339355

340356

341357
static PyObject *
342358
SHA3_get_capacity_bits(SHA3object *self, void *closure)
343359
{
344-
uint32_t rate = Hacl_Hash_SHA3_block_len(self->hash_state) * 8;
360+
uint32_t rate = self->block_size * 8;
361+
assert(rate <= 1600);
345362
int capacity = 1600 - rate;
346363
return PyLong_FromLong(capacity);
347364
}
@@ -350,8 +367,7 @@ SHA3_get_capacity_bits(SHA3object *self, void *closure)
350367
static PyObject *
351368
SHA3_get_rate_bits(SHA3object *self, void *closure)
352369
{
353-
uint32_t rate = Hacl_Hash_SHA3_block_len(self->hash_state) * 8;
354-
return PyLong_FromLong(rate);
370+
return PyLong_FromLong(self->block_size * 8);
355371
}
356372

357373
static PyObject *

0 commit comments

Comments
 (0)