x86 FSP: add TPM-NV anti-rollback with a persistent version floor and boot audit counters

pull/933/head
David Garske 2026-10-07 13:47:32 -07:00
parent 2cb873bd8e
commit aa6fdb8fa9
21 changed files with 1102 additions and 30 deletions

View File

@ -46,10 +46,6 @@ jobs:
export DEBIAN_FRONTEND=noninteractive
sudo apt-get update -o Acquire::Retries=3
sudo apt-get install --no-install-recommends -y -q -o Acquire::Retries=3 nasm gcc-multilib
- name: setup git
run: |
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
- name: run test
run: |
cp config/examples/kontron_vx3060_s2.config .config

View File

@ -46,10 +46,6 @@ jobs:
export DEBIAN_FRONTEND=noninteractive
sudo apt-get update -o Acquire::Retries=3
sudo apt-get install --no-install-recommends -y -q -o Acquire::Retries=3 nasm gcc-multilib qemu-system-x86 swtpm uuid-dev
- name: setup git
run: |
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
- name: run test
run: |
./tools/scripts/x86_fsp/qemu/test_qemu.sh
@ -70,10 +66,25 @@ jobs:
export DEBIAN_FRONTEND=noninteractive
sudo apt-get update -o Acquire::Retries=3
sudo apt-get install --no-install-recommends -y -q -o Acquire::Retries=3 nasm gcc-multilib qemu-system-x86 uuid-dev dosfstools mtools e2fsprogs
- name: setup git
run: |
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
- name: run test
run: |
./tools/scripts/x86_fsp/qemu/test_qemu.sh -f
# TPM-NV anti-rollback: the floor anchors on a fresh swtpm, survives a TPM
# restart, and a validly signed downgrade boots and is counted.
fsp_qemu_antirollback_test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: install req
run: |
export DEBIAN_FRONTEND=noninteractive
sudo apt-get update -o Acquire::Retries=3
sudo apt-get install --no-install-recommends -y -q -o Acquire::Retries=3 nasm gcc-multilib qemu-system-x86 swtpm uuid-dev
- name: run test
run: |
./tools/scripts/x86_fsp/qemu/test_qemu.sh -r

2
.gitignore vendored
View File

@ -181,6 +181,8 @@ tools/unit-tests/unit-update-flash-enc
tools/unit-tests/unit-update-flash-db
tools/unit-tests/unit-update-flash-enc-full
tools/unit-tests/unit-update-ram
tools/unit-tests/unit-boot-state
tools/unit-tests/unit-boot-state-halt
tools/unit-tests/unit-boot-x86-fsp
tools/unit-tests/unit-image-rsa
tools/unit-tests/unit-multiboot

View File

@ -2312,6 +2312,10 @@ ifeq ("${FSP}", "1")
OBJS += src/x86/fsp_s.o
UPDATE_OBJS := src/update_disk.o
CFLAGS+=-DWOLFBOOT_UPDATE_DISK
ifeq ($(ANTI_ROLLBACK),1)
OBJS += src/boot_state.o
OBJS += src/boot_state_tpm.o
endif
ifeq ($(64BIT),1)
LDFLAGS += -m elf_x86_64 --oformat elf64-x86-64
CFLAGS += -m64

View File

@ -0,0 +1,45 @@
ARCH=x86_64
TARGET=x86_fsp_qemu
WOLFBOOT_SMALL_STACK=1
SIGN=ECC256
HASH=SHA256
DEBUG=1
SPMATH=1
FORCE_32BIT=1
ENCRYPTION=0
WOLFBOOT_NO_PARTITIONS=1
# TPM Keystore options
#WOLFBOOT_TPM_KEYSTORE?=1
#WOLFBOOT_TPM_KEYSTORE_NV_BASE?=0x01800200
#WOLFBOOT_TPM_POLICY_NV_INDEX?=0x01800201
# 4gb - 8mb
WOLFBOOT_LOAD_BASE=0x2000000
WOLFBOOT_LOAD_ADDRESS=0x1000000
# required for keytools
WOLFBOOT_SECTOR_SIZE?=0x1000
WOLFBOOT_DATA_ADDRESS=0x1000000
FSP_M_BASE=0xffe30000
FSP_S_BASE=0xffed6000
FSP_T_BASE=0xfffe0000
FSP_S_LOAD_BASE=0x0FED5F00
WOLFBOOT_ORIGIN=0xfff80000
BOOTLOADER_PARTITION_SIZE=0xe0000
BIOS_REGION_SIZE=0x800000
MACHINE_OBJ=src/x86/qemu_fsp.o
FSP_T_BIN=./src/x86/fsp_t.bin
FSP_M_BIN=./src/x86/fsp_m.bin
FSP_S_BIN=./src/x86/fsp_s.bin
STAGE1_AUTH=1
64BIT=1
ELF=1
MULTIBOOT2=1
# TPM-NV anti-rollback (version floor and audit counters), tested by
# tools/scripts/x86_fsp/qemu/test_qemu.sh -r
ANTI_ROLLBACK=1
ANTI_ROLLBACK_HALT_ON_FAILURE=1

View File

