diff --git a/cpu/stm32/Kconfig b/cpu/stm32/Kconfig index f8e6b0ab4e..2f0d6e403d 100644 --- a/cpu/stm32/Kconfig +++ b/cpu/stm32/Kconfig @@ -39,6 +39,26 @@ orsource "kconfigs/*/Kconfig" orsource "kconfigs/*/Kconfig.lines" orsource "kconfigs/*/Kconfig.models" +choice + prompt "ReaDout Protection level" + default RDP0 + help + Set minimum running RDP level. + RDP0 is full debug permissions, RDP1 disables read from Flash but + otherwise leaves debug enabled, RDP2 disables JTAG completely. If + there is a mismatch between desired RDP level here and RDP level + set on the chip, early cpu init will hang. This ensures production + devices with the wrong RDP level, by fault or malace intent, will + not run. See cpu manual for further details on RDP. +depends on (CPU_FAM_F1 || CPU_FAM_F2 || CPU_FAM_F3 || CPU_FAM_F4 || CPU_FAM_F5 || CPU_FAM_F6 || CPU_FAM_F7) +config RDP0 + bool "RDP0" +config RDP1 + bool "RDP1" +config RDP2 + bool "RDP2" +endchoice + if TEST_KCONFIG rsource "periph/Kconfig" diff --git a/cpu/stm32/Makefile.include b/cpu/stm32/Makefile.include index 151696a9bd..c346bd3897 100644 --- a/cpu/stm32/Makefile.include +++ b/cpu/stm32/Makefile.include @@ -36,6 +36,16 @@ endif include $(RIOTCPU)/stm32/stm32_line.mk CPU_LINE ?= $(shell echo $(CPU_MODEL) | cut -c -9 | tr 'a-z-' 'A-Z_')xx +ifeq ($(CONFIG_RDP0),y) + CFLAGS += -DCONFIG_STM32_RDP=0 +endif +ifeq ($(CONFIG_RDP1),y) + CFLAGS += -DCONFIG_STM32_RDP=1 +endif +ifeq ($(CONFIG_RDP2),y) + CFLAGS += -DCONFIG_STM32_RDP=2 +endif + # Set CFLAGS CFLAGS += -D$(CPU_LINE) -DCPU_LINE_$(CPU_LINE) CFLAGS += -DSTM32_FLASHSIZE=$(FLASHSIZE)U diff --git a/cpu/stm32/cpu_init.c b/cpu/stm32/cpu_init.c index 3a6e19e862..1ffa72b35c 100644 --- a/cpu/stm32/cpu_init.c +++ b/cpu/stm32/cpu_init.c @@ -39,6 +39,7 @@ #include "periph/init.h" #include "periph/gpio.h" #include "board.h" +#include "pm_layered.h" #if defined (CPU_FAM_STM32L4) || defined (CPU_FAM_STM32G4) || \ defined(CPU_FAM_STM32L5) @@ -152,6 +153,138 @@ static void _gpio_init_ain(void) } #endif +/** + * @brief get the value of a register in a glitch resistant fashion + * + * This very teniously avoids optimization, even optimized it's better than + * nothing but periodic review should establish that it doesn't get optimized. + */ +__attribute__((always_inline)) +static inline uint32_t _multi_read_reg32(volatile uint32_t *addr, bool *glitch) +{ + uint32_t value = *addr; +// cppcheck-suppress duplicateExpression +// cppcheck-suppress knownConditionTrueFalse + if (*addr != value || *addr != value) { + /* (reason: volatile pointer forces multiple reads for glitch resistance, + glitch may force different value) */ + *glitch = true; + } + + return value; +} + +/** + * @brief Check RDP level is what the designer intended. + * + * RDP stands for "ReaDout Protection." + * + * The STM32L4 readout protection feature offers three levels of protection + * for all SRAM2 and Flash memory as well as the backup registers: + * + * - Level 0 (RDP0) means “no protection”. This is the factory default. Read, + * Write and Erase operations are permitted in the SRAM2 and Flash memory + * as well as the backup registers. Option bytes are changeable in Level 0. + * + * - Level 1 (RDP1) ensures read protection of the chip’s memories which + * includes the Flash memory and the backup registers as well as the SRAM2 + * content. Whenever a debugger access is detected or Boot mode is not set + * to a Flash memory area, any access to the Flash memory, the backup + * registers or to the SRAM2 generates a system hard fault which blocks all + * code execution until the next power-on reset. Option bytes can still be + * modified in Level 1. + * + * - Level 2 (RDP2) provides the same protection features for the SRAM2, + * Flash memory and Backup registers as described for Level 1. However, + * there are three major differences. The JTAG/SWD debugger connection is + * disabled (even at the ST factory, to ensure that there are no + * backdoors), the Boot mode is forced to User Flash memory REGARDLESS of + * what the boot 0/1 settings are, and Level 2 is permanent. Once set to + * Level 2, there is no going back; RDP/WRP option bytes can no longer be + * changed, as well as ALL the other option bytes. + * + * By way of background, changing the level of RDP protection is only + * permitted when the current protection level is ‘1’. Changing the protection + * level from '1' to '0' should automatically erase the entire user flash + * memory, SRAM2 and backup registers. + * + * The issue is that while Level 0 is 0xAA and Level 2 is 0xCC, Level 1 is any + * other number. So when OxCC is set and the chip is physically or + * electrically perturbed, flipping any bit will "fool" the CPU into thinking + * that it is in Level 1, allowing JTAG access and the changing of option + * bits. + * + * Think of this as a STM32-specific version of the Rowhammer attack. + * + * RDP may not be set correctly due to manufacturing error, glitch or + * intentional attack. It's done thrice to reduce the probablility of a + * glitch attack succeeding amongst all of the multireads desgned to make it + * tougher. + * + * This would be best served with a random delay at the beginning of the + * function. But a consistent strategy for all chips is tough. + * + * To set the RDP bytes, the J-Flash utility or the STM32 Unlock (from J-Link) + * utility, both provided by the manufacturer. + * + * You can also set the option bytes from code: + * + * 1. Unlock the option bytes by writing the correct keys to FLASH_OPTKEYR and + * clearing OPTLOCK + * 2. Set the desired option values in FLASH_OPTCR + * 3. Set OPTSTRT in FLASH_OPTCR + * + * This is the generic procedure for all option bytes. However, setting the + * RDP level in this fashion will immediately lock the CPU and force a reboot + * (and in some cases a clearing of the flash memory). + */ + +/* RDP only defined for particular families. Kconfig sets this as necessary */ +#if defined(STM32_OPTION_BYTES) + +#ifndef CONFIG_STM32_RDP +#define CONFIG_STM32_RDP 0 +#endif + +static bool _rdp_ok(void) +{ + if (CONFIG_STM32_RDP == 0) { + return true; + } + bool glitch = false; + uint32_t read1 = _multi_read_reg32(STM32_OPTION_BYTES, &glitch); + uint32_t read2 = _multi_read_reg32(STM32_OPTION_BYTES, &glitch); + uint32_t read3 = _multi_read_reg32(STM32_OPTION_BYTES, &glitch); + if (glitch) { + return false; + } + + switch (CONFIG_STM32_RDP) { + case 1: + return GET_RDP(read1) == 0xAA || + GET_RDP(read2) == 0xAA || + GET_RDP(read3) == 0xAA; + case 2: + return GET_RDP(read1) != 0xCC || + GET_RDP(read2) != 0xCC || + GET_RDP(read3) != 0xCC; + default: + return false; + } +} + +static void _rdp_check(void) +{ + if (!_rdp_ok()) { + /* halt execution */ + while (1) { + pm_set(0); + } + } +} + +#endif /* STM32_OPTION_BYTES */ + /** * @brief Initialize HW debug pins for Sub-GHz Radio */ @@ -212,6 +345,7 @@ void cpu_init(void) defined(CPU_FAM_STM32F4) || defined(CPU_FAM_STM32F7) || \ defined(CPU_FAM_STM32L1) _gpio_init_ain(); + _rdp_check(); #endif #if !defined(CPU_FAM_STM32MP1) || IS_USED(MODULE_STM32MP1_ENG_MODE) /* initialize the system clock as configured in the periph_conf.h */ diff --git a/cpu/stm32/include/periph/f0/periph_cpu.h b/cpu/stm32/include/periph/f0/periph_cpu.h index 11d8f7b389..5b0e21a9e7 100644 --- a/cpu/stm32/include/periph/f0/periph_cpu.h +++ b/cpu/stm32/include/periph/f0/periph_cpu.h @@ -41,6 +41,12 @@ extern "C" { #define STM32_BOOTLOADER_ADDR (0x1FFFC400) #endif +/** + * @brief Readout Protection (RDP) option bytes + */ +#define STM32_OPTION_BYTES ((uint32_t*) 0x1FFFF800) +#define GET_RDP(x) (x & 0xFF) + /** * @brief Override ADC resolution values * @{ diff --git a/cpu/stm32/include/periph/f1/periph_cpu.h b/cpu/stm32/include/periph/f1/periph_cpu.h index 79c2ff8e58..e8e0654837 100644 --- a/cpu/stm32/include/periph/f1/periph_cpu.h +++ b/cpu/stm32/include/periph/f1/periph_cpu.h @@ -33,6 +33,12 @@ extern "C" { #define STM32_BOOTLOADER_ADDR (0x1FFFF000) #endif +/** + * @brief Readout Protection (RDP) option bytes + */ +#define STM32_OPTION_BYTES ((uint32_t*) 0x1FFFF800) +#define GET_RDP(x) (x & 0xFF) + #endif /* ndef DOXYGEN */ /** diff --git a/cpu/stm32/include/periph/f2/periph_cpu.h b/cpu/stm32/include/periph/f2/periph_cpu.h index 656704f662..5e82d3d254 100644 --- a/cpu/stm32/include/periph/f2/periph_cpu.h +++ b/cpu/stm32/include/periph/f2/periph_cpu.h @@ -38,6 +38,12 @@ extern "C" { */ #define STM32_BOOTLOADER_ADDR (0x1FFF0000) +/** + * @brief Readout Protection (RDP) option bytes + */ +#define STM32_OPTION_BYTES ((uint32_t*) 0x1FFFC000) +#define GET_RDP(x) ((x & 0xFF00) >> 8) + /** * @brief Override the ADC resolution configuration * @{ diff --git a/cpu/stm32/include/periph/f3/periph_cpu.h b/cpu/stm32/include/periph/f3/periph_cpu.h index 03d1ead1a8..69df093792 100644 --- a/cpu/stm32/include/periph/f3/periph_cpu.h +++ b/cpu/stm32/include/periph/f3/periph_cpu.h @@ -49,6 +49,12 @@ extern "C" { */ #define STM32_BOOTLOADER_ADDR (0x1FFFD800) +/** + * @brief Readout Protection (RDP) option bytes + */ +#define STM32_OPTION_BYTES ((uint32_t*) 0x1FFFF800) +#define GET_RDP(x) (x & 0xFF) + /** * @brief Override ADC resolution values * @{ diff --git a/cpu/stm32/include/periph/f4/periph_cpu.h b/cpu/stm32/include/periph/f4/periph_cpu.h index f6e58f12fe..9318082c13 100644 --- a/cpu/stm32/include/periph/f4/periph_cpu.h +++ b/cpu/stm32/include/periph/f4/periph_cpu.h @@ -49,6 +49,12 @@ extern "C" { #define STM32_BOOTLOADER_ADDR (0x1FFF0000) #endif +/** + * @brief Readout Protection (RDP) option bytes + */ +#define STM32_OPTION_BYTES ((uint32_t*) 0x1FFFC000) +#define GET_RDP(x) ((x & 0xFF00) >> 8) + /** * @brief Override the ADC resolution configuration * @{ diff --git a/cpu/stm32/include/periph/f7/periph_cpu.h b/cpu/stm32/include/periph/f7/periph_cpu.h index f6497b77e4..a7c1d5ddd2 100644 --- a/cpu/stm32/include/periph/f7/periph_cpu.h +++ b/cpu/stm32/include/periph/f7/periph_cpu.h @@ -32,6 +32,12 @@ extern "C" { */ #define STM32_BOOTLOADER_ADDR (0x1FF00000) +/** + * @brief Readout Protection (RDP) option bytes + */ +#define STM32_OPTION_BYTES ((uint32_t*) 0x1FFF0000) +#define GET_RDP(x) ((x & 0xFF00) >> 8) + /** * @brief Override the ADC resolution configuration * @{ diff --git a/cpu/stm32/include/periph/l1/periph_cpu.h b/cpu/stm32/include/periph/l1/periph_cpu.h index b2b1734d31..b5155300fe 100644 --- a/cpu/stm32/include/periph/l1/periph_cpu.h +++ b/cpu/stm32/include/periph/l1/periph_cpu.h @@ -33,6 +33,12 @@ extern "C" { */ #define STM32_BOOTLOADER_ADDR (0x1FF00000) +/** + * @brief Readout Protection (RDP) option bytes + */ +#define STM32_OPTION_BYTES ((uint32_t*) 0x1FF80000) +#define GET_RDP(x) (x & 0xFF) + /** * @brief Override the ADC resolution configuration * @{