| /* 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/run-in-irq-context.h> | |
| #include <kunit/test.h> | |
| #include "test-utils.h" | |
| /* | |
| * 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) | |
| { | |
| 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, 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, | |
| 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) | |
| { | |
| const size_t max_len = 4096; | |
| u8 *data = alloc_buf(test, max_len); | |
| struct HASH_CTX ctx; | |
| u8 hash[HASH_SIZE]; | |
| rand_bytes_seeded_from_len(data, max_len); | |
| HASH_INIT(&ctx); | |
| for (size_t len = 0; len <= max_len; len++) { | |
| HASH(data, 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) | |
| { | |
| 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; | |
| u8 hash1[HASH_SIZE]; | |
| u8 hash2[HASH_SIZE]; | |
| size_t num_parts = 0; | |
| size_t remaining_len, cur_offset; | |
| 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(&data[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, &data[cur_offset], part_len); | |
| num_parts++; | |
| cur_offset += part_len; | |
| remaining_len -= part_len; | |
| } | |
| if (remaining_len != 0 || rand_bool()) { | |
| HASH_UPDATE(&ctx, &data[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 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(buf, 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(buf, len, buf_end - HASH_SIZE); | |
| HASH_INIT(&ctx); | |
| HASH_UPDATE(&ctx, buf, len); | |
| HASH_FINAL(&ctx, buf_end - HASH_SIZE); | |
| /* Check for overruns of the hash context. */ | |
| HASH_INIT(guarded_ctx); | |
| HASH_UPDATE(guarded_ctx, 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 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(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 ? &buf[offset] : | |
| &buf[offset + len - HASH_SIZE]; | |
| 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(&buf[offset], len, hash); | |
| HASH_INIT(&ctx); | |
| HASH_UPDATE(&ctx, &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(&buf[offset], len, hash); | |
| HASH_INIT(&ctx); | |
| 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, | |
| "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) | |
| { | |
| 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(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(&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)", | |
| 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; | |
| const size_t data_len = 128; | |
| u8 *data = alloc_buf(test, data_len); | |
| u8 hash[HASH_SIZE]; | |
| rand_bytes(data, data_len); | |
| HASH_INIT(&ctx); | |
| 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"); | |
| } | |
| #define IRQ_TEST_DATA_LEN 256 | |
| #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; | |
| }; | |
| /* | |
| * 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(&state->data[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) | |
| { | |
| 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. */ | |
| 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(&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); | |
| } | |
| struct hash_irq_test2_hash_ctx { | |
| struct HASH_CTX hash_ctx; | |
| atomic_t in_use; | |
| int offset; | |
| int step; | |
| }; | |
| 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]; | |
| 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 kunit_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, &state->data[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 != state->data_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) | |
| { | |
| const size_t data_len = 16384; | |
| struct hash_irq_test2_state *state; | |
| 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(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. | |
| */ | |
| 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++) { | |
| 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 */ | |
| kunit_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) | |
| { | |
| 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)]; | |
| struct HMAC_KEY key; | |
| struct HMAC_CTX ctx; | |
| u8 mac[HASH_SIZE]; | |
| u8 mac2[HASH_SIZE]; | |
| rand_bytes_seeded_from_len(data, max_data_len); | |
| rand_bytes_seeded_from_len(raw_key, outer_key_len); | |
| HMAC_PREPAREKEY(&key, raw_key, outer_key_len); | |
| HMAC_INIT(&ctx, &key); | |
| 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 | |
| * 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 % max_key_len; | |
| HMAC_UPDATE(&ctx, data, data_len); | |
| rand_bytes_seeded_from_len(raw_key, key_len); | |
| 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, data, 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, | |
| }; | |
| 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 */ | |
| 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]; | |
| /* The '+ 128' tries to account for per-message overhead. */ | |
| size_t num_iters = 10000000 / (len + 128); | |
| u64 t; | |
| KUNIT_ASSERT_LE(test, len, max_len); | |
| preempt_disable(); | |
| t = ktime_get_ns(); | |
| for (size_t j = 0; j < num_iters; j++) | |
| HASH(data, 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)); | |
| } | |
| } |