@ -0,0 +1,85 @@
/* boot_state.h
*
* 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
*/
/* Anti-rollback state: anchor = highest version ever booted (trust on first
* use), record = last booted. See the Anti-rollback section of Targets.md. */
#ifndef BOOT_STATE_H
#define BOOT_STATE_H
#include <stdint.h>
#define BOOT_STATE_MAGIC 0x42535431UL /* "BST1" */
#define BOOT_STATE_VERSION 1
/* Result of loading the persisted state. */
#define BOOT_STATE_OK 0 /* loaded and trustworthy */
#define BOOT_STATE_UNPROVISIONED 1 /* nothing stored yet; not an error */
#define BOOT_STATE_CORRUPT (-1) /* stored but unusable -- fatal */
#define BOOT_STATE_UNAVAILABLE (-2) /* backend could not be reached */
/* Failure codes recorded in last_fail_code. */
#define BOOT_FAIL_NONE 0
#define BOOT_FAIL_NO_IMAGE 1
#define BOOT_FAIL_VERIFY_PRIMARY 2
#define BOOT_FAIL_VERIFY_BOTH 3
#define BOOT_FAIL_ROLLBACK 4
/* On-storage layout. Packed and explicitly sized so the representation does
* not shift between the bootloader and any provisioning tool. */
struct boot_state {
uint32_t magic;
uint32_t anchor;
uint32_t record;
uint32_t rollback_count;
uint32_t last_rollback_from;
uint32_t last_rollback_to;
uint32_t boot_fail_count;
uint32_t last_fail_code;
uint16_t struct_version;
uint16_t reserved;
uint32_t crc;
} __attribute__((packed));
/* Backend, implemented per platform. v1 uses TPM NV (src/boot_state_tpm.c). */
int boot_state_backend_read(uint8_t *buf, uint32_t len);
int boot_state_backend_write(const uint8_t *buf, uint32_t len);
/* Write-locks the record until the next TPM reset, so the booted OS cannot
* rewrite it. */
int boot_state_backend_lock(void);
/* Load the persisted state. Returns one of the BOOT_STATE_* codes above. */
int boot_state_load(struct boot_state *st);
/* Called once an image has verified, immediately before handing off.
* Applies the rollback check, updates anchor and record, and persists. */
void boot_state_on_boot(uint32_t booted_version);
/* Lock the stored state against the OS without recording a boot (golden
* slot). Halts on failure with ANTI_ROLLBACK_HALT_ON_FAILURE. */
void boot_state_lock(void);
/* Called when the boot policy has decided no image may be booted. Records the
* failure so the evidence survives the power cycle. */
void boot_state_on_failure(uint32_t fail_code);
#endif /* BOOT_STATE_H */

View File

@ -40,6 +40,9 @@ extern WOLFTPM2_KEY wolftpm_srk;
#ifndef WOLFBOOT_TPM_SEAL_NV_BASE
#define WOLFBOOT_TPM_SEAL_NV_BASE 0x01400300
#endif
#ifndef WOLFBOOT_TPM_BOOT_STATE_NV_BASE
#define WOLFBOOT_TPM_BOOT_STATE_NV_BASE 0x01400400
#endif
/* The PCR bank algorithm and its digest size must always agree. They used to
* be defined together under a single #ifndef on the algorithm, which meant
* overriding only WOLFBOOT_TPM_PCR_ALG left WOLFBOOT_TPM_PCR_DIG_SZ undefined

View File

@ -57,6 +57,20 @@ ifeq ($(ACPI),1)
CFLAGS+=-D"WOLFBOOT_ACPI"
endif
# TPM NV version floor and boot audit counters (see docs/Targets.md).
# HALT_ON_FAILURE also halts when the TPM state cannot be read or locked.
ifeq ($(ANTI_ROLLBACK),1)
ifeq ($(filter $(TARGET),x86_fsp_qemu kontron_vx3060_s2 nai_68int6),)
$(error ANTI_ROLLBACK=1 is only wired into the x86 FSP disk boot)
endif
CFLAGS+=-D"WOLFBOOT_ANTI_ROLLBACK"
# The backend is TPM NV (src/boot_state_tpm.c), so wolfTPM is required.
WOLFTPM:=1
ifeq ($(ANTI_ROLLBACK_HALT_ON_FAILURE),1)
CFLAGS+=-D"WOLFBOOT_ANTI_ROLLBACK_HALT_ON_FAILURE"
endif
endif
ifeq ($(USE_CLANG),1)
ifeq ($(USE_GCC),1)
$(error USE_CLANG=1 is incompatible with USE_GCC=1; set USE_GCC=0)

264
src/boot_state.c 100644
View File

@ -0,0 +1,264 @@
/* 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 */

View File

