update_disk: golden slot tried after the A/B attempts, exempt from anti-rollback and never written

pull/922/head
David Garske 2026-10-02 16:31:05 -07:00
parent aa88c79885
commit 80dbb6da15
5 changed files with 499 additions and 10 deletions

View File

@ -413,6 +413,12 @@ The tool needs no configuration to match the loader's layout and no rebuild per
- **Raw partitions only.** A `DISK_FS` slot is a file inside a filesystem and has no partition tail to claim.
- A slot left in `testing` is refused on every path into it, including the failover after another slot fails verification, so it stays out of the boot even in an `ALLOW_DOWNGRADE` build where the version guard is compiled out.
### Golden slot
`DISK_GOLDEN_SLOT=1` adds a third slot that the disk boot path tries once, after the A/B attempts are spent (both slots invalid, unconfirmed, or failing verification). It is read from `BOOT_PART_GOLDEN` (0-based GPT index, default 2), or by `BOOT_LABEL_GOLDEN` / `BOOT_FILE_GOLDEN` like the A/B slots, and it is fully verified like any other image. Two things differ from A and B. The golden slot is exempt from the anti-rollback check, so a deliberately old recovery image still boots when the newer slots are gone; an anti-rollback refusal of the other update slot falls through to the golden slot instead of halting. And it is never written: no boot-confirmation trailer is ever armed on it, so its state cannot drift. Reaching it is announced on the console (`Falling back to the golden image on pN`).
The golden partition must be its own partition: a `BOOT_PART_GOLDEN` equal to `BOOT_PART_A` or `BOOT_PART_B` is rejected at build time, and a label that resolves to an A/B partition is rejected when the fallback is reached, since the same image under a third name recovers nothing (`cm4_emmc_rauc.config` shares one partition between A and B and needs an explicit golden partition). The slot is only prepared when the fallback is taken, so a boot that A or B satisfies never reads it; with `DISK_FS` that is also when it is mounted. When neither A nor B carries a usable version, the loader goes to the golden slot directly instead of trying them.
### Disk boot from a read-only filesystem (FAT32 / ext4)
Targets that boot from a disk (`DISK_SDCARD=1`, `DISK_EMMC=1`, or an x86 FSP/AHCI target) use `src/update_disk.c`, which by default reads the signed image from **raw offset 0 of a partition**: the image has to be written there with `dd`, and the partition cannot hold anything else.

View File

@ -835,6 +835,20 @@ ifeq ($(DISK_BOOT_CONFIRM),1)
CFLAGS+=-D"DISK_BOOT_CONFIRM=1"
endif
# Optional golden slot for disk boot (src/update_disk.c): tried once after the
# A/B attempts are spent, never written, exempt from anti-rollback. The
# partition is BOOT_PART_GOLDEN (default 2), or BOOT_LABEL_GOLDEN / BOOT_FILE_GOLDEN
# like the A/B slots.
DISK_GOLDEN_SLOT ?= 0
ifeq ($(DISK_GOLDEN_SLOT),1)
ifeq ($(WOLFBOOT_TARGET_BUILD),1)
ifeq (,$(findstring WOLFBOOT_UPDATE_DISK,$(CFLAGS)))
$(error DISK_GOLDEN_SLOT requires a disk-boot target (DISK_SDCARD=1, DISK_EMMC=1, or an x86 FSP/AHCI target))
endif
endif
CFLAGS+=-D"DISK_GOLDEN_SLOT=1"
endif
# Optional read-only filesystem support for disk boot (src/update_disk.c),
# so a boot slot can name a file instead of requiring the signed image at
# raw offset 0 of a partition. DISK_FS = fat32 | ext4 | both. Leaving it

View File

