mirror of https://github.com/wolfSSL/wolfBoot.git
856 lines
32 KiB
C
856 lines
32 KiB
C
/* update_ram.c
|
|
*
|
|
* Implementation for RAM based updater
|
|
*
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
#include "image.h"
|
|
#include "loader.h"
|
|
#include "hal.h"
|
|
#include "hooks.h"
|
|
#include "spi_flash.h"
|
|
#include "printf.h"
|
|
#include "wolfboot/wolfboot.h"
|
|
#include <string.h>
|
|
#include "encrypt.h"
|
|
|
|
#ifdef WOLFBOOT_UBOOT_LEGACY
|
|
#include "gpt.h" /* gpt_crc32_* helpers (reflected CRC-32, poly 0xEDB88320) */
|
|
#endif
|
|
|
|
#ifdef WOLFBOOT_TPM
|
|
#include "tpm.h"
|
|
#endif
|
|
#ifdef WOLFBOOT_ELF
|
|
#include "elf.h"
|
|
#endif
|
|
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
|
|
#include "../hal/zynqmp_atf.h"
|
|
#endif
|
|
|
|
extern void hal_flash_dualbank_swap(void);
|
|
/* DTS helpers declared in include/image.h under (MMU || WOLFBOOT_FDT). */
|
|
|
|
extern uint32_t kernel_load_addr;
|
|
extern uint32_t dts_load_addr;
|
|
|
|
#if defined(__WOLFBOOT) && defined(WOLFBOOT_LOAD_ADDRESS)
|
|
extern uint8_t _end[]; /* linker symbol: end of wolfBoot BSS */
|
|
#endif
|
|
|
|
/* Return non-zero if the RAM load region [img_lo, img_hi) overlaps wolfBoot's
|
|
* own region [wb_lo, wb_hi). wb_lo == 0 means the origin is unknown, so guard
|
|
* only that the image loads above wolfBoot's end (wb_hi). Pure arithmetic,
|
|
* exposed at file scope for unit testing (tools/unit-tests/unit-update-ram.c). */
|
|
static inline int ramboot_region_overlap(uintptr_t img_lo, uintptr_t img_hi,
|
|
uintptr_t wb_lo, uintptr_t wb_hi)
|
|
{
|
|
if (img_hi < img_lo)
|
|
return 1; /* header+size wrapped the end address: reject conservatively */
|
|
if (wb_lo == 0)
|
|
return (img_lo < wb_hi);
|
|
return (img_lo < wb_hi && img_hi > wb_lo);
|
|
}
|
|
|
|
#if ((defined(EXT_FLASH) && defined(NO_XIP)) || \
|
|
(defined(EXT_ENCRYPTED) && defined(MMU))) && \
|
|
!defined(WOLFBOOT_NO_RAMBOOT)
|
|
/* Load firmware to RAM on boot (single flash read) */
|
|
#undef WOLFBOOT_USE_RAMBOOT
|
|
#define WOLFBOOT_USE_RAMBOOT
|
|
#endif
|
|
|
|
#ifdef WOLFBOOT_USE_RAMBOOT
|
|
|
|
/* Function to load image from flash to ram */
|
|
int wolfBoot_ramboot(struct wolfBoot_image *img, uint8_t *src, uint8_t *dst)
|
|
{
|
|
int ret;
|
|
uint32_t img_size;
|
|
BENCHMARK_DECLARE();
|
|
|
|
/* read header into RAM */
|
|
wolfBoot_printf("Loading header %d bytes from %p to %p\n",
|
|
IMAGE_HEADER_SIZE, src, dst);
|
|
#if defined(EXT_FLASH) && defined(NO_XIP)
|
|
ret = ext_flash_read((uintptr_t)src, dst, IMAGE_HEADER_SIZE);
|
|
if (ret != IMAGE_HEADER_SIZE){
|
|
wolfBoot_printf("Error reading header at %p\n", src);
|
|
return -1;
|
|
}
|
|
#else
|
|
memcpy(dst, src, IMAGE_HEADER_SIZE);
|
|
#endif
|
|
|
|
/* check for valid header and version */
|
|
ret = wolfBoot_get_blob_version((uint8_t*)dst);
|
|
if (ret <= 0) {
|
|
wolfBoot_printf("No valid image found at %p\n", src);
|
|
return -1;
|
|
}
|
|
|
|
/* determine size of partition */
|
|
img_size = wolfBoot_image_size((uint8_t*)dst);
|
|
#if !defined(WOLFBOOT_FIXED_PARTITIONS) && !defined(WOLFBOOT_RAMBOOT_MAX_SIZE)
|
|
# error "WOLFBOOT_FIXED_PARTITIONS or WOLFBOOT_RAMBOOT_MAX_SIZE required to bound the RAM load"
|
|
#endif
|
|
/* Bound the UNAUTHENTICATED image length before it drives the copy into the
|
|
* RAM load region: the image is loaded to RAM before its signature is
|
|
* verified, so this length (read from the not-yet-authenticated header) is
|
|
* attacker-influenceable and must be range checked first. When both are
|
|
* configured, WOLFBOOT_RAMBOOT_MAX_SIZE takes precedence: it is the explicit
|
|
* cap on the RAM load region and may be tighter than the partition size. */
|
|
#if defined(WOLFBOOT_RAMBOOT_MAX_SIZE)
|
|
if (img_size > WOLFBOOT_RAMBOOT_MAX_SIZE) {
|
|
wolfBoot_printf("Invalid image size %u at %p\n", img_size, src);
|
|
return -1;
|
|
}
|
|
#elif defined(WOLFBOOT_FIXED_PARTITIONS)
|
|
if (WOLFBOOT_PARTITION_SIZE <= IMAGE_HEADER_SIZE ||
|
|
img_size > (uint32_t)(WOLFBOOT_PARTITION_SIZE - IMAGE_HEADER_SIZE)) {
|
|
wolfBoot_printf("Invalid image size %u at %p\n", img_size, src);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
#if defined(__WOLFBOOT) && defined(WOLFBOOT_LOAD_ADDRESS)
|
|
/* Overlap check: the image destination must not overwrite wolfBoot's own
|
|
* code/data/bss (ends at _end). The image occupies [dst, dst+header+size]. */
|
|
{
|
|
uintptr_t wb_hi = (uintptr_t)_end;
|
|
uintptr_t img_lo = (uintptr_t)dst;
|
|
uintptr_t img_hi = img_lo + (uintptr_t)IMAGE_HEADER_SIZE +
|
|
(uintptr_t)img_size;
|
|
#if defined(WOLFBOOT_ORIGIN)
|
|
/* wolfBoot spans [WOLFBOOT_ORIGIN, _end]; range-intersect so it holds
|
|
* whether wolfBoot is below or above the image -- e.g. ZynqMP FSBL runs
|
|
* from high OCM while the image loads to low DDR, where the plain
|
|
* "dst < _end" test gave a false positive. */
|
|
uintptr_t wb_lo = (uintptr_t)(WOLFBOOT_ORIGIN);
|
|
#else
|
|
/* Without WOLFBOOT_ORIGIN, wb_lo=0 keeps the original low-addr guard. */
|
|
uintptr_t wb_lo = 0;
|
|
#endif
|
|
if (ramboot_region_overlap(img_lo, img_hi, wb_lo, wb_hi)) {
|
|
wolfBoot_printf("Error: image %p-%p overlaps wolfBoot %p-%p\n",
|
|
(void*)img_lo, (void*)img_hi, (void*)wb_lo, (void*)wb_hi);
|
|
return -1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Read the entire image into RAM */
|
|
wolfBoot_printf("Loading image %d bytes from %p to %p...",
|
|
img_size, src + IMAGE_HEADER_SIZE, dst + IMAGE_HEADER_SIZE);
|
|
BENCHMARK_START();
|
|
#if defined(EXT_FLASH) && defined(NO_XIP)
|
|
ret = ext_flash_read((uintptr_t)src + IMAGE_HEADER_SIZE,
|
|
dst + IMAGE_HEADER_SIZE, img_size);
|
|
if (ret < 0) {
|
|
wolfBoot_printf("Error reading image at %p\n", src);
|
|
return -1;
|
|
}
|
|
#else
|
|
memcpy(dst + IMAGE_HEADER_SIZE, src + IMAGE_HEADER_SIZE, img_size);
|
|
#endif
|
|
BENCHMARK_END("done");
|
|
|
|
/* mark image as no longer external */
|
|
img->not_ext = 1;
|
|
|
|
return 0; /* success */
|
|
}
|
|
#endif /* WOLFBOOT_USE_RAMBOOT */
|
|
|
|
#ifdef WOLFBOOT_UBOOT_LEGACY
|
|
/* Validate a 64-byte U-Boot legacy image header (image_header_t).
|
|
*
|
|
* Layout (all multi-byte fields stored big-endian on flash):
|
|
* 0x00 4 ih_magic 0x27051956
|
|
* 0x04 4 ih_hcrc CRC32 of header with hcrc treated as 0
|
|
* 0x08 4 ih_time timestamp
|
|
* 0x0C 4 ih_size payload size (excl. header)
|
|
* 0x10 4 ih_load load address
|
|
* 0x14 4 ih_ep entry point
|
|
* 0x18 4 ih_dcrc data CRC32 (validated by wolfBoot signature)
|
|
* 0x1C 1 ih_os
|
|
* 0x1D 1 ih_arch
|
|
* 0x1E 1 ih_type
|
|
* 0x1F 1 ih_comp
|
|
* 0x20 32 ih_name
|
|
*
|
|
* Magic alone is a ~1-in-2^32 collision for random data, so we also
|
|
* validate the header CRC32 (~2^-32) and the payload size, dropping the
|
|
* joint false-positive probability to roughly 2^-64. This matches
|
|
* U-Boot's own mkimage/bootm validation. */
|
|
|
|
/* Read a 32-bit big-endian uImage header field from a (possibly
|
|
* unaligned) location and return it in host byte order. Done locally
|
|
* rather than via fdt32_to_cpu() so WOLFBOOT_UBOOT_LEGACY does not
|
|
* require src/fdt.c to be linked -- some targets enable uImage support
|
|
* without the FDT/MMU code (e.g. zynq7000 when MMU != 1). The memcpy
|
|
* also avoids an unaligned 32-bit load on architectures that fault. */
|
|
static uint32_t uboot_read_be32(const uint8_t *p)
|
|
{
|
|
uint32_t x;
|
|
memcpy(&x, p, sizeof(x));
|
|
#ifdef BIG_ENDIAN_ORDER
|
|
return x;
|
|
#else
|
|
return ((x & 0xFF000000U) >> 24) |
|
|
((x & 0x00FF0000U) >> 8) |
|
|
((x & 0x0000FF00U) << 8) |
|
|
((x & 0x000000FFU) << 24);
|
|
#endif
|
|
}
|
|
|
|
static int uboot_legacy_header_valid(const uint8_t *hdr, uint32_t total)
|
|
{
|
|
struct gpt_crc32_ctx ctx;
|
|
uint8_t scratch[UBOOT_IMG_HDR_SZ];
|
|
uint32_t magic;
|
|
uint32_t hcrc;
|
|
uint32_t size;
|
|
uint32_t crc;
|
|
|
|
if (hdr == NULL)
|
|
return 0;
|
|
if (total < UBOOT_IMG_HDR_SZ)
|
|
return 0;
|
|
|
|
/* ih_magic is stored big-endian on flash; UBOOT_IMG_HDR_MAGIC is
|
|
* defined as the host-order word matching that BE encoding (see
|
|
* include/image.h -- different on LE vs BE hosts). */
|
|
memcpy(&magic, hdr + 0x00, sizeof(magic));
|
|
if (magic != UBOOT_IMG_HDR_MAGIC)
|
|
return 0;
|
|
|
|
/* ih_size: big-endian payload length. uboot_read_be32 converts to
|
|
* host order (no-op on a BE host, byte-swap on LE). Reject zero
|
|
* and anything that would overrun the signed image. */
|
|
size = uboot_read_be32(hdr + 0x0C);
|
|
if (size == 0)
|
|
return 0;
|
|
if (size > (total - UBOOT_IMG_HDR_SZ))
|
|
return 0;
|
|
|
|
/* ih_hcrc: CRC32 of the header with the hcrc field treated as zero.
|
|
* Read (and convert from big-endian) before zeroing the field. */
|
|
memcpy(scratch, hdr, UBOOT_IMG_HDR_SZ);
|
|
hcrc = uboot_read_be32(scratch + 0x04);
|
|
memset(scratch + 0x04, 0, sizeof(hcrc));
|
|
gpt_crc32_init(&ctx);
|
|
gpt_crc32_update(&ctx, scratch, UBOOT_IMG_HDR_SZ);
|
|
crc = gpt_crc32_final(&ctx);
|
|
if (hcrc != crc)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
#endif /* WOLFBOOT_UBOOT_LEGACY */
|
|
|
|
void RAMFUNCTION wolfBoot_start(void)
|
|
{
|
|
int active = -1, ret = 0;
|
|
/* Candidates already tried this boot; a failed RAM image cannot be
|
|
* erased to invalidate it like the flash path does. */
|
|
int tried_boot = 0, tried_update = 0;
|
|
struct wolfBoot_image os_image;
|
|
BENCHMARK_DECLARE();
|
|
#ifdef WOLFBOOT_UBOOT_LEGACY
|
|
uint8_t *image_ptr;
|
|
/* uImage ih_ep, kept only when the entry point differs from the load
|
|
* address (see the do_boot() entry override below). */
|
|
uint32_t *uboot_entry = NULL;
|
|
/* Set when a later stage (ELF/FIT) re-derives the load address and so
|
|
* supplies its own entry point, which then wins over ih_ep. */
|
|
int stage_entry_override = 0;
|
|
#endif
|
|
uint32_t *load_address = NULL;
|
|
uint32_t *source_address = NULL;
|
|
#ifdef WOLFBOOT_FIXED_PARTITIONS
|
|
uint8_t p_state;
|
|
#endif
|
|
#if defined(MMU) || defined(WOLFBOOT_FDT)
|
|
/* Passed to the 2-arg do_boot() below; NULL when there is no DTS (e.g.
|
|
* WOLFBOOT_FDT without MMU, booting a non-FIT image -> no fixup). */
|
|
uint8_t *dts_addr = NULL;
|
|
#endif
|
|
#ifdef MMU
|
|
uint32_t dts_size = 0;
|
|
/* Validated view of the FIT staged at load_address. */
|
|
fdt_ctx fit_ctx;
|
|
/* HDR_DEVICE_TREE_DIGEST snapshot, taken before the raw DTB is loaded. */
|
|
uint8_t dts_digest[WOLFBOOT_SHA_DIGEST_SIZE];
|
|
uint8_t *dts_tlv = NULL;
|
|
uint16_t dts_tlv_len = 0;
|
|
int dts_digest_present = 0; /* 0 absent, 1 valid, -1 malformed */
|
|
#if defined(EXT_FLASH) && defined(WOLFBOOT_DTS_BOOT_ADDRESS)
|
|
/* FDT header peek (fdt_peek_size needs >= 40 bytes, 4-byte aligned) */
|
|
uint8_t dts_hdr[64] __attribute__((aligned(4)));
|
|
#endif
|
|
#endif
|
|
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
|
|
/* When wolfBoot is the FSBL, the boot FIT carries an "atf" (BL31)
|
|
* sub-image. If present, hand off to BL31 instead of jumping to the
|
|
* kernel directly. */
|
|
uintptr_t bl31_entry = 0;
|
|
#endif
|
|
#if !defined(ALLOW_DOWNGRADE) && defined(WOLFBOOT_FIXED_PARTITIONS)
|
|
uint32_t boot_v = wolfBoot_current_firmware_version();
|
|
uint32_t update_v = wolfBoot_update_firmware_version();
|
|
uint32_t max_v = (boot_v > update_v) ? boot_v : update_v;
|
|
#endif /* !ALLOW_DOWNGRADE && WOLFBOOT_FIXED_PARTITIONS */
|
|
|
|
memset(&os_image, 0, sizeof(struct wolfBoot_image));
|
|
|
|
for (;;) {
|
|
#if defined(WOLFBOOT_DUALBOOT) && defined(WOLFBOOT_FIXED_PARTITIONS)
|
|
if (active < 0)
|
|
active = wolfBoot_dualboot_candidate();
|
|
if (active == PART_BOOT)
|
|
source_address = (uint32_t*)WOLFBOOT_PARTITION_BOOT_ADDRESS;
|
|
else
|
|
source_address = (uint32_t*)WOLFBOOT_PARTITION_UPDATE_ADDRESS;
|
|
#else
|
|
if (active < 0)
|
|
active = wolfBoot_dualboot_candidate_addr((void**)&source_address);
|
|
else if (active == PART_BOOT)
|
|
source_address = (uint32_t*)hal_get_primary_address();
|
|
else
|
|
source_address = (uint32_t*)hal_get_update_address();
|
|
#endif
|
|
if (active < 0) { /* panic if no images available */
|
|
wolfBoot_printf("No valid image found!\n");
|
|
wolfBoot_panic();
|
|
break;
|
|
}
|
|
#if !defined(ALLOW_DOWNGRADE) && defined(WOLFBOOT_FIXED_PARTITIONS)
|
|
{
|
|
uint32_t active_v = (active == PART_UPDATE) ? update_v : boot_v;
|
|
if ((max_v > 0U) && (active_v < max_v)) {
|
|
wolfBoot_printf("Rollback to lower version not allowed\n");
|
|
wolfBoot_panic();
|
|
break;
|
|
}
|
|
}
|
|
#endif /* !ALLOW_DOWNGRADE && WOLFBOOT_FIXED_PARTITIONS */
|
|
|
|
#if defined(WOLFBOOT_DUALBOOT) && defined(WOLFBOOT_FIXED_PARTITIONS)
|
|
wolfBoot_printf("Trying %s partition at %p\n",
|
|
active == PART_BOOT ? "Boot" : "Update", source_address);
|
|
#else
|
|
wolfBoot_printf("Trying partition %d at %p\n",
|
|
active, source_address);
|
|
#endif
|
|
|
|
#ifdef WOLFBOOT_USE_RAMBOOT
|
|
load_address = (uint32_t *)(uintptr_t)(WOLFBOOT_LOAD_ADDRESS -
|
|
IMAGE_HEADER_SIZE);
|
|
#if defined(EXT_ENCRYPTED) && defined(MMU)
|
|
ret = wolfBoot_ram_decrypt((uint8_t*)source_address,
|
|
(uint8_t*)load_address);
|
|
#else
|
|
ret = wolfBoot_ramboot(&os_image, (uint8_t*)source_address,
|
|
(uint8_t*)load_address);
|
|
#endif
|
|
if (ret != 0) {
|
|
goto backup_on_failure;
|
|
}
|
|
#else
|
|
load_address = source_address;
|
|
#endif
|
|
#if !defined(WOLFBOOT_FIXED_PARTITIONS) || \
|
|
defined(WOLFBOOT_USE_RAMBOOT)
|
|
ret = wolfBoot_open_image_address(&os_image, (uint8_t*)load_address);
|
|
#else
|
|
ret = wolfBoot_open_image(&os_image, active);
|
|
#endif
|
|
if (ret < 0) {
|
|
goto backup_on_failure;
|
|
}
|
|
|
|
#ifndef WOLFBOOT_SKIP_BOOT_VERIFY
|
|
/* Verify image integrity (hash check) */
|
|
wolfBoot_printf("Checking integrity...");
|
|
BENCHMARK_START();
|
|
ret = wolfBoot_verify_integrity(&os_image);
|
|
if (ret < 0) {
|
|
wolfBoot_printf("FAILED\n");
|
|
goto backup_on_failure;
|
|
}
|
|
BENCHMARK_END("done");
|
|
|
|
/* Verify image authenticity (signature check) */
|
|
wolfBoot_printf("Verifying signature...");
|
|
BENCHMARK_START();
|
|
ret = wolfBoot_verify_authenticity(&os_image);
|
|
if (ret < 0) {
|
|
wolfBoot_printf("FAILED\n");
|
|
goto backup_on_failure;
|
|
}
|
|
BENCHMARK_END("done");
|
|
|
|
#endif
|
|
|
|
{
|
|
/* Success - integrity and signature valid */
|
|
#if !defined(WOLFBOOT_NO_LOAD_ADDRESS) && defined(WOLFBOOT_LOAD_ADDRESS)
|
|
load_address = (uint32_t*)WOLFBOOT_LOAD_ADDRESS;
|
|
#elif !defined(NO_XIP)
|
|
load_address = (uint32_t*)os_image.fw_base;
|
|
#else
|
|
#error missing WOLFBOOT_LOAD_ADDRESS or XIP
|
|
#endif
|
|
wolfBoot_printf("Successfully selected image in part: %d\n", active);
|
|
break;
|
|
}
|
|
|
|
backup_on_failure:
|
|
wolfBoot_printf("Failure %d: Part %d, Hdr %d, Hash %d, Sig %d\n", ret,
|
|
active, os_image.hdr_ok, os_image.sha_ok, os_image.signature_ok);
|
|
/* panic if authentication fails and no backup */
|
|
if (!wolfBoot_fallback_is_possible()) {
|
|
wolfBoot_printf("Impossible recovery with fallback.\n");
|
|
wolfBoot_panic();
|
|
break;
|
|
}
|
|
if (active == PART_BOOT)
|
|
tried_boot = 1;
|
|
else
|
|
tried_update = 1;
|
|
if (tried_boot && tried_update) {
|
|
/* Both partitions were tried and both failed: the images that
|
|
* made fallback look possible are invalid, and nothing left to
|
|
* boot. */
|
|
wolfBoot_printf(
|
|
"Both images failed verification; no valid image to boot.\n");
|
|
wolfBoot_panic();
|
|
break;
|
|
}
|
|
/* Switch to the other partition */
|
|
active ^= 1;
|
|
wolfBoot_printf("Active is now: %d\n", active);
|
|
continue;
|
|
}
|
|
#ifdef UNIT_TEST
|
|
if (wolfBoot_panicked != 0) {
|
|
wolfBoot_printf("panic!\n");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
wolfBoot_printf("Firmware Valid\n");
|
|
|
|
/* First time we boot this update, set to TESTING to await
|
|
* confirmation from the system
|
|
*/
|
|
#ifdef WOLFBOOT_FIXED_PARTITIONS
|
|
if ((wolfBoot_get_partition_state(active, &p_state) == 0) &&
|
|
(p_state == IMG_STATE_UPDATING))
|
|
{
|
|
#ifdef EXT_FLASH
|
|
ext_flash_unlock();
|
|
#else
|
|
hal_flash_unlock();
|
|
#endif
|
|
wolfBoot_set_partition_state(active, IMG_STATE_TESTING);
|
|
#ifdef EXT_FLASH
|
|
ext_flash_lock();
|
|
#else
|
|
hal_flash_lock();
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#ifdef WOLFBOOT_UBOOT_LEGACY
|
|
/* Check for U-Boot legacy format image header. Validate magic +
|
|
* header CRC32 + payload size (uboot_legacy_header_valid) before
|
|
* stripping the 64-byte header -- a non-uImage payload whose first
|
|
* 4 bytes happen to collide with UBOOT_IMG_HDR_MAGIC (~1 in 2^32)
|
|
* cannot be misinterpreted because the CRC + size checks fail.
|
|
*
|
|
* uImage header (64 bytes, big-endian fields):
|
|
* off 0 : magic 0x27051956
|
|
* off 4 : header CRC
|
|
* off 8 : creation time
|
|
* off 12 : data size
|
|
* off 16 : ih_load data load address
|
|
* off 20 : ih_ep entry point address
|
|
* off 24 : data CRC
|
|
* off 28 : os/arch/type/comp
|
|
* off 32 : name (32 bytes)
|
|
*
|
|
* After validation, honor the uImage's ih_load: U-Boot bootm copies
|
|
* the payload to ih_load and jumps to ih_ep, because PowerPC /
|
|
* VxWorks kernels are typically built non-relocatable with absolute
|
|
* references baked in. Falling back to the wolfBoot default
|
|
* WOLFBOOT_LOAD_ADDRESS would place the kernel at the wrong address
|
|
* and the first internal jump would fault.
|
|
*
|
|
* (The ih_load override applies only when ih_load is non-zero --
|
|
* typical for VxWorks: 0x00100000; for Linux PPC: 0x00000000 ->
|
|
* leave load_address alone.) */
|
|
image_ptr = wolfBoot_peek_image(&os_image, 0, NULL);
|
|
if (image_ptr != NULL &&
|
|
uboot_legacy_header_valid(image_ptr, os_image.fw_size)) {
|
|
uint32_t ih_load;
|
|
uint32_t ih_ep;
|
|
|
|
ih_load = uboot_read_be32((const uint8_t*)image_ptr + 16);
|
|
ih_ep = uboot_read_be32((const uint8_t*)image_ptr + 20);
|
|
|
|
wolfBoot_printf("U-Boot Legacy header detected: load=0x%x ep=0x%x "
|
|
"(skipping %d bytes)\n",
|
|
ih_load, ih_ep, UBOOT_IMG_HDR_SZ);
|
|
|
|
/* Skip 64 bytes (size of legacy format image header). */
|
|
os_image.fw_base += UBOOT_IMG_HDR_SZ;
|
|
os_image.fw_size -= UBOOT_IMG_HDR_SZ;
|
|
|
|
if (ih_load != 0) {
|
|
load_address = (uint32_t*)(uintptr_t)ih_load;
|
|
#ifdef WOLFBOOT_USE_RAMBOOT
|
|
/* RAMBOOT already staged the payload at os_image.fw_base, and the
|
|
* generic copy-to-RAM memcpy below is compiled out under RAMBOOT.
|
|
* Relocate the payload to ih_load ourselves when the two differ,
|
|
* mirroring the non-RAMBOOT memcpy. memmove: both ranges are in
|
|
* RAM and may overlap. */
|
|
if ((uintptr_t)ih_load != (uintptr_t)os_image.fw_base) {
|
|
memmove((void*)(uintptr_t)ih_load, os_image.fw_base,
|
|
os_image.fw_size);
|
|
}
|
|
#endif
|
|
/* bootm relocates to ih_load but enters at ih_ep: kernels built
|
|
* with a preamble ahead of the entry point set the two to
|
|
* different addresses. Remember the entry point; ih_load remains
|
|
* the relocation destination. */
|
|
if ((ih_ep != 0) && (ih_ep != ih_load)) {
|
|
uboot_entry = (uint32_t*)(uintptr_t)ih_ep;
|
|
}
|
|
} else {
|
|
/* Linux PPC path: leave load_address alone, just advance it
|
|
* past the header to match upstream behaviour. load_address is
|
|
* a uint32_t*, so advance by BYTES, not words.
|
|
* ih_ep is deliberately ignored here: with ih_load == 0 there is
|
|
* no relocation destination to enter past, and upstream enters at
|
|
* the payload start. A uImage built with "mkimage -a 0 -e <ep>"
|
|
* is therefore entered at the header offset, not at ih_ep. */
|
|
load_address = (uint32_t*)((uint8_t*)load_address +
|
|
UBOOT_IMG_HDR_SZ);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef __GNUC__
|
|
/* WOLFBOOT_LOAD_ADDRESS can be 0 address.
|
|
* Do not warn on use of NULL for memcpy */
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wnonnull"
|
|
#endif
|
|
|
|
#ifndef WOLFBOOT_USE_RAMBOOT
|
|
/* copy image to RAM */
|
|
#if defined(EXT_FLASH) && defined(NO_XIP)
|
|
wolfBoot_printf("Loading flash image from %p to RAM at %p (%d bytes)\n",
|
|
os_image.fw_base, load_address, os_image.fw_size);
|
|
ret = ext_flash_read((uintptr_t)os_image.fw_base, (uint8_t*)load_address,
|
|
os_image.fw_size);
|
|
/* Backends return the number of bytes read: a positive short read
|
|
* leaves a truncated image in RAM, so require the full size.
|
|
* ret is int, fw_size uint32_t: check the error range first and
|
|
* cast for the size comparison to keep -Wsign-compare quiet. */
|
|
if (ret < 0 || (uint32_t)ret != os_image.fw_size) {
|
|
wolfBoot_printf("Error loading image at %p (ret %d)\n",
|
|
os_image.fw_base, ret);
|
|
return;
|
|
}
|
|
#else
|
|
wolfBoot_printf("Copying image from %p to RAM at %p (%d bytes)\n",
|
|
os_image.fw_base, load_address, os_image.fw_size);
|
|
memcpy((void*)load_address, os_image.fw_base, os_image.fw_size);
|
|
#endif
|
|
#endif /* !WOLFBOOT_USE_RAMBOOT */
|
|
|
|
#ifdef WOLFBOOT_ELF
|
|
/* Load elf */
|
|
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");
|
|
}
|
|
#ifdef WOLFBOOT_UBOOT_LEGACY
|
|
else {
|
|
stage_entry_override = 1;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef MMU
|
|
/* Snapshot the digest from the verified header before os_image can be
|
|
* reused for the DTS partition below. (FIT DTBs are covered by the FIT.) */
|
|
dts_tlv_len = wolfBoot_get_header(&os_image, HDR_DEVICE_TREE_DIGEST,
|
|
&dts_tlv);
|
|
if (dts_tlv_len != 0 && dts_tlv != NULL) {
|
|
if (dts_tlv_len == WOLFBOOT_SHA_DIGEST_SIZE) {
|
|
memcpy(dts_digest, dts_tlv, WOLFBOOT_SHA_DIGEST_SIZE);
|
|
dts_digest_present = 1; /* present and well-formed */
|
|
}
|
|
else {
|
|
/* A present-but-malformed digest TLV must not silently downgrade
|
|
* to an unauthenticated DTB boot; treat it as a hard failure. */
|
|
dts_digest_present = -1;
|
|
}
|
|
}
|
|
|
|
/* 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\n");
|
|
wolfBoot_panic();
|
|
}
|
|
}
|
|
#else
|
|
(void)fpga;
|
|
#endif
|
|
if (kernel != NULL) {
|
|
void *new_load = fit_load_image(fit, kernel, NULL);
|
|
if (new_load == NULL) {
|
|
wolfBoot_printf("FIT: failed to load kernel '%s'\n", kernel);
|
|
wolfBoot_panic();
|
|
}
|
|
load_address = new_load;
|
|
#ifdef WOLFBOOT_UBOOT_LEGACY
|
|
stage_entry_override = 1;
|
|
#endif
|
|
}
|
|
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
|
|
/* Load BL31 (ARM Trusted Firmware) to its DDR exec address. Its entry
|
|
* point is the FIT `load`/`entry` address returned here. Optional: if
|
|
* absent, fall through to the normal direct boot. */
|
|
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\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,
|
|
* not by the generic staging maximum: that is 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);
|
|
memcpy(dts_addr, dts_ptr, dts_size);
|
|
}
|
|
}
|
|
#ifdef WOLFBOOT_FIT_RAMDISK
|
|
if (ramdisk != NULL) {
|
|
fdt_ctx dts_ctx;
|
|
fdt_ctx* dts_for_initrd = NULL;
|
|
|
|
/* The relocated DTB sits in the staging window, so that is
|
|
* the capacity the initrd fixup may grow into. */
|
|
if (dts_addr != NULL &&
|
|
fdt_open(&dts_ctx, dts_addr, WOLFBOOT_DTS_MAX_SIZE) == 0) {
|
|
dts_for_initrd = &dts_ctx;
|
|
}
|
|
(void)fit_load_ramdisk(fit, ramdisk, dts_for_initrd);
|
|
}
|
|
#else
|
|
(void)ramdisk;
|
|
#endif
|
|
}
|
|
else {
|
|
/* Prefer the HAL's memory-mapped DTB (unchanged for XIP targets); fall
|
|
* back to external flash at WOLFBOOT_DTS_BOOT_ADDRESS when the HAL has
|
|
* no usable address (NULL, or a flash offset on NO_XIP targets). */
|
|
dts_addr = hal_get_dts_address();
|
|
if (dts_addr != NULL) {
|
|
ret = wolfBoot_get_dts_size(dts_addr, WOLFBOOT_DTS_MAX_SIZE);
|
|
if (ret < (int)WOLFBOOT_DTS_MIN_SIZE ||
|
|
(uint32_t)ret > WOLFBOOT_DTS_MAX_SIZE) {
|
|
wolfBoot_printf("DTB parse/size check failed - ignoring\n");
|
|
dts_addr = NULL; /* never forward an unvalidated address */
|
|
}
|
|
else {
|
|
dts_size = (uint32_t)ret;
|
|
memcpy((void*)WOLFBOOT_LOAD_DTS_ADDRESS, dts_addr, dts_size);
|
|
dts_addr = (uint8_t*)WOLFBOOT_LOAD_DTS_ADDRESS;
|
|
}
|
|
}
|
|
#if defined(EXT_FLASH) && defined(WOLFBOOT_DTS_BOOT_ADDRESS)
|
|
if (dts_addr == NULL) {
|
|
/* Peek the FDT header for the size, clamp it, then read the body.
|
|
* Each ext_flash_read length is checked so a short/failed read
|
|
* never yields a partial or oversized tree. */
|
|
ret = ext_flash_read((uintptr_t)WOLFBOOT_DTS_BOOT_ADDRESS,
|
|
dts_hdr, (int)sizeof(dts_hdr));
|
|
if (ret == (int)sizeof(dts_hdr)) {
|
|
uint32_t peeked = 0;
|
|
/* Only the header has been read so far; fdt_peek_size
|
|
* validates just that much and reports the size to
|
|
* fetch. The complete blob is validated below. */
|
|
ret = fdt_peek_size(dts_hdr, (uint32_t)sizeof(dts_hdr),
|
|
&peeked);
|
|
if (ret == 0) {
|
|
dts_size = peeked;
|
|
if (ext_flash_read((uintptr_t)WOLFBOOT_DTS_BOOT_ADDRESS,
|
|
(uint8_t*)WOLFBOOT_LOAD_DTS_ADDRESS, (int)dts_size)
|
|
== (int)dts_size &&
|
|
wolfBoot_get_dts_size(
|
|
(void*)WOLFBOOT_LOAD_DTS_ADDRESS, dts_size)
|
|
== (int)dts_size)
|
|
dts_addr = (uint8_t*)WOLFBOOT_LOAD_DTS_ADDRESS;
|
|
else
|
|
dts_size = 0;
|
|
}
|
|
}
|
|
}
|
|
#endif /* EXT_FLASH && WOLFBOOT_DTS_BOOT_ADDRESS */
|
|
|
|
/* Authenticate the raw DTB before boot. dts_size == 0 with a non-NULL
|
|
* address (e.g. a zeroed fdt totalsize) is rejected. A bound digest is
|
|
* always enforced; a missing one only panics under
|
|
* WOLFBOOT_REQUIRE_SIGNED_DTB, so unsigned raw-DTB targets keep booting
|
|
* until they adopt 'sign --dts'. */
|
|
if (dts_addr != NULL) {
|
|
if (dts_size == 0) {
|
|
wolfBoot_printf("DTB has zero size - rejecting\n");
|
|
wolfBoot_panic();
|
|
}
|
|
if (dts_digest_present == 1) {
|
|
if (wolfBoot_verify_dts_digest(dts_digest, dts_addr, dts_size)
|
|
!= 0) {
|
|
wolfBoot_printf("DTB digest mismatch - rejecting\n");
|
|
wolfBoot_panic();
|
|
}
|
|
wolfBoot_printf("DTB digest verified\n");
|
|
}
|
|
else if (dts_digest_present < 0) {
|
|
wolfBoot_printf("Malformed DTB digest TLV - rejecting\n");
|
|
wolfBoot_panic();
|
|
}
|
|
else {
|
|
#ifdef WOLFBOOT_REQUIRE_SIGNED_DTB
|
|
wolfBoot_printf("No DTB digest - rejecting\n");
|
|
wolfBoot_panic();
|
|
#else
|
|
wolfBoot_printf("Warning: DTB not authenticated (sign --dts)\n");
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
#endif /* MMU */
|
|
|
|
#ifdef WOLFBOOT_UBOOT_LEGACY
|
|
/* Enter the uImage at ih_ep. Skipped if a later stage (ELF/FIT) re-derived
|
|
* the load address, since that stage provides its own entry point. The
|
|
* flag is tracked explicitly rather than by comparing load_address:
|
|
* elf_load_image_mmu() publishes its entry point before it finishes
|
|
* validating, so a rejected ELF also leaves load_address rewritten. */
|
|
if ((uboot_entry != NULL) && !stage_entry_override) {
|
|
load_address = uboot_entry;
|
|
}
|
|
#endif
|
|
|
|
wolfBoot_printf("Booting at %p\n", 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\n");
|
|
wolfBoot_panic();
|
|
}
|
|
#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
|
|
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
|
|
if (bl31_entry != 0) {
|
|
/* Hand off to BL31 (resident EL3 monitor). BL31 starts the kernel
|
|
* (BL33) at EL2; the DTB is forwarded via PMU_GLOBAL scratch (see
|
|
* hal/zynqmp_atf.c). Does not return. */
|
|
wolfBoot_printf("Handing off to BL31 at %p (kernel %p)\n",
|
|
(void*)bl31_entry, (void*)load_address);
|
|
zynqmp_atf_handoff(bl31_entry, (uintptr_t)load_address,
|
|
(uintptr_t)dts_addr, ZYNQMP_ATF_EL2);
|
|
}
|
|
#endif
|
|
#if defined(MMU) || defined(WOLFBOOT_FDT)
|
|
/* Match the do_boot() signature condition in src/boot_riscv.c. */
|
|
do_boot((uint32_t*)load_address,
|
|
(uint32_t*)dts_addr);
|
|
#else
|
|
/* Use load_address instead of os_image.fw_base, which may have
|
|
* wrong base address */
|
|
do_boot((uint32_t*)load_address);
|
|
#endif
|
|
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
}
|