@ -0,0 +1,156 @@
/* boot_state_tpm.c
*
* TPM NV backend for the 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
*/
/* TPM NV storage backend for src/boot_state.c; another root of trust can
* replace this file without touching the policy or src/tpm.c. */
#include "user_settings.h"
#ifdef WOLFBOOT_ANTI_ROLLBACK
#include "boot_state.h"
#include "tpm.h"
#include <wolftpm/tpm2_wrap.h>
#include <string.h>
#define BOOT_STATE_NV_WRITE_MASK (TPMA_NV_PLATFORMCREATE | TPMA_NV_PPWRITE | \
TPMA_NV_OWNERWRITE | TPMA_NV_AUTHWRITE | TPMA_NV_POLICYWRITE | \
TPMA_NV_TPM_NT)
#define BOOT_STATE_NV_WRITE_ATTR (TPMA_NV_PLATFORMCREATE | TPMA_NV_PPWRITE)
int boot_state_backend_read(uint8_t *buf, uint32_t len)
{
WOLFTPM2_DEV *dev = &wolftpm_dev;
WOLFTPM2_NV nv;
word32 readSz;
int rc;
memset(&nv, 0, sizeof(nv));
nv.handle.hndl = WOLFBOOT_TPM_BOOT_STATE_NV_BASE;
/* Only an undefined index (format-1 TPM_RC_HANDLE) is unprovisioned; any
* other error must not reset the floor through trust-on-first-use. */
rc = wolfTPM2_NVOpen(dev, &nv, WOLFBOOT_TPM_BOOT_STATE_NV_BASE, NULL, 0);
if (rc != 0) {
if ((rc & RC_FMT1) && ((rc & RC_MAX_FMT1) == TPM_RC_HANDLE)) {
return BOOT_STATE_UNPROVISIONED;
}
return BOOT_STATE_UNAVAILABLE;
}
/* Only an index this backend created can hold the floor: platform-created
* and writable by the platform hierarchy alone. */
if ((nv.attributes & BOOT_STATE_NV_WRITE_MASK) != BOOT_STATE_NV_WRITE_ATTR) {
return BOOT_STATE_CORRUPT;
}
wolfTPM2_SetAuthHandle(dev, 0, &nv.handle);
readSz = len;
rc = wolfTPM2_NVReadAuth(dev, &nv, WOLFBOOT_TPM_BOOT_STATE_NV_BASE,
buf, &readSz, 0);
/* Clear the auth slot the read set, so it does not carry into later ops. */
wolfTPM2_UnsetAuth(dev, 0);
/* An index smaller than the record (TPM_RC_NV_RANGE) is malformed:
* corrupt, which halts, not unavailable. */
if (rc == TPM_RC_NV_RANGE) {
return BOOT_STATE_CORRUPT;
}
if (rc != 0) {
return BOOT_STATE_UNAVAILABLE;
}
if (readSz != len) {
return BOOT_STATE_CORRUPT;
}
return 0;
}
int boot_state_backend_write(const uint8_t *buf, uint32_t len)
{
WOLFTPM2_DEV *dev = &wolftpm_dev;
WOLFTPM2_HANDLE parent;
WOLFTPM2_NV nv;
NV_Write_In in;
word32 nvAttributes = 0;
int rc;
if (len > sizeof(in.data.buffer)) {
return -1;
}
memset(&parent, 0, sizeof(parent));
memset(&nv, 0, sizeof(nv));
parent.hndl = TPM_RH_PLATFORM;
nv.handle.hndl = WOLFBOOT_TPM_BOOT_STATE_NV_BASE;
wolfTPM2_GetNvAttributesTemplate(TPM_RH_PLATFORM, &nvAttributes);
nvAttributes &= ~TPMA_NV_AUTHWRITE;
/* Created on first write (trust-on-first-use). No TPMA_NV_WRITEDEFINE:
* the record is rewritten as versions change. */
rc = wolfTPM2_NVCreateAuth(dev, &parent, &nv,
WOLFBOOT_TPM_BOOT_STATE_NV_BASE,
nvAttributes, len, NULL, 0);
if (rc == TPM_RC_NV_DEFINED) {
rc = 0;
}
wolfTPM2_UnsetAuth(dev, 1);
if (rc != 0) {
return -1;
}
/* TPMA_NV_PPWRITE: the platform authorizes the write. Its auth is empty
* until boot_state_backend_lock() disables the hierarchy. */
memset(&in, 0, sizeof(in));
in.authHandle = TPM_RH_PLATFORM;
in.nvIndex = WOLFBOOT_TPM_BOOT_STATE_NV_BASE;
in.data.size = (UINT16)len;
memcpy(in.data.buffer, buf, len);
wolfTPM2_SetAuthPassword(dev, 0, NULL);
rc = TPM2_NV_Write(&in);
wolfTPM2_UnsetAuth(dev, 0);
if (rc != 0) {
return -1;
}
return 0;
}
int boot_state_backend_lock(void)
{
WOLFTPM2_DEV *dev = &wolftpm_dev;
HierarchyControl_In in;
int rc;
/* Disable the platform hierarchy until the next Startup(CLEAR) (TCG PC
* Client Platform spec, section 10): the OS cannot write or undefine. */
memset(&in, 0, sizeof(in));
in.authHandle = TPM_RH_PLATFORM;
in.enable = TPM_RH_PLATFORM;
in.state = 0;
wolfTPM2_SetAuthPassword(dev, 0, NULL);
rc = TPM2_HierarchyControl(&in);
wolfTPM2_UnsetAuth(dev, 0);
if (rc != 0) {
return -1;
}
return 0;
}
#endif /* WOLFBOOT_ANTI_ROLLBACK */

View File