@ -123,6 +123,37 @@ static uint8_t disk_encrypt_nonce[ENCRYPT_NONCE_SIZE];
#define MAX_FAILURES 4
#endif
/* Optional last-resort slot, tried once after the A/B attempts are spent.
* It is never written and is exempt from anti-rollback: its job is to bring
* a device back when both update slots are gone, which usually means with an
* older image. */
#ifdef DISK_GOLDEN_SLOT
#ifndef BOOT_PART_GOLDEN
#define BOOT_PART_GOLDEN 2
#endif
#ifndef BOOT_FILE_GOLDEN
#define BOOT_FILE_GOLDEN NULL
#endif
#ifndef BOOT_LABEL_GOLDEN
#define BOOT_LABEL_GOLDEN NULL
#endif
/* A golden slot that is one of the A/B partitions is the same image under a
* third name and recovers nothing; configs that share one partition between
* A and B (cm4_emmc_rauc) must point the golden slot elsewhere. Labels are
* resolved at run time and checked there. */
#if (BOOT_PART_GOLDEN == BOOT_PART_A) || (BOOT_PART_GOLDEN == BOOT_PART_B)
#error "BOOT_PART_GOLDEN must name a partition other than BOOT_PART_A/B"
#endif
#define SLOT_GOLDEN 2
#define DISK_SLOTS 3
#define GOLDEN_ATTEMPTS 1
#define SLOT_IS_GOLDEN(s) ((s) == SLOT_GOLDEN)
#else
#define DISK_SLOTS 2
#define GOLDEN_ATTEMPTS 0
#define SLOT_IS_GOLDEN(s) 0
#endif
#ifndef DISK_BLOCK_SIZE
#define DISK_BLOCK_SIZE 512
#endif
@ -324,7 +355,7 @@ struct boot_slot {
/* File scope, not stack: several disk targets build with
* WOLFBOOT_SMALL_STACK=1. */
static struct boot_slot boot_slots[2];
static struct boot_slot boot_slots[DISK_SLOTS];
#ifdef DISK_BOOT_CONFIRM
/* Boot confirmation for the disk path. include/disk_trailer.h owns the
@ -336,7 +367,7 @@ static struct boot_slot boot_slots[2];
static uint8_t disk_trailer[DISK_TRAILER_SZ];
/* Last known state of each slot, filled in by disk_boot_state_reap(). */
static uint8_t slot_state[2];
static uint8_t slot_state[DISK_SLOTS];
/* Byte offset of the trailer within the slot's partition, or -1 when the slot
* cannot carry one. */
@ -753,13 +784,19 @@ void RAMFUNCTION wolfBoot_start(void)
#endif
if ((pB_ver == 0) && (pA_ver == 0)) {
wolfBoot_printf("No valid OS image found in either partition %d or %d\r\n",
boot_slots[0].part, boot_slots[1].part);
#ifdef DISK_GOLDEN_SLOT
/* Straight to the golden slot. A version-0 header is not a bootable
* A/B image here any more than it was before this option existed. */
failures = MAX_FAILURES;
#else
#ifdef DISK_ENCRYPT
disk_decrypted_header_clear(dec_hdr);
disk_crypto_clear();
#endif
wolfBoot_printf("No valid OS image found in either partition %d or %d\r\n",
boot_slots[0].part, boot_slots[1].part);
wolfBoot_panic();
#endif
}
if (pA_ver != 0U)
@ -776,6 +813,25 @@ void RAMFUNCTION wolfBoot_start(void)
do {
failures++;
#ifdef DISK_GOLDEN_SLOT
if (failures > MAX_FAILURES) {
selected = SLOT_GOLDEN;
/* Prepared only now, so a boot that A or B satisfies never
* touches the recovery partition (with DISK_FS, preparing
* mounts it and opens the image). */
(void)slot_prepare(&boot_slots[SLOT_GOLDEN], BOOT_PART_GOLDEN,
BOOT_LABEL_GOLDEN, BOOT_FILE_GOLDEN, slot_max);
if ((boot_slots[SLOT_GOLDEN].part == boot_slots[0].part) ||
(boot_slots[SLOT_GOLDEN].part == boot_slots[1].part)) {
wolfBoot_printf("Golden slot p%d is an A/B slot; "
"nothing to recover from\r\n",
boot_slots[SLOT_GOLDEN].part);
boot_slots[SLOT_GOLDEN].ready = 0;
}
wolfBoot_printf("Falling back to the golden image on p%d\r\n",
boot_slots[SLOT_GOLDEN].part);
}
#endif
slot = &boot_slots[selected];
cur_part = (uint32_t)slot->part;
#ifdef DISK_BOOT_CONFIRM
@ -785,7 +841,8 @@ void RAMFUNCTION wolfBoot_start(void)
* with ALLOW_DOWNGRADE defined the version guard that would other-
* wise refuse it is compiled out. Refuse it here so the exclusion
* holds on every path into the slot, not just the first choice. */
if (slot_state[selected] == DISK_STATE_TESTING) {
if (!SLOT_IS_GOLDEN(selected) &&
slot_state[selected] == DISK_STATE_TESTING) {
wolfBoot_printf("Slot %c was not confirmed; not booting it\r\n",
'A' + selected);
selected ^= 1;
@ -795,19 +852,26 @@ void RAMFUNCTION wolfBoot_start(void)
#ifndef ALLOW_DOWNGRADE
{
uint32_t cur_ver = selected ? pB_ver_u : pA_ver_u;
if ((max_ver > 0U) && (cur_ver < max_ver)) {
if (!SLOT_IS_GOLDEN(selected) && (max_ver > 0U) &&
(cur_ver < max_ver)) {
wolfBoot_printf("Rollback to lower version not allowed\r\n");
#ifdef DISK_GOLDEN_SLOT
/* Nothing newer can boot: only the golden slot is left. */
failures = MAX_FAILURES;
continue;
#else
#ifdef DISK_ENCRYPT
disk_decrypted_header_clear(dec_hdr);
disk_crypto_clear();
#endif
wolfBoot_panic();
return;
#endif
}
}
#endif
part_name[2] = 'A' + selected;
part_name[2] = SLOT_IS_GOLDEN(selected) ? 'G' : ('A' + selected);
/* Consumed by the boot traces, which compile out without DEBUG_UART */
(void)cur_part;
(void)part_name;
@ -968,7 +1032,7 @@ void RAMFUNCTION wolfBoot_start(void)
failures = 0;
break; /* Skip verification, boot directly */
#endif
} while (failures < MAX_FAILURES);
} while (failures < MAX_FAILURES + GOLDEN_ATTEMPTS);
if (failures) {
#ifdef DISK_ENCRYPT
@ -993,7 +1057,8 @@ void RAMFUNCTION wolfBoot_start(void)
* so enabling this cannot strand a system whose OS does not confirm. A
* slot already SUCCESS, or never written, is left untouched and a
* steady-state boot writes nothing at all. */
if (slot_state[selected] == DISK_STATE_UPDATING) {
if (!SLOT_IS_GOLDEN(selected) &&
slot_state[selected] == DISK_STATE_UPDATING) {
if (slot_state_write(&boot_slots[selected], DISK_STATE_TESTING,
(uint64_t)IMAGE_HEADER_SIZE + (uint64_t)os_image.fw_size)
!= 0) {

View File

@ -68,7 +68,7 @@ TESTS:=unit-parser unit-parser-large-header unit-fdt unit-extflash unit-string \
unit-update-flash-self-update \
unit-nsc-update \
unit-update-flash-enc unit-update-flash-enc-full unit-update-ram unit-update-ram-uboot unit-update-ram-enc unit-update-ram-enc-nopart unit-update-ram-nofixed unit-update-ram-nofixed-noramboot unit-update-ram-noramboot unit-update-ram-custom-trailer unit-custom-trailer-nopart unit-update-flash-hwswap unit-pkcs11_store unit-psa_store unit-wolfhsm_flash_hal unit-disk \
unit-update-disk unit-update-disk-confirm unit-update-disk-fsp unit-update-disk-oob unit-update-disk-fit unit-multiboot unit-boot-x86-fsp unit-loader-tpm-init unit-qspi-flash unit-fwtpm-stub unit-tpm-rsa-exp \
unit-update-disk unit-update-disk-confirm unit-update-disk-golden unit-update-disk-fsp unit-update-disk-oob unit-update-disk-fit unit-multiboot unit-boot-x86-fsp unit-loader-tpm-init unit-qspi-flash unit-fwtpm-stub unit-tpm-rsa-exp \
unit-image-nopart unit-image-sha384 unit-image-sha3-384 unit-image-dts \
unit-image-dts-sha384 unit-image-dts-sha3-384 unit-store-sbrk \
unit-tpm-blob unit-policy-create unit-policy-sign unit-rot-auth unit-sdhci-response-bits \
@ -408,6 +408,8 @@ unit-update-disk:CFLAGS+=-DMOCK_PARTITIONS -DPRINTF_ENABLED -DWOLFBOOT_RAMBOOT_M
-DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE
unit-update-disk-confirm:CFLAGS+=-DMOCK_PARTITIONS -DPRINTF_ENABLED -DWOLFBOOT_RAMBOOT_MAX_SIZE=0x400 \
-DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE
unit-update-disk-golden:CFLAGS+=-DMOCK_PARTITIONS -DPRINTF_ENABLED -DWOLFBOOT_RAMBOOT_MAX_SIZE=0x400 \
-DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE
# Non-FSP disk-boot OOB regression (CRIT-03). WOLFBOOT_RAMBOOT_MAX_SIZE is the
# cap the loader applies to the unauthenticated header fw_size before loading to
# RAM, the same bound update_disk.c and update_ram.c enforce.
@ -1104,6 +1106,9 @@ unit-update-disk: ../../include/target.h unit-update-disk.c
unit-update-disk-confirm: ../../include/target.h unit-update-disk-confirm.c
gcc -o $@ unit-update-disk-confirm.c $(CFLAGS) $(LDFLAGS)
unit-update-disk-golden: ../../include/target.h unit-update-disk-golden.c
gcc -o $@ unit-update-disk-golden.c $(CFLAGS) $(LDFLAGS)
# WOLFBOOT_FSP (x86) boot path of update_disk.c: the low-memory (tolum)
# size check must reject a slot and fall back to the other one, like every
# other per-slot rejection in the retry loop.

View File

@ -0,0 +1,399 @@
/* unit-update-disk-golden.c
*
* Golden slot of the disk boot path (DISK_GOLDEN_SLOT): tried once after the
* A/B attempts are spent, exempt from anti-rollback, never written.
*
* 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_UPDATE_DISK
#define WOLFBOOT_SELF_UPDATE_MONOLITHIC
#define RAM_CODE
#define WOLFBOOT_SELF_HEADER
#define EXT_ENCRYPTED
#define ENCRYPT_WITH_CHACHA
#define HAVE_CHACHA
#define IMAGE_HEADER_SIZE 256
#define BOOT_PART_A 0
#define BOOT_PART_B 1
#define BOOT_PART_GOLDEN 2
#define DISK_GOLDEN_SLOT
#define DISK_BOOT_CONFIRM
#define MOCK_ADDRESS_BOOT 0xCD000000
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <check.h>
#include "hal.h"
#include "target.h"
#include "wolfboot/wolfboot.h"
#include "image.h"
#include "loader.h"
#include <wolfssl/wolfcrypt/chacha.h>
#define TEST_PAYLOAD_SIZE 64
#define TEST_PART_SIZE (IMAGE_HEADER_SIZE + TEST_PAYLOAD_SIZE + 1024)
#define FILL_A 0xA1
#define FILL_B 0xB2
#define FILL_G 0xC3
#define N_PARTS 3
static uint8_t load_buffer[TEST_PART_SIZE];
#define WOLFBOOT_LOAD_ADDRESS ((uintptr_t)load_buffer)
static uint8_t part_image[N_PARTS][TEST_PART_SIZE];
static int mock_reads[N_PARTS];
static int mock_writes[N_PARTS];
static int mock_bad_fill[N_PARTS]; /* payload fills that fail integrity */
static int mock_do_boot_called;
static int mock_booted_fill;
ChaCha chacha;
static void set_u16_le(uint8_t *dst, uint16_t value)
{
dst[0] = (uint8_t)(value & 0xFF);
dst[1] = (uint8_t)(value >> 8);
}
static void set_u32_le(uint8_t *dst, uint32_t value)
{
dst[0] = (uint8_t)(value & 0xFF);
dst[1] = (uint8_t)((value >> 8) & 0xFF);
dst[2] = (uint8_t)((value >> 16) & 0xFF);
dst[3] = (uint8_t)(value >> 24);
}
static void build_image(uint8_t *image, uint32_t version, uint8_t fill)
{
memset(image, 0, TEST_PART_SIZE);
set_u32_le(image, WOLFBOOT_MAGIC);
set_u32_le(image + sizeof(uint32_t), TEST_PAYLOAD_SIZE);
set_u16_le(image + IMAGE_HEADER_OFFSET, HDR_VERSION);
set_u16_le(image + IMAGE_HEADER_OFFSET + sizeof(uint16_t), 4);
set_u32_le(image + IMAGE_HEADER_OFFSET + 2 * sizeof(uint16_t), version);
memset(image + IMAGE_HEADER_SIZE, fill, TEST_PAYLOAD_SIZE);
}
static void reset_mocks(void)
{
memset(load_buffer, 0, sizeof(load_buffer));
build_image(part_image[0], 3, FILL_A);
build_image(part_image[1], 2, FILL_B);
build_image(part_image[2], 1, FILL_G);
memset(mock_reads, 0, sizeof(mock_reads));
memset(mock_writes, 0, sizeof(mock_writes));
memset(mock_bad_fill, 0, sizeof(mock_bad_fill));
mock_do_boot_called = 0;
mock_booted_fill = -1;
wolfBoot_panicked = 0;
}
static void mark_bad(uint8_t fill)
{
int i;
for (i = 0; i < N_PARTS; i++) {
if (mock_bad_fill[i] == 0) {
mock_bad_fill[i] = fill;
return;
}
}
}
int chacha_init(void)
{
return 0;
}
int wc_Chacha_SetIV(ChaCha* ctx, const byte* inIv, word32 counter)
{
(void)ctx; (void)inIv; (void)counter;
return 0;
}
int wc_Chacha_Process(ChaCha* ctx, byte* output, const byte* input, word32 msglen)
{
(void)ctx;
memmove(output, input, msglen);
return 0;
}
void wc_ForceZero(void* mem, size_t len)
{
volatile uint8_t *p = (volatile uint8_t *)mem;
while (len-- > 0)
*p++ = 0;
}
int wolfBoot_initialize_encryption(void)
{
return 0;
}
int wolfBoot_get_encrypt_key(uint8_t *key, uint8_t *nonce)
{
memset(key, 0x5A, ENCRYPT_KEY_SIZE);
memset(nonce, 0xC3, ENCRYPT_NONCE_SIZE);
return 0;
}
int disk_init(int drv)
{
(void)drv;
return 0;
}
int disk_open(int drv)
{
(void)drv;
return 0;
}
void disk_close(int drv)
{
(void)drv;
}
int disk_part_read(int drv, int part, uint64_t off, uint64_t sz, uint8_t *buf)
{
(void)drv;
if (part < 0 || part >= N_PARTS)
return -1;
if ((off > TEST_PART_SIZE) || (sz > (TEST_PART_SIZE - off)))
return -1;
mock_reads[part]++;
memcpy(buf, part_image[part] + off, (size_t)sz);
return (int)sz;
}
int disk_part_write(int drv, int part, uint64_t off, uint64_t sz,
const uint8_t *buf)
{
(void)drv;
if (part < 0 || part >= N_PARTS)
return -1;
if ((off > TEST_PART_SIZE) || (sz > (TEST_PART_SIZE - off)))
return -1;
mock_writes[part]++;
memcpy(part_image[part] + off, buf, (size_t)sz);
return (int)sz;
}
int disk_part_size(int drv, int part, uint64_t *size)
{
(void)drv;
if (part < 0 || part >= N_PARTS)
return -1;
*size = TEST_PART_SIZE;
return 0;
}
int wolfBoot_open_image_address(struct wolfBoot_image* img, uint8_t* image)
{
uint32_t magic;
uint32_t fw_size;
memcpy(&magic, image, sizeof(magic));
if (magic != WOLFBOOT_MAGIC)
return -1;
memset(img, 0, sizeof(*img));
img->hdr = image;
memcpy(&fw_size, image + sizeof(uint32_t), sizeof(fw_size));
img->fw_size = fw_size;
img->fw_base = image + IMAGE_HEADER_SIZE;
img->hdr_ok = 1;
return 0;
}
/* The loader lands the payload itself at WOLFBOOT_LOAD_ADDRESS (the header
* stays in its own buffer), so the fill byte of whatever was loaded is the
* first byte of load_buffer. */
int wolfBoot_verify_integrity(struct wolfBoot_image* img)
{
int i;
uint8_t fill = load_buffer[0];
(void)img;
for (i = 0; i < N_PARTS; i++) {
if (mock_bad_fill[i] != 0 && mock_bad_fill[i] == fill)
return -1;
}
img->sha_ok = 1;
return 0;
}
int wolfBoot_verify_authenticity(struct wolfBoot_image* img)
{
img->signature_ok = 1;
return 0;
}
int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity)
{
(void)capacity; (void)dts_addr;
return -1;
}
void hal_prepare_boot(void)
{
}
void do_boot(const uint32_t *address)
{
mock_do_boot_called++;
mock_booted_fill = ((const uint8_t*)address)[0];
}
int hal_flash_protect(haladdr_t address, int len)
{
(void)address; (void)len;
return 0;
}
#include "update_disk.c"
/* Trailer in the partition tail, as include/disk_trailer.h lays it out. */
static void set_state(int part, uint8_t state)
{
uint64_t off = 0;
ck_assert_int_eq(disk_trailer_offset(TEST_PART_SIZE, &off), 0);
disk_trailer_encode(part_image[part] + off, state);
}
START_TEST(test_golden_untouched_when_a_boots)
{
reset_mocks();
wolfBoot_start();
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(mock_do_boot_called, 1);
ck_assert_int_eq(mock_booted_fill, FILL_A);
ck_assert_int_eq(mock_reads[BOOT_PART_GOLDEN], 0);
ck_assert_int_eq(mock_writes[BOOT_PART_GOLDEN], 0);
}
END_TEST
START_TEST(test_golden_boots_after_a_and_b_fail_despite_lower_version)
{
reset_mocks();
mark_bad(FILL_A);
mark_bad(FILL_B);
wolfBoot_start();
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(mock_do_boot_called, 1);
ck_assert_int_eq(mock_booted_fill, FILL_G);
/* A was tried; B, older than the failed A, is refused by anti-rollback:
* only its header and its state trailer were read, never its payload. */
ck_assert_int_ge(mock_reads[BOOT_PART_A], 3);
ck_assert_int_le(mock_reads[BOOT_PART_B], 2);
}
END_TEST
START_TEST(test_golden_boots_when_both_slots_are_blank)
{
reset_mocks();
memset(part_image[0], 0, TEST_PART_SIZE);
memset(part_image[1], 0, TEST_PART_SIZE);
wolfBoot_start();
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(mock_do_boot_called, 1);
ck_assert_int_eq(mock_booted_fill, FILL_G);
}
END_TEST
START_TEST(test_golden_boots_when_a_is_version_zero)
{
reset_mocks();
/* A valid, signed image whose version parses as 0 next to a blank B is
* "no valid image", not a bootable slot with the version guard relaxed. */
build_image(part_image[0], 0, FILL_A);
memset(part_image[1], 0, TEST_PART_SIZE);
wolfBoot_start();
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(mock_do_boot_called, 1);
ck_assert_int_eq(mock_booted_fill, FILL_G);
ck_assert_int_eq(wolfBoot_disk_boot_part(), BOOT_PART_GOLDEN);
}
END_TEST
START_TEST(test_golden_is_verified_and_a_bad_golden_panics)
{
reset_mocks();
mark_bad(FILL_A);
mark_bad(FILL_B);
mark_bad(FILL_G);
wolfBoot_start();
ck_assert_int_gt(wolfBoot_panicked, 0);
ck_assert_int_eq(mock_do_boot_called, 0);
ck_assert_int_ge(mock_reads[BOOT_PART_GOLDEN], 1);
}
END_TEST
START_TEST(test_golden_is_never_written_and_skips_confirmation)
{
reset_mocks();
set_state(BOOT_PART_A, DISK_STATE_UPDATING);
set_state(BOOT_PART_GOLDEN, DISK_STATE_UPDATING);
mark_bad(FILL_A);
mark_bad(FILL_B);
wolfBoot_start();
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(mock_booted_fill, FILL_G);
ck_assert_int_eq(mock_writes[BOOT_PART_GOLDEN], 0);
}
END_TEST
START_TEST(test_golden_boots_when_a_is_unconfirmed_and_b_bad)
{
reset_mocks();
set_state(BOOT_PART_A, DISK_STATE_TESTING);
mark_bad(FILL_B);
wolfBoot_start();
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(mock_booted_fill, FILL_G);
ck_assert_int_eq(mock_writes[BOOT_PART_A], 0);
}
END_TEST
Suite *wolfboot_suite(void)
{
Suite *s = suite_create("wolfBoot");
TCase *tc = tcase_create("update-disk-golden");
tcase_add_test(tc, test_golden_untouched_when_a_boots);
tcase_add_test(tc, test_golden_boots_after_a_and_b_fail_despite_lower_version);
tcase_add_test(tc, test_golden_boots_when_both_slots_are_blank);
tcase_add_test(tc, test_golden_boots_when_a_is_version_zero);
tcase_add_test(tc, test_golden_is_verified_and_a_bad_golden_panics);
tcase_add_test(tc, test_golden_is_never_written_and_skips_confirmation);
tcase_add_test(tc, test_golden_boots_when_a_is_unconfirmed_and_b_bad);
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;
}