Merge pull request #892 from danielinux/fenrir-fixes-2026-09-15

Update_ram fallback fixes + fenrir fixes 2026 09 15
pull/898/merge
David Garske 2026-09-17 08:10:03 -07:00 committed by GitHub
commit 8bac8cc589
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 1383 additions and 151 deletions

View File

@ -3125,21 +3125,23 @@ int mpfs_ddr_init(unsigned int outer_retry)
DDRPHY_REG(PHY_TIP_CFG_PARAMS) = LIBERO_SETTING_TIP_CFG_PARAMS;
mb();
/* Step 9: Run training + post-training + MTC sanity, with retry on
* MTC failure.
/* Step 9: Run training + post-training write calibration, with retry on
* calibration failure.
*
* Why MTC is the retry trigger (not PHY_TRAINING_STATUS): when the
* manual ADDCMD training picks a marginal phase/dly that doesn't
* resolve into a usable DRAM alignment, train_stat sticks at 0x1
* (BCLK_SCLK only). But TIP keeps spinning in the background and
* eventually flips the WRLVL/RDGATE/DQ_DQS bits to read 0x1D, even
* though the alignment is bogus. An outer retry keyed on
* PHY_TRAINING_STATUS sees that bogus 0x1D and stops. MTC actually
* exercises the DDR controller -- it times out unambiguously when
* training was bad, and is the reliable signal.
* The reliability gate is WRCALIB (the per-lane write-cal sweep must
* calibrate all lanes), not PHY_TRAINING_STATUS: when the manual ADDCMD
* training picks a marginal phase/dly that doesn't resolve into a usable
* DRAM alignment, TIP's train_stat self-report can still read "complete"
* even though the write path is bad. Gating on train_stat alone let bad
* boots through and the 19 MB load then hard-failed every block.
*
* Empirical baseline: ~30% per-attempt training failure rate -> 5
* retries gives ~99.7% cumulative success rate.
* MTC sanity is a secondary gate, run only when TIP did not report full
* training (0x1C). When TIP does complete full training, MTC is skipped:
* the MTC engine has a separate DDRC-internal access issue and would just
* burn all retries and end in a WDT reset.
*
* Empirical baseline: ~30% per-attempt training failure rate; 3 inner x
* 6 outer retries (up to 18 attempts) covers it with margin.
*/
{
uint32_t train_retry = 0;

View File

@ -587,11 +587,14 @@ static void hal_shm_init(void)
static void hal_shm_status_set(ShmInfo_t* info, uint32_t status)
{
IPC_TASKS_SEND(USE_IPC_SEND) = 1;
if (info != NULL) {
info->magic = SHAREM_MEM_MAGIC;
info->status = status;
}
/* publish the fields before signaling: the peer reads them right
* after seeing the IPC event */
DSB();
IPC_TASKS_SEND(USE_IPC_SEND) = 1;
}
static uint32_t hal_shm_status_wait(ShmInfo_t* info, uint32_t status,
@ -616,6 +619,9 @@ static uint32_t hal_shm_status_wait(ShmInfo_t* info, uint32_t status,
}
/* clear event */
IPC_EVENTS_RECEIVE(USE_IPC_RECV) = 0;
/* the sender published the fields before the event: order this
* core's field reads after the event observation */
DSB();
/* if we got an event and "info" not provided, just return status to
* signal event occurred */
if (info == NULL) {

View File

@ -68,7 +68,7 @@
/* Assembly helpers */
#define DMB() __asm__ volatile ("dmb")
#define DSB() __asm__ volatile ("dsb")
#define DSB() __asm__ volatile ("dsb" ::: "memory")
#define ISB() __asm__ volatile ("isb")
#define NOP() __asm__ volatile ("nop")

View File

@ -134,7 +134,7 @@ static void spi_push_tx(unsigned int sel, unsigned int pcs, unsigned char data,
| SPI_PUSHR_PCS(pcs) | data;
}
/* Perform a SPI transaction. Set cont!=0 to not let CS go low after this*/
/* Perform a SPI transaction. Set cont!=0 to keep CS low (asserted) after this */
static void spi_transaction(unsigned int sel, unsigned int pcs,
const unsigned char *out, unsigned char *in, unsigned int size,
int cont)

View File

@ -336,6 +336,12 @@ enum elbc_amask_sizes {
#define NAND_CMD_READSTART 0x30 /* Extended command for large page devices */
/* NAND device status byte (ONFI): bit 0 set = program/erase failed,
* bit 7 clear = write protected. Success requires bit 0 clear and
* bit 7 set. */
#define NAND_STATUS_FAIL (1 << 0) /* DQ0: 1 = program/erase fail */
#define NAND_STATUS_WP_N (1 << 7) /* DQ7: 0 = write protected */
/* DDR */
/* DDR3: 512MB, 333.333 MHz (666.667 MT/s) */
@ -689,6 +695,15 @@ static int hal_flash_command(uint8_t iswrite)
if (!(ltesr & ELBC_LTESR_CC)) {
ret = -1;
}
else if (ltesr & ELBC_LTESR_FCT) {
/* a CW/RSW wait timed out: the device never became ready */
ret = -1;
}
else if (iswrite == 0 && (ltesr & ELBC_LTESR_PAR)) {
/* uncorrectable ECC error during the FCM read: the data in the
* FCM buffer cannot be trusted */
ret = -1;
}
/* clear interrupt */
set32(ELBC_LTESR, ltesr & ELBC_NAND_MASK);
@ -1063,18 +1078,18 @@ static void config_io_pin(uint8_t port, uint8_t pin, int dir, int open_drain,
pin_2bit_dir = (uint32_t)(dir << (NUM_OF_PINS -
(pin % (NUM_OF_PINS / 2) + 1) * 2));
/* Setup the direction */
/* Setup the direction: one masked store - a clear-then-set pair
* would drop a concurrent update to another pin in the same
* register */
tmp_val = (pin > (NUM_OF_PINS / 2) - 1) ?
get32(GUTS_CPDIR2(port)) :
get32(GUTS_CPDIR1(port));
if (pin > (NUM_OF_PINS / 2) - 1) {
set32(GUTS_CPDIR2(port), ~pin_2bit_mask & tmp_val);
set32(GUTS_CPDIR2(port), pin_2bit_dir | tmp_val);
set32(GUTS_CPDIR2(port), (~pin_2bit_mask & tmp_val) | pin_2bit_dir);
}
else {
set32(GUTS_CPDIR1(port), ~pin_2bit_mask & tmp_val);
set32(GUTS_CPDIR1(port), pin_2bit_dir | tmp_val);
set32(GUTS_CPDIR1(port), (~pin_2bit_mask & tmp_val) | pin_2bit_dir);
}
/* Calculate pin location for 1bit mask */
@ -1089,21 +1104,21 @@ static void config_io_pin(uint8_t port, uint8_t pin, int dir, int open_drain,
set32(GUTS_CPODR(port), ~pin_1bit_mask & tmp_val);
}
/* Setup the assignment */
/* Setup the assignment: one masked store (same reason as the
* direction write above) */
tmp_val = (pin > (NUM_OF_PINS/2) - 1) ?
get32(GUTS_CPPAR2(port)):
get32(GUTS_CPPAR1(port));
pin_2bit_assign = (uint32_t)(assign <<
(NUM_OF_PINS - (pin % (NUM_OF_PINS / 2) + 1) * 2));
/* Clear and set 2 bits mask */
if (pin > (NUM_OF_PINS/2) - 1) {
set32(GUTS_CPPAR2(port), ~pin_2bit_mask & tmp_val);
set32(GUTS_CPPAR2(port), pin_2bit_assign | tmp_val);
set32(GUTS_CPPAR2(port), (~pin_2bit_mask & tmp_val) |
pin_2bit_assign);
}
else {
set32(GUTS_CPPAR1(port), ~pin_2bit_mask & tmp_val);
set32(GUTS_CPPAR1(port), pin_2bit_assign | tmp_val);
set32(GUTS_CPPAR1(port), (~pin_2bit_mask & tmp_val) |
pin_2bit_assign);
}
}
@ -1641,13 +1656,19 @@ int ext_flash_write(uintptr_t address, const uint8_t *data, int len)
page_size = 512;
set32(ELBC_FCR, ELBC_FCR_CMD(0, NAND_CMD_READA) |
ELBC_FCR_CMD(1, NAND_CMD_PAGE_PROG2) |
ELBC_FCR_CMD(2, NAND_CMD_PAGE_PROG1));
ELBC_FCR_CMD(2, NAND_CMD_PAGE_PROG1) |
ELBC_FCR_CMD(3, NAND_CMD_STATUS));
/* the CM3+RSW pair issues the status command after the program
* execute and waits for it, so MDR holds the page status like the
* large page path */
set32(ELBC_FIR, ELBC_FIR_OP(0, ELBC_FIR_OP_CW0) |
ELBC_FIR_OP(1, ELBC_FIR_OP_CM2) |
ELBC_FIR_OP(2, ELBC_FIR_OP_CA) |
ELBC_FIR_OP(3, ELBC_FIR_OP_PA) |
ELBC_FIR_OP(4, ELBC_FIR_OP_WB) |
ELBC_FIR_OP(5, ELBC_FIR_OP_CW1));
ELBC_FIR_OP(5, ELBC_FIR_OP_CW1) |
ELBC_FIR_OP(6, ELBC_FIR_OP_CM3) |
ELBC_FIR_OP(7, ELBC_FIR_OP_RSW));
#endif
(void)block_size; /* not used - shown for reference */
@ -1690,7 +1711,13 @@ int ext_flash_write(uintptr_t address, const uint8_t *data, int len)
wolfBoot_printf("write page %d, col %d, status %x\n",
page, col, status);
#endif
(void)status;
/* DQ0 set = program failed, DQ7 clear = write protected: the
* page did not program. Stop; retrying the same page fails the
* same way. */
if ((status & NAND_STATUS_FAIL) || !(status & NAND_STATUS_WP_N)) {
ret = -1;
break;
}
address += write_size;
pos += write_size;
data += write_size;
@ -1851,7 +1878,12 @@ int ext_flash_erase(uintptr_t address, int len)
#ifdef DEBUG_EXT_FLASH
wolfBoot_printf("erase page %d, status %x\n", page, status);
#endif
(void)status;
/* DQ0 set = erase failed, DQ7 clear = write protected: the block
* did not erase. Stop; erasing the same block fails the same way. */
if ((status & NAND_STATUS_FAIL) || !(status & NAND_STATUS_WP_N)) {
ret = -1;
break;
}
address += block_size;
len -= block_size;
}

View File

@ -1703,8 +1703,8 @@ void hal_prepare_boot(void)
* profile when chasing VxWorks 7 64-bit silent boot:
* - DUART1 MCR = 3 (DTR+RTS asserted; U-Boot sets this, our driver
* leaves it at the post-reset 0)
* - TCR = 0x04000000 (matches U-Boot's leftover; wolfBoot was clearing
* it; VxWorks 7 BSP early code may inherit) */
* - TCR = 0 (matches CW U-Boot's pre-bootm value; a nonzero WRC would let
* the watchdog fire silently after VxWorks starts) */
void RAMFUNCTION hal_flash_cache_disable_pre_os(void)
{
hal_flash_cache_disable();

View File

@ -223,7 +223,7 @@ int pic32_flash_write(uint32_t address, const uint8_t *data, int len)
_addr = pic32_addr_dqword_align(address);
/* Setup an aligned buffer with the following rules:
* - For addresses outside the writing range: 0xFF (no change)
* - For addresses inside the writing range: data | !current_data
* - For addresses inside the writing range: data | ~current_data
*
* This approach ensures we only flip bits from 1 to 0 when writing
* without an erase operation. When the address is aligned and length

View File

@ -519,12 +519,13 @@ void hal_prepare_boot(void)
WDOG_CNT = WDOG_CNT_UNLOCK;
while (!(WDOG_CS & WDOG_CS_ULK)) {}
/* Enable watchdog with ~2 second timeout (256k ticks at 128kHz LPO)
* Application should either service or reconfigure the watchdog
*/
WDOG_TOVAL = 0xFFFF; /* Max timeout ~512ms without prescaler */
/* Enable watchdog with 65535 ticks at 128kHz LPO: ~512ms
* without the prescaler, ~131 seconds with the 1:256 prescaler
* (WDOG_CS_PRES). Application should either service or
* reconfigure the watchdog. */
WDOG_TOVAL = 0xFFFF;
WDOG_CS = WDOG_CS_EN | WDOG_CS_UPDATE | WDOG_CS_CMD32EN |
WDOG_CS_CLK_LPO | WDOG_CS_PRES; /* With prescaler: ~131 sec */
WDOG_CS_CLK_LPO | WDOG_CS_PRES;
/* Wait for reconfiguration to complete */
while (!(WDOG_CS & WDOG_CS_RCS)) {}

View File

@ -247,14 +247,15 @@ static int uds_from_uid(uint8_t *out, size_t out_len)
static int buffer_is_all_value(const uint8_t *buf, size_t len, uint8_t value)
{
volatile uint8_t diff = 0U;
size_t i;
/* Constant-time scan: the buffer holds the UDS, the DICE root
* secret, so the loop must not early-exit on a data-dependent byte. */
for (i = 0; i < len; i++) {
if (buf[i] != value) {
return 0;
}
diff |= (uint8_t)(buf[i] ^ value);
}
return 1;
return diff == 0;
}
int hal_uds_derive_key(uint8_t *out, size_t out_len)

View File

@ -133,10 +133,10 @@ extern "C" {
#define wolfBoot_verify_signature_primary wolfBoot_verify_signature_tpm
#endif
/* Validate sector size is larger than image header size */
/* Validate sector size is at least as large as the image header size */
#if defined(WOLFBOOT_SECTOR_SIZE) && defined(IMAGE_HEADER_SIZE) && \
(WOLFBOOT_SECTOR_SIZE < IMAGE_HEADER_SIZE)
#error WOLFBOOT_SECTOR_SIZE must be larger than IMAGE_HEADER_SIZE
#error WOLFBOOT_SECTOR_SIZE must be at least as large as IMAGE_HEADER_SIZE
#endif
@ -1748,6 +1748,11 @@ uint8_t* wolfBoot_peek_image(struct wolfBoot_image *img, uint32_t offset,
/* get header type for image */
uint16_t wolfBoot_get_header(struct wolfBoot_image *img, uint16_t type, uint8_t **ptr);
#ifdef EXT_FLASH
/* Drop the cached external image header so the next open reloads it. */
void RAMFUNCTION wolfBoot_invalidate_hdr_cache(void);
#endif
/* Find the key slot ID based on the SHA hash of the key. */
int keyslot_id_by_sha(const uint8_t *hint);

View File

@ -167,18 +167,17 @@ extern int tolower(int c);
#ifdef USE_FAST_MATH
/* WC_NO_HARDEN suits verify-only builds, which do public-key
* operations only. Software DICE (WOLFCRYPT_TZ_PSA without
* WOLFBOOT_DICE_HW) signs the attestation claims with the private
* IAK, so it is excluded; hardware DICE keeps signing in the crypto
* operations only. Secure-mode worlds (TZ_PSA/PKCS11/FWTPM/WOLFHSM)
* process private keys in software, so they keep the timing-
* resistant TFM path; hardware DICE keeps signing in the crypto
* engine and stays verify-only. */
# if !defined(WOLFCRYPT_TZ_PSA) || defined(WOLFBOOT_DICE_HW)
# define WC_NO_HARDEN
# if defined(WOLFCRYPT_SECURE_MODE) && !defined(WOLFBOOT_DICE_HW)
# define TFM_TIMING_RESISTANT
# else
/* tfm.c never tests WC_NO_HARDEN, so dropping it alone changes
* no code and only un-silences an advisory that -Werror turns
* into a build failure. TFM_TIMING_RESISTANT is what makes
* tfm.c constant time. */
# define TFM_TIMING_RESISTANT
* into a build failure. */
# define WC_NO_HARDEN
# endif
#endif

View File

@ -43,6 +43,15 @@ extern "C" {
#include "wolfboot/version.h"
#include "wolfboot/wc_secure.h"
/* Partition trailers (magic + state flags) are persisted in flash only when
* the target has fixed partitions or supplies a custom trailer backend.
* Without either, get/set_trailer_at() are no-op stubs and the
* wolfBoot_{get,set}_partition_state() API is absent, so the fallback
* decision must be made on version + image validity alone. */
#if defined(WOLFBOOT_FIXED_PARTITIONS) || defined(CUSTOM_PARTITION_TRAILER)
#define HAVE_PARTITION_TRAILERS 1
#endif
#ifndef RAMFUNCTION
# if defined(__WOLFBOOT) && defined(RAM_CODE)

View File

@ -925,6 +925,10 @@ static int32_t arm_tee_psa_ps_dispatch(int32_t type, const psa_invec *in_vec,
if (data_len > 0 && data == NULL) {
return PSA_ERROR_INVALID_ARGUMENT;
}
/* Scrub the previous value before overwriting: a SET that stores
* less data (or zero) must not leave the tail of the old object
* readable via GET. Runs only after every validation check. */
wc_ForceZero(entry->data, sizeof(entry->data));
if (data_len > 0) {
XMEMCPY(entry->data, data, data_len);
}

View File

@ -173,7 +173,7 @@ static void mpu_init(void)
mpu_setattr(6, MPUSIZE_1G | MPU_RASR_ENABLE | MPU_RASR_ATTR_S |
MPU_RASR_ATTR_B | MPU_RASR_ATTR_AP_PRW_UNO | MPU_RASR_ATTR_XN);
/* System control 0xE0000000:0xEFFFFFF */
/* System control 0xE0000000:0xEFFFFFFF (256M) */
mpu_setaddr(7, 0xE0000000);
mpu_setattr(7, MPUSIZE_256M | MPU_RASR_ENABLE | MPU_RASR_ATTR_S |
MPU_RASR_ATTR_B | MPU_RASR_ATTR_AP_PRW_UNO | MPU_RASR_ATTR_XN);

View File

@ -72,7 +72,7 @@ void RAMFUNCTION do_boot(const uint32_t *app_offset)
* removes the need for a separate LINUX_PAYLOAD switch per target.
*
* Without MMU there is no DTB to pass, so we fall back to a minimal
* handoff (all GPRs cleared) used by targets like sama5d3. */
* handoff (r0-r3 cleared) used by targets like sama5d3. */
#ifdef MMU
register const uint32_t *dts_in = dts_offset;
asm volatile (

View File

@ -147,11 +147,10 @@ static int get_top_address(uint64_t *top, struct efi_hob *hoblist)
* \brief Change the stack and invoke a function with the new stack.
*
* This function changes the stack to the specified 'new_stack' value and then
* calls the function pointed to by 'other_func', passing the 'ptr' parameter as an argument.
* calls the function pointed to by 'other_func'.
*
* \param new_stack The new stack address.
* \param other_func Pointer to the function to be invoked with the new stack.
* \param ptr Pointer to the parameter to be passed to the invoked function.
*/
static void change_stack_and_invoke(uint32_t new_stack,
void (*other_func)(void))

View File

@ -117,7 +117,8 @@ static int disk_open_mbr(struct disk_drive *drive, const uint8_t *mbr_sector)
* @param[in] drv The drive number to open (0 to `MAX_DISKS - 1`).
*
* @return The number of partitions found and initialized on success, or -1 if
* the drive cannot be opened or no valid GPT partition table is found.
* the drive cannot be opened or no valid partition table (GPT or MBR) is
* found.
*/
int disk_open(int drv)
{

View File

@ -776,7 +776,7 @@ static void wolfBoot_verify_signature_xmss(uint8_t key_slot,
/* Set the public key. */
ret = wc_XmssKey_ImportPubRaw(&xmss, pubkey, KEYSTORE_PUBKEY_SIZE);
if (ret != 0) {
/* Something is wrong with the pub key or LMS parameters. */
/* Something is wrong with the pub key or XMSS parameters. */
wolfBoot_printf("error: wc_XmssKey_ImportPubRaw" \
" returned %d\n", ret);
return;
@ -1090,6 +1090,18 @@ static uint8_t *fetch_hdr_cpy(struct wolfBoot_image *img)
return hdr_cpy;
}
/**
* @brief Invalidate the cached external image header.
*
* fetch_hdr_cpy() loads the header of the first image it sees and serves
* it to every later get_header() call. Call this before opening a
* different image so TLV lookups do not read the stale header.
*/
void RAMFUNCTION wolfBoot_invalidate_hdr_cache(void)
{
hdr_cpy_done = 0;
}
static uint16_t get_header_ext(struct wolfBoot_image *img, uint16_t type,
uint8_t **ptr)
{

View File

@ -657,7 +657,7 @@ static void RAMFUNCTION set_partition_magic(uint8_t part)
#ifdef WOLFBOOT_FIXED_PARTITIONS
#ifdef HAVE_PARTITION_TRAILERS
#ifdef __CCRX__
#pragma section FRAM
#endif
@ -685,6 +685,11 @@ static void RAMFUNCTION set_partition_state(uint8_t part, uint8_t val)
set_trailer_at(part, 1, val);
}
/* Update-sector flag helpers and the fixed-partition APIs below need the
* fixed partition addresses and wolfboot_magic_trail, which a
* CUSTOM_PARTITION_TRAILER / WOLFBOOT_NO_PARTITIONS build does not define.
* The partition state APIs above stay available to custom-trailer builds. */
#ifdef WOLFBOOT_FIXED_PARTITIONS
/**
* @brief Set the flags of an update sector.
*
@ -692,7 +697,6 @@ static void RAMFUNCTION set_partition_state(uint8_t part, uint8_t val)
*
* @param[in] pos Update sector position.
* @param[in] val New flags value to set.
* @return 0 on success, -1 on failure.
*/
static void RAMFUNCTION set_update_sector_flags(uint32_t pos, uint8_t val)
{
@ -711,6 +715,7 @@ static uint8_t* RAMFUNCTION get_update_sector_flags(uint32_t pos)
{
return (uint8_t *)get_trailer_at(PART_UPDATE, 2 + pos);
}
#endif /* WOLFBOOT_FIXED_PARTITIONS */
/**
* @brief Set the state of a partition.
@ -736,6 +741,7 @@ int RAMFUNCTION wolfBoot_set_partition_state(uint8_t part, uint8_t newst)
return 0;
}
#ifdef WOLFBOOT_FIXED_PARTITIONS
/**
* @brief Set the flag for sector
*
@ -766,6 +772,7 @@ int RAMFUNCTION wolfBoot_set_update_sector_flag(uint16_t sector,
set_update_sector_flags(pos, fl_value);
return 0;
}
#endif /* WOLFBOOT_FIXED_PARTITIONS */
/**
* @brief Get the state of a partition.
@ -790,6 +797,7 @@ int RAMFUNCTION wolfBoot_get_partition_state(uint8_t part, uint8_t *st)
return 0;
}
#ifdef WOLFBOOT_FIXED_PARTITIONS
/**
* @brief Get the flag for sector
*
@ -866,8 +874,8 @@ void RAMFUNCTION wolfBoot_erase_partition(uint8_t part)
/**
* @brief Update trigger function.
*
* This function updates the boot partition state to "IMG_STATE_UPDATING".
* If the FLAGS_HOME macro is defined, it erases the last sector of the boot
* This function sets the update partition state to "IMG_STATE_UPDATING".
* If the FLAGS_HOME macro is defined, it erases the last sector of the update
* partition before updating the partition state. It also checks FLAGS_UPDATE_EXT
* and calls the appropriate flash unlock and lock functions before
* updating the partition state.
@ -933,6 +941,8 @@ void RAMFUNCTION wolfBoot_update_trigger(void)
}
}
#endif /* WOLFBOOT_FIXED_PARTITIONS */
/**
* @brief Success function.
*
@ -960,7 +970,7 @@ void RAMFUNCTION wolfBoot_success(void)
#ifdef __CCRX__
#pragma section
#endif
#endif /* WOLFBOOT_FIXED_PARTITIONS */
#endif /* HAVE_PARTITION_TRAILERS */
#ifdef WOLFBOOT_PERSIST_FAILURE_STATUS
/* Persistent failure diagnostics.
@ -2369,6 +2379,8 @@ static int pkcs11_enc_initialized = 0, pkcs11_dec_initialized = 0;
static CK_AES_CTR_PARAMS pkcs11_params;
#endif
static void pkcs11_pin_wipe(void);
int pkcs11_crypto_init(void)
{
CK_RV ret = 0;
@ -2462,6 +2474,9 @@ int pkcs11_crypto_init(void)
if (pkcs11_initialized) {
pkcs11_function_list->C_Finalize(NULL);
}
/* terminal failure: the credential must not survive in retained
* memory (same reason as the deinit wipe) */
pkcs11_pin_wipe();
}
return ret;
@ -2831,8 +2846,8 @@ exit:
* @brief Read and decrypt data from an external flash.
*
* This function reads the encrypted data from the external flash,
* decrypts it using the AES decryption algorithm, and stores the decrypted data
* in the provided buffer.
* decrypts it using the configured decryption algorithm (ChaCha20, AES-CTR,
* or PKCS#11), and stores the decrypted data in the provided buffer.
* @param address The address in the external flash to read the encrypted data from.
* @param data Pointer to the buffer to store the decrypted data.
@ -2955,7 +2970,8 @@ typedef char wolfBoot_ramboot_blockalign_check[
/**
* @brief Decrypt data from RAM.
*
* This function decrypts data from the RAM using the AES decryption algorithm.
* This function decrypts data from the RAM using the configured decryption
* algorithm (ChaCha20, AES-CTR, or PKCS#11).
*
* @param src Pointer to the source buffer containing the encrypted data.
* @param dst Pointer to the destination buffer to store the decrypted data.

View File

@ -491,9 +491,9 @@ static uint32_t sdhci_set_clock(uint32_t clock_khz)
base_clk_khz = sdhci_platform_set_clock(clock_khz, base_clk_khz);
if (base_clk_khz == 0) {
/* No usable base clock. The SD clock was already disabled above, so
* the controller is left idle. NOTE: 0 is also what the "clock already
* set" path above returns, so callers cannot currently tell these
* apart - see the DEBUG_SDHCI log for which one happened. */
* the controller is left idle. This path returns 0 (error), unlike
* the "clock already set" path above which returns last_clock_khz,
* so a 0 return is unambiguously an error for callers. */
#ifdef DEBUG_SDHCI
wolfBoot_printf("sdhci_set_clock: no usable base clock "
"(CAPS and platform hook both 0)\n");
@ -1170,7 +1170,7 @@ static int emmc_send_op_cond(uint32_t ocr_arg, uint32_t *ocr_reg)
response = SDHCI_REG(SDHCI_SRS04);
/* Check if device is ready (busy bit cleared = ready) */
/* Check if device is ready (OCR bit 31 set = ready) */
if (response & MMC_OCR_BUSY_BIT) {
/* Device is ready */
if (ocr_reg != NULL) {

View File

@ -336,7 +336,7 @@ static int self_sha384(uint8_t *hash)
* TPM2_PCR_Extend. Optionally, if DEBUG_WOLFTPM or WOLFBOOT_DEBUG_TPM defined,
* prints debug info.
*
* @param[in] pcrIndex The PCR Index (0-24 is valid range).
* @param[in] pcrIndex The PCR Index (0-23 is valid range).
* @param[in] hash Pointer to the hash value to extend into the PCR.
* @param[in] line Line number where the function is called (for debugging).
* @return 0 on success, an error code on failure.

View File

@ -286,7 +286,7 @@ void RAMFUNCTION wolfBoot_start(void)
#endif
uint32_t *load_address = NULL;
uint32_t *source_address = NULL;
#ifdef WOLFBOOT_FIXED_PARTITIONS
#ifdef HAVE_PARTITION_TRAILERS
uint8_t p_state;
#endif
#if defined(MMU) || defined(WOLFBOOT_FDT)
@ -314,15 +314,15 @@ void RAMFUNCTION wolfBoot_start(void)
* 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 (;;) {
/* Each open needs fresh image state: wolfBoot_open_image_address()
* adopts load_address only when hdr is NULL, and the external
* header cache keeps the first image opened, so without this the
* fallback re-verifies the previous partition's header. */
memset(&os_image, 0, sizeof(struct wolfBoot_image));
#ifdef EXT_FLASH
wolfBoot_invalidate_hdr_cache();
#endif
#if defined(WOLFBOOT_DUALBOOT) && defined(WOLFBOOT_FIXED_PARTITIONS)
if (active < 0)
active = wolfBoot_dualboot_candidate();
@ -343,17 +343,6 @@ void RAMFUNCTION wolfBoot_start(void)
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);
@ -463,7 +452,7 @@ backup_on_failure:
/* First time we boot this update, set to TESTING to await
* confirmation from the system
*/
#ifdef WOLFBOOT_FIXED_PARTITIONS
#ifdef HAVE_PARTITION_TRAILERS
if ((wolfBoot_get_partition_state(active, &p_state) == 0) &&
(p_state == IMG_STATE_UPDATING))
{
@ -792,11 +781,10 @@ backup_on_failure:
#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. */
/* Enter the uImage at ih_ep. Skipped if a later stage (ELF/FIT) succeeded
* and re-derived the load address, since that stage provides its own
* entry point. Tracked with an explicit flag set on each stage's success
* path rather than by comparing load_address. */
if ((uboot_entry != NULL) && !stage_entry_override) {
load_address = uboot_entry;
}

View File

@ -61,9 +61,16 @@ struct ata_async_info{
int in_progress;
int drv;
int slot;
/* 1 when the in-flight async command carries a passphrase in the
* static DMA buffer and it must be scrubbed at completion. */
int scrub_buffer;
};
static struct ata_async_info ata_async_info;
#ifdef WOLFBOOT_ATA_DISK_LOCK
static void ata_security_buffer_zeroize(void);
#endif
/**
* @brief This structure holds the necessary information for an ATA drive,
* including AHCI base address, AHCI port number, and sector cache.
@ -286,21 +293,39 @@ int ata_cmd_complete_async()
{
struct ata_drive *ata;
int slot;
int ret;
if (!ata_async_info.in_progress)
return ATA_ERR_OP_NOT_IN_PROGRESS;
ata = &ATA_Drv[ata_async_info.drv];
slot = ata_async_info.slot;
if (mmio_read32(AHCI_PxIS(ata->ahci_base, ata->ahci_port)) & AHCI_PORT_IS_TFES) {
ata_async_info.in_progress = 0;
return -1;
/* Task-file error: verify the HBA has retired the command (PxCI
* clear) before scrubbing the DMA buffer it may still reference. */
if ((mmio_read32(AHCI_PxCI(ata->ahci_base, ata->ahci_port)) &
(1 << slot)) != 0)
return ATA_ERR_BUSY;
ret = -1;
goto done;
}
slot = ata_async_info.slot;
if ((mmio_read32(AHCI_PxCI(ata->ahci_base, ata->ahci_port)) & (1 << slot)) != 0)
if ((mmio_read32(AHCI_PxCI(ata->ahci_base, ata->ahci_port)) &
(1 << slot)) != 0)
return ATA_ERR_BUSY;
ret = 0;
done:
/* The HBA has retired the command (success or task-file error), so
* the static DMA buffer is no longer in flight: scrub the passphrase
* it carried before it can be read back from SRAM. */
ata_async_info.in_progress = 0;
return 0;
#ifdef WOLFBOOT_ATA_DISK_LOCK
if (ata_async_info.scrub_buffer) {
ata_async_info.scrub_buffer = 0;
ata_security_buffer_zeroize();
}
#endif
return ret;
}
/**
@ -472,7 +497,14 @@ static int security_command_passphrase(int drv, uint8_t ata_cmd,
struct ata_drive *ata = &ATA_Drv[drv];
size_t passphrase_len = 0;
int ret;
int slot = prepare_cmd_h2d_slot(drv, buffer,
int slot;
/* A second security command must not touch the shared buffer while
* an async transfer is in flight: prepare_cmd_h2d_slot() and the
* memcpy below would clobber the passphrase still being DMA'd. */
if (ata_async_info.in_progress)
return ATA_ERR_OP_IN_PROGRESS;
slot = prepare_cmd_h2d_slot(drv, buffer,
ATA_SECURITY_COMMAND_LEN, 1);
memset(buffer, 0, ATA_SECURITY_COMMAND_LEN);
if (master)
@ -502,8 +534,12 @@ static int security_command_passphrase(int drv, uint8_t ata_cmd,
* may still be in flight when we return (the caller polls completion
* via ata_cmd_complete_async()), so clearing the buffer now would race
* the HBA and could corrupt the command still in progress. */
if (!async)
if (!async) {
ata_security_buffer_zeroize();
} else {
/* Command is in flight: scrub once the HBA retires it. */
ata_async_info.scrub_buffer = 1;
}
return ret;
}

View File

@ -109,6 +109,15 @@ static int exportPubKey = 0;
static WC_RNG rng;
static int noLocalKeys = 0;
/* Exit after the RNG has been initialised: free the DRBG state first so
* it is not left resident in process memory, then terminate. */
static void keygen_die(int code)
{
wc_FreeRng(&rng);
wc_ForceZero(&rng, sizeof(rng));
exit(code);
}
/* ML-DSA pub keys are big. */
#define KEYSLOT_MAX_PUBKEY_SIZE ML_DSA_L5_PUBKEY_SIZE
@ -567,7 +576,7 @@ static void keygen_rsa(const char *keyfile, int kbits, uint32_t id_mask,
ret = wc_InitRsaKey(&k, NULL);
if (ret != 0) {
fprintf(stderr, "Unable to initialize RSA%d key\n", kbits);
exit(1);
keygen_die(1);
}
rsa_init = 1;
@ -616,7 +625,7 @@ cleanup:
wc_FreeRsaKey(&k);
wc_ForceZero(&k, sizeof(k));
if (exit_code != 0)
exit(exit_code);
keygen_die(exit_code);
}
#define MAX_ECC_KEY_SIZE 66
@ -731,7 +740,7 @@ cleanup:
wc_ForceZero(priv_der, sizeof(priv_der));
if (exit_code != 0)
exit(exit_code);
keygen_die(exit_code);
memcpy(k_buffer, Qx, ecc_key_size);
memcpy(k_buffer + ecc_key_size, Qy, ecc_key_size);
@ -803,7 +812,7 @@ cleanup:
wc_ed25519_free(&k);
wc_ForceZero(&k, sizeof(k));
if (exit_code != 0)
exit(exit_code);
keygen_die(exit_code);
}
static void keygen_ed448(const char *privkey, uint32_t id_mask)
@ -864,7 +873,7 @@ cleanup:
wc_ed448_free(&k);
wc_ForceZero(&k, sizeof(k));
if (exit_code != 0)
exit(exit_code);
keygen_die(exit_code);
}
#include "../lms/lms_common.h"
@ -986,7 +995,7 @@ cleanup:
wc_ForceZero(&key, sizeof(key));
}
if (exit_code)
exit(exit_code);
keygen_die(exit_code);
}
#include "../xmss/xmss_common.h"
@ -1105,7 +1114,7 @@ cleanup:
wc_ForceZero(&key, sizeof(key));
}
if (exit_code)
exit(exit_code);
keygen_die(exit_code);
}
@ -1316,7 +1325,7 @@ cleanup:
priv = NULL;
}
if (exit_code != 0)
exit(exit_code);
keygen_die(exit_code);
}
static void key_gen_check(const char *kfilename)

View File

@ -1218,13 +1218,15 @@ static int sign_digest(int sign, int hash_algo,
mgf = WC_MGF1SHA384;
} else {
fprintf(stderr, "RSA-PSS requires SHA-256 or SHA-384\n");
return -1;
ret = -1;
}
ret = wc_RsaPSS_Sign(digest, digest_sz, signature, *signature_sz,
hash_type, mgf, &k->rsa, &rng);
if (ret > 0) {
*signature_sz = ret;
ret = 0;
if (ret == 0) {
ret = wc_RsaPSS_Sign(digest, digest_sz, signature, *signature_sz,
hash_type, mgf, &k->rsa, &rng);
if (ret > 0) {
*signature_sz = ret;
ret = 0;
}
}
}
else

View File

@ -204,6 +204,11 @@ exit:
wolfTPM2_UnloadHandle(&dev, &tpmSession.handle);
wolfTPM2_Cleanup(&dev);
/* Scrub the NV auth copy and the session state from the stack on
* every exit path. */
wc_ForceZero(&nv, sizeof(nv));
wc_ForceZero(&tpmSession, sizeof(tpmSession));
return rc;
}

View File

@ -41,6 +41,10 @@ CFLAGS+=-ftest-coverage
CFLAGS+=--coverage
CFLAGS+=-DUNIT_TEST_COVERAGE
CFLAGS+=-DUNIT_TEST -DWOLFSSL_USER_SETTINGS
# Pin the standard: GCC 14+ defaults to gnu23, where glibc string.h defines
# memchr/memcpy as _Generic macros that clash with wolfBoot's own declarations
# in tests that #include a .c file. gnu17 matches the CI toolchain default.
CFLAGS+=-std=gnu17
LDFLAGS+=-fprofile-arcs
LDFLAGS+=-ftest-coverage
@ -62,7 +66,7 @@ TESTS:=unit-parser unit-parser-large-header unit-fdt unit-extflash unit-string \
unit-enc-nvm-flagshome unit-delta unit-gzip unit-update-flash unit-update-flash-delta \
unit-update-flash-hook \
unit-update-flash-self-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-noramboot unit-update-flash-hwswap unit-pkcs11_store unit-psa_store unit-wolfhsm_flash_hal unit-disk \
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-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 \
@ -336,6 +340,37 @@ unit-update-ram-nofixed:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN \
-DWOLFBOOT_RAMBOOT_MAX_SIZE=WOLFBOOT_PARTITION_SIZE \
-DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT \
-DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE
# F-13604: same non-fixed-partition layout as unit-update-ram-nofixed but
# without NO_XIP, so WOLFBOOT_USE_RAMBOOT stays off and
# wolfBoot_open_image_address() runs with a varying load_address on every
# retry iteration (the A/B fallback path that must re-open the second
# partition instead of re-verifying the stale header of the first).
unit-update-ram-nofixed-noramboot:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN \
-DUNIT_TEST_AUTH -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH \
-DPART_UPDATE_EXT -DPART_SWAP_EXT -DPART_BOOT_EXT -DWOLFBOOT_DUALBOOT \
-DWOLFBOOT_NO_PARTITIONS -DUNIT_TEST_NO_FIXED_PARTITIONS \
-DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT \
-DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE
# CUSTOM_PARTITION_TRAILER: covers the HAVE_PARTITION_TRAILERS path where
# partition state is managed via externally-defined get/set_trailer_at.
unit-update-ram-custom-trailer:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN \
-DUNIT_TEST_AUTH -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH \
-DPART_UPDATE_EXT -DPART_SWAP_EXT -DPART_BOOT_EXT -DWOLFBOOT_DUALBOOT \
-DWOLFBOOT_FIXED_PARTITIONS \
-DCUSTOM_PARTITION_TRAILER \
-DWOLFBOOT_RAMBOOT_MAX_SIZE=WOLFBOOT_PARTITION_SIZE \
-DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT \
-DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE
# CUSTOM_PARTITION_TRAILER + WOLFBOOT_NO_PARTITIONS: no WOLFBOOT_FIXED_PARTITIONS,
# so wolfboot_magic_trail and the fixed partition addresses are excluded from
# libwolfboot.c. Proves the partition state API compiles and works through the
# custom get/set_trailer_at backend without fixed partitions (F-1130279967).
unit-custom-trailer-nopart:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN \
-DUNIT_TEST_AUTH -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED \
-DWOLFBOOT_NO_PARTITIONS \
-DCUSTOM_PARTITION_TRAILER \
-DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT \
-DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE
# Bound the non-FSP disk load to this test's 64-byte load_buffer (TEST_PAYLOAD_SIZE),
# the cap update_disk.c now requires; all images here are exactly that size.
unit-update-disk:CFLAGS+=-DMOCK_PARTITIONS -DPRINTF_ENABLED -DWOLFBOOT_RAMBOOT_MAX_SIZE=0x40 \
@ -912,9 +947,18 @@ unit-update-ram-enc-nopart: ../../include/target.h unit-update-ram-enc.c
unit-update-ram-nofixed: ../../include/target.h unit-update-ram-nofixed.c
gcc -o $@ unit-update-ram-nofixed.c ../../src/image.c $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS)
unit-update-ram-nofixed-noramboot: ../../include/target.h unit-update-ram-nofixed-noramboot.c
gcc -o $@ unit-update-ram-nofixed-noramboot.c ../../src/image.c $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS)
unit-update-ram-noramboot: ../../include/target.h unit-update-ram-noramboot.c
gcc -o $@ unit-update-ram-noramboot.c ../../src/image.c $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS)
unit-update-ram-custom-trailer: ../../include/target.h unit-update-ram-custom-trailer.c
gcc -o $@ unit-update-ram-custom-trailer.c ../../src/image.c $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS)
unit-custom-trailer-nopart: ../../include/target.h unit-custom-trailer-nopart.c
gcc -o $@ unit-custom-trailer-nopart.c ../../src/image.c $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS)
unit-update-flash-hwswap: ../../include/target.h unit-update-flash-hwswap.c
gcc -o $@ unit-update-flash-hwswap.c ../../src/image.c $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS)
@ -1318,6 +1362,8 @@ p1021_erase_extract.h: ../../hal/nxp_p1021.c
sed -n '/#define NAND_CMD_BLOCK_ERASE1 /p' $< >> $@
sed -n '/#define NAND_CMD_BLOCK_ERASE2 /p' $< >> $@
sed -n '/#define FLASH_PAGE_SIZE /p' $< >> $@
sed -n '/#define NAND_STATUS_WP_N /p' $< >> $@
sed -n '/#define NAND_STATUS_FAIL /p' $< >> $@
p1021_erase_fn_extract.h: ../../hal/nxp_p1021.c
sed -n '/^int ext_flash_erase/,/^}/p' $< > $@