@ -1781,11 +1781,12 @@ int wolfBoot_tpm2_init(void)
void wolfBoot_tpm2_deinit(void)
{
#ifdef WOLFBOOT_TPM_KEYSTORE
#if !defined(ARCH_SIM) && !defined(WOLFBOOT_TPM_NO_CHG_PLAT_AUTH)
/* Enable parameter encryption for session */
int rc = wolfTPM2_SetAuthSession(&wolftpm_dev, 0, &wolftpm_session,
(TPMA_SESSION_decrypt | TPMA_SESSION_encrypt |
TPMA_SESSION_continueSession));
/* Anti-rollback has already disabled the platform hierarchy. */
#if !defined(ARCH_SIM) && !defined(WOLFBOOT_TPM_NO_CHG_PLAT_AUTH) && \
!defined(WOLFBOOT_ANTI_ROLLBACK)
/* Password (empty platform auth) in slot 0; ChangePlatformAuth puts the
* parameter-encryption session in slot 1. */
int rc = wolfTPM2_SetAuthPassword(&wolftpm_dev, 0, NULL);
if (rc == 0) {
/* Change platform auth to random value, to prevent application
* from being able to use platform hierarchy. This is defined in

View File

@ -83,6 +83,10 @@ static uint8_t disk_encrypt_nonce[ENCRYPT_NONCE_SIZE];
#include "pci.h"
#include "x86/tgl_fsp.h"
#ifdef WOLFBOOT_ANTI_ROLLBACK
#include "boot_state.h"
#endif
#ifdef TARGET_kontron_vx3060_s2
#define BOOT_PART_A 5
#define BOOT_PART_B 6
@ -829,6 +833,9 @@ void RAMFUNCTION wolfBoot_start(void)
#ifdef DISK_ENCRYPT
disk_decrypted_header_clear(dec_hdr);
disk_crypto_clear();
#endif
#if defined(WOLFBOOT_ANTI_ROLLBACK) && defined(WOLFBOOT_FSP)
boot_state_on_failure(BOOT_FAIL_NO_IMAGE);
#endif
wolfBoot_panic();
#endif
@ -898,6 +905,9 @@ void RAMFUNCTION wolfBoot_start(void)
#ifdef DISK_ENCRYPT
disk_decrypted_header_clear(dec_hdr);
disk_crypto_clear();
#endif
#if defined(WOLFBOOT_ANTI_ROLLBACK) && defined(WOLFBOOT_FSP)
boot_state_on_failure(BOOT_FAIL_ROLLBACK);
#endif
wolfBoot_panic();
return;
@ -1075,6 +1085,9 @@ void RAMFUNCTION wolfBoot_start(void)
disk_crypto_clear();
#endif
wolfBoot_printf("Unable to find a valid partition!\r\n");
#if defined(WOLFBOOT_ANTI_ROLLBACK) && defined(WOLFBOOT_FSP)
boot_state_on_failure(BOOT_FAIL_VERIFY_BOTH);
#endif
wolfBoot_panic();
return;
}
@ -1307,6 +1320,26 @@ void RAMFUNCTION wolfBoot_start(void)
#ifndef WOLFBOOT_SKIP_BOOT_VERIFY
PART_SANITY_CHECK(&os_image);
#endif
#if defined(WOLFBOOT_ANTI_ROLLBACK) && defined(WOLFBOOT_FSP)
/* Last step before handoff, so a failure above never moves the floor.
* The golden slot only locks. */
#ifdef DISK_GOLDEN_SLOT
if (SLOT_IS_GOLDEN(selected))
boot_state_lock();
else
#endif
{
/* Use the version from the verified header, not the early
* pre-verification reads. */
uint32_t booted_ver;
#ifdef DISK_ENCRYPT
booted_ver = get_decrypted_blob_version(dec_hdr);
#else
booted_ver = wolfBoot_get_blob_version((uint8_t *)hdr_ptr);
#endif
boot_state_on_boot(booted_ver);
}
#endif
#ifdef DISK_ENCRYPT
disk_decrypted_header_clear(dec_hdr);
disk_crypto_clear();

View File

@ -40,7 +40,7 @@ set -e
set -x
# Parse command line options
while getopts "d:wpt" opt; do
while getopts "d:wptc" opt; do
case "$opt" in
d)
DEBUG_STAGE="$OPTARG"

View File

@ -12,6 +12,9 @@ CONFIG_FILE=${CONFIG_FILE:-"${WOLFBOOT_DIR}/.config"}
set -e
# Identity for the patch commits in the scratch edk2 clone only
GIT_ID="-c user.name=wolfBoot -c user.email=wolfboot@wolfssl.com"
if [ ! -f "${WOLFBOOT_DIR}/arch.mk" ]; then
echo "Error: Current directory is not the root directory of the source tree"
exit 1
@ -37,7 +40,7 @@ download_edkii() {
git clone "${EDKII_REPO}" edk2)
else
(cd "$WORK_DIR/edk2" &&
git stash save --include-untracked "Auto-stashed on $(date)" &&
git $GIT_ID stash save --include-untracked "Auto-stashed on $(date)" &&
git clean -fd) # This will remove untracked files/directories
fi
@ -49,9 +52,9 @@ download_edkii() {
download_sbl_patch_and_patch_edkii() {
(cd "$WORK_DIR/edk2" &&
curl -L -o 0001-Build-QEMU-FSP-2.0-binaries.patch ${SBL_PATCH_URL};
git am --keep-cr --whitespace=nowarn 0001-Build-QEMU-FSP-2.0-binaries.patch)
git $GIT_ID am --keep-cr --whitespace=nowarn 0001-Build-QEMU-FSP-2.0-binaries.patch)
(cd "$WORK_DIR/edk2" &&
git am --keep-cr --whitespace=nowarn "${SCRIPT_DIR}"/0002-add-W-no-warnings-to-compile-with-gcc-12.patch "${SCRIPT_DIR}"/0003-disable-optmizaion-patch-for-edk2.patch "${SCRIPT_DIR}"/0004-fix-PatchFv-make-regex-match-both-8-and-16-length-ad.patch;
git $GIT_ID am --keep-cr --whitespace=nowarn "${SCRIPT_DIR}"/0002-add-W-no-warnings-to-compile-with-gcc-12.patch "${SCRIPT_DIR}"/0003-disable-optmizaion-patch-for-edk2.patch "${SCRIPT_DIR}"/0004-fix-PatchFv-make-regex-match-both-8-and-16-length-ad.patch;
mkdir -p MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/c/include/)
}

View File

@ -4,7 +4,7 @@ set -e
WOLFBOOT_DIR=$(pwd)
# Parse command line options
while getopts "bf" opt; do
while getopts "bfr" opt; do
case "$opt" in
b)
REBUILD_ONLY=true
@ -12,10 +12,14 @@ while getopts "bf" opt; do
f)
FS_BOOT=true
;;
r)
ANTI_ROLLBACK=true
;;
*)
echo "Usage: $0 [-b] [-f]"
echo "Usage: $0 [-b] [-f] [-r]"
echo "-b : rebuild only (skip FSP build, key gen, ecc)"
echo "-f : boot from a file on a read-only filesystem (DISK_FS)"
echo "-r : TPM-NV anti-rollback across three boots"
exit 1
;;
esac
@ -25,6 +29,8 @@ done
if [ "$REBUILD_ONLY" != true ]; then
if [ "$FS_BOOT" = true ]; then
CONFIG=x86_fsp_qemu_fs.config
elif [ "$ANTI_ROLLBACK" = true ]; then
CONFIG=x86_fsp_qemu_antirollback.config
else
CONFIG=x86_fsp_qemu_seal.config
fi
@ -33,7 +39,7 @@ if [ "$REBUILD_ONLY" != true ]; then
cp "config/examples/${CONFIG}" .config
./tools/scripts/x86_fsp/qemu/qemu_build_fsp.sh
make keytools
if [ "$FS_BOOT" = true ]; then
if [ "$FS_BOOT" = true ] || [ "$ANTI_ROLLBACK" = true ]; then
# The filesystem config does not use the TPM, so only the image key is
# needed here. It must match SIGN= in x86_fsp_qemu_fs.config (ECC256),
# or the generated src/keystore.c raises a key-algorithm mismatch.
@ -46,7 +52,7 @@ if [ "$REBUILD_ONLY" != true ]; then
fi
fi
if [ "$FS_BOOT" = true ]; then
if [ "$FS_BOOT" = true ] || [ "$ANTI_ROLLBACK" = true ]; then
make
else
# manual add ECC256 for TPM
@ -56,7 +62,7 @@ fi
# test-app
make test-app/image.elf
if [ "$FS_BOOT" != true ]; then
if [ "$FS_BOOT" != true ] && [ "$ANTI_ROLLBACK" != true ]; then
# compute pcr0 value
PCR0=$(python ./tools/scripts/x86_fsp/compute_pcr.py --target qemu wolfboot_stage1.bin | tail -n 1)
echo $PCR0
@ -67,11 +73,66 @@ fi
if [ "$FS_BOOT" = true ]; then
FS=1 IMAGE=test-app/image.elf SIGN=--ecc256 \
./tools/scripts/x86_fsp/qemu/make_hd.sh
elif [ "$ANTI_ROLLBACK" = true ]; then
IMAGE=test-app/image.elf SIGN=--ecc256 \
./tools/scripts/x86_fsp/qemu/make_hd.sh
else
IMAGE=test-app/image.elf SIGN=--ecc384 \
./tools/scripts/x86_fsp/qemu/make_hd.sh
fi
# Boot once with the swtpm state kept from the previous boot, wait for the
# test app, and leave the console log in /tmp/qemu_output.
run_qemu_tpm() {
./tools/scripts/x86_fsp/qemu/qemu.sh -t -p $1 > /tmp/qemu_output 2>&1 &
for i in $(seq 1 30); do
sleep 1
grep -q "wolfBoot QEMU x86 FSP test app" /tmp/qemu_output && break
grep -q "PANIC" /tmp/qemu_output && break
done
timeout 5 echo 'quit' > /tmp/qemu_mon.in || true
wait
cat /tmp/qemu_output
}
expect_log() {
if ! grep -q "$1" /tmp/qemu_output; then
echo -e "\e[31mTEST FAILED: missing '$1'\e[0m"
exit 255
fi
}
reject_log() {
if grep -q "$1" /tmp/qemu_output; then
echo -e "\e[31mTEST FAILED: unexpected '$1'\e[0m"
exit 255
fi
}
if [ "$ANTI_ROLLBACK" = true ]; then
# Boot 1, fresh TPM: slot B (version 2) boots and anchors the floor.
run_qemu_tpm -c
expect_log "Anti-rollback anchor initialised at version 2"
expect_log "wolfBoot QEMU x86 FSP test app"
# Boot 2: both slots v1. It boots, the floor survived the TPM restart and
# the downgrade is written (platform hierarchy enabled again at startup).
dd if=image_v1_signed.bin of=app.bin bs=512 seek=34816 conv=notrunc
run_qemu_tpm
expect_log "ROLLBACK: booting version 1"
expect_log "ROLLBACK: total rollbacks recorded 1"
expect_log "wolfBoot QEMU x86 FSP test app"
# Boot 3: the counter keeps counting across boots.
run_qemu_tpm
expect_log "ROLLBACK: total rollbacks recorded 2"
expect_log "wolfBoot QEMU x86 FSP test app"
reject_log "WARNING: could not"
reject_log "PANIC"
echo -e "\e[32mTEST OK\e[0m"
exit 0
fi
echo "RUNNING QEMU"
# launch qemu in background
# -t starts an emulated TPM. The seal path needs it; the filesystem config

