From 60e9a0f5bec2e93c6e8fd462850676f488aa51c8 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 2 Aug 2026 15:24:06 -0700 Subject: [PATCH 01/11] lib/crypto: fips: Split fips.h into fips-aes.h and fips-sha.h In preparation for adding FIPS self-tests for AES encryption modes, split fips.h into separate files for the AES and SHA test vectors. They are still generated by the same script, but this keeps things a bit more organized. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260802222408.91757-2-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/aes.c | 2 +- lib/crypto/fips-aes.h | 20 +++++++ lib/crypto/{fips.h => fips-sha.h} | 6 +-- lib/crypto/sha1.c | 2 +- lib/crypto/sha256.c | 2 +- lib/crypto/sha3.c | 2 +- lib/crypto/sha512.c | 2 +- scripts/crypto/gen-fips-testvecs.py | 83 ++++++++++++++++++++--------- 8 files changed, 83 insertions(+), 36 deletions(-) create mode 100644 lib/crypto/fips-aes.h rename lib/crypto/{fips.h => fips-sha.h} (90%) diff --git a/lib/crypto/aes.c b/lib/crypto/aes.c index 4222a4cec2f2..617c913d512e 100644 --- a/lib/crypto/aes.c +++ b/lib/crypto/aes.c @@ -19,7 +19,7 @@ #include #include #include -#include "fips.h" +#include "fips-aes.h" static const u8 ____cacheline_aligned aes_sbox[] = { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, diff --git a/lib/crypto/fips-aes.h b/lib/crypto/fips-aes.h new file mode 100644 index 000000000000..b257cb216871 --- /dev/null +++ b/lib/crypto/fips-aes.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* This file was generated by: gen-fips-testvecs.py */ +/* clang-format off */ + +#include + +static const u8 fips_test_data[] __initconst __maybe_unused = { + 0x66, 0x69, 0x70, 0x73, 0x20, 0x74, 0x65, 0x73, + 0x74, 0x20, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, +}; + +static const u8 fips_test_key[] __initconst __maybe_unused = { + 0x66, 0x69, 0x70, 0x73, 0x20, 0x74, 0x65, 0x73, + 0x74, 0x20, 0x6b, 0x65, 0x79, 0x00, 0x00, 0x00, +}; + +static const u8 fips_test_aes_cmac_value[] __initconst __maybe_unused = { + 0xc5, 0x88, 0x28, 0x55, 0xd7, 0x2c, 0x00, 0xb6, + 0x6a, 0xa7, 0xfc, 0x82, 0x90, 0x81, 0xcf, 0x18, +}; diff --git a/lib/crypto/fips.h b/lib/crypto/fips-sha.h similarity index 90% rename from lib/crypto/fips.h rename to lib/crypto/fips-sha.h index 9fc49747db64..68af7e14e09c 100644 --- a/lib/crypto/fips.h +++ b/lib/crypto/fips-sha.h @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* This file was generated by: gen-fips-testvecs.py */ +/* clang-format off */ #include @@ -43,8 +44,3 @@ static const u8 fips_test_sha3_256_value[] __initconst __maybe_unused = { 0xba, 0x9b, 0xb6, 0xaa, 0x32, 0xa7, 0x97, 0x00, 0x98, 0xdb, 0xff, 0xe7, 0xc6, 0xde, 0xb5, 0x82, }; - -static const u8 fips_test_aes_cmac_value[] __initconst __maybe_unused = { - 0xc5, 0x88, 0x28, 0x55, 0xd7, 0x2c, 0x00, 0xb6, - 0x6a, 0xa7, 0xfc, 0x82, 0x90, 0x81, 0xcf, 0x18, -}; diff --git a/lib/crypto/sha1.c b/lib/crypto/sha1.c index daf18c862fdf..b687b89d97cb 100644 --- a/lib/crypto/sha1.c +++ b/lib/crypto/sha1.c @@ -12,7 +12,7 @@ #include #include #include -#include "fips.h" +#include "fips-sha.h" static const struct sha1_block_state sha1_iv = { .h = { SHA1_H0, SHA1_H1, SHA1_H2, SHA1_H3, SHA1_H4 }, diff --git a/lib/crypto/sha256.c b/lib/crypto/sha256.c index 5d6b77e7e141..e8c346f563c5 100644 --- a/lib/crypto/sha256.c +++ b/lib/crypto/sha256.c @@ -17,7 +17,7 @@ #include #include #include -#include "fips.h" +#include "fips-sha.h" static const struct sha256_block_state sha224_iv = { .h = { diff --git a/lib/crypto/sha3.c b/lib/crypto/sha3.c index 32b7074de792..286a2373c156 100644 --- a/lib/crypto/sha3.c +++ b/lib/crypto/sha3.c @@ -17,7 +17,7 @@ #include #include #include -#include "fips.h" +#include "fips-sha.h" /* * On some 32-bit architectures, such as h8300, GCC ends up using over 1 KB of diff --git a/lib/crypto/sha512.c b/lib/crypto/sha512.c index 605eab51aabd..0dd6fb4ca15b 100644 --- a/lib/crypto/sha512.c +++ b/lib/crypto/sha512.c @@ -17,7 +17,7 @@ #include #include #include -#include "fips.h" +#include "fips-sha.h" static const struct sha512_block_state sha384_iv = { .h = { diff --git a/scripts/crypto/gen-fips-testvecs.py b/scripts/crypto/gen-fips-testvecs.py index 9f18bcb97412..aa6c0a81fbf8 100755 --- a/scripts/crypto/gen-fips-testvecs.py +++ b/scripts/crypto/gen-fips-testvecs.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-or-later # -# Script that generates lib/crypto/fips.h +# Script that generates lib/crypto/fips-aes.h and lib/crypto/fips-sha.h # # Requires that python-cryptography be installed. # @@ -12,35 +12,66 @@ import cryptography.hazmat.primitives.cmac import hashlib import hmac -fips_test_data = b"fips test data\0\0" -fips_test_key = b"fips test key\0\0\0" -def print_static_u8_array_definition(name, value): - print('') - print(f'static const u8 {name}[] __initconst __maybe_unused = {{') +def print_static_u8_array_definition(file, name, value): + print("", file=file) + print(f"static const u8 {name}[] __initconst __maybe_unused = {{", file=file) for i in range(0, len(value), 8): - line = '\t' + ''.join(f'0x{b:02x}, ' for b in value[i:i+8]) - print(f'{line.rstrip()}') - print('};') + line = "\t" + "".join(f"0x{b:02x}, " for b in value[i : i + 8]) + print(f"{line.rstrip()}", file=file) + print("};", file=file) -print('/* SPDX-License-Identifier: GPL-2.0-or-later */') -print(f'/* This file was generated by: gen-fips-testvecs.py */') -print() -print('#include ') -print_static_u8_array_definition("fips_test_data", fips_test_data) -print_static_u8_array_definition("fips_test_key", fips_test_key) +def print_header(file): + print("/* SPDX-License-Identifier: GPL-2.0-or-later */", file=file) + print("/* This file was generated by: gen-fips-testvecs.py */", file=file) + print("/* clang-format off */", file=file) + print("", file=file) + print("#include ", file=file) -for alg in 'sha1', 'sha256', 'sha512': - ctx = hmac.new(fips_test_key, digestmod=alg) - ctx.update(fips_test_data) - print_static_u8_array_definition(f'fips_test_hmac_{alg}_value', ctx.digest()) -print_static_u8_array_definition(f'fips_test_sha3_256_value', - hashlib.sha3_256(fips_test_data).digest()) +def gen_aes_test_data(file): + fips_test_data = b"fips test data\0\0" + fips_test_key = b"fips test key\0\0\0" -aes = cryptography.hazmat.primitives.ciphers.algorithms.AES(fips_test_key) -aes_cmac = cryptography.hazmat.primitives.cmac.CMAC(aes) -aes_cmac.update(fips_test_data) -print_static_u8_array_definition('fips_test_aes_cmac_value', - aes_cmac.finalize()) + print_header(file) + print_static_u8_array_definition(file, "fips_test_data", fips_test_data) + print_static_u8_array_definition(file, "fips_test_key", fips_test_key) + + aes = cryptography.hazmat.primitives.ciphers.algorithms.AES(fips_test_key) + aes_cmac = cryptography.hazmat.primitives.cmac.CMAC(aes) + aes_cmac.update(fips_test_data) + print_static_u8_array_definition( + file, "fips_test_aes_cmac_value", aes_cmac.finalize() + ) + + +def gen_sha_test_data(file): + fips_test_data = b"fips test data\0\0" + fips_test_key = b"fips test key\0\0\0" + + print_header(file) + print_static_u8_array_definition(file, "fips_test_data", fips_test_data) + print_static_u8_array_definition(file, "fips_test_key", fips_test_key) + + for alg in "sha1", "sha256", "sha512": + ctx = hmac.new(fips_test_key, digestmod=alg) + ctx.update(fips_test_data) + print_static_u8_array_definition( + file, f"fips_test_hmac_{alg}_value", ctx.digest() + ) + + print_static_u8_array_definition( + file, "fips_test_sha3_256_value", hashlib.sha3_256(fips_test_data).digest() + ) + + +filename = "lib/crypto/fips-aes.h" +with open(filename, "w") as file: + print(f"Generating {filename}") + gen_aes_test_data(file) + +filename = "lib/crypto/fips-sha.h" +with open(filename, "w") as file: + print(f"Generating {filename}") + gen_sha_test_data(file) From e967fa98f7618e98b50b3d49a1768deba8981a98 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 2 Aug 2026 15:24:07 -0700 Subject: [PATCH 02/11] lib/crypto: aes: Add FIPS self-tests for unauthenticated modes Upcoming changes will wire up architecture-optimized implementations of ECB, CBC, CBC-CTS, CTR, and XTS. FIPS labs can consider such designs to meet the threshold for separate self-tests to be needed. The inverse direction of the block cipher also needs to be exercised, which the existing CMAC self-test doesn't do. Therefore, add FIPS self-tests for encryption and decryption in these modes as well as the "bare" AES. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260802222408.91757-3-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/aes.c | 176 +++++++++++++++++++++++++++- lib/crypto/fips-aes.h | 39 ++++++ scripts/crypto/gen-fips-testvecs.py | 48 ++++++++ 3 files changed, 258 insertions(+), 5 deletions(-) diff --git a/lib/crypto/aes.c b/lib/crypto/aes.c index 617c913d512e..e9119f82b0cc 100644 --- a/lib/crypto/aes.c +++ b/lib/crypto/aes.c @@ -522,6 +522,26 @@ void aes_decrypt(const struct aes_key *key, u8 out[AES_BLOCK_SIZE], } EXPORT_SYMBOL(aes_decrypt); +/* FIPS cryptographic algorithm self-test for "bare" AES */ +static void __init aes_fips_test(void) +{ + struct aes_key key; + u8 data[AES_BLOCK_SIZE]; + + if (aes_preparekey(&key, fips_test_key, sizeof(fips_test_key)) != 0) + panic("aes: FIPS self-test failed (preparekey)\n"); + + aes_encrypt(&key, data, fips_test_data); + if (memcmp(fips_test_aes_ecb_ctext, data, sizeof(data)) != 0) + panic("aes: FIPS self-test failed (wrong ciphertext)\n"); + + aes_decrypt(&key, data, data); + if (memcmp(fips_test_data, data, sizeof(data)) != 0) + panic("aes: FIPS self-test failed (wrong plaintext)\n"); + + memzero_explicit(&key, sizeof(key)); +} + #if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_CBC_MACS) #ifndef aes_cbcmac_blocks_arch @@ -797,7 +817,31 @@ void aes_ecb_decrypt(u8 *dst, const u8 *src, size_t len, aes_decrypt(key, &dst[i], &src[i]); } EXPORT_SYMBOL_GPL(aes_ecb_decrypt); -#endif /* CONFIG_CRYPTO_LIB_AES_ECB */ + +/* FIPS cryptographic algorithm self-test for AES-ECB */ +static void __init aes_ecb_fips_test(void) +{ + struct aes_key key; + u8 data[sizeof(fips_test_data)]; + + if (aes_preparekey(&key, fips_test_key, sizeof(fips_test_key)) != 0) + panic("aes: ECB FIPS self-test failed (preparekey)\n"); + + aes_ecb_encrypt(data, fips_test_data, sizeof(data), &key); + if (memcmp(fips_test_aes_ecb_ctext, data, sizeof(data)) != 0) + panic("aes: ECB FIPS self-test failed (wrong ciphertext)\n"); + + aes_ecb_decrypt(data, data, sizeof(data), &key); + if (memcmp(fips_test_data, data, sizeof(data)) != 0) + panic("aes: ECB FIPS self-test failed (wrong plaintext)\n"); + + memzero_explicit(&key, sizeof(key)); +} +#else /* CONFIG_CRYPTO_LIB_AES_ECB */ +static inline void aes_ecb_fips_test(void) +{ +} +#endif /* !CONFIG_CRYPTO_LIB_AES_ECB */ #if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_CBC) /* @@ -983,7 +1027,66 @@ void aes_cbc_cts_decrypt(u8 *dst, const u8 *src, size_t len, crypto_xor(pad, iv, AES_BLOCK_SIZE); /* P[n - 1] */ } EXPORT_SYMBOL_GPL(aes_cbc_cts_decrypt); -#endif /* CONFIG_CRYPTO_LIB_AES_CBC */ + +/* FIPS cryptographic algorithm self-test for AES-CBC */ +static void __init aes_cbc_fips_test(void) +{ + struct aes_key key; + u8 iv[AES_BLOCK_SIZE]; + u8 data[sizeof(fips_test_data)]; + + if (aes_preparekey(&key, fips_test_key, sizeof(fips_test_key)) != 0) + panic("aes: CBC FIPS self-test failed (preparekey)\n"); + + memcpy(iv, fips_test_iv, sizeof(iv)); + aes_cbc_encrypt(data, fips_test_data, sizeof(data), iv, &key); + if (memcmp(fips_test_aes_cbc_ctext, data, sizeof(data)) != 0) + panic("aes: CBC FIPS self-test failed (wrong ciphertext)\n"); + + memcpy(iv, fips_test_iv, sizeof(iv)); + aes_cbc_decrypt(data, data, sizeof(data), iv, &key); + if (memcmp(fips_test_data, data, sizeof(data)) != 0) + panic("aes: CBC FIPS self-test failed (wrong plaintext)\n"); + + memzero_explicit(&key, sizeof(key)); +} + +/* FIPS cryptographic algorithm self-test for AES-CBC-CTS */ +static void __init aes_cbc_cts_fips_test(void) +{ + struct aes_key key; + u8 iv[AES_BLOCK_SIZE]; + const size_t data_len = 2 * AES_BLOCK_SIZE; + u8 ptext[2 * AES_BLOCK_SIZE]; + u8 data[2 * AES_BLOCK_SIZE]; + + /* ptext = fips_test_data || fips_test_data */ + memcpy(ptext, fips_test_data, AES_BLOCK_SIZE); + memcpy(&ptext[AES_BLOCK_SIZE], ptext, AES_BLOCK_SIZE); + + if (aes_preparekey(&key, fips_test_key, sizeof(fips_test_key)) != 0) + panic("aes: CBC-CTS FIPS self-test failed (preparekey)\n"); + + memcpy(iv, fips_test_iv, sizeof(iv)); + aes_cbc_cts_encrypt(data, ptext, data_len, iv, &key); + if (memcmp(fips_test_aes_cbc_cts_ctext, data, data_len) != 0) + panic("aes: CBC-CTS FIPS self-test failed (wrong ciphertext)\n"); + + memcpy(iv, fips_test_iv, sizeof(iv)); + aes_cbc_cts_decrypt(data, data, data_len, iv, &key); + if (memcmp(ptext, data, data_len) != 0) + panic("aes: CBC-CTS FIPS self-test failed (wrong plaintext)\n"); + + memzero_explicit(&key, sizeof(key)); +} +#else /* CONFIG_CRYPTO_LIB_AES_CBC */ +static inline void aes_cbc_fips_test(void) +{ +} +static inline void aes_cbc_cts_fips_test(void) +{ +} +#endif /* !CONFIG_CRYPTO_LIB_AES_CBC */ #if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_CTR) /* @@ -1078,7 +1181,34 @@ void aes_xctr(u8 *dst, const u8 *src, size_t len, u64 *ctr, memzero_explicit(aes_input, sizeof(aes_input)); } EXPORT_SYMBOL_GPL(aes_xctr); -#endif /* CONFIG_CRYPTO_LIB_AES_CTR */ + +/* FIPS cryptographic algorithm self-test for AES-CTR */ +static void __init aes_ctr_fips_test(void) +{ + struct aes_enckey key; + u8 ctr[AES_BLOCK_SIZE]; + u8 data[sizeof(fips_test_data)]; + + if (aes_prepareenckey(&key, fips_test_key, sizeof(fips_test_key)) != 0) + panic("aes: CTR FIPS self-test failed (preparekey)\n"); + + memcpy(ctr, fips_test_iv, sizeof(ctr)); + aes_ctr(data, fips_test_data, sizeof(data), ctr, &key); + if (memcmp(fips_test_aes_ctr_ctext, data, sizeof(data)) != 0) + panic("aes: CTR FIPS self-test failed (wrong ciphertext)\n"); + + memcpy(ctr, fips_test_iv, sizeof(ctr)); + aes_ctr(data, data, sizeof(data), ctr, &key); + if (memcmp(fips_test_data, data, sizeof(data)) != 0) + panic("aes: CTR FIPS self-test failed (wrong plaintext)\n"); + + memzero_explicit(&key, sizeof(key)); +} +#else /* CONFIG_CRYPTO_LIB_AES_CTR */ +static inline void aes_ctr_fips_test(void) +{ +} +#endif /* !CONFIG_CRYPTO_LIB_AES_CTR */ #if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_XTS) int aes_xts_preparekey(struct aes_xts_key *key, const u8 *in_key, @@ -1307,7 +1437,36 @@ void aes_xts_decrypt(u8 *dst, const u8 *src, size_t len, aes_xts_decrypt_nocts(dst, src, len, tweak, key, cont); } EXPORT_SYMBOL_GPL(aes_xts_decrypt); -#endif /* CONFIG_CRYPTO_LIB_AES_XTS */ + +/* FIPS cryptographic algorithm self-test for AES-XTS */ +static void __init aes_xts_fips_test(void) +{ + struct aes_xts_key *key __free(kfree_sensitive) = kmalloc_obj(*key); + u8 tweak[AES_BLOCK_SIZE]; + u8 data[sizeof(fips_test_data)]; + + if (key == NULL) + panic("aes: XTS FIPS self-test failed (kmalloc)\n"); + + if (aes_xts_preparekey(key, fips_test_xts_key, + sizeof(fips_test_xts_key), 0) != 0) + panic("aes: XTS FIPS self-test failed (preparekey)\n"); + + memcpy(tweak, fips_test_iv, sizeof(tweak)); + aes_xts_encrypt(data, fips_test_data, sizeof(data), tweak, key, false); + if (memcmp(fips_test_aes_xts_ctext, data, sizeof(data)) != 0) + panic("aes: XTS FIPS self-test failed (wrong ciphertext)\n"); + + memcpy(tweak, fips_test_iv, sizeof(tweak)); + aes_xts_decrypt(data, data, sizeof(data), tweak, key, false); + if (memcmp(fips_test_data, data, sizeof(data)) != 0) + panic("aes: XTS FIPS self-test failed (wrong plaintext)\n"); +} +#else /* CONFIG_CRYPTO_LIB_AES_XTS */ +static inline void aes_xts_fips_test(void) +{ +} +#endif /* !CONFIG_CRYPTO_LIB_AES_XTS */ #if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_GCM) /* @@ -1905,8 +2064,15 @@ static int __init aes_mod_init(void) #ifdef aes_mod_init_arch aes_mod_init_arch(); #endif - if (fips_enabled) + if (fips_enabled) { + aes_fips_test(); aes_cmac_fips_test(); + aes_ecb_fips_test(); + aes_cbc_fips_test(); + aes_cbc_cts_fips_test(); + aes_ctr_fips_test(); + aes_xts_fips_test(); + } return 0; } subsys_initcall(aes_mod_init); diff --git a/lib/crypto/fips-aes.h b/lib/crypto/fips-aes.h index b257cb216871..cfacf5d98e07 100644 --- a/lib/crypto/fips-aes.h +++ b/lib/crypto/fips-aes.h @@ -9,12 +9,51 @@ static const u8 fips_test_data[] __initconst __maybe_unused = { 0x74, 0x20, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, }; +static const u8 fips_test_iv[] __initconst __maybe_unused = { + 0x66, 0x69, 0x70, 0x73, 0x20, 0x74, 0x65, 0x73, + 0x74, 0x20, 0x69, 0x76, 0x00, 0x00, 0x00, 0x00, +}; + static const u8 fips_test_key[] __initconst __maybe_unused = { 0x66, 0x69, 0x70, 0x73, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6b, 0x65, 0x79, 0x00, 0x00, 0x00, }; +static const u8 fips_test_xts_key[] __initconst __maybe_unused = { + 0x6b, 0x65, 0x79, 0x31, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6b, 0x65, 0x79, 0x32, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + static const u8 fips_test_aes_cmac_value[] __initconst __maybe_unused = { 0xc5, 0x88, 0x28, 0x55, 0xd7, 0x2c, 0x00, 0xb6, 0x6a, 0xa7, 0xfc, 0x82, 0x90, 0x81, 0xcf, 0x18, }; + +static const u8 fips_test_aes_ecb_ctext[] __initconst __maybe_unused = { + 0x47, 0x76, 0x48, 0xaf, 0x1b, 0xd8, 0x4c, 0xe6, + 0xb5, 0xa7, 0x20, 0x8d, 0x64, 0x88, 0xbc, 0x3f, +}; + +static const u8 fips_test_aes_cbc_ctext[] __initconst __maybe_unused = { + 0xc8, 0x7d, 0x7c, 0x25, 0xba, 0x15, 0xf7, 0xe1, + 0x08, 0xa0, 0xd0, 0x7a, 0x20, 0x37, 0xaf, 0x5e, +}; + +static const u8 fips_test_aes_cbc_cts_ctext[] __initconst __maybe_unused = { + 0x36, 0x8e, 0x37, 0xb4, 0x78, 0xe2, 0x88, 0x59, + 0xd5, 0xe8, 0x17, 0x65, 0x5c, 0xa1, 0x25, 0xe6, + 0xc8, 0x7d, 0x7c, 0x25, 0xba, 0x15, 0xf7, 0xe1, + 0x08, 0xa0, 0xd0, 0x7a, 0x20, 0x37, 0xaf, 0x5e, +}; + +static const u8 fips_test_aes_ctr_ctext[] __initconst __maybe_unused = { + 0x95, 0xf4, 0xf4, 0x7a, 0xc8, 0xa2, 0x53, 0x73, + 0x53, 0x8f, 0x95, 0xfc, 0x18, 0xfe, 0x58, 0x2f, +}; + +static const u8 fips_test_aes_xts_ctext[] __initconst __maybe_unused = { + 0xd4, 0x51, 0x7f, 0x01, 0x14, 0x91, 0x16, 0x29, + 0x26, 0xbe, 0xec, 0x9b, 0x90, 0xed, 0x59, 0x30, +}; diff --git a/scripts/crypto/gen-fips-testvecs.py b/scripts/crypto/gen-fips-testvecs.py index aa6c0a81fbf8..a79eaf081c26 100755 --- a/scripts/crypto/gen-fips-testvecs.py +++ b/scripts/crypto/gen-fips-testvecs.py @@ -32,19 +32,67 @@ def print_header(file): def gen_aes_test_data(file): fips_test_data = b"fips test data\0\0" + fips_test_iv = b"fips test iv\0\0\0\0" fips_test_key = b"fips test key\0\0\0" + fips_test_xts_key = b"key1" + (b"\0" * 12) + b"key2" + (b"\0" * 12) print_header(file) print_static_u8_array_definition(file, "fips_test_data", fips_test_data) + print_static_u8_array_definition(file, "fips_test_iv", fips_test_iv) print_static_u8_array_definition(file, "fips_test_key", fips_test_key) + print_static_u8_array_definition(file, "fips_test_xts_key", fips_test_xts_key) aes = cryptography.hazmat.primitives.ciphers.algorithms.AES(fips_test_key) + + # AES-CMAC aes_cmac = cryptography.hazmat.primitives.cmac.CMAC(aes) aes_cmac.update(fips_test_data) print_static_u8_array_definition( file, "fips_test_aes_cmac_value", aes_cmac.finalize() ) + # AES-ECB + cipher = cryptography.hazmat.primitives.ciphers.Cipher( + aes, cryptography.hazmat.primitives.ciphers.modes.ECB() + ) + encryptor = cipher.encryptor() + ctext = encryptor.update(fips_test_data) + encryptor.finalize() + print_static_u8_array_definition(file, "fips_test_aes_ecb_ctext", ctext) + + # AES-CBC + cipher = cryptography.hazmat.primitives.ciphers.Cipher( + aes, cryptography.hazmat.primitives.ciphers.modes.CBC(fips_test_iv) + ) + encryptor = cipher.encryptor() + ctext = encryptor.update(fips_test_data) + encryptor.finalize() + print_static_u8_array_definition(file, "fips_test_aes_cbc_ctext", ctext) + + # AES-CBC-CTS + cipher = cryptography.hazmat.primitives.ciphers.Cipher( + aes, cryptography.hazmat.primitives.ciphers.modes.CBC(fips_test_iv) + ) + encryptor = cipher.encryptor() + ctext = encryptor.update(fips_test_data * 2) + encryptor.finalize() + ctext = ctext[16:32] + ctext[0:16] + print_static_u8_array_definition(file, "fips_test_aes_cbc_cts_ctext", ctext) + + # AES-CTR + cipher = cryptography.hazmat.primitives.ciphers.Cipher( + aes, cryptography.hazmat.primitives.ciphers.modes.CTR(fips_test_iv) + ) + encryptor = cipher.encryptor() + ctext = encryptor.update(fips_test_data) + encryptor.finalize() + print_static_u8_array_definition(file, "fips_test_aes_ctr_ctext", ctext) + + # AES-XTS + cipher = cryptography.hazmat.primitives.ciphers.Cipher( + cryptography.hazmat.primitives.ciphers.algorithms.AES(fips_test_xts_key), + cryptography.hazmat.primitives.ciphers.modes.XTS(fips_test_iv), + ) + encryptor = cipher.encryptor() + ctext = encryptor.update(fips_test_data) + encryptor.finalize() + print_static_u8_array_definition(file, "fips_test_aes_xts_ctext", ctext) + def gen_sha_test_data(file): fips_test_data = b"fips test data\0\0" From fbdb43c0007b37e574fa0ca76afd13bed96db8f6 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 2 Aug 2026 15:24:08 -0700 Subject: [PATCH 03/11] lib/crypto: aes: Add FIPS self-tests for GCM and CCM Upcoming changes will wire up architecture-optimized implementations of GCM and CCM. FIPS labs can consider such designs to meet the threshold for separate self-tests to be needed. Therefore, add FIPS self-tests for encryption and decryption in these modes. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260802222408.91757-4-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/aes.c | 81 +++++++++++++++++++++++++---- lib/crypto/fips-aes.h | 19 +++++++ scripts/crypto/gen-fips-testvecs.py | 23 ++++++++ 3 files changed, 113 insertions(+), 10 deletions(-) diff --git a/lib/crypto/aes.c b/lib/crypto/aes.c index e9119f82b0cc..41aaa82cb1a1 100644 --- a/lib/crypto/aes.c +++ b/lib/crypto/aes.c @@ -737,14 +737,7 @@ void aes_cbcmac_final(struct aes_cbcmac_ctx *ctx, u8 out[AES_BLOCK_SIZE]) } EXPORT_SYMBOL_NS_GPL(aes_cbcmac_final, "CRYPTO_INTERNAL"); -/* - * FIPS cryptographic algorithm self-test for AES-CMAC. As per the FIPS 140-3 - * Implementation Guidance, a cryptographic algorithm self-test for at least one - * of AES-GCM, AES-CCM, AES-CMAC, or AES-GMAC is required if any of those modes - * is implemented. This fulfills that requirement via AES-CMAC. - * - * This is just for FIPS. The full tests are in the KUnit test suite. - */ +/* FIPS cryptographic algorithm self-test for AES-CMAC */ static void __init aes_cmac_fips_test(void) { struct aes_cmac_key key; @@ -1745,7 +1738,37 @@ int aes_gcm_decrypt(u8 *dst, const u8 *src, size_t data_len, const u8 *authtag, } EXPORT_SYMBOL_GPL(aes_gcm_decrypt); -#endif /* CONFIG_CRYPTO_LIB_AES_GCM */ +/* FIPS cryptographic algorithm self-test for AES-GCM */ +static void __init aes_gcm_fips_test(void) +{ + const size_t data_len = sizeof(fips_test_data); + u8 buf[sizeof(fips_test_data) + AES_BLOCK_SIZE]; + struct aes_gcm_key key; + int err; + + if (aes_gcm_preparekey(&key, fips_test_key, sizeof(fips_test_key), + AES_BLOCK_SIZE) != 0) + panic("aes: GCM FIPS self-test failed (preparekey)\n"); + + aes_gcm_encrypt(buf, fips_test_data, data_len, &buf[data_len], + fips_test_ad, sizeof(fips_test_ad), fips_test_iv, &key); + if (memcmp(fips_test_aes_gcm_ctext_and_tag, buf, sizeof(buf)) != 0) + panic("aes: GCM FIPS self-test failed (wrong ciphertext and/or tag)\n"); + + err = aes_gcm_decrypt(buf, buf, data_len, &buf[data_len], fips_test_ad, + sizeof(fips_test_ad), fips_test_iv, &key); + if (err != 0) + panic("aes: GCM FIPS self-test failed (decryption failed)\n"); + if (memcmp(fips_test_data, buf, data_len) != 0) + panic("aes: GCM FIPS self-test failed (wrong plaintext)\n"); + + memzero_explicit(&key, sizeof(key)); +} +#else /* CONFIG_CRYPTO_LIB_AES_GCM */ +static inline void aes_gcm_fips_test(void) +{ +} +#endif /* !CONFIG_CRYPTO_LIB_AES_GCM */ #if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_CCM) int aes_ccm_preparekey(struct aes_ccm_key *key, const u8 *in_key, @@ -2057,7 +2080,43 @@ int aes_ccm_decrypt(u8 *dst, const u8 *src, size_t data_len, const u8 *authtag, return err; } EXPORT_SYMBOL_GPL(aes_ccm_decrypt); -#endif /* CONFIG_CRYPTO_LIB_AES_CCM */ + +/* FIPS cryptographic algorithm self-test for AES-CCM */ +static void __init aes_ccm_fips_test(void) +{ + const size_t data_len = sizeof(fips_test_data); + const size_t nonce_len = 13; + u8 buf[sizeof(fips_test_data) + AES_BLOCK_SIZE]; + struct aes_ccm_key key; + int err; + + if (aes_ccm_preparekey(&key, fips_test_key, sizeof(fips_test_key), + AES_BLOCK_SIZE) != 0) + panic("aes: CCM FIPS self-test failed (preparekey)\n"); + + err = aes_ccm_encrypt(buf, fips_test_data, data_len, &buf[data_len], + fips_test_ad, sizeof(fips_test_ad), fips_test_iv, + nonce_len, &key); + if (err != 0) + panic("aes: CCM FIPS self-test failed (encryption failed)\n"); + if (memcmp(fips_test_aes_ccm_ctext_and_tag, buf, sizeof(buf)) != 0) + panic("aes: CCM FIPS self-test failed (wrong ciphertext and/or tag)\n"); + + err = aes_ccm_decrypt(buf, buf, data_len, &buf[data_len], fips_test_ad, + sizeof(fips_test_ad), fips_test_iv, nonce_len, + &key); + if (err != 0) + panic("aes: CCM FIPS self-test failed (decryption failed)\n"); + if (memcmp(fips_test_data, buf, data_len) != 0) + panic("aes: CCM FIPS self-test failed (wrong plaintext)\n"); + + memzero_explicit(&key, sizeof(key)); +} +#else /* CONFIG_CRYPTO_LIB_AES_CCM */ +static inline void aes_ccm_fips_test(void) +{ +} +#endif /* !CONFIG_CRYPTO_LIB_AES_CCM */ static int __init aes_mod_init(void) { @@ -2072,6 +2131,8 @@ static int __init aes_mod_init(void) aes_cbc_cts_fips_test(); aes_ctr_fips_test(); aes_xts_fips_test(); + aes_gcm_fips_test(); + aes_ccm_fips_test(); } return 0; } diff --git a/lib/crypto/fips-aes.h b/lib/crypto/fips-aes.h index cfacf5d98e07..2a1746606533 100644 --- a/lib/crypto/fips-aes.h +++ b/lib/crypto/fips-aes.h @@ -9,6 +9,11 @@ static const u8 fips_test_data[] __initconst __maybe_unused = { 0x74, 0x20, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, }; +static const u8 fips_test_ad[] __initconst __maybe_unused = { + 0x66, 0x69, 0x70, 0x73, 0x20, 0x74, 0x65, 0x73, + 0x74, 0x20, 0x61, 0x64, 0x00, 0x00, 0x00, 0x00, +}; + static const u8 fips_test_iv[] __initconst __maybe_unused = { 0x66, 0x69, 0x70, 0x73, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x69, 0x76, 0x00, 0x00, 0x00, 0x00, @@ -57,3 +62,17 @@ static const u8 fips_test_aes_xts_ctext[] __initconst __maybe_unused = { 0xd4, 0x51, 0x7f, 0x01, 0x14, 0x91, 0x16, 0x29, 0x26, 0xbe, 0xec, 0x9b, 0x90, 0xed, 0x59, 0x30, }; + +static const u8 fips_test_aes_gcm_ctext_and_tag[] __initconst __maybe_unused = { + 0x12, 0x0c, 0x5d, 0x03, 0x32, 0x93, 0x13, 0x44, + 0x06, 0x35, 0x26, 0x9d, 0xe0, 0xea, 0xbc, 0xe2, + 0x30, 0xa9, 0xa4, 0x15, 0xc5, 0x3d, 0xb3, 0xf9, + 0x30, 0x82, 0xdf, 0x9c, 0xd8, 0xc4, 0x3f, 0x2f, +}; + +static const u8 fips_test_aes_ccm_ctext_and_tag[] __initconst __maybe_unused = { + 0x11, 0x8e, 0x01, 0xcb, 0xb5, 0x22, 0x6d, 0xb4, + 0x66, 0x98, 0x97, 0x1d, 0x35, 0x53, 0x78, 0xdd, + 0xd1, 0xc5, 0xff, 0xb6, 0x90, 0xcf, 0xb1, 0xf2, + 0x87, 0x99, 0xd6, 0x1e, 0xd5, 0xd1, 0xed, 0x63, +}; diff --git a/scripts/crypto/gen-fips-testvecs.py b/scripts/crypto/gen-fips-testvecs.py index a79eaf081c26..b8c8a78cb8a8 100755 --- a/scripts/crypto/gen-fips-testvecs.py +++ b/scripts/crypto/gen-fips-testvecs.py @@ -8,6 +8,7 @@ # Copyright 2025 Google LLC import cryptography.hazmat.primitives.ciphers +import cryptography.hazmat.primitives.ciphers.aead import cryptography.hazmat.primitives.cmac import hashlib import hmac @@ -32,12 +33,14 @@ def print_header(file): def gen_aes_test_data(file): fips_test_data = b"fips test data\0\0" + fips_test_ad = b"fips test ad\0\0\0\0" fips_test_iv = b"fips test iv\0\0\0\0" fips_test_key = b"fips test key\0\0\0" fips_test_xts_key = b"key1" + (b"\0" * 12) + b"key2" + (b"\0" * 12) print_header(file) print_static_u8_array_definition(file, "fips_test_data", fips_test_data) + print_static_u8_array_definition(file, "fips_test_ad", fips_test_ad) print_static_u8_array_definition(file, "fips_test_iv", fips_test_iv) print_static_u8_array_definition(file, "fips_test_key", fips_test_key) print_static_u8_array_definition(file, "fips_test_xts_key", fips_test_xts_key) @@ -93,6 +96,26 @@ def gen_aes_test_data(file): ctext = encryptor.update(fips_test_data) + encryptor.finalize() print_static_u8_array_definition(file, "fips_test_aes_xts_ctext", ctext) + # AES-GCM + cipher = cryptography.hazmat.primitives.ciphers.aead.AESGCM(fips_test_key) + ct_and_tag = cipher.encrypt( + nonce=fips_test_iv[:12], data=fips_test_data, associated_data=fips_test_ad + ) + print_static_u8_array_definition( + file, "fips_test_aes_gcm_ctext_and_tag", ct_and_tag + ) + + # AES-CCM + cipher = cryptography.hazmat.primitives.ciphers.aead.AESCCM( + fips_test_key, tag_length=16 + ) + ct_and_tag = cipher.encrypt( + nonce=fips_test_iv[:13], data=fips_test_data, associated_data=fips_test_ad + ) + print_static_u8_array_definition( + file, "fips_test_aes_ccm_ctext_and_tag", ct_and_tag + ) + def gen_sha_test_data(file): fips_test_data = b"fips test data\0\0" From baf9e7f8019391a4ef8d734ff69e97333a54b09c Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 2 Aug 2026 16:30:01 -0700 Subject: [PATCH 04/11] lib/crypto: tests: Create test-utils.h Move hash-test-template's test RNG code and the buffer allocation code from various test suites into a header test-utils.h. This allows them to be shared by other test suites. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260802233005.161467-2-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/tests/chacha20poly1305_kunit.c | 9 +- lib/crypto/tests/hash-test-template.h | 58 +---------- lib/crypto/tests/mldsa_kunit.c | 19 ++-- lib/crypto/tests/nh_kunit.c | 5 +- lib/crypto/tests/sha256_kunit.c | 21 +--- lib/crypto/tests/test-utils.h | 111 ++++++++++++++++++++++ 6 files changed, 124 insertions(+), 99 deletions(-) create mode 100644 lib/crypto/tests/test-utils.h diff --git a/lib/crypto/tests/chacha20poly1305_kunit.c b/lib/crypto/tests/chacha20poly1305_kunit.c index 97a68fab88a7..d5504d0f4ad7 100644 --- a/lib/crypto/tests/chacha20poly1305_kunit.c +++ b/lib/crypto/tests/chacha20poly1305_kunit.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include "test-utils.h" struct chacha20poly1305_testvec { const u8 *input, *output, *assoc, *nonce, *key; @@ -8890,11 +8890,8 @@ static void test_chacha20poly1305(struct kunit *test) bool ret; struct scatterlist sg_src[3]; - computed_output = kunit_kmalloc(test, MAXIMUM_TEST_BUFFER_LEN, - GFP_KERNEL); - input = kunit_kmalloc(test, MAXIMUM_TEST_BUFFER_LEN, GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, computed_output); - KUNIT_ASSERT_NOT_NULL(test, input); + computed_output = alloc_buf(test, MAXIMUM_TEST_BUFFER_LEN); + input = alloc_buf(test, MAXIMUM_TEST_BUFFER_LEN); for (i = 0; i < ARRAY_SIZE(chacha20poly1305_enc_vectors); ++i) { memset(computed_output, 0, MAXIMUM_TEST_BUFFER_LEN); diff --git a/lib/crypto/tests/hash-test-template.h b/lib/crypto/tests/hash-test-template.h index 61b43e62779f..e6ab1dc8f9d7 100644 --- a/lib/crypto/tests/hash-test-template.h +++ b/lib/crypto/tests/hash-test-template.h @@ -8,69 +8,13 @@ #include #include #include +#include "test-utils.h" /* test_buf is a guarded buffer, i.e. &test_buf[TEST_BUF_LEN] is not mapped. */ #define TEST_BUF_LEN 16384 static u8 *test_buf; - static u8 *orig_test_buf; -static u64 random_seed; - -/* - * This is a simple linear congruential generator. It is used only for testing, - * which does not require cryptographically secure random numbers. A hard-coded - * algorithm is used instead of so that it matches the - * algorithm used by the test vector generation script. This allows the input - * data in random test vectors to be concisely stored as just the seed. - */ -static u32 rand32(void) -{ - random_seed = (random_seed * 25214903917 + 11) & ((1ULL << 48) - 1); - return random_seed >> 16; -} - -static void rand_bytes(u8 *out, size_t len) -{ - for (size_t i = 0; i < len; i++) - out[i] = rand32(); -} - -static void rand_bytes_seeded_from_len(u8 *out, size_t len) -{ - random_seed = len; - rand_bytes(out, len); -} - -static bool rand_bool(void) -{ - return rand32() % 2; -} - -/* Generate a random length, preferring small lengths. */ -static size_t rand_length(size_t max_len) -{ - size_t len; - - switch (rand32() % 3) { - case 0: - len = rand32() % 128; - break; - case 1: - len = rand32() % 3072; - break; - default: - len = rand32(); - break; - } - return len % (max_len + 1); -} - -static size_t rand_offset(size_t max_offset) -{ - return min(rand32() % 128, max_offset); -} - static int hash_suite_init(struct kunit_suite *suite) { /* diff --git a/lib/crypto/tests/mldsa_kunit.c b/lib/crypto/tests/mldsa_kunit.c index 67f8f93e3dc6..ec64e10c97f2 100644 --- a/lib/crypto/tests/mldsa_kunit.c +++ b/lib/crypto/tests/mldsa_kunit.c @@ -8,6 +8,7 @@ #include #include #include +#include "test-utils.h" #define Q 8380417 /* The prime q = 2^23 - 2^13 + 1 */ @@ -60,14 +61,6 @@ static void do_mldsa_and_assert_success(struct kunit *test, KUNIT_ASSERT_EQ(test, err, 0); } -static u8 *kunit_kmemdup_or_fail(struct kunit *test, const u8 *src, size_t len) -{ - u8 *dst = kunit_kmalloc(test, len, GFP_KERNEL); - - KUNIT_ASSERT_NOT_NULL(test, dst); - return memcpy(dst, src, len); -} - /* * Test that changing coefficients in a valid signature's z vector results in * the following behavior from mldsa_verify(): @@ -83,7 +76,7 @@ static u8 *kunit_kmemdup_or_fail(struct kunit *test, const u8 *src, size_t len) static void test_mldsa_z_range(struct kunit *test, const struct mldsa_testvector *tv) { - u8 *sig = kunit_kmemdup_or_fail(test, tv->sig, tv->sig_len); + u8 *sig = memdup_buf(test, tv->sig, tv->sig_len); const int lambda = params[tv->alg].lambda; const s32 gamma1 = params[tv->alg].gamma1; const int beta = params[tv->alg].beta; @@ -146,7 +139,7 @@ static void test_mldsa_bad_hints(struct kunit *test, { const int omega = params[tv->alg].omega; const int k = params[tv->alg].k; - u8 *sig = kunit_kmemdup_or_fail(test, tv->sig, tv->sig_len); + u8 *sig = memdup_buf(test, tv->sig, tv->sig_len); /* Pointer to the encoded hint vector in the signature */ u8 *hintvec = &sig[tv->sig_len - omega - k]; u8 h; @@ -202,9 +195,9 @@ static void test_mldsa_mutation(struct kunit *test, const int msg_len = tv->msg_len; const int pk_len = tv->pk_len; const int num_iter = 200; - u8 *sig = kunit_kmemdup_or_fail(test, tv->sig, sig_len); - u8 *msg = kunit_kmemdup_or_fail(test, tv->msg, msg_len); - u8 *pk = kunit_kmemdup_or_fail(test, tv->pk, pk_len); + u8 *sig = memdup_buf(test, tv->sig, sig_len); + u8 *msg = memdup_buf(test, tv->msg, msg_len); + u8 *pk = memdup_buf(test, tv->pk, pk_len); /* Initially the signature is valid. */ do_mldsa_and_assert_success(test, tv); diff --git a/lib/crypto/tests/nh_kunit.c b/lib/crypto/tests/nh_kunit.c index a8a3c3f345cb..49e0fb3dd294 100644 --- a/lib/crypto/tests/nh_kunit.c +++ b/lib/crypto/tests/nh_kunit.c @@ -5,14 +5,13 @@ #include #include #include "nh-testvecs.h" +#include "test-utils.h" static void test_nh(struct kunit *test) { - u32 *key = kunit_kmalloc(test, NH_KEY_BYTES, GFP_KERNEL); + u32 *key = memdup_buf(test, nh_test_key, NH_KEY_BYTES); __le64 hash[NH_NUM_PASSES]; - KUNIT_ASSERT_NOT_NULL(test, key); - memcpy(key, nh_test_key, NH_KEY_BYTES); le32_to_cpu_array(key, NH_KEY_WORDS); nh(key, nh_test_msg, 16, hash); diff --git a/lib/crypto/tests/sha256_kunit.c b/lib/crypto/tests/sha256_kunit.c index 5dccdee79693..8758db3673c7 100644 --- a/lib/crypto/tests/sha256_kunit.c +++ b/lib/crypto/tests/sha256_kunit.c @@ -4,6 +4,7 @@ */ #include #include "sha256-testvecs.h" +#include "test-utils.h" /* Generate the HASH_KUNIT_CASES using hash-test-template.h. */ #define HASH sha256 @@ -22,26 +23,6 @@ #define HMAC_USINGRAWKEY hmac_sha256_usingrawkey #include "hash-test-template.h" -static void free_guarded_buf(void *buf) -{ - vfree(buf); -} - -/* - * Allocate a KUnit-managed buffer that has length @len bytes immediately - * followed by an unmapped page, and assert that the allocation succeeds. - */ -static void *alloc_guarded_buf(struct kunit *test, size_t len) -{ - size_t full_len = round_up(len, PAGE_SIZE); - void *buf = vmalloc(full_len); - - KUNIT_ASSERT_NOT_NULL(test, buf); - KUNIT_ASSERT_EQ(test, 0, - kunit_add_action_or_reset(test, free_guarded_buf, buf)); - return buf + full_len - len; -} - /* * Test for sha256_finup_2x(). Specifically, choose various data lengths and * salt lengths, and for each one, verify that sha256_finup_2x() produces the diff --git a/lib/crypto/tests/test-utils.h b/lib/crypto/tests/test-utils.h new file mode 100644 index 000000000000..73ca21f9176e --- /dev/null +++ b/lib/crypto/tests/test-utils.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Test utility functions shared by the crypto library tests. + * + * For now this is simply a header that's included into the KUnit test suites + * that need it. If this gets too large it could be made its own translation + * unit and libcrypto_test_utils module, but that seems overkill for now. + */ +#ifndef LIB_CRYPTO_TEST_UTILS_H +#define LIB_CRYPTO_TEST_UTILS_H + +#include +#include +#include +#include +#include + +static u64 random_seed; + +static __maybe_unused void action_free_guarded_buf(void *buf) +{ + vfree(buf); +} + +/* + * Allocate a KUnit-managed buffer that has length @size bytes (> 0) immediately + * followed by an unmapped page, and assert that the allocation succeeds. + */ +static __maybe_unused void *alloc_guarded_buf(struct kunit *test, size_t size) +{ + size_t full_size = round_up(size, PAGE_SIZE); + void *buf = vmalloc(full_size); + + KUNIT_ASSERT_NOT_NULL(test, buf); + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, action_free_guarded_buf, + buf)); + return buf + full_size - size; +} + +static __maybe_unused void *alloc_buf(struct kunit *test, size_t size) +{ + void *buf = kunit_kmalloc(test, size, GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, buf); + return buf; +} + +static __maybe_unused void *memdup_buf(struct kunit *test, const void *src, + size_t size) +{ + void *dst = alloc_buf(test, size); + + return memcpy(dst, src, size); +} + +/* + * This is a simple linear congruential generator. It is used only for testing, + * which does not require cryptographically secure random numbers. A hard-coded + * algorithm is used instead of so that it matches the + * algorithm used by the test vector generation script. This allows the input + * data in random test vectors to be concisely stored as just the seed. + */ +static __maybe_unused u32 rand32(void) +{ + random_seed = (random_seed * 25214903917 + 11) & ((1ULL << 48) - 1); + return random_seed >> 16; +} + +static __maybe_unused void rand_bytes(u8 *out, size_t len) +{ + for (size_t i = 0; i < len; i++) + out[i] = rand32(); +} + +static __maybe_unused void rand_bytes_seeded_from_len(u8 *out, size_t len) +{ + random_seed = len; + rand_bytes(out, len); +} + +static __maybe_unused bool rand_bool(void) +{ + return rand32() % 2; +} + +/* Generate a random length, preferring small lengths. */ +static __maybe_unused size_t rand_length(size_t max_len) +{ + size_t len; + + switch (rand32() % 3) { + case 0: + len = rand32() % 128; + break; + case 1: + len = rand32() % 3072; + break; + default: + len = rand32(); + break; + } + return len % (max_len + 1); +} + +static __maybe_unused size_t rand_offset(size_t max_offset) +{ + return min(rand32() % 128, max_offset); +} + +#endif /* LIB_CRYPTO_TEST_UTILS_H */ From 81dbcc3e0424982b4639f96149620e97cf0cddbf Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 2 Aug 2026 16:30:02 -0700 Subject: [PATCH 05/11] lib/crypto: tests: Use per-test-case buffers in hash tests Make the test cases more self-contained by replacing the test suite scoped guarded buffer ('test_buf') with buffers allocated by each test case using the helper functions in test-utils.h. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260802233005.161467-3-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/tests/aes_cbc_macs_kunit.c | 12 +- lib/crypto/tests/blake2b_kunit.c | 33 ++--- lib/crypto/tests/blake2s_kunit.c | 33 ++--- lib/crypto/tests/ghash_kunit.c | 37 +++-- lib/crypto/tests/hash-test-template.h | 191 +++++++++++++------------- lib/crypto/tests/md5_kunit.c | 2 - lib/crypto/tests/poly1305_kunit.c | 21 ++- lib/crypto/tests/polyval_kunit.c | 38 +++-- lib/crypto/tests/sha1_kunit.c | 2 - lib/crypto/tests/sha224_kunit.c | 2 - lib/crypto/tests/sha256_kunit.c | 27 ++-- lib/crypto/tests/sha384_kunit.c | 2 - lib/crypto/tests/sha3_kunit.c | 30 ++-- lib/crypto/tests/sha512_kunit.c | 2 - lib/crypto/tests/sm3_kunit.c | 2 - 15 files changed, 193 insertions(+), 241 deletions(-) diff --git a/lib/crypto/tests/aes_cbc_macs_kunit.c b/lib/crypto/tests/aes_cbc_macs_kunit.c index ae3745212f03..6afdb3a04c17 100644 --- a/lib/crypto/tests/aes_cbc_macs_kunit.c +++ b/lib/crypto/tests/aes_cbc_macs_kunit.c @@ -34,18 +34,9 @@ static void aes_cmac_withtestkey(const u8 *data, size_t data_len, static int aes_cbc_macs_suite_init(struct kunit_suite *suite) { u8 raw_key[AES_KEYSIZE_256]; - int err; rand_bytes_seeded_from_len(raw_key, sizeof(raw_key)); - err = aes_cmac_preparekey(&test_key, raw_key, sizeof(raw_key)); - if (err) - return err; - return hash_suite_init(suite); -} - -static void aes_cbc_macs_suite_exit(struct kunit_suite *suite) -{ - hash_suite_exit(suite); + return aes_cmac_preparekey(&test_key, raw_key, sizeof(raw_key)); } /* Verify compatibility of the AES-CMAC implementation with RFC 4493. */ @@ -218,7 +209,6 @@ static struct kunit_suite aes_cbc_macs_test_suite = { .name = "aes_cbc_macs", .test_cases = aes_cbc_macs_test_cases, .suite_init = aes_cbc_macs_suite_init, - .suite_exit = aes_cbc_macs_suite_exit, }; kunit_test_suite(aes_cbc_macs_test_suite); diff --git a/lib/crypto/tests/blake2b_kunit.c b/lib/crypto/tests/blake2b_kunit.c index bc0be7da1e76..e2ce6cf3a99d 100644 --- a/lib/crypto/tests/blake2b_kunit.c +++ b/lib/crypto/tests/blake2b_kunit.c @@ -41,9 +41,9 @@ static void blake2b_init_default(struct blake2b_ctx *ctx) static void test_blake2b_all_key_and_hash_lens(struct kunit *test) { const size_t data_len = 100; - u8 *data = &test_buf[0]; - u8 *key = data + data_len; - u8 *hash = key + BLAKE2B_KEY_SIZE; + u8 *data = alloc_buf(test, data_len); + u8 *key = alloc_buf(test, BLAKE2B_KEY_SIZE); + u8 *hash = alloc_buf(test, BLAKE2B_HASH_SIZE); struct blake2b_ctx main_ctx; u8 main_hash[BLAKE2B_HASH_SIZE]; @@ -68,11 +68,13 @@ static void test_blake2b_all_key_and_hash_lens(struct kunit *test) static void test_blake2b_with_guarded_key_buf(struct kunit *test) { const size_t data_len = 100; + u8 *data = alloc_buf(test, data_len); + u8 *guarded_key_buf = alloc_guarded_buf(test, BLAKE2B_KEY_SIZE); - rand_bytes(test_buf, data_len); + rand_bytes(data, data_len); for (int key_len = 0; key_len <= BLAKE2B_KEY_SIZE; key_len++) { u8 key[BLAKE2B_KEY_SIZE]; - u8 *guarded_key = &test_buf[TEST_BUF_LEN - key_len]; + u8 *guarded_key = &guarded_key_buf[BLAKE2B_KEY_SIZE - key_len]; u8 hash1[BLAKE2B_HASH_SIZE]; u8 hash2[BLAKE2B_HASH_SIZE]; struct blake2b_ctx ctx; @@ -80,14 +82,13 @@ static void test_blake2b_with_guarded_key_buf(struct kunit *test) rand_bytes(key, key_len); memcpy(guarded_key, key, key_len); - blake2b(key, key_len, test_buf, data_len, - hash1, BLAKE2B_HASH_SIZE); - blake2b(guarded_key, key_len, test_buf, data_len, - hash2, BLAKE2B_HASH_SIZE); + blake2b(key, key_len, data, data_len, hash1, BLAKE2B_HASH_SIZE); + blake2b(guarded_key, key_len, data, data_len, hash2, + BLAKE2B_HASH_SIZE); KUNIT_ASSERT_MEMEQ(test, hash1, hash2, BLAKE2B_HASH_SIZE); blake2b_init_key(&ctx, BLAKE2B_HASH_SIZE, guarded_key, key_len); - blake2b_update(&ctx, test_buf, data_len); + blake2b_update(&ctx, data, data_len); blake2b_final(&ctx, hash2); KUNIT_ASSERT_MEMEQ(test, hash1, hash2, BLAKE2B_HASH_SIZE); } @@ -100,14 +101,16 @@ static void test_blake2b_with_guarded_key_buf(struct kunit *test) static void test_blake2b_with_guarded_out_buf(struct kunit *test) { const size_t data_len = 100; + u8 *data = alloc_buf(test, data_len); + u8 *out_buf = alloc_guarded_buf(test, BLAKE2B_HASH_SIZE); - rand_bytes(test_buf, data_len); + rand_bytes(data, data_len); for (int out_len = 1; out_len <= BLAKE2B_HASH_SIZE; out_len++) { u8 hash[BLAKE2B_HASH_SIZE]; - u8 *guarded_hash = &test_buf[TEST_BUF_LEN - out_len]; + u8 *guarded_hash = &out_buf[BLAKE2B_HASH_SIZE - out_len]; - blake2b(NULL, 0, test_buf, data_len, hash, out_len); - blake2b(NULL, 0, test_buf, data_len, guarded_hash, out_len); + blake2b(NULL, 0, data, data_len, hash, out_len); + blake2b(NULL, 0, data, data_len, guarded_hash, out_len); KUNIT_ASSERT_MEMEQ(test, hash, guarded_hash, out_len); } } @@ -124,8 +127,6 @@ static struct kunit_case blake2b_test_cases[] = { static struct kunit_suite blake2b_test_suite = { .name = "blake2b", .test_cases = blake2b_test_cases, - .suite_init = hash_suite_init, - .suite_exit = hash_suite_exit, }; kunit_test_suite(blake2b_test_suite); diff --git a/lib/crypto/tests/blake2s_kunit.c b/lib/crypto/tests/blake2s_kunit.c index 6832d9aa7b82..682f695f8a09 100644 --- a/lib/crypto/tests/blake2s_kunit.c +++ b/lib/crypto/tests/blake2s_kunit.c @@ -41,9 +41,9 @@ static void blake2s_init_default(struct blake2s_ctx *ctx) static void test_blake2s_all_key_and_hash_lens(struct kunit *test) { const size_t data_len = 100; - u8 *data = &test_buf[0]; - u8 *key = data + data_len; - u8 *hash = key + BLAKE2S_KEY_SIZE; + u8 *data = alloc_buf(test, data_len); + u8 *key = alloc_buf(test, BLAKE2S_KEY_SIZE); + u8 *hash = alloc_buf(test, BLAKE2S_HASH_SIZE); struct blake2s_ctx main_ctx; u8 main_hash[BLAKE2S_HASH_SIZE]; @@ -68,11 +68,13 @@ static void test_blake2s_all_key_and_hash_lens(struct kunit *test) static void test_blake2s_with_guarded_key_buf(struct kunit *test) { const size_t data_len = 100; + u8 *data = alloc_buf(test, data_len); + u8 *guarded_key_buf = alloc_guarded_buf(test, BLAKE2S_KEY_SIZE); - rand_bytes(test_buf, data_len); + rand_bytes(data, data_len); for (int key_len = 0; key_len <= BLAKE2S_KEY_SIZE; key_len++) { u8 key[BLAKE2S_KEY_SIZE]; - u8 *guarded_key = &test_buf[TEST_BUF_LEN - key_len]; + u8 *guarded_key = &guarded_key_buf[BLAKE2S_KEY_SIZE - key_len]; u8 hash1[BLAKE2S_HASH_SIZE]; u8 hash2[BLAKE2S_HASH_SIZE]; struct blake2s_ctx ctx; @@ -80,14 +82,13 @@ static void test_blake2s_with_guarded_key_buf(struct kunit *test) rand_bytes(key, key_len); memcpy(guarded_key, key, key_len); - blake2s(key, key_len, test_buf, data_len, - hash1, BLAKE2S_HASH_SIZE); - blake2s(guarded_key, key_len, test_buf, data_len, - hash2, BLAKE2S_HASH_SIZE); + blake2s(key, key_len, data, data_len, hash1, BLAKE2S_HASH_SIZE); + blake2s(guarded_key, key_len, data, data_len, hash2, + BLAKE2S_HASH_SIZE); KUNIT_ASSERT_MEMEQ(test, hash1, hash2, BLAKE2S_HASH_SIZE); blake2s_init_key(&ctx, BLAKE2S_HASH_SIZE, guarded_key, key_len); - blake2s_update(&ctx, test_buf, data_len); + blake2s_update(&ctx, data, data_len); blake2s_final(&ctx, hash2); KUNIT_ASSERT_MEMEQ(test, hash1, hash2, BLAKE2S_HASH_SIZE); } @@ -100,14 +101,16 @@ static void test_blake2s_with_guarded_key_buf(struct kunit *test) static void test_blake2s_with_guarded_out_buf(struct kunit *test) { const size_t data_len = 100; + u8 *data = alloc_buf(test, data_len); + u8 *out_buf = alloc_guarded_buf(test, BLAKE2S_HASH_SIZE); - rand_bytes(test_buf, data_len); + rand_bytes(data, data_len); for (int out_len = 1; out_len <= BLAKE2S_HASH_SIZE; out_len++) { u8 hash[BLAKE2S_HASH_SIZE]; - u8 *guarded_hash = &test_buf[TEST_BUF_LEN - out_len]; + u8 *guarded_hash = &out_buf[BLAKE2S_HASH_SIZE - out_len]; - blake2s(NULL, 0, test_buf, data_len, hash, out_len); - blake2s(NULL, 0, test_buf, data_len, guarded_hash, out_len); + blake2s(NULL, 0, data, data_len, hash, out_len); + blake2s(NULL, 0, data, data_len, guarded_hash, out_len); KUNIT_ASSERT_MEMEQ(test, hash, guarded_hash, out_len); } } @@ -124,8 +127,6 @@ static struct kunit_case blake2s_test_cases[] = { static struct kunit_suite blake2s_test_suite = { .name = "blake2s", .test_cases = blake2s_test_cases, - .suite_init = hash_suite_init, - .suite_exit = hash_suite_exit, }; kunit_test_suite(blake2s_test_suite); diff --git a/lib/crypto/tests/ghash_kunit.c b/lib/crypto/tests/ghash_kunit.c index 68b3837a3607..4d7aa79b3cb2 100644 --- a/lib/crypto/tests/ghash_kunit.c +++ b/lib/crypto/tests/ghash_kunit.c @@ -39,17 +39,18 @@ static void ghash_withtestkey(const u8 *data, size_t len, */ static void test_ghash_allones_key_and_message(struct kunit *test) { + const size_t max_len = 4096; + u8 *data = alloc_buf(test, max_len); struct ghash_key key; struct ghash_ctx hashofhashes_ctx; u8 hash[GHASH_BLOCK_SIZE]; - static_assert(TEST_BUF_LEN >= 4096); - memset(test_buf, 0xff, 4096); + memset(data, 0xff, max_len); - ghash_preparekey(&key, test_buf); + ghash_preparekey(&key, data); ghash_init(&hashofhashes_ctx, &key); - for (size_t len = 0; len <= 4096; len += 16) { - ghash(&key, test_buf, len, hash); + for (size_t len = 0; len <= max_len; len += 16) { + ghash(&key, data, len, hash); ghash_update(&hashofhashes_ctx, hash, sizeof(hash)); } ghash_final(&hashofhashes_ctx, hash); @@ -68,7 +69,7 @@ static void check_key_consistency(struct kunit *test, const struct ghash_key *key1, const struct ghash_key *key2) { - u8 *data = test_buf; + u8 *data = alloc_buf(test, MAX_LEN_FOR_KEY_CHECK); u8 hash1[GHASH_BLOCK_SIZE]; u8 hash2[GHASH_BLOCK_SIZE]; @@ -88,10 +89,10 @@ static void check_key_consistency(struct kunit *test, static void test_ghash_with_guarded_key(struct kunit *test) { u8 raw_key[GHASH_BLOCK_SIZE]; - u8 *guarded_raw_key = &test_buf[TEST_BUF_LEN - sizeof(raw_key)]; + u8 *guarded_raw_key = alloc_guarded_buf(test, sizeof(raw_key)); struct ghash_key key1, key2; struct ghash_key *guarded_key = - (struct ghash_key *)&test_buf[TEST_BUF_LEN - sizeof(key1)]; + alloc_guarded_buf(test, sizeof(*guarded_key)); /* Prepare with regular buffers. */ rand_bytes(raw_key, sizeof(raw_key)); @@ -116,14 +117,14 @@ static void test_ghash_with_minimally_aligned_key(struct kunit *test) { u8 raw_key[GHASH_BLOCK_SIZE]; struct ghash_key key; + const size_t align = __alignof__(struct ghash_key); + u8 *key_buf = alloc_buf(test, sizeof(struct ghash_key) + 3 * align); struct ghash_key *minaligned_key = - (struct ghash_key *)&test_buf[MAX_LEN_FOR_KEY_CHECK + - __alignof__(struct ghash_key)]; + (struct ghash_key *)(PTR_ALIGN(key_buf, 2 * align) + align); - KUNIT_ASSERT_TRUE(test, IS_ALIGNED((uintptr_t)minaligned_key, - __alignof__(struct ghash_key))); - KUNIT_ASSERT_TRUE(test, !IS_ALIGNED((uintptr_t)minaligned_key, - 2 * __alignof__(struct ghash_key))); + KUNIT_ASSERT_TRUE(test, IS_ALIGNED((uintptr_t)minaligned_key, align)); + KUNIT_ASSERT_TRUE(test, + !IS_ALIGNED((uintptr_t)minaligned_key, 2 * align)); rand_bytes(raw_key, sizeof(raw_key)); ghash_preparekey(&key, raw_key); @@ -164,12 +165,7 @@ static int ghash_suite_init(struct kunit_suite *suite) rand_bytes_seeded_from_len(raw_key, sizeof(raw_key)); ghash_preparekey(&test_key, raw_key); - return hash_suite_init(suite); -} - -static void ghash_suite_exit(struct kunit_suite *suite) -{ - hash_suite_exit(suite); + return 0; } static struct kunit_case ghash_test_cases[] = { @@ -186,7 +182,6 @@ static struct kunit_suite ghash_test_suite = { .name = "ghash", .test_cases = ghash_test_cases, .suite_init = ghash_suite_init, - .suite_exit = ghash_suite_exit, }; kunit_test_suite(ghash_test_suite); diff --git a/lib/crypto/tests/hash-test-template.h b/lib/crypto/tests/hash-test-template.h index e6ab1dc8f9d7..bb6eaa509d0f 100644 --- a/lib/crypto/tests/hash-test-template.h +++ b/lib/crypto/tests/hash-test-template.h @@ -7,38 +7,8 @@ */ #include #include -#include #include "test-utils.h" -/* test_buf is a guarded buffer, i.e. &test_buf[TEST_BUF_LEN] is not mapped. */ -#define TEST_BUF_LEN 16384 -static u8 *test_buf; -static u8 *orig_test_buf; - -static int hash_suite_init(struct kunit_suite *suite) -{ - /* - * Allocate the test buffer using vmalloc() with a page-aligned length - * so that it is immediately followed by a guard page. This allows - * buffer overreads to be detected, even in assembly code. - */ - size_t alloc_len = round_up(TEST_BUF_LEN, PAGE_SIZE); - - orig_test_buf = vmalloc(alloc_len); - if (!orig_test_buf) - return -ENOMEM; - - test_buf = orig_test_buf + alloc_len - TEST_BUF_LEN; - return 0; -} - -static void hash_suite_exit(struct kunit_suite *suite) -{ - vfree(orig_test_buf); - orig_test_buf = NULL; - test_buf = NULL; -} - /* * Test the hash function against a list of test vectors. * @@ -48,14 +18,16 @@ static void hash_suite_exit(struct kunit_suite *suite) */ static void test_hash_test_vectors(struct kunit *test) { + const size_t max_len = 16384; + u8 *data = alloc_buf(test, max_len); + for (size_t i = 0; i < ARRAY_SIZE(hash_testvecs); i++) { size_t data_len = hash_testvecs[i].data_len; u8 actual_hash[HASH_SIZE]; - KUNIT_ASSERT_LE(test, data_len, TEST_BUF_LEN); - rand_bytes_seeded_from_len(test_buf, data_len); - - HASH(test_buf, data_len, actual_hash); + KUNIT_ASSERT_LE(test, data_len, max_len); + rand_bytes_seeded_from_len(data, data_len); + HASH(data, data_len, actual_hash); KUNIT_ASSERT_MEMEQ_MSG( test, actual_hash, hash_testvecs[i].digest, HASH_SIZE, "Wrong result with test vector %zu; data_len=%zu", i, @@ -71,14 +43,15 @@ static void test_hash_test_vectors(struct kunit *test) */ static void test_hash_all_lens_up_to_4096(struct kunit *test) { + const size_t max_len = 4096; + u8 *data = alloc_buf(test, max_len); struct HASH_CTX ctx; u8 hash[HASH_SIZE]; - static_assert(TEST_BUF_LEN >= 4096); - rand_bytes_seeded_from_len(test_buf, 4096); + rand_bytes_seeded_from_len(data, max_len); HASH_INIT(&ctx); - for (size_t len = 0; len <= 4096; len++) { - HASH(test_buf, len, hash); + for (size_t len = 0; len <= max_len; len++) { + HASH(data, len, hash); HASH_UPDATE(&ctx, hash, HASH_SIZE); } HASH_FINAL(&ctx, hash); @@ -91,6 +64,9 @@ static void test_hash_all_lens_up_to_4096(struct kunit *test) */ static void test_hash_incremental_updates(struct kunit *test) { + const size_t max_len = 16384; + u8 *data = alloc_guarded_buf(test, max_len); + for (int i = 0; i < 1000; i++) { size_t total_len, offset; struct HASH_CTX ctx; @@ -99,12 +75,12 @@ static void test_hash_incremental_updates(struct kunit *test) size_t num_parts = 0; size_t remaining_len, cur_offset; - total_len = rand_length(TEST_BUF_LEN); - offset = rand_offset(TEST_BUF_LEN - total_len); - rand_bytes(&test_buf[offset], total_len); + total_len = rand_length(max_len); + offset = rand_offset(max_len - total_len); + rand_bytes(&data[offset], total_len); /* Compute the hash value in one shot. */ - HASH(&test_buf[offset], total_len, hash1); + HASH(&data[offset], total_len, hash1); /* * Compute the hash value incrementally, using a randomly @@ -116,13 +92,13 @@ static void test_hash_incremental_updates(struct kunit *test) while (rand_bool()) { size_t part_len = rand_length(remaining_len); - HASH_UPDATE(&ctx, &test_buf[cur_offset], part_len); + HASH_UPDATE(&ctx, &data[cur_offset], part_len); num_parts++; cur_offset += part_len; remaining_len -= part_len; } if (remaining_len != 0 || rand_bool()) { - HASH_UPDATE(&ctx, &test_buf[cur_offset], remaining_len); + HASH_UPDATE(&ctx, &data[cur_offset], remaining_len); num_parts++; } HASH_FINAL(&ctx, hash2); @@ -141,11 +117,13 @@ static void test_hash_incremental_updates(struct kunit *test) */ static void test_hash_buffer_overruns(struct kunit *test) { - const size_t max_tested_len = TEST_BUF_LEN - sizeof(struct HASH_CTX); - void *const buf_end = &test_buf[TEST_BUF_LEN]; + const size_t buf_len = 16384; + u8 *buf = alloc_guarded_buf(test, buf_len); + void *const buf_end = &buf[buf_len]; + const size_t max_tested_len = buf_len - sizeof(struct HASH_CTX); struct HASH_CTX *guarded_ctx = buf_end - sizeof(*guarded_ctx); - rand_bytes(test_buf, TEST_BUF_LEN); + rand_bytes(buf, buf_len); for (int i = 0; i < 100; i++) { size_t len = rand_length(max_tested_len); @@ -159,14 +137,14 @@ static void test_hash_buffer_overruns(struct kunit *test) HASH_FINAL(&ctx, hash); /* Check for overruns of the hash value buffer. */ - HASH(test_buf, len, buf_end - HASH_SIZE); + HASH(buf, len, buf_end - HASH_SIZE); HASH_INIT(&ctx); - HASH_UPDATE(&ctx, test_buf, len); + HASH_UPDATE(&ctx, buf, len); HASH_FINAL(&ctx, buf_end - HASH_SIZE); - /* Check for overuns of the hash context. */ + /* Check for overruns of the hash context. */ HASH_INIT(guarded_ctx); - HASH_UPDATE(guarded_ctx, test_buf, len); + HASH_UPDATE(guarded_ctx, buf, len); HASH_FINAL(guarded_ctx, hash); } } @@ -177,30 +155,32 @@ static void test_hash_buffer_overruns(struct kunit *test) */ static void test_hash_overlaps(struct kunit *test) { - const size_t max_tested_len = TEST_BUF_LEN - HASH_SIZE; + const size_t buf_len = 16384; + u8 *buf = alloc_guarded_buf(test, buf_len); + const size_t max_tested_len = buf_len - HASH_SIZE; struct HASH_CTX ctx; u8 hash[HASH_SIZE]; - rand_bytes(test_buf, TEST_BUF_LEN); + rand_bytes(buf, buf_len); for (int i = 0; i < 100; i++) { size_t len = rand_length(max_tested_len); size_t offset = HASH_SIZE + rand_offset(max_tested_len - len); bool left_end = rand_bool(); - u8 *ovl_hash = left_end ? &test_buf[offset] : - &test_buf[offset + len - HASH_SIZE]; + u8 *ovl_hash = left_end ? &buf[offset] : + &buf[offset + len - HASH_SIZE]; - HASH(&test_buf[offset], len, hash); - HASH(&test_buf[offset], len, ovl_hash); + HASH(&buf[offset], len, hash); + HASH(&buf[offset], len, ovl_hash); KUNIT_ASSERT_MEMEQ_MSG( test, hash, ovl_hash, HASH_SIZE, "Overlap test 1 failed with len=%zu offset=%zu left_end=%d", len, offset, left_end); /* Repeat the above test, but this time use init+update+final */ - HASH(&test_buf[offset], len, hash); + HASH(&buf[offset], len, hash); HASH_INIT(&ctx); - HASH_UPDATE(&ctx, &test_buf[offset], len); + HASH_UPDATE(&ctx, &buf[offset], len); HASH_FINAL(&ctx, ovl_hash); KUNIT_ASSERT_MEMEQ_MSG( test, hash, ovl_hash, HASH_SIZE, @@ -208,10 +188,10 @@ static void test_hash_overlaps(struct kunit *test) len, offset, left_end); /* Test modifying the source data after it was used. */ - HASH(&test_buf[offset], len, hash); + HASH(&buf[offset], len, hash); HASH_INIT(&ctx); - HASH_UPDATE(&ctx, &test_buf[offset], len); - rand_bytes(&test_buf[offset], len); + HASH_UPDATE(&ctx, &buf[offset], len); + rand_bytes(&buf[offset], len); HASH_FINAL(&ctx, ovl_hash); KUNIT_ASSERT_MEMEQ_MSG( test, hash, ovl_hash, HASH_SIZE, @@ -226,20 +206,22 @@ static void test_hash_overlaps(struct kunit *test) */ static void test_hash_alignment_consistency(struct kunit *test) { + const size_t max_len = 16384; + u8 *data = alloc_guarded_buf(test, max_len); u8 hash1[128 + HASH_SIZE]; u8 hash2[128 + HASH_SIZE]; for (int i = 0; i < 100; i++) { - size_t len = rand_length(TEST_BUF_LEN); - size_t data_offs1 = rand_offset(TEST_BUF_LEN - len); - size_t data_offs2 = rand_offset(TEST_BUF_LEN - len); + size_t len = rand_length(max_len); + size_t data_offs1 = rand_offset(max_len - len); + size_t data_offs2 = rand_offset(max_len - len); size_t hash_offs1 = rand_offset(128); size_t hash_offs2 = rand_offset(128); - rand_bytes(&test_buf[data_offs1], len); - HASH(&test_buf[data_offs1], len, &hash1[hash_offs1]); - memmove(&test_buf[data_offs2], &test_buf[data_offs1], len); - HASH(&test_buf[data_offs2], len, &hash2[hash_offs2]); + rand_bytes(&data[data_offs1], len); + HASH(&data[data_offs1], len, &hash1[hash_offs1]); + memmove(&data[data_offs2], &data[data_offs1], len); + HASH(&data[data_offs2], len, &hash2[hash_offs2]); KUNIT_ASSERT_MEMEQ_MSG( test, &hash1[hash_offs1], &hash2[hash_offs2], HASH_SIZE, "Alignment consistency test failed with len=%zu data_offs=(%zu,%zu) hash_offs=(%zu,%zu)", @@ -252,11 +234,14 @@ static void test_hash_ctx_zeroization(struct kunit *test) { static const u8 zeroes[sizeof(struct HASH_CTX)]; struct HASH_CTX ctx; + const size_t data_len = 128; + u8 *data = alloc_buf(test, data_len); + u8 hash[HASH_SIZE]; - rand_bytes(test_buf, 128); + rand_bytes(data, data_len); HASH_INIT(&ctx); - HASH_UPDATE(&ctx, test_buf, 128); - HASH_FINAL(&ctx, test_buf); + HASH_UPDATE(&ctx, data, data_len); + HASH_FINAL(&ctx, hash); KUNIT_ASSERT_MEMEQ_MSG(test, &ctx, zeroes, sizeof(ctx), "Hash context was not zeroized by finalization"); } @@ -265,6 +250,7 @@ static void test_hash_ctx_zeroization(struct kunit *test) #define IRQ_TEST_NUM_BUFFERS 3 /* matches max concurrency level */ struct hash_irq_test1_state { + u8 *data; u8 expected_hashes[IRQ_TEST_NUM_BUFFERS][HASH_SIZE]; atomic_t seqno; }; @@ -280,7 +266,8 @@ static bool hash_irq_test1_func(void *state_) u32 i = (u32)atomic_inc_return(&state->seqno) % IRQ_TEST_NUM_BUFFERS; u8 actual_hash[HASH_SIZE]; - HASH(&test_buf[i * IRQ_TEST_DATA_LEN], IRQ_TEST_DATA_LEN, actual_hash); + HASH(&state->data[i * IRQ_TEST_DATA_LEN], IRQ_TEST_DATA_LEN, + actual_hash); return memcmp(actual_hash, state->expected_hashes[i], HASH_SIZE) == 0; } @@ -290,12 +277,14 @@ static bool hash_irq_test1_func(void *state_) */ static void test_hash_interrupt_context_1(struct kunit *test) { + const size_t total_data_len = IRQ_TEST_NUM_BUFFERS * IRQ_TEST_DATA_LEN; struct hash_irq_test1_state state = {}; /* Prepare some test messages and compute the expected hash of each. */ - rand_bytes(test_buf, IRQ_TEST_NUM_BUFFERS * IRQ_TEST_DATA_LEN); + state.data = alloc_buf(test, total_data_len); + rand_bytes(state.data, total_data_len); for (int i = 0; i < IRQ_TEST_NUM_BUFFERS; i++) - HASH(&test_buf[i * IRQ_TEST_DATA_LEN], IRQ_TEST_DATA_LEN, + HASH(&state.data[i * IRQ_TEST_DATA_LEN], IRQ_TEST_DATA_LEN, state.expected_hashes[i]); kunit_run_irq_test(test, hash_irq_test1_func, 100000, &state); @@ -309,6 +298,8 @@ struct hash_irq_test2_hash_ctx { }; struct hash_irq_test2_state { + u8 *data; + size_t data_len; struct hash_irq_test2_hash_ctx ctxs[IRQ_TEST_NUM_BUFFERS]; u8 expected_hash[HASH_SIZE]; u16 update_lens[32]; @@ -341,7 +332,7 @@ static bool hash_irq_test2_func(void *state_) ctx->step++; } else if (ctx->step < state->num_steps - 1) { /* Update step */ - HASH_UPDATE(&ctx->hash_ctx, &test_buf[ctx->offset], + HASH_UPDATE(&ctx->hash_ctx, &state->data[ctx->offset], state->update_lens[ctx->step - 1]); ctx->offset += state->update_lens[ctx->step - 1]; ctx->step++; @@ -349,7 +340,7 @@ static bool hash_irq_test2_func(void *state_) /* Final step */ u8 actual_hash[HASH_SIZE]; - if (WARN_ON_ONCE(ctx->offset != TEST_BUF_LEN)) + if (WARN_ON_ONCE(ctx->offset != state->data_len)) ret = false; HASH_FINAL(&ctx->hash_ctx, actual_hash); if (memcmp(actual_hash, state->expected_hash, HASH_SIZE) != 0) @@ -370,20 +361,23 @@ static bool hash_irq_test2_func(void *state_) */ static void test_hash_interrupt_context_2(struct kunit *test) { + const size_t data_len = 16384; struct hash_irq_test2_state *state; - int remaining = TEST_BUF_LEN; + size_t remaining = data_len; state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, state); + state->data_len = data_len; + state->data = alloc_buf(test, data_len); - rand_bytes(test_buf, TEST_BUF_LEN); - HASH(test_buf, TEST_BUF_LEN, state->expected_hash); + rand_bytes(state->data, data_len); + HASH(state->data, data_len, state->expected_hash); /* * Generate a list of update lengths to use. Ensure that it contains * multiple entries but is limited to a maximum length. */ - static_assert(TEST_BUF_LEN / 4096 > 1); + KUNIT_ASSERT_GT(test, data_len / 4096, 1); for (state->num_steps = 0; state->num_steps < ARRAY_SIZE(state->update_lens) - 1 && remaining; state->num_steps++) { @@ -429,21 +423,23 @@ static void test_hash_interrupt_context_2(struct kunit *test) */ static void test_hmac(struct kunit *test) { + const size_t max_data_len = 4096; + const size_t max_key_len = 293; + const size_t outer_key_len = 32; + u8 *data = alloc_guarded_buf(test, max_data_len); + u8 *raw_key = alloc_guarded_buf(test, max_key_len); static const u8 zeroes[sizeof(struct HMAC_CTX)]; - u8 *raw_key; struct HMAC_KEY key; struct HMAC_CTX ctx; u8 mac[HASH_SIZE]; u8 mac2[HASH_SIZE]; - static_assert(TEST_BUF_LEN >= 4096 + 293); - rand_bytes_seeded_from_len(test_buf, 4096); - raw_key = &test_buf[4096]; + rand_bytes_seeded_from_len(data, max_data_len); + rand_bytes_seeded_from_len(raw_key, outer_key_len); - rand_bytes_seeded_from_len(raw_key, 32); - HMAC_PREPAREKEY(&key, raw_key, 32); + HMAC_PREPAREKEY(&key, raw_key, outer_key_len); HMAC_INIT(&ctx, &key); - for (size_t data_len = 0; data_len <= 4096; data_len++) { + for (size_t data_len = 0; data_len <= max_data_len; data_len++) { /* * Cycle through key lengths as well. Somewhat arbitrarily go * up to 293, which is somewhat larger than the largest hash @@ -451,17 +447,17 @@ static void test_hmac(struct kunit *test) * hashed down to one block); going higher would not be useful. * To reduce correlation with data_len, use a prime number here. */ - size_t key_len = data_len % 293; + size_t key_len = data_len % max_key_len; - HMAC_UPDATE(&ctx, test_buf, data_len); + HMAC_UPDATE(&ctx, data, data_len); rand_bytes_seeded_from_len(raw_key, key_len); - HMAC_USINGRAWKEY(raw_key, key_len, test_buf, data_len, mac); + HMAC_USINGRAWKEY(raw_key, key_len, data, data_len, mac); HMAC_UPDATE(&ctx, mac, HASH_SIZE); /* Verify that HMAC() is consistent with HMAC_USINGRAWKEY(). */ HMAC_PREPAREKEY(&key, raw_key, key_len); - HMAC(&key, test_buf, data_len, mac2); + HMAC(&key, data, data_len, mac2); KUNIT_ASSERT_MEMEQ_MSG( test, mac, mac2, HASH_SIZE, "HMAC gave different results with raw and prepared keys"); @@ -484,14 +480,17 @@ static void benchmark_hash(struct kunit *test) 1, 16, 64, 127, 128, 200, 256, 511, 512, 1024, 3173, 4096, 16384, }; + const size_t max_len = 16384; + u8 *data = alloc_buf(test, max_len); u8 hash[HASH_SIZE]; if (!IS_ENABLED(CONFIG_CRYPTO_LIB_BENCHMARK)) kunit_skip(test, "not enabled"); /* Warm-up */ - for (size_t i = 0; i < 10000000; i += TEST_BUF_LEN) - HASH(test_buf, TEST_BUF_LEN, hash); + memset(data, 0, max_len); + for (size_t i = 0; i < 10000000; i += max_len) + HASH(data, max_len, hash); for (size_t i = 0; i < ARRAY_SIZE(lens_to_test); i++) { size_t len = lens_to_test[i]; @@ -499,11 +498,11 @@ static void benchmark_hash(struct kunit *test) size_t num_iters = 10000000 / (len + 128); u64 t; - KUNIT_ASSERT_LE(test, len, TEST_BUF_LEN); + KUNIT_ASSERT_LE(test, len, max_len); preempt_disable(); t = ktime_get_ns(); for (size_t j = 0; j < num_iters; j++) - HASH(test_buf, len, hash); + HASH(data, len, hash); t = ktime_get_ns() - t; preempt_enable(); kunit_info(test, "len=%zu: %llu MB/s", len, diff --git a/lib/crypto/tests/md5_kunit.c b/lib/crypto/tests/md5_kunit.c index 38bd52c25ae3..1598f8585b45 100644 --- a/lib/crypto/tests/md5_kunit.c +++ b/lib/crypto/tests/md5_kunit.c @@ -30,8 +30,6 @@ static struct kunit_case hash_test_cases[] = { static struct kunit_suite hash_test_suite = { .name = "md5", .test_cases = hash_test_cases, - .suite_init = hash_suite_init, - .suite_exit = hash_suite_exit, }; kunit_test_suite(hash_test_suite); diff --git a/lib/crypto/tests/poly1305_kunit.c b/lib/crypto/tests/poly1305_kunit.c index 7ac191bd96b6..f3cb6245bc29 100644 --- a/lib/crypto/tests/poly1305_kunit.c +++ b/lib/crypto/tests/poly1305_kunit.c @@ -46,12 +46,7 @@ static void poly1305_withtestkey(const u8 *data, size_t len, static int poly1305_suite_init(struct kunit_suite *suite) { rand_bytes_seeded_from_len(test_key, POLY1305_KEY_SIZE); - return hash_suite_init(suite); -} - -static void poly1305_suite_exit(struct kunit_suite *suite) -{ - hash_suite_exit(suite); + return 0; } /* @@ -81,19 +76,20 @@ static void poly1305_suite_exit(struct kunit_suite *suite) */ static void test_poly1305_allones_keys_and_message(struct kunit *test) { + const size_t max_len = 4096; + u8 *data = alloc_buf(test, max_len); struct poly1305_desc_ctx mac_ctx, macofmacs_ctx; u8 mac[POLY1305_DIGEST_SIZE]; - static_assert(TEST_BUF_LEN >= 4096); - memset(test_buf, 0xff, 4096); + memset(data, 0xff, max_len); - poly1305_init(&mac_ctx, test_buf); - poly1305_init(&macofmacs_ctx, test_buf); + poly1305_init(&mac_ctx, data); + poly1305_init(&macofmacs_ctx, data); for (int i = 0; i < 32; i++) { - for (size_t len = 0; len <= 4096; len += 16) { + for (size_t len = 0; len <= max_len; len += 16) { struct poly1305_desc_ctx tmp_ctx; - poly1305_update(&mac_ctx, test_buf, len); + poly1305_update(&mac_ctx, data, len); tmp_ctx = mac_ctx; poly1305_final(&tmp_ctx, mac); poly1305_update(&macofmacs_ctx, mac, @@ -157,7 +153,6 @@ static struct kunit_suite poly1305_test_suite = { .name = "poly1305", .test_cases = poly1305_test_cases, .suite_init = poly1305_suite_init, - .suite_exit = poly1305_suite_exit, }; kunit_test_suite(poly1305_test_suite); diff --git a/lib/crypto/tests/polyval_kunit.c b/lib/crypto/tests/polyval_kunit.c index d1f53a690ab8..2835929e890c 100644 --- a/lib/crypto/tests/polyval_kunit.c +++ b/lib/crypto/tests/polyval_kunit.c @@ -66,17 +66,18 @@ static void test_polyval_rfc8452_testvec(struct kunit *test) */ static void test_polyval_allones_key_and_message(struct kunit *test) { + const size_t max_len = 4096; + u8 *data = alloc_buf(test, max_len); struct polyval_key key; struct polyval_ctx hashofhashes_ctx; u8 hash[POLYVAL_BLOCK_SIZE]; - static_assert(TEST_BUF_LEN >= 4096); - memset(test_buf, 0xff, 4096); + memset(data, 0xff, max_len); - polyval_preparekey(&key, test_buf); + polyval_preparekey(&key, data); polyval_init(&hashofhashes_ctx, &key); - for (size_t len = 0; len <= 4096; len += 16) { - polyval(&key, test_buf, len, hash); + for (size_t len = 0; len <= max_len; len += 16) { + polyval(&key, data, len, hash); polyval_update(&hashofhashes_ctx, hash, sizeof(hash)); } polyval_final(&hashofhashes_ctx, hash); @@ -95,7 +96,7 @@ static void check_key_consistency(struct kunit *test, const struct polyval_key *key1, const struct polyval_key *key2) { - u8 *data = test_buf; + u8 *data = alloc_buf(test, MAX_LEN_FOR_KEY_CHECK); u8 hash1[POLYVAL_BLOCK_SIZE]; u8 hash2[POLYVAL_BLOCK_SIZE]; @@ -115,10 +116,10 @@ static void check_key_consistency(struct kunit *test, static void test_polyval_with_guarded_key(struct kunit *test) { u8 raw_key[POLYVAL_BLOCK_SIZE]; - u8 *guarded_raw_key = &test_buf[TEST_BUF_LEN - sizeof(raw_key)]; + u8 *guarded_raw_key = alloc_guarded_buf(test, sizeof(raw_key)); struct polyval_key key1, key2; struct polyval_key *guarded_key = - (struct polyval_key *)&test_buf[TEST_BUF_LEN - sizeof(key1)]; + alloc_guarded_buf(test, sizeof(*guarded_key)); /* Prepare with regular buffers. */ rand_bytes(raw_key, sizeof(raw_key)); @@ -137,21 +138,20 @@ static void test_polyval_with_guarded_key(struct kunit *test) /* * Test that polyval_key only needs to be aligned to * __alignof__(struct polyval_key), i.e. 8 bytes. The assembly code may prefer - * 16-byte or higher alignment, but it musn't require it. + * 16-byte or higher alignment, but it mustn't require it. */ static void test_polyval_with_minimally_aligned_key(struct kunit *test) { u8 raw_key[POLYVAL_BLOCK_SIZE]; struct polyval_key key; + const size_t align = __alignof__(struct polyval_key); + u8 *key_buf = alloc_buf(test, sizeof(struct polyval_key) + 3 * align); struct polyval_key *minaligned_key = - (struct polyval_key *)&test_buf[MAX_LEN_FOR_KEY_CHECK + - __alignof__(struct polyval_key)]; + (struct polyval_key *)(PTR_ALIGN(key_buf, 2 * align) + align); - KUNIT_ASSERT_TRUE(test, IS_ALIGNED((uintptr_t)minaligned_key, - __alignof__(struct polyval_key))); + KUNIT_ASSERT_TRUE(test, IS_ALIGNED((uintptr_t)minaligned_key, align)); KUNIT_ASSERT_TRUE(test, - !IS_ALIGNED((uintptr_t)minaligned_key, - 2 * __alignof__(struct polyval_key))); + !IS_ALIGNED((uintptr_t)minaligned_key, 2 * align)); rand_bytes(raw_key, sizeof(raw_key)); polyval_preparekey(&key, raw_key); @@ -192,12 +192,7 @@ static int polyval_suite_init(struct kunit_suite *suite) rand_bytes_seeded_from_len(raw_key, sizeof(raw_key)); polyval_preparekey(&test_key, raw_key); - return hash_suite_init(suite); -} - -static void polyval_suite_exit(struct kunit_suite *suite) -{ - hash_suite_exit(suite); + return 0; } static struct kunit_case polyval_test_cases[] = { @@ -215,7 +210,6 @@ static struct kunit_suite polyval_test_suite = { .name = "polyval", .test_cases = polyval_test_cases, .suite_init = polyval_suite_init, - .suite_exit = polyval_suite_exit, }; kunit_test_suite(polyval_test_suite); diff --git a/lib/crypto/tests/sha1_kunit.c b/lib/crypto/tests/sha1_kunit.c index 24ba8d5669c8..27286afaa407 100644 --- a/lib/crypto/tests/sha1_kunit.c +++ b/lib/crypto/tests/sha1_kunit.c @@ -30,8 +30,6 @@ static struct kunit_case hash_test_cases[] = { static struct kunit_suite hash_test_suite = { .name = "sha1", .test_cases = hash_test_cases, - .suite_init = hash_suite_init, - .suite_exit = hash_suite_exit, }; kunit_test_suite(hash_test_suite); diff --git a/lib/crypto/tests/sha224_kunit.c b/lib/crypto/tests/sha224_kunit.c index 962ad46b9c99..bcf8b90f9ae2 100644 --- a/lib/crypto/tests/sha224_kunit.c +++ b/lib/crypto/tests/sha224_kunit.c @@ -30,8 +30,6 @@ static struct kunit_case hash_test_cases[] = { static struct kunit_suite hash_test_suite = { .name = "sha224", .test_cases = hash_test_cases, - .suite_init = hash_suite_init, - .suite_exit = hash_suite_exit, }; kunit_test_suite(hash_test_suite); diff --git a/lib/crypto/tests/sha256_kunit.c b/lib/crypto/tests/sha256_kunit.c index 8758db3673c7..17daa09adfe7 100644 --- a/lib/crypto/tests/sha256_kunit.c +++ b/lib/crypto/tests/sha256_kunit.c @@ -86,19 +86,20 @@ static void test_sha256_finup_2x(struct kunit *test) static void test_sha256_finup_2x_defaultctx(struct kunit *test) { const size_t data_len = 128; + u8 *data = alloc_buf(test, 2 * data_len); struct sha256_ctx ctx; u8 hash1_a[SHA256_DIGEST_SIZE]; u8 hash2_a[SHA256_DIGEST_SIZE]; u8 hash1_b[SHA256_DIGEST_SIZE]; u8 hash2_b[SHA256_DIGEST_SIZE]; - rand_bytes(test_buf, 2 * data_len); + rand_bytes(data, 2 * data_len); sha256_init(&ctx); - sha256_finup_2x(&ctx, test_buf, &test_buf[data_len], data_len, hash1_a, + sha256_finup_2x(&ctx, data, &data[data_len], data_len, hash1_a, hash2_a); - sha256_finup_2x(NULL, test_buf, &test_buf[data_len], data_len, hash1_b, + sha256_finup_2x(NULL, data, &data[data_len], data_len, hash1_b, hash2_b); KUNIT_ASSERT_MEMEQ(test, hash1_a, hash1_b, SHA256_DIGEST_SIZE); @@ -112,18 +113,19 @@ static void test_sha256_finup_2x_defaultctx(struct kunit *test) static void test_sha256_finup_2x_hugelen(struct kunit *test) { const size_t data_len = 4 * SHA256_BLOCK_SIZE; + u8 *data = alloc_buf(test, data_len); struct sha256_ctx ctx = {}; u8 expected_hash[SHA256_DIGEST_SIZE]; u8 hash[SHA256_DIGEST_SIZE]; - rand_bytes(test_buf, data_len); + rand_bytes(data, data_len); for (size_t align = 0; align < SHA256_BLOCK_SIZE; align++) { sha256_init(&ctx); ctx.ctx.bytecount = 0x123456789abcd00 + align; - sha256_finup_2x(&ctx, test_buf, test_buf, data_len, hash, hash); + sha256_finup_2x(&ctx, data, data, data_len, hash, hash); - sha256_update(&ctx, test_buf, data_len); + sha256_update(&ctx, data, data_len); sha256_final(&ctx, expected_hash); KUNIT_ASSERT_MEMEQ(test, hash, expected_hash, @@ -142,6 +144,7 @@ static void benchmark_sha256_finup_2x(struct kunit *test) static const size_t salt_lens_to_test[] = { 0, 32, 64 }; const size_t data_len = 4096; const size_t num_iters = 4096; + u8 *data = alloc_buf(test, data_len * 2); struct sha256_ctx ctx; u8 hash1[SHA256_DIGEST_SIZE]; u8 hash2[SHA256_DIGEST_SIZE]; @@ -151,12 +154,12 @@ static void benchmark_sha256_finup_2x(struct kunit *test) if (!sha256_finup_2x_is_optimized()) kunit_skip(test, "not relevant"); - rand_bytes(test_buf, data_len * 2); + rand_bytes(data, data_len * 2); /* Warm-up */ for (size_t i = 0; i < num_iters; i++) - sha256_finup_2x(NULL, &test_buf[0], &test_buf[data_len], - data_len, hash1, hash2); + sha256_finup_2x(NULL, &data[0], &data[data_len], data_len, + hash1, hash2); for (size_t i = 0; i < ARRAY_SIZE(salt_lens_to_test); i++) { size_t salt_len = salt_lens_to_test[i]; @@ -167,12 +170,12 @@ static void benchmark_sha256_finup_2x(struct kunit *test) * not measured; we're just interested in sha256_finup_2x(). */ sha256_init(&ctx); - sha256_update(&ctx, test_buf, salt_len); + sha256_update(&ctx, data, salt_len); preempt_disable(); t0 = ktime_get_ns(); for (size_t j = 0; j < num_iters; j++) - sha256_finup_2x(&ctx, &test_buf[0], &test_buf[data_len], + sha256_finup_2x(&ctx, &data[0], &data[data_len], data_len, hash1, hash2); t1 = ktime_get_ns(); preempt_enable(); @@ -196,8 +199,6 @@ static struct kunit_case hash_test_cases[] = { static struct kunit_suite hash_test_suite = { .name = "sha256", .test_cases = hash_test_cases, - .suite_init = hash_suite_init, - .suite_exit = hash_suite_exit, }; kunit_test_suite(hash_test_suite); diff --git a/lib/crypto/tests/sha384_kunit.c b/lib/crypto/tests/sha384_kunit.c index e1ef5c995bb6..76409822d965 100644 --- a/lib/crypto/tests/sha384_kunit.c +++ b/lib/crypto/tests/sha384_kunit.c @@ -30,8 +30,6 @@ static struct kunit_case hash_test_cases[] = { static struct kunit_suite hash_test_suite = { .name = "sha384", .test_cases = hash_test_cases, - .suite_init = hash_suite_init, - .suite_exit = hash_suite_exit, }; kunit_test_suite(hash_test_suite); diff --git a/lib/crypto/tests/sha3_kunit.c b/lib/crypto/tests/sha3_kunit.c index ed5fbe80337f..81b113079755 100644 --- a/lib/crypto/tests/sha3_kunit.c +++ b/lib/crypto/tests/sha3_kunit.c @@ -273,12 +273,10 @@ static void test_shake_all_lens_up_to_4096(struct kunit *test) { struct sha3_ctx main_ctx; const size_t max_len = 4096; - u8 *const in = test_buf; - u8 *const out = &test_buf[TEST_BUF_LEN - max_len]; + u8 *const in = alloc_buf(test, max_len); + u8 *const out = alloc_buf(test, max_len); u8 main_hash[SHA3_256_DIGEST_SIZE]; - KUNIT_ASSERT_LE(test, 2 * max_len, TEST_BUF_LEN); - rand_bytes_seeded_from_len(in, max_len); for (int alg = 0; alg < 2; alg++) { sha3_256_init(&main_ctx); @@ -309,12 +307,8 @@ static void test_shake_all_lens_up_to_4096(struct kunit *test) static void test_shake_multiple_squeezes(struct kunit *test) { const size_t max_len = 512; - u8 *ref_out; - - KUNIT_ASSERT_GE(test, TEST_BUF_LEN, 2 * max_len); - - ref_out = kunit_kzalloc(test, max_len, GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ref_out); + u8 *buf = alloc_buf(test, max_len); + u8 *ref_out = alloc_buf(test, max_len); for (int i = 0; i < 2000; i++) { const int alg = rand32() % 2; @@ -322,8 +316,8 @@ static void test_shake_multiple_squeezes(struct kunit *test) const size_t out_len = rand_length(max_len); const size_t in_offs = rand_offset(max_len - in_len); const size_t out_offs = rand_offset(max_len - out_len); - u8 *const in = &test_buf[in_offs]; - u8 *const out = &test_buf[out_offs]; + u8 *const in = &buf[in_offs]; + u8 *const out = &buf[out_offs]; struct shake_ctx ctx; size_t remaining_len, j, num_parts; @@ -368,16 +362,12 @@ static void test_shake_multiple_squeezes(struct kunit *test) static void test_shake_with_guarded_bufs(struct kunit *test) { const size_t max_len = 512; - u8 *reg_buf; - - KUNIT_ASSERT_GE(test, TEST_BUF_LEN, max_len); - - reg_buf = kunit_kzalloc(test, max_len, GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, reg_buf); + u8 *buf = alloc_guarded_buf(test, max_len); + u8 *reg_buf = alloc_buf(test, max_len); for (int alg = 0; alg < 2; alg++) { for (size_t len = 0; len <= max_len; len++) { - u8 *guarded_buf = &test_buf[TEST_BUF_LEN - len]; + u8 *guarded_buf = &buf[max_len - len]; rand_bytes(reg_buf, len); memcpy(guarded_buf, reg_buf, len); @@ -413,8 +403,6 @@ static struct kunit_case sha3_test_cases[] = { static struct kunit_suite sha3_test_suite = { .name = "sha3", .test_cases = sha3_test_cases, - .suite_init = hash_suite_init, - .suite_exit = hash_suite_exit, }; kunit_test_suite(sha3_test_suite); diff --git a/lib/crypto/tests/sha512_kunit.c b/lib/crypto/tests/sha512_kunit.c index 8923e2d7d3d4..e03926c24403 100644 --- a/lib/crypto/tests/sha512_kunit.c +++ b/lib/crypto/tests/sha512_kunit.c @@ -30,8 +30,6 @@ static struct kunit_case hash_test_cases[] = { static struct kunit_suite hash_test_suite = { .name = "sha512", .test_cases = hash_test_cases, - .suite_init = hash_suite_init, - .suite_exit = hash_suite_exit, }; kunit_test_suite(hash_test_suite); diff --git a/lib/crypto/tests/sm3_kunit.c b/lib/crypto/tests/sm3_kunit.c index dc8136acdff6..1cba384946e9 100644 --- a/lib/crypto/tests/sm3_kunit.c +++ b/lib/crypto/tests/sm3_kunit.c @@ -22,8 +22,6 @@ static struct kunit_case sm3_test_cases[] = { static struct kunit_suite sm3_test_suite = { .name = "sm3", .test_cases = sm3_test_cases, - .suite_init = hash_suite_init, - .suite_exit = hash_suite_exit, }; kunit_test_suite(sm3_test_suite); From 9396dc9495387a415d24a1adbd2d2ea12abacc96 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 2 Aug 2026 16:30:03 -0700 Subject: [PATCH 06/11] lib/crypto: tests: Add aead-test-template.h Add aead-test-template.h which contains shared test logic for AEAD algorithms. It's similar to the existing hash-test-template.h, which contains shared test logic for hash algorithms. The initial use cases for this will be AES-CCM and AES-GCM. It may be used by the existing ChaCha20Poly1305 test suite in the future as well, though the ChaCha20Poly1305 API currently lacks some of the functionality that the "template" expects to be present. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260802233005.161467-4-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/tests/aead-test-template.h | 1039 +++++++++++++++++++++++++ 1 file changed, 1039 insertions(+) create mode 100644 lib/crypto/tests/aead-test-template.h diff --git a/lib/crypto/tests/aead-test-template.h b/lib/crypto/tests/aead-test-template.h new file mode 100644 index 000000000000..0c0138d2fa8a --- /dev/null +++ b/lib/crypto/tests/aead-test-template.h @@ -0,0 +1,1039 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Shared KUnit test cases for AEAD algorithms, including a benchmark + * + * Copyright 2026 Google LLC + */ + +/* + * This file implements KUnit test cases shared by the different KUnit test + * suites for Authenticated Encryption with Associated Data (AEAD) algorithms. + * + * Test suites including this file must #define the following: + * + * Data structs: + * - AEAD_KEY: name of key struct + * - AEAD_CTX: name of context for incremental computation + * + * Constants: + * - AEAD_VALID_KEY_LENS: array of all valid key lengths in bytes + * - AEAD_VALID_NONCE_LENS: array of all valid nonce lengths in bytes + * - AEAD_VALID_TAG_LENS: array of all valid authtag lengths in bytes + * - AEAD_MAX_KEY_LEN: max key length in bytes (assumed to fit on stack) + * - AEAD_MAX_NONCE_LEN: max nonce length in bytes (assumed to fit on stack) + * - AEAD_MAX_TAG_LEN: max authtag length in bytes (assumed to fit on stack) + * - AEAD_MONTE_CARLO_CHECKSUM: checksum of a deterministically generated series + * of (ciphertext, authtag) pairs (see test_aead_monte_carlo()) + * + * Functions: + * - AEAD_PREPAREKEY: key preparation + * - AEAD_ENCRYPT and AEAD_DECRYPT: one-shot encryption and decryption + * - AEAD_INIT, AEAD_AUTH_UPDATE, AEAD_ENCRYPT_UPDATE, AEAD_ENCRYPT_FINAL, + * AEAD_DECRYPT_UPDATE, AEAD_DECRYPT_FINAL: functions for incremental + * encryption and decryption + * + * Function prototypes and their behavior must match the AES-CCM API. + */ + +#include +#include +#include +#include +#include +#include "test-utils.h" + +/* + * Allocate a KUnit-managed struct AEAD_KEY and prepare it with a random key, + * using a random key length and random authentication tag length. + */ +static struct AEAD_KEY *aead_alloc_random_key(struct kunit *test, + size_t *tag_len_ret) +{ + size_t key_len = + AEAD_VALID_KEY_LENS[rand32() % ARRAY_SIZE(AEAD_VALID_KEY_LENS)]; + size_t tag_len = + AEAD_VALID_TAG_LENS[rand32() % ARRAY_SIZE(AEAD_VALID_TAG_LENS)]; + u8 raw_key[AEAD_MAX_KEY_LEN]; + struct AEAD_KEY *key = alloc_buf(test, sizeof(*key)); + int err; + + rand_bytes(raw_key, key_len); + err = AEAD_PREPAREKEY(key, raw_key, key_len, tag_len); + KUNIT_ASSERT_EQ(test, 0, err); + *tag_len_ret = tag_len; + return key; +} + +/* + * Allocate a KUnit-managed slab buffer of length @len bytes and initialize it + * with random data. + */ +static u8 *aead_alloc_random_data(struct kunit *test, size_t len) +{ + u8 *buf = alloc_buf(test, len); + + rand_bytes(buf, len); + return buf; +} + +/* + * Allocate a KUnit-managed guarded buffer of length @len bytes and initialize + * it with random data. + */ +static u8 *aead_alloc_random_data_guarded(struct kunit *test, size_t len) +{ + u8 *buf = alloc_guarded_buf(test, len); + + rand_bytes(buf, len); + return buf; +} + +/* Process the given associated data using a random incremental strategy. */ +static size_t aead_auth_incrementally(struct AEAD_CTX *ctx, const u8 *ad, + size_t ad_len) +{ + size_t num_parts = 0; + size_t pos = 0; + + while (rand_bool()) { + size_t part_len = rand_length(ad_len - pos); + + AEAD_AUTH_UPDATE(ctx, &ad[pos], part_len); + pos += part_len; + num_parts++; + } + if (pos < ad_len || rand_bool()) { + AEAD_AUTH_UPDATE(ctx, &ad[pos], ad_len - pos); + num_parts++; + } + return num_parts; +} + +/* Process the given en/decrypted data using a random incremental strategy. */ +static size_t aead_crypt_incrementally(struct AEAD_CTX *ctx, u8 *dst, + const u8 *src, size_t data_len, bool enc) +{ + size_t num_parts = 0; + size_t pos = 0; + + while (rand_bool()) { + size_t part_len = rand_length(data_len - pos); + + if (enc) + AEAD_ENCRYPT_UPDATE(ctx, &dst[pos], &src[pos], + part_len); + else + AEAD_DECRYPT_UPDATE(ctx, &dst[pos], &src[pos], + part_len); + pos += part_len; + num_parts++; + } + if (pos < data_len || rand_bool()) { + if (enc) + AEAD_ENCRYPT_UPDATE(ctx, &dst[pos], &src[pos], + data_len - pos); + else + AEAD_DECRYPT_UPDATE(ctx, &dst[pos], &src[pos], + data_len - pos); + num_parts++; + } + return num_parts; +} + +struct aead_incremental_info { + size_t num_data_parts; + size_t num_ad_parts; +}; + +static const char *aead_incr_info_str(struct kunit *test, + const struct aead_incremental_info *info) +{ + const size_t max_str_len = 64; + char *str = alloc_buf(test, max_str_len); + + snprintf(str, max_str_len, "num_data_parts=%zu num_ad_parts=%zu", + info->num_data_parts, info->num_ad_parts); + return str; +} + +/* + * Encrypt data using a random incremental strategy. + * Return information about the incremental strategy used. + */ +static struct aead_incremental_info +aead_encrypt_incrementally(struct kunit *test, struct AEAD_CTX *ctx, u8 *dst, + const u8 *src, size_t data_len, u8 *tag, + const u8 *ad, size_t ad_len, const u8 *nonce, + size_t nonce_len, const struct AEAD_KEY *key) +{ + struct aead_incremental_info info; + int err; + + err = AEAD_INIT(ctx, data_len, ad_len, nonce, nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + info.num_ad_parts = aead_auth_incrementally(ctx, ad, ad_len); + info.num_data_parts = aead_crypt_incrementally(ctx, dst, src, data_len, + /* enc= */ true); + AEAD_ENCRYPT_FINAL(ctx, tag); + KUNIT_ASSERT_TRUE_MSG(test, mem_is_zero(ctx, sizeof(*ctx)), + "encrypt_final didn't zeroize context"); + return info; +} + +/* + * Decrypt authentic data using a random incremental strategy. + * Return information about the incremental strategy used. + */ +static struct aead_incremental_info +aead_decrypt_incrementally(struct kunit *test, struct AEAD_CTX *ctx, u8 *dst, + const u8 *src, size_t data_len, const u8 *tag, + const u8 *ad, size_t ad_len, const u8 *nonce, + size_t nonce_len, const struct AEAD_KEY *key) +{ + struct aead_incremental_info info; + int err; + + err = AEAD_INIT(ctx, data_len, ad_len, nonce, nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + info.num_ad_parts = aead_auth_incrementally(ctx, ad, ad_len); + info.num_data_parts = aead_crypt_incrementally(ctx, dst, src, data_len, + /* enc= */ false); + err = AEAD_DECRYPT_FINAL(ctx, tag); + KUNIT_ASSERT_EQ(test, 0, err); + KUNIT_ASSERT_TRUE_MSG(test, mem_is_zero(ctx, sizeof(*ctx)), + "decrypt_final didn't zeroize context"); + return info; +} + +/* Return true if key_len is declared to be a valid key length. */ +static bool aead_is_key_len_expected_valid(size_t key_len) +{ + for (size_t i = 0; i < ARRAY_SIZE(AEAD_VALID_KEY_LENS); i++) { + if (AEAD_VALID_KEY_LENS[i] == key_len) + return true; + } + return false; +} + +/* Return true if nonce_len is declared to be a valid nonce length. */ +static bool aead_is_nonce_len_expected_valid(size_t nonce_len) +{ + for (size_t i = 0; i < ARRAY_SIZE(AEAD_VALID_NONCE_LENS); i++) { + if (AEAD_VALID_NONCE_LENS[i] == nonce_len) + return true; + } + return false; +} + +/* Return true if tag_len is declared to be a valid tag length. */ +static bool aead_is_tag_len_expected_valid(size_t tag_len) +{ + for (size_t i = 0; i < ARRAY_SIZE(AEAD_VALID_TAG_LENS); i++) { + if (AEAD_VALID_TAG_LENS[i] == tag_len) + return true; + } + return false; +} + +struct aead_basic_validation_test_ctx { + struct AEAD_KEY key; + struct AEAD_CTX ctx; + u8 *raw_key_buf_end; + u8 *nonce_buf_end; + u8 *tag_buf_end; + u8 pt[64]; /* plaintext */ + u8 ct[64]; /* ciphertext */ + u8 decrypted[64]; + u8 ad[16]; /* associated data */ + u8 *unused_buf; + size_t data_len; + size_t ad_len; +}; + +static struct aead_basic_validation_test_ctx * +aead_alloc_basic_validation_test_ctx(struct kunit *test) +{ + struct aead_basic_validation_test_ctx *ctx = + alloc_buf(test, sizeof(*ctx)); + + memset(ctx, 0, sizeof(*ctx)); + ctx->raw_key_buf_end = + aead_alloc_random_data_guarded(test, AEAD_MAX_KEY_LEN) + + AEAD_MAX_KEY_LEN; + ctx->nonce_buf_end = + aead_alloc_random_data_guarded(test, AEAD_MAX_NONCE_LEN) + + AEAD_MAX_NONCE_LEN; + ctx->tag_buf_end = + aead_alloc_random_data_guarded(test, AEAD_MAX_TAG_LEN) + + AEAD_MAX_TAG_LEN; + + /* + * A pointer to this buffer is passed when passing a length that is + * expected to be invalid. It should never actually be accessed. + */ + ctx->unused_buf = + alloc_buf(test, max3(AEAD_MAX_KEY_LEN, AEAD_MAX_NONCE_LEN, + AEAD_MAX_TAG_LEN)); + + ctx->data_len = sizeof(ctx->pt); + ctx->ad_len = sizeof(ctx->ad); + return ctx; +} + +/* + * Given an expected-valid key_len, nonce_len, and tag_len, verify round-trip + * encryption and decryption with them. Use guarded buffers for each of the raw + * key, nonce, and tag to detect any buffer overruns in them. Also, verify that + * every byte of the tag is actually checked. + */ +static void aead_do_basic_checks(struct kunit *test, + struct aead_basic_validation_test_ctx *ctx, + size_t key_len, size_t nonce_len, + size_t tag_len) +{ + /* Set up exact-size guarded buffers for (raw_key, nonce, tag). */ + const u8 *raw_key = ctx->raw_key_buf_end - key_len; + const u8 *nonce = ctx->nonce_buf_end - nonce_len; + u8 *tag = ctx->tag_buf_end - tag_len; + int err; + + /* Key preparation should succeed. */ + err = AEAD_PREPAREKEY(&ctx->key, raw_key, key_len, tag_len); + KUNIT_ASSERT_EQ_MSG(test, 0, err, + "key_len=%zu, tag_len=%zu wasn't accepted", key_len, + tag_len); + + /* Encryption should succeed. */ + err = AEAD_ENCRYPT(ctx->ct, ctx->pt, ctx->data_len, tag, ctx->ad, + ctx->ad_len, nonce, nonce_len, &ctx->key); + KUNIT_ASSERT_EQ_MSG( + test, 0, err, + "Encryption failed with key_len=%zu, nonce_len=%zu, tag_len=%zu", + key_len, nonce_len, tag_len); + + /* Decryption should succeed and give the original data. */ + err = AEAD_DECRYPT(ctx->decrypted, ctx->ct, ctx->data_len, tag, ctx->ad, + ctx->ad_len, nonce, nonce_len, &ctx->key); + KUNIT_ASSERT_EQ_MSG( + test, 0, err, + "Decryption failed with key_len=%zu, nonce_len=%zu, tag_len=%zu", + key_len, nonce_len, tag_len); + KUNIT_ASSERT_MEMEQ_MSG( + test, ctx->pt, ctx->decrypted, ctx->data_len, + "Decryption gave wrong output with key_len=%zu, nonce_len=%zu, tag_len=%zu", + key_len, nonce_len, tag_len); + + /* + * Every byte of the tag should actually be checked. + * And on authentication failure, the dst buffer should be cleared. + */ + for (size_t i = 0; i < tag_len; i++) { + memset(ctx->decrypted, 0xff, ctx->data_len); + tag[i] ^= 1; + err = AEAD_DECRYPT(ctx->decrypted, ctx->ct, ctx->data_len, tag, + ctx->ad, ctx->ad_len, nonce, nonce_len, + &ctx->key); + KUNIT_ASSERT_EQ_MSG( + test, -EBADMSG, err, + "Decryption with bad auth tag with key_len=%zu, nonce_len=%zu, tag_len=%zu didn't fail with -EBADMSG", + key_len, nonce_len, tag_len); + KUNIT_ASSERT_TRUE_MSG( + test, mem_is_zero(ctx->decrypted, ctx->data_len), + "dst wasn't cleared on authentication failure"); + tag[i] ^= 1; + } +} + +/* Verify that the given expected-invalid key_len is actually rejected. */ +static void +aead_verify_invalid_key_len(struct kunit *test, + struct aead_basic_validation_test_ctx *ctx, + size_t key_len) +{ + int err; + + /* + * The preparekey function should reject the key_len. It should do so + * before writing to the key struct. + */ + memset(&ctx->key, 0, sizeof(ctx->key)); + err = AEAD_PREPAREKEY(&ctx->key, ctx->unused_buf, key_len, + AEAD_MAX_TAG_LEN); + KUNIT_ASSERT_EQ_MSG(test, -EINVAL, err, + "key_len=%zu wasn't rejected with -EINVAL", + key_len); + KUNIT_ASSERT_TRUE_MSG( + test, mem_is_zero(&ctx->key, sizeof(ctx->key)), + "Key struct was written to before length validation"); +} + +/* + * Test that every valid key length is accepted and basic checks pass with it, + * and test that invalid key lengths are rejected. + */ +static void test_aead_all_key_lens(struct kunit *test) +{ + struct aead_basic_validation_test_ctx *ctx = + aead_alloc_basic_validation_test_ctx(test); + + for (size_t key_len = 0; key_len <= AEAD_MAX_KEY_LEN; key_len++) { + if (aead_is_key_len_expected_valid(key_len)) + aead_do_basic_checks(test, ctx, key_len, + AEAD_MAX_NONCE_LEN, + AEAD_MAX_TAG_LEN); + else + aead_verify_invalid_key_len(test, ctx, key_len); + } + aead_verify_invalid_key_len(test, ctx, AEAD_MAX_KEY_LEN + 1); + aead_verify_invalid_key_len(test, ctx, AEAD_MAX_KEY_LEN * 2); + aead_verify_invalid_key_len(test, ctx, U32_MAX); + aead_verify_invalid_key_len(test, ctx, SIZE_MAX); +} + +/* Verify that the given expected-invalid nonce_len is actually rejected. */ +static void +aead_verify_invalid_nonce_len(struct kunit *test, + struct aead_basic_validation_test_ctx *ctx, + size_t nonce_len) +{ + static const u8 raw_key[AEAD_MAX_KEY_LEN]; + int err; + + /* Key preparation should succeed, as nonce_len isn't given yet. */ + err = AEAD_PREPAREKEY(&ctx->key, raw_key, sizeof(raw_key), + AEAD_MAX_TAG_LEN); + KUNIT_ASSERT_EQ(test, 0, err); + + /* The init function should reject the nonce_len. */ + memset(&ctx->ctx, 0, sizeof(ctx->ctx)); + err = AEAD_INIT(&ctx->ctx, ctx->data_len, ctx->ad_len, ctx->unused_buf, + nonce_len, &ctx->key); + KUNIT_ASSERT_EQ_MSG(test, -EINVAL, err, + "nonce_len=%zu wasn't rejected with -EINVAL (init)", + nonce_len); + KUNIT_ASSERT_TRUE_MSG( + test, mem_is_zero(&ctx->ctx, sizeof(ctx->ctx)), + "Context struct was written to before length validation"); + + /* The encrypt function should reject the nonce_len. */ + err = AEAD_ENCRYPT(ctx->ct, ctx->pt, ctx->data_len, ctx->unused_buf, + ctx->ad, ctx->ad_len, ctx->unused_buf, nonce_len, + &ctx->key); + KUNIT_ASSERT_EQ_MSG( + test, -EINVAL, err, + "nonce_len=%zu wasn't rejected with -EINVAL (encrypt)", + nonce_len); + + /* The decrypt function should reject the nonce_len. */ + err = AEAD_DECRYPT(ctx->pt, ctx->ct, ctx->data_len, ctx->unused_buf, + ctx->ad, ctx->ad_len, ctx->unused_buf, nonce_len, + &ctx->key); + KUNIT_ASSERT_EQ_MSG( + test, -EINVAL, err, + "nonce_len=%zu wasn't rejected with -EINVAL (decrypt)", + nonce_len); +} + +/* + * Test that every valid nonce length is accepted and basic checks pass with it, + * and test that invalid nonce lengths are rejected. + */ +static void test_aead_all_nonce_lens(struct kunit *test) +{ + struct aead_basic_validation_test_ctx *ctx = + aead_alloc_basic_validation_test_ctx(test); + + for (size_t nonce_len = 0; nonce_len <= AEAD_MAX_NONCE_LEN; + nonce_len++) { + if (aead_is_nonce_len_expected_valid(nonce_len)) + aead_do_basic_checks(test, ctx, AEAD_MAX_KEY_LEN, + nonce_len, AEAD_MAX_TAG_LEN); + else + aead_verify_invalid_nonce_len(test, ctx, nonce_len); + } + aead_verify_invalid_nonce_len(test, ctx, AEAD_MAX_NONCE_LEN + 1); + aead_verify_invalid_nonce_len(test, ctx, AEAD_MAX_NONCE_LEN * 2); + aead_verify_invalid_nonce_len(test, ctx, U32_MAX); + aead_verify_invalid_nonce_len(test, ctx, SIZE_MAX); +} + +/* Verify that the given expected-invalid tag_len is actually rejected. */ +static void +aead_verify_invalid_tag_len(struct kunit *test, + struct aead_basic_validation_test_ctx *ctx, + size_t tag_len) +{ + static const u8 raw_key[AEAD_MAX_KEY_LEN]; + int err; + + /* + * The preparekey function should reject the tag_len. It should do so + * before writing to the key struct. + */ + memset(&ctx->key, 0, sizeof(ctx->key)); + err = AEAD_PREPAREKEY(&ctx->key, raw_key, sizeof(raw_key), tag_len); + KUNIT_ASSERT_EQ_MSG(test, -EINVAL, err, + "tag_len=%zu wasn't rejected with -EINVAL", + tag_len); + KUNIT_ASSERT_TRUE_MSG( + test, mem_is_zero(&ctx->key, sizeof(ctx->key)), + "Key struct was written to before length validation"); +} + +/* + * Test that every valid authentication tag length is accepted and basic checks + * pass with it, and test that invalid authentication tag lengths are rejected. + */ +static void test_aead_all_tag_lens(struct kunit *test) +{ + struct aead_basic_validation_test_ctx *ctx = + aead_alloc_basic_validation_test_ctx(test); + + for (size_t tag_len = 0; tag_len <= AEAD_MAX_TAG_LEN; tag_len++) { + if (aead_is_tag_len_expected_valid(tag_len)) + aead_do_basic_checks(test, ctx, AEAD_MAX_KEY_LEN, + AEAD_MAX_NONCE_LEN, tag_len); + else + aead_verify_invalid_tag_len(test, ctx, tag_len); + } + aead_verify_invalid_tag_len(test, ctx, AEAD_MAX_TAG_LEN + 1); + aead_verify_invalid_tag_len(test, ctx, AEAD_MAX_TAG_LEN * 2); + aead_verify_invalid_tag_len(test, ctx, U32_MAX); + aead_verify_invalid_tag_len(test, ctx, SIZE_MAX); +} + +/* + * Test that one-shot encryption and decryption are consistent with each other + * and with incremental encryption and decryption. + */ +static void test_aead_incremental_updates(struct kunit *test) +{ + const size_t max_data_len = 1024; + const size_t max_ad_len = 512; + const size_t nonce_len = AEAD_MAX_NONCE_LEN; + size_t tag_len; + struct AEAD_KEY *key = aead_alloc_random_key(test, &tag_len); + struct AEAD_CTX *ctx = alloc_buf(test, sizeof(*ctx)); + u8 *pt = aead_alloc_random_data(test, max_data_len); + u8 *ad = aead_alloc_random_data(test, max_ad_len); + u8 *nonce = aead_alloc_random_data(test, nonce_len); + u8 *ct = alloc_buf(test, max_data_len); + u8 *ct2 = alloc_buf(test, max_data_len); + u8 *decrypted = alloc_buf(test, max_data_len); + u8 *tag = alloc_buf(test, tag_len); + u8 *tag2 = alloc_buf(test, tag_len); + int err; + + for (int i = 0; i < 500; i++) { + /* Select the lengths to test. */ + const size_t data_len = rand_length(max_data_len); + const size_t ad_len = rand_length(max_ad_len); + struct aead_incremental_info incr_info; + + /* Try one-shot encryption and decryption. */ + err = AEAD_ENCRYPT(ct, pt, data_len, tag, ad, ad_len, nonce, + nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + err = AEAD_DECRYPT(decrypted, ct, data_len, tag, ad, ad_len, + nonce, nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + KUNIT_ASSERT_MEMEQ_MSG( + test, pt, decrypted, data_len, + "Decryption didn't invert encryption; data_len=%zu, ad_len=%zu", + data_len, ad_len); + + /* Try incremental encryption and decryption. */ + incr_info = aead_encrypt_incrementally(test, ctx, ct2, pt, + data_len, tag2, ad, + ad_len, nonce, nonce_len, + key); + KUNIT_ASSERT_MEMEQ_MSG( + test, ct, ct2, data_len, + "One-shot and incremental encryption gave different ciphertexts; data_len=%zu ad_len=%zu %s", + data_len, ad_len, aead_incr_info_str(test, &incr_info)); + KUNIT_ASSERT_MEMEQ_MSG( + test, tag, tag2, tag_len, + "One-shot and incremental encryption gave different auth tags; data_len=%zu ad_len=%zu %s", + data_len, ad_len, aead_incr_info_str(test, &incr_info)); + incr_info = aead_decrypt_incrementally(test, ctx, decrypted, + ct2, data_len, tag2, ad, + ad_len, nonce, nonce_len, + key); + KUNIT_ASSERT_MEMEQ_MSG( + test, pt, decrypted, data_len, + "One-shot and incremental decryption gave different plaintexts; data_len=%zu ad_len=%zu %s", + data_len, ad_len, aead_incr_info_str(test, &incr_info)); + } +} + +/* + * Test using guarded buffers for the plaintext, ciphertext, and associated + * data. This detects out-of-bounds accesses, even in assembly code. + * + * Note: other test cases cover overrun of raw_key, nonce, and tag. + */ +static void test_aead_data_buffer_overruns(struct kunit *test) +{ + const size_t max_data_len = 1024; + const size_t max_ad_len = 512; + const size_t nonce_len = AEAD_MAX_NONCE_LEN; + size_t tag_len; + struct AEAD_KEY *key = aead_alloc_random_key(test, &tag_len); + const u8 *nonce = aead_alloc_random_data(test, nonce_len); + const u8 *pt_end = aead_alloc_random_data_guarded(test, max_data_len) + + max_data_len; + const u8 *ad_end = + aead_alloc_random_data_guarded(test, max_ad_len) + max_ad_len; + u8 *ct_end = alloc_guarded_buf(test, max_data_len) + max_data_len; + u8 *decrypted_end = + alloc_guarded_buf(test, max_data_len) + max_data_len; + u8 *tag = alloc_buf(test, tag_len); + + for (int i = 0; i < 200; i++) { + /* Select the lengths to test. */ + const size_t data_len = rand_length(max_data_len); + const size_t ad_len = rand_length(max_ad_len); + /* Set up exact-size guarded buffers. */ + const u8 *pt = pt_end - data_len; + const u8 *ad = ad_end - ad_len; + u8 *ct = ct_end - data_len; + u8 *decrypted = decrypted_end - data_len; + int err; + + /* Encrypt and decrypt. */ + err = AEAD_ENCRYPT(ct, pt, data_len, tag, ad, ad_len, nonce, + nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + err = AEAD_DECRYPT(decrypted, ct, data_len, tag, ad, ad_len, + nonce, nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + KUNIT_ASSERT_MEMEQ_MSG( + test, pt, decrypted, data_len, + "Decryption didn't invert encryption; data_len=%zu, ad_len=%zu", + data_len, ad_len); + } +} + +/* + * Test that encryption and decryption produce the same results regardless of + * how the buffers are aligned in memory. + */ +static void test_aead_alignment_consistency(struct kunit *test) +{ + const size_t max_data_len = 4096; + const size_t max_ad_len = 4096; + const size_t max_offset = 128; + const size_t nonce_len = AEAD_MAX_NONCE_LEN; + const size_t key_len = AEAD_MAX_KEY_LEN; + const size_t tag_len = AEAD_MAX_TAG_LEN; + u8 *raw_key1_buf = alloc_buf(test, key_len + max_offset); + u8 *raw_key2_buf = alloc_buf(test, key_len + max_offset); + u8 *nonce1_buf = alloc_buf(test, nonce_len + max_offset); + u8 *nonce2_buf = alloc_buf(test, nonce_len + max_offset); + u8 *pt1_buf = alloc_buf(test, max_data_len); + u8 *pt2_buf = alloc_buf(test, max_data_len); + u8 *ct1_buf = alloc_buf(test, max_data_len); + u8 *ct2_buf = alloc_buf(test, max_data_len); + u8 *ad1_buf = alloc_buf(test, max_ad_len); + u8 *ad2_buf = alloc_buf(test, max_ad_len); + u8 *tag1_buf = alloc_buf(test, tag_len + max_offset); + u8 *tag2_buf = alloc_buf(test, tag_len + max_offset); + struct AEAD_KEY *key = alloc_buf(test, sizeof(*key)); + int err; + + for (int i = 0; i < 100; i++) { + /* Generate lengths. */ + size_t data_len = rand_length(max_data_len); + size_t ad_len = rand_length(max_ad_len); + + /* Generate two sets of alignments. */ + u8 *raw_key1 = raw_key1_buf + rand_offset(max_offset); + u8 *raw_key2 = raw_key2_buf + rand_offset(max_offset); + u8 *nonce1 = nonce1_buf + rand_offset(max_offset); + u8 *nonce2 = nonce2_buf + rand_offset(max_offset); + u8 *pt1 = pt1_buf + rand_offset(max_data_len - data_len); + u8 *pt2 = pt2_buf + rand_offset(max_data_len - data_len); + u8 *ct1 = ct1_buf + rand_offset(max_data_len - data_len); + u8 *ct2 = ct2_buf + rand_offset(max_data_len - data_len); + u8 *ad1 = ad1_buf + rand_offset(max_ad_len - ad_len); + u8 *ad2 = ad2_buf + rand_offset(max_ad_len - ad_len); + u8 *tag1 = tag1_buf + rand_offset(max_offset); + u8 *tag2 = tag2_buf + rand_offset(max_offset); + + /* + * Generate inputs in the first set of buffers using the first + * set of alignments. + */ + rand_bytes(raw_key1, key_len); + rand_bytes(nonce1, nonce_len); + rand_bytes(pt1, data_len); + rand_bytes(ad1, ad_len); + + /* + * Copy the inputs to the second set of buffers using the second + * set of alignments. + */ + memcpy(raw_key2, raw_key1, key_len); + memcpy(nonce2, nonce1, nonce_len); + memcpy(pt2, pt1, data_len); + memcpy(ad2, ad1, ad_len); + + /* Verify encryption consistency. */ + + err = AEAD_PREPAREKEY(key, raw_key1, key_len, tag_len); + KUNIT_ASSERT_EQ(test, 0, err); + err = AEAD_ENCRYPT(ct1, pt1, data_len, tag1, ad1, ad_len, + nonce1, nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + + err = AEAD_PREPAREKEY(key, raw_key2, key_len, tag_len); + KUNIT_ASSERT_EQ(test, 0, err); + err = AEAD_ENCRYPT(ct2, pt2, data_len, tag2, ad2, ad_len, + nonce2, nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + + KUNIT_ASSERT_MEMEQ(test, ct1, ct2, data_len); + KUNIT_ASSERT_MEMEQ(test, tag1, tag2, tag_len); + + /* Verify decryption consistency. */ + + err = AEAD_PREPAREKEY(key, raw_key1, key_len, tag_len); + KUNIT_ASSERT_EQ(test, 0, err); + err = AEAD_DECRYPT(pt1, ct1, data_len, tag1, ad1, ad_len, + nonce1, nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + + err = AEAD_PREPAREKEY(key, raw_key2, key_len, tag_len); + KUNIT_ASSERT_EQ(test, 0, err); + err = AEAD_DECRYPT(pt2, ct2, data_len, tag2, ad2, ad_len, + nonce2, nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + + KUNIT_ASSERT_MEMEQ(test, pt1, pt2, data_len); + } +} + +static void test_aead_inplace(struct kunit *test) +{ + const size_t max_data_len = 1024; + const size_t max_ad_len = 512; + const size_t nonce_len = AEAD_MAX_NONCE_LEN; + size_t tag_len; + struct AEAD_KEY *key = aead_alloc_random_key(test, &tag_len); + u8 *data = aead_alloc_random_data(test, max_data_len + tag_len); + u8 *data2 = alloc_buf(test, max_data_len + tag_len); + u8 *ad = aead_alloc_random_data(test, max_ad_len); + const u8 *nonce = aead_alloc_random_data(test, nonce_len); + + for (int i = 0; i < 100; i++) { + size_t data_len = rand_length(max_data_len); + size_t ad_len = rand_length(max_ad_len); + int err; + + /* Encrypt out-of-place. */ + err = AEAD_ENCRYPT(data2, data, data_len, data2 + data_len, ad, + ad_len, nonce, nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + + /* Encrypt in-place. */ + err = AEAD_ENCRYPT(data, data, data_len, data + data_len, ad, + ad_len, nonce, nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + + /* Compare the results. */ + KUNIT_ASSERT_MEMEQ(test, data2, data, data_len + tag_len); + + /* Decrypt out-of-place. */ + err = AEAD_DECRYPT(data2, data, data_len, data + data_len, ad, + ad_len, nonce, nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + + /* Decrypt in-place. */ + err = AEAD_DECRYPT(data, data, data_len, data + data_len, ad, + ad_len, nonce, nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + + /* Compare the results. */ + KUNIT_ASSERT_MEMEQ(test, data2, data, data_len); + } +} + +/* + * Monte-Carlo test for AEAD algorithms. This deterministically generates + * random AEAD inputs, encrypts them, verifies decryption, and computes and + * verifies the checksum of all computed (ciphertext, tag) pairs. + */ +static void test_aead_monte_carlo(struct kunit *test) +{ + const size_t max_data_len = 1024; + const size_t max_ad_len = 293; + u8 raw_key[AEAD_MAX_KEY_LEN]; + u8 nonce[AEAD_MAX_NONCE_LEN]; + u8 tag[AEAD_MAX_TAG_LEN]; + u8 *pt = alloc_buf(test, max_data_len); + u8 *ct = alloc_buf(test, max_data_len); + u8 *decrypted = alloc_buf(test, max_data_len); + u8 *ad = alloc_buf(test, max_ad_len); + struct AEAD_KEY *key = alloc_buf(test, sizeof(*key)); + struct blake2s_ctx checksum_ctx; + u8 actual_checksum[BLAKE2S_HASH_SIZE]; + int err; + + blake2s_init(&checksum_ctx, BLAKE2S_HASH_SIZE); + + for (size_t data_len = 0; data_len <= max_data_len; data_len++) { + size_t ad_len = data_len % max_ad_len; + size_t key_len = + AEAD_VALID_KEY_LENS[data_len % + ARRAY_SIZE(AEAD_VALID_KEY_LENS)]; + size_t nonce_len = + AEAD_VALID_NONCE_LENS[data_len % + ARRAY_SIZE(AEAD_VALID_NONCE_LENS)]; + size_t tag_len = + AEAD_VALID_TAG_LENS[data_len % + ARRAY_SIZE(AEAD_VALID_TAG_LENS)]; + + rand_bytes_seeded_from_len(pt, data_len); + rand_bytes_seeded_from_len(ad, ad_len); + rand_bytes_seeded_from_len(raw_key, key_len); + rand_bytes_seeded_from_len(nonce, nonce_len); + + err = AEAD_PREPAREKEY(key, raw_key, key_len, tag_len); + KUNIT_ASSERT_EQ(test, 0, err); + + err = AEAD_ENCRYPT(ct, pt, data_len, tag, ad, ad_len, nonce, + nonce_len, key); + KUNIT_ASSERT_EQ_MSG( + test, 0, err, + "Encryption failed with data_len=%zu, ad_len=%zu", + data_len, ad_len); + err = AEAD_DECRYPT(decrypted, ct, data_len, tag, ad, ad_len, + nonce, nonce_len, key); + KUNIT_ASSERT_EQ_MSG( + test, 0, err, + "Decryption failed with data_len=%zu, ad_len=%zu", + data_len, ad_len); + KUNIT_ASSERT_MEMEQ_MSG( + test, pt, decrypted, data_len, + "Decryption didn't invert encryption; data_len=%zu, ad_len=%zu", + data_len, ad_len); + + blake2s_update(&checksum_ctx, ct, data_len); + blake2s_update(&checksum_ctx, tag, tag_len); + } + + blake2s_final(&checksum_ctx, actual_checksum); + KUNIT_EXPECT_MEMEQ_MSG(test, actual_checksum, AEAD_MONTE_CARLO_CHECKSUM, + BLAKE2S_HASH_SIZE, + "Monte-Carlo checksum mismatch"); +} + +#define IRQ_TEST_DATA_LEN 256 +#define IRQ_TEST_NUM_BUFFERS 3 /* matches max concurrency level */ + +struct aead_irq_test_slot { + /* Fields written only at test case initialization time */ + u8 raw_key[AEAD_MAX_KEY_LEN]; + u8 nonce[AEAD_MAX_NONCE_LEN]; + u8 pt[IRQ_TEST_DATA_LEN]; + u8 ct[IRQ_TEST_DATA_LEN + AEAD_MAX_TAG_LEN]; + u8 ad[IRQ_TEST_DATA_LEN]; + + /* Fields written throughout the test case */ + struct AEAD_KEY key; + u8 scratch_buf[IRQ_TEST_DATA_LEN + AEAD_MAX_TAG_LEN]; + int phase; + atomic_t in_use; +}; + +struct aead_irq_test_state { + struct aead_irq_test_slot slots[IRQ_TEST_NUM_BUFFERS]; +}; + +static bool aead_irq_test_func(void *state_) +{ + struct aead_irq_test_state *state = state_; + struct aead_irq_test_slot *slot; + size_t data_len; + bool ok = true; + + /* + * Find a free slot. This should always succeed, since the number of + * slots is equal to the max concurrency level of kunit_run_irq_test(). + */ + for (slot = &state->slots[0]; + slot < &state->slots[ARRAY_SIZE(state->slots)]; slot++) { + if (atomic_cmpxchg(&slot->in_use, 0, 1) == 0) + break; + } + if (WARN_ON_ONCE(slot == &state->slots[ARRAY_SIZE(state->slots)])) + return false; + /* + * This execution context now has exclusive access to 'slot'. + * Next, execute the next operation that the slot is set to perform. + */ + + data_len = sizeof(slot->pt); + if (slot->phase == 0) { + /* Phase 0: Prepare slot's key in current context. */ + ok = ok && AEAD_PREPAREKEY(&slot->key, slot->raw_key, + sizeof(slot->raw_key), + AEAD_MAX_TAG_LEN) == 0; + } else if (slot->phase == 1) { + /* + * Phase 1: Encrypt plaintext using key that may have been + * prepared in a different context. + */ + ok = ok && AEAD_ENCRYPT(slot->scratch_buf, slot->pt, data_len, + &slot->scratch_buf[data_len], slot->ad, + sizeof(slot->ad), slot->nonce, + sizeof(slot->nonce), &slot->key) == 0; + /* Verify the ciphertext (with concatenated auth tag) matches */ + ok = ok && + memcmp(slot->scratch_buf, slot->ct, sizeof(slot->ct)) == 0; + } else { + /* + * Phase 2: Decrypt ciphertext using key that may have been + * prepared in a different context. + */ + ok = ok && AEAD_DECRYPT(slot->scratch_buf, slot->ct, data_len, + &slot->ct[data_len], slot->ad, + sizeof(slot->ad), slot->nonce, + sizeof(slot->nonce), &slot->key) == 0; + /* Verify the plaintext matches. */ + ok = ok && memcmp(slot->scratch_buf, slot->pt, data_len) == 0; + } + slot->phase = (slot->phase + 1) % 3; + atomic_set_release(&slot->in_use, 0); + return ok; +} + +/* + * Test that encryption and decryption produce the correct results in task, + * softirq, and hardirq contexts running concurrently -- including with keys + * prepared in other contexts. This is needed to cover fallback code paths that + * execute in contexts where FPU or vector registers cannot be used. + */ +static void test_aead_interrupt_context(struct kunit *test) +{ + struct aead_irq_test_state *state = alloc_buf(test, sizeof(*state)); + + memset(state, 0, sizeof(*state)); + + /* + * For each slot, generate a set of AEAD inputs: a key, a nonce, a + * plaintext, and some associated data. Then generate the corresponding + * ciphertext with concatenated auth tag. + */ + for (int i = 0; i < IRQ_TEST_NUM_BUFFERS; i++) { + struct aead_irq_test_slot *slot = &state->slots[i]; + int err; + + rand_bytes(slot->raw_key, sizeof(slot->raw_key)); + rand_bytes(slot->nonce, sizeof(slot->nonce)); + rand_bytes(slot->pt, sizeof(slot->pt)); + rand_bytes(slot->ad, sizeof(slot->ad)); + err = AEAD_PREPAREKEY(&slot->key, slot->raw_key, + sizeof(slot->raw_key), AEAD_MAX_TAG_LEN); + KUNIT_ASSERT_EQ(test, 0, err); + err = AEAD_ENCRYPT(slot->ct, slot->pt, sizeof(slot->pt), + &slot->ct[sizeof(slot->pt)], slot->ad, + sizeof(slot->ad), slot->nonce, + sizeof(slot->nonce), &slot->key); + KUNIT_ASSERT_EQ(test, 0, err); + } + + kunit_run_irq_test(test, aead_irq_test_func, 100000, state); +} + +/* Benchmark AEAD encryption and decryption on various data lengths. */ +static void benchmark_aead(struct kunit *test) +{ + static const size_t data_lens_to_test[] = { + 16, 64, 128, 256, 512, 1024, 1420, 4096, 16384, + }; + const size_t max_data_len = 16384; + const size_t ad_len = 16; + const size_t key_len = AEAD_MAX_KEY_LEN; + const size_t nonce_len = AEAD_MAX_NONCE_LEN; + const size_t tag_len = AEAD_MAX_TAG_LEN; + const u8 *raw_key, *nonce, *ad; + u8 *pt, *ct, *tag; + struct AEAD_KEY *key; + int err; + + if (!IS_ENABLED(CONFIG_CRYPTO_LIB_BENCHMARK)) + kunit_skip(test, "not enabled"); + + raw_key = aead_alloc_random_data(test, key_len); + nonce = aead_alloc_random_data(test, nonce_len); + ad = aead_alloc_random_data(test, ad_len); + pt = aead_alloc_random_data(test, max_data_len); + ct = alloc_buf(test, max_data_len); + tag = alloc_buf(test, tag_len); + + key = alloc_buf(test, sizeof(*key)); + err = AEAD_PREPAREKEY(key, raw_key, key_len, tag_len); + KUNIT_ASSERT_EQ(test, 0, err); + + /* Warm-up */ + for (size_t i = 0; i < 10000000; i += max_data_len) { + err = AEAD_ENCRYPT(ct, pt, max_data_len, tag, ad, ad_len, nonce, + nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + err = AEAD_DECRYPT(pt, ct, max_data_len, tag, ad, ad_len, nonce, + nonce_len, key); + KUNIT_ASSERT_EQ(test, 0, err); + } + + for (size_t i = 0; i < ARRAY_SIZE(data_lens_to_test); i++) { + size_t data_len = data_lens_to_test[i]; + size_t num_iters = 10000000 / (data_len + 128); + u64 t_enc, t_dec; + bool ok = true; + + KUNIT_ASSERT_LE(test, data_len, max_data_len); + + preempt_disable(); + + t_enc = ktime_get_ns(); + for (size_t j = 0; j < num_iters; j++) { + err = AEAD_ENCRYPT(ct, pt, data_len, tag, ad, ad_len, + nonce, nonce_len, key); + ok &= (err == 0); + } + t_enc = ktime_get_ns() - t_enc; + + t_dec = ktime_get_ns(); + for (size_t j = 0; j < num_iters; j++) { + err = AEAD_DECRYPT(pt, ct, data_len, tag, ad, ad_len, + nonce, nonce_len, key); + ok &= (err == 0); + } + t_dec = ktime_get_ns() - t_dec; + + preempt_enable(); + + KUNIT_ASSERT_TRUE_MSG(test, ok, "data_len=%zu", data_len); + + kunit_info(test, "data_len=%zu: enc %llu MB/s, dec %llu MB/s", + data_len, + div64_u64((u64)data_len * num_iters * 1000, + t_enc ?: 1), + div64_u64((u64)data_len * num_iters * 1000, + t_dec ?: 1)); + } +} + +/* clang-format off */ +#define AEAD_KUNIT_CASES \ + KUNIT_CASE(test_aead_all_key_lens), \ + KUNIT_CASE(test_aead_all_nonce_lens), \ + KUNIT_CASE(test_aead_all_tag_lens), \ + KUNIT_CASE(test_aead_incremental_updates), \ + KUNIT_CASE(test_aead_data_buffer_overruns), \ + KUNIT_CASE(test_aead_alignment_consistency), \ + KUNIT_CASE(test_aead_inplace), \ + KUNIT_CASE(test_aead_monte_carlo), \ + KUNIT_CASE(test_aead_interrupt_context), \ + KUNIT_CASE(benchmark_aead) From 2aeef50ecadca2fea0c96abed49452ff9b582b48 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 2 Aug 2026 16:30:04 -0700 Subject: [PATCH 07/11] lib/crypto: tests: Add KUnit test suite for AES-CCM Add a KUnit test suite for the AES-CCM library API. It consists of: - All the shared test cases from aead-test-template.h. These include extensive consistency tests, a "Monte-Carlo test", and a benchmark. - Tests against hardcoded AES-CCM test vectors from external sources. - Tests for CCM-specific message length validation. To generate the expected aes_ccm_monte_carlo_checksum[] value, add a script gen-aead-testvecs.py which computes it using python-cryptography. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260802233005.161467-5-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/.kunitconfig | 1 + lib/crypto/tests/Kconfig | 9 + lib/crypto/tests/Makefile | 1 + lib/crypto/tests/aes_ccm_kunit.c | 356 ++++++++++++++++++++++++++++ scripts/crypto/gen-aead-testvecs.py | 60 +++++ 5 files changed, 427 insertions(+) create mode 100644 lib/crypto/tests/aes_ccm_kunit.c create mode 100755 scripts/crypto/gen-aead-testvecs.py diff --git a/lib/crypto/.kunitconfig b/lib/crypto/.kunitconfig index 3efc854a2c08..f0237e602f28 100644 --- a/lib/crypto/.kunitconfig +++ b/lib/crypto/.kunitconfig @@ -3,6 +3,7 @@ CONFIG_KUNIT=y CONFIG_CRYPTO_LIB_ENABLE_ALL_FOR_KUNIT=y CONFIG_CRYPTO_LIB_AES_CBC_MACS_KUNIT_TEST=y +CONFIG_CRYPTO_LIB_AES_CCM_KUNIT_TEST=y CONFIG_CRYPTO_LIB_BLAKE2B_KUNIT_TEST=y CONFIG_CRYPTO_LIB_BLAKE2S_KUNIT_TEST=y CONFIG_CRYPTO_LIB_CHACHA20POLY1305_KUNIT_TEST=y diff --git a/lib/crypto/tests/Kconfig b/lib/crypto/tests/Kconfig index bc084dde424f..320236e996b1 100644 --- a/lib/crypto/tests/Kconfig +++ b/lib/crypto/tests/Kconfig @@ -9,6 +9,15 @@ config CRYPTO_LIB_AES_CBC_MACS_KUNIT_TEST KUnit tests for the AES-CMAC, AES-XCBC-MAC, and AES-CBC-MAC message authentication codes. +config CRYPTO_LIB_AES_CCM_KUNIT_TEST + tristate "KUnit tests for AES-CCM" if !KUNIT_ALL_TESTS + depends on KUNIT && CRYPTO_LIB_AES_CCM + default KUNIT_ALL_TESTS + select CRYPTO_LIB_BENCHMARK_VISIBLE + # This test uses BLAKE2s, but that's always built-in. + help + KUnit tests for the AES-CCM authenticated encryption algorithm. + config CRYPTO_LIB_BLAKE2B_KUNIT_TEST tristate "KUnit tests for BLAKE2b" if !KUNIT_ALL_TESTS depends on KUNIT && CRYPTO_LIB_BLAKE2B diff --git a/lib/crypto/tests/Makefile b/lib/crypto/tests/Makefile index a739413500b6..085852ba008b 100644 --- a/lib/crypto/tests/Makefile +++ b/lib/crypto/tests/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-or-later obj-$(CONFIG_CRYPTO_LIB_AES_CBC_MACS_KUNIT_TEST) += aes_cbc_macs_kunit.o +obj-$(CONFIG_CRYPTO_LIB_AES_CCM_KUNIT_TEST) += aes_ccm_kunit.o obj-$(CONFIG_CRYPTO_LIB_BLAKE2B_KUNIT_TEST) += blake2b_kunit.o obj-$(CONFIG_CRYPTO_LIB_BLAKE2S_KUNIT_TEST) += blake2s_kunit.o obj-$(CONFIG_CRYPTO_LIB_CHACHA20POLY1305_KUNIT_TEST) += chacha20poly1305_kunit.o diff --git a/lib/crypto/tests/aes_ccm_kunit.c b/lib/crypto/tests/aes_ccm_kunit.c new file mode 100644 index 000000000000..e8f2b3446017 --- /dev/null +++ b/lib/crypto/tests/aes_ccm_kunit.c @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * KUnit test suite for AES-CCM + * + * Copyright 2026 Google LLC + */ +#include +#include +#include "test-utils.h" + +/* AES-CCM test vectors from external sources */ +static const struct aes_ccm_testvec { + const char *name; + const char *key; + size_t key_len; + const char *nonce; + size_t nonce_len; + const char *ad; + size_t ad_len; + const char *ptext; + const char *ctext; + size_t data_len; + const char *tag; + size_t tag_len; +} aes_ccm_testvecs[] = { + { + .name = "RFC 3610 Packet Vector #1", + .key = "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7" + "\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + .key_len = 16, + .nonce = "\x00\x00\x00\x03\x02\x01\x00\xa0" + "\xa1\xa2\xa3\xa4\xa5", + .nonce_len = 13, + .ad = "\x00\x01\x02\x03\x04\x05\x06\x07", + .ad_len = 8, + .ptext = "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17" + "\x18\x19\x1a\x1b\x1c\x1d\x1e", + .ctext = "\x58\x8c\x97\x9a\x61\xc6\x63\xd2" + "\xf0\x66\xd0\xc2\xc0\xf9\x89\x80" + "\x6d\x5f\x6b\x61\xda\xc3\x84", + .data_len = 23, + .tag = "\x17\xe8\xd1\x2c\xfd\xf9\x26\xe0", + .tag_len = 8, + }, + { + .name = "RFC 3610 Packet Vector #5", + .key = "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7" + "\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + .key_len = 16, + .nonce = "\x00\x00\x00\x07\x06\x05\x04\xa0" + "\xa1\xa2\xa3\xa4\xa5", + .nonce_len = 13, + .ad = "\x00\x01\x02\x03\x04\x05\x06\x07" + "\x08\x09\x0a\x0b", + .ad_len = 12, + .ptext = "\x0c\x0d\x0e\x0f\x10\x11\x12\x13" + "\x14\x15\x16\x17\x18\x19\x1a\x1b" + "\x1c\x1d\x1e\x1f", + .ctext = "\xdc\xf1\xfb\x7b\x5d\x9e\x23\xfb" + "\x9d\x4e\x13\x12\x53\x65\x8a\xd8" + "\x6e\xbd\xca\x3e", + .data_len = 20, + .tag = "\x51\xe8\x3f\x07\x7d\x9c\x2d\x93", + .tag_len = 8, + }, + { + .name = "RFC 3610 Packet Vector #9", + .key = "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7" + "\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + .key_len = 16, + .nonce = "\x00\x00\x00\x0b\x0a\x09\x08\xa0" + "\xa1\xa2\xa3\xa4\xa5", + .nonce_len = 13, + .ad = "\x00\x01\x02\x03\x04\x05\x06\x07", + .ad_len = 8, + .ptext = "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17" + "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + "\x20", + .ctext = "\x82\x53\x1a\x60\xcc\x24\x94\x5a" + "\x4b\x82\x79\x18\x1a\xb5\xc8\x4d" + "\xf2\x1c\xe7\xf9\xb7\x3f\x42\xe1" + "\x97", + .data_len = 25, + .tag = "\xea\x9c\x07\xe5\x6b\x5e\xb1\x7e" + "\x5f\x4e", + .tag_len = 10, + }, + { + .name = "NIST SP 800-38C Example 1", + .key = "\x40\x41\x42\x43\x44\x45\x46\x47" + "\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + .key_len = 16, + .nonce = "\x10\x11\x12\x13\x14\x15\x16", + .nonce_len = 7, + .ad = "\x00\x01\x02\x03\x04\x05\x06\x07", + .ad_len = 8, + .ptext = "\x20\x21\x22\x23", + .ctext = "\x71\x62\x01\x5b", + .data_len = 4, + .tag = "\x4d\xac\x25\x5d", + .tag_len = 4, + }, + { + .name = "NIST SP 800-38C Example 2", + .key = "\x40\x41\x42\x43\x44\x45\x46\x47" + "\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + .key_len = 16, + .nonce = "\x10\x11\x12\x13\x14\x15\x16\x17", + .nonce_len = 8, + .ad = "\x00\x01\x02\x03\x04\x05\x06\x07" + "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + .ad_len = 16, + .ptext = "\x20\x21\x22\x23\x24\x25\x26\x27" + "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", + .ctext = "\xd2\xa1\xf0\xe0\x51\xea\x5f\x62" + "\x08\x1a\x77\x92\x07\x3d\x59\x3d", + .data_len = 16, + .tag = "\x1f\xc6\x4f\xbf\xac\xcd", + .tag_len = 6, + }, + { + .name = "NIST SP 800-38C Example 3", + .key = "\x40\x41\x42\x43\x44\x45\x46\x47" + "\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + .key_len = 16, + .nonce = "\x10\x11\x12\x13\x14\x15\x16\x17" + "\x18\x19\x1a\x1b", + .nonce_len = 12, + .ad = "\x00\x01\x02\x03\x04\x05\x06\x07" + "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13", + .ad_len = 20, + .ptext = "\x20\x21\x22\x23\x24\x25\x26\x27" + "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f" + "\x30\x31\x32\x33\x34\x35\x36\x37", + .ctext = "\xe3\xb2\x01\xa9\xf5\xb7\x1a\x7a" + "\x9b\x1c\xea\xec\xcd\x97\xe7\x0b" + "\x61\x76\xaa\xd9\xa4\x42\x8a\xa5", + .data_len = 24, + .tag = "\x48\x43\x92\xfb\xc1\xb0\x99\x51", + .tag_len = 8, + }, +}; + +static void test_aes_ccm_one_test_vector(struct kunit *test, + const struct aes_ccm_testvec *tv) +{ + u8 *ctext = alloc_buf(test, tv->data_len); + u8 *decrypted = alloc_buf(test, tv->data_len); + u8 *tag = alloc_buf(test, tv->tag_len); + struct aes_ccm_key key; + int err; + + err = aes_ccm_preparekey(&key, tv->key, tv->key_len, tv->tag_len); + KUNIT_ASSERT_EQ_MSG(test, 0, err, "Failed to prepare key for %s", + tv->name); + + err = aes_ccm_encrypt(ctext, tv->ptext, tv->data_len, tag, tv->ad, + tv->ad_len, tv->nonce, tv->nonce_len, &key); + KUNIT_ASSERT_EQ_MSG(test, 0, err, "Encryption failed for %s", tv->name); + KUNIT_ASSERT_MEMEQ_MSG(test, tv->ctext, ctext, tv->data_len, + "Wrong ciphertext for %s", tv->name); + KUNIT_ASSERT_MEMEQ_MSG(test, tag, tv->tag, tv->tag_len, + "Wrong tag for %s", tv->name); + + err = aes_ccm_decrypt(decrypted, ctext, tv->data_len, tag, tv->ad, + tv->ad_len, tv->nonce, tv->nonce_len, &key); + KUNIT_ASSERT_EQ_MSG(test, 0, err, "Decryption failed for %s", tv->name); + KUNIT_ASSERT_MEMEQ_MSG(test, tv->ptext, decrypted, tv->data_len, + "Wrong plaintext for %s", tv->name); +} + +static void test_aes_ccm_test_vectors(struct kunit *test) +{ + for (size_t i = 0; i < ARRAY_SIZE(aes_ccm_testvecs); i++) + test_aes_ccm_one_test_vector(test, &aes_ccm_testvecs[i]); +} + +/* + * Test NIST SP 800-38C Example 4, which uses a deterministically-generated + * 65536-byte associated data string. + */ +static void test_aes_ccm_nist_sp800_38c_example4(struct kunit *test) +{ + struct aes_ccm_testvec tv = { + .name = "NIST SP 800-38C Example 4", + .key_len = 16, + .nonce_len = 13, + .ad_len = 65536, + .ctext = "\x69\x91\x5d\xad\x1e\x84\xc6\x37" + "\x6a\x68\xc2\x96\x7e\x4d\xab\x61" + "\x5a\xe0\xfd\x1f\xae\xc4\x4c\xc4" + "\x84\x82\x85\x29\x46\x3c\xcf\x72", + .data_len = 32, + .tag = "\xb4\xac\x6b\xec\x93\xe8\x59\x8e" + "\x7f\x0d\xad\xbc\xea\x5b", + .tag_len = 14, + }; + u8 *key, *nonce, *ad, *ptext; + + key = alloc_buf(test, tv.key_len); + for (size_t i = 0; i < tv.key_len; i++) + key[i] = 0x40 + i; + + nonce = alloc_guarded_buf(test, tv.nonce_len); + for (size_t i = 0; i < tv.nonce_len; i++) + nonce[i] = 0x10 + i; + + ad = alloc_guarded_buf(test, tv.ad_len); + for (size_t i = 0; i < tv.ad_len; i++) + ad[i] = (u8)i; + + ptext = alloc_guarded_buf(test, tv.data_len); + for (size_t i = 0; i < tv.data_len; i++) + ptext[i] = 0x20 + i; + + tv.key = key; + tv.nonce = nonce; + tv.ad = ad; + tv.ptext = ptext; + + test_aes_ccm_one_test_vector(test, &tv); +} + +static const size_t aes_ccm_valid_key_lens[] = { 16, 24, 32 }; +#define AEAD_MAX_KEY_LEN 32 +#define AEAD_VALID_KEY_LENS aes_ccm_valid_key_lens + +static const size_t aes_ccm_valid_nonce_lens[] = { 7, 8, 9, 10, 11, 12, 13 }; +#define AEAD_MAX_NONCE_LEN 13 +#define AEAD_VALID_NONCE_LENS aes_ccm_valid_nonce_lens + +static const size_t aes_ccm_valid_tag_lens[] = { 4, 6, 8, 10, 12, 14, 16 }; +#define AEAD_MAX_TAG_LEN 16 +#define AEAD_VALID_TAG_LENS aes_ccm_valid_tag_lens + +#define AEAD_KEY aes_ccm_key +#define AEAD_CTX aes_ccm_ctx +#define AEAD_PREPAREKEY aes_ccm_preparekey +#define AEAD_ENCRYPT aes_ccm_encrypt +#define AEAD_DECRYPT aes_ccm_decrypt + +#define AEAD_INIT aes_ccm_init +#define AEAD_AUTH_UPDATE aes_ccm_auth_update +#define AEAD_ENCRYPT_UPDATE aes_ccm_encrypt_update +#define AEAD_DECRYPT_UPDATE aes_ccm_decrypt_update +#define AEAD_ENCRYPT_FINAL aes_ccm_encrypt_final +#define AEAD_DECRYPT_FINAL aes_ccm_decrypt_final + +/* This value was generated by gen-aead-testvecs.py. */ +static const u8 aes_ccm_monte_carlo_checksum[BLAKE2S_HASH_SIZE] = { + 0x70, 0x1c, 0xde, 0xa4, 0xe2, 0x03, 0x50, 0xb2, 0xf5, 0x9e, 0x61, + 0x66, 0xe4, 0xe5, 0x13, 0x1a, 0x00, 0x95, 0x34, 0x03, 0xb7, 0x61, + 0x2c, 0xdb, 0xc3, 0x15, 0x36, 0x84, 0x93, 0x7f, 0xb4, 0x5b, +}; +#define AEAD_MONTE_CARLO_CHECKSUM aes_ccm_monte_carlo_checksum + +#include "aead-test-template.h" + +/* + * Test that for each AES-CCM nonce length, the message length is validated + * against the correct corresponding maximum message length. + */ +static void test_aes_ccm_data_len_too_large(struct kunit *test) +{ + static const struct { + size_t nonce_len; + u64 max_data_len; + } lens[] = { + /* clang-format off */ + { 7, 0xffffffffffffffff }, /* U64_MAX */ + { 8, 0xffffffffffffff }, + { 9, 0xffffffffffff }, + { 10, 0xffffffffff }, + { 11, 0xffffffff }, + { 12, 0xffffff }, + { 13, 0xffff }, + /* clang-format on */ + }; + u8 nonce[13] = {}; + u8 raw_key[AES_KEYSIZE_256] = {}; + int err; + struct aes_ccm_key *key = alloc_buf(test, sizeof(*key)); + struct aes_ccm_ctx ctx; + + err = aes_ccm_preparekey(key, raw_key, sizeof(raw_key), 16); + KUNIT_ASSERT_EQ(test, 0, err); + + for (size_t i = 0; i < ARRAY_SIZE(lens); i++) { + size_t nonce_len = lens[i].nonce_len; + u64 max_data_len = lens[i].max_data_len; + + /* data_len <= max_data_len should be accepted. */ + err = aes_ccm_init(&ctx, 0, 0, nonce, nonce_len, key); + KUNIT_ASSERT_EQ_MSG( + test, 0, err, + "data_len=0 wasn't accepted with nonce_len=%zu", + nonce_len); + err = aes_ccm_init(&ctx, max_data_len, 0, nonce, nonce_len, + key); + KUNIT_ASSERT_EQ_MSG( + test, 0, err, + "data_len=%llu wasn't accepted with nonce_len=%zu", + max_data_len, nonce_len); + + /* data_len > max_data_len should be rejected. */ + if (max_data_len == U64_MAX) + continue; + err = aes_ccm_init(&ctx, max_data_len + 1, 0, nonce, nonce_len, + key); + KUNIT_ASSERT_EQ_MSG( + test, -EOVERFLOW, err, + "data_len=%llu wasn't rejected with -EOVERFLOW with nonce_len=%zu (aes_ccm_init)", + max_data_len + 1, nonce_len); + if (max_data_len + 1 <= SIZE_MAX) { + err = aes_ccm_encrypt(NULL, NULL, max_data_len + 1, + NULL, NULL, 0, nonce, nonce_len, + key); + KUNIT_ASSERT_EQ_MSG( + test, -EOVERFLOW, err, + "data_len=%llu wasn't rejected with -EOVERFLOW with nonce_len=%zu (aes_ccm_encrypt)", + max_data_len + 1, nonce_len); + err = aes_ccm_decrypt(NULL, NULL, max_data_len + 1, + NULL, NULL, 0, nonce, nonce_len, + key); + KUNIT_ASSERT_EQ_MSG( + test, -EOVERFLOW, err, + "data_len=%llu wasn't rejected with -EOVERFLOW with nonce_len=%zu (aes_ccm_decrypt)", + max_data_len + 1, nonce_len); + } + err = aes_ccm_init(&ctx, U64_MAX, 0, nonce, nonce_len, key); + KUNIT_ASSERT_EQ_MSG( + test, -EOVERFLOW, err, + "data_len=U64_MAX wasn't rejected with -EOVERFLOW with nonce_len=%zu (aes_ccm_init)", + nonce_len); + } +} + +static struct kunit_case aes_ccm_test_cases[] = { + KUNIT_CASE(test_aes_ccm_test_vectors), + KUNIT_CASE(test_aes_ccm_nist_sp800_38c_example4), + KUNIT_CASE(test_aes_ccm_data_len_too_large), + AEAD_KUNIT_CASES, + {}, +}; + +static struct kunit_suite aes_ccm_test_suite = { + .name = "aes_ccm", + .test_cases = aes_ccm_test_cases, +}; +kunit_test_suite(aes_ccm_test_suite); + +MODULE_DESCRIPTION("KUnit tests and benchmark for AES-CCM"); +MODULE_LICENSE("GPL"); diff --git a/scripts/crypto/gen-aead-testvecs.py b/scripts/crypto/gen-aead-testvecs.py new file mode 100755 index 000000000000..048043735819 --- /dev/null +++ b/scripts/crypto/gen-aead-testvecs.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Script that generates known-good data used in the AEAD tests. +# +# Requires that python-cryptography be installed. +# +# Copyright 2026 Google LLC + +import hashlib +import sys +import cryptography.hazmat.primitives.ciphers.aead + + +# Deterministically generate 'length' random bytes. +def rand_bytes(length): + seed = length + out = [] + for _ in range(length): + seed = (seed * 25214903917 + 11) % 2**48 + out.append((seed >> 16) % 256) + return bytes(out) + + +# Deterministically generate many different AEAD inputs using exactly the same +# method that the test uses; encrypt them using an independent implementation of +# the algorithm; compute the checksum of all the resulting (ciphertext, authtag) +# pairs concatenated to each other; and print the checksum as a C struct. +def gen_monte_carlo_checksum(alg): + blake2s = hashlib.blake2s() + for data_len in range(1025): + ad_len = data_len % 293 + pt = rand_bytes(data_len) + ad = rand_bytes(ad_len) + if alg == "aes-ccm": + key_len = [16, 24, 32][data_len % 3] + key = rand_bytes(key_len) + nonce = rand_bytes([7, 8, 9, 10, 11, 12, 13][data_len % 7]) + tag_len = [4, 6, 8, 10, 12, 14, 16][data_len % 7] + ccm = cryptography.hazmat.primitives.ciphers.aead.AESCCM( + key, tag_length=tag_len + ) + ct_and_tag = ccm.encrypt(nonce, pt, ad) + + blake2s.update(ct_and_tag) + + name = f"{alg.replace('-', '_')}_monte_carlo_checksum" + value = blake2s.digest() + print(f"static const u8 {name}[BLAKE2S_HASH_SIZE] = {{") + for i in range(0, len(value), 11): + line = "\t" + "".join(f"0x{b:02x}, " for b in value[i : i + 11]) + print(f"{line.rstrip()}") + print("};") + + +if len(sys.argv) != 2 or sys.argv[1] not in ("aes-ccm"): + sys.stderr.write("Usage: gen-aead-testvecs.py [aes-ccm]\n") + sys.exit(1) + +gen_monte_carlo_checksum(sys.argv[1]) From b09bd2d92ee1bbbf6db4533d5d9d2a7b39ae48c4 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 2 Aug 2026 16:30:05 -0700 Subject: [PATCH 08/11] lib/crypto: tests: Add KUnit test suite for AES-GCM Add a KUnit test suite for the AES-GCM library API. It consists of: - All the shared test cases from aead-test-template.h. These include extensive consistency tests, a "Monte-Carlo test", and a benchmark. - Tests against hardcoded AES-GCM test vectors from external sources. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260802233005.161467-6-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/.kunitconfig | 1 + lib/crypto/tests/Kconfig | 9 + lib/crypto/tests/Makefile | 1 + lib/crypto/tests/aes_gcm_kunit.c | 472 ++++++++++++++++++++++++++++ scripts/crypto/gen-aead-testvecs.py | 13 +- 5 files changed, 494 insertions(+), 2 deletions(-) create mode 100644 lib/crypto/tests/aes_gcm_kunit.c diff --git a/lib/crypto/.kunitconfig b/lib/crypto/.kunitconfig index f0237e602f28..60e0a77f9889 100644 --- a/lib/crypto/.kunitconfig +++ b/lib/crypto/.kunitconfig @@ -4,6 +4,7 @@ CONFIG_CRYPTO_LIB_ENABLE_ALL_FOR_KUNIT=y CONFIG_CRYPTO_LIB_AES_CBC_MACS_KUNIT_TEST=y CONFIG_CRYPTO_LIB_AES_CCM_KUNIT_TEST=y +CONFIG_CRYPTO_LIB_AES_GCM_KUNIT_TEST=y CONFIG_CRYPTO_LIB_BLAKE2B_KUNIT_TEST=y CONFIG_CRYPTO_LIB_BLAKE2S_KUNIT_TEST=y CONFIG_CRYPTO_LIB_CHACHA20POLY1305_KUNIT_TEST=y diff --git a/lib/crypto/tests/Kconfig b/lib/crypto/tests/Kconfig index 320236e996b1..e121114624da 100644 --- a/lib/crypto/tests/Kconfig +++ b/lib/crypto/tests/Kconfig @@ -18,6 +18,15 @@ config CRYPTO_LIB_AES_CCM_KUNIT_TEST help KUnit tests for the AES-CCM authenticated encryption algorithm. +config CRYPTO_LIB_AES_GCM_KUNIT_TEST + tristate "KUnit tests for AES-GCM" if !KUNIT_ALL_TESTS + depends on KUNIT && CRYPTO_LIB_AES_GCM + default KUNIT_ALL_TESTS + select CRYPTO_LIB_BENCHMARK_VISIBLE + # This test uses BLAKE2s, but that's always built-in. + help + KUnit tests for the AES-GCM authenticated encryption algorithm. + config CRYPTO_LIB_BLAKE2B_KUNIT_TEST tristate "KUnit tests for BLAKE2b" if !KUNIT_ALL_TESTS depends on KUNIT && CRYPTO_LIB_BLAKE2B diff --git a/lib/crypto/tests/Makefile b/lib/crypto/tests/Makefile index 085852ba008b..c97c1d78784a 100644 --- a/lib/crypto/tests/Makefile +++ b/lib/crypto/tests/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_CRYPTO_LIB_AES_CBC_MACS_KUNIT_TEST) += aes_cbc_macs_kunit.o obj-$(CONFIG_CRYPTO_LIB_AES_CCM_KUNIT_TEST) += aes_ccm_kunit.o +obj-$(CONFIG_CRYPTO_LIB_AES_GCM_KUNIT_TEST) += aes_gcm_kunit.o obj-$(CONFIG_CRYPTO_LIB_BLAKE2B_KUNIT_TEST) += blake2b_kunit.o obj-$(CONFIG_CRYPTO_LIB_BLAKE2S_KUNIT_TEST) += blake2s_kunit.o obj-$(CONFIG_CRYPTO_LIB_CHACHA20POLY1305_KUNIT_TEST) += chacha20poly1305_kunit.o diff --git a/lib/crypto/tests/aes_gcm_kunit.c b/lib/crypto/tests/aes_gcm_kunit.c new file mode 100644 index 000000000000..6959d86b316a --- /dev/null +++ b/lib/crypto/tests/aes_gcm_kunit.c @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * KUnit test suite for AES-GCM + * + * Copyright 2026 Google LLC + */ +#include +#include +#include "test-utils.h" + +/* The kernel's AES-GCM implementation supports only 12-byte nonces. */ +#define AES_GCM_NONCE_LEN 12 + +/* + * AES-GCM test vectors from the original paper "The Galois/Counter Mode of + * Operation (GCM)" by McGrew & Viega. Vectors with nonce lengths other than 12 + * bytes are excluded. + */ +static const struct aes_gcm_testvec { + const char *name; + const char *key; + size_t key_len; + const char *nonce; + size_t nonce_len; + const char *ad; + size_t ad_len; + const char *ptext; + const char *ctext; + size_t data_len; + const char *tag; + size_t tag_len; +} aes_gcm_testvecs[] = { + /* AES-128 Test Vectors */ + { + .name = "McGrew & Viega Test Case 1", + .key = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00", + .key_len = 16, + .nonce = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00", + .nonce_len = 12, + .ptext = "", + .ctext = "", + .data_len = 0, + .ad = "", + .ad_len = 0, + .tag = "\x58\xe2\xfc\xce\xfa\x7e\x30\x61" + "\x36\x7f\x1d\x57\xa4\xe7\x45\x5a", + .tag_len = 16, + }, + { + .name = "McGrew & Viega Test Case 2", + .key = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00", + .key_len = 16, + .nonce = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00", + .nonce_len = 12, + .ptext = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00", + .ctext = "\x03\x88\xda\xce\x60\xb6\xa3\x92" + "\xf3\x28\xc2\xb9\x71\xb2\xfe\x78", + .data_len = 16, + .ad = "", + .ad_len = 0, + .tag = "\xab\x6e\x47\xd4\x2c\xec\x13\xbd" + "\xf5\x3a\x67\xb2\x12\x57\xbd\xdf", + .tag_len = 16, + }, + { + .name = "McGrew & Viega Test Case 3", + .key = "\xfe\xff\xe9\x92\x86\x65\x73\x1c" + "\x6d\x6a\x8f\x94\x67\x30\x83\x08", + .key_len = 16, + .nonce = "\xca\xfe\xba\xbe\xfa\xce\xdb\xad" + "\xde\xca\xf8\x88", + .nonce_len = 12, + .ptext = "\xd9\x31\x32\x25\xf8\x84\x06\xe5" + "\xa5\x59\x09\xc5\xaf\xf5\x26\x9a" + "\x86\xa7\xa9\x53\x15\x34\xf7\xda" + "\x2e\x4c\x30\x3d\x8a\x31\x8a\x72" + "\x1c\x3c\x0c\x95\x95\x68\x09\x53" + "\x2f\xcf\x0e\x24\x49\xa6\xb5\x25" + "\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57" + "\xba\x63\x7b\x39\x1a\xaf\xd2\x55", + .ctext = "\x42\x83\x1e\xc2\x21\x77\x74\x24" + "\x4b\x72\x21\xb7\x84\xd0\xd4\x9c" + "\xe3\xaa\x21\x2f\x2c\x02\xa4\xe0" + "\x35\xc1\x7e\x23\x29\xac\xa1\x2e" + "\x21\xd5\x14\xb2\x54\x66\x93\x1c" + "\x7d\x8f\x6a\x5a\xac\x84\xaa\x05" + "\x1b\xa3\x0b\x39\x6a\x0a\xac\x97" + "\x3d\x58\xe0\x91\x47\x3f\x59\x85", + .data_len = 64, + .ad = "", + .ad_len = 0, + .tag = "\x4d\x5c\x2a\xf3\x27\xcd\x64\xa6" + "\x2c\xf3\x5a\xbd\x2b\xa6\xfa\xb4", + .tag_len = 16, + }, + { + .name = "McGrew & Viega Test Case 4", + .key = "\xfe\xff\xe9\x92\x86\x65\x73\x1c" + "\x6d\x6a\x8f\x94\x67\x30\x83\x08", + .key_len = 16, + .nonce = "\xca\xfe\xba\xbe\xfa\xce\xdb\xad" + "\xde\xca\xf8\x88", + .nonce_len = 12, + .ptext = "\xd9\x31\x32\x25\xf8\x84\x06\xe5" + "\xa5\x59\x09\xc5\xaf\xf5\x26\x9a" + "\x86\xa7\xa9\x53\x15\x34\xf7\xda" + "\x2e\x4c\x30\x3d\x8a\x31\x8a\x72" + "\x1c\x3c\x0c\x95\x95\x68\x09\x53" + "\x2f\xcf\x0e\x24\x49\xa6\xb5\x25" + "\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57" + "\xba\x63\x7b\x39", + .ctext = "\x42\x83\x1e\xc2\x21\x77\x74\x24" + "\x4b\x72\x21\xb7\x84\xd0\xd4\x9c" + "\xe3\xaa\x21\x2f\x2c\x02\xa4\xe0" + "\x35\xc1\x7e\x23\x29\xac\xa1\x2e" + "\x21\xd5\x14\xb2\x54\x66\x93\x1c" + "\x7d\x8f\x6a\x5a\xac\x84\xaa\x05" + "\x1b\xa3\x0b\x39\x6a\x0a\xac\x97" + "\x3d\x58\xe0\x91", + .data_len = 60, + .ad = "\xfe\xed\xfa\xce\xde\xad\xbe\xef" + "\xfe\xed\xfa\xce\xde\xad\xbe\xef" + "\xab\xad\xda\xd2", + .ad_len = 20, + .tag = "\x5b\xc9\x4f\xbc\x32\x21\xa5\xdb" + "\x94\xfa\xe9\x5a\xe7\x12\x1a\x47", + .tag_len = 16, + }, + + /* AES-192 Test Vectors */ + { + .name = "McGrew & Viega Test Case 7", + .key = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00", + .key_len = 24, + .nonce = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00", + .nonce_len = 12, + .ptext = "", + .ctext = "", + .data_len = 0, + .ad = "", + .ad_len = 0, + .tag = "\xcd\x33\xb2\x8a\xc7\x73\xf7\x4b" + "\xa0\x0e\xd1\xf3\x12\x57\x24\x35", + .tag_len = 16, + }, + { + .name = "McGrew & Viega Test Case 8", + .key = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00", + .key_len = 24, + .nonce = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00", + .nonce_len = 12, + .ptext = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00", + .ctext = "\x98\xe7\x24\x7c\x07\xf0\xfe\x41" + "\x1c\x26\x7e\x43\x84\xb0\xf6\x00", + .data_len = 16, + .ad = "", + .ad_len = 0, + .tag = "\x2f\xf5\x8d\x80\x03\x39\x27\xab" + "\x8e\xf4\xd4\x58\x75\x14\xf0\xfb", + .tag_len = 16, + }, + { + .name = "McGrew & Viega Test Case 9", + .key = "\xfe\xff\xe9\x92\x86\x65\x73\x1c" + "\x6d\x6a\x8f\x94\x67\x30\x83\x08" + "\xfe\xff\xe9\x92\x86\x65\x73\x1c", + .key_len = 24, + .nonce = "\xca\xfe\xba\xbe\xfa\xce\xdb\xad" + "\xde\xca\xf8\x88", + .nonce_len = 12, + .ptext = "\xd9\x31\x32\x25\xf8\x84\x06\xe5" + "\xa5\x59\x09\xc5\xaf\xf5\x26\x9a" + "\x86\xa7\xa9\x53\x15\x34\xf7\xda" + "\x2e\x4c\x30\x3d\x8a\x31\x8a\x72" + "\x1c\x3c\x0c\x95\x95\x68\x09\x53" + "\x2f\xcf\x0e\x24\x49\xa6\xb5\x25" + "\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57" + "\xba\x63\x7b\x39\x1a\xaf\xd2\x55", + .ctext = "\x39\x80\xca\x0b\x3c\x00\xe8\x41" + "\xeb\x06\xfa\xc4\x87\x2a\x27\x57" + "\x85\x9e\x1c\xea\xa6\xef\xd9\x84" + "\x62\x85\x93\xb4\x0c\xa1\xe1\x9c" + "\x7d\x77\x3d\x00\xc1\x44\xc5\x25" + "\xac\x61\x9d\x18\xc8\x4a\x3f\x47" + "\x18\xe2\x44\x8b\x2f\xe3\x24\xd9" + "\xcc\xda\x27\x10\xac\xad\xe2\x56", + .data_len = 64, + .ad = "", + .ad_len = 0, + .tag = "\x99\x24\xa7\xc8\x58\x73\x36\xbf" + "\xb1\x18\x02\x4d\xb8\x67\x4a\x14", + .tag_len = 16, + }, + { + .name = "McGrew & Viega Test Case 10", + .key = "\xfe\xff\xe9\x92\x86\x65\x73\x1c" + "\x6d\x6a\x8f\x94\x67\x30\x83\x08" + "\xfe\xff\xe9\x92\x86\x65\x73\x1c", + .key_len = 24, + .nonce = "\xca\xfe\xba\xbe\xfa\xce\xdb\xad" + "\xde\xca\xf8\x88", + .nonce_len = 12, + .ptext = "\xd9\x31\x32\x25\xf8\x84\x06\xe5" + "\xa5\x59\x09\xc5\xaf\xf5\x26\x9a" + "\x86\xa7\xa9\x53\x15\x34\xf7\xda" + "\x2e\x4c\x30\x3d\x8a\x31\x8a\x72" + "\x1c\x3c\x0c\x95\x95\x68\x09\x53" + "\x2f\xcf\x0e\x24\x49\xa6\xb5\x25" + "\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57" + "\xba\x63\x7b\x39", + .ctext = "\x39\x80\xca\x0b\x3c\x00\xe8\x41" + "\xeb\x06\xfa\xc4\x87\x2a\x27\x57" + "\x85\x9e\x1c\xea\xa6\xef\xd9\x84" + "\x62\x85\x93\xb4\x0c\xa1\xe1\x9c" + "\x7d\x77\x3d\x00\xc1\x44\xc5\x25" + "\xac\x61\x9d\x18\xc8\x4a\x3f\x47" + "\x18\xe2\x44\x8b\x2f\xe3\x24\xd9" + "\xcc\xda\x27\x10", + .data_len = 60, + .ad = "\xfe\xed\xfa\xce\xde\xad\xbe\xef" + "\xfe\xed\xfa\xce\xde\xad\xbe\xef" + "\xab\xad\xda\xd2", + .ad_len = 20, + .tag = "\x25\x19\x49\x8e\x80\xf1\x47\x8f" + "\x37\xba\x55\xbd\x6d\x27\x61\x8c", + .tag_len = 16, + }, + + /* AES-256 Test Vectors */ + { + .name = "McGrew & Viega Test Case 13", + .key = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00", + .key_len = 32, + .nonce = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00", + .nonce_len = 12, + .ptext = "", + .ctext = "", + .data_len = 0, + .ad = "", + .ad_len = 0, + .tag = "\x53\x0f\x8a\xfb\xc7\x45\x36\xb9" + "\xa9\x63\xb4\xf1\xc4\xcb\x73\x8b", + .tag_len = 16, + }, + { + .name = "McGrew & Viega Test Case 14", + .key = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00", + .key_len = 32, + .nonce = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00", + .nonce_len = 12, + .ptext = "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00", + .ctext = "\xce\xa7\x40\x3d\x4d\x60\x6b\x6e" + "\x07\x4e\xc5\xd3\xba\xf3\x9d\x18", + .data_len = 16, + .ad = "", + .ad_len = 0, + .tag = "\xd0\xd1\xc8\xa7\x99\x99\x6b\xf0" + "\x26\x5b\x98\xb5\xd4\x8a\xb9\x19", + .tag_len = 16, + }, + { + .name = "McGrew & Viega Test Case 15", + .key = "\xfe\xff\xe9\x92\x86\x65\x73\x1c" + "\x6d\x6a\x8f\x94\x67\x30\x83\x08" + "\xfe\xff\xe9\x92\x86\x65\x73\x1c" + "\x6d\x6a\x8f\x94\x67\x30\x83\x08", + .key_len = 32, + .nonce = "\xca\xfe\xba\xbe\xfa\xce\xdb\xad" + "\xde\xca\xf8\x88", + .nonce_len = 12, + .ptext = "\xd9\x31\x32\x25\xf8\x84\x06\xe5" + "\xa5\x59\x09\xc5\xaf\xf5\x26\x9a" + "\x86\xa7\xa9\x53\x15\x34\xf7\xda" + "\x2e\x4c\x30\x3d\x8a\x31\x8a\x72" + "\x1c\x3c\x0c\x95\x95\x68\x09\x53" + "\x2f\xcf\x0e\x24\x49\xa6\xb5\x25" + "\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57" + "\xba\x63\x7b\x39\x1a\xaf\xd2\x55", + .ctext = "\x52\x2d\xc1\xf0\x99\x56\x7d\x07" + "\xf4\x7f\x37\xa3\x2a\x84\x42\x7d" + "\x64\x3a\x8c\xdc\xbf\xe5\xc0\xc9" + "\x75\x98\xa2\xbd\x25\x55\xd1\xaa" + "\x8c\xb0\x8e\x48\x59\x0d\xbb\x3d" + "\xa7\xb0\x8b\x10\x56\x82\x88\x38" + "\xc5\xf6\x1e\x63\x93\xba\x7a\x0a" + "\xbc\xc9\xf6\x62\x89\x80\x15\xad", + .data_len = 64, + .ad = "", + .ad_len = 0, + .tag = "\xb0\x94\xda\xc5\xd9\x34\x71\xbd" + "\xec\x1a\x50\x22\x70\xe3\xcc\x6c", + .tag_len = 16, + }, + { + .name = "McGrew & Viega Test Case 16", + .key = "\xfe\xff\xe9\x92\x86\x65\x73\x1c" + "\x6d\x6a\x8f\x94\x67\x30\x83\x08" + "\xfe\xff\xe9\x92\x86\x65\x73\x1c" + "\x6d\x6a\x8f\x94\x67\x30\x83\x08", + .key_len = 32, + .nonce = "\xca\xfe\xba\xbe\xfa\xce\xdb\xad" + "\xde\xca\xf8\x88", + .nonce_len = 12, + .ptext = "\xd9\x31\x32\x25\xf8\x84\x06\xe5" + "\xa5\x59\x09\xc5\xaf\xf5\x26\x9a" + "\x86\xa7\xa9\x53\x15\x34\xf7\xda" + "\x2e\x4c\x30\x3d\x8a\x31\x8a\x72" + "\x1c\x3c\x0c\x95\x95\x68\x09\x53" + "\x2f\xcf\x0e\x24\x49\xa6\xb5\x25" + "\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57" + "\xba\x63\x7b\x39", + .ctext = "\x52\x2d\xc1\xf0\x99\x56\x7d\x07" + "\xf4\x7f\x37\xa3\x2a\x84\x42\x7d" + "\x64\x3a\x8c\xdc\xbf\xe5\xc0\xc9" + "\x75\x98\xa2\xbd\x25\x55\xd1\xaa" + "\x8c\xb0\x8e\x48\x59\x0d\xbb\x3d" + "\xa7\xb0\x8b\x10\x56\x82\x88\x38" + "\xc5\xf6\x1e\x63\x93\xba\x7a\x0a" + "\xbc\xc9\xf6\x62", + .data_len = 60, + .ad = "\xfe\xed\xfa\xce\xde\xad\xbe\xef" + "\xfe\xed\xfa\xce\xde\xad\xbe\xef" + "\xab\xad\xda\xd2", + .ad_len = 20, + .tag = "\x76\xfc\x6e\xce\x0f\x4e\x17\x68" + "\xcd\xdf\x88\x53\xbb\x2d\x55\x1b", + .tag_len = 16, + }, +}; + +static void test_aes_gcm_one_test_vector(struct kunit *test, + const struct aes_gcm_testvec *tv) +{ + u8 *ctext = alloc_buf(test, tv->data_len); + u8 *decrypted = alloc_buf(test, tv->data_len); + u8 *tag = alloc_buf(test, tv->tag_len); + struct aes_gcm_key key; + int err; + + KUNIT_ASSERT_EQ(test, AES_GCM_NONCE_LEN, tv->nonce_len); + + err = aes_gcm_preparekey(&key, tv->key, tv->key_len, tv->tag_len); + KUNIT_ASSERT_EQ_MSG(test, 0, err, "Failed to prepare key for %s", + tv->name); + + aes_gcm_encrypt(ctext, tv->ptext, tv->data_len, tag, tv->ad, tv->ad_len, + tv->nonce, &key); + KUNIT_ASSERT_MEMEQ_MSG(test, tv->ctext, ctext, tv->data_len, + "Wrong ciphertext for %s", tv->name); + KUNIT_ASSERT_MEMEQ_MSG(test, tag, tv->tag, tv->tag_len, + "Wrong tag for %s", tv->name); + + err = aes_gcm_decrypt(decrypted, ctext, tv->data_len, tag, tv->ad, + tv->ad_len, tv->nonce, &key); + KUNIT_ASSERT_EQ_MSG(test, 0, err, "Decryption failed for %s", tv->name); + KUNIT_ASSERT_MEMEQ_MSG(test, tv->ptext, decrypted, tv->data_len, + "Wrong plaintext for %s", tv->name); +} + +static void test_aes_gcm_test_vectors(struct kunit *test) +{ + for (size_t i = 0; i < ARRAY_SIZE(aes_gcm_testvecs); i++) + test_aes_gcm_one_test_vector(test, &aes_gcm_testvecs[i]); +} + +static int aes_gcm_init_test(struct aes_gcm_ctx *ctx, u64 data_len, u64 ad_len, + const u8 *nonce, size_t nonce_len, + const struct aes_gcm_key *key) +{ + if (nonce_len != AES_GCM_NONCE_LEN) + return -EINVAL; + /* + * Ignore data_len and ad_len. Incremental AES-GCM doesn't need them at + * initialization time. + */ + aes_gcm_init(ctx, nonce, key); + return 0; +} + +static int aes_gcm_encrypt_test(u8 *dst, const u8 *src, size_t data_len, + u8 *authtag, const u8 *ad, size_t ad_len, + const u8 *nonce, size_t nonce_len, + const struct aes_gcm_key *key) +{ + if (nonce_len != AES_GCM_NONCE_LEN) + return -EINVAL; + /* aes_gcm_encrypt() returns void. */ + aes_gcm_encrypt(dst, src, data_len, authtag, ad, ad_len, nonce, key); + return 0; +} + +static int aes_gcm_decrypt_test(u8 *dst, const u8 *src, size_t data_len, + const u8 *authtag, const u8 *ad, size_t ad_len, + const u8 *nonce, size_t nonce_len, + const struct aes_gcm_key *key) +{ + if (nonce_len != AES_GCM_NONCE_LEN) + return -EINVAL; + return aes_gcm_decrypt(dst, src, data_len, authtag, ad, ad_len, nonce, + key); +} + +static const size_t aes_gcm_valid_key_lens[] = { 16, 24, 32 }; +#define AEAD_MAX_KEY_LEN 32 +#define AEAD_VALID_KEY_LENS aes_gcm_valid_key_lens + +static const size_t aes_gcm_valid_nonce_lens[] = { AES_GCM_NONCE_LEN }; +#define AEAD_MAX_NONCE_LEN AES_GCM_NONCE_LEN +#define AEAD_VALID_NONCE_LENS aes_gcm_valid_nonce_lens + +static const size_t aes_gcm_valid_tag_lens[] = { 4, 8, 12, 13, 14, 15, 16 }; +#define AEAD_MAX_TAG_LEN 16 +#define AEAD_VALID_TAG_LENS aes_gcm_valid_tag_lens + +#define AEAD_KEY aes_gcm_key +#define AEAD_CTX aes_gcm_ctx +#define AEAD_PREPAREKEY aes_gcm_preparekey +#define AEAD_ENCRYPT aes_gcm_encrypt_test +#define AEAD_DECRYPT aes_gcm_decrypt_test + +#define AEAD_INIT aes_gcm_init_test +#define AEAD_AUTH_UPDATE aes_gcm_auth_update +#define AEAD_ENCRYPT_UPDATE aes_gcm_encrypt_update +#define AEAD_DECRYPT_UPDATE aes_gcm_decrypt_update +#define AEAD_ENCRYPT_FINAL aes_gcm_encrypt_final +#define AEAD_DECRYPT_FINAL aes_gcm_decrypt_final + +/* This value was generated by gen-aead-testvecs.py. */ +static const u8 aes_gcm_monte_carlo_checksum[BLAKE2S_HASH_SIZE] = { + 0x6d, 0xd0, 0x6e, 0x6b, 0xde, 0x3e, 0x92, 0x9f, 0xae, 0x1f, 0xf1, + 0x84, 0x99, 0x5a, 0x9e, 0x7b, 0xfe, 0x20, 0x9e, 0x22, 0x7c, 0x5f, + 0x15, 0xb3, 0x59, 0x89, 0xd0, 0xb7, 0x74, 0x5d, 0xd2, 0xa5, +}; +#define AEAD_MONTE_CARLO_CHECKSUM aes_gcm_monte_carlo_checksum + +#include "aead-test-template.h" + +static struct kunit_case aes_gcm_test_cases[] = { + KUNIT_CASE(test_aes_gcm_test_vectors), + AEAD_KUNIT_CASES, + {}, +}; + +static struct kunit_suite aes_gcm_test_suite = { + .name = "aes_gcm", + .test_cases = aes_gcm_test_cases, +}; +kunit_test_suite(aes_gcm_test_suite); + +MODULE_DESCRIPTION("KUnit tests and benchmark for AES-GCM"); +MODULE_LICENSE("GPL"); diff --git a/scripts/crypto/gen-aead-testvecs.py b/scripts/crypto/gen-aead-testvecs.py index 048043735819..d77d646f75b5 100755 --- a/scripts/crypto/gen-aead-testvecs.py +++ b/scripts/crypto/gen-aead-testvecs.py @@ -41,6 +41,15 @@ def gen_monte_carlo_checksum(alg): key, tag_length=tag_len ) ct_and_tag = ccm.encrypt(nonce, pt, ad) + elif alg == "aes-gcm": + key_len = [16, 24, 32][data_len % 3] + key = rand_bytes(key_len) + nonce = rand_bytes(12) + tag_len = [4, 8, 12, 13, 14, 15, 16][data_len % 7] + gcm = cryptography.hazmat.primitives.ciphers.aead.AESGCM(key) + # python-cryptography supports only 16-byte GCM tags. However, in + # GCM, shorter tags are simply truncated. Do that below. + ct_and_tag = gcm.encrypt(nonce, pt, ad)[: data_len + tag_len] blake2s.update(ct_and_tag) @@ -53,8 +62,8 @@ def gen_monte_carlo_checksum(alg): print("};") -if len(sys.argv) != 2 or sys.argv[1] not in ("aes-ccm"): - sys.stderr.write("Usage: gen-aead-testvecs.py [aes-ccm]\n") +if len(sys.argv) != 2 or sys.argv[1] not in ("aes-ccm", "aes-gcm"): + sys.stderr.write("Usage: gen-aead-testvecs.py [aes-ccm|aes-gcm]\n") sys.exit(1) gen_monte_carlo_checksum(sys.argv[1]) From faa6c4c4e4ac69926564688a926105621295d613 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 3 Aug 2026 11:18:41 -0700 Subject: [PATCH 09/11] kunit: irq: Continue increasing hrtimer interval for longer Currently, kunit_irq_test_timer_func() stops increasing the hrtimer interval as soon as some forward progress is made in each of softirq and task context. Update it to use a more aggressive strategy: increase the interval as long as the hrtimer is running significantly faster than either context. This resolves an occasional hang in the CRC and crypto library tests under qemu-system-s390x. It was exposed by the change in the default preemption model on s390 from NONE to LAZY. That seems to have exposed the issue by allowing some forward progress to be made while the actual system timer tick is still starved, preventing jiffies from increasing or the task context from making much progress towards max_iterations. Fixes: 201ceb94aa1d ("kunit: irq: Ensure timer doesn't fire too frequently") Cc: stable@vger.kernel.org Reviewed-by: David Gow Acked-by: Ard Biesheuvel Link: https://patch.msgid.link/20260803181842.44648-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- include/kunit/run-in-irq-context.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/kunit/run-in-irq-context.h b/include/kunit/run-in-irq-context.h index bfe60d6cf28d..3802b6fb218e 100644 --- a/include/kunit/run-in-irq-context.h +++ b/include/kunit/run-in-irq-context.h @@ -38,11 +38,13 @@ static enum hrtimer_restart kunit_irq_test_timer_func(struct hrtimer *timer) softirq_calls = atomic_read(&state->softirq_func_calls); /* - * If the timer is firing too often for the softirq or task to ever have - * a chance to run, increase the timer interval. This is needed on very - * slow systems. + * If the hrtimer is running much faster than the bh_work or the task, + * then it is firing too fast and might be starving those contexts as + * well as the actual system timer tick. Increase the interval. */ - if (hardirq_calls >= 20 && (softirq_calls == 0 || task_calls == 0)) + if (hardirq_calls >= 20 && + (hardirq_calls / 2 > softirq_calls || + hardirq_calls / 2 > task_calls)) state->interval = ktime_add_ns(state->interval, 250); if (!state->func(state->test_specific_state)) From d1a5224851bec09c10b1ee0e1c46778a3d4accad Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 5 Aug 2026 22:38:04 -0700 Subject: [PATCH 10/11] kunit: irq: Unregister on-stack timer and work from debugobjects In kunit_run_irq_test(), call destroy_hrtimer_on_stack() and destroy_work_on_stack() to unregister the hrtimer and work from the debugobjects infrastructure (when CONFIG_DEBUG_OBJECTS_TIMERS=y and CONFIG_DEBUG_OBJECTS_WORK=y) before the function returns. Found via code review; the lack of the unregistrations didn't actually cause a warning, since the objects are inactive upon return anyway. But they should be there, otherwise debugobjects keeps tracking the objects. Fixes: 950a81224e8b ("lib/crypto: tests: Add hash-test-template.h and gen-hash-testvecs.py") Reviewed-by: David Gow Link: https://patch.msgid.link/20260806053804.106724-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- include/kunit/run-in-irq-context.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/kunit/run-in-irq-context.h b/include/kunit/run-in-irq-context.h index 3802b6fb218e..b46fa1696360 100644 --- a/include/kunit/run-in-irq-context.h +++ b/include/kunit/run-in-irq-context.h @@ -137,6 +137,8 @@ static inline void kunit_run_irq_test(struct kunit *test, bool (*func)(void *), /* Cancel the timer and work. */ hrtimer_cancel(&state.timer); flush_work(&state.bh_work); + destroy_hrtimer_on_stack(&state.timer); + destroy_work_on_stack(&state.bh_work); /* Sanity check: the timer and BH functions should have been run. */ KUNIT_EXPECT_GT_MSG(test, atomic_read(&state.hardirq_func_calls), 0, From 561131a27f5533e6e63520b4bc43d9c832a27c09 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Fri, 7 Aug 2026 14:58:42 +0200 Subject: [PATCH 11/11] lib/crypto: aes-cmac: Use __cleanup() instead of memzero_explicit() By using __cleanup(aes_cmac_zeroize_key) for clearing the key data, we can save one line of code here. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260807125845.1477067-6-thuth@redhat.com Signed-off-by: Eric Biggers --- lib/crypto/aes.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/crypto/aes.c b/lib/crypto/aes.c index 41aaa82cb1a1..f1549839b3de 100644 --- a/lib/crypto/aes.c +++ b/lib/crypto/aes.c @@ -740,7 +740,7 @@ EXPORT_SYMBOL_NS_GPL(aes_cbcmac_final, "CRYPTO_INTERNAL"); /* FIPS cryptographic algorithm self-test for AES-CMAC */ static void __init aes_cmac_fips_test(void) { - struct aes_cmac_key key; + struct aes_cmac_key key __cleanup(aes_cmac_zeroize_key); u8 mac[AES_BLOCK_SIZE]; if (aes_cmac_preparekey(&key, fips_test_key, sizeof(fips_test_key)) != @@ -749,7 +749,6 @@ static void __init aes_cmac_fips_test(void) aes_cmac(&key, fips_test_data, sizeof(fips_test_data), mac); if (memcmp(fips_test_aes_cmac_value, mac, sizeof(mac)) != 0) panic("aes: CMAC FIPS self-test failed (wrong MAC)\n"); - memzero_explicit(&key, sizeof(key)); } #else /* CONFIG_CRYPTO_LIB_AES_CBC_MACS */ static inline void aes_cmac_fips_test(void)