View File

@ -12,6 +12,7 @@
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <x86/ahci.h>
#include <x86/ata.h>
#define WOLFBOOT_ATA_DISK_LOCK
@ -25,10 +26,18 @@
* slot" error path. */
static int mock_slots_full;
/* When set, port IS reads report a task-file error so
* ata_cmd_complete_async() takes its error exit. */
static int mock_tfes;
uint32_t mmio_read32(uintptr_t address)
{
(void)address;
return mock_slots_full ? 0xFFFFFFFF : 0;
if (mock_slots_full)
return 0xFFFFFFFF;
if (mock_tfes)
return AHCI_PORT_IS_TFES;
return 0;
}
void mmio_write32(uintptr_t address, uint32_t value)
@ -54,6 +63,7 @@ static uint8_t *ctable_mem;
static void setup(void)
{
mock_slots_full = 0;
mock_tfes = 0;
clb_mem = mmap(NULL, sizeof(struct hba_cmd_header) * 32,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
@ -120,6 +130,45 @@ START_TEST(test_unlock_zeroizes_passphrase_on_no_free_slot)
}
END_TEST
START_TEST(test_erase_unit_async_zeroizes_on_completion)
{
static const char passphrase[] = "unit-test-disk-secret";
volatile uint8_t *pw =
(volatile uint8_t *)buffer + ATA_SECURITY_PASSWORD_OFFSET;
int r;
int i;
r = ata_security_erase_unit(0, passphrase, 0);
ck_assert_int_eq(r, ATA_ERR_BUSY);
/* Command is in flight: scrubbing now would race the HBA DMA, so
* the passphrase must still be present until completion. */
for (i = 0; i < (int)strlen(passphrase); i++)
ck_assert_uint_eq(pw[i], (uint8_t)passphrase[i]);
r = ata_cmd_complete_async();
ck_assert_int_eq(r, 0);
assert_password_field_zero("after async SECURITY ERASE UNIT completion");
}
END_TEST
START_TEST(test_erase_unit_async_zeroizes_on_port_error)
{
static const char passphrase[] = "unit-test-disk-secret";
int r;
r = ata_security_erase_unit(0, passphrase, 0);
ck_assert_int_eq(r, ATA_ERR_BUSY);
mock_tfes = 1;
r = ata_cmd_complete_async();
ck_assert_int_eq(r, -1);
assert_password_field_zero("after async port-error completion");
}
END_TEST
static Suite *ata_security_passphrase_zeroize_suite(void)
{
Suite *s = suite_create("ata_security_passphrase_zeroize");
@ -127,6 +176,8 @@ static Suite *ata_security_passphrase_zeroize_suite(void)
tcase_add_checked_fixture(tc, setup, teardown);
tcase_add_test(tc, test_unlock_zeroizes_passphrase_after_command_completes);
tcase_add_test(tc, test_unlock_zeroizes_passphrase_on_no_free_slot);
tcase_add_test(tc, test_erase_unit_async_zeroizes_on_completion);
tcase_add_test(tc, test_erase_unit_async_zeroizes_on_port_error);
suite_add_tcase(s, tc);
return s;
}

View File

@ -0,0 +1,134 @@
/* unit-custom-trailer-nopart.c
*
* Compile + behaviour gate for the CUSTOM_PARTITION_TRAILER /
* WOLFBOOT_NO_PARTITIONS configuration: no WOLFBOOT_FIXED_PARTITIONS, so
* wolfboot_magic_trail and the fixed partition addresses are excluded from
* libwolfboot.c. The partition state API must still compile and work through
* the externally-defined get/set_trailer_at backend, while the
* fixed-partition functions (sector flags, erase, trigger, success) are
* absent.
*/
#ifndef WOLFBOOT_HASH_SHA256
#define WOLFBOOT_HASH_SHA256
#endif
#define NO_FORK 1
#include <check.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include "target.h"
#include "user_settings.h"
#include "wolfboot/wolfboot.h"
/* Custom partition trailer backend (mocked).
* Layout per partition: [state(1)][magic(4)]
* get_trailer_at(part, 0) -> magic (base+1 .. base+4)
* get_trailer_at(part, 1) -> state (base+0)
*/
static uint8_t mock_trailer_boot[5];
static uint8_t mock_trailer_update[5];
uint8_t* get_trailer_at(uint8_t part, uint32_t at)
{
uint8_t *base = (part == PART_BOOT) ? mock_trailer_boot
: mock_trailer_update;
if (at == 0)
return &base[1];
return &base[at - 1];
}
void set_trailer_at(uint8_t part, uint32_t at, uint8_t val)
{
uint8_t *base = (part == PART_BOOT) ? mock_trailer_boot
: mock_trailer_update;
if (at == 0)
base[1] = val;
else
base[at - 1] = val;
}
void set_partition_magic(uint8_t part)
{
uint8_t *base = (part == PART_BOOT) ? mock_trailer_boot
: mock_trailer_update;
/* WOLFBOOT_MAGIC_TRAIL = 0x544F4F42 on LE: 'B','O','O','T' */
base[1] = 'B';
base[2] = 'O';
base[3] = 'O';
base[4] = 'T';
(void)part;
}
#include "libwolfboot.c"
#include "unit-mock-flash.c"
static void reset_trailers(void)
{
memset(mock_trailer_boot, 0, sizeof(mock_trailer_boot));
memset(mock_trailer_update, 0, sizeof(mock_trailer_update));
}
/* State API round-trips through the custom backend with no fixed
* partitions present. */
START_TEST(test_set_get_partition_state)
{
uint8_t st = 0;
reset_trailers();
/* No magic yet: set writes the magic then the state. */
ck_assert_int_eq(wolfBoot_set_partition_state(PART_BOOT,
IMG_STATE_TESTING), 0);
ck_assert_int_eq(wolfBoot_get_partition_state(PART_BOOT, &st), 0);
ck_assert_uint_eq(st, IMG_STATE_TESTING);
/* Update partition is independent. */
ck_assert_int_eq(wolfBoot_set_partition_state(PART_UPDATE,
IMG_STATE_UPDATING), 0);
ck_assert_int_eq(wolfBoot_get_partition_state(PART_UPDATE, &st), 0);
ck_assert_uint_eq(st, IMG_STATE_UPDATING);
/* Writing UPDATE must not clobber BOOT: re-read BOOT and confirm it
* still holds TESTING, not the UPDATE value. */
ck_assert_int_eq(wolfBoot_get_partition_state(PART_BOOT, &st), 0);
ck_assert_uint_eq(st, IMG_STATE_TESTING);
/* PART_NONE is rejected. */
ck_assert_int_eq(wolfBoot_set_partition_state(PART_NONE, 0), -1);
ck_assert_int_eq(wolfBoot_get_partition_state(PART_NONE, &st), -1);
/* get on a partition without magic is rejected. */
reset_trailers();
ck_assert_int_eq(wolfBoot_get_partition_state(PART_BOOT, &st), -1);
}
END_TEST
int main(int argc, char *argv[])
{
int failed;
Suite *s;
TCase *tc;
SRunner *sr;
s = suite_create("custom-trailer-nopart");
tc = tcase_create("state-api");
tcase_add_checked_fixture(tc, reset_trailers, NULL);
tcase_add_test(tc, test_set_get_partition_state);
suite_add_tcase(s, tc);
sr = srunner_create(s);
srunner_set_fork_status(sr, CK_NOFORK);
srunner_run_all(sr, CK_NORMAL);
failed = srunner_ntests_failed(sr);
srunner_free(sr);
(void)argc;
(void)argv;
return (failed == 0) ? 0 : 1;
}

View File

@ -7,6 +7,8 @@
static const char *mock_xmss_param;
static int mock_exit_code;
static jmp_buf mock_exit_env;
static int mock_force_zero_count;
static int mock_free_rng_count;
static void mock_exit(int code);
@ -22,8 +24,10 @@ static void mock_exit(int code);
#define wc_XmssKey_ExportPubRaw mock_wc_XmssKey_ExportPubRaw
#define wc_XmssKey_Free mock_wc_XmssKey_Free
#define wc_ForceZero mock_wc_ForceZero
#define wc_FreeRng mock_wc_FreeRng
#include "../keytools/keygen.c"
#undef wc_ForceZero
#undef wc_FreeRng
#undef wc_XmssKey_Free
#undef wc_XmssKey_ExportPubRaw
#undef wc_XmssKey_GetPrivLen
@ -115,12 +119,22 @@ void mock_wc_ForceZero(void *mem, size_t len)
{
(void)mem;
(void)len;
mock_force_zero_count++;
}
int mock_wc_FreeRng(WC_RNG *rng)
{
(void)rng;
mock_free_rng_count++;
return 0;
}
static void setup(void)
{
mock_xmss_param = NULL;
mock_exit_code = 0;
mock_force_zero_count = 0;
mock_free_rng_count = 0;
unsetenv("XMSS_PARAMS");
}
@ -140,6 +154,11 @@ static void run_keygen_xmss(void)
ck_assert_int_eq(jumped, 1);
ck_assert_int_eq(mock_exit_code, 1);
/* The RNG must be freed (wc_FreeRng) and zeroized (wc_ForceZero) after
* key generation (F-12883). keygen_die() frees+zeroizes the RNG; the
* key is also zeroized in the caller's cleanup. */
ck_assert_int_ge(mock_force_zero_count, 1);
ck_assert_int_eq(mock_free_rng_count, 1);
}
START_TEST(test_keygen_xmss_uses_env_param_when_set)

