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
97 changes: 95 additions & 2 deletions Lib/test/test_hashlib.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,8 @@
import threading
import unittest
import warnings
from functools import partial
from operator import attrgetter
from test import support
from test.support import _4G, bigmemtest
from test.support.import_helper import import_fresh_module
Expand DownExpand Up@@ -52,16 +54,42 @@
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
# bpo-46913: Don't test the _sha3 extension on a Python UBSAN build
# TODO(gh-99108): Revisit this after _sha3 uses HACL*.
SKIP_SHA3 = support.check_sanitizer(ub=True)
SKIP_SHA3 = _sha3 is None or support.check_sanitizer(ub=True)
requires_sha3 = unittest.skipUnless(not SKIP_SHA3, 'requires _sha3')


Expand DownExpand Up@@ -1273,5 +1301,70 @@ def readable(self):
hashlib.file_digest(NonBlocking(), hashlib.sha256)


@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 numberDiff line numberDiff 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.
48 changes: 32 additions & 16 deletions Modules/sha3module.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,6 +64,12 @@ typedef struct {
bool use_mutex;
PyMutex mutex;
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;

#include "clinic/sha3module.c.h"
Expand All@@ -77,7 +83,7 @@ newSHA3object(PyTypeObject *type)
return NULL;
}
HASHLIB_INIT_MUTEX(newobj);

newobj->digest_size = newobj->block_size = 0;
return newobj;
}

Expand DownExpand Up@@ -143,6 +149,16 @@ py_sha3_new_impl(PyTypeObject *type, PyObject *data_obj, int usedforsecurity,
goto error;
}

if (self->hash_state == NULL) {
(void)PyErr_NoMemory();
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);
if (buf.len >= HASHLIB_GIL_MINSIZE) {
Expand DownExpand Up@@ -204,6 +220,12 @@ _sha3_sha3_224_copy_impl(SHA3object *self)
ENTER_HASHLIB(self);
newobj->hash_state = Hacl_Hash_SHA3_copy(self->hash_state);
LEAVE_HASHLIB(self);
if (newobj->hash_state == NULL) {
Py_DECREF(newobj);
return PyErr_NoMemory();
}
newobj->digest_size = self->digest_size;
newobj->block_size = self->block_size;
return (PyObject *)newobj;
}

Expand All@@ -222,10 +244,9 @@ _sha3_sha3_224_digest_impl(SHA3object *self)
// This function errors out if the algorithm is Shake. Here, we know this
// not to be the case, and therefore do not perform error checking.
ENTER_HASHLIB(self);
Hacl_Hash_SHA3_digest(self->hash_state, digest);
(void)Hacl_Hash_SHA3_digest(self->hash_state, digest);
LEAVE_HASHLIB(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@@ -241,10 +262,9 @@ _sha3_sha3_224_hexdigest_impl(SHA3object *self)
{
unsigned char digest[SHA3_MAX_DIGESTSIZE];
ENTER_HASHLIB(self);
Hacl_Hash_SHA3_digest(self->hash_state, digest);
(void)Hacl_Hash_SHA3_digest(self->hash_state, digest);
LEAVE_HASHLIB(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 DownExpand Up@@ -295,8 +315,7 @@ static PyMethodDef SHA3_methods[] = {
static PyObject *
SHA3_get_block_size(SHA3object *self, void *closure)
{
uint32_t rate = Hacl_Hash_SHA3_block_len(self->hash_state);
return PyLong_FromLong(rate);
return PyLong_FromLong(self->block_size);
}


Expand DownExpand Up@@ -331,17 +350,15 @@ static PyObject *
SHA3_get_digest_size(SHA3object *self, void *closure)
{
// Preserving previous behavior: variable-length algorithms return 0
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(SHA3object *self, void *closure)
{
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@@ -350,8 +367,7 @@ SHA3_get_capacity_bits(SHA3object *self, void *closure)
static PyObject *
SHA3_get_rate_bits(SHA3object *self, void *closure)
{
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