mirror of https://github.com/wolfSSL/wolfBoot.git
265 lines
8.0 KiB
C
265 lines
8.0 KiB
C
/* boot_state.c
|
|
*
|
|
* Persistent anti-rollback reference and boot audit counters.
|
|
*
|
|
* 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 2 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
|
|
*/
|
|
|
|
#include "user_settings.h"
|
|
#include "boot_state.h"
|
|
#include "printf.h"
|
|
#include "loader.h"
|
|
#include <string.h>
|
|
|
|
#ifdef WOLFBOOT_ANTI_ROLLBACK
|
|
|
|
#define CRC32_POLY_REVERSED 0xEDB88320UL
|
|
#define CRC32_INIT 0xFFFFFFFFUL
|
|
#define CRC32_BITS_PER_BYTE 8
|
|
|
|
/* Bitwise CRC-32 (IEEE 802.3). Table-free: the record is 40 bytes and this
|
|
* runs once or twice per boot, so the table is not worth the footprint. */
|
|
static uint32_t boot_state_crc32(const uint8_t *buf, uint32_t len)
|
|
{
|
|
uint32_t crc = CRC32_INIT;
|
|
uint32_t i;
|
|
int bit;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
crc ^= (uint32_t)buf[i];
|
|
for (bit = 0; bit < CRC32_BITS_PER_BYTE; bit++) {
|
|
if (crc & 1U) {
|
|
crc = (crc >> 1) ^ CRC32_POLY_REVERSED;
|
|
}
|
|
else {
|
|
crc >>= 1;
|
|
}
|
|
}
|
|
}
|
|
return crc ^ CRC32_INIT;
|
|
}
|
|
|
|
/* The CRC covers every byte of the record except the trailing crc field. */
|
|
static uint32_t boot_state_compute_crc(const struct boot_state *st)
|
|
{
|
|
return boot_state_crc32((const uint8_t *)st,
|
|
(uint32_t)(sizeof(struct boot_state)
|
|
- sizeof(st->crc)));
|
|
}
|
|
|
|
static void boot_state_log(const char *msg)
|
|
{
|
|
wolfBoot_printf("%s\r\n", msg);
|
|
}
|
|
|
|
static void boot_state_log_val(const char *msg, uint32_t value)
|
|
{
|
|
wolfBoot_printf("%s%u\r\n", msg, value);
|
|
}
|
|
|
|
int boot_state_load(struct boot_state *st)
|
|
{
|
|
int ret;
|
|
|
|
if (st == NULL) {
|
|
return BOOT_STATE_UNAVAILABLE;
|
|
}
|
|
|
|
memset(st, 0, sizeof(struct boot_state));
|
|
|
|
ret = boot_state_backend_read((uint8_t *)st, sizeof(struct boot_state));
|
|
if (ret == BOOT_STATE_UNPROVISIONED) {
|
|
return BOOT_STATE_UNPROVISIONED;
|
|
}
|
|
/* A present but malformed record cannot be trusted: corrupt, which halts,
|
|
* not unavailable. */
|
|
if (ret == BOOT_STATE_CORRUPT) {
|
|
return BOOT_STATE_CORRUPT;
|
|
}
|
|
if (ret != 0) {
|
|
return BOOT_STATE_UNAVAILABLE;
|
|
}
|
|
|
|
/* Storage exists. From here on anything unexpected means the reference
|
|
* cannot be trusted, which is the one condition that must stop the boot. */
|
|
if (st->magic != BOOT_STATE_MAGIC) {
|
|
return BOOT_STATE_CORRUPT;
|
|
}
|
|
if (st->struct_version != BOOT_STATE_VERSION) {
|
|
return BOOT_STATE_CORRUPT;
|
|
}
|
|
if (st->crc != boot_state_compute_crc(st)) {
|
|
return BOOT_STATE_CORRUPT;
|
|
}
|
|
return BOOT_STATE_OK;
|
|
}
|
|
|
|
/* Persist, refreshing the CRC first. Failures are logged but not fatal: losing
|
|
* an audit write must not turn a good image into an unbootable unit. */
|
|
static void boot_state_save(struct boot_state *st)
|
|
{
|
|
st->magic = BOOT_STATE_MAGIC;
|
|
st->struct_version = BOOT_STATE_VERSION;
|
|
st->reserved = 0;
|
|
st->crc = boot_state_compute_crc(st);
|
|
|
|
if (boot_state_backend_write((const uint8_t *)st,
|
|
sizeof(struct boot_state)) != 0) {
|
|
#if defined(WOLFBOOT_ANTI_ROLLBACK_HALT_ON_FAILURE)
|
|
boot_state_log("could not persist anti-rollback state, refusing to boot");
|
|
/* Never returns. */
|
|
wolfBoot_panic();
|
|
#else
|
|
boot_state_log("WARNING: could not persist boot state");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* Load for update. A corrupt reference halts here, which is the whole point
|
|
* of the check. Unprovisioned is a clean slate, not a fault. */
|
|
static int boot_state_load_for_update(struct boot_state *st)
|
|
{
|
|
int status = boot_state_load(st);
|
|
|
|
if (status == BOOT_STATE_CORRUPT) {
|
|
boot_state_log("Version reference is corrupt, refusing to boot");
|
|
/* Never returns. */
|
|
wolfBoot_panic();
|
|
}
|
|
if (status == BOOT_STATE_UNAVAILABLE) {
|
|
#if defined(WOLFBOOT_ANTI_ROLLBACK_HALT_ON_FAILURE)
|
|
boot_state_log("anti-rollback state unavailable, refusing to boot");
|
|
/* Never returns. */
|
|
wolfBoot_panic();
|
|
#else
|
|
boot_state_log("WARNING: boot state backend unavailable");
|
|
#endif
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static int boot_state_update(uint32_t booted_version)
|
|
{
|
|
struct boot_state st;
|
|
struct boot_state before;
|
|
int status;
|
|
|
|
status = boot_state_load_for_update(&st);
|
|
if (status == BOOT_STATE_UNAVAILABLE) {
|
|
return status;
|
|
}
|
|
|
|
if (status == BOOT_STATE_UNPROVISIONED) {
|
|
/* Trust on first use: this image defines the floor. */
|
|
memset(&st, 0, sizeof(st));
|
|
st.anchor = booted_version;
|
|
st.record = booted_version;
|
|
boot_state_log_val("Anti-rollback anchor initialised at version ",
|
|
booted_version);
|
|
boot_state_save(&st);
|
|
return status;
|
|
}
|
|
|
|
/* Never persist over anything but a trustworthy record (a corrupt one
|
|
* already halted above). */
|
|
if (status != BOOT_STATE_OK) {
|
|
return status;
|
|
}
|
|
|
|
/* Snapshot so the write can be skipped when nothing actually changes. */
|
|
memcpy(&before, &st, sizeof(before));
|
|
|
|
if (booted_version < st.anchor) {
|
|
/* Explicitly allowed: an older but validly signed image boots. The
|
|
* audit event is the deliverable, not a veto. */
|
|
st.rollback_count++;
|
|
st.last_rollback_from = st.anchor;
|
|
st.last_rollback_to = booted_version;
|
|
boot_state_log_val("ROLLBACK: booting version ", booted_version);
|
|
boot_state_log_val("ROLLBACK: highest version previously booted was ",
|
|
st.anchor);
|
|
boot_state_log_val("ROLLBACK: total rollbacks recorded ",
|
|
st.rollback_count);
|
|
}
|
|
else if (booted_version > st.anchor) {
|
|
st.anchor = booted_version;
|
|
}
|
|
|
|
st.record = booted_version;
|
|
|
|
/* Skip the NV write when nothing changed (the CRC is recomputed on
|
|
* save). */
|
|
if (memcmp(&before, &st, sizeof(st) - sizeof(st.crc)) == 0) {
|
|
return status;
|
|
}
|
|
|
|
boot_state_save(&st);
|
|
return status;
|
|
}
|
|
|
|
void boot_state_on_boot(uint32_t booted_version)
|
|
{
|
|
(void)boot_state_update(booted_version);
|
|
boot_state_lock();
|
|
}
|
|
|
|
void boot_state_lock(void)
|
|
{
|
|
/* On every boot, even with nothing written or the state unreadable, so
|
|
* the OS can never move the floor. */
|
|
if (boot_state_backend_lock() != 0) {
|
|
#if defined(WOLFBOOT_ANTI_ROLLBACK_HALT_ON_FAILURE)
|
|
boot_state_log("could not lock anti-rollback state, refusing to boot");
|
|
/* Never returns. */
|
|
wolfBoot_panic();
|
|
#else
|
|
boot_state_log("WARNING: could not lock boot state");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void boot_state_on_failure(uint32_t fail_code)
|
|
{
|
|
struct boot_state st;
|
|
int status;
|
|
|
|
status = boot_state_load(&st);
|
|
if (status == BOOT_STATE_UNAVAILABLE) {
|
|
return;
|
|
}
|
|
if (status == BOOT_STATE_CORRUPT) {
|
|
/* Do not persist: a fresh record over a corrupt one would reset the
|
|
* floor to zero on the next boot. */
|
|
boot_state_log("Boot failure with a corrupt version reference; "
|
|
"not persisting over it");
|
|
return;
|
|
}
|
|
if (status == BOOT_STATE_UNPROVISIONED) {
|
|
/* Nothing stored yet: start a record to capture the failure. */
|
|
memset(&st, 0, sizeof(st));
|
|
}
|
|
|
|
st.boot_fail_count++;
|
|
st.last_fail_code = fail_code;
|
|
boot_state_log_val("Recording boot failure, code ", fail_code);
|
|
boot_state_save(&st);
|
|
}
|
|
|
|
#endif /* WOLFBOOT_ANTI_ROLLBACK */
|