View File

@ -351,6 +351,11 @@ int ext_flash_write(uintptr_t address, const uint8_t *data, int len)
int mock_ext_flash_short_len = 0;
int mock_ext_flash_short_bytes = 0;
/* Records the source address of the largest ext_flash_read() call (the
* image load to RAM), so a test can verify which partition was booted. */
uintptr_t mock_max_read_addr = 0;
int mock_max_read_len = 0;
int ext_flash_read(uintptr_t address, uint8_t *data, int len)
{
int i;
@ -359,6 +364,10 @@ int ext_flash_read(uintptr_t address, uint8_t *data, int len)
if (mock_ext_flash_short_len == len && mock_ext_flash_short_bytes > 0)
ret = len - mock_ext_flash_short_bytes;
if (ret > mock_max_read_len) {
mock_max_read_len = ret;
mock_max_read_addr = address;
}
for (i = 0; i < ret; i++) {
data[i] = a[i];
}

View File

@ -45,12 +45,21 @@ static int g_pages[MAX_TRACKED_PAGES];
static int g_page_calls;
static int g_cmd_calls;
static int g_cmd_ret;
static uint32_t g_status;
static void mock_reset(int cmd_ret)
/* ONFI status byte: DQ0 set = program/erase fail, DQ7 clear = protected.
* 0x80 is a clean success (no fail, not protected); 0x81 is an
* erase/program failure (DQ0 set); 0x00 is write-protected (DQ7 clear). */
#define STATUS_OK 0x80
#define STATUS_ERASE_FAIL 0x81
#define STATUS_WP_PROTECTED 0x00
static void mock_reset(int cmd_ret, uint32_t status)
{
g_page_calls = 0;
g_cmd_calls = 0;
g_cmd_ret = cmd_ret;
g_status = status;
}
static void hal_flash_set_addr(int page, int col)
@ -81,7 +90,7 @@ static void set32(volatile unsigned int *addr, unsigned int val)
static uint32_t get32(volatile unsigned int *addr)
{
(void)addr;
return 0; /* MDR status: no error */
return g_status; /* MDR: the NAND status byte */
}
/* The real ext_flash_erase() from hal/nxp_p1021.c (extracted). */
@ -96,7 +105,7 @@ START_TEST (test_erase_advances_through_blocks)
{
int ret;
mock_reset(0);
mock_reset(0, STATUS_OK);
ret = ext_flash_erase(0, 2 * (int)TEST_BLOCK_SIZE);
@ -112,7 +121,40 @@ START_TEST (test_erase_stops_on_command_error)
{
int ret;
mock_reset(-1);
mock_reset(-1, STATUS_OK);
ret = ext_flash_erase(0, 2 * (int)TEST_BLOCK_SIZE);
ck_assert_int_eq(ret, -1);
ck_assert_int_eq(g_page_calls, 1);
ck_assert_int_eq(g_pages[0], 0);
}
END_TEST
START_TEST (test_erase_stops_on_status_fail)
{
int ret;
/* The command sequence completes (cmd_ret 0) but the NAND reports an
* erase failure in the status byte (DQ0 set). */
mock_reset(0, STATUS_ERASE_FAIL);
ret = ext_flash_erase(0, 2 * (int)TEST_BLOCK_SIZE);
ck_assert_int_eq(ret, -1);
ck_assert_int_eq(g_page_calls, 1);
ck_assert_int_eq(g_pages[0], 0);
}
END_TEST
START_TEST (test_erase_stops_on_write_protected)
{
int ret;
/* The command sequence completes (cmd_ret 0) but the NAND reports the
* block as write-protected in the status byte (DQ7 clear). The erase
* must fail and not advance to the next block. */
mock_reset(0, STATUS_WP_PROTECTED);
ret = ext_flash_erase(0, 2 * (int)TEST_BLOCK_SIZE);
@ -129,6 +171,8 @@ Suite *p1021_erase_suite(void)
tcase_add_test(tc, test_erase_advances_through_blocks);
tcase_add_test(tc, test_erase_stops_on_command_error);
tcase_add_test(tc, test_erase_stops_on_status_fail);
tcase_add_test(tc, test_erase_stops_on_write_protected);
tcase_set_timeout(tc, 10);
suite_add_tcase(s, tc);
return s;

View File

@ -220,6 +220,8 @@ static void setup(void)
flash_idx = 0;
g_fbcr_n = 0;
set32(ELBC_LTESR, ELBC_LTESR_CC); /* FCM commands complete instantly */
/* MDR: DQ0 clear = no fail, DQ7 set = not write protected */
set32(ELBC_MDR, 0x80);
}
static void teardown(void)
@ -457,6 +459,52 @@ START_TEST(test_p1021_write_unaligned)
}
END_TEST
/* A program that reports failure in the status byte (DQ0 set) must stop:
* the first page is the last programmed, the loop does not advance to the
* next page. setup() pins MDR to 0x80 (success), so override it here. */
START_TEST(test_p1021_write_status_fail)
{
uint8_t data[2 * 1024];
size_t i;
fill(data, sizeof(data), 0x50);
/* DQ0 set = program failed, DQ7 set = not protected. */
set32(ELBC_MDR, 0x81);
ck_assert_int_eq(ext_flash_write(0, data, 600), -1);
/* Only the first page was programmed; the loop stopped before page 1. */
ck_assert_int_eq(g_fbcr_n, 1);
ck_assert_uint_eq(g_fbcr_log[0], 0);
for (i = 0; i < 512; i++)
ck_assert_uint_eq(g_nand[i], data[i]);
for (i = 0; i < 512; i++)
ck_assert_uint_eq(NAND(1, i), 0xFF);
}
END_TEST
/* A program on a write-protected block (DQ7 clear) must stop the same way:
* no later page is programmed. */
START_TEST(test_p1021_write_write_protected)
{
uint8_t data[2 * 1024];
size_t i;
fill(data, sizeof(data), 0x60);
/* DQ0 clear = no fail, DQ7 clear = write protected. */
set32(ELBC_MDR, 0x00);
ck_assert_int_eq(ext_flash_write(0, data, 600), -1);
ck_assert_int_eq(g_fbcr_n, 1);
ck_assert_uint_eq(g_fbcr_log[0], 0);
for (i = 0; i < 512; i++)
ck_assert_uint_eq(g_nand[i], data[i]);
for (i = 0; i < 512; i++)
ck_assert_uint_eq(NAND(1, i), 0xFF);
}
END_TEST
/* A full-page read from column 0 must keep BC = 0 (full page + spare,
* the only ECC-checking setting). */
START_TEST(test_p1021_read_full_page)
@ -571,6 +619,8 @@ Suite *p1021_fcm_suite(void)
tcase_add_test(tc, test_p1021_write_partial);
tcase_add_test(tc, test_p1021_write_multipart);
tcase_add_test(tc, test_p1021_write_unaligned);
tcase_add_test(tc, test_p1021_write_status_fail);
tcase_add_test(tc, test_p1021_write_write_protected);
tcase_add_test(tc, test_p1021_read_full_page);
tcase_add_test(tc, test_p1021_read_short_spare_loaded);
tcase_add_test(tc, test_p1021_read_multipart);

View File

@ -1,6 +1,7 @@
/* unit-pkcs11-pin-zeroize.c
*
* Unit test for the PKCS#11 login credential lifetime (F-12114).
* Unit test for the PKCS#11 login credential lifetime (F-12114,
* F-12942).
*
* pkcs11_pin is a file-scope copy of the credential supplied to
* C_Login() for the token holding the firmware-decryption key.
@ -58,6 +59,7 @@ static uint8_t test_encrypt_key[ENCRYPT_PKCS11_KEY_ID_SIZE +
/* ---- PKCS#11 stubs ---- */
static int stub_close_session_calls;
static int stub_login_fail;
static CK_RV stub_C_Initialize(CK_VOID_PTR pInitArgs)
{
@ -98,6 +100,9 @@ static CK_RV stub_C_Login(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType,
(void)userType;
(void)pPin;
(void)ulPinLen;
if (stub_login_fail) {
return CKR_PIN_INCORRECT;
}
return CKR_OK;
}
@ -171,6 +176,7 @@ void panic(void)
static void reset_stub_state(void)
{
stub_close_session_calls = 0;
stub_login_fail = 0;
}
/* F-12114: the pre-handoff deinitializer must erase the PKCS#11
@ -223,6 +229,32 @@ START_TEST(test_pkcs11_deinit_no_session)
}
END_TEST
/* F-12942: a terminal initialization failure after C_Login() was
* attempted (login rejected) tears the session down and must also
* erase the credential copy: the bootloader memory is retained
* after the handoff, as on the deinit path. */
START_TEST(test_pkcs11_pin_wiped_on_init_failure)
{
int ret;
size_t i;
reset_stub_state();
encrypt_initialized = 0;
memcpy(pkcs11_pin, ENCRYPT_PKCS11_PIN, sizeof(ENCRYPT_PKCS11_PIN));
stub_login_fail = 1;
ret = pkcs11_crypto_init();
stub_login_fail = 0;
ck_assert_int_eq(ret, CKR_PIN_INCORRECT);
ck_assert_int_eq(encrypt_initialized, 0);
for (i = 0; i < sizeof(pkcs11_pin); i++) {
ck_assert_msg(pkcs11_pin[i] == 0,
"pkcs11_pin byte %zu not wiped", i);
}
}
END_TEST
Suite *wolfboot_suite(void)
{
Suite *s = suite_create("wolfboot-pkcs11-pin");
@ -230,6 +262,7 @@ Suite *wolfboot_suite(void)
tcase_add_test(tc, test_pkcs11_pin_wiped_on_deinit);
tcase_add_test(tc, test_pkcs11_deinit_no_session);
tcase_add_test(tc, test_pkcs11_pin_wiped_on_init_failure);
suite_add_tcase(s, tc);
return s;
}

View File

@ -0,0 +1,352 @@
/* unit-update-ram-custom-trailer.c
*
* Tests update_ram.c with CUSTOM_PARTITION_TRAILER (custom callbacks) and
* WOLFBOOT_FIXED_PARTITIONS. Covers the HAVE_PARTITION_TRAILERS path where
* partition state is managed via externally-defined get/set_trailer_at.
*/
#ifndef WOLFBOOT_HASH_SHA256
#define WOLFBOOT_HASH_SHA256
#endif
#define IMAGE_HEADER_SIZE 256
#define MOCK_ADDRESS_UPDATE 0xCC000000
#define MOCK_ADDRESS_BOOT 0xCD000000
#define MOCK_ADDRESS_SWAP 0xCE000000
#define NO_FORK 1
#include <check.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include "target.h"
static __thread unsigned char
wolfboot_ram[WOLFBOOT_RAMBOOT_MAX_SIZE + IMAGE_HEADER_SIZE];
#define WOLFBOOT_LOAD_ADDRESS (((uintptr_t)wolfboot_ram) + IMAGE_HEADER_SIZE)
#define TEST_SIZE_SMALL 5300
#define DIGEST_TLV_OFF_IN_HDR 28
#define STAGE_ADDR_SENTINEL UINTPTR_MAX
#include "user_settings.h"
#include "wolfboot/wolfboot.h"
/* Custom partition trailer state (mocked)
* Layout: [state][magic(4 bytes)]
* get_trailer_at(part, 0) -> magic (32-bit, base[1..4])
* get_trailer_at(part, 1) -> state (8-bit, base[0])
*/
static uint8_t mock_trailer_boot[5];
static uint8_t mock_trailer_update[5];
uint8_t* get_trailer_at(uint8_t part, uint32_t at)
{
uint8_t *base = (part == PART_BOOT) ? mock_trailer_boot : mock_trailer_update;
if (at == 0)
return &base[1]; /* magic at base[1..4] */
return &base[at - 1]; /* state at base[0], etc. */
}
void set_trailer_at(uint8_t part, uint32_t at, uint8_t val)
{
uint8_t *base = (part == PART_BOOT) ? mock_trailer_boot : mock_trailer_update;
if (at == 0)
base[1] = val; /* magic byte 0 */
else
base[at - 1] = val;
}
void set_partition_magic(uint8_t part)
{
uint8_t *base = (part == PART_BOOT) ? mock_trailer_boot : mock_trailer_update;
/* WOLFBOOT_MAGIC_TRAIL = 0x544F4F42 on LE: bytes are 'B','O','O','T' */
base[1] = 'B';
base[2] = 'O';
base[3] = 'O';
base[4] = 'T';
(void)part;
}
#define wolfBoot_dualboot_candidate wolfBoot_dualboot_candidate_impl
#include "libwolfboot.c"
#undef wolfBoot_dualboot_candidate
static int dualboot_candidate_calls;
int wolfBoot_dualboot_candidate(void)
{
dualboot_candidate_calls++;
ck_assert_msg(dualboot_candidate_calls == 1,
"wolfBoot_dualboot_candidate() called %d times",
dualboot_candidate_calls);
return wolfBoot_dualboot_candidate_impl();
}
#include "update_ram.c"
#include "unit-mock-flash.c"
#include <wolfssl/wolfcrypt/settings.h>
#include <wolfssl/wolfcrypt/sha256.h>
int wolfBoot_staged_ok = 0;
const uint32_t *wolfBoot_stage_address =
(const uint32_t *)(uintptr_t)STAGE_ADDR_SENTINEL;
void* hal_get_primary_address(void)
{
return (void *)(uintptr_t)WOLFBOOT_PARTITION_BOOT_ADDRESS;
}
void* hal_get_update_address(void)
{
return (void *)(uintptr_t)WOLFBOOT_PARTITION_UPDATE_ADDRESS;
}
void do_boot(const uint32_t *address)
{
wolfBoot_staged_ok++;
wolfBoot_stage_address = address;
}
static int mock_flash_protect_called = 0;
static haladdr_t mock_flash_protect_addr = 0;
static int mock_flash_protect_len = 0;
int hal_flash_protect(haladdr_t address, int len)
{
mock_flash_protect_called++;
mock_flash_protect_addr = address;
mock_flash_protect_len = len;
return 0;
}
static void reset_mock_stats(void)
{
wolfBoot_panicked = 0;
wolfBoot_staged_ok = 0;
dualboot_candidate_calls = 0;
mock_flash_protect_called = 0;
mock_flash_protect_addr = 0;
mock_flash_protect_len = 0;
mock_max_read_addr = 0;
mock_max_read_len = 0;
memset(mock_trailer_boot, 0, sizeof(mock_trailer_boot));
memset(mock_trailer_update, 0, sizeof(mock_trailer_update));
}
static void prepare_flash(void)
{
int ret;
char ext_path[64];
char int_path[64];
snprintf(ext_path, sizeof(ext_path),
"/tmp/wolfboot-unit-ext-file-custom-trailer-%d.bin", (int)getpid());
snprintf(int_path, sizeof(int_path),
"/tmp/wolfboot-unit-int-file-custom-trailer-%d.bin", (int)getpid());
ret = mmap_file(ext_path,
(void *)(uintptr_t)MOCK_ADDRESS_UPDATE,
WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE, NULL);
ck_assert_int_ge(ret, 0);
ret = mmap_file(int_path,
(void *)(uintptr_t)MOCK_ADDRESS_BOOT,
WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE, NULL);
ck_assert_int_ge(ret, 0);
ext_flash_unlock();
ext_flash_erase(WOLFBOOT_PARTITION_BOOT_ADDRESS,
WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE);
ext_flash_erase(WOLFBOOT_PARTITION_UPDATE_ADDRESS,
WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE);
ext_flash_lock();
}
static void cleanup_flash(void)
{
char ext_path[64];
char int_path[64];
munmap((void *)WOLFBOOT_PARTITION_BOOT_ADDRESS,
WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE);
munmap((void *)WOLFBOOT_PARTITION_UPDATE_ADDRESS,
WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE);
snprintf(ext_path, sizeof(ext_path),
"/tmp/wolfboot-unit-ext-file-custom-trailer-%d.bin", (int)getpid());
snprintf(int_path, sizeof(int_path),
"/tmp/wolfboot-unit-int-file-custom-trailer-%d.bin", (int)getpid());
unlink(ext_path);
unlink(int_path);
}
static int add_payload(uint8_t part, uint32_t version, uint32_t size)
{
uint32_t word;
uint16_t word16;
int i;
uint8_t *base = (uint8_t *)WOLFBOOT_PARTITION_BOOT_ADDRESS;
int ret;
wc_Sha256 sha;
uint8_t digest[SHA256_DIGEST_SIZE];
ret = wc_InitSha256_ex(&sha, NULL, INVALID_DEVID);
if (ret != 0)
return ret;
if (part == PART_UPDATE)
base = (uint8_t *)WOLFBOOT_PARTITION_UPDATE_ADDRESS;
srandom(part);
ext_flash_unlock();
ext_flash_write((uintptr_t)base, "WOLF", 4);
ext_flash_write((uintptr_t)base + 4, (void *)&size, 4);
word = 4 << 16 | HDR_VERSION;
ext_flash_write((uintptr_t)base + 8, (void *)&word, 4);
ext_flash_write((uintptr_t)base + 12, (void *)&version, 4);
word = 2 << 16 | HDR_IMG_TYPE;
ext_flash_write((uintptr_t)base + 16, (void *)&word, 4);
word16 = HDR_IMG_TYPE_AUTH_NONE | HDR_IMG_TYPE_APP;
ext_flash_write((uintptr_t)base + 20, (void *)&word16, 2);
ret = wc_Sha256Update(&sha, base, DIGEST_TLV_OFF_IN_HDR);
if (ret != 0)
return ret;
size += IMAGE_HEADER_SIZE;
for (i = IMAGE_HEADER_SIZE; i < (int)size; i += 4) {
uint32_t rand_word = (random() << 16) | random();
ext_flash_write((uintptr_t)base + i, (void *)&rand_word, 4);
}
for (i = IMAGE_HEADER_SIZE; i < (int)size; i += WOLFBOOT_SHA_BLOCK_SIZE) {
int len = WOLFBOOT_SHA_BLOCK_SIZE;
if (((int)size - i) < len)
len = (int)size - i;
ret = wc_Sha256Update(&sha, base + i, len);
if (ret != 0)
return ret;
}
ret = wc_Sha256Final(&sha, digest);
if (ret != 0)
return ret;
wc_Sha256Free(&sha);
word = SHA256_DIGEST_SIZE << 16 | HDR_SHA256;
ext_flash_write((uintptr_t)base + DIGEST_TLV_OFF_IN_HDR, (void *)&word, 4);
ext_flash_write((uintptr_t)base + DIGEST_TLV_OFF_IN_HDR + 4, digest,
SHA256_DIGEST_SIZE);
ext_flash_lock();
return 0;
}
/* Test 1: Update partition in UPDATING state transitions to TESTING after boot */
START_TEST(test_custom_trailer_updating_sets_testing)
{
uint8_t state;
reset_mock_stats();
prepare_flash();
ck_assert_int_eq(add_payload(PART_BOOT, 1, TEST_SIZE_SMALL), 0);
ck_assert_int_eq(add_payload(PART_UPDATE, 2, TEST_SIZE_SMALL), 0);
/* Set the update partition to UPDATING state via custom trailer */
set_partition_magic(PART_UPDATE);
mock_trailer_update[0] = IMG_STATE_UPDATING; /* state at base[0] */
wolfBoot_start();
/* After boot, the update partition should be in TESTING state */
ck_assert_int_eq(wolfBoot_staged_ok, 1);
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(wolfBoot_get_partition_state(PART_UPDATE, &state), 0);
ck_assert_int_eq(state, IMG_STATE_TESTING);
cleanup_flash();
}
END_TEST
/* Test 2: Invalid update falls back to boot partition */
START_TEST(test_custom_trailer_invalid_update_falls_back_to_boot)
{
uint8_t bad_digest[SHA256_DIGEST_SIZE];
reset_mock_stats();
prepare_flash();
ck_assert_int_eq(add_payload(PART_BOOT, 1, TEST_SIZE_SMALL), 0);
ck_assert_int_eq(add_payload(PART_UPDATE, 2, TEST_SIZE_SMALL), 0);
memset(bad_digest, 0xBA, sizeof(bad_digest));
ext_flash_unlock();
ext_flash_write(WOLFBOOT_PARTITION_UPDATE_ADDRESS + DIGEST_TLV_OFF_IN_HDR + 4,
bad_digest, sizeof(bad_digest));
ext_flash_lock();
wolfBoot_start();
/* Falls back to boot partition (version 1): the image loaded to RAM
* must come from the BOOT partition payload, not the corrupted UPDATE. */
ck_assert_int_eq(wolfBoot_staged_ok, 1);
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_uint_eq(mock_max_read_addr,
(uintptr_t)WOLFBOOT_PARTITION_BOOT_ADDRESS);
cleanup_flash();
}
END_TEST
/* Test 3: Newer update is preferred over boot */
START_TEST(test_custom_trailer_newer_update_prefers_update)
{
int candidate;
reset_mock_stats();
prepare_flash();
ck_assert_int_eq(add_payload(PART_BOOT, 1, TEST_SIZE_SMALL), 0);
ck_assert_int_eq(add_payload(PART_UPDATE, 2, TEST_SIZE_SMALL), 0);
candidate = wolfBoot_dualboot_candidate_impl();
ck_assert_int_eq(candidate, PART_UPDATE);
cleanup_flash();
}
END_TEST
static Suite *wolfboot_suite(void)
{
Suite *s = suite_create("wolfboot-update-ram-custom-trailer");
TCase *tc = tcase_create("custom_trailer");
tcase_add_test(tc, test_custom_trailer_updating_sets_testing);
tcase_add_test(tc, test_custom_trailer_invalid_update_falls_back_to_boot);
tcase_add_test(tc, test_custom_trailer_newer_update_prefers_update);
tcase_set_timeout(tc, 5);
suite_add_tcase(s, tc);
return s;
}
int main(int argc, char *argv[])
{
int fails;
Suite *s;
SRunner *sr;
argv0 = strdup(argv[0]);
(void)argc;
s = wolfboot_suite();
sr = srunner_create(s);
#if (NO_FORK == 1)
srunner_set_fork_status(sr, CK_NOFORK);
#endif
srunner_run_all(sr, CK_NORMAL);
fails = srunner_ntests_failed(sr);
srunner_free(sr);
return (fails == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@ -0,0 +1,356 @@
/* unit-update-ram-nofixed-noramboot.c
*
* Reproducer for fallback selection in update_ram.c without fixed
* partitions and without RAMBOOT (XIP): the configuration in which
* wolfBoot_open_image_address() is called with a varying load_address
* on every retry iteration.
*
* Pins F-13604: os_image was zeroed once, before the retry loop, so the
* fallback iteration kept the stale img->hdr of the failed partition
* and re-verified the wrong image. wolfBoot_open_image_address() only
* adopts the address when img->hdr is NULL (documented precondition:
* the struct is memset to 0 before each call), so the second
* partition was never examined and a valid alternate image could not
* boot.
*/
#ifndef WOLFBOOT_HASH_SHA256
#define WOLFBOOT_HASH_SHA256
#endif
#define IMAGE_HEADER_SIZE 256
#define MOCK_ADDRESS_UPDATE 0xCC000000
#define MOCK_ADDRESS_BOOT 0xCD000000
#define MOCK_ADDRESS_SWAP 0xCE000000
#define NO_FORK 1
#include <check.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include "target.h"
#define TEST_SIZE_SMALL 5300
#define DIGEST_TLV_OFF_IN_HDR 28
#define STAGE_ADDR_SENTINEL UINTPTR_MAX
#include "user_settings.h"
#include "wolfboot/wolfboot.h"
#define wolfBoot_dualboot_candidate_addr wolfBoot_dualboot_candidate_addr_impl
#include "libwolfboot.c"
#undef wolfBoot_dualboot_candidate_addr
static int dualboot_candidate_addr_calls;
int wolfBoot_dualboot_candidate_addr(void** addr)
{
dualboot_candidate_addr_calls++;
ck_assert_msg(dualboot_candidate_addr_calls == 1,
"wolfBoot_dualboot_candidate_addr() called %d times",
dualboot_candidate_addr_calls);
return wolfBoot_dualboot_candidate_addr_impl(addr);
}
#include "update_ram.c"
#include "unit-mock-flash.c"
#include <wolfssl/wolfcrypt/settings.h>
#include <wolfssl/wolfcrypt/sha256.h>
int wolfBoot_staged_ok = 0;
const uint32_t *wolfBoot_stage_address =
(const uint32_t *)(uintptr_t)STAGE_ADDR_SENTINEL;
void* hal_get_primary_address(void)
{
return (void *)(uintptr_t)WOLFBOOT_PARTITION_BOOT_ADDRESS;
}
void* hal_get_update_address(void)
{
return (void *)(uintptr_t)WOLFBOOT_PARTITION_UPDATE_ADDRESS;
}
void do_boot(const uint32_t *address)
{
if (wolfBoot_panicked)
return;
wolfBoot_staged_ok++;
wolfBoot_stage_address = address;
}
static int mock_flash_protect_called = 0;
static haladdr_t mock_flash_protect_addr = 0;
static int mock_flash_protect_len = 0;
int hal_flash_protect(haladdr_t address, int len)
{
mock_flash_protect_called++;
mock_flash_protect_addr = address;
mock_flash_protect_len = len;
return 0;
}
static void reset_mock_stats(void)
{
wolfBoot_panicked = 0;
wolfBoot_staged_ok = 0;
dualboot_candidate_addr_calls = 0;
mock_flash_protect_called = 0;
mock_flash_protect_addr = 0;
mock_flash_protect_len = 0;
}
static void prepare_flash(void)
{
int ret;
char ext_path[64];
char int_path[64];
snprintf(ext_path, sizeof(ext_path),
"/tmp/wolfboot-unit-ext-file-nofixed-noramboot-%d.bin", (int)getpid());
snprintf(int_path, sizeof(int_path),
"/tmp/wolfboot-unit-int-file-nofixed-noramboot-%d.bin", (int)getpid());
ret = mmap_file(ext_path,
(void *)(uintptr_t)MOCK_ADDRESS_UPDATE,
WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE, NULL);
ck_assert_int_ge(ret, 0);
ret = mmap_file(int_path,
(void *)(uintptr_t)MOCK_ADDRESS_BOOT,
WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE, NULL);
ck_assert_int_ge(ret, 0);
ext_flash_unlock();
ext_flash_erase(WOLFBOOT_PARTITION_BOOT_ADDRESS,
WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE);
ext_flash_erase(WOLFBOOT_PARTITION_UPDATE_ADDRESS,
WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE);
ext_flash_lock();
}
static void cleanup_flash(void)
{
char ext_path[64];
char int_path[64];
munmap((void *)WOLFBOOT_PARTITION_BOOT_ADDRESS,
WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE);
munmap((void *)WOLFBOOT_PARTITION_UPDATE_ADDRESS,
WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE);
snprintf(ext_path, sizeof(ext_path),
"/tmp/wolfboot-unit-ext-file-nofixed-noramboot-%d.bin", (int)getpid());
snprintf(int_path, sizeof(int_path),
"/tmp/wolfboot-unit-int-file-nofixed-noramboot-%d.bin", (int)getpid());
unlink(ext_path);
unlink(int_path);
}
static int add_payload(uint8_t part, uint32_t version, uint32_t size)
{
uint32_t word;
uint16_t word16;
int i;
int ret;
uint8_t *base = (uint8_t *)WOLFBOOT_PARTITION_BOOT_ADDRESS;
wc_Sha256 sha;
uint8_t digest[SHA256_DIGEST_SIZE];
ret = wc_InitSha256_ex(&sha, NULL, INVALID_DEVID);
if (ret != 0)
return ret;
if (part == PART_UPDATE)
base = (uint8_t *)WOLFBOOT_PARTITION_UPDATE_ADDRESS;
srandom(part);
ext_flash_unlock();
ext_flash_write((uintptr_t)base, "WOLF", 4);
ext_flash_write((uintptr_t)base + 4, (void *)&size, 4);
word = 4 << 16 | HDR_VERSION;
ext_flash_write((uintptr_t)base + 8, (void *)&word, 4);
ext_flash_write((uintptr_t)base + 12, (void *)&version, 4);
word = 2 << 16 | HDR_IMG_TYPE;
ext_flash_write((uintptr_t)base + 16, (void *)&word, 4);
word16 = HDR_IMG_TYPE_AUTH_NONE | HDR_IMG_TYPE_APP;
ext_flash_write((uintptr_t)base + 20, (void *)&word16, 2);
ret = wc_Sha256Update(&sha, base, DIGEST_TLV_OFF_IN_HDR);
if (ret != 0)
return ret;
size += IMAGE_HEADER_SIZE;
for (i = IMAGE_HEADER_SIZE; i < (int)size; i += 4) {
uint32_t rand_word = (random() << 16) | random();
ext_flash_write((uintptr_t)base + i, (void *)&rand_word, 4);
}
for (i = IMAGE_HEADER_SIZE; i < (int)size; i += WOLFBOOT_SHA_BLOCK_SIZE) {
int len = WOLFBOOT_SHA_BLOCK_SIZE;
if (((int)size - i) < len)
len = (int)size - i;
ret = wc_Sha256Update(&sha, base + i, len);
if (ret != 0)
return ret;
}
ret = wc_Sha256Final(&sha, digest);
if (ret != 0)
return ret;
wc_Sha256Free(&sha);
word = SHA256_DIGEST_SIZE << 16 | HDR_SHA256;
ext_flash_write((uintptr_t)base + DIGEST_TLV_OFF_IN_HDR, (void *)&word, 4);
ext_flash_write((uintptr_t)base + DIGEST_TLV_OFF_IN_HDR + 4, digest,
SHA256_DIGEST_SIZE);
ext_flash_lock();
return 0;
}
START_TEST(test_invalid_boot_falls_back_to_update)
{
uint8_t bad_digest[SHA256_DIGEST_SIZE];
reset_mock_stats();
prepare_flash();
/* BOOT is the newer image but carries a corrupted digest: the
* candidate selection picks it first, and the fallback must boot
* the valid, older UPDATE image. */
ck_assert_int_eq(add_payload(PART_BOOT, 2, TEST_SIZE_SMALL), 0);
ck_assert_int_eq(add_payload(PART_UPDATE, 1, TEST_SIZE_SMALL), 0);
memset(bad_digest, 0xBA, sizeof(bad_digest));
ext_flash_unlock();
ext_flash_write(WOLFBOOT_PARTITION_BOOT_ADDRESS + DIGEST_TLV_OFF_IN_HDR + 4,
bad_digest, sizeof(bad_digest));
ext_flash_lock();
wolfBoot_start();
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(wolfBoot_staged_ok, 1);
ck_assert_uint_eq((uintptr_t)wolfBoot_stage_address,
(uintptr_t)(WOLFBOOT_PARTITION_UPDATE_ADDRESS + IMAGE_HEADER_SIZE));
#ifndef TZEN
ck_assert_int_eq(mock_flash_protect_called, 1);
ck_assert_uint_eq((uintptr_t)mock_flash_protect_addr,
(uintptr_t)WOLFBOOT_ORIGIN);
ck_assert_int_eq(mock_flash_protect_len, BOOTLOADER_PARTITION_SIZE);
#endif
cleanup_flash();
}
END_TEST
START_TEST(test_invalid_update_falls_back_to_boot)
{
uint8_t bad_digest[SHA256_DIGEST_SIZE];
reset_mock_stats();
prepare_flash();
/* Mirror of the previous case: the newer UPDATE image is corrupt,
* the fallback must boot the valid, older BOOT image. */
ck_assert_int_eq(add_payload(PART_BOOT, 1, TEST_SIZE_SMALL), 0);
ck_assert_int_eq(add_payload(PART_UPDATE, 2, TEST_SIZE_SMALL), 0);
memset(bad_digest, 0xBA, sizeof(bad_digest));
ext_flash_unlock();
ext_flash_write(WOLFBOOT_PARTITION_UPDATE_ADDRESS + DIGEST_TLV_OFF_IN_HDR + 4,
bad_digest, sizeof(bad_digest));
ext_flash_lock();
wolfBoot_start();
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(wolfBoot_staged_ok, 1);
ck_assert_uint_eq((uintptr_t)wolfBoot_stage_address,
(uintptr_t)(WOLFBOOT_PARTITION_BOOT_ADDRESS + IMAGE_HEADER_SIZE));
#ifndef TZEN
ck_assert_int_eq(mock_flash_protect_called, 1);
ck_assert_uint_eq((uintptr_t)mock_flash_protect_addr,
(uintptr_t)WOLFBOOT_ORIGIN);
ck_assert_int_eq(mock_flash_protect_len, BOOTLOADER_PARTITION_SIZE);
#endif
cleanup_flash();
}
END_TEST
START_TEST(test_candidate_addr_equal_versions_prefers_boot)
{
void *addr = NULL;
int ret;
reset_mock_stats();
prepare_flash();
ck_assert_int_eq(add_payload(PART_BOOT, 1, TEST_SIZE_SMALL), 0);
ck_assert_int_eq(add_payload(PART_UPDATE, 1, TEST_SIZE_SMALL), 0);
ret = wolfBoot_dualboot_candidate_addr_impl(&addr);
ck_assert_int_eq(ret, 0);
ck_assert_ptr_eq(addr, hal_get_primary_address());
cleanup_flash();
}
END_TEST
START_TEST(test_candidate_addr_newer_update_prefers_update)
{
void *addr = NULL;
int ret;
reset_mock_stats();
prepare_flash();
ck_assert_int_eq(add_payload(PART_BOOT, 1, TEST_SIZE_SMALL), 0);
ck_assert_int_eq(add_payload(PART_UPDATE, 2, TEST_SIZE_SMALL), 0);
ret = wolfBoot_dualboot_candidate_addr_impl(&addr);
ck_assert_int_eq(ret, 1);
ck_assert_ptr_eq(addr, hal_get_update_address());
cleanup_flash();
}
END_TEST
static Suite *wolfboot_suite(void)
{
Suite *s = suite_create("wolfboot-update-ram-nofixed-noramboot");
TCase *tc = tcase_create("fallback");
TCase *tc_candidate = tcase_create("candidate_addr");
tcase_add_test(tc, test_invalid_boot_falls_back_to_update);
tcase_add_test(tc, test_invalid_update_falls_back_to_boot);
tcase_set_timeout(tc, 5);
suite_add_tcase(s, tc);
tcase_add_test(tc_candidate, test_candidate_addr_equal_versions_prefers_boot);
tcase_add_test(tc_candidate, test_candidate_addr_newer_update_prefers_update);
tcase_set_timeout(tc_candidate, 5);
suite_add_tcase(s, tc_candidate);
return s;
}
int main(int argc, char *argv[])
{
int fails;
Suite *s;
SRunner *sr;
argv0 = strdup(argv[0]);
(void)argc;
s = wolfboot_suite();
sr = srunner_create(s);
#if (NO_FORK == 1)
srunner_set_fork_status(sr, CK_NOFORK);
#endif
srunner_run_all(sr, CK_NORMAL);
fails = srunner_ntests_failed(sr);
srunner_free(sr);
return fails;
}

View File

@ -81,6 +81,8 @@ static void reset_mock_stats(void)
{
wolfBoot_panicked = 0;
wolfBoot_staged_ok = 0;
mock_max_read_addr = 0;
mock_max_read_len = 0;
}
static void prepare_flash(void)
@ -184,16 +186,15 @@ START_TEST (test_noramboot_sunnyday) {
}
END_TEST
/* Regression test for F-4410: firmware versions with the high bit set
* (>= 0x80000000) must still feed the anti-rollback guard in wolfBoot_start.
/* Regression test for F-4410 + F-12922: firmware versions with the high
* bit set (>= 0x80000000) must be read without signed-int clamping (the
* two version asserts below), and a failed high-version image must not
* block fallback to the lower-versioned (but valid) UPDATE partition.
*
* BOOT carries the higher version but is marked oversize so wolfBoot_open_image()
* rejects it and the boot path falls back to the lower-versioned (but valid)
* UPDATE partition. That downgrade must be denied. Before the fix the versions
* were cast through a signed int and clamped to 0, collapsing max_v to 0 and
* silently bypassing the "(max_v > 0U)" guard, so the lower UPDATE image was
* staged for boot. */
START_TEST (test_noramboot_highversion_rollback_denied) {
* BOOT carries the higher version but is marked oversize so
* wolfBoot_open_image() rejects it; the boot path must fall back to the
* valid UPDATE image instead of panicking on the version difference. */
START_TEST (test_noramboot_fallback_to_lower_version) {
uint32_t oversize = WOLFBOOT_PARTITION_SIZE;
reset_mock_stats();
@ -212,10 +213,14 @@ START_TEST (test_noramboot_highversion_rollback_denied) {
wolfBoot_start();
/* Rollback to the lower UPDATE version must be denied: wolfBoot panics and
* stages nothing. */
ck_assert(!wolfBoot_staged_ok);
ck_assert_int_eq(wolfBoot_panicked, 1);
/* A failed high-version boot image must not block fallback to the
* valid lower-version update image (F-12922). The image loaded to RAM
* must come from the UPDATE partition payload, not the oversize BOOT
* partition. */
ck_assert(wolfBoot_staged_ok);
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_uint_eq(mock_max_read_addr,
(uintptr_t)WOLFBOOT_PARTITION_UPDATE_ADDRESS + IMAGE_HEADER_SIZE);
cleanup_flash();
}
END_TEST
@ -249,19 +254,20 @@ Suite *wolfboot_suite(void)
TCase *sunnyday = tcase_create("Non-RAMBOOT sunny day");
TCase *ext_short_read =
tcase_create("Non-RAMBOOT short ext flash read rejected");
TCase *rollback_denied =
tcase_create("Non-RAMBOOT high-version rollback denied");
TCase *fallback_to_lower_version =
tcase_create("Non-RAMBOOT fallback to lower version");
tcase_add_test(sunnyday, test_noramboot_sunnyday);
tcase_add_test(ext_short_read,
test_noramboot_ext_flash_short_read_rejected);
tcase_add_test(rollback_denied, test_noramboot_highversion_rollback_denied);
tcase_add_test(fallback_to_lower_version,
test_noramboot_fallback_to_lower_version);
suite_add_tcase(s, sunnyday);
suite_add_tcase(s, ext_short_read);
suite_add_tcase(s, rollback_denied);
suite_add_tcase(s, fallback_to_lower_version);
tcase_set_timeout(sunnyday, 5);
tcase_set_timeout(ext_short_read, 5);
tcase_set_timeout(rollback_denied, 5);
tcase_set_timeout(fallback_to_lower_version, 5);
return s;
}

View File

@ -434,9 +434,10 @@ START_TEST (test_invalid_update_type) {
ext_flash_lock();
wolfBoot_update_trigger();
wolfBoot_start();
ck_assert(!wolfBoot_staged_ok);
ck_assert_int_eq(wolfBoot_panicked, 1);
ck_assert_int_eq(get_version_ramloaded(), 2);
/* Failed update must fall back to the valid boot image, not panic. */
ck_assert(wolfBoot_staged_ok);
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(get_version_ramloaded(), 1);
cleanup_flash();
}
@ -453,8 +454,10 @@ START_TEST (test_update_toolarge) {
wolfBoot_update_trigger();
wolfBoot_start();
ck_assert(!wolfBoot_staged_ok);
ck_assert_int_eq(wolfBoot_panicked, 1);
/* Failed update must fall back to the valid boot image, not panic. */
ck_assert(wolfBoot_staged_ok);
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(get_version_ramloaded(), 1);
cleanup_flash();
}
@ -471,8 +474,10 @@ START_TEST (test_invalid_sha) {
ext_flash_lock();
wolfBoot_update_trigger();
wolfBoot_start();
ck_assert(!wolfBoot_staged_ok);
ck_assert_int_eq(wolfBoot_panicked, 1);
/* Failed update must fall back to the valid boot image, not panic. */
ck_assert(wolfBoot_staged_ok);
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(get_version_ramloaded(), 1);
cleanup_flash();
}