wolfBoot/tools/unit-tests/unit-psa_store.c

469 lines
16 KiB
C

/* unit-psa_store.c
*
* Unit test for PSA storage module
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
#define WOLFBOOT_HASH_SHA256
#define EXT_FLASH
#define PART_UPDATE_EXT
#define NVM_FLASH_WRITEONCE
#define KEYSTORE_PUBKEY_SIZE KEYSTORE_PUBKEY_SIZE_ECC256
#include <check.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define XMALLOC_OVERRIDE
#define XMALLOC(n,h,t) malloc(n)
#define XFREE(p,h,t) free(p)
#include "user_settings.h"
#include "wolfssl/wolfcrypt/sha.h"
#include "wolfssl/wolfcrypt/error-crypt.h"
#include "wolfboot/wolfboot.h"
#include "wolfpsa/psa_store.h"
#include "hal.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#define MOCK_ADDRESS 0xCF000000
uint8_t *vault_base = (uint8_t *)MOCK_ADDRESS;
#include "psa_store.c"
const uint32_t keyvault_size = KEYVAULT_OBJ_SIZE * KEYVAULT_MAX_ITEMS + 2 * WOLFBOOT_SECTOR_SIZE;
#include "unit-mock-flash.c"
START_TEST(test_cross_sector_write_preserves_length)
{
enum { type = WOLFPSA_STORE_KEY };
const unsigned long id1 = 7;
const unsigned long id2 = 9;
void *store = NULL;
unsigned char *payload;
struct store_handle *handle;
int ret;
payload = malloc(WOLFBOOT_SECTOR_SIZE);
ck_assert_ptr_nonnull(payload);
for (ret = 0; ret < WOLFBOOT_SECTOR_SIZE; ret++)
payload[ret] = (unsigned char)(ret & 0xFF);
ret = mmap_file("/tmp/wolfboot-unit-psa-keyvault.bin", vault_base,
keyvault_size, NULL);
ck_assert_int_eq(ret, 0);
memset(vault_base, 0xEE, keyvault_size);
ret = wolfPSA_Store_Open(type, id1, id2, 0, &store);
ck_assert_int_eq(ret, 0);
ck_assert_ptr_nonnull(store);
ret = wolfPSA_Store_Write(store, payload, WOLFBOOT_SECTOR_SIZE);
ck_assert_int_eq(ret, WOLFBOOT_SECTOR_SIZE);
handle = store;
ck_assert_uint_eq(handle->in_buffer_offset,
2 * sizeof(uint32_t) + WOLFBOOT_SECTOR_SIZE);
ck_assert_uint_eq(handle->hdr->size,
2 * sizeof(uint32_t) + WOLFBOOT_SECTOR_SIZE);
wolfPSA_Store_Close(store);
free(payload);
}
END_TEST
START_TEST(test_close_clears_handle_state)
{
enum { type = WOLFPSA_STORE_KEY };
const unsigned long id1 = 17;
const unsigned long id2 = 21;
void *store = NULL;
struct store_handle *handle;
int ret;
ret = mmap_file("/tmp/wolfboot-unit-psa-keyvault.bin", vault_base,
keyvault_size, NULL);
ck_assert_int_eq(ret, 0);
memset(vault_base, 0xEE, keyvault_size);
ret = wolfPSA_Store_Open(type, id1, id2, 0, &store);
ck_assert_int_eq(ret, 0);
ck_assert_ptr_nonnull(store);
handle = store;
ck_assert_ptr_nonnull(handle->buffer);
ck_assert_ptr_nonnull(handle->hdr);
ck_assert_uint_ne(handle->in_buffer_offset, 0);
wolfPSA_Store_Close(store);
ck_assert_uint_eq(handle->flags, 0);
ck_assert_uint_eq(handle->pos, 0);
ck_assert_ptr_null(handle->buffer);
ck_assert_ptr_null(handle->hdr);
ck_assert_uint_eq(handle->in_buffer_offset, 0);
}
END_TEST
START_TEST(test_delete_object_ignores_metadata_prefix)
{
enum { type = WOLFPSA_STORE_KEY };
const uint32_t tok_id = VAULT_HEADER_MAGIC;
const uint32_t obj_id = 0x01020308U;
uint32_t *words;
uint8_t bitmap_before[BITMAP_SIZE];
int ret;
ret = mmap_file("/tmp/wolfboot-unit-psa-keyvault.bin", vault_base,
keyvault_size, NULL);
ck_assert_int_eq(ret, 0);
memset(vault_base, 0xFF, keyvault_size);
words = (uint32_t *)vault_base;
words[0] = VAULT_HEADER_MAGIC;
words[1] = obj_id;
words[2] = (uint32_t)type;
words[3] = 0;
words[4] = 0;
memcpy(bitmap_before, vault_base + sizeof(uint32_t), BITMAP_SIZE);
delete_object(type, tok_id, obj_id);
ck_assert_mem_eq(vault_base + sizeof(uint32_t), bitmap_before, BITMAP_SIZE);
ck_assert_uint_eq(((uint32_t *)vault_base)[0], VAULT_HEADER_MAGIC);
ck_assert_uint_eq(((uint32_t *)vault_base)[1], obj_id);
}
END_TEST
START_TEST(test_delete_object_corrupted_pos_no_oob)
{
enum { type = WOLFPSA_STORE_KEY };
const uint32_t tok_id = 0x0A0B0C0DU;
const uint32_t obj_id = 0x10203040U;
struct obj_hdr *hdr;
int ret;
ret = mmap_file("/tmp/wolfboot-unit-psa-keyvault.bin", vault_base,
keyvault_size, NULL);
ck_assert_int_eq(ret, 0);
memset(vault_base, 0xFF, keyvault_size);
/* Valid header magic and zeroed bitmap so check_vault() accepts the
* sector without restoring/reinitializing it. */
((uint32_t *)vault_base)[0] = VAULT_HEADER_MAGIC;
memset(vault_base + sizeof(uint32_t), 0x00, BITMAP_SIZE);
/* Simulate a power-fault-corrupted node: valid tok/obj/type but the
* 'pos' field was never written and is left as erased flash
* (WOLFPSA_INVALID_ID). delete_object() must not turn this into an
* out-of-bounds bitmap_put(0xFFFFFFFF, 0). */
hdr = NODES_TABLE;
hdr->token_id = tok_id;
hdr->object_id = obj_id;
hdr->type = type;
hdr->pos = WOLFPSA_INVALID_ID;
hdr->size = 2 * sizeof(uint32_t);
delete_object(type, tok_id, obj_id);
/* If we get here without a crash, the OOB write was avoided. The node
* should also have been invalidated. */
ck_assert_uint_eq(NODES_TABLE->token_id, WOLFPSA_INVALID_ID);
ck_assert_uint_eq(NODES_TABLE->object_id, WOLFPSA_INVALID_ID);
}
END_TEST
START_TEST(test_find_object_search_stops_at_header_sector)
{
enum { type = WOLFPSA_STORE_KEY };
const uint32_t tok_id = 0x11223344U;
const uint32_t obj_id = 0x55667788U;
struct obj_hdr *backup_hdr;
uint32_t *payload_ids;
int ret;
ret = mmap_file("/tmp/wolfboot-unit-psa-keyvault.bin", vault_base,
keyvault_size, NULL);
ck_assert_int_eq(ret, 0);
memset(vault_base, 0xFF, keyvault_size);
backup_hdr = (struct obj_hdr *)(vault_base + WOLFBOOT_SECTOR_SIZE);
backup_hdr->token_id = tok_id;
backup_hdr->object_id = obj_id;
backup_hdr->type = type;
backup_hdr->pos = 0;
backup_hdr->size = 2 * sizeof(uint32_t);
payload_ids = (uint32_t *)(vault_base + 2 * WOLFBOOT_SECTOR_SIZE);
payload_ids[0] = tok_id;
payload_ids[1] = obj_id;
ck_assert_ptr_null(find_object_header(type, tok_id, obj_id));
ck_assert_ptr_null(find_object_buffer(type, tok_id, obj_id));
}
END_TEST
START_TEST(test_shorter_overwrite_clears_tail)
{
enum { type = WOLFPSA_STORE_KEY };
const unsigned long id1 = 3;
const unsigned long id2 = 4;
void *store = NULL;
unsigned char long_key[200];
unsigned char short_key[32];
uint8_t *obj_buf;
int ret, i;
ret = mmap_file("/tmp/wolfboot-unit-psa-keyvault.bin", vault_base,
keyvault_size, NULL);
ck_assert_int_eq(ret, 0);
memset(vault_base, 0xFF, keyvault_size);
memset(long_key, 0xAA, sizeof(long_key));
memset(short_key, 0xBB, sizeof(short_key));
/* First write: 200-byte key */
ret = wolfPSA_Store_Open(type, id1, id2, 0, &store);
ck_assert_int_eq(ret, 0);
ret = wolfPSA_Store_Write(store, long_key, 200);
ck_assert_int_eq(ret, 200);
wolfPSA_Store_Close(store);
/* Second write: 32-byte key (shorter than original) */
ret = wolfPSA_Store_Open(type, id1, id2, 0, &store);
ck_assert_int_eq(ret, 0);
ret = wolfPSA_Store_Write(store, short_key, 32);
ck_assert_int_eq(ret, 32);
wolfPSA_Store_Close(store);
/* Tail bytes (beyond the new key) must NOT contain the old 0xAA pattern */
obj_buf = find_object_buffer(type, id1, id2);
ck_assert_ptr_nonnull(obj_buf);
for (i = 2 * (int)sizeof(uint32_t) + 32;
i < 2 * (int)sizeof(uint32_t) + 200; i++) {
ck_assert_msg(obj_buf[i] != 0xAA,
"Old key material survives at offset %d: 0x%02x", i, obj_buf[i]);
}
}
END_TEST
START_TEST(test_cache_commit_zeroizes_cached_sector)
{
int ret;
int i;
ret = mmap_file("/tmp/wolfboot-unit-psa-keyvault.bin", vault_base,
keyvault_size, NULL);
ck_assert_int_eq(ret, 0);
memset(vault_base, 0xFF, keyvault_size);
/* Simulate cached_sector staging key-object plaintext, as every
* caller of cache_commit() does before committing to flash. */
memset(cached_sector, 0x5A, sizeof(cached_sector));
cache_commit(0);
for (i = 0; i < (int)sizeof(cached_sector); i++) {
ck_assert_msg(cached_sector[i] == 0,
"cached_sector retains stale data at offset %d: 0x%02x",
i, cached_sector[i]);
}
}
END_TEST
/* A negative length must be rejected before it enters the unsigned
* offset arithmetic: pre-fix, a sufficiently negative len wrapped to a
* large unsigned value in 'in_buffer_offset + len', entered the
* truncation branch, and was silently replaced by the remaining object
* bytes (Read) or the remaining capacity (Write, bypassing the later
* len < 0 guard). */
START_TEST(test_store_rejects_negative_len)
{
enum { type = WOLFPSA_STORE_KEY };
const unsigned long id1 = 31;
const unsigned long id2 = 33;
void *store = NULL;
unsigned char rd[16];
unsigned char wr[16];
int ret;
ret = mmap_file("/tmp/wolfboot-unit-psa-keyvault.bin", vault_base,
keyvault_size, NULL);
ck_assert_int_eq(ret, 0);
memset(vault_base, 0xEE, keyvault_size);
/* Create the object with 3 bytes of content */
ret = wolfPSA_Store_Open(type, id1, id2, 0, &store);
ck_assert_int_eq(ret, 0);
ret = wolfPSA_Store_Write(store, (unsigned char *)"abc", 3);
ck_assert_int_eq(ret, 3);
wolfPSA_Store_Close(store);
/* Read: pre-fix the negative len was replaced by the 3 remaining
* bytes and copied out; post-fix it is rejected. */
ret = wolfPSA_Store_Open(type, id1, id2, 1, &store);
ck_assert_int_eq(ret, 0);
ret = wolfPSA_Store_Read(store, rd, -32768);
ck_assert_int_eq(ret, -1);
wolfPSA_Store_Close(store);
/* Write: pre-fix the negative len was clamped to the remaining
* capacity (4088) and read that many bytes from the 16-byte buffer
* below; post-fix it is rejected. */
ret = wolfPSA_Store_Open(type, id1, id2, 0, &store);
ck_assert_int_eq(ret, 0);
ret = wolfPSA_Store_Write(store, wr, -32768);
ck_assert_int_eq(ret, -1);
wolfPSA_Store_Close(store);
}
END_TEST
/* Removing an object must erase the payload from flash, not just
* invalidate the metadata: key material must not remain recoverable
* after the API reports a successful deletion. */
START_TEST(test_remove_erases_payload_from_flash)
{
const int type = WOLFPSA_STORE_KEY;
const unsigned long id1_a = 10;
const unsigned long id2_a = 20;
const unsigned long id1_b = 30;
const unsigned long id2_b = 40;
void *store = NULL;
unsigned char key_a[256];
unsigned char key_b[128];
uint8_t *buf_a;
uint8_t *buf_b;
uint32_t i;
int ret;
memset(key_a, 0xAB, sizeof(key_a));
memset(key_b, 0xCD, sizeof(key_b));
ret = mmap_file("/tmp/wolfboot-unit-psa-keyvault.bin", vault_base,
keyvault_size, NULL);
ck_assert_int_eq(ret, 0);
memset(vault_base, 0xEE, keyvault_size);
/* Two live objects: A gets slot 0, B gets the adjacent slot 1 */
ret = wolfPSA_Store_Open(type, id1_a, id2_a, 0, &store);
ck_assert_int_eq(ret, 0);
ret = wolfPSA_Store_Write(store, key_a, sizeof(key_a));
ck_assert_int_eq(ret, (int)sizeof(key_a));
wolfPSA_Store_Close(store);
ret = wolfPSA_Store_Open(type, id1_b, id2_b, 0, &store);
ck_assert_int_eq(ret, 0);
ret = wolfPSA_Store_Write(store, key_b, sizeof(key_b));
ck_assert_int_eq(ret, (int)sizeof(key_b));
wolfPSA_Store_Close(store);
buf_a = find_object_buffer(type, id1_a, id2_a);
buf_b = find_object_buffer(type, id1_b, id2_b);
ck_assert_ptr_nonnull(buf_a);
ck_assert_ptr_nonnull(buf_b);
ck_assert_ptr_eq(buf_a, vault_base + 2 * WOLFBOOT_SECTOR_SIZE);
ck_assert_ptr_eq(buf_b, vault_base + 2 * WOLFBOOT_SECTOR_SIZE +
KEYVAULT_OBJ_SIZE);
/* Remove A: the payload must be erased from the raw flash */
ret = wolfPSA_Store_Remove(type, id1_a, id2_a);
ck_assert_int_eq(ret, 0);
/* The 8-byte id prefix is preserved by the erase (it keeps the slot
* identity used by the backup-recovery check); the payload region
* itself must be erased to 0xFF across the whole slot. */
ck_assert_uint_eq(((uint32_t *)buf_a)[0], (uint32_t)id1_a);
ck_assert_uint_eq(((uint32_t *)buf_a)[1], (uint32_t)id2_a);
for (i = 2 * sizeof(uint32_t); i < KEYVAULT_OBJ_SIZE; i++) {
ck_assert_msg(buf_a[i] == 0xFF,
"Payload survives removal at slot offset %u: 0x%02x",
i, buf_a[i]);
}
/* The sector read-modify-write must leave the neighboring object B
* intact in flash */
for (i = 2 * sizeof(uint32_t);
i < 2 * sizeof(uint32_t) + sizeof(key_b); i++) {
ck_assert_msg(buf_b[i] == 0xCD,
"Neighbor object clobbered at slot offset %u: 0x%02x",
i, buf_b[i]);
}
ret = wolfPSA_Store_Open(type, id1_b, id2_b, 1, &store);
ck_assert_int_eq(ret, 0);
ret = wolfPSA_Store_Read(store, key_b, sizeof(key_b));
ck_assert_int_eq(ret, (int)sizeof(key_b));
wolfPSA_Store_Close(store);
/* A is no longer addressable */
ret = wolfPSA_Store_Open(type, id1_a, id2_a, 1, &store);
ck_assert_int_eq(ret, NOT_AVAILABLE_E);
ret = wolfPSA_Store_Remove(type, id1_a, id2_a);
ck_assert_int_eq(ret, NOT_AVAILABLE_E);
}
END_TEST
Suite *wolfboot_suite(void)
{
Suite *s = suite_create("wolfBoot-psa-store");
TCase *tcase_write = tcase_create("cross_sector_write");
TCase *tcase_close = tcase_create("close_state");
TCase *tcase_delete = tcase_create("delete_object");
TCase *tcase_delete_corrupted = tcase_create("delete_corrupted_pos");
TCase *tcase_find_bounds = tcase_create("find_bounds");
TCase *tcase_tail = tcase_create("shorter_overwrite_clears_tail");
TCase *tcase_zeroize = tcase_create("cache_commit_zeroizes_cached_sector");
TCase *tcase_neg_len = tcase_create("rejects_negative_len");
TCase *tcase_remove_erase = tcase_create("remove_erases_payload");
tcase_add_test(tcase_write, test_cross_sector_write_preserves_length);
tcase_add_test(tcase_close, test_close_clears_handle_state);
tcase_add_test(tcase_delete, test_delete_object_ignores_metadata_prefix);
tcase_add_test(tcase_delete_corrupted, test_delete_object_corrupted_pos_no_oob);
tcase_add_test(tcase_find_bounds, test_find_object_search_stops_at_header_sector);
tcase_add_test(tcase_tail, test_shorter_overwrite_clears_tail);
tcase_add_test(tcase_zeroize, test_cache_commit_zeroizes_cached_sector);
tcase_add_test(tcase_neg_len, test_store_rejects_negative_len);
tcase_add_test(tcase_remove_erase, test_remove_erases_payload_from_flash);
suite_add_tcase(s, tcase_write);
suite_add_tcase(s, tcase_close);
suite_add_tcase(s, tcase_delete);
suite_add_tcase(s, tcase_delete_corrupted);
suite_add_tcase(s, tcase_find_bounds);
suite_add_tcase(s, tcase_tail);
suite_add_tcase(s, tcase_zeroize);
suite_add_tcase(s, tcase_neg_len);
suite_add_tcase(s, tcase_remove_erase);
return s;
}
int main(void)
{
int fails;
Suite *s = wolfboot_suite();
SRunner *sr = srunner_create(s);
srunner_run_all(sr, CK_NORMAL);
fails = srunner_ntests_failed(sr);
srunner_free(sr);
return fails;
}