KUnit tests for SHA-2 and Poly1305
Subsystems (2)
Eric Biggers
maintainerv5 cover KUnit tests for SHA-2 and Poly1305
This series is also available at:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git libcrypto-kunit-v5
This series adds the first KUnit tests for lib/crypto/, including tests for SHA-2 and Poly1305.
Changed in v5:
- Moved hash_irq_test2_state from stack to heap
(fixes https://lore.kernel.org/r/[email protected])
- Replaced HRTIMER_MODE_REL with HRTIMER_MODE_REL_HARD to make hardirq context be used on CONFIG_PREEMPT_RT (fixes a WARN_ON_ONCE).
- Removed an unnecessary initialization in hash_irq_test2_func()
- Added comments that explain kconfig option naming
- Added link to Poly1305 paper
Changed in v4:
- Added Poly1305 tests.
- Split the addition of hash-test-template.h and gen-hash-testvecs.py into a separate patch.
- Added two more test cases to hash-test-template.h (test_hash_all_lens_up_to_4096 and test_hash_interrupt_context_2).
- Simplified test_hmac to use a single consolidated test vector.
- Lots of other cleanups.
Changed in v3:
- Split from SHA-512 and SHA-256 series, as per the request from Linus that the tests be kept last on the branch.
Eric Biggers (4):
lib/crypto: tests: Add hash-test-template.h and gen-hash-testvecs.py lib/crypto: tests: Add KUnit tests for SHA-224 and SHA-256 lib/crypto: tests: Add KUnit tests for SHA-384 and SHA-512 lib/crypto: tests: Add KUnit tests for Poly1305
lib/crypto/Kconfig | 2 + lib/crypto/Makefile | 2 + lib/crypto/tests/Kconfig | 50 ++ lib/crypto/tests/Makefile | 5 + lib/crypto/tests/hash-test-template.h | 683 ++++++++++++++++++++++++++ lib/crypto/tests/poly1305-testvecs.h | 186 +++++++ lib/crypto/tests/poly1305_kunit.c | 165 +++++++ lib/crypto/tests/sha224-testvecs.h | 238 +++++++++ lib/crypto/tests/sha224_kunit.c | 39 ++ lib/crypto/tests/sha256-testvecs.h | 238 +++++++++ lib/crypto/tests/sha256_kunit.c | 39 ++ lib/crypto/tests/sha384-testvecs.h | 290 +++++++++++ lib/crypto/tests/sha384_kunit.c | 39 ++ lib/crypto/tests/sha512-testvecs.h | 342 +++++++++++++ lib/crypto/tests/sha512_kunit.c | 39 ++ scripts/crypto/gen-hash-testvecs.py | 147 ++++++ 16 files changed, 2504 insertions(+) create mode 100644 lib/crypto/tests/Kconfig create mode 100644 lib/crypto/tests/Makefile create mode 100644 lib/crypto/tests/hash-test-template.h create mode 100644 lib/crypto/tests/poly1305-testvecs.h create mode 100644 lib/crypto/tests/poly1305_kunit.c create mode 100644 lib/crypto/tests/sha224-testvecs.h create mode 100644 lib/crypto/tests/sha224_kunit.c create mode 100644 lib/crypto/tests/sha256-testvecs.h create mode 100644 lib/crypto/tests/sha256_kunit.c create mode 100644 lib/crypto/tests/sha384-testvecs.h create mode 100644 lib/crypto/tests/sha384_kunit.c create mode 100644 lib/crypto/tests/sha512-testvecs.h create mode 100644 lib/crypto/tests/sha512_kunit.c create mode 100755 scripts/crypto/gen-hash-testvecs.py
Signature · 1 line
2.50.1
Eric Biggers
maintainerPATCH v5 1/4 lib/crypto: tests: Add hash-test-template.h and gen-hash-testvecs.py
Add hash-test-template.h which generates the following KUnit test cases for hash functions:
test_hash_test_vectors test_hash_all_lens_up_to_4096 test_hash_incremental_updates test_hash_buffer_overruns test_hash_overlaps test_hash_alignment_consistency test_hash_ctx_zeroization test_hash_interrupt_context_1 test_hash_interrupt_context_2
test_hmac (when HMAC is supported) benchmark_hash (when CONFIG_CRYPTO_LIB_BENCHMARK=y)
The initial use cases for this will be sha224_kunit, sha256_kunit, sha384_kunit, sha512_kunit, and poly1305_kunit.
Add a Python script gen-hash-testvecs.py which generates the test vectors required by test_hash_test_vectors, test_hash_all_lens_up_to_4096, and test_hmac.
--- lib/crypto/tests/hash-test-template.h | 683 ++++++++++++++++++++++++++ scripts/crypto/gen-hash-testvecs.py | 102 ++++ 2 files changed, 785 insertions(+) create mode 100644 lib/crypto/tests/hash-test-template.h create mode 100755 scripts/crypto/gen-hash-testvecs.py
lib/crypto/tests/hash-test-template.h new file +683 -0
@@ -0,0 +1,683 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* +* Test cases for hash functions, including a benchmark. This is included by +* KUnit test suites that want to use it. See sha512_kunit.c for an example. +* +* Copyright 2025 Google LLC +*/ +#include <kunit/test.h> +#include <linux/hrtimer.h> +#include <linux/timekeeping.h> +#include <linux/vmalloc.h> +#include <linux/workqueue.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 <linux/prandom.h> 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) +{ +/* +* 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. +* +* Note that it's only necessary to run each test vector in one way (e.g., +* one-shot instead of incremental), since consistency between different ways of +* using the APIs is verified by other test cases. +*/ +static void test_hash_test_vectors (struct kunit *test) +{ +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_MEMEQ_MSG ( + test, actual_hash, hash_testvecs[i].digest,HASH_SIZE , +"Wrong result with test vector %zu; data_len=%zu" , i, + data_len); + } +} + +/* +* Test that the hash function produces correct results for *every* length up to +* 4096 bytes. To do this, generate seeded random data, then calculate a hash +* value for each length 0..4096, then hash the hash values. Verify just the +* final hash value, which should match only when all hash values were correct. +*/ +static void test_hash_all_lens_up_to_4096 (struct kunit *test) +{ +struct HASH_CTX ctx; +u8 hash[HASH_SIZE ]; + +static_assert (TEST_BUF_LEN >=4096 ); +rand_bytes_seeded_from_len (test_buf,4096 ); +HASH_INIT (&ctx); +for (size_t len =0 ; len <=4096 ; len++) { +HASH (test_buf, len, hash); +HASH_UPDATE (&ctx, hash,HASH_SIZE ); + } +HASH_FINAL (&ctx, hash); +KUNIT_ASSERT_MEMEQ (test, hash, hash_testvec_consolidated,HASH_SIZE ); +} + +/* +* Test that the hash function produces the same result with a one-shot +* computation as it does with an incremental computation. +*/ +static void test_hash_incremental_updates (struct kunit *test) +{ +for (int i =0 ; i <1000 ; i++) { +size_t total_len, offset; +struct HASH_CTX ctx; +u8 hash1[HASH_SIZE ]; +u8 hash2[HASH_SIZE ]; +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); + +/* Compute the hash value in one shot. */ +HASH (&test_buf[offset], total_len, hash1); + +/* +* Compute the hash value incrementally, using a randomly +* selected sequence of update lengths that sum to total_len. +*/ +HASH_INIT (&ctx); + remaining_len = total_len; + cur_offset = offset; +while (rand_bool ()) { +size_t part_len =rand_length (remaining_len); + +HASH_UPDATE (&ctx, &test_buf[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); + num_parts++; + } +HASH_FINAL (&ctx, hash2); + +/* Verify that the two hash values are the same. */ +KUNIT_ASSERT_MEMEQ_MSG ( + test, hash1, hash2,HASH_SIZE , +"Incremental test failed with total_len=%zu num_parts=%zu offset=%zu" , + total_len, num_parts, offset); + } +} + +/* +* Test that the hash function does not overrun any buffers. Uses a guard page +* to catch buffer overruns even if they occur in assembly code. +*/ +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 ]; +struct HASH_CTX *guarded_ctx = buf_end -sizeof (*guarded_ctx); + +rand_bytes (test_buf,TEST_BUF_LEN ); + +for (int i =0 ; i <100 ; i++) { +size_t len =rand_length (max_tested_len); +struct HASH_CTX ctx; +u8 hash[HASH_SIZE ]; + +/* Check for overruns of the data buffer. */ +HASH (buf_end - len, len, hash); +HASH_INIT (&ctx); +HASH_UPDATE (&ctx, buf_end - len, len); +HASH_FINAL (&ctx, hash); + +/* Check for overruns of the hash value buffer. */ +HASH (test_buf, len, buf_end -HASH_SIZE ); +HASH_INIT (&ctx); +HASH_UPDATE (&ctx, test_buf, len); +HASH_FINAL (&ctx, buf_end -HASH_SIZE ); + +/* Check for overuns of the hash context. */ +HASH_INIT (guarded_ctx); +HASH_UPDATE (guarded_ctx, test_buf, len); +HASH_FINAL (guarded_ctx, hash); + } +} + +/* +* Test that the caller is permitted to alias the output digest and source data +* buffer, and also modify the source data buffer after it has been used. +*/ +static void test_hash_overlaps (struct kunit *test) +{ +const size_t max_tested_len =TEST_BUF_LEN -HASH_SIZE ; +struct HASH_CTX ctx; +u8 hash[HASH_SIZE ]; + +rand_bytes (test_buf,TEST_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 ]; + +HASH (&test_buf[offset], len, hash); +HASH (&test_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_INIT (&ctx); +HASH_UPDATE (&ctx, &test_buf[offset], len); +HASH_FINAL (&ctx, ovl_hash); +KUNIT_ASSERT_MEMEQ_MSG ( + test, hash, ovl_hash,HASH_SIZE , +"Overlap test 2 failed with len=%zu offset=%zu left_end=%d" , + len, offset, left_end); + +/* Test modifying the source data after it was used. */ +HASH (&test_buf[offset], len, hash); +HASH_INIT (&ctx); +HASH_UPDATE (&ctx, &test_buf[offset], len); +rand_bytes (&test_buf[offset], len); +HASH_FINAL (&ctx, ovl_hash); +KUNIT_ASSERT_MEMEQ_MSG ( + test, hash, ovl_hash,HASH_SIZE , +"Overlap test 3 failed with len=%zu offset=%zu left_end=%d" , + len, offset, left_end); + } +} + +/* +* Test that if the same data is hashed at different alignments in memory, the +* results are the same. +*/ +static void test_hash_alignment_consistency (struct kunit *test) +{ +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 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]); +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)" , + len, data_offs1, data_offs2, hash_offs1, hash_offs2); + } +} + +/* Test that HASH_FINAL zeroizes the context. */ +static void test_hash_ctx_zeroization (struct kunit *test) +{ +static const u8 zeroes[sizeof (struct HASH_CTX )]; +struct HASH_CTX ctx; + +rand_bytes (test_buf,128 ); +HASH_INIT (&ctx); +HASH_UPDATE (&ctx, test_buf,128 ); +HASH_FINAL (&ctx, test_buf); +KUNIT_ASSERT_MEMEQ_MSG (test, &ctx, zeroes,sizeof (ctx), +"Hash context was not zeroized by finalization" ); +} + +#define IRQ_TEST_HRTIMER_INTERVAL us_to_ktime(5) + +struct hash_irq_test_state { +bool (*func)(void *test_specific_state); +void *test_specific_state; +bool task_func_reported_failure; +bool hardirq_func_reported_failure; +bool softirq_func_reported_failure; +unsigned long hardirq_func_calls; +unsigned long softirq_func_calls; +struct hrtimer timer; +struct work_struct bh_work; +}; + +static enum hrtimer_restart hash_irq_test_timer_func (struct hrtimer *timer) +{ +struct hash_irq_test_state *state = +container_of (timer,typeof (*state), timer); + +WARN_ON_ONCE (!in_hardirq ()); + state->hardirq_func_calls++; + +if (!state->func (state->test_specific_state)) + state->hardirq_func_reported_failure = true; + +hrtimer_forward_now (&state->timer,IRQ_TEST_HRTIMER_INTERVAL ); +queue_work (system_bh_wq, &state->bh_work); +return HRTIMER_RESTART ; +} + +static void hash_irq_test_bh_work_func (struct work_struct *work) +{ +struct hash_irq_test_state *state = +container_of (work,typeof (*state), bh_work); + +WARN_ON_ONCE (!in_serving_softirq ()); + state->softirq_func_calls++; + +if (!state->func (state->test_specific_state)) + state->softirq_func_reported_failure = true; +} + +/* +* Helper function which repeatedly runs the given @func in task, softirq, and +* hardirq context concurrently, and reports a failure to KUnit if any +* invocation of @func in any context returns false. @func is passed +* @test_specific_state as its argument. At most 3 invocations of @func will +* run concurrently: one in each of task, softirq, and hardirq context. +* +* The main purpose of this interrupt context testing is to validate fallback +* code paths that run in contexts where the normal code path cannot be used, +* typically due to the FPU or vector registers already being in-use in kernel +* mode. These code paths aren't covered when the test code is executed only by +* the KUnit test runner thread in task context. The reason for the concurrency +* is because merely using hardirq context is not sufficient to reach a fallback +* code path on some architectures; the hardirq actually has to occur while the +* FPU or vector unit was already in-use in kernel mode. +* +* Another purpose of this testing is to detect issues with the architecture's +* irq_fpu_usable() and kernel_fpu_begin/end() or equivalent functions, +* especially in softirq context when the softirq may have interrupted a task +* already using kernel-mode FPU or vector (if the arch didn't prevent that). +* Crypto functions are often executed in softirqs, so this is important. +*/ +static void run_irq_test (struct kunit *test,bool (*func)(void *), +int max_iterations,void *test_specific_state) +{ +struct hash_irq_test_state state = { + .func = func, + .test_specific_state = test_specific_state, + }; +unsigned long end_jiffies; + +/* +* Set up a hrtimer (the way we access hardirq context) and a work +* struct for the BH workqueue (the way we access softirq context). +*/ +hrtimer_setup_on_stack (&state.timer, hash_irq_test_timer_func, +CLOCK_MONOTONIC ,HRTIMER_MODE_REL_HARD ); +INIT_WORK (&state.bh_work, hash_irq_test_bh_work_func); + +/* Run for up to max_iterations or 1 second, whichever comes first. */ + end_jiffies = jiffies +HZ ; +hrtimer_start (&state.timer,IRQ_TEST_HRTIMER_INTERVAL , +HRTIMER_MODE_REL_HARD ); +for (int i =0 ; i < max_iterations && !time_after (jiffies, end_jiffies); + i++) { +if (!func (test_specific_state)) + state.task_func_reported_failure = true; + } + +/* Cancel the timer and work. */ +hrtimer_cancel (&state.timer); +flush_work (&state.bh_work); + +/* Sanity check: the timer and BH functions should have been run. */ +KUNIT_EXPECT_GT_MSG (test, state.hardirq_func_calls,0 , +"Timer function was not called" ); +KUNIT_EXPECT_GT_MSG (test, state.softirq_func_calls,0 , +"BH work function was not called" ); + +/* Check for incorrect hash values reported from any context. */ +KUNIT_EXPECT_FALSE_MSG ( + test, state.task_func_reported_failure, +"Incorrect hash values reported from task context" ); +KUNIT_EXPECT_FALSE_MSG ( + test, state.hardirq_func_reported_failure, +"Incorrect hash values reported from hardirq context" ); +KUNIT_EXPECT_FALSE_MSG ( + test, state.softirq_func_reported_failure, +"Incorrect hash values reported from softirq context" ); +} + +#define IRQ_TEST_DATA_LEN 256 +#define IRQ_TEST_NUM_BUFFERS 3/* matches max concurrency level */ + +struct hash_irq_test1_state { +u8 expected_hashes[IRQ_TEST_NUM_BUFFERS ][HASH_SIZE ]; +atomic_t seqno; +}; + +/* +* Compute the hash of one of the test messages and verify that it matches the +* expected hash from @state->expected_hashes. To increase the chance of +* detecting problems, cycle through multiple messages. +*/ +static bool hash_irq_test1_func (void *state_) +{ +struct hash_irq_test1_state *state = 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); +return memcmp (actual_hash, state->expected_hashes[i],HASH_SIZE ) ==0 ; +} + +/* +* Test that if hashes are computed in task, softirq, and hardirq context +* concurrently, then all results are as expected. +*/ +static void test_hash_interrupt_context_1 (struct kunit *test) +{ +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 ); +for (int i =0 ; i <IRQ_TEST_NUM_BUFFERS ; i++) +HASH (&test_buf[i *IRQ_TEST_DATA_LEN ],IRQ_TEST_DATA_LEN , + state.expected_hashes[i]); + +run_irq_test (test, hash_irq_test1_func,100000 , &state); +} + +struct hash_irq_test2_hash_ctx { +struct HASH_CTX hash_ctx; +atomic_t in_use; +int offset; +int step; +}; + +struct hash_irq_test2_state { +struct hash_irq_test2_hash_ctx ctxs[IRQ_TEST_NUM_BUFFERS ]; +u8 expected_hash[HASH_SIZE ]; +u16 update_lens[32 ]; +int num_steps; +}; + +static bool hash_irq_test2_func (void *state_) +{ +struct hash_irq_test2_state *state = state_; +struct hash_irq_test2_hash_ctx *ctx; +bool ret = true; + +for (ctx = &state->ctxs[0 ]; ctx < &state->ctxs[ARRAY_SIZE (state->ctxs)]; + ctx++) { +if (atomic_cmpxchg (&ctx->in_use,0 ,1 ) ==0 ) +break ; + } +if (WARN_ON_ONCE (ctx == &state->ctxs[ARRAY_SIZE (state->ctxs)])) { +/* +* This should never happen, as the number of contexts is equal +* to the maximum concurrency level of run_irq_test(). +*/ +return false; + } + +if (ctx->step ==0 ) { +/* Init step */ +HASH_INIT (&ctx->hash_ctx); + ctx->offset =0 ; + ctx->step++; + }else if (ctx->step < state->num_steps -1 ) { +/* Update step */ +HASH_UPDATE (&ctx->hash_ctx, &test_buf[ctx->offset], + state->update_lens[ctx->step -1 ]); + ctx->offset += state->update_lens[ctx->step -1 ]; + ctx->step++; + }else { +/* Final step */ +u8 actual_hash[HASH_SIZE ]; + +if (WARN_ON_ONCE (ctx->offset !=TEST_BUF_LEN )) + ret = false; +HASH_FINAL (&ctx->hash_ctx, actual_hash); +if (memcmp (actual_hash, state->expected_hash,HASH_SIZE ) !=0 ) + ret = false; + ctx->step =0 ; + } +atomic_set_release (&ctx->in_use,0 ); +return ret; +} + +/* +* Test that if hashes are computed in task, softirq, and hardirq context +* concurrently, *including doing different parts of the same incremental +* computation in different contexts*, then all results are as expected. +* Besides detecting bugs similar to those that test_hash_interrupt_context_1 +* can detect, this test case can also detect bugs where hash function +* implementations don't correctly handle these mixed incremental computations. +*/ +static void test_hash_interrupt_context_2 (struct kunit *test) +{ +struct hash_irq_test2_state *state; +int remaining =TEST_BUF_LEN ; + + state =kunit_kzalloc (test,sizeof (*state),GFP_KERNEL ); +KUNIT_ASSERT_NOT_NULL (test, state); + +rand_bytes (test_buf,TEST_BUF_LEN ); +HASH (test_buf,TEST_BUF_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 ); +for (state->num_steps =0 ; + state->num_steps <ARRAY_SIZE (state->update_lens) -1 && remaining; + state->num_steps++) { + state->update_lens[state->num_steps] = +rand_length (min (remaining,4096 )); + remaining -= state->update_lens[state->num_steps]; + } +if (remaining) + state->update_lens[state->num_steps++] = remaining; + state->num_steps +=2 ;/* for init and final */ + +run_irq_test (test, hash_irq_test2_func,250000 , state); +} + +#define UNKEYED_HASH_KUNIT_CASES \ + KUNIT_CASE(test_hash_test_vectors), \ + KUNIT_CASE(test_hash_all_lens_up_to_4096), \ + KUNIT_CASE(test_hash_incremental_updates), \ + KUNIT_CASE(test_hash_buffer_overruns), \ + KUNIT_CASE(test_hash_overlaps), \ + KUNIT_CASE(test_hash_alignment_consistency), \ + KUNIT_CASE(test_hash_ctx_zeroization), \ + KUNIT_CASE(test_hash_interrupt_context_1), \ + KUNIT_CASE(test_hash_interrupt_context_2) +/* benchmark_hash is omitted so that the suites can put it last. */ + +#ifdef HMAC +/* +* Test the corresponding HMAC variant. +* +* This test case is fairly short, since HMAC is just a simple C wrapper around +* the underlying unkeyed hash function, which is already well-tested by the +* other test cases. It's not useful to test things like data alignment or +* interrupt context again for HMAC, nor to have a long list of test vectors. +* +* Thus, just do a single consolidated test, which covers all data lengths up to +* 4096 bytes and all key lengths up to 292 bytes. For each data length, select +* a key length, generate the inputs from a seed, and compute the HMAC value. +* Concatenate all these HMAC values together, and compute the HMAC of that. +* Verify that value. If this fails, then the HMAC implementation is wrong. +* This won't show which specific input failed, but that should be fine. Any +* failure would likely be non-input-specific or also show in the unkeyed tests. +*/ +static void test_hmac (struct kunit *test) +{ +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 (raw_key,32 ); +HMAC_PREPAREKEY (&key, raw_key,32 ); +HMAC_INIT (&ctx, &key); +for (size_t data_len =0 ; data_len <=4096 ; data_len++) { +/* +* Cycle through key lengths as well. Somewhat arbitrarily go +* up to 293, which is somewhat larger than the largest hash +* block size (which is the size at which the key starts being +* 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 ; + +HMAC_UPDATE (&ctx, test_buf, data_len); + +rand_bytes_seeded_from_len (raw_key, key_len); +HMAC_USINGRAWKEY (raw_key, key_len, test_buf, 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); +KUNIT_ASSERT_MEMEQ_MSG ( + test, mac, mac2,HASH_SIZE , +"HMAC gave different results with raw and prepared keys" ); + } +HMAC_FINAL (&ctx, mac); +KUNIT_EXPECT_MEMEQ_MSG (test, mac, hmac_testvec_consolidated,HASH_SIZE , +"HMAC gave wrong result" ); +KUNIT_EXPECT_MEMEQ_MSG (test, &ctx, zeroes,sizeof (ctx), +"HMAC context was not zeroized by finalization" ); +} +#define HASH_KUNIT_CASES UNKEYED_HASH_KUNIT_CASES, KUNIT_CASE(test_hmac) +#else +#define HASH_KUNIT_CASES UNKEYED_HASH_KUNIT_CASES +#endif + +/* Benchmark the hash function on various data lengths. */ +static void benchmark_hash (struct kunit *test) +{ +static const size_t lens_to_test[] = { +1 ,16 ,64 ,127 ,128 ,200 ,256 , +511 ,512 ,1024 ,3173 ,4096 ,16384 , + }; +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); + +for (size_t i =0 ; i <ARRAY_SIZE (lens_to_test); i++) { +size_t len = lens_to_test[i]; +/* The '+ 128' tries to account for per-message overhead. */ +size_t num_iters =10000000 / (len +128 ); +u64 t; + +KUNIT_ASSERT_LE (test, len,TEST_BUF_LEN ); +preempt_disable (); + t =ktime_get_ns (); +for (size_t j =0 ; j < num_iters; j++) +HASH (test_buf, len, hash); + t =ktime_get_ns () - t; +preempt_enable (); +kunit_info (test,"len=%zu: %llu MB/s" , len, +div64_u64 ((u64 )len * num_iters *1000 , t ?:1 )); + } +}
scripts/crypto/gen-hash-testvecs.py new file +102 -0
102 lines not on this page · raw message
Signature · 1 line
2.50.1
Eric Biggers
maintainerPATCH v5 2/4 lib/crypto: tests: Add KUnit tests for SHA-224 and SHA-256
In reply to Eric BiggersAdd KUnit test suites for the SHA-224 and SHA-256 library functions, including the corresponding HMAC support. The core test logic is in the previously-added hash-test-template.h. This commit just adds the actual KUnit suites, and it adds the generated test vectors to the tree so that gen-hash-testvecs.py won't have to be run at build time.
--- lib/crypto/Kconfig | 2 + lib/crypto/Makefile | 2 + lib/crypto/tests/Kconfig | 29 ++++ lib/crypto/tests/Makefile | 3 + lib/crypto/tests/sha224-testvecs.h | 238 +++++++++++++++++++++++++++++ lib/crypto/tests/sha224_kunit.c | 39 +++++ lib/crypto/tests/sha256-testvecs.h | 238 +++++++++++++++++++++++++++++ lib/crypto/tests/sha256_kunit.c | 39 +++++ 8 files changed, 590 insertions(+) create mode 100644 lib/crypto/tests/Kconfig create mode 100644 lib/crypto/tests/Makefile create mode 100644 lib/crypto/tests/sha224-testvecs.h create mode 100644 lib/crypto/tests/sha224_kunit.c create mode 100644 lib/crypto/tests/sha256-testvecs.h create mode 100644 lib/crypto/tests/sha256_kunit.c
lib/crypto/Kconfig +2 -0
@@ -178,10 +178,12 @@ config CRYPTO_LIB_SHA512_ARCH default y if X86_64 config CRYPTO_LIB_SM3 tristate +source "lib/crypto/tests/Kconfig" + if !KMSAN # avoid false positives from assembly if ARM source "lib/crypto/arm/Kconfig" endif if ARM64
lib/crypto/Makefile +2 -0
@@ -6,10 +6,12 @@ quiet_cmd_perlasm = PERLASM $@ cmd_perlasm = $(PERL) $(<) > $(@) quiet_cmd_perlasm_with_args = PERLASM $@ cmd_perlasm_with_args = $(PERL) $(<) void $(@) +obj-$(CONFIG_KUNIT) += tests/ + obj-$(CONFIG_CRYPTO_HASH_INFO) += hash_info.o obj-$(CONFIG_CRYPTO_LIB_UTILS) += libcryptoutils.o libcryptoutils-y := memneq.o utils.o
lib/crypto/tests/Kconfig new file +29 -0
@@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +# Option is named *_SHA256_KUNIT_TEST, though both SHA-224 and SHA-256 tests are +# included, for consistency with the naming used elsewhere (e.g. CRYPTO_SHA256). +config CRYPTO_LIB_SHA256_KUNIT_TEST + tristate "KUnit tests for SHA-224 and SHA-256" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS || CRYPTO_SELFTESTS + select CRYPTO_LIB_BENCHMARK_VISIBLE + select CRYPTO_LIB_SHA256 + help + KUnit tests for the SHA-224 and SHA-256 cryptographic hash functions + and their corresponding HMACs. + +config CRYPTO_LIB_BENCHMARK_VISIBLE + bool + +config CRYPTO_LIB_BENCHMARK + bool "Include benchmarks in KUnit tests for cryptographic functions" + depends on CRYPTO_LIB_BENCHMARK_VISIBLE + help + Include benchmarks in the KUnit tests for cryptographic functions. + The benchmark results are printed to the kernel log when the + corresponding KUnit test suite runs. + + This is useful for evaluating the performance of the cryptographic + functions. However, it will increase the runtime of the KUnit tests. + + If you're only interested in correctness testing, leave this disabled.
lib/crypto/tests/Makefile new file +3 -0
@@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +obj-$(CONFIG_CRYPTO_LIB_SHA256_KUNIT_TEST) += sha224_kunit.o sha256_kunit.o
lib/crypto/tests/sha224-testvecs.h new file +238 -0
238 lines not on this page · raw message
lib/crypto/tests/sha224_kunit.c new file +39 -0
@@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* +* Copyright 2025 Google LLC +*/ +#include <crypto/sha2.h> +#include "sha224-testvecs.h" + +#define HASH sha224 +#define HASH_CTX sha224_ctx +#define HASH_SIZE SHA224_DIGEST_SIZE +#define HASH_INIT sha224_init +#define HASH_UPDATE sha224_update +#define HASH_FINAL sha224_final +#define HMAC_KEY hmac_sha224_key +#define HMAC_CTX hmac_sha224_ctx +#define HMAC_PREPAREKEY hmac_sha224_preparekey +#define HMAC_INIT hmac_sha224_init +#define HMAC_UPDATE hmac_sha224_update +#define HMAC_FINAL hmac_sha224_final +#define HMAC hmac_sha224 +#define HMAC_USINGRAWKEY hmac_sha224_usingrawkey +#include "hash-test-template.h" + +static struct kunit_case hash_test_cases[] = { +HASH_KUNIT_CASES , +KUNIT_CASE (benchmark_hash), + {}, +}; + +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); + +MODULE_DESCRIPTION ("KUnit tests and benchmark for SHA-224 and HMAC-SHA224" ); +MODULE_LICENSE ("GPL" );
lib/crypto/tests/sha256-testvecs.h new file +238 -0
238 lines not on this page · raw message
lib/crypto/tests/sha256_kunit.c new file +39 -0
@@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* +* Copyright 2025 Google LLC +*/ +#include <crypto/sha2.h> +#include "sha256-testvecs.h" + +#define HASH sha256 +#define HASH_CTX sha256_ctx +#define HASH_SIZE SHA256_DIGEST_SIZE +#define HASH_INIT sha256_init +#define HASH_UPDATE sha256_update +#define HASH_FINAL sha256_final +#define HMAC_KEY hmac_sha256_key +#define HMAC_CTX hmac_sha256_ctx +#define HMAC_PREPAREKEY hmac_sha256_preparekey +#define HMAC_INIT hmac_sha256_init +#define HMAC_UPDATE hmac_sha256_update +#define HMAC_FINAL hmac_sha256_final +#define HMAC hmac_sha256 +#define HMAC_USINGRAWKEY hmac_sha256_usingrawkey +#include "hash-test-template.h" + +static struct kunit_case hash_test_cases[] = { +HASH_KUNIT_CASES , +KUNIT_CASE (benchmark_hash), + {}, +}; + +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); + +MODULE_DESCRIPTION ("KUnit tests and benchmark for SHA-256 and HMAC-SHA256" ); +MODULE_LICENSE ("GPL" );
Signature · 1 line
2.50.1
Eric Biggers
maintainerPATCH v5 3/4 lib/crypto: tests: Add KUnit tests for SHA-384 and SHA-512
In reply to Eric BiggersAdd KUnit test suites for the SHA-384 and SHA-512 library functions, including the corresponding HMAC support. The core test logic is in the previously-added hash-test-template.h. This commit just adds the actual KUnit suites, and it adds the generated test vectors to the tree so that gen-hash-testvecs.py won't have to be run at build time.
--- lib/crypto/tests/Kconfig | 12 + lib/crypto/tests/Makefile | 1 + lib/crypto/tests/sha384-testvecs.h | 290 ++++++++++++++++++++++++ lib/crypto/tests/sha384_kunit.c | 39 ++++ lib/crypto/tests/sha512-testvecs.h | 342 +++++++++++++++++++++++++++++ lib/crypto/tests/sha512_kunit.c | 39 ++++ 6 files changed, 723 insertions(+) create mode 100644 lib/crypto/tests/sha384-testvecs.h create mode 100644 lib/crypto/tests/sha384_kunit.c create mode 100644 lib/crypto/tests/sha512-testvecs.h create mode 100644 lib/crypto/tests/sha512_kunit.c
lib/crypto/tests/Kconfig +12 -0
@@ -10,10 +10,22 @@ config CRYPTO_LIB_SHA256_KUNIT_TEST select CRYPTO_LIB_SHA256 help KUnit tests for the SHA-224 and SHA-256 cryptographic hash functions and their corresponding HMACs. +# Option is named *_SHA512_KUNIT_TEST, though both SHA-384 and SHA-512 tests are +# included, for consistency with the naming used elsewhere (e.g. CRYPTO_SHA512). +config CRYPTO_LIB_SHA512_KUNIT_TEST + tristate "KUnit tests for SHA-384 and SHA-512" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS || CRYPTO_SELFTESTS + select CRYPTO_LIB_BENCHMARK_VISIBLE + select CRYPTO_LIB_SHA512 + help + KUnit tests for the SHA-384 and SHA-512 cryptographic hash functions + and their corresponding HMACs. + config CRYPTO_LIB_BENCHMARK_VISIBLE bool config CRYPTO_LIB_BENCHMARK bool "Include benchmarks in KUnit tests for cryptographic functions"
lib/crypto/tests/Makefile +1 -0
@@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-or-later obj-$(CONFIG_CRYPTO_LIB_SHA256_KUNIT_TEST) += sha224_kunit.o sha256_kunit.o +obj-$(CONFIG_CRYPTO_LIB_SHA512_KUNIT_TEST) += sha384_kunit.o sha512_kunit.o
lib/crypto/tests/sha384-testvecs.h new file +290 -0
290 lines not on this page · raw message
lib/crypto/tests/sha384_kunit.c new file +39 -0
@@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* +* Copyright 2025 Google LLC +*/ +#include <crypto/sha2.h> +#include "sha384-testvecs.h" + +#define HASH sha384 +#define HASH_CTX sha384_ctx +#define HASH_SIZE SHA384_DIGEST_SIZE +#define HASH_INIT sha384_init +#define HASH_UPDATE sha384_update +#define HASH_FINAL sha384_final +#define HMAC_KEY hmac_sha384_key +#define HMAC_CTX hmac_sha384_ctx +#define HMAC_PREPAREKEY hmac_sha384_preparekey +#define HMAC_INIT hmac_sha384_init +#define HMAC_UPDATE hmac_sha384_update +#define HMAC_FINAL hmac_sha384_final +#define HMAC hmac_sha384 +#define HMAC_USINGRAWKEY hmac_sha384_usingrawkey +#include "hash-test-template.h" + +static struct kunit_case hash_test_cases[] = { +HASH_KUNIT_CASES , +KUNIT_CASE (benchmark_hash), + {}, +}; + +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); + +MODULE_DESCRIPTION ("KUnit tests and benchmark for SHA-384 and HMAC-SHA384" ); +MODULE_LICENSE ("GPL" );
lib/crypto/tests/sha512-testvecs.h new file +342 -0
342 lines not on this page · raw message
lib/crypto/tests/sha512_kunit.c new file +39 -0
@@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* +* Copyright 2025 Google LLC +*/ +#include <crypto/sha2.h> +#include "sha512-testvecs.h" + +#define HASH sha512 +#define HASH_CTX sha512_ctx +#define HASH_SIZE SHA512_DIGEST_SIZE +#define HASH_INIT sha512_init +#define HASH_UPDATE sha512_update +#define HASH_FINAL sha512_final +#define HMAC_KEY hmac_sha512_key +#define HMAC_CTX hmac_sha512_ctx +#define HMAC_PREPAREKEY hmac_sha512_preparekey +#define HMAC_INIT hmac_sha512_init +#define HMAC_UPDATE hmac_sha512_update +#define HMAC_FINAL hmac_sha512_final +#define HMAC hmac_sha512 +#define HMAC_USINGRAWKEY hmac_sha512_usingrawkey +#include "hash-test-template.h" + +static struct kunit_case hash_test_cases[] = { +HASH_KUNIT_CASES , +KUNIT_CASE (benchmark_hash), + {}, +}; + +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); + +MODULE_DESCRIPTION ("KUnit tests and benchmark for SHA-512 and HMAC-SHA512" ); +MODULE_LICENSE ("GPL" );
Signature · 1 line
2.50.1
Eric Biggers
maintainerPATCH v5 4/4 lib/crypto: tests: Add KUnit tests for Poly1305
In reply to Eric BiggersAdd a KUnit test suite for the Poly1305 functions. Most of its test cases are instantiated from hash-test-template.h, which is also used by the SHA-2 tests. A couple additional test cases are also included to test edge cases specific to Poly1305.
--- lib/crypto/tests/Kconfig | 9 ++ lib/crypto/tests/Makefile | 1 + lib/crypto/tests/poly1305-testvecs.h | 186 +++++++++++++++++++++++++++ lib/crypto/tests/poly1305_kunit.c | 165 ++++++++++++++++++++++++ scripts/crypto/gen-hash-testvecs.py | 49 ++++++- 5 files changed, 408 insertions(+), 2 deletions(-) create mode 100644 lib/crypto/tests/poly1305-testvecs.h create mode 100644 lib/crypto/tests/poly1305_kunit.c
lib/crypto/tests/Kconfig +9 -0
@@ -1,7 +1,16 @@ # SPDX-License-Identifier: GPL-2.0-or-later +config CRYPTO_LIB_POLY1305_KUNIT_TEST + tristate "KUnit tests for Poly1305" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS || CRYPTO_SELFTESTS + select CRYPTO_LIB_BENCHMARK_VISIBLE + select CRYPTO_LIB_POLY1305 + help + KUnit tests for the Poly1305 library functions. + # Option is named *_SHA256_KUNIT_TEST, though both SHA-224 and SHA-256 tests are # included, for consistency with the naming used elsewhere (e.g. CRYPTO_SHA256). config CRYPTO_LIB_SHA256_KUNIT_TEST tristate "KUnit tests for SHA-224 and SHA-256" if !KUNIT_ALL_TESTS depends on KUNIT
lib/crypto/tests/Makefile +1 -0
@@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-or-later +obj-$(CONFIG_CRYPTO_LIB_POLY1305_KUNIT_TEST) += poly1305_kunit.o obj-$(CONFIG_CRYPTO_LIB_SHA256_KUNIT_TEST) += sha224_kunit.o sha256_kunit.o obj-$(CONFIG_CRYPTO_LIB_SHA512_KUNIT_TEST) += sha384_kunit.o sha512_kunit.o
lib/crypto/tests/poly1305-testvecs.h new file +186 -0
186 lines not on this page · raw message
lib/crypto/tests/poly1305_kunit.c new file +165 -0
165 lines not on this page · raw message
scripts/crypto/gen-hash-testvecs.py +47 -2
74 lines not on this page · raw message
Signature · 1 line
2.50.1
Quoted 30 lines · Eric Biggers
This series is also available at:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git libcrypto-kunit-v5
This series adds the first KUnit tests for
lib/crypto/, including tests for SHA-2 and Poly1305.Changed in v5:
- Movedhash_irq_test2_statefrom stack to heap
(fixes https://lore.kernel.org/r/[email protected])
- ReplacedHRTIMER_MODE_RELwithHRTIMER_MODE_REL_HARDto make hardirq context be used onCONFIG_PREEMPT_RT(fixes aWARN_ON_ONCE).
- Removed an unnecessary initialization inhash_irq_test2_func()
- Added comments that explain kconfig option naming
- Added link to Poly1305 paperChanged in v4:
- Added Poly1305 tests.
- Split the addition ofhash-test-template.handgen-hash-testvecs.pyinto a separate patch.
- Added two more test cases tohash-test-template.h(test_hash_all_lens_up_to_4096andtest_hash_interrupt_context_2).
- Simplifiedtest_hmacto use a single consolidated test vector.
- Lots of other cleanups.Changed in v3:
- Split from SHA-512 and SHA-256 series, as per the request from Linus that the tests be kept last on the branch.
Quoted 6 lines
Eric Biggers (4):
lib/crypto: tests: Addhash-test-template.handgen-hash-testvecs.pylib/crypto: tests: Add KUnit tests for SHA-224 and SHA-256 lib/crypto: tests: Add KUnit tests for SHA-384 and SHA-512 lib/crypto: tests: Add KUnit tests for Poly1305