mirror of https://github.com/wolfSSL/wolfBoot.git
update_disk: golden slot tried after the A/B attempts, exempt from anti-rollback and never written
parent
aa88c79885
commit
80dbb6da15
|
|
@ -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.
|
||||
|
|
|
|||
14
options.mk
14
options.mk
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue