mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
Merge branch 'feature/add_kasan_support' into 'master'
feat(kasan): add Kernel Address Sanitizer (KASAN) support for ESP-IDF Closes IDF-13467 See merge request espressif/esp-idf!48106
This commit is contained in:
@@ -56,6 +56,10 @@ else()
|
||||
"system_time.c"
|
||||
"stack_check.c"
|
||||
"ubsan.c")
|
||||
|
||||
if(CONFIG_COMPILER_KASAN)
|
||||
list(APPEND srcs "kasan.c")
|
||||
endif()
|
||||
if(CONFIG_SOC_WDT_SUPPORTED)
|
||||
list(APPEND srcs "int_wdt.c")
|
||||
endif()
|
||||
@@ -110,11 +114,30 @@ else()
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u start_app_other_cores")
|
||||
endif()
|
||||
|
||||
# Disable stack protection in files which are involved in initialization of that feature
|
||||
set_source_files_properties(
|
||||
"startup.c" "stack_check.c" "port/cpu_start.c"
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
-fno-stack-protector)
|
||||
if(CONFIG_COMPILER_KASAN)
|
||||
# Files involved in stack-protector initialisation: disable stack protector.
|
||||
# Also exclude from KASAN: cpu_start.c runs before kasan_init_shadow() and
|
||||
# panic.c / startup.c must not recurse into KASAN during crash handling.
|
||||
set_source_files_properties(
|
||||
"startup.c" "stack_check.c" "port/cpu_start.c"
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
"-fno-stack-protector -fno-sanitize=kernel-address")
|
||||
|
||||
# kasan.c and ubsan.c implement sanitizer runtime stubs – must never be
|
||||
# instrumented themselves (infinite recursion).
|
||||
# panic.c / port/panic_handler.c must not be instrumented to avoid
|
||||
# recursion in the error path.
|
||||
set_source_files_properties(
|
||||
"kasan.c" "ubsan.c" "panic.c" "port/panic_handler.c"
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
"-fno-sanitize=kernel-address")
|
||||
else()
|
||||
# Disable stack protection in files which are involved in initialization of that feature
|
||||
set_source_files_properties(
|
||||
"startup.c" "stack_check.c" "port/cpu_start.c"
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
-fno-stack-protector)
|
||||
endif()
|
||||
|
||||
target_linker_script(${COMPONENT_LIB} INTERFACE "ld/${target}/memory.ld.in")
|
||||
|
||||
@@ -137,6 +160,23 @@ endif()
|
||||
# due to -ffunction-sections -Wl,--gc-sections options.
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u __ubsan_include")
|
||||
|
||||
# Force-link the __asan_*_noabort stubs: GCC-instrumented code calls them but
|
||||
# the linker cannot see the call sites at GC time, so without explicit -u flags
|
||||
# they would be silently dropped (and any -u flag against the file is enough
|
||||
# to pull kasan.c in as well).
|
||||
if(CONFIG_COMPILER_KASAN)
|
||||
foreach(__kasan_stub
|
||||
__asan_load1_noabort __asan_load2_noabort
|
||||
__asan_load4_noabort __asan_load8_noabort
|
||||
__asan_load16_noabort __asan_loadN_noabort
|
||||
__asan_store1_noabort __asan_store2_noabort
|
||||
__asan_store4_noabort __asan_store8_noabort
|
||||
__asan_store16_noabort __asan_storeN_noabort
|
||||
__asan_handle_no_return)
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u ${__kasan_stub}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_system_include_startup_funcs")
|
||||
|
||||
# [refactor-todo] requirements due to init code, should be removable
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief KASAN shadow nibble poison tags.
|
||||
*
|
||||
* Each 4-bit nibble in the shadow covers a 4-byte granule of real memory.
|
||||
* Values 0x0-0x3 indicate valid (or partially valid) memory; 0xC-0xF
|
||||
* indicate poisoned memory with the tag describing the reason.
|
||||
*/
|
||||
#define KASAN_POISON_HEAP_FREE ((uint8_t)0xF) /**< Freed heap region */
|
||||
#define KASAN_POISON_HEAP_LRZ ((uint8_t)0xE) /**< Heap left redzone (before alloc) */
|
||||
#define KASAN_POISON_HEAP_RRZ ((uint8_t)0xD) /**< Heap right redzone (after alloc) */
|
||||
#define KASAN_POISON_UNINIT ((uint8_t)0xC) /**< Never-allocated / uninitialised */
|
||||
|
||||
#if CONFIG_COMPILER_KASAN
|
||||
/**
|
||||
* @brief Initialise KASAN shadow memory.
|
||||
*
|
||||
* Must be called before heap_caps_init() so that the shadow region is ready
|
||||
* when the first allocation hook fires.
|
||||
*/
|
||||
void kasan_init_shadow(void);
|
||||
|
||||
/**
|
||||
* @brief Poison a memory region in the KASAN shadow.
|
||||
*
|
||||
* Marks [addr, addr+size) as invalid with the given @p tag.
|
||||
*/
|
||||
void kasan_poison_region(const void *addr, size_t size, uint8_t tag);
|
||||
|
||||
/**
|
||||
* @brief Unpoison a memory region in the KASAN shadow.
|
||||
*
|
||||
* Marks [addr, addr+size) as valid (accessible).
|
||||
*/
|
||||
void kasan_unpoison_region(const void *addr, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Temporarily disable KASAN load/store checks on the current core.
|
||||
*
|
||||
* Increments a nested suppression counter; while it is non-zero, the
|
||||
* __asan_load_N / __asan_store_N runtime stubs return without touching shadow
|
||||
* memory. Each call must be paired with kasan_enable_checks().
|
||||
*
|
||||
* Intended for short critical sections that manipulate cache/MMU state or
|
||||
* otherwise execute in conditions where accessing the KASAN shadow region
|
||||
* would itself fault (for example, the early SPI flash chip probe in
|
||||
* esp_flash_init_default_chip()).
|
||||
*
|
||||
* This API is safe to call from any context (task, ISR, panic handler).
|
||||
*/
|
||||
void kasan_disable_checks(void);
|
||||
|
||||
/**
|
||||
* @brief Re-enable KASAN load/store checks suppressed by kasan_disable_checks().
|
||||
*
|
||||
* Decrements the suppression counter. Calls must be balanced; otherwise
|
||||
* checks remain disabled (or, if unbalanced the other way, the counter wraps
|
||||
* around and produces undefined behaviour).
|
||||
*/
|
||||
void kasan_enable_checks(void);
|
||||
#endif
|
||||
|
||||
#if CONFIG_KASAN_NO_HALT
|
||||
/**
|
||||
* @brief Return the number of KASAN errors reported since boot or last reset.
|
||||
*/
|
||||
uint32_t kasan_get_error_count(void);
|
||||
|
||||
/**
|
||||
* @brief Reset the KASAN error counter to zero.
|
||||
*/
|
||||
void kasan_reset_error_count(void);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,512 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Kernel Address Sanitizer (KASAN) runtime for ESP-IDF.
|
||||
*
|
||||
* GCC -fsanitize=kernel-address instruments loads/stores with calls to
|
||||
* __asan_load<N>_noabort / __asan_store<N>_noabort. These stubs check a
|
||||
* shadow memory region and report a violation if poisoned memory is accessed.
|
||||
*
|
||||
* Shadow layout (nibble-based):
|
||||
* One shadow byte covers 8 bytes of real memory via two 4-bit nibbles.
|
||||
* Low nibble (bits 0-3) → real bytes 0-3; high nibble (bits 4-7) → 4-7.
|
||||
* Shadow address = shadow_offset + (real_addr >> 3)
|
||||
* Nibble select = (real_addr >> 2) & 1
|
||||
*
|
||||
* Nibble values:
|
||||
* 0x0 all 4 bytes valid
|
||||
* 0x1-0x3 first N bytes valid (partial granule)
|
||||
* 0xC uninitialised / never allocated
|
||||
* 0xD heap right redzone
|
||||
* 0xE heap left redzone
|
||||
* 0xF freed heap block
|
||||
*
|
||||
* The 4-byte granule matches TLSF's native alignment, so ROM TLSF can be used.
|
||||
*
|
||||
* This file MUST be compiled with -fno-sanitize=kernel-address.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_cpu.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_system.h"
|
||||
#include "soc/soc.h"
|
||||
|
||||
#if CONFIG_COMPILER_KASAN
|
||||
|
||||
#define KASAN_SHADOW_MAP_BASE ((uintptr_t)SOC_DRAM_LOW)
|
||||
#define KASAN_SHADOW_SIZE ((size_t)((((uintptr_t)SOC_DRAM_HIGH - (uintptr_t)SOC_DRAM_LOW) + 7U) >> 3))
|
||||
|
||||
/*
|
||||
* Shadow array — DRAM_ATTR so loads/stores remain valid when the SPI flash
|
||||
* cache is disabled (IRAM KASAN stubs still run during flash operations).
|
||||
*/
|
||||
DRAM_ATTR uint8_t __attribute__((aligned(4)))
|
||||
kasan_shadow_mem[KASAN_SHADOW_SIZE];
|
||||
|
||||
/*
|
||||
* Runtime shadow offset: &kasan_shadow_mem[0] - (MAP_BASE >> 3).
|
||||
* DRAM_ATTR so it is accessible with cache disabled.
|
||||
*/
|
||||
DRAM_ATTR uintptr_t kasan_shadow_offset;
|
||||
|
||||
/* Nibble poison tags */
|
||||
#define KASAN_NIBBLE_VALID 0x0
|
||||
#define KASAN_NIBBLE_UNINIT 0xC
|
||||
#define KASAN_NIBBLE_HEAP_RRZ 0xD
|
||||
#define KASAN_NIBBLE_HEAP_LRZ 0xE
|
||||
#define KASAN_NIBBLE_HEAP_FREE 0xF
|
||||
|
||||
/*
|
||||
* Suppression counter for KASAN checks.
|
||||
*
|
||||
* Incremented (and the corresponding decrement on exit) when:
|
||||
* - a report is in flight: the report path itself executes instrumented code,
|
||||
* which would otherwise recurse;
|
||||
* - kasan_disable_checks() is called explicitly: the panic handler disables
|
||||
* checks for the remainder of crash handling, since backtrace and stack
|
||||
* dumps legitimately read guard pages and poisoned redzones that would
|
||||
* otherwise trigger spurious reports.
|
||||
*
|
||||
* Atomic so it is safe across cores and ISRs. DRAM-resident so it remains
|
||||
* accessible with cache disabled.
|
||||
*/
|
||||
static DRAM_ATTR atomic_uint_fast32_t s_kasan_suppress_depth;
|
||||
|
||||
#if CONFIG_KASAN_NO_HALT
|
||||
static DRAM_ATTR atomic_uint_fast32_t s_kasan_error_count;
|
||||
#endif
|
||||
|
||||
static inline bool kasan_checks_are_disabled(void)
|
||||
{
|
||||
return atomic_load_explicit(&s_kasan_suppress_depth, memory_order_relaxed) != 0;
|
||||
}
|
||||
|
||||
static inline void kasan_report_enter(void)
|
||||
{
|
||||
atomic_fetch_add_explicit(&s_kasan_suppress_depth, 1, memory_order_relaxed);
|
||||
}
|
||||
|
||||
static inline void kasan_report_exit(void)
|
||||
{
|
||||
atomic_fetch_sub_explicit(&s_kasan_suppress_depth, 1, memory_order_relaxed);
|
||||
}
|
||||
|
||||
#if CONFIG_KASAN_NO_HALT
|
||||
static inline uint32_t kasan_error_count_get(void)
|
||||
{
|
||||
return (uint32_t)atomic_load_explicit(&s_kasan_error_count, memory_order_relaxed);
|
||||
}
|
||||
|
||||
static inline void kasan_error_count_reset(void)
|
||||
{
|
||||
atomic_store_explicit(&s_kasan_error_count, 0, memory_order_relaxed);
|
||||
}
|
||||
|
||||
static inline void kasan_error_count_inc(void)
|
||||
{
|
||||
atomic_fetch_add_explicit(&s_kasan_error_count, 1, memory_order_relaxed);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ---- Shadow accessors --------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* These helpers are always inlined into their callers, so they carry no
|
||||
* placement attribute of their own: when inlined into a flash-resident API
|
||||
* (e.g. kasan_poison_region) they stay in flash, and when inlined into the
|
||||
* IRAM hot path (kasan_is_valid_access / the __asan_* stubs) they run from
|
||||
* IRAM with the rest of that function.
|
||||
*/
|
||||
static inline __attribute__((always_inline)) uint8_t *kasan_mem_to_shadow(uintptr_t addr)
|
||||
{
|
||||
return (uint8_t *)(kasan_shadow_offset + (addr >> 3));
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline)) int kasan_is_high_nibble(uintptr_t addr)
|
||||
{
|
||||
return (addr >> 2) & 1;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline)) uint8_t kasan_get_nibble(uintptr_t addr)
|
||||
{
|
||||
uint8_t *shadow = kasan_mem_to_shadow(addr);
|
||||
if (kasan_is_high_nibble(addr)) {
|
||||
return (*shadow >> 4) & 0xF;
|
||||
} else {
|
||||
return *shadow & 0xF;
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline)) void kasan_set_nibble(uintptr_t addr, uint8_t val)
|
||||
{
|
||||
uint8_t *shadow = kasan_mem_to_shadow(addr);
|
||||
if (kasan_is_high_nibble(addr)) {
|
||||
*shadow = (*shadow & 0x0F) | ((val & 0xF) << 4);
|
||||
} else {
|
||||
*shadow = (*shadow & 0xF0) | (val & 0xF);
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline)) bool kasan_addr_in_shadow_range(uintptr_t addr)
|
||||
{
|
||||
uintptr_t map_base = KASAN_SHADOW_MAP_BASE;
|
||||
uintptr_t map_end = map_base + ((uintptr_t)KASAN_SHADOW_SIZE << 3);
|
||||
return (addr >= map_base && addr < map_end);
|
||||
}
|
||||
|
||||
/* ---- Poison / unpoison -------------------------------------------------- */
|
||||
|
||||
void kasan_poison_region(const void *addr, size_t size, uint8_t tag)
|
||||
{
|
||||
if (!kasan_shadow_offset || size == 0) {
|
||||
return;
|
||||
}
|
||||
uintptr_t start = (uintptr_t)addr;
|
||||
uintptr_t end = start + size;
|
||||
|
||||
uintptr_t map_base = KASAN_SHADOW_MAP_BASE;
|
||||
uintptr_t map_end = map_base + ((uintptr_t)KASAN_SHADOW_SIZE << 3);
|
||||
if (end <= map_base || start >= map_end) {
|
||||
return;
|
||||
}
|
||||
if (start < map_base) {
|
||||
start = map_base;
|
||||
}
|
||||
if (end > map_end) {
|
||||
end = map_end;
|
||||
}
|
||||
|
||||
uintptr_t aligned_start = (start + 3) & ~3UL;
|
||||
uintptr_t aligned_end = end & ~3UL;
|
||||
|
||||
if (start < aligned_start && start < end) {
|
||||
kasan_set_nibble(start & ~3UL, tag);
|
||||
}
|
||||
|
||||
for (uintptr_t a = aligned_start; a < aligned_end; a += 4) {
|
||||
if ((a & 4) == 0 && (a + 4) < aligned_end) {
|
||||
uint8_t *shadow = kasan_mem_to_shadow(a);
|
||||
*shadow = (tag << 4) | tag;
|
||||
a += 4;
|
||||
} else {
|
||||
kasan_set_nibble(a, tag);
|
||||
}
|
||||
}
|
||||
|
||||
if (aligned_end < end) {
|
||||
kasan_set_nibble(aligned_end, tag);
|
||||
}
|
||||
}
|
||||
|
||||
void kasan_unpoison_region(const void *addr, size_t size)
|
||||
{
|
||||
if (!kasan_shadow_offset || size == 0) {
|
||||
return;
|
||||
}
|
||||
uintptr_t start = (uintptr_t)addr;
|
||||
uintptr_t end = start + size;
|
||||
|
||||
uintptr_t map_base = KASAN_SHADOW_MAP_BASE;
|
||||
uintptr_t map_end = map_base + ((uintptr_t)KASAN_SHADOW_SIZE << 3);
|
||||
if (end <= map_base || start >= map_end) {
|
||||
return;
|
||||
}
|
||||
if (start < map_base) {
|
||||
start = map_base;
|
||||
}
|
||||
if (end > map_end) {
|
||||
end = map_end;
|
||||
}
|
||||
|
||||
uintptr_t granule_start = start & ~3UL;
|
||||
uintptr_t granule_end = (end + 3) & ~3UL;
|
||||
|
||||
for (uintptr_t a = granule_start; a < granule_end; a += 4) {
|
||||
if (a + 4 > end && end > a) {
|
||||
uint8_t partial = (uint8_t)(end - a);
|
||||
if (partial > 0 && partial < 4) {
|
||||
kasan_set_nibble(a, partial);
|
||||
} else {
|
||||
kasan_set_nibble(a, KASAN_NIBBLE_VALID);
|
||||
}
|
||||
} else {
|
||||
if ((a & 4) == 0 && (a + 4) < granule_end) {
|
||||
uint8_t *shadow = kasan_mem_to_shadow(a);
|
||||
*shadow = 0x00;
|
||||
a += 4;
|
||||
} else {
|
||||
kasan_set_nibble(a, KASAN_NIBBLE_VALID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Shadow initialisation ---------------------------------------------- */
|
||||
|
||||
void kasan_disable_checks(void)
|
||||
{
|
||||
atomic_fetch_add_explicit(&s_kasan_suppress_depth, 1, memory_order_acquire);
|
||||
}
|
||||
|
||||
void kasan_enable_checks(void)
|
||||
{
|
||||
atomic_fetch_sub_explicit(&s_kasan_suppress_depth, 1, memory_order_release);
|
||||
}
|
||||
|
||||
void kasan_init_shadow(void)
|
||||
{
|
||||
/*
|
||||
* The shadow array lives in .data (DRAM_ATTR), not .bss, so it is loaded
|
||||
* from the image rather than implicitly zeroed at startup. Zero it here
|
||||
* explicitly so every nibble starts as 0 (= valid). The alloc hook then
|
||||
* marks redzones and the free hook poisons freed blocks; no bulk poisoning
|
||||
* is done here so boot-path code sees clean shadow and no false positives.
|
||||
*/
|
||||
memset(kasan_shadow_mem, 0, KASAN_SHADOW_SIZE);
|
||||
|
||||
kasan_shadow_offset = (uintptr_t)kasan_shadow_mem
|
||||
- (KASAN_SHADOW_MAP_BASE >> 3);
|
||||
|
||||
esp_rom_printf("KASAN: kernel-address sanitizer initialized (shadow %u bytes, map base 0x%08" PRIxPTR ")\n",
|
||||
(unsigned)KASAN_SHADOW_SIZE, (uintptr_t)KASAN_SHADOW_MAP_BASE);
|
||||
}
|
||||
|
||||
/* ---- Error counter (CONFIG_KASAN_NO_HALT) ------------------------------- */
|
||||
|
||||
#if CONFIG_KASAN_NO_HALT
|
||||
uint32_t kasan_get_error_count(void)
|
||||
{
|
||||
return kasan_error_count_get();
|
||||
}
|
||||
|
||||
void kasan_reset_error_count(void)
|
||||
{
|
||||
kasan_error_count_reset();
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ---- Access validation -------------------------------------------------- */
|
||||
|
||||
static inline __attribute__((always_inline)) bool kasan_is_valid_access(uintptr_t addr, size_t size)
|
||||
{
|
||||
/* Address outside monitored DRAM window, not mapped to shadow region, assume valid to avoid false positives */
|
||||
if (!kasan_addr_in_shadow_range(addr) || !kasan_addr_in_shadow_range(addr + size - 1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fast path: both nibbles in the shadow byte are valid.
|
||||
*
|
||||
* Each shadow byte covers 8 real bytes (two 4-byte granules), so we can
|
||||
* only short-circuit when the *entire* access falls inside the shadow
|
||||
* byte(s) we have actually examined. Larger or mis-aligned accesses fall
|
||||
* through to the slow path below, which walks every granule.
|
||||
*/
|
||||
uintptr_t offset_in_byte = addr & 7U;
|
||||
uint8_t shadow_byte = *kasan_mem_to_shadow(addr);
|
||||
if (shadow_byte == 0x00) {
|
||||
if ((offset_in_byte + size) <= 8U) {
|
||||
return true;
|
||||
}
|
||||
if ((offset_in_byte + size) <= 16U) {
|
||||
uint8_t next_shadow = *kasan_mem_to_shadow(addr + 8);
|
||||
if (next_shadow == 0x00) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Slow path: check each granule. */
|
||||
uintptr_t end_addr = addr + size;
|
||||
for (uintptr_t a = addr; a < end_addr;) {
|
||||
uint8_t nibble = kasan_get_nibble(a);
|
||||
|
||||
if (nibble == KASAN_NIBBLE_VALID) {
|
||||
a = (a & ~3UL) + 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nibble >= 1 && nibble <= 3) {
|
||||
uintptr_t granule_base = a & ~3UL;
|
||||
uintptr_t offset_in_granule = a - granule_base;
|
||||
uintptr_t access_end_in_granule = end_addr - granule_base;
|
||||
if (access_end_in_granule > 4) {
|
||||
access_end_in_granule = 4;
|
||||
}
|
||||
if (offset_in_granule >= nibble || access_end_in_granule > nibble) {
|
||||
return false;
|
||||
}
|
||||
a = granule_base + 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ---- Error reporting ---------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Bug-type and access-type strings live in DRAM so that the IRAM error
|
||||
* reporting path stays safe when the SPI flash cache is disabled (ISR
|
||||
* context, spi_flash operations, early boot). Plain string literals would
|
||||
* land in .rodata (flash) and dereferencing them with cache off would mask
|
||||
* the real KASAN violation with a cache-error panic.
|
||||
*/
|
||||
static DRAM_ATTR const char kasan_str_use_after_free[] = "use-after-free";
|
||||
static DRAM_ATTR const char kasan_str_underflow_lrz[] = "heap-buffer-underflow (left redzone)";
|
||||
static DRAM_ATTR const char kasan_str_overflow_rrz[] = "heap-buffer-overflow (right redzone)";
|
||||
static DRAM_ATTR const char kasan_str_uninitialised[] = "uninitialised memory";
|
||||
static DRAM_ATTR const char kasan_str_overflow_partial[] = "heap-buffer-overflow (partial granule)";
|
||||
static DRAM_ATTR const char kasan_str_unknown_poison[] = "unknown poison";
|
||||
static DRAM_ATTR const char kasan_str_write[] = "WRITE";
|
||||
static DRAM_ATTR const char kasan_str_read[] = "READ";
|
||||
#if !CONFIG_KASAN_NO_HALT
|
||||
static DRAM_ATTR const char kasan_str_abort_msg[] = "KASAN: invalid memory access";
|
||||
#endif
|
||||
|
||||
static DRAM_ATTR const char kasan_fmt_error[] = "KASAN error: %s of size %u at 0x%08x\n";
|
||||
static DRAM_ATTR const char kasan_fmt_bug[] = " Bug type: %s (shadow nibble=0x%x)\n";
|
||||
|
||||
static IRAM_ATTR const char *kasan_nibble_to_string(uint8_t nibble)
|
||||
{
|
||||
switch (nibble) {
|
||||
case KASAN_NIBBLE_HEAP_FREE: return kasan_str_use_after_free;
|
||||
case KASAN_NIBBLE_HEAP_LRZ: return kasan_str_underflow_lrz;
|
||||
case KASAN_NIBBLE_HEAP_RRZ: return kasan_str_overflow_rrz;
|
||||
case KASAN_NIBBLE_UNINIT: return kasan_str_uninitialised;
|
||||
default:
|
||||
if (nibble >= 1 && nibble <= 3) {
|
||||
return kasan_str_overflow_partial;
|
||||
}
|
||||
return kasan_str_unknown_poison;
|
||||
}
|
||||
}
|
||||
|
||||
static IRAM_ATTR void kasan_report(uintptr_t addr, size_t size, bool is_write)
|
||||
{
|
||||
kasan_report_enter();
|
||||
|
||||
if (esp_cpu_dbgr_is_attached()) {
|
||||
esp_cpu_dbgr_break();
|
||||
}
|
||||
|
||||
const char *access_type = is_write ? kasan_str_write : kasan_str_read;
|
||||
uint8_t nibble = kasan_get_nibble(addr);
|
||||
const char *bug_type = kasan_nibble_to_string(nibble);
|
||||
|
||||
esp_rom_printf(kasan_fmt_error, access_type, (unsigned)size, (unsigned)addr);
|
||||
esp_rom_printf(kasan_fmt_bug, bug_type, nibble);
|
||||
|
||||
#if CONFIG_KASAN_NO_HALT
|
||||
kasan_error_count_inc();
|
||||
kasan_report_exit();
|
||||
#else
|
||||
/*
|
||||
* Avoid flash-resident helpers (strlcat, libc string operations) here:
|
||||
* kasan_report runs from IRAM and may execute with the SPI flash cache
|
||||
* disabled. esp_rom_printf above has already emitted the detailed
|
||||
* diagnostic via ROM; pass a short DRAM-resident message to the abort
|
||||
* path so the panic itself does not touch flash.
|
||||
*/
|
||||
esp_system_abort(kasan_str_abort_msg);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ---- Check dispatcher --------------------------------------------------- */
|
||||
|
||||
static IRAM_ATTR void kasan_check(uintptr_t addr, size_t size, bool is_write)
|
||||
{
|
||||
if (__builtin_expect(kasan_checks_are_disabled() || !kasan_shadow_offset, 0)) {
|
||||
return;
|
||||
}
|
||||
if (!kasan_is_valid_access(addr, size)) {
|
||||
kasan_report(addr, size, is_write);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- GCC-emitted KASAN stubs -------------------------------------------- */
|
||||
|
||||
/*
|
||||
* __attribute__((used)) prevents --gc-sections from removing stubs that GCC's
|
||||
* instrumentation pass calls but that the linker cannot see at analysis time.
|
||||
*/
|
||||
__attribute__((used)) void IRAM_ATTR __asan_load1_noabort(void *addr)
|
||||
{
|
||||
kasan_check((uintptr_t)addr, 1, false);
|
||||
}
|
||||
|
||||
__attribute__((used)) void IRAM_ATTR __asan_load2_noabort(void *addr)
|
||||
{
|
||||
kasan_check((uintptr_t)addr, 2, false);
|
||||
}
|
||||
|
||||
__attribute__((used)) void IRAM_ATTR __asan_load4_noabort(void *addr)
|
||||
{
|
||||
kasan_check((uintptr_t)addr, 4, false);
|
||||
}
|
||||
|
||||
__attribute__((used)) void IRAM_ATTR __asan_load8_noabort(void *addr)
|
||||
{
|
||||
kasan_check((uintptr_t)addr, 8, false);
|
||||
}
|
||||
|
||||
__attribute__((used)) void IRAM_ATTR __asan_load16_noabort(void *addr)
|
||||
{
|
||||
kasan_check((uintptr_t)addr, 16, false);
|
||||
}
|
||||
|
||||
__attribute__((used)) void IRAM_ATTR __asan_loadN_noabort(void *addr, size_t size)
|
||||
{
|
||||
kasan_check((uintptr_t)addr, size, false);
|
||||
}
|
||||
|
||||
__attribute__((used)) void IRAM_ATTR __asan_store1_noabort(void *addr)
|
||||
{
|
||||
kasan_check((uintptr_t)addr, 1, true);
|
||||
}
|
||||
|
||||
__attribute__((used)) void IRAM_ATTR __asan_store2_noabort(void *addr)
|
||||
{
|
||||
kasan_check((uintptr_t)addr, 2, true);
|
||||
}
|
||||
|
||||
__attribute__((used)) void IRAM_ATTR __asan_store4_noabort(void *addr)
|
||||
{
|
||||
kasan_check((uintptr_t)addr, 4, true);
|
||||
}
|
||||
|
||||
__attribute__((used)) void IRAM_ATTR __asan_store8_noabort(void *addr)
|
||||
{
|
||||
kasan_check((uintptr_t)addr, 8, true);
|
||||
}
|
||||
|
||||
__attribute__((used)) void IRAM_ATTR __asan_store16_noabort(void *addr)
|
||||
{
|
||||
kasan_check((uintptr_t)addr, 16, true);
|
||||
}
|
||||
|
||||
__attribute__((used)) void IRAM_ATTR __asan_storeN_noabort(void *addr, size_t size)
|
||||
{
|
||||
kasan_check((uintptr_t)addr, size, true);
|
||||
}
|
||||
|
||||
/* Called before noreturn functions; nothing to do on bare-metal. */
|
||||
void IRAM_ATTR __asan_handle_no_return(void)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* CONFIG_COMPILER_KASAN */
|
||||
@@ -401,6 +401,18 @@ void IRAM_ATTR do_multicore_settings(void)
|
||||
FORCE_INLINE_ATTR IRAM_ATTR void init_cpu(void)
|
||||
{
|
||||
#ifdef __riscv
|
||||
// Configure the global pointer register.
|
||||
// This must be the first thing the IDF app does on RISC-V, as any other
|
||||
// piece of code could be relaxed by the linker to access something relative
|
||||
// to __global_pointer$. With KASAN enabled, even calls like
|
||||
// esp_cpu_dbgr_is_attached() are instrumented and may emit gp-relative
|
||||
// loads, so gp must be set up before any C function call.
|
||||
__asm__ __volatile__(
|
||||
".option push\n"
|
||||
".option norelax\n"
|
||||
"la gp, __global_pointer$\n"
|
||||
".option pop"
|
||||
);
|
||||
if (esp_cpu_dbgr_is_attached()) {
|
||||
/* Let debugger some time to detect that target started, halt it, enable ebreaks and resume.
|
||||
500ms should be enough. */
|
||||
@@ -408,15 +420,6 @@ FORCE_INLINE_ATTR IRAM_ATTR void init_cpu(void)
|
||||
esp_rom_delay_us(100000);
|
||||
}
|
||||
}
|
||||
// Configure the global pointer register
|
||||
// (This should be the first thing IDF app does, as any other piece of code could be
|
||||
// relaxed by the linker to access something relative to __global_pointer$)
|
||||
__asm__ __volatile__(
|
||||
".option push\n"
|
||||
".option norelax\n"
|
||||
"la gp, __global_pointer$\n"
|
||||
".option pop"
|
||||
);
|
||||
#endif
|
||||
|
||||
/* NOTE: When ESP-TEE is enabled, this sets up the callback function
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
#include "esp_private/panic_internal.h"
|
||||
#include "esp_private/panic_reason.h"
|
||||
|
||||
#if CONFIG_COMPILER_KASAN
|
||||
#include "esp_kasan.h"
|
||||
#endif
|
||||
|
||||
#if SOC_WDT_SUPPORTED || SOC_RTC_WDT_SUPPORTED
|
||||
#include "hal/wdt_types.h"
|
||||
#include "hal/wdt_hal.h"
|
||||
@@ -128,6 +132,13 @@ void busy_wait(void)
|
||||
|
||||
static void panic_handler(void *frame, bool pseudo_excause)
|
||||
{
|
||||
#if CONFIG_COMPILER_KASAN
|
||||
/* Disable KASAN checks for the remainder of crash handling: backtrace and
|
||||
* stack dumps legitimately read guard pages and poisoned redzones, which
|
||||
* would otherwise trigger spurious KASAN reports. */
|
||||
kasan_disable_checks();
|
||||
#endif
|
||||
|
||||
/* If watchdogs are enabled, the panic handler runs the risk of getting aborted pre-emptively because
|
||||
* an overzealous watchdog decides to reset it. Hence, we feed the WDTs here.
|
||||
*
|
||||
|
||||
@@ -24,6 +24,10 @@ CORE: 10: init_show_cpu_freq in components/esp_system/startup_funcs.c on BIT(0)
|
||||
CORE: 20: init_show_app_info in components/esp_app_format/esp_app_desc.c on BIT(0)
|
||||
CORE: 21: init_efuse_show_app_info in components/efuse/src/esp_efuse_startup.c on BIT(0)
|
||||
|
||||
# When KASAN is enabled, initialise the KASAN shadow region before the heap allocator.
|
||||
# The shadow offset must be set up before the first heap_caps_init hook fires.
|
||||
CORE: 98: init_kasan_shadow in components/heap/heap_kasan.c on BIT(0)
|
||||
|
||||
# Set the standard stream to non blocking and register the default vfs
|
||||
CORE: 99: init_vfs_linux_coop in components/vfs/vfs_linux_default_coop.c on BIT(0)
|
||||
|
||||
|
||||
@@ -66,11 +66,13 @@ if(NOT BOOTLOADER_BUILD)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(ldfragments linker.lf)
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS ${includes}
|
||||
PRIV_INCLUDE_DIRS ${priv_includes}
|
||||
LDFRAGMENTS linker.lf
|
||||
PRIV_REQUIRES soc)
|
||||
LDFRAGMENTS ${ldfragments}
|
||||
PRIV_REQUIRES soc esp_system)
|
||||
|
||||
if(CONFIG_HEAP_TLSF_USE_ROM_IMPL AND NOT BOOTLOADER_BUILD)
|
||||
# After registering the component, set the tlsf_set_rom_patches symbol as undefined
|
||||
@@ -110,3 +112,38 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# KASAN heap integration: hook into alloc/free to maintain shadow memory.
|
||||
# heap_kasan.c holds the implementation (compiled without KASAN instrumentation).
|
||||
# heap_kasan_hooks.c provides strong (non-weak) overrides of the hook stubs;
|
||||
# it intentionally avoids including esp_heap_caps.h to prevent GCC from
|
||||
# inheriting the 'weak' attribute from the declarations in that header.
|
||||
if(CONFIG_COMPILER_KASAN AND CONFIG_HEAP_USE_HOOKS)
|
||||
target_sources(${COMPONENT_LIB} PRIVATE
|
||||
"heap_kasan.c"
|
||||
"heap_kasan_hooks.c"
|
||||
)
|
||||
# Sources excluded from KASAN instrumentation:
|
||||
# heap_kasan.c / heap_kasan_hooks.c implement the sanitizer hooks themselves
|
||||
# (instrumenting them would cause infinite recursion).
|
||||
# heap_caps_init.c runs while heap metadata is being brought up: the first
|
||||
# allocations happen before the shadow is fully initialised, and the
|
||||
# memcpy of the registered-heaps array touches DRAM regions whose
|
||||
# shadow state is still being populated.
|
||||
set_source_files_properties(
|
||||
"heap_caps_init.c"
|
||||
"heap_kasan.c"
|
||||
"heap_kasan_hooks.c"
|
||||
PROPERTIES COMPILE_OPTIONS "-fno-sanitize=kernel-address"
|
||||
)
|
||||
idf_component_get_property(esp_system_lib esp_system COMPONENT_LIB)
|
||||
target_link_libraries(${COMPONENT_LIB} PRIVATE ${esp_system_lib})
|
||||
# Force the linker to include our strong hook definitions. Without this,
|
||||
# --gc-sections would silently discard them because the call site in
|
||||
# heap_caps_base.c uses a weak-symbol pointer (if (hook != NULL) ...) which
|
||||
# doesn't create a strong reference that the linker follows.
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE
|
||||
"-u esp_heap_trace_alloc_hook"
|
||||
"-u esp_heap_trace_free_hook"
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "multi_heap.h"
|
||||
#include "esp_log.h"
|
||||
#include "heap_private.h"
|
||||
#include "heap_kasan_layout.h"
|
||||
#include "esp_system.h"
|
||||
|
||||
/*
|
||||
@@ -480,13 +481,22 @@ void heap_caps_dump_all(void)
|
||||
|
||||
size_t heap_caps_get_allocated_size( void *ptr )
|
||||
{
|
||||
/* The heap layout is:
|
||||
* [block-owner][left redzone][user][right redzone]
|
||||
* so undo the KASAN shift before removing the block-owner word.
|
||||
*/
|
||||
ptr = KASAN_USER_TO_PTR(ptr);
|
||||
// add the block owner bytes back to ptr before handing over
|
||||
// to multi heap layer.
|
||||
ptr = MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(ptr);
|
||||
heap_t *heap = find_containing_heap(ptr);
|
||||
assert(heap);
|
||||
size_t size = multi_heap_get_allocated_size(heap->heap, ptr);
|
||||
return MULTI_HEAP_REMOVE_BLOCK_OWNER_SIZE(size);
|
||||
size = MULTI_HEAP_REMOVE_BLOCK_OWNER_SIZE(size);
|
||||
if (size > 2 * KASAN_RZ) {
|
||||
size -= 2 * KASAN_RZ;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t heap_caps_get_containing_block_size(void *ptr)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "multi_heap.h"
|
||||
#include "esp_log.h"
|
||||
#include "heap_private.h"
|
||||
#include "heap_kasan_layout.h"
|
||||
#if CONFIG_HEAP_TASK_TRACKING
|
||||
#include "esp_heap_task_info.h"
|
||||
#include "esp_heap_task_info_internal.h"
|
||||
@@ -28,9 +29,28 @@
|
||||
#define CALL_HOOK(hook, ...) {}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* KASAN redzone support: inflate allocations by 2*KASAN_RZ bytes and shift
|
||||
* the user pointer past the left redzone. heap_kasan.c poisons the redzones.
|
||||
*
|
||||
* Final allocation layout (with CONFIG_HEAP_TASK_TRACKING=y):
|
||||
*
|
||||
* [ block-owner word ][ left redzone ][ user bytes ][ right redzone ]
|
||||
*
|
||||
* The ordering is intentional: user underflows must hit the left redzone
|
||||
* before they can reach the task-tracking block-owner word.
|
||||
*
|
||||
* Pointer arithmetic macros live in heap_kasan_layout.h.
|
||||
*/
|
||||
|
||||
//This is normally provided by the heap-memalign-hw component.
|
||||
extern void esp_heap_adjust_alignment_to_hw(size_t *p_alignment, size_t *p_size, uint32_t *p_caps);
|
||||
|
||||
#if CONFIG_COMPILER_KASAN && CONFIG_HEAP_USE_HOOKS
|
||||
bool kasan_heap_should_defer_free(void);
|
||||
void kasan_heap_clear_deferred_free(void);
|
||||
#endif
|
||||
|
||||
//Default alignment the multiheap allocator / tlsf will align 'unaligned' memory to, in bytes
|
||||
#define UNALIGNED_MEM_ALIGNMENT_BYTES 4
|
||||
|
||||
@@ -67,6 +87,17 @@ HEAP_IRAM_ATTR void heap_caps_free( void *ptr)
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* KASAN free hook must see the same pointer that the alloc hook saw, i.e.
|
||||
* the user-visible (IRAM) pointer with the redzone shift applied. Call
|
||||
* it before any DIRAM -> DRAM translation; otherwise the shadow update
|
||||
* would touch the wrong address range and use-after-free detection on
|
||||
* IRAM-aliased blocks would be incorrect.
|
||||
*/
|
||||
#if CONFIG_COMPILER_KASAN && CONFIG_HEAP_USE_HOOKS
|
||||
CALL_HOOK(esp_heap_trace_free_hook, ptr);
|
||||
#endif
|
||||
|
||||
if ((!esp_dram_match_iram() && esp_ptr_in_diram_iram(ptr)) ||
|
||||
(!esp_rtc_dram_match_rtc_iram() && esp_ptr_in_rtc_iram_fast(ptr))) {
|
||||
//Memory allocated here is actually allocated in the DRAM alias region and
|
||||
@@ -75,17 +106,29 @@ HEAP_IRAM_ATTR void heap_caps_free( void *ptr)
|
||||
uint32_t *dramAddrPtr = (uint32_t *)ptr;
|
||||
ptr = (void *)dramAddrPtr[-1];
|
||||
}
|
||||
void *block_owner_ptr = MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(ptr);
|
||||
|
||||
/* Reverse the pointer transforms in the opposite order used on alloc:
|
||||
* user -> left redzone start -> block-owner word.
|
||||
*/
|
||||
void *raw_ptr = KASAN_USER_TO_PTR(ptr);
|
||||
void *block_owner_ptr = MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(raw_ptr);
|
||||
heap_t *heap = find_containing_heap(block_owner_ptr);
|
||||
assert(heap != NULL && "free() target pointer is outside heap areas");
|
||||
|
||||
#if CONFIG_HEAP_TASK_TRACKING
|
||||
heap_caps_update_per_task_info_free(heap, ptr);
|
||||
heap_caps_update_per_task_info_free(heap, raw_ptr);
|
||||
#endif
|
||||
|
||||
#if CONFIG_COMPILER_KASAN && CONFIG_HEAP_USE_HOOKS
|
||||
if (kasan_heap_should_defer_free()) {
|
||||
kasan_heap_clear_deferred_free();
|
||||
return;
|
||||
}
|
||||
multi_heap_free(heap->heap, block_owner_ptr);
|
||||
#else
|
||||
multi_heap_free(heap->heap, block_owner_ptr);
|
||||
|
||||
CALL_HOOK(esp_heap_trace_free_hook, ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
HEAP_IRAM_ATTR static inline void *aligned_or_unaligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment, size_t offset) {
|
||||
@@ -139,6 +182,8 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_aligned_alloc_base(size_t alignment
|
||||
size = (size + 3) & (~3); // int overflow checked above
|
||||
}
|
||||
|
||||
const size_t alloc_size = KASAN_ADD_RZ(size);
|
||||
|
||||
for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) {
|
||||
//Iterate over heaps and check capabilities at this priority
|
||||
heap_t *heap;
|
||||
@@ -159,7 +204,7 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_aligned_alloc_base(size_t alignment
|
||||
//This is special, insofar that what we're going to get back is a DRAM address. If so,
|
||||
//we need to 'invert' it (lowest address in DRAM == highest address in IRAM and vice-versa) and
|
||||
//add a pointer to the DRAM equivalent before the address we're going to return.
|
||||
ret = aligned_or_unaligned_alloc(heap->heap, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(size) + 4,
|
||||
ret = aligned_or_unaligned_alloc(heap->heap, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(alloc_size) + 4,
|
||||
alignment, MULTI_HEAP_BLOCK_OWNER_SIZE()); // int overflow checked above
|
||||
if (ret != NULL) {
|
||||
#if CONFIG_HEAP_TASK_TRACKING
|
||||
@@ -171,13 +216,14 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_aligned_alloc_base(size_t alignment
|
||||
|
||||
MULTI_HEAP_SET_BLOCK_OWNER(ret);
|
||||
ret = MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ret);
|
||||
ret = KASAN_PTR_TO_USER(ret);
|
||||
uint32_t *iptr = dram_alloc_to_iram_addr(ret, size + 4); // int overflow checked above
|
||||
CALL_HOOK(esp_heap_trace_alloc_hook, iptr, size, caps);
|
||||
return iptr;
|
||||
}
|
||||
} else {
|
||||
//Just try to alloc, nothing special.
|
||||
ret = aligned_or_unaligned_alloc(heap->heap, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(size),
|
||||
ret = aligned_or_unaligned_alloc(heap->heap, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(alloc_size),
|
||||
alignment, MULTI_HEAP_BLOCK_OWNER_SIZE());
|
||||
if (ret != NULL) {
|
||||
#if CONFIG_HEAP_TASK_TRACKING
|
||||
@@ -189,6 +235,7 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_aligned_alloc_base(size_t alignment
|
||||
|
||||
MULTI_HEAP_SET_BLOCK_OWNER(ret);
|
||||
ret = MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ret);
|
||||
ret = KASAN_PTR_TO_USER(ret);
|
||||
CALL_HOOK(esp_heap_trace_alloc_hook, ret, size, caps);
|
||||
return ret;
|
||||
}
|
||||
@@ -236,31 +283,43 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_realloc_base( void *ptr, size_t siz
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//The pointer to memory may be aliased, we need to
|
||||
//recover the corresponding address before to manage a new allocation:
|
||||
if(esp_ptr_in_diram_iram((void *)ptr)) {
|
||||
uint32_t *dram_addr = (uint32_t *)ptr;
|
||||
dram_ptr = (void *)dram_addr[-1];
|
||||
/*
|
||||
* DIRAM aliasing detection must happen on the original user pointer:
|
||||
* dram_alloc_to_iram_addr stores the underlying DRAM address one word
|
||||
* before the IRAM pointer, so the KASAN redzone shift (which moves the
|
||||
* pointer by KASAN_RZ on the IRAM side) would land us in the wrong place
|
||||
* if we applied it first.
|
||||
*/
|
||||
void *raw_ptr;
|
||||
if (esp_ptr_in_diram_iram(ptr)) {
|
||||
uint32_t *iram_addr = (uint32_t *)ptr;
|
||||
dram_ptr = (void *)iram_addr[-1];
|
||||
/* On the DRAM side the layout is [block-owner][left redzone][user]
|
||||
* so undo redzone first, then block-owner, matching alloc order. */
|
||||
dram_ptr = KASAN_USER_TO_PTR(dram_ptr);
|
||||
dram_ptr = MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(dram_ptr);
|
||||
|
||||
heap = find_containing_heap(dram_ptr);
|
||||
assert(heap != NULL && "realloc() pointer is outside heap areas");
|
||||
|
||||
//with pointers that reside on diram space, we avoid using
|
||||
//the realloc implementation due to address translation issues,
|
||||
//instead force a malloc/copy/free
|
||||
/* with pointers that reside on diram space, we avoid using
|
||||
* the realloc implementation due to address translation issues,
|
||||
* instead force a malloc/copy/free */
|
||||
ptr_in_diram_case = true;
|
||||
|
||||
raw_ptr = NULL; /* not used in the diram path */
|
||||
} else {
|
||||
heap = find_containing_heap(ptr);
|
||||
/* Reverse the alloc-time layout transform in the same order as free():
|
||||
* user -> left redzone start -> block-owner word. Doing the
|
||||
* block-owner removal *before* find_containing_heap() matches the
|
||||
* free() path and the DIRAM branch above. */
|
||||
raw_ptr = KASAN_USER_TO_PTR(ptr);
|
||||
raw_ptr = MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(raw_ptr);
|
||||
|
||||
heap = find_containing_heap(raw_ptr);
|
||||
assert(heap != NULL && "realloc() pointer is outside heap areas");
|
||||
}
|
||||
|
||||
// shift ptr by block owner offset. Since the ptr returned to the user
|
||||
// does not include the block owner bytes (that are located at the
|
||||
// beginning of the allocated memory) we have to add them back before
|
||||
// processing the realloc.
|
||||
ptr = MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(ptr);
|
||||
const size_t alloc_size = KASAN_ADD_RZ(size);
|
||||
|
||||
// are the existing heap's capabilities compatible with the
|
||||
// requested ones?
|
||||
@@ -273,17 +332,17 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_realloc_base( void *ptr, size_t siz
|
||||
// (which will resize the block if it can)
|
||||
|
||||
#if CONFIG_HEAP_TASK_TRACKING
|
||||
size_t old_size = multi_heap_get_full_block_size(heap->heap, ptr);
|
||||
TaskHandle_t old_task = MULTI_HEAP_GET_BLOCK_OWNER(ptr);
|
||||
size_t old_size = multi_heap_get_full_block_size(heap->heap, raw_ptr);
|
||||
TaskHandle_t old_task = MULTI_HEAP_GET_BLOCK_OWNER(raw_ptr);
|
||||
#endif
|
||||
|
||||
void *r = multi_heap_realloc(heap->heap, ptr, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(size));
|
||||
void *r = multi_heap_realloc(heap->heap, raw_ptr, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(alloc_size));
|
||||
if (r != NULL) {
|
||||
MULTI_HEAP_SET_BLOCK_OWNER(r);
|
||||
|
||||
#if CONFIG_HEAP_TASK_TRACKING
|
||||
heap_caps_update_per_task_info_realloc(heap,
|
||||
MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ptr),
|
||||
MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(raw_ptr),
|
||||
MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(r),
|
||||
old_size, old_task,
|
||||
multi_heap_get_full_block_size(heap->heap, r),
|
||||
@@ -291,6 +350,19 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_realloc_base( void *ptr, size_t siz
|
||||
#endif
|
||||
|
||||
r = MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(r);
|
||||
r = KASAN_PTR_TO_USER(r);
|
||||
#if CONFIG_COMPILER_KASAN && CONFIG_HEAP_USE_HOOKS
|
||||
/*
|
||||
* If multi_heap_realloc() relocated the block (r != ptr), the
|
||||
* old user range needs its shadow re-poisoned as use-after-free
|
||||
* so that lingering references hit a KASAN violation. When the
|
||||
* block was resized in place, alloc hook below will simply
|
||||
* refresh the shadow over the new range.
|
||||
*/
|
||||
if (r != ptr) {
|
||||
CALL_HOOK(esp_heap_trace_free_hook, ptr);
|
||||
}
|
||||
#endif
|
||||
CALL_HOOK(esp_heap_trace_alloc_hook, r, size, caps);
|
||||
return r;
|
||||
}
|
||||
@@ -307,14 +379,17 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_realloc_base( void *ptr, size_t siz
|
||||
if(ptr_in_diram_case) {
|
||||
old_size = multi_heap_get_allocated_size(heap->heap, dram_ptr);
|
||||
} else {
|
||||
old_size = multi_heap_get_allocated_size(heap->heap, ptr);
|
||||
old_size = multi_heap_get_allocated_size(heap->heap, raw_ptr);
|
||||
}
|
||||
|
||||
assert(old_size > 0);
|
||||
// do not copy the block owner bytes
|
||||
memcpy(new_p, MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ptr), MIN(size, old_size));
|
||||
// add the block owner bytes to ptr since they are removed in heap_caps_free
|
||||
heap_caps_free(MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ptr));
|
||||
old_size = MULTI_HEAP_REMOVE_BLOCK_OWNER_SIZE(old_size);
|
||||
/* Subtract KASAN redzone overhead to get the actual user-data size. */
|
||||
if (old_size > 2 * KASAN_RZ) {
|
||||
old_size -= 2 * KASAN_RZ;
|
||||
}
|
||||
memcpy(new_p, ptr, MIN(size, old_size));
|
||||
heap_caps_free(ptr);
|
||||
return new_p;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* KASAN heap hooks for ESP-IDF.
|
||||
*
|
||||
* Provides strong definitions of the weak heap hooks so the KASAN shadow stays
|
||||
* in sync with every heap_caps_malloc / heap_caps_free.
|
||||
*
|
||||
* When CONFIG_KASAN_HEAP_REDZONE_SIZE > 0, each allocation gets a poisoned
|
||||
* guard band on both sides (left and right redzones) for overflow/underflow
|
||||
* detection.
|
||||
*
|
||||
* When CONFIG_KASAN_QUARANTINE_SIZE > 0, freed blocks are held in a FIFO
|
||||
* before the real free, keeping their shadow poisoned to catch use-after-free.
|
||||
*
|
||||
* Compiled with -fno-sanitize=kernel-address to avoid recursive instrumentation.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_rom_sys.h"
|
||||
/*
|
||||
* Avoid including esp_heap_caps.h: it declares the hook symbols as weak, and
|
||||
* GCC propagates that attribute to definitions in the same TU. Forward-declare
|
||||
* only what we need.
|
||||
*/
|
||||
void heap_caps_free(void *ptr);
|
||||
size_t heap_caps_get_allocated_size(void *ptr);
|
||||
|
||||
void kasan_heap_alloc_impl(void *ptr, size_t size, uint32_t caps);
|
||||
void kasan_heap_free_impl(void *ptr);
|
||||
bool kasan_heap_should_defer_free(void);
|
||||
void kasan_heap_clear_deferred_free(void);
|
||||
|
||||
#include "esp_kasan.h"
|
||||
#include "esp_private/startup_internal.h"
|
||||
#include "heap_private.h"
|
||||
#include "heap_kasan_layout.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/portmacro.h"
|
||||
|
||||
#if CONFIG_COMPILER_KASAN && CONFIG_HEAP_USE_HOOKS
|
||||
|
||||
/*
|
||||
* Initialise the KASAN shadow region before the heap allocator (priority 100).
|
||||
* Runs at priority 99 so the shadow offset is in place before the first heap
|
||||
* caps registration, and before the alloc/free hooks below start running.
|
||||
*/
|
||||
ESP_SYSTEM_INIT_FN(init_kasan_shadow, CORE, BIT(0), 98)
|
||||
{
|
||||
kasan_init_shadow();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* ---- Left-redzone header ------------------------------------------------ */
|
||||
|
||||
#define KASAN_LRZ_MAGIC 0xA5B6C7D8UL
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
size_t user_size;
|
||||
} kasan_lrz_hdr_t;
|
||||
|
||||
#if HEAP_KASAN_RZ_ENABLED
|
||||
_Static_assert((CONFIG_KASAN_HEAP_REDZONE_SIZE % 4) == 0,
|
||||
"CONFIG_KASAN_HEAP_REDZONE_SIZE must be a multiple of 4");
|
||||
_Static_assert(sizeof(kasan_lrz_hdr_t) <= KASAN_RZ,
|
||||
"CONFIG_KASAN_HEAP_REDZONE_SIZE is too small to hold the KASAN header");
|
||||
#endif
|
||||
|
||||
/* ---- Quarantine ring-buffer --------------------------------------------- */
|
||||
|
||||
static inline unsigned kasan_q_core_id(void)
|
||||
{
|
||||
int core_id = xPortGetCoreID();
|
||||
assert(core_id >= 0 && core_id < portNUM_PROCESSORS);
|
||||
return (unsigned)core_id;
|
||||
}
|
||||
|
||||
#if CONFIG_KASAN_QUARANTINE_SIZE > 0
|
||||
|
||||
/*
|
||||
* Per-core "deferred-free ticket counter". Each call into
|
||||
* kasan_heap_free_impl() enqueues one block in the quarantine and bumps the
|
||||
* counter; heap_caps_free() pops one ticket through kasan_heap_should_defer_free
|
||||
* + kasan_heap_clear_deferred_free. A counter (rather than a bool) is required
|
||||
* because frees can nest: e.g. heap_caps_free(A) starts on core 0, an ISR fires
|
||||
* mid-way and runs heap_caps_free(B) on the same core, then heap_caps_free(A)
|
||||
* resumes. With a bool the ISR's "clear" would mask the outer free's defer
|
||||
* request and the outer pointer would be released both via multi_heap_free()
|
||||
* *and* later via the quarantine eviction (double free).
|
||||
*
|
||||
* Access is from one core at a time but can be from an ISR on that core, so
|
||||
* an atomic fetch-add is sufficient; we don't need a cross-core barrier here.
|
||||
*/
|
||||
static DRAM_ATTR atomic_uint_fast32_t s_q_defer_free[portNUM_PROCESSORS];
|
||||
|
||||
#define KASAN_Q_ENTRIES 64U
|
||||
|
||||
typedef struct {
|
||||
void *ptr;
|
||||
size_t size;
|
||||
} kasan_q_entry_t;
|
||||
|
||||
static kasan_q_entry_t s_q[KASAN_Q_ENTRIES];
|
||||
static unsigned s_q_head;
|
||||
static unsigned s_q_tail;
|
||||
static size_t s_q_bytes;
|
||||
static portMUX_TYPE s_q_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
static void kasan_q_release_one(void *ptr)
|
||||
{
|
||||
void *raw_ptr = KASAN_USER_TO_RAW(ptr);
|
||||
void *block_owner_ptr = MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(raw_ptr);
|
||||
heap_t *heap = find_containing_heap(block_owner_ptr);
|
||||
assert(heap != NULL && "quarantine free target pointer is outside heap areas");
|
||||
multi_heap_free(heap->heap, block_owner_ptr);
|
||||
}
|
||||
|
||||
static void kasan_q_add(void *ptr, size_t size)
|
||||
{
|
||||
/*
|
||||
* Collect every block that needs to be evicted into a small on-stack
|
||||
* array. We update head/tail/bytes atomically inside the critical
|
||||
* section and only call multi_heap_free() after we have exited it. This
|
||||
* avoids the previous "drop the lock, do work, re-acquire" pattern where
|
||||
* a concurrent kasan_q_add() on the other core could mutate s_q_head /
|
||||
* s_q_tail / s_q_bytes during the lock-release window and we would
|
||||
* resume with stale state.
|
||||
*/
|
||||
void *evict[KASAN_Q_ENTRIES];
|
||||
unsigned n_evict = 0;
|
||||
|
||||
portENTER_CRITICAL(&s_q_mux);
|
||||
|
||||
/* If the ring is full, evict the oldest entry to make room. */
|
||||
unsigned next = (s_q_head + 1U) % KASAN_Q_ENTRIES;
|
||||
if (next == s_q_tail) {
|
||||
evict[n_evict++] = s_q[s_q_tail].ptr;
|
||||
s_q_bytes -= s_q[s_q_tail].size;
|
||||
s_q_tail = (s_q_tail + 1U) % KASAN_Q_ENTRIES;
|
||||
}
|
||||
|
||||
/* Insert the new entry at head. */
|
||||
s_q[s_q_head].ptr = ptr;
|
||||
s_q[s_q_head].size = size;
|
||||
s_q_head = (s_q_head + 1U) % KASAN_Q_ENTRIES;
|
||||
s_q_bytes += size;
|
||||
|
||||
/* Trim back to the configured byte budget. */
|
||||
while (s_q_bytes > (size_t)CONFIG_KASAN_QUARANTINE_SIZE
|
||||
&& s_q_tail != s_q_head
|
||||
&& n_evict < KASAN_Q_ENTRIES) {
|
||||
evict[n_evict++] = s_q[s_q_tail].ptr;
|
||||
s_q_bytes -= s_q[s_q_tail].size;
|
||||
s_q_tail = (s_q_tail + 1U) % KASAN_Q_ENTRIES;
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL(&s_q_mux);
|
||||
|
||||
for (unsigned i = 0; i < n_evict; i++) {
|
||||
kasan_q_release_one(evict[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CONFIG_KASAN_QUARANTINE_SIZE > 0 */
|
||||
|
||||
bool kasan_heap_should_defer_free(void)
|
||||
{
|
||||
#if CONFIG_KASAN_QUARANTINE_SIZE > 0
|
||||
return atomic_load_explicit(&s_q_defer_free[kasan_q_core_id()],
|
||||
memory_order_acquire) > 0U;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void kasan_heap_clear_deferred_free(void)
|
||||
{
|
||||
#if CONFIG_KASAN_QUARANTINE_SIZE > 0
|
||||
/*
|
||||
* Consume one ticket. Wrapping below zero is treated as a programming
|
||||
* error (call to clear() without matching enqueue from kasan_q_add).
|
||||
*/
|
||||
uint_fast32_t prev = atomic_fetch_sub_explicit(&s_q_defer_free[kasan_q_core_id()],
|
||||
1U, memory_order_release);
|
||||
assert(prev > 0U && "kasan_heap_clear_deferred_free: counter underflow");
|
||||
(void)prev;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ---- Heap hooks --------------------------------------------------------- */
|
||||
|
||||
void kasan_heap_alloc_impl(void *ptr, size_t size, uint32_t caps)
|
||||
{
|
||||
(void)caps;
|
||||
if (ptr == NULL || size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark the full granule containing the tail of the user allocation as
|
||||
* unconditionally valid (instead of "first N bytes valid"). Many libc
|
||||
* memcpy / strlen / strcpy implementations on RISC-V perform word-at-a-
|
||||
* time loads and may legitimately touch the bytes between the requested
|
||||
* end and the next 4-byte boundary; treating those as a partial poison
|
||||
* would flag a false positive. The actual right redzone still starts at
|
||||
* the next granule boundary, so genuine overflows past 4 bytes are still
|
||||
* detected.
|
||||
*/
|
||||
const size_t granule_aligned_size = (size + 3U) & ~(size_t)3U;
|
||||
kasan_unpoison_region(ptr, granule_aligned_size);
|
||||
|
||||
#if CONFIG_KASAN_HEAP_REDZONE_SIZE > 0
|
||||
uint8_t *lrz = (uint8_t *)ptr - KASAN_RZ;
|
||||
kasan_lrz_hdr_t hdr = { .magic = KASAN_LRZ_MAGIC, .user_size = size };
|
||||
memcpy(lrz, &hdr, sizeof(hdr));
|
||||
kasan_poison_region(lrz, KASAN_RZ, KASAN_POISON_HEAP_LRZ);
|
||||
|
||||
/* Start RRZ at the next granule boundary, not at user_ptr + size. */
|
||||
uint8_t *rrz = (uint8_t *)ptr + granule_aligned_size;
|
||||
kasan_poison_region(rrz, KASAN_RZ, KASAN_POISON_HEAP_RRZ);
|
||||
#endif
|
||||
}
|
||||
|
||||
void kasan_heap_free_impl(void *ptr)
|
||||
{
|
||||
if (ptr == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if CONFIG_KASAN_HEAP_REDZONE_SIZE > 0
|
||||
kasan_lrz_hdr_t hdr;
|
||||
uint8_t *lrz = (uint8_t *)ptr - KASAN_RZ;
|
||||
memcpy(&hdr, lrz, sizeof(hdr));
|
||||
if (hdr.magic == KASAN_LRZ_MAGIC) {
|
||||
size_t total = KASAN_RZ + hdr.user_size + KASAN_RZ;
|
||||
kasan_poison_region(lrz, total, KASAN_POISON_HEAP_FREE);
|
||||
} else {
|
||||
size_t poison_size = heap_caps_get_allocated_size(ptr);
|
||||
if (poison_size == 0) {
|
||||
poison_size = 8;
|
||||
}
|
||||
kasan_poison_region(ptr, poison_size, KASAN_POISON_HEAP_FREE);
|
||||
}
|
||||
#else
|
||||
size_t poison_size = heap_caps_get_allocated_size(ptr);
|
||||
if (poison_size == 0) {
|
||||
poison_size = 8;
|
||||
}
|
||||
kasan_poison_region(ptr, poison_size, KASAN_POISON_HEAP_FREE);
|
||||
#endif
|
||||
|
||||
#if CONFIG_KASAN_QUARANTINE_SIZE > 0
|
||||
size_t q_size = 8;
|
||||
#if CONFIG_KASAN_HEAP_REDZONE_SIZE > 0
|
||||
{
|
||||
kasan_lrz_hdr_t qhdr;
|
||||
memcpy(&qhdr, (uint8_t *)ptr - KASAN_RZ, sizeof(qhdr));
|
||||
if (qhdr.magic == KASAN_LRZ_MAGIC) {
|
||||
q_size = qhdr.user_size;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
kasan_q_add(ptr, q_size);
|
||||
/*
|
||||
* Bump the per-core counter *after* the block is safely in the
|
||||
* quarantine. This keeps in-progress heap_caps_free() invocations on
|
||||
* the same core (including ISR-driven nested ones) in 1:1 lock-step with
|
||||
* kasan_heap_clear_deferred_free() consumes, so neither the outer call
|
||||
* nor the ISR-injected call can hand its pointer back to multi_heap_free
|
||||
* after the block is already queued for deferred release.
|
||||
*/
|
||||
atomic_fetch_add_explicit(&s_q_defer_free[kasan_q_core_id()], 1U,
|
||||
memory_order_release);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* CONFIG_COMPILER_KASAN && CONFIG_HEAP_USE_HOOKS */
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Strong (non-weak) definitions of the heap trace hooks for KASAN.
|
||||
*
|
||||
* IMPORTANT: This file must NOT include esp_heap_caps.h or any header that
|
||||
* transitively includes it (e.g. freertos/idf_additions.h). The reason is
|
||||
* that esp_heap_caps.h declares esp_heap_trace_alloc_hook and
|
||||
* esp_heap_trace_free_hook with __attribute__((weak)), and GCC propagates the
|
||||
* weak attribute to the definition in the same TU. By keeping this file free
|
||||
* of that header we get strong (globally overriding) definitions that the
|
||||
* linker will prefer over the empty weak stubs.
|
||||
*
|
||||
* The actual KASAN logic lives in heap_kasan.c; this file just calls into it.
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if CONFIG_COMPILER_KASAN && CONFIG_HEAP_USE_HOOKS
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_kasan.h"
|
||||
|
||||
/* Forward-declare the real implementation from heap_kasan.c */
|
||||
void kasan_heap_alloc_impl(void *ptr, size_t size, uint32_t caps);
|
||||
void kasan_heap_free_impl(void *ptr);
|
||||
|
||||
/*
|
||||
* These definitions are strong because this TU never sees the weak declaration
|
||||
* from esp_heap_caps.h. The linker will therefore use these in preference to
|
||||
* the empty weak stubs generated by the heap component's own code.
|
||||
*/
|
||||
void esp_heap_trace_alloc_hook(void *ptr, size_t size, uint32_t caps)
|
||||
{
|
||||
kasan_heap_alloc_impl(ptr, size, caps);
|
||||
}
|
||||
|
||||
void esp_heap_trace_free_hook(void *ptr)
|
||||
{
|
||||
kasan_heap_free_impl(ptr);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_COMPILER_KASAN && CONFIG_HEAP_USE_HOOKS */
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "sdkconfig.h"
|
||||
|
||||
/*
|
||||
* Shared KASAN heap redzone layout helpers for heap_caps_base.c, heap_caps.c,
|
||||
* and heap_kasan.c.
|
||||
*
|
||||
* Allocation layout (with CONFIG_HEAP_TASK_TRACKING=y):
|
||||
*
|
||||
* [ block-owner word ][ left redzone ][ user bytes ][ right redzone ]
|
||||
*
|
||||
* The user-visible pointer points at the start of the user region.
|
||||
* KASAN_USER_TO_PTR / KASAN_USER_TO_RAW move back to the left redzone start;
|
||||
* KASAN_PTR_TO_USER moves a block-owner-relative pointer to the user region.
|
||||
*/
|
||||
|
||||
#if CONFIG_COMPILER_KASAN && CONFIG_HEAP_USE_HOOKS && (CONFIG_KASAN_HEAP_REDZONE_SIZE > 0)
|
||||
#define HEAP_KASAN_RZ_ENABLED 1
|
||||
#else
|
||||
#define HEAP_KASAN_RZ_ENABLED 0
|
||||
#endif
|
||||
|
||||
#if HEAP_KASAN_RZ_ENABLED
|
||||
#define KASAN_RZ CONFIG_KASAN_HEAP_REDZONE_SIZE
|
||||
#define KASAN_ADD_RZ(sz) ((sz) + 2 * KASAN_RZ)
|
||||
#define KASAN_PTR_TO_USER(p) ((void *)((uint8_t *)(p) + KASAN_RZ))
|
||||
#define KASAN_USER_TO_PTR(p) ((void *)((uint8_t *)(p) - KASAN_RZ))
|
||||
#define KASAN_USER_TO_RAW(p) KASAN_USER_TO_PTR(p)
|
||||
#else
|
||||
#define KASAN_RZ 0
|
||||
#define KASAN_ADD_RZ(sz) (sz)
|
||||
#define KASAN_PTR_TO_USER(p) (p)
|
||||
#define KASAN_USER_TO_PTR(p) (p)
|
||||
#define KASAN_USER_TO_RAW(p) (p)
|
||||
#endif
|
||||
Reference in New Issue
Block a user