mirror of https://github.com/wolfSSL/wolfBoot.git
454 lines
14 KiB
C
454 lines
14 KiB
C
/* unit-wolfhsm_flash_hal.c
|
|
*
|
|
* Unit tests for the wolfHSM whFlashCb adapter (src/wolfhsm_flash_hal.c).
|
|
*/
|
|
|
|
#include <check.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
|
|
#include "user_settings.h"
|
|
#include "wolfssl/wolfcrypt/settings.h"
|
|
#include "wolfssl/wolfcrypt/misc.h"
|
|
|
|
#include "hal.h"
|
|
#include "wolfhsm/wh_error.h"
|
|
#include "wolfhsm/wh_flash.h"
|
|
#include "wolfboot/wolfhsm_flash_hal.h"
|
|
|
|
#define MOCK_FLASH_BASE 0xCF000000U
|
|
#define MOCK_FLASH_SECTOR (8U * 1024U)
|
|
#define MOCK_FLASH_SECTORS 14U
|
|
#define MOCK_FLASH_SIZE (MOCK_FLASH_SECTORS * MOCK_FLASH_SECTOR)
|
|
|
|
static int g_flash_locked = 1;
|
|
static int g_flash_write_fail = 0;
|
|
static int g_flash_erase_fail = 0;
|
|
|
|
void hal_flash_unlock(void) { g_flash_locked = 0; }
|
|
void hal_flash_lock(void) { g_flash_locked = 1; }
|
|
|
|
int hal_flash_erase(haladdr_t addr, int len)
|
|
{
|
|
if (g_flash_locked || g_flash_erase_fail) {
|
|
return -1;
|
|
}
|
|
if (addr < MOCK_FLASH_BASE ||
|
|
addr + (uintptr_t)len > MOCK_FLASH_BASE + MOCK_FLASH_SIZE) {
|
|
return -1;
|
|
}
|
|
memset((void *)addr, 0xFF, (size_t)len);
|
|
return 0;
|
|
}
|
|
|
|
int hal_flash_write(haladdr_t addr, const uint8_t *data, int len)
|
|
{
|
|
if (g_flash_locked || g_flash_write_fail) {
|
|
return -1;
|
|
}
|
|
if (addr < MOCK_FLASH_BASE ||
|
|
addr + (uintptr_t)len > MOCK_FLASH_BASE + MOCK_FLASH_SIZE) {
|
|
return -1;
|
|
}
|
|
memcpy((void *)addr, data, (size_t)len);
|
|
return 0;
|
|
}
|
|
|
|
#include "../../src/wolfhsm_flash_hal.c"
|
|
|
|
static void mock_flash_init(void)
|
|
{
|
|
/* Prefer MAP_FIXED_NOREPLACE so the kernel refuses to clobber an
|
|
* existing mapping at MOCK_FLASH_BASE (Linux >= 4.17). Older kernels
|
|
* fall back to plain MAP_FIXED. */
|
|
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
|
|
#ifdef MAP_FIXED_NOREPLACE
|
|
flags |= MAP_FIXED_NOREPLACE;
|
|
#else
|
|
flags |= MAP_FIXED;
|
|
#endif
|
|
void *p = mmap((void *)(uintptr_t)MOCK_FLASH_BASE, MOCK_FLASH_SIZE,
|
|
PROT_READ | PROT_WRITE, flags, -1, 0);
|
|
ck_assert_ptr_eq(p, (void *)(uintptr_t)MOCK_FLASH_BASE);
|
|
memset((void *)(uintptr_t)MOCK_FLASH_BASE, 0xFF, MOCK_FLASH_SIZE);
|
|
g_flash_locked = 1;
|
|
g_flash_write_fail = 0;
|
|
g_flash_erase_fail = 0;
|
|
}
|
|
|
|
static void mock_flash_fini(void)
|
|
{
|
|
munmap((void *)(uintptr_t)MOCK_FLASH_BASE, MOCK_FLASH_SIZE);
|
|
}
|
|
|
|
START_TEST(test_init_rejects_null)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
whFlashH5Ctx cfg;
|
|
|
|
cfg.base = MOCK_FLASH_BASE;
|
|
cfg.size = MOCK_FLASH_SIZE;
|
|
cfg.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
|
|
ck_assert_int_eq(whFlashH5_Cb.Init(NULL, &cfg), WH_ERROR_BADARGS);
|
|
ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, NULL), WH_ERROR_BADARGS);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_init_rejects_bad_config)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
whFlashH5Ctx cfg;
|
|
|
|
cfg.base = 0;
|
|
cfg.size = MOCK_FLASH_SIZE;
|
|
cfg.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS);
|
|
|
|
cfg.base = MOCK_FLASH_BASE;
|
|
cfg.size = 0;
|
|
ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS);
|
|
|
|
cfg.size = MOCK_FLASH_SIZE;
|
|
cfg.partition_size = 0;
|
|
ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS);
|
|
|
|
cfg.base = MOCK_FLASH_BASE + 4U;
|
|
cfg.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS);
|
|
|
|
cfg.base = MOCK_FLASH_BASE;
|
|
cfg.size = MOCK_FLASH_SIZE + 4U;
|
|
ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS);
|
|
|
|
cfg.size = MOCK_FLASH_SIZE;
|
|
cfg.partition_size = 100U;
|
|
ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS);
|
|
|
|
cfg.partition_size = MOCK_FLASH_SIZE;
|
|
ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_BADARGS);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_init_accepts_valid)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
whFlashH5Ctx cfg;
|
|
|
|
cfg.base = MOCK_FLASH_BASE;
|
|
cfg.size = MOCK_FLASH_SIZE;
|
|
cfg.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
ck_assert_int_eq(whFlashH5_Cb.Init(&ctx, &cfg), WH_ERROR_OK);
|
|
ck_assert_uint_eq(whFlashH5_Cb.PartitionSize(&ctx), MOCK_FLASH_SIZE / 2U);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_read_bounds)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
uint8_t buf[16];
|
|
|
|
ctx.base = MOCK_FLASH_BASE;
|
|
ctx.size = MOCK_FLASH_SIZE;
|
|
ctx.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
|
|
ck_assert_int_eq(whFlashH5_Cb.Read(&ctx, MOCK_FLASH_SIZE + 1U, 0U, buf),
|
|
WH_ERROR_BADARGS);
|
|
ck_assert_int_eq(whFlashH5_Cb.Read(&ctx, 0U, MOCK_FLASH_SIZE + 1U, buf),
|
|
WH_ERROR_BADARGS);
|
|
ck_assert_int_eq(whFlashH5_Cb.Read(&ctx, 0U, 0U, NULL), WH_ERROR_OK);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_program_single_partial_preserves_neighbours)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
uint8_t data[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
|
|
uint8_t readback[8];
|
|
|
|
mock_flash_init();
|
|
ctx.base = MOCK_FLASH_BASE;
|
|
ctx.size = MOCK_FLASH_SIZE;
|
|
ctx.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
|
|
ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data),
|
|
WH_ERROR_OK);
|
|
memcpy(readback, (void *)(uintptr_t)MOCK_FLASH_BASE, sizeof(readback));
|
|
ck_assert_mem_eq(readback, data, sizeof(data));
|
|
ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[8], 0xFFU);
|
|
ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[MOCK_FLASH_SECTOR - 1U],
|
|
0xFFU);
|
|
mock_flash_fini();
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_program_crosses_sector_boundary)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
const uint32_t off = MOCK_FLASH_SECTOR - 8U;
|
|
uint8_t data[16];
|
|
uint8_t readback[16];
|
|
int i;
|
|
|
|
for (i = 0; i < (int)sizeof(data); i++) {
|
|
data[i] = (uint8_t)(0x10 + i);
|
|
}
|
|
mock_flash_init();
|
|
ctx.base = MOCK_FLASH_BASE;
|
|
ctx.size = MOCK_FLASH_SIZE;
|
|
ctx.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
|
|
ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, off, sizeof(data), data),
|
|
WH_ERROR_OK);
|
|
memcpy(readback, (void *)(uintptr_t)(MOCK_FLASH_BASE + off),
|
|
sizeof(readback));
|
|
ck_assert_mem_eq(readback, data, sizeof(data));
|
|
ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[off - 1U], 0xFFU);
|
|
ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[off + 16U], 0xFFU);
|
|
mock_flash_fini();
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_program_propagates_write_failure)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
uint8_t data[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
|
|
|
|
mock_flash_init();
|
|
ctx.base = MOCK_FLASH_BASE;
|
|
ctx.size = MOCK_FLASH_SIZE;
|
|
ctx.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
|
|
g_flash_write_fail = 1;
|
|
ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data),
|
|
WH_ERROR_ABORTED);
|
|
g_flash_write_fail = 0;
|
|
mock_flash_fini();
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_erase_alignment)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
|
|
mock_flash_init();
|
|
ctx.base = MOCK_FLASH_BASE;
|
|
ctx.size = MOCK_FLASH_SIZE;
|
|
ctx.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
|
|
ck_assert_int_eq(whFlashH5_Cb.Erase(&ctx, 100U, MOCK_FLASH_SECTOR),
|
|
WH_ERROR_BADARGS);
|
|
ck_assert_int_eq(whFlashH5_Cb.Erase(&ctx, 0U, 100U), WH_ERROR_BADARGS);
|
|
ck_assert_int_eq(whFlashH5_Cb.Erase(&ctx, 0U, MOCK_FLASH_SECTOR),
|
|
WH_ERROR_OK);
|
|
mock_flash_fini();
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_verify)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
uint8_t data[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
|
|
uint8_t bad[8] = { 0 };
|
|
|
|
mock_flash_init();
|
|
ctx.base = MOCK_FLASH_BASE;
|
|
ctx.size = MOCK_FLASH_SIZE;
|
|
ctx.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
|
|
ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data),
|
|
WH_ERROR_OK);
|
|
ck_assert_int_eq(whFlashH5_Cb.Verify(&ctx, 0U, sizeof(data), data),
|
|
WH_ERROR_OK);
|
|
ck_assert_int_eq(whFlashH5_Cb.Verify(&ctx, 0U, sizeof(bad), bad),
|
|
WH_ERROR_NOTVERIFIED);
|
|
mock_flash_fini();
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_blank_check)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
uint8_t data[8] = { 1, 0, 0, 0, 0, 0, 0, 0 };
|
|
|
|
mock_flash_init();
|
|
ctx.base = MOCK_FLASH_BASE;
|
|
ctx.size = MOCK_FLASH_SIZE;
|
|
ctx.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
|
|
ck_assert_int_eq(whFlashH5_Cb.BlankCheck(&ctx, 0U, MOCK_FLASH_SIZE),
|
|
WH_ERROR_OK);
|
|
ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data),
|
|
WH_ERROR_OK);
|
|
ck_assert_int_eq(whFlashH5_Cb.BlankCheck(&ctx, 0U, sizeof(data)),
|
|
WH_ERROR_NOTBLANK);
|
|
mock_flash_fini();
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_cleanup)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
|
|
ck_assert_int_eq(whFlashH5_Cb.Cleanup(NULL), WH_ERROR_BADARGS);
|
|
ck_assert_int_eq(whFlashH5_Cb.Cleanup(&ctx), WH_ERROR_OK);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_program_propagates_erase_failure)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
uint8_t data[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
|
|
|
|
mock_flash_init();
|
|
ctx.base = MOCK_FLASH_BASE;
|
|
ctx.size = MOCK_FLASH_SIZE;
|
|
ctx.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
|
|
g_flash_erase_fail = 1;
|
|
ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data),
|
|
WH_ERROR_ABORTED);
|
|
g_flash_erase_fail = 0;
|
|
mock_flash_fini();
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_read_returns_flash_contents)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
uint8_t data[16];
|
|
uint8_t buf[16];
|
|
int i;
|
|
|
|
for (i = 0; i < (int)sizeof(data); i++) {
|
|
data[i] = (uint8_t)(0xA0 + i);
|
|
}
|
|
mock_flash_init();
|
|
ctx.base = MOCK_FLASH_BASE;
|
|
ctx.size = MOCK_FLASH_SIZE;
|
|
ctx.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
|
|
ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, sizeof(data), data),
|
|
WH_ERROR_OK);
|
|
memset(buf, 0, sizeof(buf));
|
|
ck_assert_int_eq(whFlashH5_Cb.Read(&ctx, 0U, sizeof(buf), buf),
|
|
WH_ERROR_OK);
|
|
ck_assert_mem_eq(buf, data, sizeof(data));
|
|
mock_flash_fini();
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_callbacks_reject_null_context)
|
|
{
|
|
uint8_t data[8] = { 0 };
|
|
|
|
ck_assert_int_eq(whFlashH5_Cb.Read(NULL, 0U, sizeof(data), data),
|
|
WH_ERROR_BADARGS);
|
|
ck_assert_int_eq(whFlashH5_Cb.Program(NULL, 0U, sizeof(data), data),
|
|
WH_ERROR_BADARGS);
|
|
ck_assert_int_eq(whFlashH5_Cb.Erase(NULL, 0U, MOCK_FLASH_SECTOR),
|
|
WH_ERROR_BADARGS);
|
|
ck_assert_int_eq(whFlashH5_Cb.Verify(NULL, 0U, sizeof(data), data),
|
|
WH_ERROR_BADARGS);
|
|
ck_assert_int_eq(whFlashH5_Cb.BlankCheck(NULL, 0U, sizeof(data)),
|
|
WH_ERROR_BADARGS);
|
|
ck_assert_uint_eq(whFlashH5_Cb.PartitionSize(NULL), 0U);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_callbacks_reject_null_data)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
|
|
ctx.base = MOCK_FLASH_BASE;
|
|
ctx.size = MOCK_FLASH_SIZE;
|
|
ctx.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
|
|
ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, 0U, 8U, NULL),
|
|
WH_ERROR_BADARGS);
|
|
ck_assert_int_eq(whFlashH5_Cb.Verify(&ctx, 0U, 8U, NULL),
|
|
WH_ERROR_BADARGS);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_program_spans_three_sectors)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
const uint32_t off = MOCK_FLASH_SECTOR - 4U;
|
|
const uint32_t sz = (2U * MOCK_FLASH_SECTOR) + 8U;
|
|
uint8_t data[(2U * MOCK_FLASH_SECTOR) + 8U];
|
|
uint8_t readback[sizeof(data)];
|
|
uint32_t i;
|
|
|
|
for (i = 0U; i < sizeof(data); i++) {
|
|
data[i] = (uint8_t)(i & 0xFFU);
|
|
}
|
|
mock_flash_init();
|
|
ctx.base = MOCK_FLASH_BASE;
|
|
ctx.size = MOCK_FLASH_SIZE;
|
|
ctx.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
|
|
ck_assert_int_eq(whFlashH5_Cb.Program(&ctx, off, sz, data), WH_ERROR_OK);
|
|
memcpy(readback, (void *)(uintptr_t)(MOCK_FLASH_BASE + off), sz);
|
|
ck_assert_mem_eq(readback, data, sz);
|
|
ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[off - 1U], 0xFFU);
|
|
ck_assert_uint_eq(((uint8_t *)(uintptr_t)MOCK_FLASH_BASE)[off + sz], 0xFFU);
|
|
mock_flash_fini();
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_writelock_writeunlock_noop)
|
|
{
|
|
whFlashH5Ctx ctx;
|
|
|
|
ctx.base = MOCK_FLASH_BASE;
|
|
ctx.size = MOCK_FLASH_SIZE;
|
|
ctx.partition_size = MOCK_FLASH_SIZE / 2U;
|
|
|
|
ck_assert_int_eq(whFlashH5_Cb.WriteLock(&ctx, 0U, MOCK_FLASH_SECTOR),
|
|
WH_ERROR_OK);
|
|
ck_assert_int_eq(whFlashH5_Cb.WriteUnlock(&ctx, 0U, MOCK_FLASH_SECTOR),
|
|
WH_ERROR_OK);
|
|
}
|
|
END_TEST
|
|
|
|
Suite *wolfboot_suite(void)
|
|
{
|
|
Suite *s = suite_create("wolfHSM-flash-hal");
|
|
TCase *tc = tcase_create("flash-hal");
|
|
|
|
tcase_add_test(tc, test_init_rejects_null);
|
|
tcase_add_test(tc, test_init_rejects_bad_config);
|
|
tcase_add_test(tc, test_init_accepts_valid);
|
|
tcase_add_test(tc, test_read_bounds);
|
|
tcase_add_test(tc, test_program_single_partial_preserves_neighbours);
|
|
tcase_add_test(tc, test_program_crosses_sector_boundary);
|
|
tcase_add_test(tc, test_program_propagates_write_failure);
|
|
tcase_add_test(tc, test_erase_alignment);
|
|
tcase_add_test(tc, test_verify);
|
|
tcase_add_test(tc, test_blank_check);
|
|
tcase_add_test(tc, test_cleanup);
|
|
tcase_add_test(tc, test_program_propagates_erase_failure);
|
|
tcase_add_test(tc, test_read_returns_flash_contents);
|
|
tcase_add_test(tc, test_callbacks_reject_null_context);
|
|
tcase_add_test(tc, test_callbacks_reject_null_data);
|
|
tcase_add_test(tc, test_program_spans_three_sectors);
|
|
tcase_add_test(tc, test_writelock_writeunlock_noop);
|
|
suite_add_tcase(s, tc);
|
|
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;
|
|
}
|