View File

@ -54,7 +54,9 @@ download_split_tool() {
}
patch_tgl_fsp() {
(cd "$WORK_DIR"/FSP && git am --keep-cr "${PATCH}")
# Identity for the patch commit in the scratch FSP clone only
(cd "$WORK_DIR"/FSP && git -c user.name=wolfBoot \
-c user.email=wolfboot@wolfssl.com am --keep-cr "${PATCH}")
}
rebase_fsp_component() {

View File

@ -240,6 +240,8 @@ TESTS+=unit-xspi-tfd-index
TESTS+=unit-pic32-pfswap
TESTS+=unit-secure-handoff
TESTS+=unit-imx-rt700
TESTS+=unit-boot-state
TESTS+=unit-boot-state-halt
# linux_loader.c is x86 32-bit only, so its unit tests need a working 32-bit
# (multilib) toolchain. Probe whether "gcc -m32" can link, and only add the
@ -868,6 +870,20 @@ unit-linux-loader-initrd: ../../include/target.h unit-linux-loader-initrd.c
-DWOLFBOOT_LOAD_BASE=0x100000 \
-Wl,--defsym,_end_wb=_start_wolfboot+0x100000
# unit-boot-state exercises the anti-rollback state machine in src/boot_state.c
# through a mock storage backend. Built standalone (not via the common CFLAGS)
# so the stubs-boot-state/ headers shadow user_settings.h/printf.h/loader.h and the
# host build needs no target configuration. boot_state.c has no inline asm, so
# no -m32 is required.
unit-boot-state: unit-boot-state.c ../../src/boot_state.c
gcc -o $@ unit-boot-state.c -Istubs-boot-state -I../../src -I../../include \
-g -Wall -DUNIT_TEST -DWOLFBOOT_ANTI_ROLLBACK
unit-boot-state-halt: unit-boot-state.c ../../src/boot_state.c
gcc -o $@ unit-boot-state.c -Istubs-boot-state -I../../src -I../../include \
-g -Wall -DUNIT_TEST -DWOLFBOOT_ANTI_ROLLBACK \
-DWOLFBOOT_ANTI_ROLLBACK_HALT_ON_FAILURE
unit-boot-x86-fsp: ../../include/target.h unit-boot-x86_fsp.c
gcc -o $@ $^ $(CFLAGS) -DWOLFBOOT_LOAD_BASE=0x100000 -DWOLFBOOT_FSP \
-DUCODE0_ADDRESS=0 -ffunction-sections -fdata-sections $(LDFLAGS) \
@ -1700,14 +1716,14 @@ unit-p1021-read-badblock: unit-p1021-read-badblock.c p1021_read_extract.h \
gcc -o $@ unit-p1021-read-badblock.c $(CFLAGS) $(LDFLAGS)
# unit-kontron-tgl-spi runs the real tgl_lock_bios_region() from
# hal/kontron_vx3060_s2.c against mocked PCI config space and an
# hal/x86_fsp_tgl.c against mocked PCI config space and an
# MMIO array at the SPI BAR address (F-12104: the lock was never
# applied - no hal_flash_protect() override - and targeted the
# wrong register space).
kontron_spi_extract.h: ../../hal/kontron_vx3060_s2.c
kontron_spi_extract.h: ../../hal/x86_fsp_tgl.c
sed -n '/^#define SPI_PCI_DEV/,/^#define SPI_FLOCKDN/p' $< > $@
kontron_spi_fn_extract.h: ../../hal/kontron_vx3060_s2.c
kontron_spi_fn_extract.h: ../../hal/x86_fsp_tgl.c
sed -n '/^int tgl_lock_bios_region/,/^}/p' $< > $@
sed -n '/^int hal_flash_protect/,/^}/p' $< >> $@

View File

@ -0,0 +1,27 @@
/* hal.h (host test stub)
*
* 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
*/
/* The build's loader.h pulls in hooks.h and the platform panic;
* boot_state.c only needs the declaration of the panic it calls. */
#ifndef UNIT_BOOT_STATE_LOADER_H
#define UNIT_BOOT_STATE_LOADER_H
void wolfBoot_panic(void);
#endif

View File

@ -0,0 +1,27 @@
/* printf.h (host test stub)
*
* 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
*/
/* boot_state.c logs its decisions with wolfBoot_printf; the host test asserts
* on return values, so the log is dropped to keep PASS/FAIL output clean. */
#ifndef UNIT_BOOT_STATE_PRINTF_H
#define UNIT_BOOT_STATE_PRINTF_H
#define wolfBoot_printf(...) do {} while (0)
#endif

View File

@ -0,0 +1,26 @@
/* user_settings.h (host test stub)
*
* 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
*/
/* Shadows the build's user_settings.h so boot_state.c builds on the host
* without a target configuration. */
#ifndef UNIT_BOOT_STATE_USER_SETTINGS_H
#define UNIT_BOOT_STATE_USER_SETTINGS_H
#endif

View File

@ -0,0 +1,296 @@
/* unit-boot-state.c
*
* Host unit tests for the anti-rollback state machine in src/boot_state.c:
* the bitwise CRC-32, the load/validate path and its BOOT_STATE_* codes,
* trust-on-first-use anchoring, the rollback-audit path and the
* skip-write-when-unchanged comparison. The storage backend is isolated behind
* boot_state_backend_{read,write}(), so a mock backend exercises the whole
* policy on the host. Built with plain gcc; boot_state.c has no inline asm.
*
* 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 <stdio.h>
#include <string.h>
#include <stdint.h>
#include <setjmp.h>
#include "boot_state.h"
/* Mock backend and halt stub. A nonzero mock_read_code is returned by the
* read; zero serves the record from mock_nv. */
static uint8_t mock_nv[sizeof(struct boot_state)];
static int mock_read_code;
static int mock_write_fail;
static int mock_write_called;
static uint8_t mock_write_buf[sizeof(struct boot_state)];
static jmp_buf halt_env;
static int halted;
#if defined(WOLFBOOT_ANTI_ROLLBACK_HALT_ON_FAILURE)
static const int halt_on_failure = 1;
#else
static const int halt_on_failure = 0;
#endif
static int mock_lock_called;
static int mock_lock_fail;
int boot_state_backend_read(uint8_t *buf, uint32_t len)
{
if (mock_read_code != 0)
return mock_read_code;
memcpy(buf, mock_nv, len);
return 0;
}
int boot_state_backend_write(const uint8_t *buf, uint32_t len)
{
mock_write_called++;
if (len <= sizeof(mock_write_buf)) {
memcpy(mock_write_buf, buf, len);
memcpy(mock_nv, buf, len);
}
return mock_write_fail ? -1 : 0;
}
int boot_state_backend_lock(void)
{
mock_lock_called++;
return mock_lock_fail ? -1 : 0;
}
void wolfBoot_panic(void)
{
halted = 1;
longjmp(halt_env, 1);
}
#include "../../src/boot_state.c"
static int fails;
#define CHECK(cond, msg) \
do { if (!(cond)) { printf("FAIL: %s\n", (msg)); fails++; } } while (0)
static void make_valid(struct boot_state *st, uint32_t anchor, uint32_t record)
{
memset(st, 0, sizeof(*st));
st->magic = BOOT_STATE_MAGIC;
st->struct_version = BOOT_STATE_VERSION;
st->anchor = anchor;
st->record = record;
st->crc = boot_state_compute_crc(st);
}
static void reset_mock(void)
{
memset(mock_nv, 0, sizeof(mock_nv));
mock_read_code = 0;
mock_write_fail = 0;
mock_write_called = 0;
memset(mock_write_buf, 0, sizeof(mock_write_buf));
halted = 0;
mock_lock_called = 0;
mock_lock_fail = 0;
}
int main(void)
{
struct boot_state st, rec;
/* CRC-32 known-answer: the IEEE 802.3 check value of "123456789". */
CHECK(boot_state_crc32((const uint8_t *)"123456789", 9) == 0xCBF43926u,
"crc32 KAT");
/* Unprovisioned passes through unchanged. */
reset_mock();
mock_read_code = BOOT_STATE_UNPROVISIONED;
CHECK(boot_state_load(&st) == BOOT_STATE_UNPROVISIONED,
"load unprovisioned");
/* A well-formed record loads OK with its fields intact. */
reset_mock();
make_valid(&rec, 5, 5);
memcpy(mock_nv, &rec, sizeof(rec));
CHECK(boot_state_load(&st) == BOOT_STATE_OK, "load ok");
CHECK(st.anchor == 5 && st.record == 5, "load ok fields");
/* Wrong magic is corrupt (checked before the CRC). */
reset_mock();
make_valid(&rec, 5, 5);
rec.magic ^= 0xFFu;
memcpy(mock_nv, &rec, sizeof(rec));
CHECK(boot_state_load(&st) == BOOT_STATE_CORRUPT, "load bad magic");
/* A CRC mismatch is corrupt. */
reset_mock();
make_valid(&rec, 7, 7);
rec.crc ^= 0xA5A5A5A5u;
memcpy(mock_nv, &rec, sizeof(rec));
CHECK(boot_state_load(&st) == BOOT_STATE_CORRUPT, "load bad crc");
/* A present but malformed record (short read) is CORRUPT, not
* UNAVAILABLE. */
reset_mock();
mock_read_code = BOOT_STATE_CORRUPT;
CHECK(boot_state_load(&st) == BOOT_STATE_CORRUPT, "load backend corrupt");
/* An unreachable backend is UNAVAILABLE. */
reset_mock();
mock_read_code = BOOT_STATE_UNAVAILABLE;
CHECK(boot_state_load(&st) == BOOT_STATE_UNAVAILABLE,
"load backend unavailable");
/* An unreachable backend writes nothing but is still locked.
* HALT_ON_FAILURE refuses to boot without it. */
reset_mock();
mock_read_code = BOOT_STATE_UNAVAILABLE;
if (setjmp(halt_env) == 0) {
boot_state_on_boot(7);
CHECK(!halt_on_failure, "unavailable did not halt with HALT_ON_FAILURE");
}
else {
CHECK(halt_on_failure, "unavailable halted without HALT_ON_FAILURE");
}
CHECK(mock_write_called == 0, "unavailable does not write");
CHECK(mock_lock_called == (halt_on_failure ? 0 : 1),
"unavailable still locks unless it halted");
/* boot_state_lock (golden slot) locks without reading or writing. */
reset_mock();
boot_state_lock();
CHECK(mock_write_called == 0 && mock_lock_called == 1, "lock only");
/* A failed write warns and still locks, or halts with HALT_ON_FAILURE. */
reset_mock();
mock_read_code = BOOT_STATE_UNPROVISIONED;
mock_write_fail = 1;
if (setjmp(halt_env) == 0) {
boot_state_on_boot(7);
CHECK(!halt_on_failure, "write failure did not halt with HALT_ON_FAILURE");
CHECK(mock_lock_called == 1, "write failure still locks");
}
else {
CHECK(halt_on_failure, "write failure halted without HALT_ON_FAILURE");
CHECK(mock_lock_called == 0, "HALT_ON_FAILURE write failure halts before lock");
}
/* A failed lock warns and boots; HALT_ON_FAILURE halts. */
reset_mock();
make_valid(&rec, 10, 10);
memcpy(mock_nv, &rec, sizeof(rec));
mock_lock_fail = 1;
if (setjmp(halt_env) == 0) {
boot_state_on_boot(10);
CHECK(!halt_on_failure, "lock failure did not halt with HALT_ON_FAILURE");
}
else {
CHECK(halt_on_failure, "lock failure halted without HALT_ON_FAILURE");
}
CHECK(mock_lock_called == 1, "lock attempted");
/* Trust on first use: the first booted image sets anchor and record. */
reset_mock();
mock_read_code = BOOT_STATE_UNPROVISIONED;
boot_state_on_boot(42);
CHECK(mock_write_called == 1, "tofu writes");
memcpy(&st, mock_write_buf, sizeof(st));
CHECK(st.anchor == 42 && st.record == 42, "tofu anchor/record");
CHECK(mock_lock_called == 1, "tofu locks");
/* A higher version advances the anchor. */
reset_mock();
make_valid(&rec, 10, 10);
memcpy(mock_nv, &rec, sizeof(rec));
boot_state_on_boot(20);
CHECK(mock_write_called == 1, "advance writes");
memcpy(&st, mock_write_buf, sizeof(st));
CHECK(st.anchor == 20 && st.record == 20, "advance fields");
/* Re-booting the same version changes nothing and skips the write. */
reset_mock();
make_valid(&rec, 10, 10);
memcpy(mock_nv, &rec, sizeof(rec));
boot_state_on_boot(10);
CHECK(mock_write_called == 0, "same version skips write");
CHECK(mock_lock_called == 1, "same version still locks");
/* A validly signed older image still boots, keeps the anchor, and leaves
* an audit event behind. */
reset_mock();
make_valid(&rec, 10, 10);
memcpy(mock_nv, &rec, sizeof(rec));
boot_state_on_boot(4);
CHECK(mock_write_called == 1, "rollback writes audit");
memcpy(&st, mock_write_buf, sizeof(st));
CHECK(st.anchor == 10, "rollback keeps anchor");
CHECK(st.record == 4, "rollback records booted");
CHECK(st.rollback_count == 1, "rollback counted");
CHECK(mock_lock_called == 1, "rollback locks");
CHECK(st.last_rollback_from == 10 && st.last_rollback_to == 4,
"rollback from/to");
/* A corrupt reference halts the boot and is not overwritten. */
reset_mock();
make_valid(&rec, 9, 9);
rec.crc ^= 1u;
memcpy(mock_nv, &rec, sizeof(rec));
if (setjmp(halt_env) == 0) {
boot_state_on_boot(9);
CHECK(0, "corrupt reference did not halt");
}
else {
CHECK(halted == 1, "corrupt reference halted");
CHECK(mock_write_called == 0, "corrupt reference not overwritten");
}
/* on_failure leaves a corrupt reference untouched and does not halt. */
reset_mock();
make_valid(&rec, 9, 9);
rec.crc ^= 1u;
memcpy(mock_nv, &rec, sizeof(rec));
boot_state_on_failure(BOOT_FAIL_VERIFY_BOTH);
CHECK(mock_write_called == 0, "failure keeps corrupt reference");
CHECK(halted == 0, "failure on corrupt does not halt");
/* on_failure when unprovisioned starts a fresh record with the code. */
reset_mock();
mock_read_code = BOOT_STATE_UNPROVISIONED;
boot_state_on_failure(BOOT_FAIL_NO_IMAGE);
CHECK(mock_write_called == 1, "failure unprovisioned writes");
memcpy(&st, mock_write_buf, sizeof(st));
CHECK(st.boot_fail_count == 1, "failure count set");
CHECK(st.last_fail_code == BOOT_FAIL_NO_IMAGE, "failure code recorded");
/* on_failure on a valid record bumps the counter, keeps anchor/record. */
reset_mock();
make_valid(&rec, 10, 8);
memcpy(mock_nv, &rec, sizeof(rec));
boot_state_on_failure(BOOT_FAIL_VERIFY_BOTH);
CHECK(mock_write_called == 1, "failure valid writes");
memcpy(&st, mock_write_buf, sizeof(st));
CHECK(st.boot_fail_count == 1, "failure valid count");
CHECK(st.last_fail_code == BOOT_FAIL_VERIFY_BOTH, "failure valid code");
CHECK(st.anchor == 10 && st.record == 8, "failure preserves anchor/record");
if (fails == 0) {
printf("PASS\n");
return 0;
}
printf("%d check(s) failed\n", fails);
return 1;
}