diff --git a/Misc/NEWS.d/next/Library/2026-08-15-13-12-09.gh-issue-155835.DsbSmy.rst b/Misc/NEWS.d/next/Library/2026-08-15-13-12-09.gh-issue-155835.DsbSmy.rst new file mode 100644 index 00000000000000..7c48daad1c29d8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-15-13-12-09.gh-issue-155835.DsbSmy.rst @@ -0,0 +1,3 @@ +:mod:`hashlib`: Fix data races when accessing +:attr:`~hashlib.hash.digest_size` and :attr:`~hashlib.hash.block_size` on +SHA-3 objects. Patch by Bénédikt Tran. diff --git a/Modules/sha3module.c b/Modules/sha3module.c index 3ddd0323575b70..3f694238de8654 100644 --- a/Modules/sha3module.c +++ b/Modules/sha3module.c @@ -67,6 +67,12 @@ sha3_get_state(PyObject *module) typedef struct { HASHLIB_OBJECT_HEAD Hacl_Hash_SHA3_state_t *hash_state; + // HACL* update functions entirely replace the state, which can lead + // to races on the free-threaded build. Since the kind of hash is static, + // we can store its corresponding metadata once. + uint32_t digest_size; + uint32_t block_size; + int is_shake; } SHA3object; #define _SHA3object_CAST(op) ((SHA3object *)(op)) @@ -96,7 +102,7 @@ newSHA3object(PyTypeObject *type) return NULL; } HASHLIB_INIT_MUTEX(newobj); - + newobj->digest_size = newobj->block_size = 0; PyObject_GC_Track(newobj); return newobj; } @@ -179,6 +185,11 @@ py_sha3_new_impl(PyTypeObject *type, PyObject *data_obj, int usedforsecurity, goto error; } + // set the metadata once we know that the state is valid + int is_shake = Hacl_Hash_SHA3_is_shake(self->hash_state); + self->digest_size = is_shake ? 0 : Hacl_Hash_SHA3_hash_len(self->hash_state); + self->block_size = Hacl_Hash_SHA3_block_len(self->hash_state); + if (data) { GET_BUFFER_VIEW_OR_ERROR(data, &buf, goto error); /* Do not use self->mutex here as this is the constructor @@ -253,6 +264,8 @@ _sha3_sha3_224_copy_impl(SHA3object *self, PyTypeObject *cls) Py_DECREF(newobj); return PyErr_NoMemory(); } + newobj->digest_size = self->digest_size; + newobj->block_size = self->block_size; return (PyObject *)newobj; } @@ -273,8 +286,7 @@ _sha3_sha3_224_digest_impl(SHA3object *self) HASHLIB_ACQUIRE_LOCK(self); (void)Hacl_Hash_SHA3_digest(self->hash_state, digest); HASHLIB_RELEASE_LOCK(self); - return PyBytes_FromStringAndSize((const char *)digest, - Hacl_Hash_SHA3_hash_len(self->hash_state)); + return PyBytes_FromStringAndSize((const char *)digest, self->digest_size); } @@ -292,8 +304,7 @@ _sha3_sha3_224_hexdigest_impl(SHA3object *self) HASHLIB_ACQUIRE_LOCK(self); (void)Hacl_Hash_SHA3_digest(self->hash_state, digest); HASHLIB_RELEASE_LOCK(self); - return _Py_strhex((const char *)digest, - Hacl_Hash_SHA3_hash_len(self->hash_state)); + return _Py_strhex((const char *)digest, self->digest_size); } @@ -334,8 +345,7 @@ static PyObject * SHA3_get_block_size(PyObject *op, void *Py_UNUSED(closure)) { SHA3object *self = _SHA3object_CAST(op); - uint32_t rate = Hacl_Hash_SHA3_block_len(self->hash_state); - return PyLong_FromLong(rate); + return PyLong_FromLong(self->block_size); } @@ -371,10 +381,7 @@ SHA3_get_digest_size(PyObject *op, void *Py_UNUSED(closure)) { // Preserving previous behavior: variable-length algorithms return 0 SHA3object *self = _SHA3object_CAST(op); - if (Hacl_Hash_SHA3_is_shake(self->hash_state)) - return PyLong_FromLong(0); - else - return PyLong_FromLong(Hacl_Hash_SHA3_hash_len(self->hash_state)); + return PyLong_FromLong(self->digest_size); } @@ -382,7 +389,7 @@ static PyObject * SHA3_get_capacity_bits(PyObject *op, void *Py_UNUSED(closure)) { SHA3object *self = _SHA3object_CAST(op); - uint32_t rate = Hacl_Hash_SHA3_block_len(self->hash_state) * 8; + uint32_t rate = self->block_size * 8; assert(rate <= 1600); int capacity = 1600 - rate; return PyLong_FromLong(capacity); @@ -393,8 +400,7 @@ static PyObject * SHA3_get_rate_bits(PyObject *op, void *Py_UNUSED(closure)) { SHA3object *self = _SHA3object_CAST(op); - uint32_t rate = Hacl_Hash_SHA3_block_len(self->hash_state) * 8; - return PyLong_FromLong(rate); + return PyLong_FromLong(self->block_size * 8); } static PyObject *