mirror of https://github.com/wolfSSL/wolfBoot.git
1391 lines
48 KiB
C
1391 lines
48 KiB
C
/* update_disk.c
|
|
*
|
|
* Implementation for RAM based updater, for systems that provide
|
|
* drives and partition mapping.
|
|
*
|
|
*
|
|
* 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
|
|
*/
|
|
/**
|
|
* @file update_disk.c
|
|
*
|
|
* @brief Implementation for RAM based updater, for systems that provide
|
|
* drives and partition mapping.
|
|
*
|
|
* This file contains the implementation for the RAM-based updater for systems
|
|
* that offer drives and partition mapping. It includes functions to read
|
|
* and load OS images from disk partitions, validate their integrity and
|
|
* authenticity, and perform the boot process.
|
|
*/
|
|
|
|
#ifdef WOLFBOOT_UPDATE_DISK
|
|
|
|
#include "image.h"
|
|
#include "loader.h"
|
|
#include "hal.h"
|
|
#include "hooks.h"
|
|
#include "spi_flash.h"
|
|
#include "printf.h"
|
|
#include "wolfboot/wolfboot.h"
|
|
#include "tpm.h"
|
|
#if defined(WOLFBOOT_MEASURED_BOOT) && defined(WOLFBOOT_MEASURED_PCR_OS)
|
|
#include <wolfssl/wolfcrypt/sha256.h>
|
|
#endif
|
|
#include "disk.h"
|
|
#ifdef DISK_BOOT_CONFIRM
|
|
#include "disk_trailer.h"
|
|
#endif
|
|
#ifdef WOLFBOOT_DISK_FS
|
|
#include "disk_fs.h"
|
|
#endif
|
|
#ifdef WOLFBOOT_ELF
|
|
#include "elf.h"
|
|
#endif
|
|
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
|
|
#include "../hal/zynqmp_atf.h"
|
|
#endif
|
|
|
|
/* Disk encryption support for AES-256, AES-128, or ChaCha20 */
|
|
#if defined(ENCRYPT_WITH_AES256) || defined(ENCRYPT_WITH_AES128) || \
|
|
defined(ENCRYPT_WITH_CHACHA)
|
|
#define DISK_ENCRYPT
|
|
#include "encrypt.h"
|
|
#include <wolfssl/wolfcrypt/memory.h> /* wc_ForceZero */
|
|
|
|
/* Module-level storage for encryption nonce */
|
|
static uint8_t disk_encrypt_nonce[ENCRYPT_NONCE_SIZE];
|
|
#endif
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#ifdef WOLFBOOT_FSP
|
|
#include "stage2_params.h"
|
|
#include "x86/common.h"
|
|
#include "x86/ahci.h"
|
|
#include "x86/ata.h"
|
|
#include "pci.h"
|
|
#include "x86/tgl_fsp.h"
|
|
|
|
#ifdef TARGET_kontron_vx3060_s2
|
|
#define BOOT_PART_A 5
|
|
#define BOOT_PART_B 6
|
|
#endif
|
|
#endif /* WOLFBOOT_FSP */
|
|
|
|
/* Default values for BOOT_DISK, BOOT_PART_A and BOOT_PART_B */
|
|
#ifndef BOOT_DISK
|
|
#define BOOT_DISK 0
|
|
#endif
|
|
#ifndef BOOT_PART_A
|
|
#define BOOT_PART_A 0
|
|
#endif
|
|
#ifndef BOOT_PART_B
|
|
#define BOOT_PART_B 1
|
|
#endif
|
|
|
|
/* Optional read-only filesystem support. When a slot's partition holds a
|
|
* supported filesystem, the signed image is read from BOOT_FILE_x instead
|
|
* of from the start of the partition. A partition that holds no
|
|
* filesystem is read exactly as before. */
|
|
#ifndef BOOT_FILE_A
|
|
#define BOOT_FILE_A NULL
|
|
#endif
|
|
#ifndef BOOT_FILE_B
|
|
#define BOOT_FILE_B NULL
|
|
#endif
|
|
/* Optional partition selection by name, overriding BOOT_PART_x. Matches
|
|
* the GPT partition label first, then the filesystem volume label. */
|
|
#ifndef BOOT_LABEL_A
|
|
#define BOOT_LABEL_A NULL
|
|
#endif
|
|
#ifndef BOOT_LABEL_B
|
|
#define BOOT_LABEL_B NULL
|
|
#endif
|
|
|
|
#ifndef MAX_FAILURES
|
|
#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
|
|
|
|
#if !defined(WOLFBOOT_FSP) && !defined(WOLFBOOT_RAMBOOT_MAX_SIZE)
|
|
# error "WOLFBOOT_RAMBOOT_MAX_SIZE required to bound the disk image RAM load"
|
|
#endif
|
|
|
|
#ifdef DISK_ENCRYPT
|
|
|
|
/* Module-level storage for encryption key */
|
|
static uint8_t disk_encrypt_key[ENCRYPT_KEY_SIZE];
|
|
|
|
static uint16_t get_hdr_u16(const uint8_t *p)
|
|
{
|
|
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
|
|
}
|
|
|
|
static uint32_t get_hdr_u32(const uint8_t *p)
|
|
{
|
|
return ((uint32_t)p[0]) |
|
|
((uint32_t)p[1] << 8) |
|
|
((uint32_t)p[2] << 16) |
|
|
((uint32_t)p[3] << 24);
|
|
}
|
|
|
|
/**
|
|
* @brief Get the version from an already-decrypted header.
|
|
*
|
|
* This function extracts the version from a decrypted header blob
|
|
* without calling wolfBoot_get_blob_version, which might try to
|
|
* decrypt again if EXT_ENCRYPTED && MMU is defined.
|
|
*
|
|
* @param hdr Pointer to the decrypted header.
|
|
*
|
|
* @return The version number, or 0 if not found.
|
|
*/
|
|
static uint32_t get_decrypted_blob_version(uint8_t *hdr)
|
|
{
|
|
uint32_t *magic = (uint32_t *)hdr;
|
|
uint8_t *p = hdr + IMAGE_HEADER_OFFSET;
|
|
uint8_t *max_p = hdr + IMAGE_HEADER_SIZE;
|
|
|
|
if (*magic != WOLFBOOT_MAGIC)
|
|
return 0;
|
|
|
|
/* Search for version TLV */
|
|
while ((size_t)(max_p - p) >= 4U) {
|
|
size_t remaining = (size_t)(max_p - p);
|
|
size_t tlv_total;
|
|
uint16_t tlv_type, tlv_len;
|
|
|
|
/* Skip padding bytes and unaligned half-words before reading TLVs. */
|
|
if ((p[0] == HDR_PADDING) || ((((uintptr_t)p) & 0x01U) != 0U)) {
|
|
p++;
|
|
continue;
|
|
}
|
|
|
|
tlv_type = get_hdr_u16(p);
|
|
if (tlv_type == 0 || tlv_type == 0xFFFF)
|
|
break;
|
|
|
|
tlv_len = get_hdr_u16(p + 2);
|
|
tlv_total = 4U + (size_t)tlv_len;
|
|
if (remaining < tlv_total)
|
|
break;
|
|
|
|
if (tlv_type == HDR_VERSION && tlv_len == 4) {
|
|
return get_hdr_u32(p + 4);
|
|
}
|
|
|
|
p += tlv_total;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Set up decryption context with IV at specified block offset.
|
|
*
|
|
* This function sets up the AES/ChaCha context with the IV positioned
|
|
* at the specified block offset. It matches how sign.c sets up encryption.
|
|
*
|
|
* @param block_offset Block offset for IV counter (0 = start of image).
|
|
*/
|
|
static void disk_crypto_set_iv(uint32_t block_offset)
|
|
{
|
|
#if defined(ENCRYPT_WITH_CHACHA)
|
|
wc_Chacha_SetIV(&chacha, disk_encrypt_nonce, block_offset);
|
|
#elif defined(ENCRYPT_WITH_AES128) || defined(ENCRYPT_WITH_AES256)
|
|
/* For AES CTR, we need to construct the IV with the counter.
|
|
* The sign tool uses the IV directly without byte-reversal,
|
|
* so we must match that behavior here. */
|
|
uint8_t iv[ENCRYPT_BLOCK_SIZE];
|
|
uint32_t ctr;
|
|
|
|
/* Copy nonce/IV (first 12 bytes for CTR nonce, last 4 for counter) */
|
|
memcpy(iv, disk_encrypt_nonce, ENCRYPT_NONCE_SIZE);
|
|
|
|
/* Add block offset to the counter portion (last 4 bytes, big-endian) */
|
|
/* The IV from sign.c is already in the correct format, we just need
|
|
* to add the block offset to the counter portion */
|
|
ctr = ((uint32_t)iv[12] << 24) | ((uint32_t)iv[13] << 16) |
|
|
((uint32_t)iv[14] << 8) | (uint32_t)iv[15];
|
|
ctr += block_offset;
|
|
iv[12] = (uint8_t)(ctr >> 24);
|
|
iv[13] = (uint8_t)(ctr >> 16);
|
|
iv[14] = (uint8_t)(ctr >> 8);
|
|
iv[15] = (uint8_t)(ctr);
|
|
|
|
wc_AesSetIV(&aes_dec, iv);
|
|
/* Scrub the stack copy: the counter bytes are derived from the
|
|
* secret disk-encryption nonce (matches aes_set_iv in libwolfboot.c). */
|
|
wc_ForceZero(iv, sizeof(iv));
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* @brief Decrypt an image header in RAM.
|
|
*
|
|
* This function decrypts the image header using the configured encryption
|
|
* algorithm (AES-256/AES-128 CTR mode or ChaCha20).
|
|
*
|
|
* @param src Pointer to the encrypted header.
|
|
* @param dst Pointer to the destination buffer for decrypted header.
|
|
*
|
|
* @return 0 if successful, -1 on failure.
|
|
*/
|
|
static int decrypt_header(const uint8_t *src, uint8_t *dst)
|
|
{
|
|
uint32_t magic;
|
|
|
|
/* Reset IV to start of image (block 0) */
|
|
disk_crypto_set_iv(0);
|
|
|
|
/* Decrypt header - CTR mode handles counter increment internally */
|
|
crypto_decrypt(dst, src, IMAGE_HEADER_SIZE);
|
|
|
|
magic = *((uint32_t*)dst);
|
|
if (magic != WOLFBOOT_MAGIC)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static void disk_crypto_clear(void)
|
|
{
|
|
wc_ForceZero(disk_encrypt_key, sizeof(disk_encrypt_key));
|
|
wc_ForceZero(disk_encrypt_nonce, sizeof(disk_encrypt_nonce));
|
|
}
|
|
|
|
static void disk_decrypted_header_clear(uint8_t *hdr)
|
|
{
|
|
wc_ForceZero(hdr, IMAGE_HEADER_SIZE);
|
|
}
|
|
|
|
#endif /* DISK_ENCRYPT */
|
|
|
|
extern int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity);
|
|
#ifdef MMU
|
|
/* Platform hook: return a DTB the boot firmware handed us (e.g. the RPi
|
|
* firmware's fully-patched dtb), used below when the loaded image carries no
|
|
* FDT of its own. Weak default returns none; platform HALs (e.g. hal/cm4.c)
|
|
* override it. Defined here (not in an arch boot_*.c) so every MMU disk target -
|
|
* AArch64, ARM32, RISC-V - resolves the symbol. */
|
|
void* WEAKFUNCTION hal_get_boot_dts(void)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Weak default: no A/B boot-slot bookkeeping. Platform HALs (e.g. hal/cm4.c's
|
|
* RAUC path) override it. Defined here so every MMU disk target resolves the
|
|
* symbol. */
|
|
int WEAKFUNCTION hal_boot_slot_select(void)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if defined(WOLFBOOT_NO_LOAD_ADDRESS) || !defined(WOLFBOOT_LOAD_ADDRESS)
|
|
/* from the linker, where wolfBoot ends */
|
|
extern uint8_t _end_wb[];
|
|
#endif
|
|
|
|
/**
|
|
* @brief One A/B boot slot: a partition, and optionally a file on it.
|
|
*
|
|
* The whole point of this indirection is that the retry loop below reads
|
|
* through slot_read() and so is written once, whether the image lives at
|
|
* the start of a partition or in a file on a filesystem.
|
|
*/
|
|
struct boot_slot {
|
|
int part;
|
|
int ready;
|
|
#ifdef WOLFBOOT_DISK_FS
|
|
const char *file;
|
|
struct fs_volume vol;
|
|
struct fs_file f;
|
|
#endif
|
|
};
|
|
|
|
/* File scope, not stack: several disk targets build with
|
|
* WOLFBOOT_SMALL_STACK=1. */
|
|
static struct boot_slot boot_slots[DISK_SLOTS];
|
|
|
|
#ifdef DISK_BOOT_CONFIRM
|
|
/* Boot confirmation for the disk path. include/disk_trailer.h owns the
|
|
* format: the offset, the magic and the four state values are defined there
|
|
* once and shared with the userspace tool that stages and confirms. */
|
|
|
|
/* Not on the stack: the disk targets that build WOLFBOOT_SMALL_STACK=1 keep
|
|
* boot_slots and the filesystem cache off it for the same reason. */
|
|
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[DISK_SLOTS];
|
|
|
|
/* Byte offset of the trailer within the slot's partition, or -1 when the slot
|
|
* cannot carry one. */
|
|
static int slot_trailer_off(struct boot_slot *s, uint64_t *off)
|
|
{
|
|
uint64_t sz = 0;
|
|
|
|
if ((s == NULL) || (off == NULL) || (s->ready == 0)) {
|
|
return -1;
|
|
}
|
|
#ifdef WOLFBOOT_DISK_FS
|
|
/* A file-backed slot has no partition tail to claim: the filesystem owns
|
|
* it. Boot confirmation is raw-partition only. */
|
|
if (s->vol.type != FS_TYPE_RAW) {
|
|
return -1;
|
|
}
|
|
#endif
|
|
if (disk_part_size(BOOT_DISK, s->part, &sz) != 0) {
|
|
return -1;
|
|
}
|
|
return disk_trailer_offset(sz, off);
|
|
}
|
|
|
|
/* Current state of a slot. */
|
|
static int slot_state_read(struct boot_slot *s, uint8_t *state)
|
|
{
|
|
uint64_t off = 0;
|
|
|
|
if (state == NULL) {
|
|
return -1;
|
|
}
|
|
*state = DISK_STATE_NEW;
|
|
if (slot_trailer_off(s, &off) != 0) {
|
|
return -1;
|
|
}
|
|
if (disk_part_read(BOOT_DISK, s->part, off, DISK_TRAILER_SZ,
|
|
disk_trailer) != (int)DISK_TRAILER_SZ) {
|
|
return -1;
|
|
}
|
|
*state = disk_trailer_decode(disk_trailer);
|
|
return 0;
|
|
}
|
|
|
|
/* Record a slot's state. img_end is the first byte past the image, so a
|
|
* trailer that would land inside it is refused rather than corrupting it. */
|
|
static int slot_state_write(struct boot_slot *s, uint8_t state,
|
|
uint64_t img_end)
|
|
{
|
|
uint64_t off = 0;
|
|
|
|
if (slot_trailer_off(s, &off) != 0) {
|
|
return -1;
|
|
}
|
|
if (off < img_end) {
|
|
wolfBoot_printf("Boot state would overlap the image on p%d\r\n",
|
|
s->part);
|
|
return -1;
|
|
}
|
|
disk_trailer_encode(disk_trailer, state);
|
|
if (disk_part_write(BOOT_DISK, s->part, off, DISK_TRAILER_SZ,
|
|
disk_trailer) != (int)DISK_TRAILER_SZ) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Read both slots' states, and drop any slot still marked TESTING out of the
|
|
* election. Such a slot was armed before the previous boot and nothing
|
|
* confirmed it, so whatever it started did not come up.
|
|
*
|
|
* Nothing is written here. Zeroing the version in memory removes the slot
|
|
* from the selection AND from max_ver, which is what lets an older surviving
|
|
* slot boot without relaxing the anti-rollback guard for anything still live.
|
|
* A slot that merely fails verification keeps its version and still blocks an
|
|
* older one, exactly as before. */
|
|
static void disk_boot_state_reap(uint32_t *pA_ver, uint32_t *pB_ver)
|
|
{
|
|
uint8_t st;
|
|
int i;
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
st = DISK_STATE_NEW;
|
|
slot_state[i] = DISK_STATE_NEW;
|
|
if (slot_state_read(&boot_slots[i], &st) != 0) {
|
|
continue;
|
|
}
|
|
slot_state[i] = st;
|
|
if (st != DISK_STATE_TESTING) {
|
|
continue;
|
|
}
|
|
wolfBoot_printf("Slot %c was not confirmed; skipping it\r\n",
|
|
'A' + i);
|
|
if (i == 0) {
|
|
*pA_ver = 0;
|
|
}
|
|
else {
|
|
*pB_ver = 0;
|
|
}
|
|
}
|
|
}
|
|
#endif /* DISK_BOOT_CONFIRM */
|
|
|
|
/**
|
|
* @brief Read from a boot slot.
|
|
*
|
|
* With WOLFBOOT_DISK_FS undefined, or on a partition that holds no
|
|
* filesystem, this is exactly the disk_part_read() call the loader has
|
|
* always made.
|
|
*/
|
|
static int slot_read(struct boot_slot *s, uint64_t off, uint64_t sz,
|
|
uint8_t *buf)
|
|
{
|
|
if (s->ready == 0) {
|
|
return -1;
|
|
}
|
|
#ifdef WOLFBOOT_DISK_FS
|
|
if (s->vol.type != FS_TYPE_RAW) {
|
|
return fs_read(&s->f, off, sz, buf);
|
|
}
|
|
#endif
|
|
return disk_part_read(BOOT_DISK, s->part, off, sz, buf);
|
|
}
|
|
|
|
#ifdef WOLFBOOT_DISK_FS
|
|
/**
|
|
* @brief Find a partition by name.
|
|
*
|
|
* The GPT partition label is tried first. MBR disks have no partition
|
|
* names at all -- src/disk.c zeroes that field on the MBR path -- so the
|
|
* filesystem's own volume label is tried next, which is what makes name
|
|
* based selection usable on the MBR layouts the SD card targets use.
|
|
*/
|
|
/* Not on the stack: this runs on the same WOLFBOOT_SMALL_STACK disk
|
|
* targets that boot_slots and the filesystem metadata cache are kept off
|
|
* the stack for. */
|
|
static struct fs_volume slot_label_probe;
|
|
|
|
static int slot_find_by_label(const char *label)
|
|
{
|
|
int n;
|
|
int i;
|
|
|
|
i = disk_find_partition_by_label(BOOT_DISK, label);
|
|
if (i >= 0) {
|
|
return i;
|
|
}
|
|
n = disk_part_count(BOOT_DISK);
|
|
for (i = 0; i < n; i++) {
|
|
if (fs_mount(&slot_label_probe, BOOT_DISK, i) != WOLFBOOT_FS_OK) {
|
|
continue;
|
|
}
|
|
if (fs_label_eq(&slot_label_probe, label) == 1) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
#endif /* WOLFBOOT_DISK_FS */
|
|
|
|
/**
|
|
* @brief Resolve, mount and open one boot slot.
|
|
*
|
|
* @param max_size Upper bound on the image size, applied to the size the
|
|
* filesystem reports before it can drive any I/O. The
|
|
* payload lands in RAM before its signature is checked,
|
|
* so this bound has to come from the build, not the media.
|
|
*
|
|
* @return 0 when the slot can be read from, -1 otherwise.
|
|
*/
|
|
static int slot_prepare(struct boot_slot *s, int part, const char *label,
|
|
const char *file, uint64_t max_size)
|
|
{
|
|
#ifdef WOLFBOOT_DISK_FS
|
|
int ret;
|
|
#endif
|
|
|
|
memset(s, 0, sizeof(*s));
|
|
s->part = part;
|
|
|
|
#ifdef WOLFBOOT_DISK_FS
|
|
s->file = file;
|
|
if (label != NULL) {
|
|
s->part = slot_find_by_label(label);
|
|
if (s->part < 0) {
|
|
wolfBoot_printf("No partition named %s\r\n", label);
|
|
return -1;
|
|
}
|
|
}
|
|
ret = fs_mount(&s->vol, BOOT_DISK, s->part);
|
|
if (ret != WOLFBOOT_FS_OK) {
|
|
/* Neutral wording: fs_mount() also reports E_IO for a partition that
|
|
* does not exist or cannot be read, which a mis-set BOOT_PART_x or
|
|
* BOOT_LABEL_x produces. The code distinguishes the cases. */
|
|
wolfBoot_printf("p%d: cannot mount (%d)\r\n", s->part, ret);
|
|
return -1;
|
|
}
|
|
if (s->vol.type == FS_TYPE_RAW) {
|
|
if (file != NULL) {
|
|
wolfBoot_printf("p%d: no filesystem, reading raw\r\n", s->part);
|
|
}
|
|
}
|
|
else if (file == NULL) {
|
|
/* Falling back to a raw read here would parse the filesystem's own
|
|
* boot sector as an image header. Say why instead. */
|
|
wolfBoot_printf("p%d: %s filesystem but no boot file set\r\n",
|
|
s->part, fs_type_name(&s->vol));
|
|
return -1;
|
|
}
|
|
ret = fs_open(&s->vol, &s->f, file, max_size);
|
|
if (ret != WOLFBOOT_FS_OK) {
|
|
wolfBoot_printf("p%d: cannot open %s (%d)\r\n", s->part,
|
|
(file != NULL) ? file : "image", ret);
|
|
return -1;
|
|
}
|
|
if (s->vol.type != FS_TYPE_RAW) {
|
|
wolfBoot_printf("p%d: %s, %s\r\n", s->part, fs_type_name(&s->vol),
|
|
file);
|
|
}
|
|
#else
|
|
(void)label;
|
|
(void)file;
|
|
(void)max_size;
|
|
#endif
|
|
|
|
s->ready = 1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief function for starting the boot process.
|
|
*
|
|
* This function starts the boot process by attempting to read and load
|
|
* the OS image from disk partitions. It then verifies the integrity and
|
|
* authenticity of the loaded image before initiating the boot.
|
|
*/
|
|
#if defined(MMU) || defined(WOLFBOOT_FDT)
|
|
/* File scope so wolfBoot_get_dts_address() can hand it to a hook; exactly
|
|
* one update strategy object is linked per build. */
|
|
static void* wolfboot_dts_addr = NULL;
|
|
|
|
void* wolfBoot_get_dts_address(void)
|
|
{
|
|
return wolfboot_dts_addr;
|
|
}
|
|
#endif
|
|
|
|
/* GPT index of the slot that passed verification and is booting, or -1. */
|
|
static int disk_boot_part = -1;
|
|
|
|
int wolfBoot_disk_boot_part(void)
|
|
{
|
|
return disk_boot_part;
|
|
}
|
|
|
|
#if defined(MMU) || defined(WOLFBOOT_FDT)
|
|
/* Tell the OS which slot it is booting from, as /chosen/wolfboot,boot-part,
|
|
* so whatever confirms the boot (lib-fs success) can address that slot and
|
|
* no other. Called from the DTB fixup pass (hal_dts_fixup) rather than
|
|
* writing the staged DTB here: a HAL may edit a copy of it, and may replace
|
|
* bootargs wholesale, so a /chosen property set in that pass is what
|
|
* survives. Nothing is written before a slot has been verified. */
|
|
int wolfBoot_disk_dts_fixup(fdt_ctx* ctx)
|
|
{
|
|
int ret;
|
|
|
|
if (disk_boot_part < 0) {
|
|
return 0;
|
|
}
|
|
ret = fdt_fixup_chosen_val(ctx, "wolfboot,boot-part",
|
|
(uint32_t)disk_boot_part);
|
|
if (ret != 0) {
|
|
/* Booting on would hand the OS either nothing to confirm, leaving the
|
|
* slot in TESTING until the next boot rejects it, or a value the DTB
|
|
* already carried, aiming the confirmation at the wrong slot. */
|
|
wolfBoot_printf("FDT: Failed to set wolfboot,boot-part (%d)\r\n", ret);
|
|
wolfBoot_panic();
|
|
}
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
void RAMFUNCTION wolfBoot_start(void)
|
|
{
|
|
uint8_t p_hdr[IMAGE_HEADER_SIZE] XALIGNED_STACK(16);
|
|
#ifdef DISK_ENCRYPT
|
|
uint8_t dec_hdr[IMAGE_HEADER_SIZE] XALIGNED_STACK(16);
|
|
#endif
|
|
#ifdef WOLFBOOT_FSP
|
|
struct stage2_parameter *stage2_params;
|
|
#endif
|
|
struct wolfBoot_image os_image;
|
|
struct boot_slot *slot;
|
|
uint64_t slot_max;
|
|
uint32_t pA_ver = 0U, pB_ver = 0U;
|
|
uint32_t pA_ver_u = 0U, pB_ver_u = 0U;
|
|
uint32_t cur_part = 0;
|
|
int ret = -1;
|
|
int selected;
|
|
uint32_t *load_address;
|
|
int failures = 0;
|
|
uint32_t load_off;
|
|
uint32_t max_ver;
|
|
const uint8_t *hdr_ptr = NULL;
|
|
#if defined(MMU) || defined(WOLFBOOT_FDT)
|
|
uint8_t *dts_addr = NULL;
|
|
#ifdef WOLFBOOT_FDT
|
|
uint32_t dts_size = 0;
|
|
/* Validated view of the FIT staged at load_address. */
|
|
fdt_ctx fit_ctx;
|
|
#ifdef WOLFBOOT_FIT_RAMDISK
|
|
/* FIT ramdisk node, set once the FIT parses. The initrd fixup is
|
|
* deferred until dts_addr is final. */
|
|
const char *fit_ramdisk = NULL;
|
|
#endif
|
|
#endif
|
|
#endif
|
|
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
|
|
/* BL31 (ARM-TF) entry, set when the boot FIT carries an "atf" sub-image. */
|
|
uintptr_t bl31_entry = 0;
|
|
#endif
|
|
char part_name[4] = {'P', ':', 'X', '\0'};
|
|
#if defined(WOLFBOOT_MEASURED_BOOT) && defined(WOLFBOOT_MEASURED_PCR_OS)
|
|
int measure_ret;
|
|
#if defined(WOLFBOOT_SKIP_BOOT_VERIFY)
|
|
#error "measured boot: WOLFBOOT_MEASURED_PCR_OS needs the OS digest, which WOLFBOOT_SKIP_BOOT_VERIFY does not produce"
|
|
#endif
|
|
#if WOLFBOOT_SHA_DIGEST_SIZE < WOLFBOOT_TPM_PCR_DIG_SZ
|
|
#error "measured boot: image digest narrower than the PCR bank is not supported"
|
|
#endif
|
|
#if WOLFBOOT_TPM_PCR_DIG_SZ != 32
|
|
#error "measured boot: the OS re-hash path only implements a SHA-256 PCR bank"
|
|
#endif
|
|
uint8_t os_pcr[WOLFBOOT_TPM_PCR_DIG_SZ];
|
|
wc_Sha256 os_sha;
|
|
#endif
|
|
BENCHMARK_DECLARE();
|
|
|
|
#ifdef DISK_ENCRYPT
|
|
/* Initialize encryption - this sets up the cipher with key from storage */
|
|
if (wolfBoot_initialize_encryption() != 0) {
|
|
disk_decrypted_header_clear(dec_hdr);
|
|
disk_crypto_clear();
|
|
wolfBoot_printf("Error initializing encryption\r\n");
|
|
wolfBoot_panic();
|
|
}
|
|
/* Retrieve encryption key and nonce for disk decryption */
|
|
if (wolfBoot_get_encrypt_key(disk_encrypt_key, disk_encrypt_nonce) != 0) {
|
|
disk_decrypted_header_clear(dec_hdr);
|
|
disk_crypto_clear();
|
|
wolfBoot_printf("Error getting encryption key\r\n");
|
|
wolfBoot_panic();
|
|
}
|
|
wolfBoot_printf("Disk encryption enabled\r\n");
|
|
#endif
|
|
|
|
ret = disk_init(BOOT_DISK);
|
|
if (ret != 0) {
|
|
#ifdef DISK_ENCRYPT
|
|
disk_decrypted_header_clear(dec_hdr);
|
|
disk_crypto_clear();
|
|
#endif
|
|
wolfBoot_panic();
|
|
}
|
|
|
|
/* (Removed) DDR self-test that PDMA/CPU-wrote 0xA5A5/0x5A5A patterns
|
|
* into the image load region (0x82000000 / 0xC2000000). It was a
|
|
* debug aid for the now-fixed DDR write scramble (auto-init reorder
|
|
* in run_training), and it pre-clobbered the first 64 words of the
|
|
* load region, corrupting the image the integrity check then read. */
|
|
|
|
if (disk_open(BOOT_DISK) < 0) {
|
|
#ifdef DISK_ENCRYPT
|
|
disk_decrypted_header_clear(dec_hdr);
|
|
disk_crypto_clear();
|
|
#endif
|
|
wolfBoot_printf("Error opening disk %d\r\n", BOOT_DISK);
|
|
wolfBoot_panic();
|
|
}
|
|
|
|
#ifdef WOLFBOOT_FSP
|
|
stage2_params = stage2_get_parameters();
|
|
#endif
|
|
|
|
#if !defined(WOLFBOOT_NO_LOAD_ADDRESS) && defined(WOLFBOOT_LOAD_ADDRESS)
|
|
load_address = (uint32_t*)WOLFBOOT_LOAD_ADDRESS;
|
|
#else
|
|
/* load the image just after wolfboot, 16 bytes aligned */
|
|
load_address = (uint32_t *)((((uintptr_t)_end_wb) + 0xf) & ~0xf);
|
|
#endif
|
|
|
|
wolfBoot_printf("Load address %p\r\n", (void*)load_address);
|
|
|
|
/* Upper bound on anything the media may claim about the image size.
|
|
* The payload is copied into the load region before its signature is
|
|
* checked, so this has to come from the build rather than the media.
|
|
* The header sits ahead of the payload in the same file, hence the
|
|
* IMAGE_HEADER_SIZE. */
|
|
#if defined(WOLFBOOT_FSP)
|
|
/* Fail closed on an inverted tolum: with tolum at or below the load
|
|
* address there is no low-memory window, so the cap is zero. Both
|
|
* are low-memory addresses, so compare them in their 32-bit form.
|
|
* The subtraction would otherwise wrap into a near-2^32 bound, which
|
|
* is the opposite of a cap. */
|
|
if ((uint32_t)(uintptr_t)(stage2_params->tolum) >
|
|
(uint32_t)(uintptr_t)load_address) {
|
|
slot_max = (uint64_t)(uint32_t)(uintptr_t)(stage2_params->tolum) -
|
|
(uint64_t)(uint32_t)(uintptr_t)load_address;
|
|
}
|
|
else {
|
|
slot_max = 0;
|
|
}
|
|
#else
|
|
slot_max = (uint64_t)WOLFBOOT_RAMBOOT_MAX_SIZE +
|
|
(uint64_t)IMAGE_HEADER_SIZE;
|
|
#endif
|
|
|
|
(void)slot_prepare(&boot_slots[0], BOOT_PART_A, BOOT_LABEL_A,
|
|
BOOT_FILE_A, slot_max);
|
|
(void)slot_prepare(&boot_slots[1], BOOT_PART_B, BOOT_LABEL_B,
|
|
BOOT_FILE_B, slot_max);
|
|
|
|
wolfBoot_printf("Checking primary OS image in %d,%d...\r\n", BOOT_DISK,
|
|
boot_slots[0].part);
|
|
if (slot_read(&boot_slots[0], 0, IMAGE_HEADER_SIZE, p_hdr)
|
|
== IMAGE_HEADER_SIZE) {
|
|
#ifdef DISK_ENCRYPT
|
|
if (decrypt_header(p_hdr, dec_hdr) == 0) {
|
|
/* Use local version parser to avoid double-decryption issue
|
|
* when EXT_ENCRYPTED && MMU is also defined */
|
|
pA_ver = get_decrypted_blob_version(dec_hdr);
|
|
}
|
|
#else
|
|
pA_ver = wolfBoot_get_blob_version((uint8_t*)p_hdr);
|
|
#endif
|
|
}
|
|
|
|
wolfBoot_printf("Checking secondary OS image in %d,%d...\r\n", BOOT_DISK,
|
|
boot_slots[1].part);
|
|
if (slot_read(&boot_slots[1], 0, IMAGE_HEADER_SIZE, p_hdr)
|
|
== IMAGE_HEADER_SIZE) {
|
|
#ifdef DISK_ENCRYPT
|
|
if (decrypt_header(p_hdr, dec_hdr) == 0) {
|
|
/* Use local version parser to avoid double-decryption issue
|
|
* when EXT_ENCRYPTED && MMU is also defined */
|
|
pB_ver = get_decrypted_blob_version(dec_hdr);
|
|
}
|
|
#else
|
|
pB_ver = wolfBoot_get_blob_version((uint8_t*)p_hdr);
|
|
#endif
|
|
}
|
|
|
|
#ifdef DISK_BOOT_CONFIRM
|
|
disk_boot_state_reap(&pA_ver, &pB_ver);
|
|
#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_panic();
|
|
#endif
|
|
}
|
|
|
|
if (pA_ver != 0U)
|
|
pA_ver_u = pA_ver;
|
|
if (pB_ver != 0U)
|
|
pB_ver_u = pB_ver;
|
|
|
|
wolfBoot_printf("Versions, A:%u B:%u\r\n", pA_ver_u, pB_ver_u);
|
|
#if DISK_BLOCK_SIZE < 1024
|
|
wolfBoot_printf("Load block size: %u bytes\r\n",
|
|
(unsigned int)DISK_BLOCK_SIZE);
|
|
#else
|
|
wolfBoot_printf("Load block size: %uKB\r\n",
|
|
(unsigned int)(DISK_BLOCK_SIZE / 1024U));
|
|
#endif
|
|
max_ver = (pB_ver_u > pA_ver_u) ? pB_ver_u : pA_ver_u;
|
|
|
|
/* Choose partition with higher version */
|
|
selected = (pB_ver_u > pA_ver_u) ? 1 : 0;
|
|
|
|
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
|
|
/* A slot still in TESTING did not confirm last boot. Zeroing its
|
|
* version in disk_boot_state_reap() takes it out of the election,
|
|
* but a failover below (selected ^= 1) can still land on it, and
|
|
* 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_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;
|
|
continue;
|
|
}
|
|
#endif
|
|
#ifndef ALLOW_DOWNGRADE
|
|
{
|
|
uint32_t cur_ver = selected ? pB_ver_u : pA_ver_u;
|
|
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] = SLOT_IS_GOLDEN(selected) ? 'G' : ('A' + selected);
|
|
/* Consumed by the boot traces, which compile out without DEBUG_UART */
|
|
(void)cur_part;
|
|
(void)part_name;
|
|
|
|
wolfBoot_printf("Attempting boot from %s\r\n", part_name);
|
|
|
|
/* Fetch header only */
|
|
if (slot_read(slot, 0, IMAGE_HEADER_SIZE, p_hdr)
|
|
!= IMAGE_HEADER_SIZE) {
|
|
wolfBoot_printf("Error reading image header from disk: p%d\r\n",
|
|
cur_part);
|
|
selected ^= 1;
|
|
continue;
|
|
}
|
|
|
|
hdr_ptr = p_hdr;
|
|
#ifdef DISK_ENCRYPT
|
|
/* Decrypt header to parse image size */
|
|
if (decrypt_header(p_hdr, dec_hdr) != 0) {
|
|
wolfBoot_printf("Error decrypting header for %s\r\n", part_name);
|
|
selected ^= 1;
|
|
continue;
|
|
}
|
|
hdr_ptr = dec_hdr;
|
|
#endif
|
|
memset(&os_image, 0, sizeof(os_image));
|
|
ret = wolfBoot_open_image_address(&os_image, (void*)hdr_ptr);
|
|
if (ret < 0) {
|
|
wolfBoot_printf("Error parsing loaded image\r\n");
|
|
selected ^= 1;
|
|
continue;
|
|
}
|
|
|
|
/* Bound the UNAUTHENTICATED image length before it drives the disk
|
|
* read into the RAM load region. An attacker controlling the boot
|
|
* media could otherwise declare an arbitrary fw_size and overrun the
|
|
* load region before any integrity or signature check runs. Mirrors
|
|
* the cap update_ram.c applies to RAMBOOT loads; on FSP the tolum
|
|
* check below applies in addition (whichever is tighter wins). */
|
|
#ifdef WOLFBOOT_DISK_FS
|
|
/* A file too short to hold what its header declares is a truncated
|
|
* image. Failing the slot here reports the real reason, instead of
|
|
* the short read further down that looks like an I/O error. */
|
|
if ((slot->vol.type != FS_TYPE_RAW) &&
|
|
(((uint64_t)os_image.fw_size + (uint64_t)IMAGE_HEADER_SIZE) >
|
|
fs_size(&slot->f))) {
|
|
wolfBoot_printf("Image larger than file for %s\r\n", part_name);
|
|
selected ^= 1;
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
#ifdef WOLFBOOT_RAMBOOT_MAX_SIZE
|
|
if (os_image.fw_size > WOLFBOOT_RAMBOOT_MAX_SIZE) {
|
|
wolfBoot_printf("Image size %u exceeds max RAM load size\r\n",
|
|
os_image.fw_size);
|
|
selected ^= 1;
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
#ifdef WOLFBOOT_FSP
|
|
/* Verify image size fits in low memory. Reuse the validated
|
|
* slot_max: it is zero when tolum is inverted, where the raw
|
|
* subtraction would wrap into a near-2^32 limit. */
|
|
if (os_image.fw_size > slot_max) {
|
|
wolfBoot_printf("Image size %u doesn't fit in low memory\r\n",
|
|
os_image.fw_size);
|
|
selected ^= 1;
|
|
continue;
|
|
}
|
|
/* Log memory load */
|
|
x86_log_memory_load((uint32_t)(uintptr_t)load_address,
|
|
(uint32_t)(uintptr_t)load_address + os_image.fw_size,
|
|
part_name);
|
|
#endif
|
|
|
|
/* Read the payload into RAM (skip header) */
|
|
wolfBoot_printf("Loading image from disk...");
|
|
BENCHMARK_START();
|
|
load_off = 0;
|
|
do {
|
|
uint32_t chunk = os_image.fw_size - load_off;
|
|
if (chunk > DISK_BLOCK_SIZE)
|
|
chunk = DISK_BLOCK_SIZE;
|
|
wolfBoot_watchdog_feed();
|
|
ret = slot_read(slot, IMAGE_HEADER_SIZE + load_off, chunk,
|
|
((uint8_t *)load_address) + load_off);
|
|
if (ret <= 0)
|
|
break;
|
|
load_off += ret;
|
|
} while (load_off < os_image.fw_size);
|
|
|
|
/* A short read must fail here, as an I/O error. `ret == 0` breaks the
|
|
* loop above without being negative, and a truncated load would
|
|
* otherwise sail through to the integrity check and be reported as a
|
|
* corrupt image -- pointing the operator at the wrong problem, and on
|
|
* a system with anti-rollback leaving no bootable slot at all. */
|
|
if (ret <= 0 || load_off != os_image.fw_size) {
|
|
wolfBoot_printf("Error reading image from disk: p%d "
|
|
"(%u of %u bytes)\r\n", cur_part,
|
|
(unsigned int)load_off, (unsigned int)os_image.fw_size);
|
|
selected ^= 1;
|
|
continue;
|
|
}
|
|
BENCHMARK_END("done");
|
|
|
|
#ifdef DISK_ENCRYPT
|
|
/* Decrypt the payload in RAM */
|
|
wolfBoot_printf("Decrypting image...");
|
|
BENCHMARK_START();
|
|
if ((IMAGE_HEADER_SIZE % ENCRYPT_BLOCK_SIZE) != 0) {
|
|
disk_decrypted_header_clear(dec_hdr);
|
|
disk_crypto_clear();
|
|
wolfBoot_printf("Encrypted disk images require aligned header size\r\n");
|
|
wolfBoot_panic();
|
|
}
|
|
disk_crypto_set_iv(IMAGE_HEADER_SIZE / ENCRYPT_BLOCK_SIZE);
|
|
crypto_decrypt((uint8_t*)load_address, (uint8_t*)load_address,
|
|
os_image.fw_size);
|
|
BENCHMARK_END("done");
|
|
#endif
|
|
|
|
memset(&os_image, 0, sizeof(os_image));
|
|
ret = wolfBoot_open_image_address(&os_image, (void*)hdr_ptr);
|
|
if (ret < 0) {
|
|
wolfBoot_printf("Error parsing loaded image\r\n");
|
|
selected ^= 1;
|
|
continue;
|
|
}
|
|
os_image.fw_base = (uint8_t*)load_address;
|
|
/* Now in RAM: an EXT_FLASH build would otherwise verify through
|
|
* ext_flash_check_read() and hash flash, not the staged image. */
|
|
os_image.not_ext = 1;
|
|
|
|
#ifndef WOLFBOOT_SKIP_BOOT_VERIFY
|
|
wolfBoot_printf("Checking image integrity...");
|
|
BENCHMARK_START();
|
|
if (wolfBoot_verify_integrity(&os_image) != 0) {
|
|
wolfBoot_printf("Error validating integrity for %s\r\n", part_name);
|
|
selected ^= 1;
|
|
continue;
|
|
}
|
|
BENCHMARK_END("done");
|
|
|
|
wolfBoot_printf("Verifying image signature...");
|
|
BENCHMARK_START();
|
|
if (wolfBoot_verify_authenticity(&os_image) != 0) {
|
|
wolfBoot_printf("Error validating authenticity for %s\r\n",
|
|
part_name);
|
|
selected ^= 1;
|
|
continue;
|
|
} else {
|
|
BENCHMARK_END("done");
|
|
failures = 0;
|
|
break; /* Success case */
|
|
}
|
|
#else
|
|
failures = 0;
|
|
break; /* Skip verification, boot directly */
|
|
#endif
|
|
} while (failures < MAX_FAILURES + GOLDEN_ATTEMPTS);
|
|
|
|
if (failures) {
|
|
#ifdef DISK_ENCRYPT
|
|
disk_decrypted_header_clear(dec_hdr);
|
|
disk_crypto_clear();
|
|
#endif
|
|
wolfBoot_printf("Unable to find a valid partition!\r\n");
|
|
wolfBoot_panic();
|
|
return;
|
|
}
|
|
|
|
/* NOTE: the boot disk is intentionally kept open here. A/B boot-slot
|
|
* bookkeeping (hal_boot_slot_select) and the firmware-DTB / RAUC bootargs
|
|
* path (hal_get_boot_dts) below still need to read/write the env partition;
|
|
* disk_close(BOOT_DISK) is deferred to just before hal_prepare_boot(). */
|
|
wolfBoot_printf("Firmware Valid.\r\n");
|
|
disk_boot_part = (int)boot_slots[selected].part;
|
|
|
|
#ifdef DISK_BOOT_CONFIRM
|
|
/* Put the slot on probation, but only if an update was staged into it.
|
|
* This mirrors update_ram.c, which promotes UPDATING to TESTING and
|
|
* nothing else: a device that never stages an update is never probated,
|
|
* 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_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) {
|
|
/* Loud, because the consequence is silent: without the TESTING
|
|
* mark a failed boot of this image is never detected and the
|
|
* slot is retried for ever. */
|
|
wolfBoot_printf("WARNING: could not arm boot confirmation on "
|
|
"p%d; a failed boot of this image will not be detected\r\n",
|
|
boot_slots[selected].part);
|
|
}
|
|
}
|
|
#endif
|
|
#if defined(WOLFBOOT_MEASURED_BOOT) && defined(WOLFBOOT_MEASURED_PCR_OS)
|
|
/* Measure the verified OS image into its own PCR, separate from the
|
|
* firmware measurement in WOLFBOOT_MEASURED_PCR_A. PCR4 is the TCG slot for
|
|
* the boot payload the boot manager launches. The authenticated digest is
|
|
* always re-hashed into the PCR bank algorithm and that value extended, so
|
|
* the measurement is derived the same way whatever the image hash width. */
|
|
/* sha_hash is set only by a successful verify; NULL under
|
|
* WOLFBOOT_SKIP_BOOT_VERIFY. Fail-secure rather than dereference it. */
|
|
if (os_image.sha_hash == NULL) {
|
|
wolfBoot_printf("No OS digest available to measure\r\n");
|
|
wolfBoot_panic();
|
|
}
|
|
if (wc_InitSha256(&os_sha) != 0 ||
|
|
wc_Sha256Update(&os_sha, os_image.sha_hash,
|
|
WOLFBOOT_SHA_DIGEST_SIZE) != 0 ||
|
|
wc_Sha256Final(&os_sha, os_pcr) != 0) {
|
|
wolfBoot_printf("Failed to re-hash the OS digest\r\n");
|
|
wolfBoot_panic();
|
|
}
|
|
measure_ret = wolfBoot_tpm2_extend(WOLFBOOT_MEASURED_PCR_OS, os_pcr,
|
|
__LINE__);
|
|
if (measure_ret != 0) {
|
|
/* Fail-secure, as stage1 does for its own measurement: a working
|
|
* TPM that cannot record the OS measurement must not boot it. */
|
|
wolfBoot_printf("Failed to measure the OS image into its PCR\r\n");
|
|
wolfBoot_panic();
|
|
}
|
|
#endif /* WOLFBOOT_MEASURED_BOOT && WOLFBOOT_MEASURED_PCR_OS */
|
|
|
|
load_address = (uint32_t*)os_image.fw_base;
|
|
|
|
#ifdef WOLFBOOT_FDT
|
|
/* Is this a Flattened uImage Tree (FIT) image (FDT format)? The
|
|
* capacity handed to the parser is the number of verified bytes
|
|
* staged at load_address, so a FIT that overstates its own size is
|
|
* rejected here rather than read past. */
|
|
if (fdt_open(&fit_ctx, (void*)load_address, os_image.fw_size) == 0) {
|
|
fdt_ctx* fit = &fit_ctx;
|
|
const char *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL;
|
|
const char *fpga = NULL;
|
|
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
|
|
void *atf_load;
|
|
#endif
|
|
|
|
wolfBoot_printf("Flattened uImage Tree: Size %d\n",
|
|
(int)fdt_size(fit));
|
|
|
|
(void)fit_find_images(fit, &kernel, &flat_dt, &ramdisk, &fpga);
|
|
#ifdef WOLFBOOT_FPGA_BITSTREAM
|
|
/* Program the PL before booting so PL-dependent clocks and
|
|
* peripherals are up first. */
|
|
if (fpga != NULL) {
|
|
if (fit_load_fpga(fit, fpga) != 0) {
|
|
wolfBoot_printf("FIT: FPGA load failed\r\n");
|
|
#ifdef DISK_ENCRYPT
|
|
disk_decrypted_header_clear(dec_hdr);
|
|
disk_crypto_clear();
|
|
#endif
|
|
wolfBoot_panic();
|
|
}
|
|
}
|
|
#else
|
|
(void)fpga;
|
|
#endif
|
|
if (kernel != NULL) {
|
|
void *new_load = fit_load_kernel(fit, kernel, NULL);
|
|
if (new_load == NULL) {
|
|
wolfBoot_printf("FIT: failed to load kernel '%s'\r\n",
|
|
kernel);
|
|
#ifdef DISK_ENCRYPT
|
|
disk_decrypted_header_clear(dec_hdr);
|
|
disk_crypto_clear();
|
|
#endif
|
|
wolfBoot_panic();
|
|
}
|
|
load_address = new_load;
|
|
}
|
|
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
|
|
/* Load BL31 (ARM-TF) from the FIT "atf" sub-image, if present. */
|
|
atf_load = fit_load_image(fit, "atf", NULL);
|
|
if (atf_load != NULL) {
|
|
bl31_entry = (uintptr_t)atf_load;
|
|
wolfBoot_printf("FIT: BL31 (atf) loaded at %p\r\n", atf_load);
|
|
}
|
|
#endif
|
|
if (flat_dt != NULL) {
|
|
int dt_len = 0;
|
|
uint8_t *dts_ptr = fit_load_image(fit, flat_dt, &dt_len);
|
|
/* Bound the parse by the sub-image's own declared length,
|
|
* the tightest bound available here. */
|
|
int parsed = (dts_ptr != NULL && dt_len > 0)
|
|
? wolfBoot_get_dts_size(dts_ptr, (uint32_t)dt_len) : -1;
|
|
if (dts_ptr != NULL &&
|
|
parsed >= (int)WOLFBOOT_DTS_MIN_SIZE &&
|
|
(uint32_t)parsed <= WOLFBOOT_DTS_MAX_SIZE) {
|
|
/* Relocate to the load DTS address. The copy length is
|
|
* the parsed DTB size, clamped to WOLFBOOT_DTS_MAX_SIZE,
|
|
* not the FIT-declared property length. The staging window
|
|
* at WOLFBOOT_LOAD_DTS_ADDRESS must be at least that large
|
|
* (or the bound must be overridden for the target). */
|
|
dts_addr = (uint8_t*)WOLFBOOT_LOAD_DTS_ADDRESS;
|
|
dts_size = (uint32_t)parsed;
|
|
wolfBoot_printf("Loading DTS: %p -> %p (%d bytes)\n",
|
|
dts_ptr, dts_addr, dts_size);
|
|
/* The FIT is signature-verified as a whole, so its DTB (and
|
|
* the bootargs inside it) are authenticated. */
|
|
fdt_set_dtb_authenticated(1);
|
|
if (wolfBoot_fit_memcpy(dts_addr, dts_ptr, dts_size) != 0) {
|
|
wolfBoot_printf("FIT: failed to load DTS\r\n");
|
|
#ifdef DISK_ENCRYPT
|
|
disk_decrypted_header_clear(dec_hdr);
|
|
disk_crypto_clear();
|
|
#endif
|
|
wolfBoot_panic();
|
|
}
|
|
}
|
|
else {
|
|
/* Do not fall through to the firmware DTB silently: it is
|
|
* a different device tree than the FIT names. */
|
|
wolfBoot_printf("FIT: '%s' is not a usable DTB (%d); "
|
|
"trying the boot firmware's instead\r\n", flat_dt,
|
|
parsed);
|
|
}
|
|
}
|
|
#ifdef WOLFBOOT_FIT_RAMDISK
|
|
fit_ramdisk = ramdisk;
|
|
#else
|
|
(void)ramdisk;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#if defined(WOLFBOOT_ELF) && !defined(WOLFBOOT_FSP)
|
|
/* Load elf sections and return the new entry point */
|
|
/* Skip for FSP, since it expects ELF image directly */
|
|
if (elf_load_image_mmu((uint8_t*)load_address, os_image.fw_size,
|
|
(uintptr_t*)&load_address, NULL) != 0){
|
|
wolfBoot_printf("Invalid elf, falling back to raw binary\n");
|
|
}
|
|
#endif
|
|
|
|
wolfBoot_printf("Booting at %p\r\n", (void*)load_address);
|
|
|
|
#ifdef WOLFBOOT_ENABLE_WOLFHSM_CLIENT
|
|
(void)hal_hsm_disconnect();
|
|
#elif defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER)
|
|
(void)hal_hsm_server_cleanup();
|
|
#endif
|
|
|
|
#ifdef ENCRYPT_PKCS11
|
|
pkcs11_crypto_deinit();
|
|
#endif
|
|
#ifndef TZEN
|
|
if (hal_flash_protect(WOLFBOOT_ORIGIN, BOOTLOADER_PARTITION_SIZE) < 0) {
|
|
wolfBoot_printf("Error protecting bootloader flash region\r\n");
|
|
#ifdef DISK_ENCRYPT
|
|
disk_decrypted_header_clear(dec_hdr);
|
|
disk_crypto_clear();
|
|
#endif
|
|
wolfBoot_panic();
|
|
}
|
|
#endif
|
|
#ifdef MMU
|
|
/* If the loaded image carried no FDT (e.g. a kernel-only FIT), fall back to
|
|
* a DTB the boot firmware handed us. Done BEFORE hal_prepare_boot() so any
|
|
* FDT relocation/fixup runs while the MMU/caches are still enabled (some
|
|
* platforms, e.g. CM4, disable the MMU in hal_prepare_boot() and FDT edits
|
|
* would then fault on Device-nGnRnE memory). */
|
|
/* Run A/B boot-slot bookkeeping unconditionally (not gated on dts_addr), so
|
|
* failover works whether or not the FIT embedded its own fdt. */
|
|
(void)hal_boot_slot_select();
|
|
if (dts_addr == NULL) {
|
|
dts_addr = (uint8_t*)hal_get_boot_dts();
|
|
}
|
|
if (dts_addr == NULL) {
|
|
/* Fall back to the firmware DTB, forwarded unauthenticated: no
|
|
* digest check here, and only a mapped HAL pointer is usable. */
|
|
uint8_t *cfg_dts = (uint8_t*)hal_get_dts_address();
|
|
int cfg_len;
|
|
|
|
if (cfg_dts != NULL) {
|
|
cfg_len = wolfBoot_get_dts_size(cfg_dts, WOLFBOOT_DTS_MAX_SIZE);
|
|
if (cfg_len < (int)WOLFBOOT_DTS_MIN_SIZE ||
|
|
(uint32_t)cfg_len > WOLFBOOT_DTS_MAX_SIZE) {
|
|
wolfBoot_printf("DTB parse/size check failed - ignoring\r\n");
|
|
}
|
|
else if ((uintptr_t)cfg_dts ==
|
|
(uintptr_t)WOLFBOOT_LOAD_DTS_ADDRESS) {
|
|
/* Usual case: the HAL returns the staging address. */
|
|
dts_addr = cfg_dts;
|
|
}
|
|
else if (wolfBoot_fit_memcpy((void*)WOLFBOOT_LOAD_DTS_ADDRESS,
|
|
cfg_dts, (uint32_t)cfg_len) == 0) {
|
|
/* Relocate first: the initrd fixup grows the tree in place
|
|
* against WOLFBOOT_DTS_MAX_SIZE of writable space. */
|
|
dts_addr = (uint8_t*)WOLFBOOT_LOAD_DTS_ADDRESS;
|
|
}
|
|
else {
|
|
wolfBoot_printf("DTB relocation failed - ignoring\r\n");
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#if defined(WOLFBOOT_FDT) && defined(WOLFBOOT_FIT_RAMDISK)
|
|
/* Deferred until dts_addr is final so the fixup lands on the tree the
|
|
* kernel is handed. Outside the MMU guard for the MPFS E51 boot. */
|
|
if (fit_ramdisk != NULL) {
|
|
fdt_ctx dts_ctx;
|
|
fdt_ctx* dts_for_initrd = NULL;
|
|
|
|
/* The DTB is in the staging window; that is the growth capacity. */
|
|
if (dts_addr != NULL &&
|
|
fdt_open(&dts_ctx, dts_addr, WOLFBOOT_DTS_MAX_SIZE) == 0) {
|
|
dts_for_initrd = &dts_ctx;
|
|
}
|
|
else if (dts_addr != NULL) {
|
|
wolfBoot_printf("FIT: DTB at %p is not usable for the initrd "
|
|
"fixup\n", dts_addr);
|
|
}
|
|
if (fit_load_ramdisk(&fit_ctx, fit_ramdisk, dts_for_initrd) != 0) {
|
|
wolfBoot_printf("FIT: booting without an initrd\n");
|
|
}
|
|
}
|
|
#endif
|
|
/* Deferred from just after verification (see NOTE above): close the boot
|
|
* disk now that all env / DTB reads and writes are done, before handoff. */
|
|
disk_close(BOOT_DISK);
|
|
#if defined(MMU) || defined(WOLFBOOT_FDT)
|
|
/* After every relocation/fallback/digest check, so a hook never sees
|
|
* an unvalidated blob. */
|
|
wolfboot_dts_addr = (void*)dts_addr;
|
|
#endif
|
|
#ifdef WOLFBOOT_HOOK_PREBOOT
|
|
/* Before hal_prepare_boot(), so a hook still has the MMU and caches as
|
|
* wolfBoot set them up. */
|
|
wolfBoot_hook_preboot(&os_image);
|
|
#endif
|
|
hal_prepare_boot();
|
|
|
|
#ifdef WOLFBOOT_HOOK_BOOT
|
|
wolfBoot_hook_boot(&os_image);
|
|
#endif
|
|
#ifndef WOLFBOOT_SKIP_BOOT_VERIFY
|
|
PART_SANITY_CHECK(&os_image);
|
|
#endif
|
|
#ifdef DISK_ENCRYPT
|
|
disk_decrypted_header_clear(dec_hdr);
|
|
disk_crypto_clear();
|
|
#endif
|
|
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
|
|
if (bl31_entry != 0) {
|
|
wolfBoot_printf("Handing off to BL31 at %p (kernel %p)\r\n",
|
|
(void*)bl31_entry, (void*)load_address);
|
|
zynqmp_atf_handoff(bl31_entry, (uintptr_t)load_address,
|
|
(uintptr_t)dts_addr, ZYNQMP_ATF_EL2);
|
|
}
|
|
#endif
|
|
#ifdef WOLFBOOT_FSP
|
|
/* Hand the verified payload length to the Linux loader (via do_boot) so it
|
|
* can bound the signed container header against the image. */
|
|
stage2_params->payload_size = (uint32_t)os_image.fw_size;
|
|
#endif
|
|
do_boot((uint32_t*)load_address
|
|
#if defined(MMU) || defined(WOLFBOOT_FDT)
|
|
,(uint32_t*)dts_addr
|
|
#endif
|
|
);
|
|
}
|
|
#endif /* WOLFBOOT_UPDATE_DISK */
|