mirror of https://github.com/wolfSSL/wolfBoot.git
277 lines
9.8 KiB
C
277 lines
9.8 KiB
C
/* unit-diagnostics.c
|
|
*
|
|
* Unit tests for the persistent failure diagnostics store.
|
|
*
|
|
* 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 IMAGE_HEADER_SIZE 256
|
|
#define WOLFBOOT_PERSIST_FAILURE_STATUS
|
|
#define WOLFBOOT_DIAGNOSTICS_ADDRESS 0xD0000000
|
|
#define WOLFBOOT_DIAGNOSTICS_SECTORS 2
|
|
#define WC_RSA_BLINDING
|
|
#define ECC_TIMING_RESISTANT
|
|
#include <stdio.h>
|
|
#include "libwolfboot.c"
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/mman.h>
|
|
#include <check.h>
|
|
|
|
#include "unit-mock-flash.c"
|
|
|
|
Suite *wolfboot_suite(void);
|
|
|
|
#define DIAG_REGION_SIZE (WOLFBOOT_DIAGNOSTICS_SECTORS * WOLFBOOT_SECTOR_SIZE)
|
|
|
|
static void diag_mmap(const char *path)
|
|
{
|
|
int ret = mmap_file(path, (void *)(uintptr_t)WOLFBOOT_DIAGNOSTICS_ADDRESS,
|
|
DIAG_REGION_SIZE, NULL);
|
|
ck_assert(ret >= 0);
|
|
}
|
|
|
|
static void record_one(uint8_t phase, uint8_t cause, uint8_t part, uint32_t ver)
|
|
{
|
|
int ret = wolfBoot_record_failure(phase, cause, part, ver);
|
|
ck_assert_int_eq(ret, 0);
|
|
}
|
|
|
|
START_TEST(test_record_and_read_newest_first)
|
|
{
|
|
struct wolfBoot_failure_record rec, oldest;
|
|
|
|
diag_mmap("/tmp/wolfboot-unit-diag-basic.bin");
|
|
ck_assert_int_eq(wolfBoot_clear_failures(), 0);
|
|
|
|
/* empty store */
|
|
ck_assert_int_eq(wolfBoot_get_failure_count(), 0);
|
|
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), -1);
|
|
|
|
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
|
|
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 11);
|
|
record_one(WOLFBOOT_FAILURE_PHASE_BOOT,
|
|
WOLFBOOT_FAILURE_CAUSE_SIGNATURE, PART_BOOT, 22);
|
|
record_one(WOLFBOOT_FAILURE_PHASE_ROLLBACK,
|
|
WOLFBOOT_FAILURE_CAUSE_NOT_CONFIRMED, PART_BOOT, 33);
|
|
|
|
ck_assert_int_eq(wolfBoot_get_failure_count(), 3);
|
|
|
|
/* index 0 is the most recent record */
|
|
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), 0);
|
|
ck_assert_uint_eq(rec.phase, WOLFBOOT_FAILURE_PHASE_ROLLBACK);
|
|
ck_assert_uint_eq(rec.cause, WOLFBOOT_FAILURE_CAUSE_NOT_CONFIRMED);
|
|
ck_assert_uint_eq(rec.partition, PART_BOOT);
|
|
ck_assert_uint_eq(rec.fw_version, 33);
|
|
|
|
ck_assert_int_eq(wolfBoot_get_failure(1, &rec), 0);
|
|
ck_assert_uint_eq(rec.phase, WOLFBOOT_FAILURE_PHASE_BOOT);
|
|
ck_assert_uint_eq(rec.cause, WOLFBOOT_FAILURE_CAUSE_SIGNATURE);
|
|
ck_assert_uint_eq(rec.fw_version, 22);
|
|
|
|
ck_assert_int_eq(wolfBoot_get_failure(2, &oldest), 0);
|
|
ck_assert_uint_eq(oldest.phase, WOLFBOOT_FAILURE_PHASE_UPDATE);
|
|
ck_assert_uint_eq(oldest.cause, WOLFBOOT_FAILURE_CAUSE_HASH);
|
|
ck_assert_uint_eq(oldest.partition, PART_UPDATE);
|
|
ck_assert_uint_eq(oldest.fw_version, 11);
|
|
|
|
/* sequence numbers increase from oldest to newest */
|
|
wolfBoot_get_failure(0, &rec);
|
|
ck_assert_uint_gt(rec.seq, oldest.seq);
|
|
|
|
/* invalid arguments */
|
|
ck_assert_int_eq(wolfBoot_get_failure(3, &rec), -1);
|
|
ck_assert_int_eq(wolfBoot_get_failure(-1, &rec), -1);
|
|
ck_assert_int_eq(wolfBoot_get_failure(0, NULL), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_ring_wrap_and_ordering)
|
|
{
|
|
struct wolfBoot_failure_record rec;
|
|
int slots = (int)DIAG_SLOTS_PER_SECTOR;
|
|
int total = 2 * slots + 1; /* force a full wrap back onto the first sector */
|
|
int count, i;
|
|
|
|
diag_mmap("/tmp/wolfboot-unit-diag-wrap.bin");
|
|
ck_assert_int_eq(wolfBoot_clear_failures(), 0);
|
|
|
|
for (i = 1; i <= total; i++)
|
|
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
|
|
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, (uint32_t)i);
|
|
|
|
/* With two sectors, after wrapping we keep the most recent full sector plus
|
|
* the one record written into the freshly recycled sector. */
|
|
count = wolfBoot_get_failure_count();
|
|
ck_assert_int_eq(count, slots + 1);
|
|
|
|
/* newest is the last record written */
|
|
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), 0);
|
|
ck_assert_uint_eq(rec.fw_version, (uint32_t)total);
|
|
|
|
/* oldest retained record */
|
|
ck_assert_int_eq(wolfBoot_get_failure(count - 1, &rec), 0);
|
|
ck_assert_uint_eq(rec.fw_version, (uint32_t)(total - count + 1));
|
|
|
|
/* records are returned strictly newest-first and contiguous */
|
|
for (i = 0; i < count; i++) {
|
|
ck_assert_int_eq(wolfBoot_get_failure(i, &rec), 0);
|
|
ck_assert_uint_eq(rec.fw_version, (uint32_t)(total - i));
|
|
}
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_crc_rejection)
|
|
{
|
|
struct wolfBoot_failure_record rec;
|
|
uint8_t *p;
|
|
|
|
diag_mmap("/tmp/wolfboot-unit-diag-crc.bin");
|
|
ck_assert_int_eq(wolfBoot_clear_failures(), 0);
|
|
|
|
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
|
|
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 1);
|
|
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
|
|
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 2);
|
|
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
|
|
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 3);
|
|
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
|
|
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 4);
|
|
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
|
|
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 5);
|
|
ck_assert_int_eq(wolfBoot_get_failure_count(), 5);
|
|
|
|
/* Corrupt the 3rd record (slot 2) of the active sector. Records are read
|
|
* contiguously, so the count truncates at the first invalid slot. */
|
|
p = (uint8_t *)(uintptr_t)(DIAG_SECTOR_ADDR(0) + DIAG_HDR_SIZE +
|
|
2 * DIAG_RECORD_SIZE);
|
|
p[0] ^= 0xFF;
|
|
ck_assert_int_eq(wolfBoot_get_failure_count(), 2);
|
|
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), 0);
|
|
ck_assert_uint_eq(rec.fw_version, 2);
|
|
|
|
/* Corrupt the sector header: the whole sector is rejected. */
|
|
p = (uint8_t *)(uintptr_t)DIAG_SECTOR_ADDR(0);
|
|
p[0] ^= 0xFF;
|
|
ck_assert_int_eq(wolfBoot_get_failure_count(), 0);
|
|
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_clear)
|
|
{
|
|
struct wolfBoot_failure_record rec;
|
|
|
|
diag_mmap("/tmp/wolfboot-unit-diag-clear.bin");
|
|
ck_assert_int_eq(wolfBoot_clear_failures(), 0);
|
|
|
|
record_one(WOLFBOOT_FAILURE_PHASE_BOOT,
|
|
WOLFBOOT_FAILURE_CAUSE_HEADER, PART_BOOT, 1);
|
|
record_one(WOLFBOOT_FAILURE_PHASE_BOOT,
|
|
WOLFBOOT_FAILURE_CAUSE_HASH, PART_BOOT, 2);
|
|
ck_assert_int_eq(wolfBoot_get_failure_count(), 2);
|
|
|
|
ck_assert_int_eq(wolfBoot_clear_failures(), 0);
|
|
ck_assert_int_eq(wolfBoot_get_failure_count(), 0);
|
|
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), -1);
|
|
|
|
/* recording works again after a clear; the sequence restarts */
|
|
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
|
|
WOLFBOOT_FAILURE_CAUSE_SIGNATURE, PART_UPDATE, 7);
|
|
ck_assert_int_eq(wolfBoot_get_failure_count(), 1);
|
|
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), 0);
|
|
ck_assert_uint_eq(rec.fw_version, 7);
|
|
ck_assert_uint_eq(rec.seq, 1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_torn_write_recovery)
|
|
{
|
|
struct wolfBoot_failure_record rec;
|
|
uint8_t *dirty;
|
|
int i;
|
|
|
|
diag_mmap("/tmp/wolfboot-unit-diag-torn.bin");
|
|
ck_assert_int_eq(wolfBoot_clear_failures(), 0);
|
|
|
|
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
|
|
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 1);
|
|
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
|
|
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 2);
|
|
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
|
|
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 3);
|
|
ck_assert_int_eq(wolfBoot_get_failure_count(), 3);
|
|
|
|
/* Simulate a power failure mid-write: the next slot (3) is partially
|
|
* programmed, so it is neither erased nor a valid record. */
|
|
dirty = (uint8_t *)(uintptr_t)(DIAG_SECTOR_ADDR(0) + DIAG_HDR_SIZE +
|
|
3 * DIAG_RECORD_SIZE);
|
|
for (i = 0; i < (int)DIAG_RECORD_SIZE; i++)
|
|
dirty[i] = 0xA5;
|
|
|
|
/* Recording must not get stuck on the dirty slot: it rotates to a fresh
|
|
* sector instead of reprogramming it. */
|
|
record_one(WOLFBOOT_FAILURE_PHASE_BOOT,
|
|
WOLFBOOT_FAILURE_CAUSE_SIGNATURE, PART_BOOT, 4);
|
|
|
|
/* The dirty slot was left untouched (never reprogrammed). */
|
|
ck_assert_int_ne(diag_read_record(DIAG_SECTOR_ADDR(0), 3, &rec), 0);
|
|
ck_assert_uint_eq(dirty[0], 0xA5);
|
|
|
|
/* All valid records remain readable, newest-first, in order. */
|
|
ck_assert_int_eq(wolfBoot_get_failure_count(), 4);
|
|
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), 0);
|
|
ck_assert_uint_eq(rec.fw_version, 4);
|
|
ck_assert_int_eq(wolfBoot_get_failure(3, &rec), 0);
|
|
ck_assert_uint_eq(rec.fw_version, 1);
|
|
|
|
/* Recording keeps making progress. */
|
|
record_one(WOLFBOOT_FAILURE_PHASE_UPDATE,
|
|
WOLFBOOT_FAILURE_CAUSE_HASH, PART_UPDATE, 5);
|
|
ck_assert_int_eq(wolfBoot_get_failure_count(), 5);
|
|
ck_assert_int_eq(wolfBoot_get_failure(0, &rec), 0);
|
|
ck_assert_uint_eq(rec.fw_version, 5);
|
|
}
|
|
END_TEST
|
|
|
|
Suite *wolfboot_suite(void)
|
|
{
|
|
Suite *s = suite_create("wolfboot-diagnostics");
|
|
TCase *diag = tcase_create("Failure diagnostics");
|
|
tcase_add_test(diag, test_record_and_read_newest_first);
|
|
tcase_add_test(diag, test_ring_wrap_and_ordering);
|
|
tcase_add_test(diag, test_crc_rejection);
|
|
tcase_add_test(diag, test_clear);
|
|
tcase_add_test(diag, test_torn_write_recovery);
|
|
suite_add_tcase(s, diag);
|
|
return s;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int fails;
|
|
argv0 = strdup(argv[0]);
|
|
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;
|
|
}
|