diff --git a/hal/mpfs250_ddr.c b/hal/mpfs250_ddr.c index ba7b6607..551c82f2 100644 --- a/hal/mpfs250_ddr.c +++ b/hal/mpfs250_ddr.c @@ -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; diff --git a/hal/nrf5340.c b/hal/nrf5340.c index f1762086..64ae0611 100644 --- a/hal/nrf5340.c +++ b/hal/nrf5340.c @@ -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) { diff --git a/hal/nrf5340.h b/hal/nrf5340.h index da9bdc87..eb48b0a2 100644 --- a/hal/nrf5340.h +++ b/hal/nrf5340.h @@ -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") diff --git a/hal/nxp_ls1028a.c b/hal/nxp_ls1028a.c index 190fd49a..e493fd63 100644 --- a/hal/nxp_ls1028a.c +++ b/hal/nxp_ls1028a.c @@ -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) diff --git a/hal/nxp_p1021.c b/hal/nxp_p1021.c index 774be3bc..09b05b66 100644 --- a/hal/nxp_p1021.c +++ b/hal/nxp_p1021.c @@ -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; } diff --git a/hal/nxp_t2080.c b/hal/nxp_t2080.c index d93716dd..d39f6a6b 100644 --- a/hal/nxp_t2080.c +++ b/hal/nxp_t2080.c @@ -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(); diff --git a/hal/pic32c.c b/hal/pic32c.c index aa31097d..1ec3f932 100644 --- a/hal/pic32c.c +++ b/hal/pic32c.c @@ -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 diff --git a/hal/s32k1xx.c b/hal/s32k1xx.c index 74923582..0e7f732c 100644 --- a/hal/s32k1xx.c +++ b/hal/s32k1xx.c @@ -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)) {} diff --git a/hal/stm32h5.c b/hal/stm32h5.c index 5cd6b5f9..279edff4 100644 --- a/hal/stm32h5.c +++ b/hal/stm32h5.c @@ -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) diff --git a/include/image.h b/include/image.h index 1fdf340e..3389f999 100644 --- a/include/image.h +++ b/include/image.h @@ -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); diff --git a/include/user_settings.h b/include/user_settings.h index 1234d7f7..16291235 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -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 diff --git a/include/wolfboot/wolfboot.h b/include/wolfboot/wolfboot.h index 1b947395..d86812d6 100644 --- a/include/wolfboot/wolfboot.h +++ b/include/wolfboot/wolfboot.h @@ -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) diff --git a/src/arm_tee_psa_ipc.c b/src/arm_tee_psa_ipc.c index f6244585..63223279 100644 --- a/src/arm_tee_psa_ipc.c +++ b/src/arm_tee_psa_ipc.c @@ -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); } diff --git a/src/boot_arm.c b/src/boot_arm.c index d2eb1bc8..f59c76c2 100644 --- a/src/boot_arm.c +++ b/src/boot_arm.c @@ -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); diff --git a/src/boot_arm32.c b/src/boot_arm32.c index 1ab63868..381b750c 100644 --- a/src/boot_arm32.c +++ b/src/boot_arm32.c @@ -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 ( diff --git a/src/boot_x86_fsp.c b/src/boot_x86_fsp.c index 85edaf17..5b4596f1 100644 --- a/src/boot_x86_fsp.c +++ b/src/boot_x86_fsp.c @@ -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)) diff --git a/src/disk.c b/src/disk.c index 53dc9e8d..79114a6c 100644 --- a/src/disk.c +++ b/src/disk.c @@ -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) { diff --git a/src/image.c b/src/image.c index 24c806d4..dc48fc86 100644 --- a/src/image.c +++ b/src/image.c @@ -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) { diff --git a/src/libwolfboot.c b/src/libwolfboot.c index 5569d645..498171d7 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -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. diff --git a/src/sdhci.c b/src/sdhci.c index af046f24..0bb7da8e 100644 --- a/src/sdhci.c +++ b/src/sdhci.c @@ -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) { diff --git a/src/tpm.c b/src/tpm.c index 3c9fc21e..e240946e 100644 --- a/src/tpm.c +++ b/src/tpm.c @@ -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. diff --git a/src/update_ram.c b/src/update_ram.c index 4b55dec5..d5a541df 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -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; } diff --git a/src/x86/ata.c b/src/x86/ata.c index 5858c4d3..535cca92 100644 --- a/src/x86/ata.c +++ b/src/x86/ata.c @@ -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; } diff --git a/tools/keytools/keygen.c b/tools/keytools/keygen.c index 3751f588..2dc1f2d9 100644 --- a/tools/keytools/keygen.c +++ b/tools/keytools/keygen.c @@ -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) diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index 4938e268..6a3d62a7 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -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 diff --git a/tools/tpm/rot.c b/tools/tpm/rot.c index 9de7bce1..9f0b03e2 100644 --- a/tools/tpm/rot.c +++ b/tools/tpm/rot.c @@ -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; } diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index dfce2e7e..93068196 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -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' $< > $@ diff --git a/tools/unit-tests/unit-ata-security-passphrase-zeroize.c b/tools/unit-tests/unit-ata-security-passphrase-zeroize.c index f0df0d6c..9541360c 100644 --- a/tools/unit-tests/unit-ata-security-passphrase-zeroize.c +++ b/tools/unit-tests/unit-ata-security-passphrase-zeroize.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #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; } diff --git a/tools/unit-tests/unit-custom-trailer-nopart.c b/tools/unit-tests/unit-custom-trailer-nopart.c new file mode 100644 index 00000000..16e05911 --- /dev/null +++ b/tools/unit-tests/unit-custom-trailer-nopart.c @@ -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 +#include +#include +#include +#include +#include + +#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; +} diff --git a/tools/unit-tests/unit-keygen-xmss-params.c b/tools/unit-tests/unit-keygen-xmss-params.c index b8e9a45f..e40a6478 100644 --- a/tools/unit-tests/unit-keygen-xmss-params.c +++ b/tools/unit-tests/unit-keygen-xmss-params.c @@ -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) diff --git a/tools/unit-tests/unit-mock-flash.c b/tools/unit-tests/unit-mock-flash.c index c0d29618..476faf4e 100644 --- a/tools/unit-tests/unit-mock-flash.c +++ b/tools/unit-tests/unit-mock-flash.c @@ -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]; } diff --git a/tools/unit-tests/unit-p1021-erase-advance.c b/tools/unit-tests/unit-p1021-erase-advance.c index 3d4b4c50..ab0320d4 100644 --- a/tools/unit-tests/unit-p1021-erase-advance.c +++ b/tools/unit-tests/unit-p1021-erase-advance.c @@ -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; diff --git a/tools/unit-tests/unit-p1021-fcm-bytes.c b/tools/unit-tests/unit-p1021-fcm-bytes.c index 9ffdf3c3..1d96ef34 100644 --- a/tools/unit-tests/unit-p1021-fcm-bytes.c +++ b/tools/unit-tests/unit-p1021-fcm-bytes.c @@ -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); diff --git a/tools/unit-tests/unit-pkcs11-pin-zeroize.c b/tools/unit-tests/unit-pkcs11-pin-zeroize.c index 591cbe25..8c801eb9 100644 --- a/tools/unit-tests/unit-pkcs11-pin-zeroize.c +++ b/tools/unit-tests/unit-pkcs11-pin-zeroize.c @@ -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; } diff --git a/tools/unit-tests/unit-update-ram-custom-trailer.c b/tools/unit-tests/unit-update-ram-custom-trailer.c new file mode 100644 index 00000000..634026a5 --- /dev/null +++ b/tools/unit-tests/unit-update-ram-custom-trailer.c @@ -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 +#include +#include +#include +#include +#include + +#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 +#include + +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; +} diff --git a/tools/unit-tests/unit-update-ram-nofixed-noramboot.c b/tools/unit-tests/unit-update-ram-nofixed-noramboot.c new file mode 100644 index 00000000..816b6fc6 --- /dev/null +++ b/tools/unit-tests/unit-update-ram-nofixed-noramboot.c @@ -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 +#include +#include +#include +#include +#include + +#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 +#include + +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; +} diff --git a/tools/unit-tests/unit-update-ram-noramboot.c b/tools/unit-tests/unit-update-ram-noramboot.c index b8dca806..284c3a1d 100644 --- a/tools/unit-tests/unit-update-ram-noramboot.c +++ b/tools/unit-tests/unit-update-ram-noramboot.c @@ -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; } diff --git a/tools/unit-tests/unit-update-ram.c b/tools/unit-tests/unit-update-ram.c index f96f2aef..62ad476a 100644 --- a/tools/unit-tests/unit-update-ram.c +++ b/tools/unit-tests/unit-update-ram.c @@ -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(); }