mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 04:58:43 +03:00
feat(build): support KASAN in the build system v2 (cmakev2)
Mirror Kernel Address Sanitizer support into cmakev2 so that CONFIG_COMPILER_KASAN instruments application code the same way build system v1 does.
This commit is contained in:
@@ -430,39 +430,12 @@ endif()
|
||||
|
||||
# KASAN: exclude low-level / hardware-access components from instrumentation.
|
||||
# Apply the exclusion after add_subdirectory() so every target already exists.
|
||||
#
|
||||
# Rationale per bucket:
|
||||
# - hal / soc / esp_rom + every esp_hal_* peripheral HAL: perform volatile
|
||||
# MMIO accesses (outside the shadow window, so each check is a no-op but
|
||||
# still pays the indirect call into __asan_load* / __asan_store*).
|
||||
# - spi_flash: runs with the flash cache disabled.
|
||||
# - esp_hw_support: RTC/PMU register access, runs with MSPI bus held.
|
||||
# - bootloader_support: runs before kasan_init_shadow().
|
||||
# - freertos: scheduler / ISR plumbing is too hot to instrument.
|
||||
# - heap: walks its own TLSF metadata living inside the (poisoned) pool;
|
||||
# instrumented metadata walks would self-trigger. Shadow updates are
|
||||
# handled explicitly via heap_kasan.c / heap_kasan_hooks.c, which are
|
||||
# individually compiled with -fno-sanitize via set_source_files_properties.
|
||||
# The exclusion set itself is defined in tools/cmake/kasan.cmake and shared with
|
||||
# the Build system v2, which applies it in idf_build_library().
|
||||
if(CONFIG_COMPILER_KASAN AND NOT BOOTLOADER_BUILD)
|
||||
# Match every esp_hal_* peripheral HAL component dynamically (instead of
|
||||
# listing them one by one) so newly added HAL components stay excluded
|
||||
# without revisiting this file.
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/tools/cmake/kasan.cmake")
|
||||
idf_build_get_property(__kasan_all_components BUILD_COMPONENTS)
|
||||
set(__kasan_excluded_components "")
|
||||
foreach(__kasan_c ${__kasan_all_components})
|
||||
if(__kasan_c MATCHES "^esp_hal_")
|
||||
list(APPEND __kasan_excluded_components ${__kasan_c})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
list(APPEND __kasan_excluded_components
|
||||
hal soc esp_rom
|
||||
spi_flash
|
||||
esp_hw_support
|
||||
bootloader_support
|
||||
freertos
|
||||
heap
|
||||
)
|
||||
kasan_filter_excluded_components(__kasan_excluded_components ${__kasan_all_components})
|
||||
|
||||
foreach(__kasan_comp ${__kasan_excluded_components})
|
||||
set(__kasan_lib "__idf_${__kasan_comp}")
|
||||
|
||||
@@ -96,6 +96,10 @@ idf_build_set_property(SET_COMPILER_OPTIMIZATION NO)
|
||||
# file name, which LTO does not preserve, so it must never be compiled or linked
|
||||
# with LTO regardless of the application's CONFIG_COMPILER_LTO_* options.
|
||||
idf_build_set_property(SET_COMPILER_LTO NO)
|
||||
# The bootloader runs before kasan_init_shadow() and carries no sanitizer
|
||||
# runtime, so it must never be instrumented regardless of the application's
|
||||
# CONFIG_COMPILER_KASAN option.
|
||||
idf_build_set_property(SET_COMPILER_KASAN NO)
|
||||
|
||||
# Perform internal IDF project initialisation
|
||||
idf_project_init()
|
||||
|
||||
61
tools/cmake/kasan.cmake
Normal file
61
tools/cmake/kasan.cmake
Normal file
@@ -0,0 +1,61 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Kernel Address Sanitizer (CONFIG_COMPILER_KASAN) exclusion policy.
|
||||
#
|
||||
# Included by both build systems so that the set of components excluded from
|
||||
# instrumentation has a single definition. The only build system API relied on
|
||||
# here is idf_component_get_property(), which both provide with the same
|
||||
# signature. Applying the result is left to each build system, because the
|
||||
# component-name to library-target mapping differs between them.
|
||||
|
||||
# Store in <var> the subset of the given component names that must not be
|
||||
# compiled with KASAN instrumentation. A component qualifies either because it
|
||||
# is part of the built-in low-level set below, or because it declares the
|
||||
# NO_KASAN component property in its own CMakeLists.txt.
|
||||
#
|
||||
# Rationale per bucket:
|
||||
# - hal / soc / esp_rom + every esp_hal_* peripheral HAL: perform volatile
|
||||
# MMIO accesses (outside the shadow window, so each check is a no-op but
|
||||
# still pays the indirect call into __asan_load* / __asan_store*).
|
||||
# - spi_flash: runs with the flash cache disabled.
|
||||
# - esp_hw_support: RTC/PMU register access, runs with MSPI bus held.
|
||||
# - bootloader_support: runs before kasan_init_shadow().
|
||||
# - freertos: scheduler / ISR plumbing is too hot to instrument.
|
||||
# - heap: walks its own TLSF metadata living inside the (poisoned) pool;
|
||||
# instrumented metadata walks would self-trigger. Shadow updates are
|
||||
# handled explicitly via heap_kasan.c / heap_kasan_hooks.c, which are
|
||||
# individually compiled with -fno-sanitize via set_source_files_properties.
|
||||
function(kasan_filter_excluded_components var)
|
||||
set(excluded_components
|
||||
hal soc esp_rom
|
||||
spi_flash
|
||||
esp_hw_support
|
||||
bootloader_support
|
||||
freertos
|
||||
heap)
|
||||
|
||||
# Match every esp_hal_* peripheral HAL component dynamically instead of
|
||||
# listing them one by one, so newly added HAL components stay excluded
|
||||
# without revisiting this file.
|
||||
set(excluded_pattern "^esp_hal_")
|
||||
|
||||
set(excluded "")
|
||||
foreach(component_name IN LISTS ARGN)
|
||||
if(component_name IN_LIST excluded_components
|
||||
OR component_name MATCHES "${excluded_pattern}")
|
||||
list(APPEND excluded "${component_name}")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
# Any other component, including one shipped by a user or pulled from
|
||||
# the component registry, opts out by setting the NO_KASAN component
|
||||
# property in its own CMakeLists.txt.
|
||||
idf_component_get_property(no_kasan "${component_name}" NO_KASAN)
|
||||
if(no_kasan)
|
||||
list(APPEND excluded "${component_name}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(${var} "${excluded}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
@@ -7,6 +7,8 @@ include(utilities)
|
||||
include(CheckCCompilerFlag)
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(component_validation)
|
||||
# Shared with the Build system v1: single definition of the KASAN exclusion set.
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/kasan.cmake)
|
||||
|
||||
#[[api
|
||||
.. cmakev2:function:: idf_build_set_property
|
||||
@@ -317,6 +319,57 @@ function(__idf_build_link_whole_archive target scope library)
|
||||
target_link_libraries(${target} ${scope} ${library})
|
||||
endfunction()
|
||||
|
||||
#[[
|
||||
__kasan_exclude_components(<library>)
|
||||
|
||||
Compile the low-level components linked to ``library`` without Kernel
|
||||
Address Sanitizer instrumentation.
|
||||
|
||||
The exclusion set is defined once in ``tools/cmake/kasan.cmake`` and shared
|
||||
with the Build system v1. Called from ``idf_build_library`` once
|
||||
LIBRARY_COMPONENTS_LINKED is populated, so every component target already
|
||||
exists and ``-fno-sanitize`` is appended after the global ``-fsanitize``
|
||||
added while the component was processed, which is what makes it win.
|
||||
|
||||
A subproject that opted out of instrumentation altogether by setting the
|
||||
SET_COMPILER_KASAN build property to NO never had ``-fsanitize`` applied, so
|
||||
there is nothing to undo and this is a no-op there.
|
||||
#]]
|
||||
function(__kasan_exclude_components library)
|
||||
idf_build_get_property(set_compiler_kasan SET_COMPILER_KASAN)
|
||||
if(NOT DEFINED set_compiler_kasan OR set_compiler_kasan STREQUAL "")
|
||||
set(set_compiler_kasan YES)
|
||||
endif()
|
||||
if(NOT CONFIG_COMPILER_KASAN OR NOT set_compiler_kasan)
|
||||
return()
|
||||
endif()
|
||||
|
||||
idf_library_get_property(components_linked "${library}" LIBRARY_COMPONENTS_LINKED)
|
||||
kasan_filter_excluded_components(excluded ${components_linked})
|
||||
|
||||
foreach(component_name IN LISTS excluded)
|
||||
idf_component_get_property(component_real_target "${component_name}" COMPONENT_REAL_TARGET)
|
||||
idf_component_get_property(component_real_target_type "${component_name}" COMPONENT_REAL_TARGET_TYPE)
|
||||
|
||||
# Components that created no target, and INTERFACE libraries, have no
|
||||
# sources to de-instrument. Adding an INTERFACE compile option would
|
||||
# also propagate -fno-sanitize to every consumer, including the
|
||||
# application under test.
|
||||
if(NOT component_real_target OR "${component_real_target}" STREQUAL "NOTFOUND")
|
||||
continue()
|
||||
endif()
|
||||
if(NOT "${component_real_target_type}" STREQUAL "STATIC_LIBRARY")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
# idf_build_library may run more than once per configure. Appending the
|
||||
# option again is harmless: CMake de-duplicates compile options, and
|
||||
# every copy lands after the global -fsanitize added while the component
|
||||
# was processed, so the surviving one still wins.
|
||||
target_compile_options("${component_real_target}" PRIVATE "-fno-sanitize=kernel-address")
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
#[[api
|
||||
.. cmakev2:function:: idf_build_library
|
||||
|
||||
@@ -433,6 +486,13 @@ function(idf_build_library library)
|
||||
idf_library_set_property("${library}" LIBRARY_COMPONENT_INTERFACES_LINKED "${component_interface}" APPEND)
|
||||
endforeach()
|
||||
|
||||
# Kernel Address Sanitizer (CONFIG_COMPILER_KASAN): de-instrument the
|
||||
# low-level components. Applied here, once the set of components linked to
|
||||
# the library is known and every component target already exists, so that
|
||||
# -fno-sanitize lands after the global -fsanitize added while the component
|
||||
# was processed and therefore wins.
|
||||
__kasan_exclude_components("${library}")
|
||||
|
||||
# Collect linker fragment files from all components linked to the library
|
||||
# interface and store them in the __LDGEN_FRAGMENT_FILES files. This
|
||||
# property is used by ldgen to generate template-based linker scripts.
|
||||
|
||||
@@ -861,6 +861,18 @@ endfunction()
|
||||
|
||||
List of linker script files added to the link command (with ``-T``) for the
|
||||
component.
|
||||
|
||||
.. cmakev2:component_property:: NO_KASAN
|
||||
|
||||
When set to a true value, the component is compiled without Kernel Address
|
||||
Sanitizer instrumentation (``-fno-sanitize=kernel-address``) while
|
||||
``CONFIG_COMPILER_KASAN`` is enabled. Set it on a component that runs before
|
||||
the sanitizer shadow is initialised, executes with the flash cache disabled,
|
||||
or is otherwise too low-level to instrument.
|
||||
|
||||
The low-level ESP-IDF components are excluded already; the built-in set is
|
||||
defined in ``tools/cmake/kasan.cmake`` and shared with the CMake-based build
|
||||
system v1, which honours this property as well.
|
||||
#]]
|
||||
|
||||
#[[api
|
||||
|
||||
@@ -356,6 +356,31 @@ function(__init_project_configuration)
|
||||
list(APPEND compile_options "-fstack-protector-all")
|
||||
endif()
|
||||
|
||||
# Kernel Address Sanitizer (CONFIG_COMPILER_KASAN): instrument every memory
|
||||
# load and store with a shadow-memory check. The flag goes into the C and C++
|
||||
# options rather than the language-agnostic list because the assembler does
|
||||
# not accept -fsanitize. Low-level components are de-instrumented again in
|
||||
# idf_build_library(), see __kasan_exclude_components().
|
||||
#
|
||||
# A subproject that must never be instrumented (for example the bootloader,
|
||||
# which runs before the sanitizer shadow is initialised) sets the
|
||||
# SET_COMPILER_KASAN build property to NO before idf_project_init(), the
|
||||
# same way it uses SET_COMPILER_LTO and SET_COMPILER_OPTIMIZATION; an unset
|
||||
# property means instrumentation is allowed.
|
||||
idf_build_get_property(set_compiler_kasan SET_COMPILER_KASAN)
|
||||
if(NOT DEFINED set_compiler_kasan OR set_compiler_kasan STREQUAL "")
|
||||
set(set_compiler_kasan YES)
|
||||
endif()
|
||||
if(CONFIG_COMPILER_KASAN AND set_compiler_kasan)
|
||||
list(APPEND c_compile_options "-fsanitize=kernel-address")
|
||||
list(APPEND cxx_compile_options "-fsanitize=kernel-address")
|
||||
if(NOT CONFIG_KASAN_STACK)
|
||||
list(APPEND c_compile_options "--param" "asan-stack=0")
|
||||
list(APPEND cxx_compile_options "--param" "asan-stack=0")
|
||||
endif()
|
||||
list(APPEND link_options "-fsanitize=kernel-address")
|
||||
endif()
|
||||
|
||||
if(CONFIG_COMPILER_DUMP_RTL_FILES)
|
||||
list(APPEND compile_options "-fdump-rtl-expand")
|
||||
endif()
|
||||
|
||||
Reference in New Issue
Block a user