Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 90 additions & 2 deletions Lib/test/test_hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -52,18 +54,39 @@
def get_fips_mode():
return 0


try:
import _md5
except ImportError:
_md5 = None
requires_md5 = unittest.skipUnless(_md5, 'requires _md5')


try:
import _blake2
except ImportError:
_blake2 = None

requires_blake2 = unittest.skipUnless(_blake2, 'requires _blake2')


try:
import _sha1
except ImportError:
_sha1 = None
requires_sha1 = unittest.skipUnless(_sha1, 'requires _sha1')


try:
import _sha2
except ImportError:
_sha2 = None
requires_sha2 = unittest.skipUnless(_sha2, 'requires _sha2')


try:
import _sha3
except ImportError:
_sha3 = None

requires_sha3 = unittest.skipUnless(_sha3, 'requires _sha3')


Expand Down Expand Up @@ -1418,5 +1441,70 @@ 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_md5
@support.subTests("attrname", ["block_size", "digest_size"])
def test_HACL_md5_attributes(self, attrname):
self.check_HACL_attribute(_md5, "md5", attrname)

@requires_sha1
@support.subTests("attrname", ["block_size", "digest_size"])
def test_HACL_sha1_attributes(self, attrname):
self.check_HACL_attribute(_sha1, "sha1", attrname)

@requires_sha2
@support.subTests("size", [224, 256, 384, 512])
@support.subTests("attrname", ["block_size", "digest_size"])
def test_HACL_sha2_attributes(self, size, attrname):
self.check_HACL_attribute(_sha2, f"sha{size}", attrname)

@requires_sha3
@support.subTests("size", [224, 256, 384, 512])
@support.subTests(
"attrname",
["block_size", "digest_size", "_capacity_bits", "_rate_bits"],
)
def test_HACL_sha3_attributes(self, size, attrname):
self.check_HACL_attribute(_sha3, f"sha3_{size}", attrname)

@requires_sha3
@support.subTests("size", [128, 256])
@support.subTests(
"attrname",
["block_size", "digest_size", "_capacity_bits", "_rate_bits"],
)
def test_HACL_shake_attributes(self, size, attrname):
self.check_HACL_attribute(_sha3, f"shake_{size}", attrname)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -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.
34 changes: 20 additions & 14 deletions Modules/sha3module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand All @@ -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);
}


Expand All @@ -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);
}


Expand Down Expand Up @@ -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);
}


Expand Down Expand Up @@ -371,18 +381,15 @@ 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);
}


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);
Expand All @@ -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 *
Expand Down
Loading