diff --git a/src/nacl/bindings/crypto_generichash.py b/src/nacl/bindings/crypto_generichash.py index 521873cc..e5e70d7f 100644 --- a/src/nacl/bindings/crypto_generichash.py +++ b/src/nacl/bindings/crypto_generichash.py @@ -40,6 +40,7 @@ _OVERLONG = "{0} length greater than {1} bytes" _TOOBIG = "{0} greater than {1}" +_TOOSMALL = "{0} less than {1}" def _checkparams( @@ -70,6 +71,14 @@ def _checkparams( raising=exc.TypeError, ) + # libsodium's blake2b_final() calls sodium_misuse() (abort) when outlen + # is 0; reject that here so callers get a Python exception instead. + ensure( + digest_size >= 1, + _TOOSMALL.format("Digest_size", 1), + raising=exc.ValueError, + ) + ensure( digest_size <= crypto_generichash_BYTES_MAX, _TOOBIG.format("Digest_size", crypto_generichash_BYTES_MAX), @@ -271,6 +280,14 @@ def generichash_blake2b_final(state: Blake2State) -> bytes: raising=exc.TypeError, ) + # Blake2State can be constructed directly, bypassing _checkparams(). + # Guard digest_size here so a zero digest does not abort via sodium_misuse. + ensure( + state.digest_size >= 1, + _TOOSMALL.format("Digest_size", 1), + raising=exc.ValueError, + ) + _digest = ffi.new("unsigned char[]", crypto_generichash_BYTES_MAX) rc = lib.crypto_generichash_blake2b_final( state._statebuf, _digest, state.digest_size diff --git a/tests/test_generichash.py b/tests/test_generichash.py index 7aee1e4e..e9906074 100644 --- a/tests/test_generichash.py +++ b/tests/test_generichash.py @@ -155,6 +155,36 @@ def test_expected_bindings_level_pickle_and_copy_failures(): copy.copy(st) +def test_generichash_blake2b_final_rejects_zero_directly_constructed_state(): + # A Blake2State constructed directly with digest_size=0 used to abort + # the process (SIGABRT via sodium_misuse in blake2b_final) instead of + # raising a Python exception. Same failure class as issue #964, which + # covered oversized digest_size; zero is also rejected by libsodium. + from nacl.bindings.crypto_generichash import ( + Blake2State, + generichash_blake2b_final, + ) + + with pytest.raises(exc.ValueError): + generichash_blake2b_final(Blake2State(0)) + + +def test_generichash_blake2b_init_rejects_zero_digest_size(): + from nacl.bindings.crypto_generichash import generichash_blake2b_init + + with pytest.raises(exc.ValueError): + generichash_blake2b_init(digest_size=0) + + +def test_generichash_blake2b_salt_personal_rejects_zero_digest_size(): + from nacl.bindings.crypto_generichash import ( + generichash_blake2b_salt_personal, + ) + + with pytest.raises(exc.ValueError): + generichash_blake2b_salt_personal(b"msg", digest_size=0) + + @pytest.mark.parametrize( ["message", "key", "outlen", "output"], blake2_reference_vectors() )