cpu/stm32: Add hardening changes to stm32

Initialize STM32 RDP in a glitch-resistant fashion to prevent
debugger use when restrictions are set by the designer.
This commit is contained in:
VanL 2021-10-12 13:00:19 -05:00
parent a85853ae30
commit ee832148b3
10 changed files with 206 additions and 0 deletions

View File

@ -39,6 +39,26 @@ orsource "kconfigs/*/Kconfig"
orsource "kconfigs/*/Kconfig.lines" orsource "kconfigs/*/Kconfig.lines"
orsource "kconfigs/*/Kconfig.models" 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 if TEST_KCONFIG
rsource "periph/Kconfig" rsource "periph/Kconfig"

View File

@ -36,6 +36,16 @@ endif
include $(RIOTCPU)/stm32/stm32_line.mk include $(RIOTCPU)/stm32/stm32_line.mk
CPU_LINE ?= $(shell echo $(CPU_MODEL) | cut -c -9 | tr 'a-z-' 'A-Z_')xx 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 # Set CFLAGS
CFLAGS += -D$(CPU_LINE) -DCPU_LINE_$(CPU_LINE) CFLAGS += -D$(CPU_LINE) -DCPU_LINE_$(CPU_LINE)
CFLAGS += -DSTM32_FLASHSIZE=$(FLASHSIZE)U CFLAGS += -DSTM32_FLASHSIZE=$(FLASHSIZE)U

View File

@ -39,6 +39,7 @@
#include "periph/init.h" #include "periph/init.h"
#include "periph/gpio.h" #include "periph/gpio.h"
#include "board.h" #include "board.h"
#include "pm_layered.h"
#if defined (CPU_FAM_STM32L4) || defined (CPU_FAM_STM32G4) || \ #if defined (CPU_FAM_STM32L4) || defined (CPU_FAM_STM32G4) || \
defined(CPU_FAM_STM32L5) defined(CPU_FAM_STM32L5)
@ -152,6 +153,138 @@ static void _gpio_init_ain(void)
} }
#endif #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 chips 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 * @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_STM32F4) || defined(CPU_FAM_STM32F7) || \
defined(CPU_FAM_STM32L1) defined(CPU_FAM_STM32L1)
_gpio_init_ain(); _gpio_init_ain();
_rdp_check();
#endif #endif
#if !defined(CPU_FAM_STM32MP1) || IS_USED(MODULE_STM32MP1_ENG_MODE) #if !defined(CPU_FAM_STM32MP1) || IS_USED(MODULE_STM32MP1_ENG_MODE)
/* initialize the system clock as configured in the periph_conf.h */ /* initialize the system clock as configured in the periph_conf.h */

View File

@ -41,6 +41,12 @@ extern "C" {
#define STM32_BOOTLOADER_ADDR (0x1FFFC400) #define STM32_BOOTLOADER_ADDR (0x1FFFC400)
#endif #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 * @brief Override ADC resolution values
* @{ * @{

View File

@ -33,6 +33,12 @@ extern "C" {
#define STM32_BOOTLOADER_ADDR (0x1FFFF000) #define STM32_BOOTLOADER_ADDR (0x1FFFF000)
#endif #endif
/**
* @brief Readout Protection (RDP) option bytes
*/
#define STM32_OPTION_BYTES ((uint32_t*) 0x1FFFF800)
#define GET_RDP(x) (x & 0xFF)
#endif /* ndef DOXYGEN */ #endif /* ndef DOXYGEN */
/** /**

View File

@ -38,6 +38,12 @@ extern "C" {
*/ */
#define STM32_BOOTLOADER_ADDR (0x1FFF0000) #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 * @brief Override the ADC resolution configuration
* @{ * @{

View File

@ -49,6 +49,12 @@ extern "C" {
*/ */
#define STM32_BOOTLOADER_ADDR (0x1FFFD800) #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 * @brief Override ADC resolution values
* @{ * @{

View File

@ -49,6 +49,12 @@ extern "C" {
#define STM32_BOOTLOADER_ADDR (0x1FFF0000) #define STM32_BOOTLOADER_ADDR (0x1FFF0000)
#endif #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 * @brief Override the ADC resolution configuration
* @{ * @{

View File

@ -32,6 +32,12 @@ extern "C" {
*/ */
#define STM32_BOOTLOADER_ADDR (0x1FF00000) #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 * @brief Override the ADC resolution configuration
* @{ * @{

View File

@ -33,6 +33,12 @@ extern "C" {
*/ */
#define STM32_BOOTLOADER_ADDR (0x1FF00000) #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 * @brief Override the ADC resolution configuration
* @{ * @{