mirror of
https://github.com/espressif/esp-idf.git
synced 2026-08-18 06:35:35 +03:00
fix(esp_common/esp_fault): make ESP_FAULT_ASSERT survive optimization
ESP_FAULT_ASSERT(C) was silently deleted by the optimizer when C is a cached flag/status already proven by a preceding `if (!C) return/goto`: the compiler folds C to a constant and drops all three checks, removing the fault-injection protection with no warning.
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
# CI specific options start from "--parallel-count xxx". could ignore when running locally
|
||||
- run_cmd idf-build-apps build
|
||||
-p tools/test_apps/system/clang_build_test
|
||||
components/esp_security/test_apps/fault_assert_opt_check
|
||||
-t $IDF_TARGET
|
||||
--parallel-count ${CI_NODE_TOTAL:-1}
|
||||
--parallel-index ${CI_NODE_INDEX:-1}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#endif
|
||||
bootloader_stack_overhead = 0x2000; /* For safety margin between bootloader data section and startup stacks */
|
||||
bootloader_dram_seg_len = 0x5000;
|
||||
bootloader_iram_loader_seg_len = 0x7000;
|
||||
bootloader_iram_loader_seg_len = 0x8000;
|
||||
bootloader_iram_seg_len = 0x2D00;
|
||||
|
||||
/* Start of the lower region is determined by region size and the end of the higher region */
|
||||
@@ -54,9 +54,9 @@ MEMORY
|
||||
* 3. Update SRAM_DRAM_END in components/esp_system/ld/esp32p4/memory.ld.in to the same value.
|
||||
*/
|
||||
#if !CONFIG_ESP32P4_SELECTS_REV_LESS_V3
|
||||
#define BOOTLOADER_IRAM_LOADER_SEG_START_EXPECTED 0x4FFAEFC0
|
||||
#define BOOTLOADER_IRAM_LOADER_SEG_START_EXPECTED 0x4FFADFC0
|
||||
#else
|
||||
#define BOOTLOADER_IRAM_LOADER_SEG_START_EXPECTED 0x4FF2CBD0
|
||||
#define BOOTLOADER_IRAM_LOADER_SEG_START_EXPECTED 0x4FF2BBD0
|
||||
#endif
|
||||
ASSERT(bootloader_iram_loader_seg_start == BOOTLOADER_IRAM_LOADER_SEG_START_EXPECTED,
|
||||
"bootloader_iram_loader_seg_start inconsistent with SRAM_DRAM_END");
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_rom_sys.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -19,6 +20,10 @@ extern "C" {
|
||||
* - Expands CONDITION multiple times (condition must have no side effects)
|
||||
* - Compiler is told all registers are invalid before evaluating CONDITION each time, to avoid a fault
|
||||
* causing a misread of a register used in all three evaluations of CONDITION.
|
||||
* - The result of each evaluation is stored into a volatile variable and re-read before the branch.
|
||||
* This prevents the compiler from constant-folding CONDITION and deleting the whole check when it
|
||||
* has already proven the value - e.g. when this macro follows a normal "if (!cond) { ... }" check
|
||||
* of the same value, which would otherwise silently remove the fault-injection protection.
|
||||
* - If CONDITION is ever false, a system reset is triggered.
|
||||
*
|
||||
* @note Place this macro after a "normal" check of CONDITION that will fail with a normal error
|
||||
@@ -40,13 +45,20 @@ extern "C" {
|
||||
* @param CONDITION A condition which will evaluate true unless an attacker used fault injection to skip or corrupt some other critical system calculation.
|
||||
*
|
||||
*/
|
||||
#define ESP_FAULT_ASSERT(CONDITION) do { \
|
||||
asm volatile ("" ::: "memory"); \
|
||||
if(!(CONDITION)) _ESP_FAULT_RESET(); \
|
||||
asm volatile ("" ::: "memory"); \
|
||||
if(!(CONDITION)) _ESP_FAULT_RESET(); \
|
||||
asm volatile ("" ::: "memory"); \
|
||||
if(!(CONDITION)) _ESP_FAULT_RESET(); \
|
||||
#define ESP_FAULT_ASSERT(CONDITION) do { \
|
||||
bool esp_fault_assert_chk; \
|
||||
asm volatile ("" ::: "memory"); \
|
||||
esp_fault_assert_chk = (CONDITION); \
|
||||
asm volatile ("" : "+r"(esp_fault_assert_chk)); \
|
||||
if(!esp_fault_assert_chk) _ESP_FAULT_RESET(); \
|
||||
asm volatile ("" ::: "memory"); \
|
||||
esp_fault_assert_chk = (CONDITION); \
|
||||
asm volatile ("" : "+r"(esp_fault_assert_chk)); \
|
||||
if(!esp_fault_assert_chk) _ESP_FAULT_RESET(); \
|
||||
asm volatile ("" ::: "memory"); \
|
||||
esp_fault_assert_chk = (CONDITION); \
|
||||
asm volatile ("" : "+r"(esp_fault_assert_chk)); \
|
||||
if(!esp_fault_assert_chk) _ESP_FAULT_RESET(); \
|
||||
} while(0)
|
||||
|
||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
|
||||
@@ -6,3 +6,9 @@ components/esp_security/test_apps/crypto_drivers:
|
||||
depends_components:
|
||||
- esp_security
|
||||
- esp_hal_security
|
||||
|
||||
components/esp_security/test_apps/fault_assert_opt_check:
|
||||
enable:
|
||||
- if: IDF_TARGET in ["esp32", "esp32c3"] # one Xtensa + one RISC-V
|
||||
depends_components:
|
||||
- esp_common
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
set(COMPONENTS main)
|
||||
|
||||
project(fault_assert_opt_check)
|
||||
|
||||
# Regression guard: fail the build if ESP_FAULT_ASSERT() gets optimized away.
|
||||
idf_build_get_property(python PYTHON)
|
||||
add_custom_command(
|
||||
TARGET ${CMAKE_PROJECT_NAME}.elf POST_BUILD
|
||||
COMMAND ${python} "${CMAKE_CURRENT_SOURCE_DIR}/check_fault_asserts.py"
|
||||
"$<TARGET_FILE:${CMAKE_PROJECT_NAME}.elf>" "${CMAKE_OBJDUMP}"
|
||||
COMMENT "Verifying ESP_FAULT_ASSERT() survived optimization"
|
||||
VERBATIM)
|
||||
@@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 | ESP32-C3 |
|
||||
| ----------------- | ----- | -------- |
|
||||
@@ -0,0 +1,77 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
"""Post-build regression guard for ESP_FAULT_ASSERT().
|
||||
|
||||
Disassembles the test app and verifies that the ESP_FAULT_ASSERT() calls in the
|
||||
known test functions still emit their reset blocks. One intact assert produces
|
||||
three independent reset-on-failure paths, i.e. three references to
|
||||
``esp_rom_software_reset_system`` in the function. If the macro ever regresses
|
||||
and the optimizer folds the checks away, the count drops and this exits non-zero,
|
||||
failing the build.
|
||||
|
||||
Usage: check_fault_asserts.py <app.elf> <objdump>
|
||||
"""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
RESET_SYM = 'esp_rom_software_reset_system'
|
||||
|
||||
# function name -> number of ESP_FAULT_ASSERT calls it contains (x3 reset blocks each)
|
||||
EXPECTED = {
|
||||
'test_fa_guarded_flag': 1,
|
||||
'test_fa_guarded_status': 1,
|
||||
}
|
||||
|
||||
FUNC_RE = re.compile(r'^[0-9a-fA-F]+ <(.+)>:$')
|
||||
|
||||
|
||||
def reset_counts(elf: str, objdump: str) -> dict:
|
||||
dis = subprocess.run([objdump, '-d', elf], capture_output=True, text=True, check=True).stdout
|
||||
counts: dict = {}
|
||||
cur = None
|
||||
for line in dis.splitlines():
|
||||
m = FUNC_RE.match(line)
|
||||
if m:
|
||||
cur = m.group(1)
|
||||
counts.setdefault(cur, 0)
|
||||
elif cur and RESET_SYM in line:
|
||||
counts[cur] += 1
|
||||
return counts
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 3:
|
||||
print(__doc__)
|
||||
return 2
|
||||
elf, objdump = sys.argv[1], sys.argv[2]
|
||||
counts = reset_counts(elf, objdump)
|
||||
|
||||
failed = False
|
||||
for fn, n_asserts in EXPECTED.items():
|
||||
expected = 3 * n_asserts
|
||||
found = counts.get(fn)
|
||||
if found is None:
|
||||
print(f'ERROR: {fn} not found in {elf} (renamed/removed?)')
|
||||
failed = True
|
||||
elif found < expected:
|
||||
print(
|
||||
f'ERROR: ESP_FAULT_ASSERT optimized away in {fn}: '
|
||||
f'{found} reset checks, expected {expected}. '
|
||||
f'See components/esp_common/include/esp_fault.h'
|
||||
)
|
||||
failed = True
|
||||
else:
|
||||
print(f'OK: {fn} -> {found} reset checks ({found // 3} assert(s) x3)')
|
||||
|
||||
if failed:
|
||||
print('FAILED: ESP_FAULT_ASSERT regression check')
|
||||
return 1
|
||||
print('PASSED: ESP_FAULT_ASSERT checks survived optimization')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "test_fault_assert.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Regression guard for ESP_FAULT_ASSERT() being silently optimised away.
|
||||
*
|
||||
* ESP_FAULT_ASSERT(C) must emit three independent "evaluate C -> reset if false"
|
||||
* checks. When C is a value the optimiser can already prove (e.g. a flag pinned
|
||||
* by a preceding "if (!C) return"), a naive implementation lets GCC constant-fold
|
||||
* C and delete all three checks, removing the fault-injection protection with no
|
||||
* warning.
|
||||
*
|
||||
* The functions below place ESP_FAULT_ASSERT in exactly that
|
||||
* "proven-true cached value" shape. check_fault_asserts.py disassembles the built
|
||||
* app and fails the build if any of them lost its reset blocks. Keep them
|
||||
* noinline+used so each is an independent symbol the checker can find.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "esp_fault.h"
|
||||
#include "esp_attr.h"
|
||||
|
||||
/* volatile so the initial value is opaque: only the early-return "proves" it,
|
||||
* which is the precise condition that triggers the optimiser elimination. */
|
||||
volatile bool fa_test_flag = true;
|
||||
volatile int fa_test_status = 0;
|
||||
volatile int fa_test_sink;
|
||||
|
||||
/* Cached bool guarded by an early return -> the original elimination case. */
|
||||
bool NOINLINE_ATTR test_fa_guarded_flag(void)
|
||||
{
|
||||
bool valid = fa_test_flag;
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
ESP_FAULT_ASSERT(valid);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Cached status compared to a constant, guarded by an early return. */
|
||||
int NOINLINE_ATTR test_fa_guarded_status(void)
|
||||
{
|
||||
int status = fa_test_status;
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
ESP_FAULT_ASSERT(status == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* Reference the test functions so they are linked (not GC'd). */
|
||||
fa_test_sink = (int)test_fa_guarded_flag() + test_fa_guarded_status();
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# -O2
|
||||
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
||||
@@ -0,0 +1,2 @@
|
||||
# -Os
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
@@ -0,0 +1,4 @@
|
||||
# The clang-built esp32 (Xtensa) bootloader is slightly larger than the GCC one and
|
||||
# overflows the default 0x7000 limit; move the partition table offset to give it room.
|
||||
# Harmless for GCC builds.
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x9000
|
||||
@@ -21,7 +21,7 @@
|
||||
#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM
|
||||
#define SRAM_END ULP_HP_MEM_START
|
||||
#else
|
||||
#define SRAM_END 0x4FFAEFC0 /* 2nd stage bootloader iram_loader_seg start address */
|
||||
#define SRAM_END 0x4FFADFC0 /* 2nd stage bootloader iram_loader_seg start address */
|
||||
#endif
|
||||
#define SRAM_SIZE SRAM_END - SRAM_START
|
||||
#else
|
||||
@@ -29,7 +29,7 @@
|
||||
#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM
|
||||
#define SRAM_LOW_END ULP_HP_MEM_START
|
||||
#else
|
||||
#define SRAM_LOW_END 0x4FF2CBD0 /* 2nd stage bootloader iram_loader_seg start address */
|
||||
#define SRAM_LOW_END 0x4FF2BBD0 /* 2nd stage bootloader iram_loader_seg start address */
|
||||
#endif
|
||||
#define SRAM_LOW_SIZE SRAM_LOW_END - SRAM_LOW_START
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Increasing TEE IRAM size
|
||||
# 38KB
|
||||
CONFIG_SECURE_TEE_IRAM_SIZE=0x9800
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# builds across various configurations - and is not intended for production use.
|
||||
|
||||
# Reducing TEE IRAM size
|
||||
# 29KB
|
||||
CONFIG_SECURE_TEE_IRAM_SIZE=0x7400
|
||||
# 29.5KB
|
||||
CONFIG_SECURE_TEE_IRAM_SIZE=0x7600
|
||||
|
||||
# TEE Secure Storage: Release mode
|
||||
CONFIG_SECURE_TEE_SEC_STG_MODE_RELEASE=y
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# builds across various configurations - and is not intended for production use.
|
||||
|
||||
# Increasing TEE I/DRAM sizes
|
||||
# 34KB
|
||||
CONFIG_SECURE_TEE_IRAM_SIZE=0x8800
|
||||
# 38KB
|
||||
CONFIG_SECURE_TEE_IRAM_SIZE=0x9800
|
||||
# 22KB
|
||||
CONFIG_SECURE_TEE_DRAM_SIZE=0x5800
|
||||
|
||||
|
||||
@@ -14,6 +14,6 @@ CONFIG_SECURE_TEE_SEC_STG_SUPPORT_SECP384R1_SIGN=y
|
||||
# Enabling flash protection over SPI1
|
||||
CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1=y
|
||||
|
||||
# Increasing TEE DRAM size
|
||||
# 20KB
|
||||
CONFIG_SECURE_TEE_DRAM_SIZE=0x5000
|
||||
# Increasing TEE IRAM size
|
||||
# 36KB
|
||||
CONFIG_SECURE_TEE_IRAM_SIZE=0x9000
|
||||
|
||||
@@ -16,3 +16,7 @@ CONFIG_SECURE_TEE_ATT_KEY_STR_ID="tee_att_keyN"
|
||||
|
||||
# Enabling flash protection over SPI1
|
||||
CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1=y
|
||||
|
||||
# Increasing TEE IRAM size
|
||||
# 38KB
|
||||
CONFIG_SECURE_TEE_IRAM_SIZE=0x9800
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
factory, 0, 0, 0x10000, 1M
|
||||
nvs, data, nvs, , 0x6000,
|
||||
factory, 0, 0, , 1M
|
||||
flash_test, data, fat, , 528K
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
CONFIG_ESP_TASK_WDT_EN=n
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0X9000
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_SECURE_FLASH_ENC_ENABLED=y
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
CONFIG_IDF_TARGET="esp32c5"
|
||||
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0xD000
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0xE000
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="test/partitions_efuse_emul.csv"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
CONFIG_IDF_TARGET="esp32p4"
|
||||
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0xD000
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0XE000
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="test/partitions_efuse_emul.csv"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user