diff --git a/.gitignore b/.gitignore index 847f11b361..dad06b3071 100644 --- a/.gitignore +++ b/.gitignore @@ -185,6 +185,8 @@ tools/unit-tests/unit-update-flash-self-update tools/unit-tests/unit-update-flash-hook tools/unit-tests/unit-loader-tpm-init tools/unit-tests/unit-update-ram-nofixed +tools/unit-tests/unit-update-ram-noramboot +tools/unit-tests/unit-update-flash-hwswap tools/unit-tests/unit-uart-flash tools/unit-tests/unit-max-space tools/unit-tests/unit-sdhci-disk-unaligned @@ -200,6 +202,15 @@ tools/unit-tests/unit-mpusize tools/unit-tests/unit-otp-keystore tools/unit-tests/unit-tpm-api-names tools/unit-tests/unit-elf-bss-guard +tools/unit-tests/unit-fit-fpga +tools/unit-tests/unit-flash-erase-c0 +tools/unit-tests/unit-flash-erase-g0 +tools/unit-tests/unit-flash-erase-l0 +tools/unit-tests/unit-flash-erase-u3 +tools/unit-tests/unit-flash-erase-wb +tools/unit-tests/unit-fwtpm-nv-oob +tools/unit-tests/unit-x86-paging-oob + # Elf preprocessing tools diff --git a/hal/stm32c0.c b/hal/stm32c0.c index 7b14fb5598..4e703e45e7 100644 --- a/hal/stm32c0.c +++ b/hal/stm32c0.c @@ -20,18 +20,24 @@ */ #include +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #include +#endif +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #ifndef NVM_FLASH_WRITEONCE # error "wolfBoot STM32C0 HAL: no WRITEONCE support detected. Please define NVM_FLASH_WRITEONCE" #endif +#endif /* STM32 C0 register configuration */ /* Assembly helpers */ +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #define DMB() __asm__ volatile ("dmb") #define ISB() __asm__ volatile ("isb") #define DSB() __asm__ volatile ("dsb") +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ /*** RCC ***/ @@ -65,12 +71,14 @@ #define SYSCFG_APB2_CLOCK_ER_VAL (1 << 0) /* RM0490 - 5.4.14 - RCC_APBENR2 - SYSCFGEN */ #define FLASH_BASE (0x40022000) /*FLASH_R_BASE = 0x40000000UL + 0x00020000UL + 0x00002000UL */ +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #define FLASH_ACR (*(volatile uint32_t *)(FLASH_BASE + 0x00)) /* RM0490 - 3.7.1 - FLASH_ACR */ #define FLASH_KEY (*(volatile uint32_t *)(FLASH_BASE + 0x08)) /* RM0490 - 3.7.2 - FLASH_KEYR */ #define FLASH_OPTKEY (*(volatile uint32_t *)(FLASH_BASE + 0x0C)) /* RM0490 - 3.7.3 - FLASH_OPTKEYR */ #define FLASH_SR (*(volatile uint32_t *)(FLASH_BASE + 0x10)) /* RM0490 - 3.7.4 - FLASH_SR */ #define FLASH_CR (*(volatile uint32_t *)(FLASH_BASE + 0x14)) /* RM0490 - 3.7.5 - FLASH_CR */ #define FLASH_SECR (*(volatile uint32_t *)(FLASH_BASE + 0x80)) /* RM0490 - 3.7.13 - FLASH_SECR */ +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ #define FLASHMEM_ADDRESS_SPACE (0x08000000) #define FLASH_PAGE_SIZE (0x800) /* 2KB */ @@ -108,6 +116,7 @@ #define FLASH_OPTKEY2 (0x4C5D6E7F) +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE static void RAMFUNCTION flash_set_waitstates(unsigned int waitstates) { uint32_t reg, mask_val, set_val; @@ -191,6 +200,7 @@ void RAMFUNCTION hal_flash_lock(void) if ((FLASH_CR & FLASH_CR_LOCK) == 0) FLASH_CR |= FLASH_CR_LOCK; } +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) @@ -199,7 +209,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) uint32_t p; if (len == 0) return -1; - end_address = address + len - 1; + end_address = address + len; for (p = address; p < end_address; p += FLASH_PAGE_SIZE) { uint32_t reg = FLASH_CR & (~(FLASH_CR_PNB_MASK << FLASH_CR_PNB_SHIFT)); FLASH_CR = reg | ((p >> FLASH_PAGE_SIZE_SHIFT) << FLASH_CR_PNB_SHIFT) | FLASH_CR_PER; @@ -211,6 +221,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) return 0; } +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE static void clock_pll_off(void) { uint32_t reg32; @@ -308,3 +319,4 @@ void RAMFUNCTION hal_prepare_boot(void) do_secure_boot(); #endif } +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ diff --git a/hal/stm32g0.c b/hal/stm32g0.c index 8be7f7efa4..e55e359440 100644 --- a/hal/stm32g0.c +++ b/hal/stm32g0.c @@ -20,18 +20,24 @@ */ #include +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #include +#endif +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #ifndef NVM_FLASH_WRITEONCE # error "wolfBoot STM32G0 HAL: no WRITEONCE support detected. Please define NVM_FLASH_WRITEONCE" #endif +#endif /* STM32 G0 register configuration */ /* Assembly helpers */ +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #define DMB() __asm__ volatile ("dmb") #define ISB() __asm__ volatile ("isb") #define DSB() __asm__ volatile ("dsb") +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ /*** RCC ***/ @@ -63,11 +69,13 @@ #define SYSCFG_APB2_CLOCK_ER_VAL (1 << 0) /* RM0444 - 5.4.15 - RCC_APBENR2 - SYSCFGEN */ #define FLASH_BASE (0x40022000) /*FLASH_R_BASE = 0x40000000UL + 0x00020000UL + 0x00002000UL */ +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #define FLASH_ACR (*(volatile uint32_t *)(FLASH_BASE + 0x00)) /* RM0444 - 3.7.1 - FLASH_ACR */ #define FLASH_KEY (*(volatile uint32_t *)(FLASH_BASE + 0x08)) /* RM0444 - 3.7.2 - FLASH_KEYR */ #define FLASH_SR (*(volatile uint32_t *)(FLASH_BASE + 0x10)) /* RM0444 - 3.7.4 - FLASH_SR */ #define FLASH_CR (*(volatile uint32_t *)(FLASH_BASE + 0x14)) /* RM0444 - 3.7.5 - FLASH_CR */ #define FLASH_SECR (*(volatile uint32_t *)(FLASH_BASE + 0x80)) /* RM0444 - 3.7.12 - FLASH_SECR */ +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ #define FLASHMEM_ADDRESS_SPACE (0x08000000) #define FLASH_PAGE_SIZE (0x800) /* 2KB */ @@ -103,6 +111,7 @@ #define FLASH_KEY2 (0xCDEF89AB) +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE static void RAMFUNCTION flash_set_waitstates(unsigned int waitstates) { uint32_t reg = FLASH_ACR; @@ -181,6 +190,7 @@ void RAMFUNCTION hal_flash_lock(void) if ((FLASH_CR & FLASH_CR_LOCK) == 0) FLASH_CR |= FLASH_CR_LOCK; } +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) @@ -190,7 +200,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) if (len == 0) return -1; address -= FLASHMEM_ADDRESS_SPACE; - end_address = address + len - 1; + end_address = address + len; for (p = address; p < end_address; p += FLASH_PAGE_SIZE) { while (FLASH_SR & (FLASH_SR_BSY1 | FLASH_SR_BSY2)); flash_clear_errors(); @@ -210,6 +220,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) return 0; } +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE static void clock_pll_off(void) { uint32_t reg32; @@ -356,3 +367,4 @@ void RAMFUNCTION hal_prepare_boot(void) do_secure_boot(); #endif } +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ diff --git a/hal/stm32h5.c b/hal/stm32h5.c index 2a55dc0b9d..3a2da3790e 100644 --- a/hal/stm32h5.c +++ b/hal/stm32h5.c @@ -182,6 +182,14 @@ __attribute__((weak)) int stm32h5_obkeys_read_uds(uint8_t *out, size_t out_len) #endif #if defined(WOLFCRYPT_TZ_PSA) +static NOINLINEFUNCTION void hal_secret_zeroize(void *ptr, size_t len) +{ + volatile uint8_t *p = (volatile uint8_t *)ptr; + while (len-- > 0U) { + *p++ = 0U; + } +} + static int uds_from_uid(uint8_t *out, size_t out_len) { uint8_t uid[12]; @@ -231,6 +239,8 @@ static int uds_from_uid(uint8_t *out, size_t out_len) copy_len = out_len; } memcpy(out, digest, copy_len); + hal_secret_zeroize(digest, sizeof(digest)); + hal_secret_zeroize(&hash, sizeof(hash)); return 0; } @@ -246,14 +256,6 @@ static int buffer_is_all_value(const uint8_t *buf, size_t len, uint8_t value) return 1; } -static NOINLINEFUNCTION void hal_secret_zeroize(void *ptr, size_t len) -{ - volatile uint8_t *p = (volatile uint8_t *)ptr; - while (len-- > 0U) { - *p++ = 0U; - } -} - int hal_uds_derive_key(uint8_t *out, size_t out_len) { #if defined(FLASH_OTP_KEYSTORE) diff --git a/hal/stm32l0.c b/hal/stm32l0.c index 9cd2771196..6c35e80071 100644 --- a/hal/stm32l0.c +++ b/hal/stm32l0.c @@ -20,11 +20,15 @@ */ #include +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #include +#endif /* STM32 L0 register configuration */ /* Assembly helpers */ +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #define DMB() __asm__ volatile ("dmb") +#endif /*** RCC ***/ @@ -49,11 +53,13 @@ #define PWR_APB1_CLOCK_ER_VAL (1 << 28) #define FLASH_BASE (0x40022000) #define FLASH_ACR (*(volatile uint32_t *)(FLASH_BASE + 0x00)) +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #define FLASH_PECR (*(volatile uint32_t *)(FLASH_BASE + 0x04)) #define FLASH_PEKEY (*(volatile uint32_t *)(FLASH_BASE + 0x0c)) #define FLASH_PRGKEY (*(volatile uint32_t *)(FLASH_BASE + 0x10)) #define FLASH_SR (*(volatile uint32_t *)(FLASH_BASE + 0x18)) #define FLASHMEM_ADDRESS_SPACE (0x08000000) +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ #define FLASH_PAGE_SIZE (128) /* Register values */ @@ -73,6 +79,7 @@ #define FLASH_PECR_ERASE (1 << 9) +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE static void RAMFUNCTION flash_set_waitstates(unsigned int waitstates) { if (waitstates && ((FLASH_ACR & 1) == 0)) @@ -82,6 +89,7 @@ static void RAMFUNCTION flash_set_waitstates(unsigned int waitstates) while ((FLASH_ACR & 1) != waitstates) ; } +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ static RAMFUNCTION void flash_wait_complete(void) { @@ -94,6 +102,7 @@ static void RAMFUNCTION clear_errors(void) FLASH_SR |= ( FLASH_SR_SIZERR | FLASH_SR_PGAERR | FLASH_SR_WRPERR | FLASH_SR_EOP ); } +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) { int i = 0; @@ -151,6 +160,7 @@ void RAMFUNCTION hal_flash_lock(void) if ((FLASH_PECR & FLASH_PECR_PRGLOCK) == 0) FLASH_PECR |= FLASH_PECR_PRGLOCK; } +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) @@ -159,7 +169,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) uint32_t p; if (len == 0) return -1; - end_address = address + len - 1; + end_address = address + len; for (p = address; p < end_address; p += FLASH_PAGE_SIZE) { FLASH_PECR |= FLASH_PECR_PROG | FLASH_PECR_ERASE; *(volatile uint32_t *)(p + FLASHMEM_ADDRESS_SPACE) = 0xFFFFFFFF; @@ -169,6 +179,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) return 0; } +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE static void clock_pll_off(void) { uint32_t reg32; @@ -270,4 +281,4 @@ void hal_prepare_boot(void) clock_pll_off(); #endif } - +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ diff --git a/hal/stm32l5.c b/hal/stm32l5.c index af312f7d2d..d90755f9a9 100644 --- a/hal/stm32l5.c +++ b/hal/stm32l5.c @@ -120,6 +120,14 @@ int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) #define STM32L5_UID2 (*(volatile uint32_t *)(STM32L5_UID_BASE + 0x8)) #if defined(WOLFCRYPT_TZ_PSA) +static NOINLINEFUNCTION void hal_secret_zeroize(void *ptr, size_t len) +{ + volatile uint8_t *p = (volatile uint8_t *)ptr; + while (len-- > 0U) { + *p++ = 0U; + } +} + static int uds_from_uid(uint8_t *out, size_t out_len) { uint8_t uid[12]; @@ -173,6 +181,8 @@ static int uds_from_uid(uint8_t *out, size_t out_len) copy_len = out_len; } memcpy(out, digest, copy_len); + hal_secret_zeroize(digest, sizeof(digest)); + hal_secret_zeroize(&hash, sizeof(hash)); return 0; } diff --git a/hal/stm32u3.c b/hal/stm32u3.c index b79c30613b..762eaf5b4c 100644 --- a/hal/stm32u3.c +++ b/hal/stm32u3.c @@ -27,13 +27,16 @@ */ #include +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #include #include -#include "hal/stm32u3.h" #include "hal.h" #include "printf.h" +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ +#include "hal/stm32u3.h" +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE static void RAMFUNCTION flash_set_waitstates(unsigned int waitstates) { uint32_t reg = FLASH_ACR; @@ -148,6 +151,7 @@ void RAMFUNCTION hal_flash_opt_lock(void) if ((FLASH_NS_CR & FLASH_CR_OPTLOCK) == 0) FLASH_NS_CR |= FLASH_CR_OPTLOCK; } +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ /* Erase — matches STM32U5 hal pattern exactly (same Cortex-M33 flash controller) */ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) @@ -161,7 +165,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) if (address < ARCH_FLASH_OFFSET) return -1; - end_address = address + len - 1; + end_address = address + len; for (p = address; p < end_address; p += FLASH_PAGE_SIZE) { uint32_t reg; uint32_t bker = 0; @@ -194,6 +198,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) return 0; } +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE /* --- UART: USART1 on PA9 (TX) / PA10 (RX), AF7 --- */ #define USART1_BASE (0x40013800U) @@ -356,3 +361,4 @@ void RAMFUNCTION hal_cache_invalidate(void) ; ICACHE_SR |= ICACHE_SR_BSYENDF; } +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ diff --git a/hal/stm32u3.h b/hal/stm32u3.h index 8636c169cc..92602904c0 100644 --- a/hal/stm32u3.h +++ b/hal/stm32u3.h @@ -36,9 +36,11 @@ #include /* Assembly helpers */ +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #define DMB() __asm__ volatile ("dmb") #define ISB() __asm__ volatile ("isb") #define DSB() __asm__ volatile ("dsb") +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ /* -------- RCC (AHB1 + 0x10C00 = 0x40030C00) -------- */ #define RCC_BASE (0x40030C00) @@ -125,10 +127,12 @@ #define FLASH_ACR_LPM (1 << 11) /* Non-secure flash registers (fail with PGSERR on programmed pages) */ +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #define FLASH_NS_KEYR (*(volatile uint32_t *)(FLASH_BASE + 0x08)) #define FLASH_NS_OPTKEYR (*(volatile uint32_t *)(FLASH_BASE + 0x10)) #define FLASH_NS_SR (*(volatile uint32_t *)(FLASH_BASE + 0x20)) #define FLASH_NS_CR (*(volatile uint32_t *)(FLASH_BASE + 0x28)) +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ /* Secure flash registers (unused when TZEN=0, kept for reference) */ #define FLASH_SKEYR (*(volatile uint32_t *)(FLASH_BASE + 0x0C)) diff --git a/hal/stm32wb.c b/hal/stm32wb.c index 6de1b9ddea..b17e2bfaa0 100644 --- a/hal/stm32wb.c +++ b/hal/stm32wb.c @@ -20,7 +20,9 @@ */ #include +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #include "image.h" +#endif #ifndef __WOLFBOOT #undef WOLFSSL_STM32_PKA @@ -34,7 +36,9 @@ PKA_HandleTypeDef hpka = { }; /* STM32 WB register configuration */ /* Assembly helpers */ +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #define DMB() __asm__ volatile ("dmb") +#endif /*** RCC ***/ #ifndef RCC_BASE @@ -90,10 +94,12 @@ PKA_HandleTypeDef hpka = { }; #ifndef FLASH_BASE #define FLASH_BASE (0x58004000) #endif +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE #define FLASH_ACR (*(volatile uint32_t *)(FLASH_BASE + 0x00)) #define FLASH_KEY (*(volatile uint32_t *)(FLASH_BASE + 0x08)) #define FLASH_SR (*(volatile uint32_t *)(FLASH_BASE + 0x10)) #define FLASH_CR (*(volatile uint32_t *)(FLASH_BASE + 0x14)) +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ #define FLASHMEM_ADDRESS_SPACE (0x08000000) #define FLASH_PAGE_SIZE (0x1000) /* 4KB */ @@ -126,6 +132,7 @@ PKA_HandleTypeDef hpka = { }; #define FLASH_KEY2 (0xCDEF89ABUL) +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE static void RAMFUNCTION flash_set_waitstates(unsigned int waitstates) { uint32_t reg = FLASH_ACR; @@ -143,9 +150,11 @@ static void RAMFUNCTION flash_clear_errors(void) { FLASH_SR |= ( FLASH_SR_SIZERR | FLASH_SR_PGAERR | FLASH_SR_WRPERR | FLASH_SR_PROGERR); } +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE void RAMFUNCTION hal_flash_unlock(void) { flash_wait_complete(); @@ -211,6 +220,7 @@ int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) FLASH_CR &= ~FLASH_CR_PG; return 0; } +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) @@ -220,7 +230,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) if (len == 0) return -1; address -= FLASHMEM_ADDRESS_SPACE; - end_address = address + len - 1; + end_address = address + len; flash_wait_complete(); for (p = address; p < end_address; p += FLASH_PAGE_SIZE) { uint32_t reg; @@ -236,6 +246,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) return 0; } +#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE static void clock_pll_off(void) { uint32_t reg32; @@ -358,4 +369,5 @@ uint32_t HAL_GetTick(void) return 0; } -#endif +#endif /* WOLFSSL_STM32_PKA */ +#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */ diff --git a/hal/va416x0.c b/hal/va416x0.c index 26c06f2212..446e3cd1e9 100644 --- a/hal/va416x0.c +++ b/hal/va416x0.c @@ -21,6 +21,7 @@ #include +#ifndef WOLFBOOT_UNIT_TEST_VA416X0_FRAM #include "image.h" #include "string.h" @@ -40,7 +41,9 @@ #include "printf.h" #include "loader.h" +#endif +#ifndef WOLFBOOT_UNIT_TEST_VA416X0_FRAM const stc_iocfg_pin_cfg_t bootDefaultConfig[] = { {VOR_PORTB,14,en_iocfg_dir_dncare, {{.fltclk=0,.invinp=0,.iewo=0,.opendrn=0,.invout=0,.plevel=0,.pen=0,.pwoa=0,.funsel=3,.iodis=0}}}, /* UART1 TX */ @@ -163,6 +166,7 @@ void uart_flush(void) while (DEBUG_UART_BASE->TXSTATUS & UART_TXSTATUS_WRBUSY_Msk); } #endif /* DEBUG_UART */ +#endif /* !WOLFBOOT_UNIT_TEST_VA416X0_FRAM */ /* FRAM Driver */ @@ -194,6 +198,15 @@ static void FRAM_WaitIdle(uint8_t spiBank) (SPI_FIFO_CLR_RXFIFO_Msk | SPI_FIFO_CLR_TXFIFO_Msk); } +static void FRAM_AbortWriteTransaction(uint8_t spiBank) +{ + /* Terminate a split write transaction after a command-phase failure so + * the next FRAM operation does not inherit the previous chip-select state. + */ + FRAM_WaitIdle(spiBank); + spiHandle.state = hal_spi_state_ready; +} + /* Init SPI FRAM access */ hal_status_t FRAM_Init(uint8_t spiBank, uint8_t csNum) { @@ -220,10 +233,13 @@ hal_status_t FRAM_Init(uint8_t spiBank, uint8_t csNum) spiData[0] = FRAM_WREN; /* Set Write Enable Latch(WEL) bit */ status = HAL_Spi_Transmit(&spiHandle, spiData, 1, 0, true); HAL_Timer_DelayMs(1); - status = HAL_Spi_Transmit(&spiHandle, spiData, 1, 0, true); - spiData[0] = FRAM_WRSR; /* Write single-byte Status Register message */ - spiData[1] = 0x00; /* Clear the BP1/BP0 protection */ - status = HAL_Spi_Transmit(&spiHandle, spiData, 2, 0, true); + if (status == hal_status_ok) + status = HAL_Spi_Transmit(&spiHandle, spiData, 1, 0, true); + if (status == hal_status_ok) { + spiData[0] = FRAM_WRSR; /* Write single-byte Status Register message */ + spiData[1] = 0x00; /* Clear the BP1/BP0 protection */ + status = HAL_Spi_Transmit(&spiHandle, spiData, 2, 0, true); + } FRAM_WaitIdle(spiBank); spiHandle.state = hal_spi_state_ready; } @@ -255,11 +271,17 @@ hal_status_t FRAM_Write(uint8_t spiBank, uint32_t addr, uint8_t *buf, spiData[0] = FRAM_WREN; status = HAL_Spi_Transmit(&spiHandle, spiData, 1, 0, true); + if (status != hal_status_ok) + return status; spiData[0] = FRAM_WRITE; /* Write command */ spiData[1] = (uint8_t)((addr>>16) & 0xFF); /* Address high byte */ spiData[2] = (uint8_t)((addr>>8) & 0xFF); /* Address mid byte */ spiData[3] = (uint8_t)( addr & 0xFF); /* Address low byte */ status = HAL_Spi_Transmit(&spiHandle, spiData, 4, 0, false); + if (status != hal_status_ok) { + FRAM_AbortWriteTransaction(spiBank); + return status; + } return HAL_Spi_Transmit(&spiHandle, buf, len, 0, true); } @@ -319,6 +341,7 @@ hal_status_t FRAM_Erase(uint8_t spiBank, uint32_t addr, uint32_t len) return 0; } +#ifndef WOLFBOOT_UNIT_TEST_VA416X0_FRAM void RAMFUNCTION hal_flash_unlock(void) { @@ -654,3 +677,4 @@ uint64_t hal_get_timer_us(void) ((uint64_t)elapsed_ticks * 1000000ULL / SystemCoreClock); } #endif +#endif /* !WOLFBOOT_UNIT_TEST_VA416X0_FRAM */ diff --git a/hal/x86_64_efi.c b/hal/x86_64_efi.c index 63ff3664d5..49f93c9f2f 100644 --- a/hal/x86_64_efi.c +++ b/hal/x86_64_efi.c @@ -112,7 +112,7 @@ void RAMFUNCTION x86_64_efi_do_boot(uint32_t *boot_addr, uint8_t *dts_address) mem_path_device->Header.SubType = EFI_DEVICE_PATH_PROTOCOL_MEM_SUBTYPE; mem_path_device->MemoryType = EfiLoaderData; mem_path_device->StartingAddress = (EFI_PHYSICAL_ADDRESS)boot_addr; - mem_path_device->EndingAddress = (EFI_PHYSICAL_ADDRESS)(boot_addr+*size); + mem_path_device->EndingAddress = (EFI_PHYSICAL_ADDRESS)((uint8_t*)boot_addr+*size); SetDevicePathNodeLength(&mem_path_device->Header, sizeof(MEMMAP_DEVICE_PATH)); diff --git a/options.mk b/options.mk index 45e292395c..37f676d797 100644 --- a/options.mk +++ b/options.mk @@ -63,6 +63,7 @@ ifeq ($(WOLFBOOT_ATTESTATION_IAK),1) endif ifeq ($(WOLFBOOT_UDS_UID_FALLBACK_FORTEST),1) + $(warning WOLFBOOT_UDS_UID_FALLBACK_FORTEST=1 derives UDS from the publicly-readable device UID; do not use in production) CFLAGS+=-D"WOLFBOOT_UDS_UID_FALLBACK_FORTEST" endif @@ -824,6 +825,7 @@ ifeq ($(BOOT_BENCHMARK),1) endif ifeq ($(ALLOW_DOWNGRADE),1) + $(warning ALLOW_DOWNGRADE=1 disables anti-rollback enforcement; signed older firmware images can replace newer ones) CFLAGS+= -D"ALLOW_DOWNGRADE" endif diff --git a/src/arm_tee_psa_ipc.c b/src/arm_tee_psa_ipc.c index 422dc7a562..aaf752673d 100644 --- a/src/arm_tee_psa_ipc.c +++ b/src/arm_tee_psa_ipc.c @@ -262,6 +262,9 @@ static struct wolfboot_ps_entry *wolfboot_ps_alloc(psa_storage_uid_t uid) return NULL; } +static int32_t arm_tee_psa_ps_dispatch(int32_t type, const psa_invec *in_vec, + size_t in_len, psa_outvec *out_vec, size_t out_len); + static psa_status_t wolfboot_psa_open_key(psa_key_id_t id, psa_key_id_t *key) { psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; @@ -774,136 +777,7 @@ static int32_t arm_tee_psa_dispatch(psa_handle_t handle, int32_t type, } if (handle == (psa_handle_t)ARM_TEE_PROTECTED_STORAGE_HANDLE) { - if (type == ARM_TEE_PS_SET) { - const psa_storage_uid_t *uid; - const void *data; - const psa_storage_create_flags_t *flags; - struct wolfboot_ps_entry *entry; - size_t data_len; - if (in_vec == NULL || in_len < 3) { - return PSA_ERROR_INVALID_ARGUMENT; - } - uid = (const psa_storage_uid_t *)in_vec[0].base; - data = in_vec[1].base; - flags = (const psa_storage_create_flags_t *)in_vec[2].base; - /* Snapshot the NS-supplied length once into a Secure-stack local. - * in_vec lives in NS memory and may be mutated concurrently (a - * preempting NS interrupt or NS-accessible DMA), so re-reading - * in_vec[1].len after the bounds check would allow a TOCTOU - * double-fetch to grow the copy past WOLFBOOT_PS_MAX_DATA. */ - data_len = in_vec[1].len; - if (uid == NULL || flags == NULL) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if (data_len > WOLFBOOT_PS_MAX_DATA) { - return PSA_ERROR_INSUFFICIENT_STORAGE; - } - entry = wolfboot_ps_find(*uid); - if (entry == NULL) { - entry = wolfboot_ps_alloc(*uid); - if (entry == NULL) { - return PSA_ERROR_INSUFFICIENT_STORAGE; - } - } else if ((entry->flags & PSA_STORAGE_FLAG_WRITE_ONCE) != 0U) { - return PSA_ERROR_NOT_PERMITTED; - } - if (data_len > 0 && data == NULL) { - return PSA_ERROR_INVALID_ARGUMENT; - } - if (data_len > 0) { - XMEMCPY(entry->data, data, data_len); - } - entry->size = data_len; - entry->flags = *flags; - return PSA_SUCCESS; - } - if (type == ARM_TEE_PS_GET) { - const psa_storage_uid_t *uid; - const rot_size_t *offset; - struct wolfboot_ps_entry *entry; - size_t read_len; - if (in_vec == NULL || in_len < 2 || out_vec == NULL || out_len < 1) { - return PSA_ERROR_INVALID_ARGUMENT; - } - uid = (const psa_storage_uid_t *)in_vec[0].base; - offset = (const rot_size_t *)in_vec[1].base; - if (uid == NULL || offset == NULL) { - return PSA_ERROR_INVALID_ARGUMENT; - } - entry = wolfboot_ps_find(*uid); - if (entry == NULL) { - return PSA_ERROR_DOES_NOT_EXIST; - } - if (*offset > entry->size) { - return PSA_ERROR_INVALID_ARGUMENT; - } - read_len = entry->size - *offset; - if (read_len > out_vec[0].len) { - read_len = out_vec[0].len; - } - if (read_len > 0 && out_vec[0].base != NULL) { - XMEMCPY(out_vec[0].base, entry->data + *offset, read_len); - } - out_vec[0].len = read_len; - return PSA_SUCCESS; - } - if (type == ARM_TEE_PS_GET_INFO) { - const psa_storage_uid_t *uid; - struct wolfboot_ps_entry *entry; - if (in_vec == NULL || in_len < 1 || out_vec == NULL || out_len < 1) { - return PSA_ERROR_INVALID_ARGUMENT; - } - uid = (const psa_storage_uid_t *)in_vec[0].base; - if (uid == NULL) { - return PSA_ERROR_INVALID_ARGUMENT; - } - entry = wolfboot_ps_find(*uid); - if (entry == NULL) { - return PSA_ERROR_DOES_NOT_EXIST; - } - { - struct psa_storage_info_t info; - info.capacity = WOLFBOOT_PS_MAX_DATA; - info.size = entry->size; - info.flags = entry->flags; - if (out_vec[0].len < sizeof(info)) { - return PSA_ERROR_BUFFER_TOO_SMALL; - } - XMEMCPY(out_vec[0].base, &info, sizeof(info)); - out_vec[0].len = sizeof(info); - } - return PSA_SUCCESS; - } - if (type == ARM_TEE_PS_REMOVE) { - const psa_storage_uid_t *uid; - struct wolfboot_ps_entry *entry; - if (in_vec == NULL || in_len < 1) { - return PSA_ERROR_INVALID_ARGUMENT; - } - uid = (const psa_storage_uid_t *)in_vec[0].base; - if (uid == NULL) { - return PSA_ERROR_INVALID_ARGUMENT; - } - entry = wolfboot_ps_find(*uid); - if (entry == NULL) { - return PSA_ERROR_DOES_NOT_EXIST; - } - wc_ForceZero(entry->data, sizeof(entry->data)); - entry->in_use = 0; - entry->uid = 0; - entry->size = 0; - entry->flags = 0; - return PSA_SUCCESS; - } - if (type == ARM_TEE_PS_GET_SUPPORT) { - if (out_vec != NULL && out_len >= 1 && out_vec[0].base != NULL) { - uint32_t support = 0; - XMEMCPY(out_vec[0].base, &support, sizeof(support)); - out_vec[0].len = sizeof(support); - } - return PSA_SUCCESS; - } - return PSA_ERROR_NOT_SUPPORTED; + return arm_tee_psa_ps_dispatch(type, in_vec, in_len, out_vec, out_len); } if (handle == (psa_handle_t)ARM_TEE_ATTESTATION_HANDLE) { @@ -1005,6 +879,151 @@ static int32_t arm_tee_psa_dispatch(psa_handle_t handle, int32_t type, return PSA_ERROR_NOT_SUPPORTED; } +static int32_t arm_tee_psa_ps_dispatch(int32_t type, const psa_invec *in_vec, + size_t in_len, psa_outvec *out_vec, size_t out_len) +{ + if (type == ARM_TEE_PS_SET) { + const psa_storage_uid_t *uid; + const void *data; + const psa_storage_create_flags_t *flags; + struct wolfboot_ps_entry *entry; + size_t data_len; + if (in_vec == NULL || in_len < 3) { + return PSA_ERROR_INVALID_ARGUMENT; + } + uid = (const psa_storage_uid_t *)in_vec[0].base; + data = in_vec[1].base; + flags = (const psa_storage_create_flags_t *)in_vec[2].base; + /* Snapshot the NS-supplied length once into a Secure-stack local. + * in_vec lives in NS memory and may be mutated concurrently (a + * preempting NS interrupt or NS-accessible DMA), so re-reading + * in_vec[1].len after the bounds check would allow a TOCTOU + * double-fetch to grow the copy past WOLFBOOT_PS_MAX_DATA. */ + data_len = in_vec[1].len; + if (uid == NULL || in_vec[0].len < sizeof(*uid) || + flags == NULL || in_vec[2].len < sizeof(*flags)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (data_len > WOLFBOOT_PS_MAX_DATA) { + return PSA_ERROR_INSUFFICIENT_STORAGE; + } + entry = wolfboot_ps_find(*uid); + if (entry == NULL) { + entry = wolfboot_ps_alloc(*uid); + if (entry == NULL) { + return PSA_ERROR_INSUFFICIENT_STORAGE; + } + } else if ((entry->flags & PSA_STORAGE_FLAG_WRITE_ONCE) != 0U) { + return PSA_ERROR_NOT_PERMITTED; + } + if (data_len > 0 && data == NULL) { + return PSA_ERROR_INVALID_ARGUMENT; + } + if (data_len > 0) { + XMEMCPY(entry->data, data, data_len); + } + entry->size = data_len; + entry->flags = *flags; + return PSA_SUCCESS; + } + if (type == ARM_TEE_PS_GET) { + const psa_storage_uid_t *uid; + const rot_size_t *offset; + struct wolfboot_ps_entry *entry; + size_t read_len; + if (in_vec == NULL || in_len < 2 || out_vec == NULL || out_len < 1) { + return PSA_ERROR_INVALID_ARGUMENT; + } + uid = (const psa_storage_uid_t *)in_vec[0].base; + offset = (const rot_size_t *)in_vec[1].base; + if (uid == NULL || in_vec[0].len < sizeof(*uid) || + offset == NULL || in_vec[1].len < sizeof(*offset)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + entry = wolfboot_ps_find(*uid); + if (entry == NULL) { + return PSA_ERROR_DOES_NOT_EXIST; + } + if (*offset > entry->size) { + return PSA_ERROR_INVALID_ARGUMENT; + } + read_len = entry->size - *offset; + if (read_len > out_vec[0].len) { + read_len = out_vec[0].len; + } + if (read_len > 0 && out_vec[0].base != NULL) { + XMEMCPY(out_vec[0].base, entry->data + *offset, read_len); + } + out_vec[0].len = read_len; + return PSA_SUCCESS; + } + if (type == ARM_TEE_PS_GET_INFO) { + const psa_storage_uid_t *uid; + struct wolfboot_ps_entry *entry; + if (in_vec == NULL || in_len < 1 || out_vec == NULL || out_len < 1) { + return PSA_ERROR_INVALID_ARGUMENT; + } + uid = (const psa_storage_uid_t *)in_vec[0].base; + if (uid == NULL || in_vec[0].len < sizeof(*uid)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + entry = wolfboot_ps_find(*uid); + if (entry == NULL) { + return PSA_ERROR_DOES_NOT_EXIST; + } + { + struct psa_storage_info_t info; + info.capacity = WOLFBOOT_PS_MAX_DATA; + info.size = entry->size; + info.flags = entry->flags; + if (out_vec[0].len < sizeof(info)) { + return PSA_ERROR_BUFFER_TOO_SMALL; + } + XMEMCPY(out_vec[0].base, &info, sizeof(info)); + out_vec[0].len = sizeof(info); + } + return PSA_SUCCESS; + } + if (type == ARM_TEE_PS_REMOVE) { + const psa_storage_uid_t *uid; + struct wolfboot_ps_entry *entry; + if (in_vec == NULL || in_len < 1) { + return PSA_ERROR_INVALID_ARGUMENT; + } + uid = (const psa_storage_uid_t *)in_vec[0].base; + if (uid == NULL || in_vec[0].len < sizeof(*uid)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + entry = wolfboot_ps_find(*uid); + if (entry == NULL) { + return PSA_ERROR_DOES_NOT_EXIST; + } + wc_ForceZero(entry->data, sizeof(entry->data)); + entry->in_use = 0; + entry->uid = 0; + entry->size = 0; + entry->flags = 0; + return PSA_SUCCESS; + } + if (type == ARM_TEE_PS_GET_SUPPORT) { + if (out_vec != NULL && out_len >= 1 && out_vec[0].base != NULL) { + uint32_t support = 0; + XMEMCPY(out_vec[0].base, &support, sizeof(support)); + out_vec[0].len = sizeof(support); + } + return PSA_SUCCESS; + } + return PSA_ERROR_NOT_SUPPORTED; +} + +#ifdef UNIT_TEST +int32_t arm_tee_psa_test_ps_dispatch(int32_t type, const psa_invec *in_vec, + size_t in_len, psa_outvec *out_vec, size_t out_len) +{ + return arm_tee_psa_ps_dispatch(type, in_vec, in_len, out_vec, out_len); +} +#endif + int32_t arm_tee_psa_call(psa_handle_t handle, int32_t type, const psa_invec *in_vec, size_t in_len, psa_outvec *out_vec, size_t out_len) diff --git a/src/image.c b/src/image.c index 6aba68e7a6..3bedbef9ea 100644 --- a/src/image.c +++ b/src/image.c @@ -501,6 +501,7 @@ static void wolfBoot_verify_signature_rsa_common(uint8_t key_slot, ret = wh_Client_RsaSetKeyId(&rsa, hsmKeyIdPubKey); #endif if (ret != 0) { + wc_FreeRsaKey(&rsa); return; } #else @@ -509,11 +510,13 @@ static void wolfBoot_verify_signature_rsa_common(uint8_t key_slot, ret = wh_Client_KeyCache(&hsmClientCtx, WH_NVM_FLAGS_USAGE_VERIFY, NULL, 0, pubkey, pubkey_sz, &hsmKeyId); if (ret != WH_ERROR_OK) { + wc_FreeRsaKey(&rsa); return; } /* Associate this RSA struct with the keyId of the cached key */ ret = wh_Client_RsaSetKeyId(&rsa, hsmKeyId); if (ret != WH_ERROR_OK) { + wc_FreeRsaKey(&rsa); return; } #endif /* !WOLFBOOT_USE_WOLFHSM_PUBKEY_ID */ @@ -533,6 +536,7 @@ static void wolfBoot_verify_signature_rsa_common(uint8_t key_slot, !defined(WOLFBOOT_USE_WOLFHSM_PUBKEY_ID) /* evict the key after use, since we aren't using the RSA import API */ if (WH_ERROR_OK != wh_Client_KeyEvict(&hsmClientCtx, hsmKeyId)) { + wc_FreeRsaKey(&rsa); return; } #elif defined(WOLFBOOT_CERT_CHAIN_VERIFY) diff --git a/src/libwolfboot.c b/src/libwolfboot.c index 2ad131fd30..5a37ac5f9e 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -1623,6 +1623,10 @@ static int RAMFUNCTION hal_set_key(const uint8_t *k, const uint8_t *nonce) ret = hal_flash_erase(addr_align, WOLFBOOT_SECTOR_SIZE); #endif exit_lock: +#if !defined(WOLFBOOT_SMALL_STACK) && !defined(NVM_FLASH_WRITEONCE) && \ + !defined(WOLFBOOT_ENCRYPT_CACHE) + ForceZero(ENCRYPT_CACHE, NVM_CACHE_SIZE); +#endif hal_flash_lock(); return ret; #endif diff --git a/src/tpm.c b/src/tpm.c index dc132c5e24..6a11aad253 100644 --- a/src/tpm.c +++ b/src/tpm.c @@ -1007,8 +1007,8 @@ int wolfBoot_unseal_blob(const uint8_t* pubkey_hint, uint8_t* policyRef = NULL; /* optional nonce */ uint32_t policyRefSz = 0; - if (policy == NULL || policySz <= 0 || secret == NULL || - secret_sz == NULL) { + if (policy == NULL || policySz < (uint16_t)sizeof(pcrMask) || + secret == NULL || secret_sz == NULL) { return -1; } diff --git a/src/update_disk.c b/src/update_disk.c index ca1d2f53ff..dfcd8e456f 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -263,7 +263,7 @@ void RAMFUNCTION wolfBoot_start(void) struct stage2_parameter *stage2_params; #endif struct wolfBoot_image os_image; - int pA_ver = 0, pB_ver = 0; + uint32_t pA_ver = 0U, pB_ver = 0U; uint32_t pA_ver_u = 0U, pB_ver_u = 0U; uint32_t cur_part = 0; int ret = -1; @@ -358,10 +358,10 @@ void RAMFUNCTION wolfBoot_start(void) wolfBoot_panic(); } - if (pA_ver > 0) - pA_ver_u = (uint32_t)pA_ver; - if (pB_ver > 0) - pB_ver_u = (uint32_t)pB_ver; + if (pA_ver != 0U) + pA_ver_u = pA_ver; + if (pB_ver != 0U) + pB_ver_u = pB_ver; wolfBoot_printf("Versions, A:%u B:%u\r\n", pA_ver_u, pB_ver_u); wolfBoot_printf("Load block size: %dKB\r\n", DISK_BLOCK_SIZE / 1024); diff --git a/src/update_flash_hwswap.c b/src/update_flash_hwswap.c index 3aac85f4c4..22ba5fde49 100644 --- a/src/update_flash_hwswap.c +++ b/src/update_flash_hwswap.c @@ -47,17 +47,9 @@ void RAMFUNCTION wolfBoot_start(void) struct wolfBoot_image fw_image; uint8_t p_state; #ifndef ALLOW_DOWNGRADE - int boot_v_raw = (int)wolfBoot_current_firmware_version(); - int update_v_raw = (int)wolfBoot_update_firmware_version(); - uint32_t boot_v = 0U; - uint32_t update_v = 0U; - uint32_t max_v; - - if (boot_v_raw >= 0) - boot_v = (uint32_t)boot_v_raw; - if (update_v_raw >= 0) - update_v = (uint32_t)update_v_raw; - max_v = (boot_v > update_v) ? boot_v : update_v; + 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 active = wolfBoot_dualboot_candidate(); diff --git a/src/update_ram.c b/src/update_ram.c index 1708674df5..35944358c9 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -232,17 +232,9 @@ void RAMFUNCTION wolfBoot_start(void) uint32_t dts_size = 0; #endif #if !defined(ALLOW_DOWNGRADE) && defined(WOLFBOOT_FIXED_PARTITIONS) - int boot_v_raw = (int)wolfBoot_current_firmware_version(); - int update_v_raw = (int)wolfBoot_update_firmware_version(); - uint32_t boot_v = 0U; - uint32_t update_v = 0U; - uint32_t max_v = 0U; - - if (boot_v_raw >= 0) - boot_v = (uint32_t)boot_v_raw; - if (update_v_raw >= 0) - update_v = (uint32_t)update_v_raw; - max_v = (boot_v > update_v) ? boot_v : update_v; + 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)); diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index b8a82270a6..a399633097 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -1531,8 +1531,10 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, ALIGN_8(header_idx); if (CMD.custom_tlv[i].buffer == NULL) { + uint8_t tmp[sizeof(CMD.custom_tlv[i].val)]; + header_store_u64_le(tmp, CMD.custom_tlv[i].val); header_append_tag(header, &header_idx, CMD.custom_tlv[i].tag, - CMD.custom_tlv[i].len, &CMD.custom_tlv[i].val); + CMD.custom_tlv[i].len, tmp); } else { header_append_tag(header, &header_idx, CMD.custom_tlv[i].tag, CMD.custom_tlv[i].len, CMD.custom_tlv[i].buffer); diff --git a/tools/uart-flash-server/ufserver.c b/tools/uart-flash-server/ufserver.c index bd15999268..0c47a6db58 100644 --- a/tools/uart-flash-server/ufserver.c +++ b/tools/uart-flash-server/ufserver.c @@ -388,7 +388,7 @@ static void serve_update(uint8_t *base, const char *uart_dev) idx++; } - v = buf[4] + (buf[3] << 8) + (buf[2] << 16) + (buf[1] << 24); + v = buf[4] + (buf[3] << 8) + (buf[2] << 16) + ((uint32_t)buf[1] << 24); printf("Boot partition version from test app: %u\n", v); continue; } @@ -409,7 +409,7 @@ static void serve_update(uint8_t *base, const char *uart_dev) } if (idx == 5) { printf("\r\n** TARGET REBOOT **\n"); - v = buf[1] + (buf[2] << 8) + (buf[3] << 16) + (buf[4] << 24); + v = buf[1] + (buf[2] << 8) + (buf[3] << 16) + ((uint32_t)buf[4] << 24); printf("Version running on target: %u\n", v); } continue; diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index d4f163828d..a40c523085 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -51,7 +51,7 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128 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-ram unit-update-ram-nofixed unit-pkcs11_store unit-psa_store unit-disk \ + unit-update-flash-enc unit-update-ram unit-update-ram-nofixed unit-update-ram-noramboot unit-update-flash-hwswap unit-pkcs11_store unit-psa_store unit-disk \ unit-update-disk 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-store-sbrk \ unit-tpm-blob unit-policy-create unit-policy-sign unit-rot-auth unit-sdhci-response-bits \ @@ -63,10 +63,17 @@ TESTS+=unit-fit-gzip unit-fit-nogzip TESTS+=unit-fit-fpga TESTS+=unit-mpusize TESTS+=unit-flash-erase-h7 +TESTS+=unit-flash-erase-wb +TESTS+=unit-flash-erase-l0 +TESTS+=unit-flash-erase-g0 +TESTS+=unit-flash-erase-c0 +TESTS+=unit-flash-erase-u3 TESTS+=unit-otp-keystore TESTS+=unit-x86-paging-oob TESTS+=unit-fwtpm-nv-oob TESTS+=unit-elf-bss-guard +TESTS+=unit-arm-tee-psa-ipc +TESTS+=unit-va416x0-fram # linux_loader.c is x86 32-bit only, so its unit tests need a working 32-bit # (multilib) toolchain. Probe whether "gcc -m32" can link, and only add the @@ -104,6 +111,7 @@ run: $(TESTS) done python3 unit-sign-delta-tlv.py || exit 1 python3 unit-sign-delta-cert-inv-off.py || exit 1 + python3 unit-sign-custom-tlv-le.py || exit 1 WOLFCRYPT_SRC:=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha.c \ @@ -149,6 +157,14 @@ unit-update-ram: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 -DNO_XIP \ -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE +unit-update-flash-hwswap: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 -DNO_XIP \ + -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE +unit-update-ram-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 -DNO_XIP -DWOLFBOOT_NO_RAMBOOT \ + -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE unit-update-ram-nofixed: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 \ @@ -263,6 +279,14 @@ unit-store-sbrk: unit-store-sbrk.c ../../src/store_sbrk.c unit-string: ../../include/target.h unit-string.c gcc -o $@ $^ $(CFLAGS) -DDEBUG_UART -DPRINTF_ENABLED $(LDFLAGS) +unit-arm-tee-psa-ipc: ../../include/target.h unit-arm-tee-psa-ipc.c ../../src/arm_tee_psa_ipc.c + gcc -o $@ unit-arm-tee-psa-ipc.c $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFPSA)/wolfpsa \ + -ffunction-sections -fdata-sections \ + $(LDFLAGS) -Wl,--gc-sections + +unit-va416x0-fram: unit-va416x0-fram.c ../../hal/va416x0.c + gcc -o $@ unit-va416x0-fram.c $(CFLAGS) $(LDFLAGS) + unit-max-space: ../../include/target.h unit-max-space.c gcc -o $@ $^ $(CFLAGS) $(LDFLAGS) @@ -276,6 +300,32 @@ unit-mpusize: ../../include/target.h unit-mpusize.c unit-flash-erase-h7: unit-flash-erase-h7.c ../../hal/stm32h7.c gcc -o $@ unit-flash-erase-h7.c $(CFLAGS) $(LDFLAGS) +# unit-flash-erase-wb includes hal/stm32wb.c directly (guarded to hal_flash_erase +# via WOLFBOOT_UNIT_TEST_FLASH_ERASE), so stm32wb.c is not a separate input. +unit-flash-erase-wb: unit-flash-erase-wb.c ../../hal/stm32wb.c + gcc -o $@ unit-flash-erase-wb.c $(CFLAGS) $(LDFLAGS) + +# unit-flash-erase-l0 includes hal/stm32l0.c directly (guarded to hal_flash_erase +# via WOLFBOOT_UNIT_TEST_FLASH_ERASE), so stm32l0.c is not a separate input. +unit-flash-erase-l0: unit-flash-erase-l0.c ../../hal/stm32l0.c + gcc -o $@ unit-flash-erase-l0.c $(CFLAGS) $(LDFLAGS) + +# unit-flash-erase-g0 includes hal/stm32g0.c directly (guarded to hal_flash_erase +# via WOLFBOOT_UNIT_TEST_FLASH_ERASE), so stm32g0.c is not a separate input. +unit-flash-erase-g0: unit-flash-erase-g0.c ../../hal/stm32g0.c + gcc -o $@ unit-flash-erase-g0.c $(CFLAGS) $(LDFLAGS) + +# unit-flash-erase-c0 includes hal/stm32c0.c directly (guarded to hal_flash_erase +# via WOLFBOOT_UNIT_TEST_FLASH_ERASE), so stm32c0.c is not a separate input. +unit-flash-erase-c0: unit-flash-erase-c0.c ../../hal/stm32c0.c + gcc -o $@ unit-flash-erase-c0.c $(CFLAGS) $(LDFLAGS) + +# unit-flash-erase-u3 includes hal/stm32u3.c directly (guarded to hal_flash_erase +# via WOLFBOOT_UNIT_TEST_FLASH_ERASE), so stm32u3.c is not a separate input. +# -I../../ so that #include "hal/stm32u3.h" resolves from the repo root. +unit-flash-erase-u3: unit-flash-erase-u3.c ../../hal/stm32u3.c ../../hal/stm32u3.h + gcc -o $@ unit-flash-erase-u3.c -I../../ $(CFLAGS) $(LDFLAGS) + # unit-otp-keystore includes src/flash_otp_keystore.c directly (guarded to its # host-portable code via WOLFBOOT_UNIT_TEST_OTP_KEYSTORE), so it is not a # separate input. @@ -426,6 +476,12 @@ unit-update-ram: ../../include/target.h unit-update-ram.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-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-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) + unit-update-disk: ../../include/target.h unit-update-disk.c gcc -o $@ unit-update-disk.c $(CFLAGS) $(LDFLAGS) diff --git a/tools/unit-tests/arm_cmse.h b/tools/unit-tests/arm_cmse.h new file mode 100644 index 0000000000..7423acb491 --- /dev/null +++ b/tools/unit-tests/arm_cmse.h @@ -0,0 +1,10 @@ +#ifndef UNIT_TEST_ARM_CMSE_H +#define UNIT_TEST_ARM_CMSE_H + +#include + +#define CMSE_NONSECURE 0 +#define cmse_check_address_range(ptr, size, flags) \ + ((void *)(uintptr_t)(ptr)) + +#endif diff --git a/tools/unit-tests/unit-arm-tee-psa-ipc.c b/tools/unit-tests/unit-arm-tee-psa-ipc.c new file mode 100644 index 0000000000..d0e560a10d --- /dev/null +++ b/tools/unit-tests/unit-arm-tee-psa-ipc.c @@ -0,0 +1,193 @@ +#include +#include +#include +#include + +void ForceZero(void *mem, size_t len) +{ + volatile uint8_t *p = (volatile uint8_t *)mem; + + while (len-- > 0) { + *p++ = 0; + } +} + +void wc_ForceZero(void *mem, size_t len) +{ + ForceZero(mem, len); +} + +#include "../../src/arm_tee_psa_ipc.c" + +static void reset_ps_state(void) +{ + memset(g_ps_entries, 0, sizeof(g_ps_entries)); +} + +START_TEST(test_ps_set_rejects_short_uid_vector) +{ + psa_storage_uid_t uid = 0x1122334455667788ULL; + psa_storage_create_flags_t flags = 0; + uint8_t data[4] = {1, 2, 3, 4}; + psa_invec in_vec[3]; + + reset_ps_state(); + in_vec[0].base = &uid; + in_vec[0].len = sizeof(uid) - 1; + in_vec[1].base = data; + in_vec[1].len = sizeof(data); + in_vec[2].base = &flags; + in_vec[2].len = sizeof(flags); + + ck_assert_int_eq( + arm_tee_psa_test_ps_dispatch(ARM_TEE_PS_SET, in_vec, 3, NULL, 0), + PSA_ERROR_INVALID_ARGUMENT); +} +END_TEST + +START_TEST(test_ps_get_rejects_short_offset_vector) +{ + psa_storage_uid_t uid = 7; + rot_size_t offset = 0; + uint8_t out[8]; + psa_invec in_vec[2]; + psa_outvec out_vec[1]; + + reset_ps_state(); + g_ps_entries[0].uid = uid; + g_ps_entries[0].size = 4; + g_ps_entries[0].in_use = 1; + + in_vec[0].base = &uid; + in_vec[0].len = sizeof(uid); + in_vec[1].base = &offset; + in_vec[1].len = sizeof(offset) - 1; + out_vec[0].base = out; + out_vec[0].len = sizeof(out); + + ck_assert_int_eq( + arm_tee_psa_test_ps_dispatch(ARM_TEE_PS_GET, in_vec, 2, out_vec, 1), + PSA_ERROR_INVALID_ARGUMENT); +} +END_TEST + +START_TEST(test_ps_get_info_rejects_short_uid_vector) +{ + psa_storage_uid_t uid = 9; + struct psa_storage_info_t info; + psa_invec in_vec[1]; + psa_outvec out_vec[1]; + + reset_ps_state(); + in_vec[0].base = &uid; + in_vec[0].len = sizeof(uid) - 1; + out_vec[0].base = &info; + out_vec[0].len = sizeof(info); + + ck_assert_int_eq( + arm_tee_psa_test_ps_dispatch(ARM_TEE_PS_GET_INFO, in_vec, 1, out_vec, 1), + PSA_ERROR_INVALID_ARGUMENT); +} +END_TEST + +START_TEST(test_ps_remove_rejects_short_uid_vector) +{ + psa_storage_uid_t uid = 11; + psa_invec in_vec[1]; + + reset_ps_state(); + in_vec[0].base = &uid; + in_vec[0].len = sizeof(uid) - 1; + + ck_assert_int_eq( + arm_tee_psa_test_ps_dispatch(ARM_TEE_PS_REMOVE, in_vec, 1, NULL, 0), + PSA_ERROR_INVALID_ARGUMENT); +} +END_TEST + +START_TEST(test_ps_set_get_info_remove_success_path) +{ + psa_storage_uid_t uid = 0xA5A5A5A5U; + psa_storage_create_flags_t flags = 0; + rot_size_t offset = 1; + uint8_t data[] = {0x10, 0x20, 0x30, 0x40}; + uint8_t read_buf[4] = {0}; + struct psa_storage_info_t info; + psa_invec set_in[3]; + psa_invec get_in[2]; + psa_invec info_in[1]; + psa_invec remove_in[1]; + psa_outvec get_out[1]; + psa_outvec info_out[1]; + + reset_ps_state(); + + set_in[0].base = &uid; + set_in[0].len = sizeof(uid); + set_in[1].base = data; + set_in[1].len = sizeof(data); + set_in[2].base = &flags; + set_in[2].len = sizeof(flags); + ck_assert_int_eq( + arm_tee_psa_test_ps_dispatch(ARM_TEE_PS_SET, set_in, 3, NULL, 0), + PSA_SUCCESS); + + get_in[0].base = &uid; + get_in[0].len = sizeof(uid); + get_in[1].base = &offset; + get_in[1].len = sizeof(offset); + get_out[0].base = read_buf; + get_out[0].len = sizeof(read_buf); + ck_assert_int_eq( + arm_tee_psa_test_ps_dispatch(ARM_TEE_PS_GET, get_in, 2, get_out, 1), + PSA_SUCCESS); + ck_assert_uint_eq(get_out[0].len, sizeof(data) - offset); + ck_assert_mem_eq(read_buf, data + offset, sizeof(data) - offset); + + info_in[0].base = &uid; + info_in[0].len = sizeof(uid); + info_out[0].base = &info; + info_out[0].len = sizeof(info); + ck_assert_int_eq( + arm_tee_psa_test_ps_dispatch(ARM_TEE_PS_GET_INFO, info_in, 1, info_out, 1), + PSA_SUCCESS); + ck_assert_uint_eq(info.size, sizeof(data)); + ck_assert_uint_eq(info.flags, flags); + + remove_in[0].base = &uid; + remove_in[0].len = sizeof(uid); + ck_assert_int_eq( + arm_tee_psa_test_ps_dispatch(ARM_TEE_PS_REMOVE, remove_in, 1, NULL, 0), + PSA_SUCCESS); + ck_assert_int_eq(g_ps_entries[0].in_use, 0); + ck_assert_uint_eq(g_ps_entries[0].size, 0); +} +END_TEST + +Suite *arm_tee_psa_ipc_suite(void) +{ + Suite *s = suite_create("arm-tee-psa-ipc"); + TCase *tc = tcase_create("protected-storage"); + + tcase_add_test(tc, test_ps_set_rejects_short_uid_vector); + tcase_add_test(tc, test_ps_get_rejects_short_offset_vector); + tcase_add_test(tc, test_ps_get_info_rejects_short_uid_vector); + tcase_add_test(tc, test_ps_remove_rejects_short_uid_vector); + tcase_add_test(tc, test_ps_set_get_info_remove_success_path); + suite_add_tcase(s, tc); + + return s; +} + +int main(void) +{ + int fails; + Suite *s = arm_tee_psa_ipc_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + + return fails; +} diff --git a/tools/unit-tests/unit-delta.c b/tools/unit-tests/unit-delta.c index 7257583d92..b870b32ca5 100644 --- a/tools/unit-tests/unit-delta.c +++ b/tools/unit-tests/unit-delta.c @@ -630,6 +630,22 @@ START_TEST(test_wb_patch_and_diff_size_changing_update) } END_TEST +START_TEST(test_wb_patch_and_diff_shrinking_update) +{ + uint8_t src_a[3077]; + uint8_t src_b[2048]; + + fill_pattern(src_a, sizeof(src_a), 0x27182818U); + fill_pattern(src_b, sizeof(src_b), 0x31415926U); + memcpy(src_a + 512, src_b, sizeof(src_b)); + src_a[0] = ESC; + src_a[sizeof(src_a) - 1] ^= 0x5A; + + (void)run_roundtrip_case(src_a, sizeof(src_a), src_b, sizeof(src_b), + sizeof(src_b) + DELTA_BLOCK_SIZE); +} +END_TEST + START_TEST(test_wb_patch_and_diff_single_byte_difference) { uint8_t src_a[SRC_SIZE]; @@ -677,6 +693,7 @@ Suite *patch_diff_suite(void) tcase_add_test(tc_wolfboot_delta, test_wb_diff_get_sector_size_accepts_16bit_limit); tcase_add_test(tc_wolfboot_delta, test_wb_diff_get_sector_size_rejects_values_above_16bit); tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_size_changing_update); + tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_shrinking_update); tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_single_byte_difference); suite_add_tcase(s, tc_wolfboot_delta); diff --git a/tools/unit-tests/unit-flash-erase-c0.c b/tools/unit-tests/unit-flash-erase-c0.c new file mode 100644 index 0000000000..9920d43972 --- /dev/null +++ b/tools/unit-tests/unit-flash-erase-c0.c @@ -0,0 +1,152 @@ +/* unit-flash-erase-c0.c + * + * Unit tests for the page-loop bound in hal_flash_erase() (hal/stm32c0.c). + * Regression for F-3963: end_address = address + len - 1 (inclusive) combined + * with p < end_address (strict) skips the final page when len % PAGE_SIZE != 0. + * + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include + +/* hal/stm32c0.c is tightly coupled to STM32C0 hardware registers. + * Compile only hal_flash_erase() in isolation by defining this guard; + * all other functions and hardware macros are excluded and replaced below. */ +#define WOLFBOOT_UNIT_TEST_FLASH_ERASE + +/* RAMFUNCTION must be empty on the host */ +#define RAMFUNCTION + +/* DMB is a no-op on the host (stm32c0 DMB fires before STRT is written, so + * we capture from flash_wait_complete instead). */ +#define DMB() /* nothing */ + +/* Mocked flash control/status registers */ +static uint32_t mock_FLASH_CR; +static uint32_t mock_FLASH_SR; +#define FLASH_CR mock_FLASH_CR +#define FLASH_SR mock_FLASH_SR + +/* Constants from hal/stm32c0.c (must mirror exactly). */ +#define FLASH_CR_STRT (1 << 16) +#define FLASH_CR_PER (1 << 1) +#define FLASH_CR_PNB_SHIFT 3 +#define FLASH_CR_PNB_MASK 0x7f + +/* Record the FLASH_CR value on each page-erase command (captured at the + * start of flash_wait_complete, when STRT has just been written). */ +#define ERASE_LOG_MAX 64 +static uint32_t erase_cr[ERASE_LOG_MAX]; +static int erase_log_n; + +/* Stubs for functions called by hal_flash_erase(). + * flash_wait_complete is called immediately after FLASH_CR |= FLASH_CR_STRT, + * so it is the right place to capture the current register state. + * It clears STRT to mirror what real hardware does automatically. */ +static void flash_wait_complete(void) +{ + if ((mock_FLASH_CR & FLASH_CR_STRT) && erase_log_n < ERASE_LOG_MAX) { + erase_cr[erase_log_n] = mock_FLASH_CR; + erase_log_n++; + } + mock_FLASH_CR &= ~FLASH_CR_STRT; +} +static void flash_clear_errors(void) {} + +#include "../../hal/stm32c0.c" + +/* Decode the page number field from a captured FLASH_CR value. */ +static uint32_t page_of(uint32_t cr) +{ + return (cr >> FLASH_CR_PNB_SHIFT) & FLASH_CR_PNB_MASK; +} + +static void reset_mocks(void) +{ + mock_FLASH_CR = 0; + mock_FLASH_SR = 0; + erase_log_n = 0; +} + +/* Erasing exactly one page must issue exactly one erase command. */ +START_TEST(test_erase_single_page_aligned) +{ + reset_mocks(); + hal_flash_erase(0x08000000UL, FLASH_PAGE_SIZE); + + ck_assert_int_eq(erase_log_n, 1); + ck_assert_uint_eq(page_of(erase_cr[0]), 0); +} +END_TEST + +/* Erasing two full pages must issue exactly two erase commands. */ +START_TEST(test_erase_two_pages_aligned) +{ + reset_mocks(); + hal_flash_erase(0x08000000UL, 2 * FLASH_PAGE_SIZE); + + ck_assert_int_eq(erase_log_n, 2); + ck_assert_uint_eq(page_of(erase_cr[0]), 0); + ck_assert_uint_eq(page_of(erase_cr[1]), 1); +} +END_TEST + +/* Regression for F-3963: len = PAGE_SIZE + 1 spans two pages and both must be + * erased. Before the fix, end_address = address + len - 1 landed exactly on + * the start of page 1, so the strict `p < end_address` guard excluded it. */ +START_TEST(test_erase_unaligned_len_covers_last_page) +{ + reset_mocks(); + /* len = 0x801: bytes [0..0x800], crossing into page 1 */ + hal_flash_erase(0x08000000UL, FLASH_PAGE_SIZE + 1); + + ck_assert_int_eq(erase_log_n, 2); + ck_assert_uint_eq(page_of(erase_cr[0]), 0); + ck_assert_uint_eq(page_of(erase_cr[1]), 1); +} +END_TEST + +Suite *flash_erase_c0_suite(void) +{ + Suite *s = suite_create("flash-erase-c0"); + TCase *tc = tcase_create("flash-erase-c0"); + + tcase_add_test(tc, test_erase_single_page_aligned); + tcase_add_test(tc, test_erase_two_pages_aligned); + tcase_add_test(tc, test_erase_unaligned_len_covers_last_page); + + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = flash_erase_c0_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + + return fails; +} diff --git a/tools/unit-tests/unit-flash-erase-g0.c b/tools/unit-tests/unit-flash-erase-g0.c new file mode 100644 index 0000000000..f5191454d8 --- /dev/null +++ b/tools/unit-tests/unit-flash-erase-g0.c @@ -0,0 +1,152 @@ +/* unit-flash-erase-g0.c + * + * Unit tests for the page-loop bound in hal_flash_erase() (hal/stm32g0.c). + * Regression for F-3964: end_address = address + len - 1 (inclusive) combined + * with p < end_address (strict) skips the final page when len % PAGE_SIZE != 0. + * + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include + +/* hal/stm32g0.c is tightly coupled to STM32G0 hardware registers. + * Compile only hal_flash_erase() in isolation by defining this guard; + * all other functions and hardware macros are excluded and replaced below. */ +#define WOLFBOOT_UNIT_TEST_FLASH_ERASE + +/* RAMFUNCTION must be empty on the host */ +#define RAMFUNCTION + +/* DMB is a no-op on the host (stm32g0 DMB fires before STRT is written, so + * we capture from flash_wait_complete instead). */ +#define DMB() /* nothing */ + +/* Mocked flash control/status registers */ +static uint32_t mock_FLASH_CR; +static uint32_t mock_FLASH_SR; +#define FLASH_CR mock_FLASH_CR +#define FLASH_SR mock_FLASH_SR + +/* Constants from hal/stm32g0.c (must mirror exactly). */ +#define FLASH_CR_STRT (1 << 16) +#define FLASH_CR_PER (1 << 1) +#define FLASH_CR_PNB_SHIFT 3 +#define FLASH_CR_PNB_MASK 0x7f + +/* Record the FLASH_CR value on each page-erase command (captured at the + * start of flash_wait_complete, when STRT has just been written). */ +#define ERASE_LOG_MAX 64 +static uint32_t erase_cr[ERASE_LOG_MAX]; +static int erase_log_n; + +/* Stubs for functions called by hal_flash_erase(). + * flash_wait_complete is called immediately after FLASH_CR |= FLASH_CR_STRT, + * so it is the right place to capture the current register state. + * It clears STRT to mirror what real hardware does automatically. */ +static void flash_wait_complete(void) +{ + if ((mock_FLASH_CR & FLASH_CR_STRT) && erase_log_n < ERASE_LOG_MAX) { + erase_cr[erase_log_n] = mock_FLASH_CR; + erase_log_n++; + } + mock_FLASH_CR &= ~FLASH_CR_STRT; +} +static void flash_clear_errors(void) {} + +#include "../../hal/stm32g0.c" + +/* Decode the page number field from a captured FLASH_CR value. */ +static uint32_t page_of(uint32_t cr) +{ + return (cr >> FLASH_CR_PNB_SHIFT) & FLASH_CR_PNB_MASK; +} + +static void reset_mocks(void) +{ + mock_FLASH_CR = 0; + mock_FLASH_SR = 0; + erase_log_n = 0; +} + +/* Erasing exactly one page must issue exactly one erase command. */ +START_TEST(test_erase_single_page_aligned) +{ + reset_mocks(); + hal_flash_erase(0x08000000UL, FLASH_PAGE_SIZE); + + ck_assert_int_eq(erase_log_n, 1); + ck_assert_uint_eq(page_of(erase_cr[0]), 0); +} +END_TEST + +/* Erasing two full pages must issue exactly two erase commands. */ +START_TEST(test_erase_two_pages_aligned) +{ + reset_mocks(); + hal_flash_erase(0x08000000UL, 2 * FLASH_PAGE_SIZE); + + ck_assert_int_eq(erase_log_n, 2); + ck_assert_uint_eq(page_of(erase_cr[0]), 0); + ck_assert_uint_eq(page_of(erase_cr[1]), 1); +} +END_TEST + +/* Regression for F-3964: len = PAGE_SIZE + 1 spans two pages and both must be + * erased. Before the fix, end_address = address + len - 1 landed exactly on + * the start of page 1, so the strict `p < end_address` guard excluded it. */ +START_TEST(test_erase_unaligned_len_covers_last_page) +{ + reset_mocks(); + /* len = 0x801: bytes [0..0x800], crossing into page 1 */ + hal_flash_erase(0x08000000UL, FLASH_PAGE_SIZE + 1); + + ck_assert_int_eq(erase_log_n, 2); + ck_assert_uint_eq(page_of(erase_cr[0]), 0); + ck_assert_uint_eq(page_of(erase_cr[1]), 1); +} +END_TEST + +Suite *flash_erase_g0_suite(void) +{ + Suite *s = suite_create("flash-erase-g0"); + TCase *tc = tcase_create("flash-erase-g0"); + + tcase_add_test(tc, test_erase_single_page_aligned); + tcase_add_test(tc, test_erase_two_pages_aligned); + tcase_add_test(tc, test_erase_unaligned_len_covers_last_page); + + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = flash_erase_g0_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + + return fails; +} diff --git a/tools/unit-tests/unit-flash-erase-l0.c b/tools/unit-tests/unit-flash-erase-l0.c new file mode 100644 index 0000000000..f808fda362 --- /dev/null +++ b/tools/unit-tests/unit-flash-erase-l0.c @@ -0,0 +1,137 @@ +/* unit-flash-erase-l0.c + * + * Unit tests for the page-loop bound in hal_flash_erase() (hal/stm32l0.c). + * Regression for F-3965: end_address = address + len - 1 (inclusive) combined + * with p < end_address (strict) skips the final page when len % PAGE_SIZE != 0. + * + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include + +/* hal/stm32l0.c is tightly coupled to STM32L0 hardware registers. + * Compile only hal_flash_erase() in isolation by defining this guard; + * all other functions and hardware macros are excluded and replaced below. */ +#define WOLFBOOT_UNIT_TEST_FLASH_ERASE + +/* RAMFUNCTION must be empty on the host */ +#define RAMFUNCTION + +/* DMB is a no-op on the host */ +#define DMB() /* nothing */ + +/* Mocked flash control/status registers */ +static uint32_t mock_FLASH_PECR; +static uint32_t mock_FLASH_SR; +#define FLASH_PECR mock_FLASH_PECR +#define FLASH_SR mock_FLASH_SR + +/* Mock flash memory: 4 pages of FLASH_PAGE_SIZE (128) bytes each. + * hal_flash_erase writes 0xFFFFFFFF to the first word of each erased page via: + * *(volatile uint32_t *)(p + FLASHMEM_ADDRESS_SPACE) = 0xFFFFFFFF + * Redirect that write into this buffer by making FLASHMEM_ADDRESS_SPACE the + * buffer's base address and calling hal_flash_erase with address = 0. */ +#define MOCK_FLASH_PAGES 4 +#define MOCK_FLASH_SIZE (MOCK_FLASH_PAGES * 128) +static uint8_t mock_flash_mem[MOCK_FLASH_SIZE]; +#define FLASHMEM_ADDRESS_SPACE ((uintptr_t)mock_flash_mem) + +#include "../../hal/stm32l0.c" + +static void reset_mocks(void) +{ + mock_FLASH_PECR = 0; + mock_FLASH_SR = 0; + memset(mock_flash_mem, 0xAA, sizeof(mock_flash_mem)); +} + +/* Return 1 if the page at index `page` had its first word written to 0xFFFFFFFF. */ +static int page_erased(int page) +{ + uint32_t word; + memcpy(&word, &mock_flash_mem[page * FLASH_PAGE_SIZE], sizeof(word)); + return word == 0xFFFFFFFFu; +} + +/* Erasing exactly one page must erase that page and nothing beyond it. */ +START_TEST(test_erase_single_page_aligned) +{ + reset_mocks(); + hal_flash_erase(0, FLASH_PAGE_SIZE); + + ck_assert_int_eq(page_erased(0), 1); + ck_assert_int_eq(page_erased(1), 0); +} +END_TEST + +/* Erasing two full pages must erase both and not touch the third. */ +START_TEST(test_erase_two_pages_aligned) +{ + reset_mocks(); + hal_flash_erase(0, 2 * FLASH_PAGE_SIZE); + + ck_assert_int_eq(page_erased(0), 1); + ck_assert_int_eq(page_erased(1), 1); + ck_assert_int_eq(page_erased(2), 0); +} +END_TEST + +/* Regression for F-3965: len = PAGE_SIZE + 1 spans two pages; both must be + * erased. Before the fix, end_address = address + len - 1 landed exactly on + * the first byte of page 1, so the strict `p < end_address` guard excluded it. */ +START_TEST(test_erase_unaligned_len_covers_last_page) +{ + reset_mocks(); + /* len = 129: bytes [0..128], crossing into page 1 */ + hal_flash_erase(0, FLASH_PAGE_SIZE + 1); + + ck_assert_int_eq(page_erased(0), 1); + ck_assert_int_eq(page_erased(1), 1); + ck_assert_int_eq(page_erased(2), 0); +} +END_TEST + +Suite *flash_erase_l0_suite(void) +{ + Suite *s = suite_create("flash-erase-l0"); + TCase *tc = tcase_create("flash-erase-l0"); + + tcase_add_test(tc, test_erase_single_page_aligned); + tcase_add_test(tc, test_erase_two_pages_aligned); + tcase_add_test(tc, test_erase_unaligned_len_covers_last_page); + + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = flash_erase_l0_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + + return fails; +} diff --git a/tools/unit-tests/unit-flash-erase-u3.c b/tools/unit-tests/unit-flash-erase-u3.c new file mode 100644 index 0000000000..351b21edb2 --- /dev/null +++ b/tools/unit-tests/unit-flash-erase-u3.c @@ -0,0 +1,168 @@ +/* unit-flash-erase-u3.c + * + * Unit tests for the page-loop bound in hal_flash_erase() (hal/stm32u3.c). + * Regression for F-3962: end_address = address + len - 1 (inclusive) combined + * with p < end_address (strict) skips the final page when len % PAGE_SIZE != 0. + * + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include + +/* hal/stm32u3.c is tightly coupled to STM32U3 hardware registers. + * Compile only hal_flash_erase() in isolation by defining this guard; + * all other functions and hardware macros are excluded and replaced below. */ +#define WOLFBOOT_UNIT_TEST_FLASH_ERASE + +/* RAMFUNCTION must be empty on the host */ +#define RAMFUNCTION + +/* DMB is a no-op on the host (capture erase commands in flash_wait_complete). */ +#define DMB() /* nothing */ + +/* ARCH_FLASH_OFFSET is normally supplied by image.h; define it directly. */ +#define ARCH_FLASH_OFFSET (0x08000000U) + +/* Mocked non-secure flash control/status registers */ +static uint32_t mock_FLASH_NS_CR; +static uint32_t mock_FLASH_NS_SR; +#define FLASH_NS_CR mock_FLASH_NS_CR +#define FLASH_NS_SR mock_FLASH_NS_SR + +/* Record the FLASH_NS_CR value on each page-erase command (captured at the + * start of flash_wait_complete, when STRT has just been written). */ +#define ERASE_LOG_MAX 128 +static uint32_t erase_cr[ERASE_LOG_MAX]; +static int erase_log_n; + +/* Stubs for functions called by hal_flash_erase(). + * flash_wait_complete is called immediately after FLASH_NS_CR |= FLASH_CR_STRT, + * so it is the right place to capture the current register state. + * It clears STRT to mirror what real hardware does automatically. */ +static void flash_wait_complete(void) +{ + if ((mock_FLASH_NS_CR & (1 << 16)) && erase_log_n < ERASE_LOG_MAX) { + erase_cr[erase_log_n] = mock_FLASH_NS_CR; + erase_log_n++; + } + mock_FLASH_NS_CR &= ~(1 << 16); /* clear STRT */ +} +static void flash_clear_errors(void) {} +void hal_cache_invalidate(void) {} + +#include "../../hal/stm32u3.c" + +/* Decode the page number field from a captured FLASH_NS_CR value. + * Bits [9:3] = PNB (7 bits); bit 11 = BKER (bank select). */ +static uint32_t page_of(uint32_t cr) +{ + return (cr >> FLASH_CR_PNB_SHIFT) & FLASH_CR_PNB_MASK; +} +static int bank_of(uint32_t cr) +{ + return (cr & FLASH_CR_BKER) ? 1 : 0; +} + +static void reset_mocks(void) +{ + mock_FLASH_NS_CR = 0; + mock_FLASH_NS_SR = 0; + erase_log_n = 0; +} + +/* Erasing exactly one page must issue exactly one erase command. */ +START_TEST(test_erase_single_page_aligned) +{ + reset_mocks(); + hal_flash_erase(0x08000000UL, FLASH_PAGE_SIZE); + + ck_assert_int_eq(erase_log_n, 1); + ck_assert_int_eq(bank_of(erase_cr[0]), 0); + ck_assert_uint_eq(page_of(erase_cr[0]), 0); +} +END_TEST + +/* Erasing two full pages must issue exactly two erase commands. */ +START_TEST(test_erase_two_pages_aligned) +{ + reset_mocks(); + hal_flash_erase(0x08000000UL, 2 * FLASH_PAGE_SIZE); + + ck_assert_int_eq(erase_log_n, 2); + ck_assert_uint_eq(page_of(erase_cr[0]), 0); + ck_assert_uint_eq(page_of(erase_cr[1]), 1); +} +END_TEST + +/* Regression for F-3962: len = PAGE_SIZE + 1 spans two pages and both must be + * erased. Before the fix, end_address = address + len - 1 landed exactly on + * the start of page 1, so the strict `p < end_address` guard excluded it. */ +START_TEST(test_erase_unaligned_len_covers_last_page) +{ + reset_mocks(); + /* len = 0x1001: bytes [0..0x1000], crossing into page 1 */ + hal_flash_erase(0x08000000UL, FLASH_PAGE_SIZE + 1); + + ck_assert_int_eq(erase_log_n, 2); + ck_assert_uint_eq(page_of(erase_cr[0]), 0); + ck_assert_uint_eq(page_of(erase_cr[1]), 1); +} +END_TEST + +/* Erasing exactly one page in bank 2 must set BKER and page 0. */ +START_TEST(test_erase_bank2_page) +{ + reset_mocks(); + hal_flash_erase(FLASH_BANK2_BASE, FLASH_PAGE_SIZE); + + ck_assert_int_eq(erase_log_n, 1); + ck_assert_int_eq(bank_of(erase_cr[0]), 1); + ck_assert_uint_eq(page_of(erase_cr[0]), 0); +} +END_TEST + +Suite *flash_erase_u3_suite(void) +{ + Suite *s = suite_create("flash-erase-u3"); + TCase *tc = tcase_create("flash-erase-u3"); + + tcase_add_test(tc, test_erase_single_page_aligned); + tcase_add_test(tc, test_erase_two_pages_aligned); + tcase_add_test(tc, test_erase_unaligned_len_covers_last_page); + tcase_add_test(tc, test_erase_bank2_page); + + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = flash_erase_u3_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + + return fails; +} diff --git a/tools/unit-tests/unit-flash-erase-wb.c b/tools/unit-tests/unit-flash-erase-wb.c new file mode 100644 index 0000000000..c9fbf4b9cb --- /dev/null +++ b/tools/unit-tests/unit-flash-erase-wb.c @@ -0,0 +1,151 @@ +/* unit-flash-erase-wb.c + * + * Unit tests for the page-loop bound in hal_flash_erase() (hal/stm32wb.c). + * Regression for F-3966: end_address = address + len - 1 (inclusive) combined + * with p < end_address (strict) skips the final page when len % PAGE_SIZE == 1. + * + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include + +/* hal/stm32wb.c is tightly coupled to STM32WB hardware registers. + * Compile only hal_flash_erase() in isolation by defining this guard; + * all other functions and hardware macros are excluded and replaced below. */ +#define WOLFBOOT_UNIT_TEST_FLASH_ERASE + +/* RAMFUNCTION must be empty on the host */ +#define RAMFUNCTION + +/* Mocked flash control registers */ +static uint32_t mock_FLASH_CR; +static uint32_t mock_FLASH_SR; +#define FLASH_CR mock_FLASH_CR +#define FLASH_SR mock_FLASH_SR + +/* Constants from hal/stm32wb.c (must mirror exactly). + * FLASHMEM_ADDRESS_SPACE and FLASH_PAGE_SIZE are defined inside stm32wb.c + * and therefore not repeated here. */ +#define FLASH_CR_STRT (1UL << 16) +#define FLASH_CR_PER (1UL << 1) +#define FLASH_CR_PG (1UL << 0) +#define FLASH_CR_FSTPG (1UL << 18) +#define FLASH_CR_PNB_SHIFT 3 +#define FLASH_CR_PNB_MASK 0xFFUL + +/* Record the FLASH_CR value on each DMB() call that has STRT set — one entry + * per page-erase command, exactly. */ +#define ERASE_LOG_MAX 64 +static uint32_t erase_cr[ERASE_LOG_MAX]; +static int erase_log_n; + +#define DMB() do { \ + if ((mock_FLASH_CR & FLASH_CR_STRT) && erase_log_n < ERASE_LOG_MAX) { \ + erase_cr[erase_log_n] = mock_FLASH_CR; \ + erase_log_n++; \ + } \ +} while (0) + +/* Stubs for functions called by hal_flash_erase(). + * Real hardware auto-clears STRT after the erase finishes; mirror that so + * the bit does not leak into the next iteration's FLASH_CR setup. */ +static void flash_wait_complete(void) { mock_FLASH_CR &= ~FLASH_CR_STRT; } +static void flash_clear_errors(void) {} + +#include "../../hal/stm32wb.c" + +/* Decode the page number field from a captured FLASH_CR value. */ +static uint32_t page_of(uint32_t cr) +{ + return (cr >> FLASH_CR_PNB_SHIFT) & FLASH_CR_PNB_MASK; +} + +static void reset_mocks(void) +{ + mock_FLASH_CR = 0; + mock_FLASH_SR = 0; + erase_log_n = 0; +} + +/* Erasing exactly one page must issue exactly one erase command. */ +START_TEST(test_erase_single_page_aligned) +{ + reset_mocks(); + hal_flash_erase(0x08000000UL, FLASH_PAGE_SIZE); + + ck_assert_int_eq(erase_log_n, 1); + ck_assert_uint_eq(page_of(erase_cr[0]), 0); +} +END_TEST + +/* Erasing two full pages must issue exactly two erase commands. */ +START_TEST(test_erase_two_pages_aligned) +{ + reset_mocks(); + hal_flash_erase(0x08000000UL, 2 * FLASH_PAGE_SIZE); + + ck_assert_int_eq(erase_log_n, 2); + ck_assert_uint_eq(page_of(erase_cr[0]), 0); + ck_assert_uint_eq(page_of(erase_cr[1]), 1); +} +END_TEST + +/* Regression for F-3966: len = PAGE_SIZE + 1 spans two pages and both must be + * erased. Before the fix, end_address = address + len - 1 landed exactly on + * the start of page 1, so the strict `p < end_address` guard excluded it. */ +START_TEST(test_erase_unaligned_len_covers_last_page) +{ + reset_mocks(); + /* len = 0x1001: bytes [0..0x1000], crossing into page 1 */ + hal_flash_erase(0x08000000UL, FLASH_PAGE_SIZE + 1); + + ck_assert_int_eq(erase_log_n, 2); + ck_assert_uint_eq(page_of(erase_cr[0]), 0); + ck_assert_uint_eq(page_of(erase_cr[1]), 1); +} +END_TEST + +Suite *flash_erase_wb_suite(void) +{ + Suite *s = suite_create("flash-erase-wb"); + TCase *tc = tcase_create("flash-erase-wb"); + + tcase_add_test(tc, test_erase_single_page_aligned); + tcase_add_test(tc, test_erase_two_pages_aligned); + tcase_add_test(tc, test_erase_unaligned_len_covers_last_page); + + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = flash_erase_wb_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + + return fails; +} diff --git a/tools/unit-tests/unit-otp-keystore.c b/tools/unit-tests/unit-otp-keystore.c index 609a1fb7c8..f3ac341858 100644 --- a/tools/unit-tests/unit-otp-keystore.c +++ b/tools/unit-tests/unit-otp-keystore.c @@ -36,38 +36,50 @@ #define KEYSTORE_PUBKEY_SIZE 64 /* model an ECC-256 keystore slot */ #define OTP_SIZE 1024 #define FLASH_OTP_BASE 0 +#define OTP_HDR_SIZE 16 /* Mock OTP flash backing store and reader. With FLASH_OTP_BASE == 0 the address * passed by the driver is a plain offset into this buffer. */ static uint8_t mock_otp[OTP_SIZE]; +/* Set to 1 to make slot reads (offset >= OTP_HDR_SIZE) return -1. */ +static int mock_otp_slot_read_fail; int hal_flash_otp_read(uint32_t flashAddress, void *data, uint32_t length) { if (flashAddress + length > (uint32_t)OTP_SIZE) return -1; + if (mock_otp_slot_read_fail && flashAddress >= OTP_HDR_SIZE) + return -1; memcpy(data, mock_otp + flashAddress, length); return 0; } #include "../../src/flash_otp_keystore.c" -/* Provision the mock OTP with a single keystore slot whose pubkey_size field is - * set to the supplied (possibly bogus) value. */ -static void setup_otp(uint32_t pubkey_size) +/* Provision the mock OTP header with n slots; slot 0 gets a specific pubkey_size. */ +static void setup_otp_n(int n, uint32_t pubkey_size) { struct wolfBoot_otp_hdr *hdr = (struct wolfBoot_otp_hdr *)mock_otp; struct keystore_slot *slot = (struct keystore_slot *)(mock_otp + OTP_HDR_SIZE); memset(mock_otp, 0, sizeof(mock_otp)); + mock_otp_slot_read_fail = 0; memcpy(hdr->keystore_hdr_magic, KEYSTORE_HDR_MAGIC, 8); - hdr->item_count = 1; + hdr->item_count = (uint16_t)n; slot->slot_id = 0; slot->key_type = 1; slot->part_id_mask = 0xFFFFFFFF; slot->pubkey_size = pubkey_size; } +/* Provision the mock OTP with a single keystore slot whose pubkey_size field is + * set to the supplied (possibly bogus) value. */ +static void setup_otp(uint32_t pubkey_size) +{ + setup_otp_n(1, pubkey_size); +} + /* A correctly provisioned slot returns its real size unchanged. */ START_TEST(test_valid_size_passthrough) { @@ -99,6 +111,45 @@ START_TEST(test_just_over_rejected) } END_TEST +/* keystore_num_pubkeys() must return 0 when the OTP magic is corrupted. */ +START_TEST(test_bad_magic_returns_zero) +{ + struct wolfBoot_otp_hdr *hdr = (struct wolfBoot_otp_hdr *)mock_otp; + setup_otp(KEYSTORE_PUBKEY_SIZE); + hdr->keystore_hdr_magic[0] ^= 0xFF; + ck_assert_int_eq(keystore_num_pubkeys(), 0); +} +END_TEST + +/* item_count one above the maximum must be rejected. */ +START_TEST(test_item_count_over_max) +{ + setup_otp_n(KEYSTORE_MAX_PUBKEYS + 1, KEYSTORE_PUBKEY_SIZE); + ck_assert_int_eq(keystore_num_pubkeys(), 0); +} +END_TEST + +/* item_count exactly at the maximum must be accepted (catches > vs >= mutation). */ +START_TEST(test_item_count_at_max) +{ + setup_otp_n(KEYSTORE_MAX_PUBKEYS, KEYSTORE_PUBKEY_SIZE); + ck_assert_int_eq(keystore_num_pubkeys(), KEYSTORE_MAX_PUBKEYS); +} +END_TEST + +/* All keystore_get_* accessors must return their failure value when the OTP + * slot read fails (header read succeeds, slot read fails). */ +START_TEST(test_slot_read_fail_propagates) +{ + setup_otp(KEYSTORE_PUBKEY_SIZE); + mock_otp_slot_read_fail = 1; + ck_assert_ptr_eq(keystore_get_buffer(0), (uint8_t *)0); + ck_assert_int_eq(keystore_get_size(0), -1); + ck_assert_uint_eq(keystore_get_mask(0), 0); + ck_assert_uint_eq(keystore_get_key_type(0), (uint32_t)-1); +} +END_TEST + Suite *otp_keystore_suite(void) { Suite *s = suite_create("otp-keystore"); @@ -107,6 +158,10 @@ Suite *otp_keystore_suite(void) tcase_add_test(tc, test_valid_size_passthrough); tcase_add_test(tc, test_oversize_rejected); tcase_add_test(tc, test_just_over_rejected); + tcase_add_test(tc, test_bad_magic_returns_zero); + tcase_add_test(tc, test_item_count_over_max); + tcase_add_test(tc, test_item_count_at_max); + tcase_add_test(tc, test_slot_read_fail_propagates); suite_add_tcase(s, tc); return s; diff --git a/tools/unit-tests/unit-sign-custom-tlv-le.py b/tools/unit-tests/unit-sign-custom-tlv-le.py new file mode 100644 index 0000000000..b30161c408 --- /dev/null +++ b/tools/unit-tests/unit-sign-custom-tlv-le.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +# unit-sign-custom-tlv-le.py +# +# Regression test for the C sign tool custom integer TLV byte order. +# +# The --custom-tlv TAG LEN VAL path in make_header_ex() (sign.c) must store +# the integer value in little-endian byte order, matching the convention used +# by wolfBoot_find_header() + im2n(). Before the fix the code passed +# &CMD.custom_tlv[i].val (a host-endian uint64_t) directly to +# header_append_tag(), which performs a raw memcpy. On a big-endian build +# host this copies the high bytes of the uint64_t first, so e.g. +# --custom-tlv 0x0032 4 0x12345678 encodes as 00 00 00 00 instead of +# 78 56 34 12, and the bootloader decodes back zero. After the fix the code +# serialises through header_store_u64_le() before calling header_append_tag(), +# giving correct LE bytes on any build host. +# +# This test signs a dummy image with --custom-tlv entries of len 2, 4, and 8, +# then reads the resulting header bytes at each TLV value offset and asserts +# they are exactly the little-endian encoding of the supplied integer. On a +# big-endian build host the assertions fail with the old code; on a +# little-endian host they pass before and after the fix (host layout already +# matches LE), making the test a regression guard for big-endian ports. +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfBoot. +# +# wolfBoot is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfBoot is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +import os +import subprocess +import sys +import tempfile + +HDR_PADDING = 0xFF + +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.abspath(os.path.join(THIS_DIR, "..", "..")) +SIGN = os.path.join(ROOT, "tools", "keytools", "sign") + + +def skip(msg): + print("SKIP unit-sign-custom-tlv-le: " + msg) + sys.exit(0) + + +def find_tlv_bytes(data, want_type, scan_end): + """Return raw value bytes for a TLV entry (mirrors wolfBoot_find_header).""" + p = 8 # skip 4-byte magic + 4-byte image size + while p + 4 <= scan_end: + htype = data[p] | (data[p + 1] << 8) + if htype == 0: + break + if data[p] == HDR_PADDING or (p & 1) != 0: + p += 1 + continue + length = data[p + 2] | (data[p + 3] << 8) + if htype == want_type: + if p + 4 + length > scan_end: + return None + return bytes(data[p + 4:p + 4 + length]) + p += 4 + length + return None + + +def ensure_sign(): + if os.path.exists(SIGN): + return True + try: + subprocess.run(["make", "sign"], + cwd=os.path.join(ROOT, "tools", "keytools"), + check=True, capture_output=True, text=True) + except (subprocess.CalledProcessError, OSError): + return False + return os.path.exists(SIGN) + + +def make_ed25519_key(path): + """Write a 64-byte raw ed25519 key (seed + public) as expected by sign.""" + try: + from cryptography.hazmat.primitives.asymmetric.ed25519 import \ + Ed25519PrivateKey + from cryptography.hazmat.primitives import serialization + except Exception: + return False + seed = b"\x42" * 32 + sk = Ed25519PrivateKey.from_private_bytes(seed) + pub = sk.public_key().public_bytes(serialization.Encoding.Raw, + serialization.PublicFormat.Raw) + with open(path, "wb") as f: + f.write(seed + pub) + return True + + +# (tag, len_bytes, integer_value, expected_le_bytes) +# Values are non-palindromic so a byte-order error is immediately visible. +# Only two cases so the total argc stays <= 14 (the sign tool's hard limit: +# "argc > 14" triggers the usage check). len=4 and len=8 are the most +# illustrative; len=1 is trivially the same on any host byte order. +TEST_CASES = [ + (0x0032, 4, 0x12345678, bytes([0x78, 0x56, 0x34, 0x12])), + (0x0033, 8, 0x0102030405060708, bytes([0x08, 0x07, 0x06, 0x05, + 0x04, 0x03, 0x02, 0x01])), +] + + +def main(): + if not ensure_sign(): + skip("could not build tools/keytools/sign") + + with tempfile.TemporaryDirectory() as work: + key = os.path.join(work, "priv.der") + if not make_ed25519_key(key): + skip("python cryptography module not available") + + image = os.path.join(work, "image.bin") + with open(image, "wb") as f: + f.write(bytes(range(256)) * 8) # 2 KiB dummy payload + + cmd = [SIGN, "--ed25519", "--sha256"] + for tag, length, val, _ in TEST_CASES: + cmd += ["--custom-tlv", hex(tag), str(length), hex(val)] + cmd += [image, key, "1"] + + r = subprocess.run(cmd, cwd=ROOT, capture_output=True, text=True) + if r.returncode != 0: + skip("sign failed: " + r.stderr.strip()) + + signed = image.replace(".bin", "_v1_signed.bin") + if not os.path.exists(signed): + skip("sign did not produce a signed image") + + with open(signed, "rb") as f: + data = f.read(512) + + failures = [] + for tag, length, val, expected in TEST_CASES: + got = find_tlv_bytes(data, tag, len(data)) + if got is None: + failures.append( + "tag 0x%04x (len=%d val=%s): TLV not found in header" % + (tag, length, hex(val))) + elif got != expected: + failures.append( + "tag 0x%04x (len=%d val=%s): got [%s], want [%s] (LE); " + "raw memcpy from host-endian uint64_t produces wrong bytes " + "on big-endian build hosts" % + (tag, length, hex(val), + " ".join("%02x" % b for b in got), + " ".join("%02x" % b for b in expected))) + + if failures: + for msg in failures: + print("FAIL unit-sign-custom-tlv-le: " + msg) + sys.exit(1) + + print("unit-sign-custom-tlv-le: OK") + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/tools/unit-tests/unit-tpm-blob.c b/tools/unit-tests/unit-tpm-blob.c index 483c340844..84683f11e1 100644 --- a/tools/unit-tests/unit-tpm-blob.c +++ b/tools/unit-tests/unit-tpm-blob.c @@ -691,6 +691,28 @@ START_TEST(test_wolfBoot_unseal_blob_rejects_negative_auth_size) } END_TEST +START_TEST(test_wolfBoot_unseal_blob_rejects_short_policy) +{ + uint8_t secret[WOLFBOOT_MAX_SEAL_SZ]; + WOLFTPM2_KEYBLOB blob; + uint8_t pubkey_hint[WOLFBOOT_SHA_DIGEST_SIZE] = {0}; + uint8_t policy[4] = {0xAA, 0xBB, 0xCC, 0xDD}; + int secret_sz; + int rc; + int i; + + memset(&blob, 0, sizeof(blob)); + memset(secret, 0, sizeof(secret)); + + for (i = 1; i <= 3; i++) { + secret_sz = (int)sizeof(secret); + rc = wolfBoot_unseal_blob(pubkey_hint, policy, (uint16_t)i, &blob, + secret, &secret_sz, NULL, 0); + ck_assert_int_eq(rc, -1); + } +} +END_TEST + START_TEST(test_wolfBoot_unseal_blob_rejects_output_larger_than_capacity) { struct { @@ -757,6 +779,7 @@ static Suite *tpm_blob_suite(void) tcase_add_test(tc, test_wolfBoot_unseal_blob_zeroes_unseal_output); tcase_add_test(tc, test_wolfBoot_unseal_blob_rejects_oversized_auth); tcase_add_test(tc, test_wolfBoot_unseal_blob_rejects_negative_auth_size); + tcase_add_test(tc, test_wolfBoot_unseal_blob_rejects_short_policy); tcase_add_test(tc, test_wolfBoot_unseal_blob_rejects_output_larger_than_capacity); suite_add_tcase(s, tc); return s; diff --git a/tools/unit-tests/unit-update-disk.c b/tools/unit-tests/unit-update-disk.c index a1c12cfa92..d92ec43fff 100644 --- a/tools/unit-tests/unit-update-disk.c +++ b/tools/unit-tests/unit-update-disk.c @@ -344,6 +344,21 @@ START_TEST(test_get_decrypted_blob_version_rejects_truncated_version_tlv) } END_TEST +START_TEST(test_update_disk_highbit_version_selects_higher_partition) +{ + reset_mocks(); + build_image(part_a_image, 0x80000001U, 0xA1); + build_image(part_b_image, 0x80000002U, 0xB2); + + wolfBoot_start(); + + ck_assert_int_eq(wolfBoot_panicked, 0); + ck_assert_int_eq(mock_do_boot_called, 1); + ck_assert_int_eq(memcmp(load_buffer, part_b_image + IMAGE_HEADER_SIZE, + TEST_PAYLOAD_SIZE), 0); +} +END_TEST + START_TEST(test_update_disk_rejects_rollback_after_higher_image_failure) { reset_mocks(); @@ -397,6 +412,7 @@ Suite *wolfboot_suite(void) tcase_add_test(tc, test_update_disk_boots_from_A_when_B_is_blank); tcase_add_test(tc, test_update_disk_boots_from_B_when_A_is_blank); tcase_add_test(tc, test_get_decrypted_blob_version_rejects_truncated_version_tlv); + tcase_add_test(tc, test_update_disk_highbit_version_selects_higher_partition); tcase_add_test(tc, test_update_disk_rejects_rollback_after_higher_image_failure); tcase_add_test(tc, test_update_disk_rejects_invalid_integrity); tcase_add_test(tc, test_update_disk_rejects_invalid_signature); diff --git a/tools/unit-tests/unit-update-flash-hwswap.c b/tools/unit-tests/unit-update-flash-hwswap.c new file mode 100644 index 0000000000..596dbe55ed --- /dev/null +++ b/tools/unit-tests/unit-update-flash-hwswap.c @@ -0,0 +1,254 @@ +/* unit-update-flash-hwswap.c + * + * unit tests for the HW-assisted swap updater (update_flash_hwswap.c) + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +#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 +#include "target.h" + +#define NO_FORK 1 /* Set to 1 to disable fork mode (e.g. for gdb debugging) */ + +#include +#include +#include +#include "user_settings.h" +#include "wolfboot/wolfboot.h" +#include "libwolfboot.c" +#include +#include +#include +#include +#include "unit-mock-flash.c" +#include +#include + +/* Mocks for the symbols referenced by update_flash_hwswap.c that are not + * provided by libwolfboot.c / unit-mock-flash.c + */ +static int do_boot_called = 0; +static int dualbank_swap_called = 0; + +void hal_flash_dualbank_swap(void) +{ + dualbank_swap_called++; +} + +int hal_flash_protect(haladdr_t address, int len) +{ + (void)address; + (void)len; + return 0; +} + +void do_boot(const uint32_t *address) +{ + (void)address; + do_boot_called++; +} + +/* update_flash_hwswap.c uses a private boot_panic() that spins forever + * (while(1);). Neutralize only that spin so the deny path can return and be + * observed by the test. The headers are pulled in first (with guards), so the + * macro only rewrites the single while() inside update_flash_hwswap.c itself. + * wolfBoot_start uses for(;;), which is unaffected. + */ +#include "loader.h" +#include "image.h" +#include "hal.h" +#include "hooks.h" +#include "spi_flash.h" +#include "printf.h" +#define while(cond) if (cond) +#include "update_flash_hwswap.c" +#undef while + +const char *argv0; + +Suite *wolfboot_suite(void); + +static void reset_mock_stats(void) +{ + do_boot_called = 0; + dualbank_swap_called = 0; +} + +static void prepare_flash(void) +{ + int ret; + ret = mmap_file("/tmp/wolfboot-unit-ext-file.bin", + (void *)(uintptr_t)MOCK_ADDRESS_UPDATE, + WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE, NULL); + ck_assert(ret >= 0); + ret = mmap_file("/tmp/wolfboot-unit-int-file.bin", + (void *)(uintptr_t)MOCK_ADDRESS_BOOT, + WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE, NULL); + ck_assert(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) +{ + munmap((void *)WOLFBOOT_PARTITION_BOOT_ADDRESS, + WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE); + munmap((void *)WOLFBOOT_PARTITION_UPDATE_ADDRESS, + WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE); +} + +#define TEST_SIZE_SMALL 5300 +#define DIGEST_TLV_OFF_IN_HDR 28 +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); /* Ensure reproducible "random" image */ + + ext_flash_unlock(); + ext_flash_write((uintptr_t)base, "WOLF", 4); + ext_flash_write((uintptr_t)base + 4, (void *)&size, 4); + + /* Headers */ + 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); + + /* Add 28B header to sha calculation */ + ret = wc_Sha256Update(&sha, base, DIGEST_TLV_OFF_IN_HDR); + if (ret != 0) + return ret; + + /* Payload */ + size += IMAGE_HEADER_SIZE; + for (i = IMAGE_HEADER_SIZE; i < (int)size; i += 4) { + uint32_t w = (random() << 16) | random(); + ext_flash_write((uintptr_t)base + i, (void *)&w, 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 = 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; +} + +/* Regression test for the uint32_t->int cast in wolfBoot_start (hwswap). + * + * BOOT carries a higher version than UPDATE, but its image is unbootable + * (oversize header), so wolfBoot_start falls back to the UPDATE partition, + * which holds a LOWER version. Anti-rollback must deny booting the lower + * version (boot_panic, no do_boot). + * + * Both versions are above INT_MAX: with the buggy (int)cast they were clamped + * to 0, max_v became 0, the "(max_v > 0U)" guard was skipped, and the + * rolled-back UPDATE image was staged for boot. + */ +START_TEST (test_hwswap_highversion_rollback_denied) { + uint32_t oversize = WOLFBOOT_PARTITION_SIZE; + + reset_mock_stats(); + prepare_flash(); + /* BOOT: higher version, but mark it oversize so wolfBoot_open_image() + * rejects it and the boot path falls back to UPDATE. The version TLV + * stays readable. */ + add_payload(PART_BOOT, 0x80000002U, TEST_SIZE_SMALL); + ext_flash_unlock(); + ext_flash_write(WOLFBOOT_PARTITION_BOOT_ADDRESS + 4, (void *)&oversize, 4); + ext_flash_lock(); + /* UPDATE: lower version, valid */ + add_payload(PART_UPDATE, 0x80000001U, TEST_SIZE_SMALL); + + ck_assert_uint_eq(wolfBoot_current_firmware_version(), 0x80000002U); + ck_assert_uint_eq(wolfBoot_update_firmware_version(), 0x80000001U); + + wolfBoot_start(); + + /* Rollback to the lower UPDATE version must be denied */ + ck_assert_int_eq(do_boot_called, 0); + cleanup_flash(); +} +END_TEST + +Suite *wolfboot_suite(void) +{ + Suite *s = suite_create("wolfboot-hwswap"); + TCase *rollback_denied = + tcase_create("HW-swap high-version rollback denied"); + + tcase_add_test(rollback_denied, test_hwswap_highversion_rollback_denied); + suite_add_tcase(s, rollback_denied); + tcase_set_timeout(rollback_denied, 5); + return s; +} + +int main(int argc, char *argv[]) +{ + int fails; + argv0 = strdup(argv[0]); + Suite *s = wolfboot_suite(); + SRunner *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-nofixed.c b/tools/unit-tests/unit-update-ram-nofixed.c index 6f5ce2df0e..b828dc2748 100644 --- a/tools/unit-tests/unit-update-ram-nofixed.c +++ b/tools/unit-tests/unit-update-ram-nofixed.c @@ -231,15 +231,57 @@ START_TEST(test_invalid_update_falls_back_to_boot) } 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"); TCase *tc = tcase_create("fallback"); + TCase *tc_candidate = tcase_create("candidate_addr"); 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; } diff --git a/tools/unit-tests/unit-update-ram-noramboot.c b/tools/unit-tests/unit-update-ram-noramboot.c new file mode 100644 index 0000000000..8561830611 --- /dev/null +++ b/tools/unit-tests/unit-update-ram-noramboot.c @@ -0,0 +1,252 @@ +/* unit-update-ram-noramboot.c + * + * unit tests for update_ram.c wolfBoot_start on the non-RAMBOOT path + * (WOLFBOOT_NO_RAMBOOT), where wolfBoot_open_image() is used instead of + * wolfBoot_ramboot(). This is the configuration in which the anti-rollback + * guard in wolfBoot_start is the decisive check. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +#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 +#include "target.h" +static __thread unsigned char wolfboot_ram[2 * WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE]; + +#define WOLFBOOT_LOAD_ADDRESS (((uintptr_t)wolfboot_ram + IMAGE_HEADER_SIZE)) + +#define TEST_SIZE_SMALL 5300 + +#define NO_FORK 1 /* Set to 1 to disable fork mode (e.g. for gdb debugging) */ + +#include +#include +#include "user_settings.h" +#include "wolfboot/wolfboot.h" +#include "libwolfboot.c" +#include "update_ram.c" +#include +#include +#include +#include +#include "unit-mock-flash.c" +#include +#include + +const char *argv0; + +Suite *wolfboot_suite(void); + +int wolfBoot_staged_ok = 0; +const uint32_t *wolfBoot_stage_address = (uint32_t *) 0xFFFFFFFF; + +void do_boot(const uint32_t *address) +{ + /* Mock of do_boot: count successful boots */ + if (wolfBoot_panicked) + return; + wolfBoot_staged_ok++; + wolfBoot_stage_address = address; + printf("Called do_boot with address %p\n", address); +} + +int hal_flash_protect(haladdr_t address, int len) +{ + (void)address; + (void)len; + return 0; +} + +static void reset_mock_stats(void) +{ + wolfBoot_panicked = 0; + wolfBoot_staged_ok = 0; +} + +static void prepare_flash(void) +{ + int ret; + ret = mmap_file("/tmp/wolfboot-unit-ext-file.bin", (void *)(uintptr_t)MOCK_ADDRESS_UPDATE, + WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE, NULL); + ck_assert(ret >= 0); + ret = mmap_file("/tmp/wolfboot-unit-int-file.bin", (void *)(uintptr_t)MOCK_ADDRESS_BOOT, + WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE, NULL); + ck_assert(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) +{ + munmap((void *)WOLFBOOT_PARTITION_BOOT_ADDRESS, WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE); + munmap((void *)WOLFBOOT_PARTITION_UPDATE_ADDRESS, WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE); +} + + +#define DIGEST_TLV_OFF_IN_HDR 28 +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); /* Ensure reproducible "random" image */ + + ext_flash_unlock(); + ext_flash_write((uintptr_t)base, "WOLF", 4); + ext_flash_write((uintptr_t)base + 4, (void *)&size, 4); + + /* Headers */ + 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); + + /* Add 28B header to sha calculation */ + ret = wc_Sha256Update(&sha, base, DIGEST_TLV_OFF_IN_HDR); + if (ret != 0) + return ret; + + /* Payload */ + size += IMAGE_HEADER_SIZE; + for (i = IMAGE_HEADER_SIZE; i < (int)size; i += 4) { + uint32_t w = (random() << 16) | random(); + ext_flash_write((uintptr_t)base + i, (void *)&w, 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 = size - i; + ret = wc_Sha256Update(&sha, base + i, len); + if (ret != 0) + return ret; + } + + /* Calculate final digest */ + 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; +} + +/* Sanity: a single valid image boots on the non-RAMBOOT path. */ +START_TEST (test_noramboot_sunnyday) { + reset_mock_stats(); + prepare_flash(); + add_payload(PART_BOOT, 1, TEST_SIZE_SMALL); + wolfBoot_start(); + ck_assert(wolfBoot_staged_ok); + ck_assert_int_eq(wolfBoot_panicked, 0); + cleanup_flash(); +} +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. + * + * 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) { + uint32_t oversize = WOLFBOOT_PARTITION_SIZE; + + reset_mock_stats(); + prepare_flash(); + /* BOOT: higher version, but oversize so its open is rejected. The version + * TLV stays readable. */ + add_payload(PART_BOOT, 0x80000005U, TEST_SIZE_SMALL); + ext_flash_unlock(); + ext_flash_write(WOLFBOOT_PARTITION_BOOT_ADDRESS + 4, (void *)&oversize, 4); + ext_flash_lock(); + /* UPDATE: lower version, valid */ + add_payload(PART_UPDATE, 0x80000003U, TEST_SIZE_SMALL); + + ck_assert_uint_eq(wolfBoot_current_firmware_version(), 0x80000005U); + ck_assert_uint_eq(wolfBoot_update_firmware_version(), 0x80000003U); + + 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); + cleanup_flash(); +} +END_TEST + +Suite *wolfboot_suite(void) +{ + Suite *s = suite_create("wolfboot-noramboot"); + TCase *sunnyday = tcase_create("Non-RAMBOOT sunny day"); + TCase *rollback_denied = + tcase_create("Non-RAMBOOT high-version rollback denied"); + + tcase_add_test(sunnyday, test_noramboot_sunnyday); + tcase_add_test(rollback_denied, test_noramboot_highversion_rollback_denied); + suite_add_tcase(s, sunnyday); + suite_add_tcase(s, rollback_denied); + tcase_set_timeout(sunnyday, 5); + tcase_set_timeout(rollback_denied, 5); + return s; +} + +int main(int argc, char *argv[]) +{ + int fails; + argv0 = strdup(argv[0]); + Suite *s = wolfboot_suite(); + SRunner *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-va416x0-fram.c b/tools/unit-tests/unit-va416x0-fram.c new file mode 100644 index 0000000000..9383647fea --- /dev/null +++ b/tools/unit-tests/unit-va416x0-fram.c @@ -0,0 +1,194 @@ +#include +#include +#include +#include + +#define WOLFBOOT_UNIT_TEST_VA416X0_FRAM + +#define FRAM_SIZE (256U * 1024U) +#define ROM_SPI_BANK 0 +#define SPI_NUM_BANKS 1 +#define SPI_STATUS_TFE_Msk 0x01U +#define SPI_STATUS_BUSY_Msk 0x02U +#define SPI_FIFO_CLR_RXFIFO_Msk 0x01U +#define SPI_FIFO_CLR_TXFIFO_Msk 0x02U +#define RAMFUNCTION + +typedef enum { + hal_status_ok = 0, + hal_status_badParam = 1, + hal_status_err = 2 +} hal_status_t; + +typedef enum { + hal_spi_state_reset = 0, + hal_spi_state_ready = 1 +} hal_spi_state_t; + +typedef struct { + uint32_t STATUS; + uint32_t FIFO_CLR; +} mock_spi_bank_t; + +typedef struct { + mock_spi_bank_t BANK[SPI_NUM_BANKS]; +} mock_spi_regs_t; + +typedef struct { + bool blockmode; + bool bmstall; + int clkDiv; + bool loopback; + bool mdlycap; + int mode; + int ms; + uint8_t chipSelect; + int wordLen; +} hal_spi_init_t; + +typedef struct { + bool locked; + hal_spi_state_t state; + mock_spi_bank_t *spi; + hal_spi_init_t init; +} hal_spi_handle_t; + +enum { + hal_spi_clkmode_0 = 0, + hal_spi_ms_master = 1 +}; + +static mock_spi_regs_t mock_vor_spi; +#define VOR_SPI (&mock_vor_spi) + +static hal_status_t transmit_script[8]; +static bool transmit_close_flags[8]; +static int transmit_script_len; +static int transmit_call_count; + +static void set_transmit_script(const hal_status_t *script, int len) +{ + int i; + + memset(transmit_close_flags, 0, sizeof(transmit_close_flags)); + for (i = 0; i < len; i++) { + transmit_script[i] = script[i]; + } + transmit_script_len = len; + transmit_call_count = 0; +} + +hal_status_t HAL_Spi_Init(hal_spi_handle_t *handle) +{ + (void)handle; + return hal_status_ok; +} + +hal_status_t HAL_Spi_Transmit(hal_spi_handle_t *handle, uint8_t *data, + uint32_t len, uint32_t timeout, bool close) +{ + (void)handle; + (void)data; + (void)len; + (void)timeout; + + ck_assert_int_lt(transmit_call_count, transmit_script_len); + transmit_close_flags[transmit_call_count] = close; + + return transmit_script[transmit_call_count++]; +} + +hal_status_t HAL_Spi_TransmitReceive(hal_spi_handle_t *handle, uint8_t *tx, + uint8_t *rx, uint32_t tx_len, uint32_t rx_skip, uint32_t rx_len, + uint32_t timeout, bool close) +{ + (void)handle; + (void)tx; + (void)rx; + (void)tx_len; + (void)rx_skip; + (void)rx_len; + (void)timeout; + (void)close; + return hal_status_ok; +} + +void HAL_Timer_DelayMs(uint32_t delay) +{ + (void)delay; +} + +int wolfBoot_printf(const char *fmt, ...) +{ + (void)fmt; + return 0; +} + +#include "../../hal/va416x0.c" + +static void reset_spi_mocks(void) +{ + memset(&mock_vor_spi, 0, sizeof(mock_vor_spi)); + mock_vor_spi.BANK[0].STATUS = SPI_STATUS_TFE_Msk; +} + +START_TEST(test_fram_write_command_failure_aborts_split_transaction) +{ + uint8_t buf[4] = {1, 2, 3, 4}; + hal_status_t init_script[] = { + hal_status_ok, hal_status_ok, hal_status_ok + }; + hal_status_t fail_script[] = { + hal_status_ok, hal_status_err + }; + hal_status_t success_script[] = { + hal_status_ok, hal_status_ok, hal_status_ok + }; + + reset_spi_mocks(); + + set_transmit_script(init_script, 3); + ck_assert_int_eq(FRAM_Init(0, 0), hal_status_ok); + ck_assert_int_eq(spiHandle.state, hal_spi_state_ready); + + set_transmit_script(fail_script, 2); + ck_assert_int_eq(FRAM_Write(0, 0x20, buf, sizeof(buf)), hal_status_err); + ck_assert_int_eq(spiHandle.state, hal_spi_state_ready); + ck_assert_uint_eq(mock_vor_spi.BANK[0].FIFO_CLR, + SPI_FIFO_CLR_RXFIFO_Msk | SPI_FIFO_CLR_TXFIFO_Msk); + ck_assert_int_eq(transmit_call_count, 2); + ck_assert_int_eq(transmit_close_flags[0], true); + ck_assert_int_eq(transmit_close_flags[1], false); + + set_transmit_script(success_script, 3); + ck_assert_int_eq(FRAM_Write(0, 0x24, buf, sizeof(buf)), hal_status_ok); + ck_assert_int_eq(transmit_call_count, 3); + ck_assert_int_eq(transmit_close_flags[0], true); + ck_assert_int_eq(transmit_close_flags[1], false); + ck_assert_int_eq(transmit_close_flags[2], true); +} +END_TEST + +Suite *va416x0_fram_suite(void) +{ + Suite *s = suite_create("va416x0-fram"); + TCase *tc = tcase_create("fram"); + + tcase_add_test(tc, test_fram_write_command_failure_aborts_split_transaction); + suite_add_tcase(s, tc); + + return s; +} + +int main(void) +{ + int fails; + Suite *s = va416x0_fram_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + + return fails; +}