From 2b975037e07795870ecaa5a4b4ca7d3602ceb8dc Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Mon, 31 Aug 2026 00:33:07 +0800 Subject: [PATCH 01/11] [components][asan] Add runtime AddressSanitizer for heap Add a heap-only AddressSanitizer (kernel-address) runtime for RT-Thread. It instruments memory accesses via GCC's -fsanitize=kernel-address and detects heap-buffer-overflow and use-after-free at runtime, with a FinSH 'asan_info' command for interactive diagnosis. ## What - components/asan/: shadow memory + instrumented-access checks + report - allocator hooks to poison/unpoison heap blocks (malloc/free/realloc) - components/Kconfig: register RT_USING_ASAN with shadow/track/backtrace opts - src/SConscript: build mem/memheap/slab allocators without instrumentation - tools/building.py: inject -fsanitize=kernel-address on GCC ## Why RT-Thread lacks runtime memory-safety checking on MCU targets (ASan only existed on the x86 simulator). Heap overflow and use-after-free are the most common embedded memory bugs; this gives on-target detection with thread and block context in the report. ## Heap algorithm support - small mem: full support (overflow + use-after-free) - slab / memheap: overflow only (their allocators reuse freed blocks for metadata written via instrumented rt_memset, so free-block poisoning is disabled to avoid false positives) - userheap: mutually exclusive (Kconfig) Verified on qemu-vexpress-a9 (small mem / slab / memheap) and on a real STM32F407ZGT6 board. --- components/Kconfig | 1 + components/asan/Kconfig | 56 ++++ components/asan/SConscript | 13 + components/asan/asan.c | 537 +++++++++++++++++++++++++++++++++++++ src/SConscript | 18 ++ tools/building.py | 6 + 6 files changed, 631 insertions(+) create mode 100644 components/asan/Kconfig create mode 100644 components/asan/SConscript create mode 100644 components/asan/asan.c diff --git a/components/Kconfig b/components/Kconfig index accc38c67434..9f19018e93f8 100644 --- a/components/Kconfig +++ b/components/Kconfig @@ -36,6 +36,7 @@ rsource "drivers/Kconfig" rsource "libc/Kconfig" rsource "net/Kconfig" rsource "mprotect/Kconfig" +rsource "asan/Kconfig" rsource "utilities/Kconfig" endif diff --git a/components/asan/Kconfig b/components/asan/Kconfig new file mode 100644 index 000000000000..3001632e5e9b --- /dev/null +++ b/components/asan/Kconfig @@ -0,0 +1,56 @@ +menuconfig RT_USING_ASAN + bool "Enable AddressSanitizer (heap overflow & use-after-free check)" + default n + depends on RT_USING_HOOK && RT_HOOK_USING_FUNC_PTR && !RT_USING_USERHEAP + help + Enable runtime AddressSanitizer (kernel-address) support. It + instruments memory accesses to detect heap buffer overflow and + use-after-free at runtime. + + It requires the toolchain to support '-fsanitize=kernel-address' + (GCC 8+, verified on ARM and RISC-V). + + The shadow memory is a static array of RT_ASAN_SHADOW_SIZE bytes + and covers the first RT_ASAN_SHADOW_SIZE * 8 bytes of the heap. + Accesses beyond that range are not checked. + + Heap algorithm support: + - small mem (RT_USING_SMALL_MEM_AS_HEAP): full support, detects + both heap-buffer-overflow and use-after-free. + - slab (RT_USING_SLAB_AS_HEAP) and memheap + (RT_USING_MEMHEAP_AS_HEAP): detects heap-buffer-overflow only. + Their allocators reuse freed blocks for internal metadata written + through instrumented rt_memset/rt_memcpy, so poisoning a whole + freed block would raise false positives; use-after-free is + therefore disabled for these two. + - userheap (RT_USING_USERHEAP): not supported (mutually exclusive). + + if RT_USING_ASAN + config RT_ASAN_SHADOW_SIZE + int "ASan shadow memory size (bytes)" + default 65536 + help + Size of the static shadow memory array. Each byte maps 8 + bytes of the heap, so the checked heap range is + RT_ASAN_SHADOW_SIZE * 8 bytes. + + config RT_ASAN_TRACK_MAX + int "Max number of tracked active allocations" + default 512 + help + Size of the allocation tracking table. Each entry records + one live block (ptr, size, owner thread). Reduce this on + memory-constrained MCUs (e.g. 128 or 64). When the table + is full, further allocations are not tracked (and thus not + diagnosed) but are still unpoisoned for correctness. + + config RT_ASAN_BACKTRACE + bool "Print full backtrace on report" + default y + help + When a violation is reported, also dump the full call stack + of the faulting thread via rt_backtrace(). This requires the + target architecture to implement a backtrace backend (unwind + table or frame pointer chain). Architectures without one + print nothing extra. + endif diff --git a/components/asan/SConscript b/components/asan/SConscript new file mode 100644 index 000000000000..11db7c4cbaff --- /dev/null +++ b/components/asan/SConscript @@ -0,0 +1,13 @@ +from building import * + +cwd = GetCurrentDir() +src = Glob('*.c') +CPPPATH = [cwd] + +# The ASan runtime itself must not be instrumented, otherwise it would +# recurse infinitely. '-fno-sanitize=kernel-address' is appended after the +# global '-fsanitize=kernel-address' and therefore overrides it. +group = DefineGroup('asan', src, depend=['RT_USING_ASAN'], CPPPATH=CPPPATH, + LOCAL_CFLAGS=' -fno-sanitize=kernel-address') + +Return('group') diff --git a/components/asan/asan.c b/components/asan/asan.c new file mode 100644 index 000000000000..e2974c0fe72c --- /dev/null +++ b/components/asan/asan.c @@ -0,0 +1,537 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-30 RT-Thread first version (heap-only AddressSanitizer) + */ + +#include +#include + +#ifdef RT_USING_ASAN + +#define DBG_TAG "asan" +#define DBG_LVL DBG_INFO +#include + +/* + * Runtime AddressSanitizer (kernel-address) for RT-Thread. + * + * The compiler instruments every memory load/store and calls + * __asan_loadN_noabort / __asan_storeN_noabort. Those helpers check a + * shadow byte (8 bytes of application memory -> 1 shadow byte) and report + * when the access touches a poisoned granule. + * + * The system heap is poisoned/unpoisoned via the existing rt_malloc/rt_free + * hooks, which gives heap buffer overflow and use-after-free detection. + */ + +/* ---- shadow memory ---- */ +static rt_uintptr_t asan_heap_base; /* first checked address */ +static rt_uintptr_t asan_heap_limit; /* base + coverage */ +static rt_uint8_t asan_shadow[RT_ASAN_SHADOW_SIZE]; /* 8 bytes -> 1 byte */ + +#define ASAN_SHADOW_SCALE 8 +#define ASAN_POISON 0xF8 /* whole granule poisoned */ +#define ASAN_MIN(a, b) ((a) < (b) ? (a) : (b)) + +/* + * Poisoning a freed block enables use-after-free detection. This is only safe + * for allocators whose internal metadata is written by non-instrumented code: + * small mem assigns its header fields directly, but memheap/slab write their + * internal structures (memheap item headers, slab zone structs) through + * instrumented rt_memset/rt_memcpy and place them inside freed blocks, so + * poisoning the whole block would report those allocator-internal writes as + * false positives. For those allocators only the tail redzone (overflow) is + * kept. + */ +#if defined(RT_USING_SMALL_MEM_AS_HEAP) +#define ASAN_POISON_FREED_BLOCK 1 +#else +#define ASAN_POISON_FREED_BLOCK 0 +#endif + +/* ---- allocation tracking table ---- */ +#ifndef RT_ASAN_TRACK_MAX +#define RT_ASAN_TRACK_MAX 512 +#endif + +struct asan_track +{ + rt_uintptr_t ptr; + rt_uint32_t size; + rt_uint8_t used; + char owner[RT_NAME_MAX]; +}; + +static struct asan_track asan_tracks[RT_ASAN_TRACK_MAX]; + +/* most recently freed block, for use-after-free diagnosis */ +static struct asan_track asan_last_freed; + +/* ---- helpers ---- */ +rt_inline rt_bool_t asan_addr_in_range(rt_uintptr_t addr) +{ + return addr >= asan_heap_base && addr < asan_heap_limit; +} + +/* check whether [addr, addr+size) touches any poisoned byte */ +static rt_bool_t asan_range_is_poisoned(rt_uintptr_t addr, rt_size_t size) +{ + rt_uintptr_t a = addr; + rt_uintptr_t end = addr + size; + + if (size == 0) + return RT_FALSE; + + while (a < end) + { + rt_uintptr_t off; + rt_uint8_t s; + rt_size_t n; + + if (!asan_addr_in_range(a)) + return RT_FALSE; /* outside shadow coverage: not checked */ + + off = a - asan_heap_base; + s = asan_shadow[off >> 3]; + + if (s == 0) + { + /* whole granule addressable */ + n = ASAN_SHADOW_SCALE - (off & (ASAN_SHADOW_SCALE - 1)); + } + else if (s >= ASAN_SHADOW_SCALE) + { + return RT_TRUE; /* whole granule poisoned */ + } + else + { + /* partial granule: first s bytes addressable */ + if ((off & (ASAN_SHADOW_SCALE - 1)) >= s) + return RT_TRUE; + n = s - (off & (ASAN_SHADOW_SCALE - 1)); + } + + if (n >= end - a) + return RT_FALSE; /* remaining bytes are addressable */ + a += n; + } + + return RT_FALSE; +} + +static void asan_locate_block(rt_uintptr_t addr) +{ + rt_uint32_t i; + rt_uint32_t best = RT_ASAN_TRACK_MAX; + rt_uintptr_t best_end = 0; + + /* 1. exact match: addr is inside an active block */ + for (i = 0; i < RT_ASAN_TRACK_MAX; i++) + { + if (asan_tracks[i].used && + addr >= asan_tracks[i].ptr && + addr < asan_tracks[i].ptr + asan_tracks[i].size) + { + rt_kprintf("== block : 0x%08x size %d owner %.*s (inside block, offset +%d)\n", + asan_tracks[i].ptr, asan_tracks[i].size, + RT_NAME_MAX, asan_tracks[i].owner, + addr - asan_tracks[i].ptr); + return; + } + } + + /* 2. use-after-free: addr is inside the most recently freed block */ + if (asan_last_freed.used && + addr >= asan_last_freed.ptr && + addr < asan_last_freed.ptr + asan_last_freed.size) + { + rt_kprintf("== block : 0x%08x size %d owner %.*s (USE-AFTER-FREE, offset +%d)\n", + asan_last_freed.ptr, asan_last_freed.size, + RT_NAME_MAX, asan_last_freed.owner, + addr - asan_last_freed.ptr); + return; + } + + /* 3. overflow candidate: the active block whose tail is closest below addr */ + for (i = 0; i < RT_ASAN_TRACK_MAX; i++) + { + rt_uintptr_t blk_end; + + if (!asan_tracks[i].used) + continue; + + blk_end = asan_tracks[i].ptr + asan_tracks[i].size; + if (blk_end <= addr && blk_end >= best_end) + { + best = i; + best_end = blk_end; + } + } + + if (best != RT_ASAN_TRACK_MAX) + { + rt_kprintf("== block : 0x%08x size %d owner %.*s (overflow by %d bytes)\n", + asan_tracks[best].ptr, asan_tracks[best].size, + RT_NAME_MAX, asan_tracks[best].owner, + addr - best_end); + } + else + { + rt_kprintf("== block : (no nearby active allocation)\n"); + } +} + +static void asan_report(rt_uintptr_t addr, rt_size_t size, rt_bool_t is_write, rt_uintptr_t pc) +{ + rt_thread_t self = rt_thread_self(); + + rt_kprintf("\n"); + rt_kprintf("=================================================================\n"); + rt_kprintf("== ADDRESS SANITIZER: %s\n", + is_write ? "heap-buffer-overflow on WRITE" : "heap-buffer-overflow on READ"); + rt_kprintf("== address: 0x%08x size: %d\n", addr, size); + rt_kprintf("== pc : 0x%08x\n", pc); + if (self) + rt_kprintf("== thread : %.*s\n", RT_NAME_MAX, self->parent.name); + asan_locate_block(addr); +#ifdef RT_ASAN_BACKTRACE + rt_backtrace(); +#endif + rt_kprintf("=================================================================\n"); +} + +/* ---- instrumented access checks ---- */ +#define ASAN_DEFINE_CHECK(_size, _suffix) \ + void __asan_load##_suffix##_noabort(rt_uintptr_t addr) \ + { \ + if (asan_range_is_poisoned(addr, _size)) \ + asan_report(addr, _size, RT_FALSE, \ + (rt_uintptr_t)__builtin_return_address(0)); \ + } \ + void __asan_store##_suffix##_noabort(rt_uintptr_t addr) \ + { \ + if (asan_range_is_poisoned(addr, _size)) \ + asan_report(addr, _size, RT_TRUE, \ + (rt_uintptr_t)__builtin_return_address(0)); \ + } + +ASAN_DEFINE_CHECK(1, 1) +ASAN_DEFINE_CHECK(2, 2) +ASAN_DEFINE_CHECK(4, 4) +ASAN_DEFINE_CHECK(8, 8) +ASAN_DEFINE_CHECK(16, 16) + +/* variable-length variants */ +void __asan_loadN_noabort(rt_uintptr_t addr, rt_size_t size) +{ + if (asan_range_is_poisoned(addr, size)) + asan_report(addr, size, RT_FALSE, (rt_uintptr_t)__builtin_return_address(0)); +} + +void __asan_storeN_noabort(rt_uintptr_t addr, rt_size_t size) +{ + if (asan_range_is_poisoned(addr, size)) + asan_report(addr, size, RT_TRUE, (rt_uintptr_t)__builtin_return_address(0)); +} + +/* misc symbols referenced by some GCC versions */ +void __asan_init(void) {} +void __asan_handle_no_return(void) {} + +/* ---- poison / unpoison (allocator integration) ---- */ +static void asan_unpoison_range(rt_uintptr_t addr, rt_size_t size) +{ + rt_uintptr_t a = addr; + rt_uintptr_t end = addr + size; + + if (size == 0) + return; + + while (a < end) + { + rt_uintptr_t off; + rt_uint8_t *sh; + rt_size_t n; + rt_uint8_t k; + + if (!asan_addr_in_range(a)) + return; + + off = a - asan_heap_base; + sh = &asan_shadow[off >> 3]; + k = off & (ASAN_SHADOW_SCALE - 1); + n = ASAN_MIN(ASAN_SHADOW_SCALE - k, end - a); + + if (n == ASAN_SHADOW_SCALE) + *sh = 0; /* whole granule addressable */ + else + *sh = (rt_uint8_t)n; /* first n bytes addressable */ + + a += n; + } +} + +static void asan_poison_range(rt_uintptr_t addr, rt_size_t size) +{ + rt_uintptr_t a = addr; + rt_uintptr_t end = addr + size; + + if (size == 0) + return; + + while (a < end) + { + rt_uintptr_t off; + rt_uint8_t *sh; + rt_size_t n; + rt_uint8_t k; + + if (!asan_addr_in_range(a)) + return; + + off = a - asan_heap_base; + sh = &asan_shadow[off >> 3]; + k = off & (ASAN_SHADOW_SCALE - 1); + n = ASAN_MIN(ASAN_SHADOW_SCALE - k, end - a); + + if (n == ASAN_SHADOW_SCALE) + *sh = ASAN_POISON; /* whole granule poisoned */ + else + *sh = k; /* only first k bytes stay addressable */ + + a += n; + } +} + +/* ---- allocation tracking ---- */ +static void asan_track_add(rt_uintptr_t ptr, rt_size_t size) +{ + rt_uint32_t i; + + /* update an existing record (e.g. realloc growing in place keeps the same + * user pointer but a larger size), otherwise append a new one */ + for (i = 0; i < RT_ASAN_TRACK_MAX; i++) + { + if (asan_tracks[i].used && asan_tracks[i].ptr == ptr) + { + asan_tracks[i].size = size; + return; + } + } + + for (i = 0; i < RT_ASAN_TRACK_MAX; i++) + { + if (!asan_tracks[i].used) + { + asan_tracks[i].ptr = ptr; + asan_tracks[i].size = size; + asan_tracks[i].used = 1; + if (rt_thread_self()) + rt_strncpy(asan_tracks[i].owner, rt_thread_self()->parent.name, RT_NAME_MAX - 1); + else + rt_memset(asan_tracks[i].owner, 0, RT_NAME_MAX); + return; + } + } +} + +static rt_uint32_t asan_track_find(rt_uintptr_t ptr) +{ + rt_uint32_t i; + + for (i = 0; i < RT_ASAN_TRACK_MAX; i++) + { + if (asan_tracks[i].used && asan_tracks[i].ptr == ptr) + return i; + } + + return RT_ASAN_TRACK_MAX; /* not found */ +} + +static void asan_malloc_hook(void **ptr, rt_size_t size) +{ + rt_uintptr_t p; + rt_size_t aligned; + + if (!*ptr) + return; + + p = (rt_uintptr_t)*ptr; + aligned = RT_ALIGN(size, ASAN_SHADOW_SCALE); + + /* address reuse: this block was freed before, clear the stale record */ + if (asan_last_freed.used && asan_last_freed.ptr == p) + asan_last_freed.used = 0; + + asan_track_add(p, size); + asan_unpoison_range(p, size); + if (aligned > size) + asan_poison_range(p + size, aligned - size); +} + +static void asan_free_hook(void **ptr) +{ + rt_uintptr_t p; + rt_uint32_t idx; + + if (!*ptr) + return; + + p = (rt_uintptr_t)*ptr; + idx = asan_track_find(p); + if (idx == RT_ASAN_TRACK_MAX) + return; /* unknown block, skip */ + +#if ASAN_POISON_FREED_BLOCK + { + rt_size_t aligned = RT_ALIGN(asan_tracks[idx].size, ASAN_SHADOW_SCALE); + asan_poison_range(p, aligned); /* poison whole block -> use-after-free */ + } +#endif + + /* remember it for use-after-free diagnosis */ + asan_last_freed = asan_tracks[idx]; + asan_last_freed.used = 1; + + asan_tracks[idx].used = 0; +} + +/* rt_realloc frees/moves the old block and allocates a new one without going + * through rt_free/rt_malloc, so its hooks must be handled separately. */ +static rt_uintptr_t asan_realloc_old_ptr; + +static void asan_realloc_entry_hook(void **ptr, rt_size_t size) +{ + RT_UNUSED(size); + asan_realloc_old_ptr = (rt_uintptr_t)*ptr; +} + +static void asan_realloc_exit_hook(void **ptr, rt_size_t size) +{ + rt_uintptr_t p; + rt_size_t aligned; + rt_uint32_t idx; + + if (!*ptr) + return; + + p = (rt_uintptr_t)*ptr; + aligned = RT_ALIGN(size, ASAN_SHADOW_SCALE); + + /* when realloc moves the block, poison the old block so that a stale + * pointer to it is still detected as use-after-free */ + if (asan_realloc_old_ptr && asan_realloc_old_ptr != p) + { + idx = asan_track_find(asan_realloc_old_ptr); + if (idx != RT_ASAN_TRACK_MAX) + { +#if ASAN_POISON_FREED_BLOCK + rt_size_t old_aligned = RT_ALIGN(asan_tracks[idx].size, ASAN_SHADOW_SCALE); + + asan_poison_range(asan_realloc_old_ptr, old_aligned); +#endif + asan_tracks[idx].used = 0; + } + } + + /* address may have been reused internally by the allocator, drop any + * stale use-after-free record for it */ + if (asan_last_freed.used && asan_last_freed.ptr == p) + asan_last_freed.used = 0; + + /* track and unpoison the new block, poison its tail redzone */ + asan_track_add(p, size); + asan_unpoison_range(p, size); + if (aligned > size) + asan_poison_range(p + size, aligned - size); +} + +/* + * Override the weak rt_system_heap_init to capture the heap range and + * install the allocator hooks before the generic heap init runs. + */ +void rt_system_heap_init(void *begin_addr, void *end_addr) +{ + rt_uintptr_t begin = (rt_uintptr_t)begin_addr; + rt_uintptr_t end = (rt_uintptr_t)end_addr; + + /* + * The shadow maps one byte per ASAN_SHADOW_SCALE (8) bytes. Heap blocks + * are RT_ALIGN_SIZE (8) aligned, so align the shadow base to the same + * granularity to keep every block boundary on a shadow byte boundary. + * Otherwise (e.g. __bss_end is only 4-aligned) the partial-granule + * state cannot represent an addressable region and false positives occur + * right at block start. + */ + asan_heap_base = RT_ALIGN(begin, ASAN_SHADOW_SCALE); + asan_heap_limit = ASAN_MIN(end, asan_heap_base + + (rt_uintptr_t)sizeof(asan_shadow) * ASAN_SHADOW_SCALE); + + /* + * Start with everything addressable: the heap allocators store their own + * metadata (headers, free lists, the heap object itself) inside the heap + * region, so an initially-poisoned shadow would report their internal + * accesses as false positives. Detection is provided by poisoning the + * block tail on allocation and the whole block on free instead. + */ + rt_memset(asan_shadow, 0, sizeof(asan_shadow)); + + /* install allocator hooks */ + rt_malloc_sethook(asan_malloc_hook); + rt_free_sethook(asan_free_hook); + rt_realloc_set_entry_hook(asan_realloc_entry_hook); + rt_realloc_set_exit_hook(asan_realloc_exit_hook); + + /* run the original heap init */ + rt_system_heap_init_generic(begin_addr, end_addr); +} + +#ifdef RT_USING_FINSH +#include + +static int asan_info(int argc, char **argv) +{ + rt_uint32_t i; + rt_uint32_t active = 0; + + rt_kprintf("\n-- AddressSanitizer status --\n"); + rt_kprintf("shadow : %p, %d bytes\n", asan_shadow, sizeof(asan_shadow)); + rt_kprintf("coverage : 0x%08x - 0x%08x (%d bytes)\n", + asan_heap_base, asan_heap_limit, + asan_heap_limit - asan_heap_base); + + if (asan_last_freed.used) + { + rt_kprintf("last free: 0x%08x size %d owner %.*s\n", + asan_last_freed.ptr, asan_last_freed.size, + RT_NAME_MAX, asan_last_freed.owner); + } + else + { + rt_kprintf("last free: (none)\n"); + } + + rt_kprintf("\n-- active allocations --\n"); + for (i = 0; i < RT_ASAN_TRACK_MAX; i++) + { + if (asan_tracks[i].used) + { + active++; + rt_kprintf(" 0x%08x %6d %.*s\n", + asan_tracks[i].ptr, asan_tracks[i].size, + RT_NAME_MAX, asan_tracks[i].owner); + } + } + rt_kprintf("total: %d active blocks\n", active); + + return 0; +} +MSH_CMD_EXPORT(asan_info, dump AddressSanitizer status); +#endif /* RT_USING_FINSH */ + +#endif /* RT_USING_ASAN */ diff --git a/src/SConscript b/src/SConscript index 7b2dec5e4ce1..4f6dc3fd078a 100644 --- a/src/SConscript +++ b/src/SConscript @@ -28,6 +28,17 @@ if GetDepend('RT_USING_SMP') == False: else: SrcRemove(src, ['cpu_up.c', 'scheduler_up.c']) +# AddressSanitizer: heap allocators keep their metadata (headers, free lists) +# inside the heap region, so instrumenting them would report their own header +# accesses as false positives. Move them to a separate non-instrumented group. +asan_alloc_src = [] +if GetDepend('RT_USING_ASAN'): + for alloc_name in ['mem.c', 'memheap.c', 'slab.c']: + matched = [x for x in src if os.path.basename(x.rstr()) == alloc_name] + if matched: + asan_alloc_src += matched + SrcRemove(src, [alloc_name]) + LOCAL_CFLAGS = '' LINKFLAGS = '' @@ -59,6 +70,13 @@ else: LINKFLAGS=LINKFLAGS, LOCAL_CFLAGS=LOCAL_CFLAGS, CPPDEFINES=['__RTTHREAD__'], LOCAL_CPPDEFINES=['__RT_KERNEL_SOURCE__']) +# AddressSanitizer: build heap allocators without instrumentation. +if GetDepend('RT_USING_ASAN') and asan_alloc_src: + group = group + DefineGroup('KernelAlloc', asan_alloc_src, depend=['RT_USING_ASAN'], + CPPPATH=inc, CPPDEFINES=['__RTTHREAD__'], + LOCAL_CPPDEFINES=['__RT_KERNEL_SOURCE__'], + LOCAL_CFLAGS=' -fno-sanitize=kernel-address') + list = os.listdir(cwd) for item in list: if os.path.isfile(os.path.join(cwd, item, 'SConscript')): diff --git a/tools/building.py b/tools/building.py index 125da8521e76..d5250dc87e56 100644 --- a/tools/building.py +++ b/tools/building.py @@ -378,6 +378,12 @@ def PrepareBuilding(env, root_directory, has_libcpu=False, remove_components = [ if rtconfig.PLATFORM in ['gcc'] and str(env['LINKFLAGS']).find('nano.specs') != -1: env.AppendUnique(CPPDEFINES = ['_REENT_SMALL']) + # AddressSanitizer (kernel-address): instrument memory accesses. The + # runtime is provided by components/asan and does not need libasan. + if rtconfig.PLATFORM in ['gcc'] and 'RT_USING_ASAN' in BuildOptions: + env.Append(CFLAGS=' -fsanitize=kernel-address -fno-omit-frame-pointer') + env.Append(LINKFLAGS=' -fsanitize=kernel-address') + attach_global_macros = GetOption('global-macros') if attach_global_macros: attach_global_macros = attach_global_macros.split(',') From da78c40dc661654f9d10a971cb666c4f2084e271 Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Mon, 31 Aug 2026 01:05:49 +0800 Subject: [PATCH 02/11] style: format asan.c with clang-format --- components/asan/asan.c | 138 ++++++++++++++++++++++++++++------------- 1 file changed, 96 insertions(+), 42 deletions(-) diff --git a/components/asan/asan.c b/components/asan/asan.c index e2974c0fe72c..d41e0fe1e311 100644 --- a/components/asan/asan.c +++ b/components/asan/asan.c @@ -32,11 +32,11 @@ /* ---- shadow memory ---- */ static rt_uintptr_t asan_heap_base; /* first checked address */ static rt_uintptr_t asan_heap_limit; /* base + coverage */ -static rt_uint8_t asan_shadow[RT_ASAN_SHADOW_SIZE]; /* 8 bytes -> 1 byte */ +static rt_uint8_t asan_shadow[RT_ASAN_SHADOW_SIZE]; /* 8 bytes -> 1 byte */ -#define ASAN_SHADOW_SCALE 8 -#define ASAN_POISON 0xF8 /* whole granule poisoned */ -#define ASAN_MIN(a, b) ((a) < (b) ? (a) : (b)) +#define ASAN_SHADOW_SCALE 8 +#define ASAN_POISON 0xF8 /* whole granule poisoned */ +#define ASAN_MIN(a, b) ((a) < (b) ? (a) : (b)) /* * Poisoning a freed block enables use-after-free detection. This is only safe @@ -49,22 +49,22 @@ static rt_uint8_t asan_shadow[RT_ASAN_SHADOW_SIZE]; /* 8 bytes -> 1 byt * kept. */ #if defined(RT_USING_SMALL_MEM_AS_HEAP) -#define ASAN_POISON_FREED_BLOCK 1 +#define ASAN_POISON_FREED_BLOCK 1 #else -#define ASAN_POISON_FREED_BLOCK 0 +#define ASAN_POISON_FREED_BLOCK 0 #endif /* ---- allocation tracking table ---- */ #ifndef RT_ASAN_TRACK_MAX -#define RT_ASAN_TRACK_MAX 512 +#define RT_ASAN_TRACK_MAX 512 #endif struct asan_track { rt_uintptr_t ptr; - rt_uint32_t size; - rt_uint8_t used; - char owner[RT_NAME_MAX]; + rt_uint32_t size; + rt_uint8_t used; + char owner[RT_NAME_MAX]; }; static struct asan_track asan_tracks[RT_ASAN_TRACK_MAX]; @@ -85,16 +85,20 @@ static rt_bool_t asan_range_is_poisoned(rt_uintptr_t addr, rt_size_t size) rt_uintptr_t end = addr + size; if (size == 0) + { return RT_FALSE; + } while (a < end) { rt_uintptr_t off; - rt_uint8_t s; - rt_size_t n; + rt_uint8_t s; + rt_size_t n; if (!asan_addr_in_range(a)) + { return RT_FALSE; /* outside shadow coverage: not checked */ + } off = a - asan_heap_base; s = asan_shadow[off >> 3]; @@ -112,12 +116,16 @@ static rt_bool_t asan_range_is_poisoned(rt_uintptr_t addr, rt_size_t size) { /* partial granule: first s bytes addressable */ if ((off & (ASAN_SHADOW_SCALE - 1)) >= s) + { return RT_TRUE; + } n = s - (off & (ASAN_SHADOW_SCALE - 1)); } if (n >= end - a) + { return RT_FALSE; /* remaining bytes are addressable */ + } a += n; } @@ -163,7 +171,9 @@ static void asan_locate_block(rt_uintptr_t addr) rt_uintptr_t blk_end; if (!asan_tracks[i].used) + { continue; + } blk_end = asan_tracks[i].ptr + asan_tracks[i].size; if (blk_end <= addr && blk_end >= best_end) @@ -197,7 +207,9 @@ static void asan_report(rt_uintptr_t addr, rt_size_t size, rt_bool_t is_write, r rt_kprintf("== address: 0x%08x size: %d\n", addr, size); rt_kprintf("== pc : 0x%08x\n", pc); if (self) + { rt_kprintf("== thread : %.*s\n", RT_NAME_MAX, self->parent.name); + } asan_locate_block(addr); #ifdef RT_ASAN_BACKTRACE rt_backtrace(); @@ -206,18 +218,18 @@ static void asan_report(rt_uintptr_t addr, rt_size_t size, rt_bool_t is_write, r } /* ---- instrumented access checks ---- */ -#define ASAN_DEFINE_CHECK(_size, _suffix) \ - void __asan_load##_suffix##_noabort(rt_uintptr_t addr) \ - { \ - if (asan_range_is_poisoned(addr, _size)) \ - asan_report(addr, _size, RT_FALSE, \ - (rt_uintptr_t)__builtin_return_address(0)); \ - } \ - void __asan_store##_suffix##_noabort(rt_uintptr_t addr) \ - { \ - if (asan_range_is_poisoned(addr, _size)) \ - asan_report(addr, _size, RT_TRUE, \ - (rt_uintptr_t)__builtin_return_address(0)); \ +#define ASAN_DEFINE_CHECK(_size, _suffix) \ + void __asan_load##_suffix##_noabort(rt_uintptr_t addr) \ + { \ + if (asan_range_is_poisoned(addr, _size)) \ + asan_report(addr, _size, RT_FALSE, \ + (rt_uintptr_t)__builtin_return_address(0)); \ + } \ + void __asan_store##_suffix##_noabort(rt_uintptr_t addr) \ + { \ + if (asan_range_is_poisoned(addr, _size)) \ + asan_report(addr, _size, RT_TRUE, \ + (rt_uintptr_t)__builtin_return_address(0)); \ } ASAN_DEFINE_CHECK(1, 1) @@ -230,13 +242,17 @@ ASAN_DEFINE_CHECK(16, 16) void __asan_loadN_noabort(rt_uintptr_t addr, rt_size_t size) { if (asan_range_is_poisoned(addr, size)) + { asan_report(addr, size, RT_FALSE, (rt_uintptr_t)__builtin_return_address(0)); + } } void __asan_storeN_noabort(rt_uintptr_t addr, rt_size_t size) { if (asan_range_is_poisoned(addr, size)) + { asan_report(addr, size, RT_TRUE, (rt_uintptr_t)__builtin_return_address(0)); + } } /* misc symbols referenced by some GCC versions */ @@ -250,27 +266,35 @@ static void asan_unpoison_range(rt_uintptr_t addr, rt_size_t size) rt_uintptr_t end = addr + size; if (size == 0) + { return; + } while (a < end) { rt_uintptr_t off; rt_uint8_t *sh; - rt_size_t n; - rt_uint8_t k; + rt_size_t n; + rt_uint8_t k; if (!asan_addr_in_range(a)) + { return; + } off = a - asan_heap_base; - sh = &asan_shadow[off >> 3]; - k = off & (ASAN_SHADOW_SCALE - 1); - n = ASAN_MIN(ASAN_SHADOW_SCALE - k, end - a); + sh = &asan_shadow[off >> 3]; + k = off & (ASAN_SHADOW_SCALE - 1); + n = ASAN_MIN(ASAN_SHADOW_SCALE - k, end - a); if (n == ASAN_SHADOW_SCALE) + { *sh = 0; /* whole granule addressable */ + } else + { *sh = (rt_uint8_t)n; /* first n bytes addressable */ + } a += n; } @@ -282,27 +306,35 @@ static void asan_poison_range(rt_uintptr_t addr, rt_size_t size) rt_uintptr_t end = addr + size; if (size == 0) + { return; + } while (a < end) { rt_uintptr_t off; rt_uint8_t *sh; - rt_size_t n; - rt_uint8_t k; + rt_size_t n; + rt_uint8_t k; if (!asan_addr_in_range(a)) + { return; + } off = a - asan_heap_base; - sh = &asan_shadow[off >> 3]; - k = off & (ASAN_SHADOW_SCALE - 1); - n = ASAN_MIN(ASAN_SHADOW_SCALE - k, end - a); + sh = &asan_shadow[off >> 3]; + k = off & (ASAN_SHADOW_SCALE - 1); + n = ASAN_MIN(ASAN_SHADOW_SCALE - k, end - a); if (n == ASAN_SHADOW_SCALE) + { *sh = ASAN_POISON; /* whole granule poisoned */ + } else + { *sh = k; /* only first k bytes stay addressable */ + } a += n; } @@ -328,13 +360,17 @@ static void asan_track_add(rt_uintptr_t ptr, rt_size_t size) { if (!asan_tracks[i].used) { - asan_tracks[i].ptr = ptr; + asan_tracks[i].ptr = ptr; asan_tracks[i].size = size; asan_tracks[i].used = 1; if (rt_thread_self()) + { rt_strncpy(asan_tracks[i].owner, rt_thread_self()->parent.name, RT_NAME_MAX - 1); + } else + { rt_memset(asan_tracks[i].owner, 0, RT_NAME_MAX); + } return; } } @@ -347,7 +383,9 @@ static rt_uint32_t asan_track_find(rt_uintptr_t ptr) for (i = 0; i < RT_ASAN_TRACK_MAX; i++) { if (asan_tracks[i].used && asan_tracks[i].ptr == ptr) + { return i; + } } return RT_ASAN_TRACK_MAX; /* not found */ @@ -356,36 +394,46 @@ static rt_uint32_t asan_track_find(rt_uintptr_t ptr) static void asan_malloc_hook(void **ptr, rt_size_t size) { rt_uintptr_t p; - rt_size_t aligned; + rt_size_t aligned; if (!*ptr) + { return; + } p = (rt_uintptr_t)*ptr; aligned = RT_ALIGN(size, ASAN_SHADOW_SCALE); /* address reuse: this block was freed before, clear the stale record */ if (asan_last_freed.used && asan_last_freed.ptr == p) + { asan_last_freed.used = 0; + } asan_track_add(p, size); asan_unpoison_range(p, size); if (aligned > size) + { asan_poison_range(p + size, aligned - size); + } } static void asan_free_hook(void **ptr) { rt_uintptr_t p; - rt_uint32_t idx; + rt_uint32_t idx; if (!*ptr) + { return; + } p = (rt_uintptr_t)*ptr; idx = asan_track_find(p); if (idx == RT_ASAN_TRACK_MAX) + { return; /* unknown block, skip */ + } #if ASAN_POISON_FREED_BLOCK { @@ -414,11 +462,13 @@ static void asan_realloc_entry_hook(void **ptr, rt_size_t size) static void asan_realloc_exit_hook(void **ptr, rt_size_t size) { rt_uintptr_t p; - rt_size_t aligned; - rt_uint32_t idx; + rt_size_t aligned; + rt_uint32_t idx; if (!*ptr) + { return; + } p = (rt_uintptr_t)*ptr; aligned = RT_ALIGN(size, ASAN_SHADOW_SCALE); @@ -442,13 +492,17 @@ static void asan_realloc_exit_hook(void **ptr, rt_size_t size) /* address may have been reused internally by the allocator, drop any * stale use-after-free record for it */ if (asan_last_freed.used && asan_last_freed.ptr == p) + { asan_last_freed.used = 0; + } /* track and unpoison the new block, poison its tail redzone */ asan_track_add(p, size); asan_unpoison_range(p, size); if (aligned > size) + { asan_poison_range(p + size, aligned - size); + } } /* @@ -458,7 +512,7 @@ static void asan_realloc_exit_hook(void **ptr, rt_size_t size) void rt_system_heap_init(void *begin_addr, void *end_addr) { rt_uintptr_t begin = (rt_uintptr_t)begin_addr; - rt_uintptr_t end = (rt_uintptr_t)end_addr; + rt_uintptr_t end = (rt_uintptr_t)end_addr; /* * The shadow maps one byte per ASAN_SHADOW_SCALE (8) bytes. Heap blocks @@ -468,9 +522,9 @@ void rt_system_heap_init(void *begin_addr, void *end_addr) * state cannot represent an addressable region and false positives occur * right at block start. */ - asan_heap_base = RT_ALIGN(begin, ASAN_SHADOW_SCALE); + asan_heap_base = RT_ALIGN(begin, ASAN_SHADOW_SCALE); asan_heap_limit = ASAN_MIN(end, asan_heap_base + - (rt_uintptr_t)sizeof(asan_shadow) * ASAN_SHADOW_SCALE); + (rt_uintptr_t)sizeof(asan_shadow) * ASAN_SHADOW_SCALE); /* * Start with everything addressable: the heap allocators store their own From 4416a46da8b6a7ea3111b1545a273f05a9fa3307 Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Mon, 31 Aug 2026 10:25:29 +0800 Subject: [PATCH 03/11] components: move asan under utilities --- components/Kconfig | 1 - components/utilities/Kconfig | 1 + components/{ => utilities}/asan/Kconfig | 0 components/{ => utilities}/asan/SConscript | 0 components/{ => utilities}/asan/asan.c | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename components/{ => utilities}/asan/Kconfig (100%) rename components/{ => utilities}/asan/SConscript (100%) rename components/{ => utilities}/asan/asan.c (100%) diff --git a/components/Kconfig b/components/Kconfig index 9f19018e93f8..accc38c67434 100644 --- a/components/Kconfig +++ b/components/Kconfig @@ -36,7 +36,6 @@ rsource "drivers/Kconfig" rsource "libc/Kconfig" rsource "net/Kconfig" rsource "mprotect/Kconfig" -rsource "asan/Kconfig" rsource "utilities/Kconfig" endif diff --git a/components/utilities/Kconfig b/components/utilities/Kconfig index c32cd692bde6..d2dc05c0bbc1 100644 --- a/components/utilities/Kconfig +++ b/components/utilities/Kconfig @@ -244,5 +244,6 @@ config RT_USING_RESOURCE_ID rsource "libadt/Kconfig" rsource "rt-link/Kconfig" +rsource "asan/Kconfig" endmenu diff --git a/components/asan/Kconfig b/components/utilities/asan/Kconfig similarity index 100% rename from components/asan/Kconfig rename to components/utilities/asan/Kconfig diff --git a/components/asan/SConscript b/components/utilities/asan/SConscript similarity index 100% rename from components/asan/SConscript rename to components/utilities/asan/SConscript diff --git a/components/asan/asan.c b/components/utilities/asan/asan.c similarity index 100% rename from components/asan/asan.c rename to components/utilities/asan/asan.c From 5f7ef13dad08e997eb0082175f8535368377b7c7 Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Mon, 31 Aug 2026 10:29:57 +0800 Subject: [PATCH 04/11] components: fix asan path in comment --- tools/building.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/building.py b/tools/building.py index d5250dc87e56..44ea243d1ae7 100644 --- a/tools/building.py +++ b/tools/building.py @@ -379,7 +379,7 @@ def PrepareBuilding(env, root_directory, has_libcpu=False, remove_components = [ env.AppendUnique(CPPDEFINES = ['_REENT_SMALL']) # AddressSanitizer (kernel-address): instrument memory accesses. The - # runtime is provided by components/asan and does not need libasan. + # runtime is provided by components/utilities/asan and does not need libasan. if rtconfig.PLATFORM in ['gcc'] and 'RT_USING_ASAN' in BuildOptions: env.Append(CFLAGS=' -fsanitize=kernel-address -fno-omit-frame-pointer') env.Append(LINKFLAGS=' -fsanitize=kernel-address') From feebc5a8bb9f4d5a75ad23df0b61431dcbefc6cb Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Thu, 3 Sep 2026 11:03:40 +0800 Subject: [PATCH 05/11] [asan] expose report counter and UAF capability Add rt_asan_report_count_get() to let the utest harness verify that a deliberate violation is actually detected (ASan uses the GCC _noabort variant, so a hit only prints and does not abort). Also move the use-after-free capability flag into asan.h as RT_ASAN_HAS_UAF_DETECTION so both the runtime and tests share a single source of truth. --- components/utilities/asan/asan.c | 32 ++++++++++------------ components/utilities/asan/asan.h | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 components/utilities/asan/asan.h diff --git a/components/utilities/asan/asan.c b/components/utilities/asan/asan.c index d41e0fe1e311..fb52e406ab50 100644 --- a/components/utilities/asan/asan.c +++ b/components/utilities/asan/asan.c @@ -13,6 +13,8 @@ #ifdef RT_USING_ASAN +#include "asan.h" + #define DBG_TAG "asan" #define DBG_LVL DBG_INFO #include @@ -34,26 +36,18 @@ static rt_uintptr_t asan_heap_base; /* first checked ad static rt_uintptr_t asan_heap_limit; /* base + coverage */ static rt_uint8_t asan_shadow[RT_ASAN_SHADOW_SIZE]; /* 8 bytes -> 1 byte */ +/* total number of violations reported, exposed for utest/CI verification */ +static volatile rt_uint32_t asan_report_count; + +rt_uint32_t rt_asan_report_count_get(void) +{ + return asan_report_count; +} + #define ASAN_SHADOW_SCALE 8 #define ASAN_POISON 0xF8 /* whole granule poisoned */ #define ASAN_MIN(a, b) ((a) < (b) ? (a) : (b)) -/* - * Poisoning a freed block enables use-after-free detection. This is only safe - * for allocators whose internal metadata is written by non-instrumented code: - * small mem assigns its header fields directly, but memheap/slab write their - * internal structures (memheap item headers, slab zone structs) through - * instrumented rt_memset/rt_memcpy and place them inside freed blocks, so - * poisoning the whole block would report those allocator-internal writes as - * false positives. For those allocators only the tail redzone (overflow) is - * kept. - */ -#if defined(RT_USING_SMALL_MEM_AS_HEAP) -#define ASAN_POISON_FREED_BLOCK 1 -#else -#define ASAN_POISON_FREED_BLOCK 0 -#endif - /* ---- allocation tracking table ---- */ #ifndef RT_ASAN_TRACK_MAX #define RT_ASAN_TRACK_MAX 512 @@ -200,6 +194,8 @@ static void asan_report(rt_uintptr_t addr, rt_size_t size, rt_bool_t is_write, r { rt_thread_t self = rt_thread_self(); + asan_report_count++; + rt_kprintf("\n"); rt_kprintf("=================================================================\n"); rt_kprintf("== ADDRESS SANITIZER: %s\n", @@ -435,7 +431,7 @@ static void asan_free_hook(void **ptr) return; /* unknown block, skip */ } -#if ASAN_POISON_FREED_BLOCK +#if RT_ASAN_HAS_UAF_DETECTION { rt_size_t aligned = RT_ALIGN(asan_tracks[idx].size, ASAN_SHADOW_SCALE); asan_poison_range(p, aligned); /* poison whole block -> use-after-free */ @@ -480,7 +476,7 @@ static void asan_realloc_exit_hook(void **ptr, rt_size_t size) idx = asan_track_find(asan_realloc_old_ptr); if (idx != RT_ASAN_TRACK_MAX) { -#if ASAN_POISON_FREED_BLOCK +#if RT_ASAN_HAS_UAF_DETECTION rt_size_t old_aligned = RT_ALIGN(asan_tracks[idx].size, ASAN_SHADOW_SCALE); asan_poison_range(asan_realloc_old_ptr, old_aligned); diff --git a/components/utilities/asan/asan.h b/components/utilities/asan/asan.h new file mode 100644 index 000000000000..5c9b5586669e --- /dev/null +++ b/components/utilities/asan/asan.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-30 RT-Thread the first version + */ + +#ifndef __ASAN_H__ +#define __ASAN_H__ + +#include + +/* + * Use-after-free detection requires poisoning a whole freed block. This is only + * safe for allocators whose internal metadata is written by non-instrumented + * code (small mem). memheap/slab reuse freed blocks for metadata written via + * instrumented rt_memset/rt_memcpy, so their freed blocks must not be poisoned. + */ +#if defined(RT_USING_SMALL_MEM_AS_HEAP) +#define RT_ASAN_HAS_UAF_DETECTION 1 +#else +#define RT_ASAN_HAS_UAF_DETECTION 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Get the total number of AddressSanitizer violations reported. + * + * This is used by the utest/CI harness to verify that a deliberate + * heap-buffer-overflow or use-after-free is actually detected at runtime. + * + * @return The accumulated report count. + */ +rt_uint32_t rt_asan_report_count_get(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __ASAN_H__ */ From efc3c5ad0f4d71ecdf4a808728b37ae8893503e1 Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Thu, 3 Sep 2026 11:04:10 +0800 Subject: [PATCH 06/11] [klibc] exclude memcpy/memset/memmove from asan instrumentation rt_memcpy/rt_memset/rt_memmove copy word-at-a-time and may legally touch a few bytes past the requested count (word-aligned bulk loops). Under -fsanitize=kernel-address those accesses fall into poisoned heap redzones and raise false positives (notably during rt_realloc block migration). Mark them no_sanitize_address, mirroring how KASAN treats the same helpers. --- src/klibc/kstring.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/klibc/kstring.c b/src/klibc/kstring.c index b6d553ffa34d..aac9894a33b1 100644 --- a/src/klibc/kstring.c +++ b/src/klibc/kstring.c @@ -10,6 +10,18 @@ #include +/* + * AddressSanitizer: rt_memcpy/rt_memset/rt_memmove copy word-at-a-time and may + * legally read/write a few bytes past the requested byte count (word-aligned + * bulk loops). When instrumented these accesses fall into poisoned redzones and + * raise false positives, so disable instrumentation for them. + */ +#ifdef RT_USING_ASAN +#define RT_KLIB_NO_ASAN __attribute__((no_sanitize_address)) +#else +#define RT_KLIB_NO_ASAN +#endif + #if defined(RT_KLIBC_USING_LIBC_MEMSET) || \ defined(RT_KLIBC_USING_LIBC_MEMCPY) || \ defined(RT_KLIBC_USING_LIBC_MEMMOVE) || \ @@ -36,6 +48,7 @@ * @return The address of source memory. */ #ifndef RT_KLIBC_USING_USER_MEMSET +RT_KLIB_NO_ASAN void *rt_memset(void *s, int c, size_t count) { #if defined(RT_KLIBC_USING_LIBC_MEMSET) @@ -121,6 +134,7 @@ RTM_EXPORT(rt_memset); * @return The address of destination memory */ #ifndef RT_KLIBC_USING_USER_MEMCPY +RT_KLIB_NO_ASAN void *rt_memcpy(void *dst, const void *src, size_t count) { #if defined(RT_KLIBC_USING_LIBC_MEMCPY) @@ -211,6 +225,7 @@ RTM_EXPORT(rt_memcpy); * @return The address of destination memory. */ #ifndef RT_KLIBC_USING_USER_MEMMOVE +RT_KLIB_NO_ASAN void *rt_memmove(void *dest, const void *src, size_t n) { #ifdef RT_KLIBC_USING_LIBC_MEMMOVE From 6bf8db6cd0c7100dea022cbef204778c6b03dd37 Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Thu, 3 Sep 2026 11:04:37 +0800 Subject: [PATCH 07/11] [asan] add utest testcase and CI guard Add a utest testcase (components.asan_tc) that exercises the ASan heap detection on a real target, plus a CI config that both compiles and runs it on qemu-vexpress-a9 via utest_auto_run. Test scenarios: - heap-buffer-overflow write / read - in-bounds access (no false positive) - realloc overflow - use-after-free read / write (small mem only) --- .github/utest/configs/components/asan.cfg | 8 + .github/workflows/utest_auto_run.yml | 2 + src/utest/Kconfig | 5 + src/utest/SConscript | 3 + src/utest/asan_tc.c | 236 ++++++++++++++++++++++ 5 files changed, 254 insertions(+) create mode 100644 .github/utest/configs/components/asan.cfg create mode 100644 src/utest/asan_tc.c diff --git a/.github/utest/configs/components/asan.cfg b/.github/utest/configs/components/asan.cfg new file mode 100644 index 000000000000..751ef66bc9b4 --- /dev/null +++ b/.github/utest/configs/components/asan.cfg @@ -0,0 +1,8 @@ +# dependencies +CONFIG_RT_CONSOLEBUF_SIZE=1024 +CONFIG_RT_USING_CI_ACTION=y + +CONFIG_RT_USING_ASAN=y +CONFIG_RT_ASAN_SHADOW_SIZE=65536 +CONFIG_RT_ASAN_TRACK_MAX=512 +CONFIG_RT_UTEST_ASAN=y diff --git a/.github/workflows/utest_auto_run.yml b/.github/workflows/utest_auto_run.yml index 9a4915737b6a..087b747af820 100644 --- a/.github/workflows/utest_auto_run.yml +++ b/.github/workflows/utest_auto_run.yml @@ -152,6 +152,8 @@ jobs: config_file: "components/dfs.cfg" - platform: { UTEST: "A9", RTT_BSP: "bsp/qemu-vexpress-a9", QEMU_ARCH: "arm", QEMU_MACHINE: "vexpress-a9", SD_FILE: "sd.bin", KERNEL: "standard", "SMP_RUN":"" } config_file: "components/libc.cfg" + - platform: { UTEST: "A9", RTT_BSP: "bsp/qemu-vexpress-a9", QEMU_ARCH: "arm", QEMU_MACHINE: "vexpress-a9", SD_FILE: "sd.bin", KERNEL: "standard", "SMP_RUN":"" } + config_file: "components/asan.cfg" env: TEST_QEMU_ARCH: ${{ matrix.platform.QEMU_ARCH }} diff --git a/src/utest/Kconfig b/src/utest/Kconfig index 7f478c591ffa..0a566a5769f4 100644 --- a/src/utest/Kconfig +++ b/src/utest/Kconfig @@ -84,6 +84,11 @@ menu "Kernel Core" default n depends on RT_USING_MEMPOOL + config RT_UTEST_ASAN + bool "AddressSanitizer Test" + default n + depends on RT_USING_ASAN + rsource "perf/Kconfig" rsource "../klibc/utest/Kconfig" diff --git a/src/utest/SConscript b/src/utest/SConscript index 52aca3dcf698..7768e6e6df72 100644 --- a/src/utest/SConscript +++ b/src/utest/SConscript @@ -58,6 +58,9 @@ if GetDepend(['RT_UTEST_MTSAFE_KPRINT']): if GetDepend(['RT_UTEST_MEMPOOL']): src += ['mempool_tc.c'] +if GetDepend(['RT_UTEST_ASAN']): + src += ['asan_tc.c'] + # Stressful testcase for scheduler (MP/UP) if GetDepend(['RT_UTEST_SCHEDULER']): src += ['sched_timeout_race_tc.c'] diff --git a/src/utest/asan_tc.c b/src/utest/asan_tc.c new file mode 100644 index 000000000000..14cf9171240c --- /dev/null +++ b/src/utest/asan_tc.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2006-2024, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-30 RT-Thread the first version + */ + +/** + * Test Case Name: AddressSanitizer Heap Detection Test + * + * Test Objectives: + * - Verify the runtime AddressSanitizer (kernel-address) detects heap memory + * violations on real targets + * - Verify heap-buffer-overflow (read/write), use-after-free (read/write) and + * realloc overflow are reported + * - Verify normal in-bounds accesses do not raise false positives + * + * Test Scenarios: + * - **Scenario 1 (Heap Overflow Write / test_asan_overflow_write):** + * 1. Allocate a 10-byte block (redzone occupies [10, 16)) + * 2. Write at offset 12 which falls into the poisoned redzone + * 3. Assert the ASan report counter increased + * - **Scenario 2 (Heap Overflow Read / test_asan_overflow_read):** + * 1. Allocate a 10-byte block + * 2. Read at offset 12 inside the poisoned redzone + * 3. Assert the ASan report counter increased + * - **Scenario 3 (No False Positive / test_asan_no_false_positive):** + * 1. Allocate a 10-byte block + * 2. Write to in-bounds offsets 0 and 9 + * 3. Assert the ASan report counter did not change + * - **Scenario 4 (Realloc Overflow / test_asan_realloc_overflow):** + * 1. Allocate 10 bytes and realloc to 20 bytes (redzone occupies [20, 24)) + * 2. Write at offset 22 inside the new poisoned redzone + * 3. Assert the ASan report counter increased + * - **Scenario 5 (Use-After-Free Read / test_asan_uaf_read):** + * (only when RT_ASAN_HAS_UAF_DETECTION is enabled) + * 1. Allocate and free a block + * 2. Read from the freed block + * 3. Assert the ASan report counter increased + * - **Scenario 6 (Use-After-Free Write / test_asan_uaf_write):** + * (only when RT_ASAN_HAS_UAF_DETECTION is enabled) + * 1. Allocate and free a block + * 2. Write to the freed block + * 3. Assert the ASan report counter increased + * + * Verification Metrics: + * - Overflow/UAF accesses increase rt_asan_report_count_get() + * - In-bounds accesses leave the counter unchanged + * + * Dependencies: + * - RT_USING_ASAN enabled + * - Heap-based dynamic memory (rt_malloc/rt_free/rt_realloc) + * + * Expected Results: + * - All enabled scenarios pass without assertion failures + */ + +#include +#include "utest.h" +#include "asan.h" + +static rt_err_t utest_tc_init(void) +{ + return RT_EOK; +} + +static rt_err_t utest_tc_cleanup(void) +{ + return RT_EOK; +} + +static void test_asan_overflow_write(void) +{ + rt_uint32_t before; + rt_uint32_t after; + char *p; + + p = (char *)rt_malloc(10); + uassert_not_null(p); + if (!p) + { + return; + } + + before = rt_asan_report_count_get(); + p[12] = 0x41; /* heap-buffer-overflow write (redzone [10, 16)) */ + after = rt_asan_report_count_get(); + + rt_free(p); + + uassert_true(after > before); +} + +static void test_asan_overflow_read(void) +{ + rt_uint32_t before; + rt_uint32_t after; + volatile char v; + char *p; + + p = (char *)rt_malloc(10); + uassert_not_null(p); + if (!p) + { + return; + } + + before = rt_asan_report_count_get(); + v = p[12]; /* heap-buffer-overflow read (redzone [10, 16)) */ + after = rt_asan_report_count_get(); + + (void)v; + rt_free(p); + + uassert_true(after > before); +} + +static void test_asan_no_false_positive(void) +{ + rt_uint32_t before; + rt_uint32_t after; + char *p; + + p = (char *)rt_malloc(10); + uassert_not_null(p); + if (!p) + { + return; + } + + before = rt_asan_report_count_get(); + p[0] = 0x01; /* first in-bounds byte */ + p[9] = 0x02; /* last in-bounds byte */ + after = rt_asan_report_count_get(); + + rt_free(p); + + uassert_int_equal(after, before); +} + +static void test_asan_realloc_overflow(void) +{ + rt_uint32_t before; + rt_uint32_t after; + char *p; + char *q; + + p = (char *)rt_malloc(10); + uassert_not_null(p); + if (!p) + { + return; + } + + q = (char *)rt_realloc(p, 20); + uassert_not_null(q); + if (!q) + { + rt_free(p); + return; + } + + before = rt_asan_report_count_get(); + q[22] = 0x41; /* heap-buffer-overflow write (redzone [20, 24)) */ + after = rt_asan_report_count_get(); + + rt_free(q); + + uassert_true(after > before); +} + +#if RT_ASAN_HAS_UAF_DETECTION +static void test_asan_uaf_read(void) +{ + rt_uint32_t before; + rt_uint32_t after; + volatile char v; + char *p; + + p = (char *)rt_malloc(10); + uassert_not_null(p); + if (!p) + { + return; + } + + rt_free(p); + + before = rt_asan_report_count_get(); + v = p[0]; /* use-after-free read */ + after = rt_asan_report_count_get(); + + (void)v; + + uassert_true(after > before); +} + +static void test_asan_uaf_write(void) +{ + rt_uint32_t before; + rt_uint32_t after; + char *p; + + p = (char *)rt_malloc(10); + uassert_not_null(p); + if (!p) + { + return; + } + + rt_free(p); + + before = rt_asan_report_count_get(); + p[0] = 0x41; /* use-after-free write */ + after = rt_asan_report_count_get(); + + uassert_true(after > before); +} +#endif /* RT_ASAN_HAS_UAF_DETECTION */ + +static void testcase(void) +{ + UTEST_UNIT_RUN(test_asan_overflow_write); + UTEST_UNIT_RUN(test_asan_overflow_read); + UTEST_UNIT_RUN(test_asan_no_false_positive); + UTEST_UNIT_RUN(test_asan_realloc_overflow); +#if RT_ASAN_HAS_UAF_DETECTION + UTEST_UNIT_RUN(test_asan_uaf_read); + UTEST_UNIT_RUN(test_asan_uaf_write); +#endif +} + +UTEST_TC_EXPORT(testcase, "components.asan_tc", utest_tc_init, utest_tc_cleanup, 1000); From 701071797e6164a719252317a4e2bd9c895d966d Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Mon, 7 Sep 2026 16:09:46 +0800 Subject: [PATCH 08/11] [asan] fix allocator synchronization and allocation redzones Integrate sanitizer allocation updates under the heap lock, reserve real redzones for normal and aligned allocations, and instrument C++ accesses. Handle realloc lifecycle and allocation-size overflow, and add regression tests that retain failures across test units. Validation: 11 ARM/QEMU configurations passed, including all three heap backends with ASan disabled; 64-bit host UBSan checks passed. Restoring the old aligned-allocation functions makes both regression suites fail as expected. --- components/utilities/asan/Kconfig | 14 +- components/utilities/asan/asan.c | 459 ++++++++++++++++-------------- components/utilities/asan/asan.h | 9 + examples/test/asan_cpp_test.cpp | 25 ++ examples/test/mem_align_test.c | 90 ++++++ src/kservice.c | 114 ++++++-- src/utest/Kconfig | 5 + src/utest/SConscript | 5 + src/utest/asan_tc.c | 325 +++++++++++++++++++-- tools/building.py | 3 +- 10 files changed, 790 insertions(+), 259 deletions(-) create mode 100644 examples/test/asan_cpp_test.cpp create mode 100644 examples/test/mem_align_test.c diff --git a/components/utilities/asan/Kconfig b/components/utilities/asan/Kconfig index 3001632e5e9b..805ec7e089d3 100644 --- a/components/utilities/asan/Kconfig +++ b/components/utilities/asan/Kconfig @@ -1,7 +1,8 @@ menuconfig RT_USING_ASAN bool "Enable AddressSanitizer (heap overflow & use-after-free check)" default n - depends on RT_USING_HOOK && RT_HOOK_USING_FUNC_PTR && !RT_USING_USERHEAP + depends on RT_USING_HEAP && !RT_USING_USERHEAP + depends on RT_USING_SMALL_MEM_AS_HEAP || RT_USING_MEMHEAP_AS_HEAP || RT_USING_SLAB_AS_HEAP help Enable runtime AddressSanitizer (kernel-address) support. It instruments memory accesses to detect heap buffer overflow and @@ -14,6 +15,13 @@ menuconfig RT_USING_ASAN and covers the first RT_ASAN_SHADOW_SIZE * 8 bytes of the heap. Accesses beyond that range are not checked. + Each allocation reserves a size header, alignment padding and at least + 8 bytes of right redzone. Realloc grows within its reserved capacity or + allocates and copies the requested user bytes; shrinking keeps capacity. + Aligned allocations also use the requested size for their redzones. + Only the system heap APIs are wrapped; direct allocator/page APIs and + accesses in prebuilt, uninstrumented libraries are not checked. + Heap algorithm support: - small mem (RT_USING_SMALL_MEM_AS_HEAP): full support, detects both heap-buffer-overflow and use-after-free. @@ -41,8 +49,8 @@ menuconfig RT_USING_ASAN Size of the allocation tracking table. Each entry records one live block (ptr, size, owner thread). Reduce this on memory-constrained MCUs (e.g. 128 or 64). When the table - is full, further allocations are not tracked (and thus not - diagnosed) but are still unpoisoned for correctness. + is full, further allocations still get redzones and free/realloc + handling, but reports may lack allocation/owner details. config RT_ASAN_BACKTRACE bool "Print full backtrace on report" diff --git a/components/utilities/asan/asan.c b/components/utilities/asan/asan.c index fb52e406ab50..42e38879829e 100644 --- a/components/utilities/asan/asan.c +++ b/components/utilities/asan/asan.c @@ -27,8 +27,8 @@ * shadow byte (8 bytes of application memory -> 1 shadow byte) and report * when the access touches a poisoned granule. * - * The system heap is poisoned/unpoisoned via the existing rt_malloc/rt_free - * hooks, which gives heap buffer overflow and use-after-free detection. + * The system heap calls this runtime while holding its allocator lock. Each + * allocation reserves a header and redzones independently of the tracking table. */ /* ---- shadow memory ---- */ @@ -36,12 +36,40 @@ static rt_uintptr_t asan_heap_base; /* first checked ad static rt_uintptr_t asan_heap_limit; /* base + coverage */ static rt_uint8_t asan_shadow[RT_ASAN_SHADOW_SIZE]; /* 8 bytes -> 1 byte */ +/* Use hardware locks: scheduler-aware spinlocks can access heap-allocated + * thread objects and recursively enter the sanitizer. Never print or call + * instrumented memory helpers while this lock is held. + */ +#ifdef RT_USING_SMP +static RT_DEFINE_HW_SPINLOCK(asan_spinlock); +#endif + +static rt_base_t asan_lock(void) +{ + rt_base_t level = rt_hw_local_irq_disable(); +#ifdef RT_USING_SMP + rt_hw_spin_lock(&asan_spinlock); +#endif + return level; +} + +static void asan_unlock(rt_base_t level) +{ +#ifdef RT_USING_SMP + rt_hw_spin_unlock(&asan_spinlock); +#endif + rt_hw_local_irq_enable(level); +} + /* total number of violations reported, exposed for utest/CI verification */ static volatile rt_uint32_t asan_report_count; rt_uint32_t rt_asan_report_count_get(void) { - return asan_report_count; + rt_base_t level = asan_lock(); + rt_uint32_t count = asan_report_count; + asan_unlock(level); + return count; } #define ASAN_SHADOW_SCALE 8 @@ -56,7 +84,7 @@ rt_uint32_t rt_asan_report_count_get(void) struct asan_track { rt_uintptr_t ptr; - rt_uint32_t size; + rt_size_t size; rt_uint8_t used; char owner[RT_NAME_MAX]; }; @@ -75,114 +103,90 @@ rt_inline rt_bool_t asan_addr_in_range(rt_uintptr_t addr) /* check whether [addr, addr+size) touches any poisoned byte */ static rt_bool_t asan_range_is_poisoned(rt_uintptr_t addr, rt_size_t size) { - rt_uintptr_t a = addr; - rt_uintptr_t end = addr + size; + rt_uintptr_t off; + rt_size_t n; + rt_uint8_t shadow; + rt_bool_t poisoned = RT_FALSE; + rt_base_t level; - if (size == 0) + if (!size || addr >= asan_heap_limit) { return RT_FALSE; } - - while (a < end) + if (addr < asan_heap_base) { - rt_uintptr_t off; - rt_uint8_t s; - rt_size_t n; - - if (!asan_addr_in_range(a)) - { - return RT_FALSE; /* outside shadow coverage: not checked */ - } - - off = a - asan_heap_base; - s = asan_shadow[off >> 3]; - - if (s == 0) - { - /* whole granule addressable */ - n = ASAN_SHADOW_SCALE - (off & (ASAN_SHADOW_SCALE - 1)); - } - else if (s >= ASAN_SHADOW_SCALE) - { - return RT_TRUE; /* whole granule poisoned */ - } - else + n = asan_heap_base - addr; + if (size <= n) { - /* partial granule: first s bytes addressable */ - if ((off & (ASAN_SHADOW_SCALE - 1)) >= s) - { - return RT_TRUE; - } - n = s - (off & (ASAN_SHADOW_SCALE - 1)); + return RT_FALSE; } - - if (n >= end - a) + addr = asan_heap_base; + size -= n; + } + size = ASAN_MIN(size, asan_heap_limit - addr); + level = asan_lock(); + while (size) + { + off = addr - asan_heap_base; + n = ASAN_MIN(ASAN_SHADOW_SCALE - (off & (ASAN_SHADOW_SCALE - 1)), size); + shadow = asan_shadow[off >> 3]; + if (shadow && (shadow >= ASAN_SHADOW_SCALE || + (off & (ASAN_SHADOW_SCALE - 1)) + n > shadow)) { - return RT_FALSE; /* remaining bytes are addressable */ + poisoned = RT_TRUE; + break; } - a += n; + addr += n; + size -= n; } - - return RT_FALSE; + asan_unlock(level); + return poisoned; } static void asan_locate_block(rt_uintptr_t addr) { - rt_uint32_t i; - rt_uint32_t best = RT_ASAN_TRACK_MAX; + struct asan_track block = { 0 }; + const char *kind = "overflow candidate"; rt_uintptr_t best_end = 0; + rt_bool_t freed = RT_FALSE; + rt_uint32_t i; + rt_base_t level = asan_lock(); - /* 1. exact match: addr is inside an active block */ - for (i = 0; i < RT_ASAN_TRACK_MAX; i++) - { - if (asan_tracks[i].used && - addr >= asan_tracks[i].ptr && - addr < asan_tracks[i].ptr + asan_tracks[i].size) - { - rt_kprintf("== block : 0x%08x size %d owner %.*s (inside block, offset +%d)\n", - asan_tracks[i].ptr, asan_tracks[i].size, - RT_NAME_MAX, asan_tracks[i].owner, - addr - asan_tracks[i].ptr); - return; - } - } - - /* 2. use-after-free: addr is inside the most recently freed block */ - if (asan_last_freed.used && - addr >= asan_last_freed.ptr && - addr < asan_last_freed.ptr + asan_last_freed.size) + if (asan_last_freed.used && addr >= asan_last_freed.ptr && + addr - asan_last_freed.ptr < asan_last_freed.size) { - rt_kprintf("== block : 0x%08x size %d owner %.*s (USE-AFTER-FREE, offset +%d)\n", - asan_last_freed.ptr, asan_last_freed.size, - RT_NAME_MAX, asan_last_freed.owner, - addr - asan_last_freed.ptr); - return; + block = asan_last_freed; + kind = "USE-AFTER-FREE"; + freed = RT_TRUE; } - - /* 3. overflow candidate: the active block whose tail is closest below addr */ for (i = 0; i < RT_ASAN_TRACK_MAX; i++) { - rt_uintptr_t blk_end; - if (!asan_tracks[i].used) { continue; } - - blk_end = asan_tracks[i].ptr + asan_tracks[i].size; - if (blk_end <= addr && blk_end >= best_end) + if (addr >= asan_tracks[i].ptr && addr - asan_tracks[i].ptr < asan_tracks[i].size) { - best = i; - best_end = blk_end; + block = asan_tracks[i]; + kind = "inside block"; + break; + } + if (!freed) + { + rt_uintptr_t end = asan_tracks[i].ptr + asan_tracks[i].size; + if (end <= addr && end >= best_end) + { + best_end = end; + block = asan_tracks[i]; + } } } - - if (best != RT_ASAN_TRACK_MAX) + asan_unlock(level); + if (block.used) { - rt_kprintf("== block : 0x%08x size %d owner %.*s (overflow by %d bytes)\n", - asan_tracks[best].ptr, asan_tracks[best].size, - RT_NAME_MAX, asan_tracks[best].owner, - addr - best_end); + rt_kprintf("== block : %p size %lu owner %.*s (%s, offset +%lu)\n", + (void *)block.ptr, (unsigned long)block.size, + RT_NAME_MAX, block.owner, kind, (unsigned long)(addr - block.ptr)); } else { @@ -194,14 +198,16 @@ static void asan_report(rt_uintptr_t addr, rt_size_t size, rt_bool_t is_write, r { rt_thread_t self = rt_thread_self(); + rt_base_t level = asan_lock(); asan_report_count++; + asan_unlock(level); rt_kprintf("\n"); rt_kprintf("=================================================================\n"); rt_kprintf("== ADDRESS SANITIZER: %s\n", - is_write ? "heap-buffer-overflow on WRITE" : "heap-buffer-overflow on READ"); - rt_kprintf("== address: 0x%08x size: %d\n", addr, size); - rt_kprintf("== pc : 0x%08x\n", pc); + is_write ? "invalid heap access on WRITE" : "invalid heap access on READ"); + rt_kprintf("== address: %p size: %lu\n", (void *)addr, (unsigned long)size); + rt_kprintf("== pc : %p\n", (void *)pc); if (self) { rt_kprintf("== thread : %.*s\n", RT_NAME_MAX, self->parent.name); @@ -281,15 +287,15 @@ static void asan_unpoison_range(rt_uintptr_t addr, rt_size_t size) off = a - asan_heap_base; sh = &asan_shadow[off >> 3]; k = off & (ASAN_SHADOW_SCALE - 1); - n = ASAN_MIN(ASAN_SHADOW_SCALE - k, end - a); + n = ASAN_MIN((rt_size_t)(ASAN_SHADOW_SCALE - k), end - a); - if (n == ASAN_SHADOW_SCALE) + if (k + n == ASAN_SHADOW_SCALE) { *sh = 0; /* whole granule addressable */ } else { - *sh = (rt_uint8_t)n; /* first n bytes addressable */ + *sh = (rt_uint8_t)(k + n); /* addressable prefix through this range */ } a += n; @@ -321,7 +327,7 @@ static void asan_poison_range(rt_uintptr_t addr, rt_size_t size) off = a - asan_heap_base; sh = &asan_shadow[off >> 3]; k = off & (ASAN_SHADOW_SCALE - 1); - n = ASAN_MIN(ASAN_SHADOW_SCALE - k, end - a); + n = ASAN_MIN((rt_size_t)(ASAN_SHADOW_SCALE - k), end - a); if (n == ASAN_SHADOW_SCALE) { @@ -337,7 +343,7 @@ static void asan_poison_range(rt_uintptr_t addr, rt_size_t size) } /* ---- allocation tracking ---- */ -static void asan_track_add(rt_uintptr_t ptr, rt_size_t size) +static void asan_track_add(rt_uintptr_t ptr, rt_size_t size, rt_thread_t self) { rt_uint32_t i; @@ -359,13 +365,17 @@ static void asan_track_add(rt_uintptr_t ptr, rt_size_t size) asan_tracks[i].ptr = ptr; asan_tracks[i].size = size; asan_tracks[i].used = 1; - if (rt_thread_self()) { - rt_strncpy(asan_tracks[i].owner, rt_thread_self()->parent.name, RT_NAME_MAX - 1); - } - else - { - rt_memset(asan_tracks[i].owner, 0, RT_NAME_MAX); + rt_size_t n = 0; + if (self) + { + while (n < RT_NAME_MAX - 1 && self->parent.name[n]) + { + asan_tracks[i].owner[n] = self->parent.name[n]; + n++; + } + } + asan_tracks[i].owner[n] = '\0'; } return; } @@ -387,140 +397,176 @@ static rt_uint32_t asan_track_find(rt_uintptr_t ptr) return RT_ASAN_TRACK_MAX; /* not found */ } -static void asan_malloc_hook(void **ptr, rt_size_t size) +/* The user pointer is aligned independently of the underlying heap's alignment. + * Keep allocation metadata in-band so tracking-table exhaustion is harmless. + */ +#define ASAN_ALIGNMENT ((RT_ALIGN_SIZE > ASAN_SHADOW_SCALE) ? RT_ALIGN_SIZE : ASAN_SHADOW_SCALE) +#define ASAN_REDZONE ASAN_SHADOW_SCALE +#ifdef RT_USING_SLAB_AS_HEAP +#define ASAN_ALLOC_ALIGNMENT RT_MM_PAGE_SIZE +#else +#define ASAN_ALLOC_ALIGNMENT ASAN_ALIGNMENT +#endif +struct asan_header +{ + void *raw; + rt_size_t size; + rt_size_t capacity; +}; + +void *rt_asan_malloc(rt_size_t size, void *(*alloc)(rt_size_t)) +{ + return rt_asan_malloc_align(size, ASAN_ALIGNMENT, alloc); +} + +void *rt_asan_malloc_align(rt_size_t size, rt_size_t align, void *(*alloc)(rt_size_t)) { + rt_size_t overhead; + struct asan_header *header; rt_uintptr_t p; - rt_size_t aligned; + void *raw; + rt_size_t capacity; + rt_base_t level; + rt_thread_t self; - if (!*ptr) + if (!size || !align || (align & (align - 1))) { - return; + return RT_NULL; } - - p = (rt_uintptr_t)*ptr; - aligned = RT_ALIGN(size, ASAN_SHADOW_SCALE); - - /* address reuse: this block was freed before, clear the stale record */ - if (asan_last_freed.used && asan_last_freed.ptr == p) + if (align < ASAN_ALIGNMENT) { - asan_last_freed.used = 0; + align = ASAN_ALIGNMENT; } - - asan_track_add(p, size); + /* A power-of-two alignment is at most half the address space, so this + * addition cannot wrap. Reserve room for both allocator rounding steps. + */ + overhead = sizeof(struct asan_header) + align - 1 + ASAN_REDZONE; + if (size > (rt_size_t)-1 - overhead - (ASAN_ALIGNMENT - 1) - (ASAN_ALLOC_ALIGNMENT - 1)) + { + return RT_NULL; + } + capacity = RT_ALIGN(size, ASAN_ALIGNMENT); + raw = alloc(capacity + overhead); + if (!raw) + { + return RT_NULL; + } + p = RT_ALIGN((rt_uintptr_t)raw + sizeof(*header), align); + header = (struct asan_header *)p - 1; + header->raw = raw; + header->size = size; + header->capacity = capacity; + self = rt_thread_self(); + + level = asan_lock(); + asan_unpoison_range((rt_uintptr_t)raw, p + capacity + ASAN_REDZONE - (rt_uintptr_t)raw); + asan_poison_range(p - ASAN_REDZONE, ASAN_REDZONE); asan_unpoison_range(p, size); - if (aligned > size) + asan_poison_range(p + size, capacity + ASAN_REDZONE - size); + if (asan_last_freed.used && asan_last_freed.ptr == p) { - asan_poison_range(p + size, aligned - size); + asan_last_freed.used = 0; } + asan_track_add(p, size, self); + asan_unlock(level); + return (void *)p; } -static void asan_free_hook(void **ptr) +void rt_asan_free(void *ptr, void (*release)(void *)) { - rt_uintptr_t p; + struct asan_header *header; + rt_uintptr_t p = (rt_uintptr_t)ptr; + rt_size_t capacity; + void *raw; rt_uint32_t idx; + rt_base_t level; - if (!*ptr) + if (!ptr) { return; } - - p = (rt_uintptr_t)*ptr; + header = (struct asan_header *)ptr - 1; + raw = header->raw; + capacity = header->capacity; + level = asan_lock(); idx = asan_track_find(p); - if (idx == RT_ASAN_TRACK_MAX) + if (idx != RT_ASAN_TRACK_MAX) { - return; /* unknown block, skip */ + asan_last_freed = asan_tracks[idx]; + asan_tracks[idx].used = 0; } - -#if RT_ASAN_HAS_UAF_DETECTION + else { - rt_size_t aligned = RT_ALIGN(asan_tracks[idx].size, ASAN_SHADOW_SCALE); - asan_poison_range(p, aligned); /* poison whole block -> use-after-free */ + asan_last_freed.ptr = p; + asan_last_freed.size = header->size; + asan_last_freed.used = 1; + asan_last_freed.owner[0] = '\0'; } + /* Allocators may reuse any part of the raw block for their metadata. */ + asan_unpoison_range((rt_uintptr_t)raw, p + capacity + ASAN_REDZONE - (rt_uintptr_t)raw); + asan_unlock(level); + release(raw); +#if RT_ASAN_HAS_UAF_DETECTION + /* The heap lock still prevents reuse while the shadow is updated. */ + level = asan_lock(); + asan_poison_range(p, capacity + ASAN_REDZONE); + asan_unlock(level); #endif - - /* remember it for use-after-free diagnosis */ - asan_last_freed = asan_tracks[idx]; - asan_last_freed.used = 1; - - asan_tracks[idx].used = 0; -} - -/* rt_realloc frees/moves the old block and allocates a new one without going - * through rt_free/rt_malloc, so its hooks must be handled separately. */ -static rt_uintptr_t asan_realloc_old_ptr; - -static void asan_realloc_entry_hook(void **ptr, rt_size_t size) -{ - RT_UNUSED(size); - asan_realloc_old_ptr = (rt_uintptr_t)*ptr; } -static void asan_realloc_exit_hook(void **ptr, rt_size_t size) +void *rt_asan_realloc(void *ptr, rt_size_t size, + void *(*alloc)(rt_size_t), void (*release)(void *)) { - rt_uintptr_t p; - rt_size_t aligned; - rt_uint32_t idx; + struct asan_header *header; + void *result; + rt_base_t level; + rt_thread_t self; - if (!*ptr) + if (!ptr) { - return; + return rt_asan_malloc(size, alloc); } - - p = (rt_uintptr_t)*ptr; - aligned = RT_ALIGN(size, ASAN_SHADOW_SCALE); - - /* when realloc moves the block, poison the old block so that a stale - * pointer to it is still detected as use-after-free */ - if (asan_realloc_old_ptr && asan_realloc_old_ptr != p) + if (!size) { - idx = asan_track_find(asan_realloc_old_ptr); - if (idx != RT_ASAN_TRACK_MAX) - { -#if RT_ASAN_HAS_UAF_DETECTION - rt_size_t old_aligned = RT_ALIGN(asan_tracks[idx].size, ASAN_SHADOW_SCALE); - - asan_poison_range(asan_realloc_old_ptr, old_aligned); -#endif - asan_tracks[idx].used = 0; - } + rt_asan_free(ptr, release); + return RT_NULL; } - - /* address may have been reused internally by the allocator, drop any - * stale use-after-free record for it */ - if (asan_last_freed.used && asan_last_freed.ptr == p) + header = (struct asan_header *)ptr - 1; + if (size <= header->capacity) { - asan_last_freed.used = 0; + self = rt_thread_self(); + level = asan_lock(); + header->size = size; + asan_unpoison_range((rt_uintptr_t)ptr, size); + asan_poison_range((rt_uintptr_t)ptr + size, header->capacity + ASAN_REDZONE - size); + asan_track_add((rt_uintptr_t)ptr, size, self); + asan_unlock(level); + return ptr; } - - /* track and unpoison the new block, poison its tail redzone */ - asan_track_add(p, size); - asan_unpoison_range(p, size); - if (aligned > size) + result = rt_asan_malloc(size, alloc); + if (result) { - asan_poison_range(p + size, aligned - size); + /* Copy only user bytes, never allocator padding or a redzone. */ + rt_memcpy(result, ptr, header->size); + rt_asan_free(ptr, release); } + return result; } /* * Override the weak rt_system_heap_init to capture the heap range and - * install the allocator hooks before the generic heap init runs. + * initialize the shadow before the generic heap init runs. */ void rt_system_heap_init(void *begin_addr, void *end_addr) { rt_uintptr_t begin = (rt_uintptr_t)begin_addr; rt_uintptr_t end = (rt_uintptr_t)end_addr; - /* - * The shadow maps one byte per ASAN_SHADOW_SCALE (8) bytes. Heap blocks - * are RT_ALIGN_SIZE (8) aligned, so align the shadow base to the same - * granularity to keep every block boundary on a shadow byte boundary. - * Otherwise (e.g. __bss_end is only 4-aligned) the partial-granule - * state cannot represent an addressable region and false positives occur - * right at block start. - */ + /* User pointers are explicitly aligned to shadow granules. */ + RT_ASSERT(end > begin && end - begin >= ASAN_SHADOW_SCALE); asan_heap_base = RT_ALIGN(begin, ASAN_SHADOW_SCALE); - asan_heap_limit = ASAN_MIN(end, asan_heap_base + - (rt_uintptr_t)sizeof(asan_shadow) * ASAN_SHADOW_SCALE); + asan_heap_limit = asan_heap_base + ASAN_MIN(end - asan_heap_base, + (rt_uintptr_t)sizeof(asan_shadow) * ASAN_SHADOW_SCALE); /* * Start with everything addressable: the heap allocators store their own @@ -531,12 +577,6 @@ void rt_system_heap_init(void *begin_addr, void *end_addr) */ rt_memset(asan_shadow, 0, sizeof(asan_shadow)); - /* install allocator hooks */ - rt_malloc_sethook(asan_malloc_hook); - rt_free_sethook(asan_free_hook); - rt_realloc_set_entry_hook(asan_realloc_entry_hook); - rt_realloc_set_exit_hook(asan_realloc_exit_hook); - /* run the original heap init */ rt_system_heap_init_generic(begin_addr, end_addr); } @@ -548,37 +588,40 @@ static int asan_info(int argc, char **argv) { rt_uint32_t i; rt_uint32_t active = 0; + struct asan_track block; + rt_base_t level; + RT_UNUSED(argc); + RT_UNUSED(argv); rt_kprintf("\n-- AddressSanitizer status --\n"); - rt_kprintf("shadow : %p, %d bytes\n", asan_shadow, sizeof(asan_shadow)); - rt_kprintf("coverage : 0x%08x - 0x%08x (%d bytes)\n", - asan_heap_base, asan_heap_limit, - asan_heap_limit - asan_heap_base); - - if (asan_last_freed.used) + rt_kprintf("shadow : %p, %lu bytes\n", asan_shadow, (unsigned long)sizeof(asan_shadow)); + rt_kprintf("coverage : %p - %p\n", (void *)asan_heap_base, (void *)asan_heap_limit); + level = asan_lock(); + block = asan_last_freed; + asan_unlock(level); + if (block.used) { - rt_kprintf("last free: 0x%08x size %d owner %.*s\n", - asan_last_freed.ptr, asan_last_freed.size, - RT_NAME_MAX, asan_last_freed.owner); + rt_kprintf("last free: %p size %lu owner %.*s\n", (void *)block.ptr, + (unsigned long)block.size, RT_NAME_MAX, block.owner); } else { rt_kprintf("last free: (none)\n"); } - - rt_kprintf("\n-- active allocations --\n"); + rt_kprintf("\n-- active allocations (snapshot per entry) --\n"); for (i = 0; i < RT_ASAN_TRACK_MAX; i++) { - if (asan_tracks[i].used) + level = asan_lock(); + block = asan_tracks[i]; + asan_unlock(level); + if (block.used) { active++; - rt_kprintf(" 0x%08x %6d %.*s\n", - asan_tracks[i].ptr, asan_tracks[i].size, - RT_NAME_MAX, asan_tracks[i].owner); + rt_kprintf(" %p %6lu %.*s\n", (void *)block.ptr, + (unsigned long)block.size, RT_NAME_MAX, block.owner); } } - rt_kprintf("total: %d active blocks\n", active); - + rt_kprintf("total: %u active blocks\n", active); return 0; } MSH_CMD_EXPORT(asan_info, dump AddressSanitizer status); diff --git a/components/utilities/asan/asan.h b/components/utilities/asan/asan.h index 5c9b5586669e..e0e70902fe34 100644 --- a/components/utilities/asan/asan.h +++ b/components/utilities/asan/asan.h @@ -29,6 +29,15 @@ extern "C" { #endif +/* Internal system-heap integration. The caller must hold the heap lock. + * Callbacks operate on the underlying allocator, never rt_malloc/rt_free. + */ +void *rt_asan_malloc(rt_size_t size, void *(*alloc)(rt_size_t)); +void *rt_asan_malloc_align(rt_size_t size, rt_size_t align, void *(*alloc)(rt_size_t)); +void rt_asan_free(void *ptr, void (*release)(void *)); +void *rt_asan_realloc(void *ptr, rt_size_t size, + void *(*alloc)(rt_size_t), void (*release)(void *)); + /** * @brief Get the total number of AddressSanitizer violations reported. * diff --git a/examples/test/asan_cpp_test.cpp b/examples/test/asan_cpp_test.cpp new file mode 100644 index 000000000000..e4925eb58505 --- /dev/null +++ b/examples/test/asan_cpp_test.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "asan.h" +#include "utest.h" + +extern "C" void test_asan_cpp(void) +{ + volatile char *p = static_cast(rt_malloc(16)); + uassert_not_null(p); + if (!p) + { + return; + } + rt_uint32_t before = rt_asan_report_count_get(); + p[15] = 1; + uassert_int_equal(rt_asan_report_count_get(), before); + p[16] = 2; + uassert_true(rt_asan_report_count_get() > before); + rt_free(const_cast(p)); +} diff --git a/examples/test/mem_align_test.c b/examples/test/mem_align_test.c new file mode 100644 index 000000000000..9dd84aebb65d --- /dev/null +++ b/examples/test/mem_align_test.c @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "utest.h" + +/* These tests also run without ASan: size arithmetic must be safe in both modes. */ +static void test_align_valid(void) +{ + static const rt_size_t sizes[] = { 1, 13, 16, 31, 32 }; + static const rt_size_t aligns[] = { 1, 2, sizeof(void *), 16, 64, 256 }; + rt_size_t i; + rt_size_t j; + + for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) + { + for (j = 0; j < sizeof(aligns) / sizeof(aligns[0]); j++) + { + char *p = (char *)rt_malloc_align(sizes[i], aligns[j]); + uassert_not_null(p); + if (!p) + { + return; + } + uassert_true(((rt_uintptr_t)p & (aligns[j] - 1)) == 0); + rt_memset(p, 0x5a, sizes[i]); + uassert_int_equal(p[0], 0x5a); + uassert_int_equal(p[sizes[i] - 1], 0x5a); + rt_free_align(p); + } + } + rt_free_align(RT_NULL); +} + +static void test_align_invalid(void) +{ + static const rt_size_t aligns[] = { 0, 3, 6, 12, (rt_size_t)-1 }; + rt_size_t i; + void *p; + + for (i = 0; i < sizeof(aligns) / sizeof(aligns[0]); i++) + { + p = rt_malloc_align(16, aligns[i]); + uassert_null(p); + rt_free_align(p); + } + p = rt_malloc_align(0, 64); + uassert_null(p); + rt_free_align(p); +} + +static void test_align_overflow(void) +{ + static const rt_size_t requests[][2] = { + { (rt_size_t)-1, 64 }, + { (rt_size_t)-3, 64 }, + { (rt_size_t)-64, 64 }, + { (rt_size_t)-256, 256 }, + { (rt_size_t)-1 - sizeof(void *), 1 }, + { (rt_size_t)-1 - RT_ALIGN_SIZE, 1 }, + { (rt_size_t)-1 / 2 + 1, (rt_size_t)-1 / 2 + 1 }, + }; + rt_size_t i; + + for (i = 0; i < sizeof(requests) / sizeof(requests[0]); i++) + { + void *p = rt_malloc_align(requests[i][0], requests[i][1]); + uassert_null(p); + rt_free_align(p); + } +} + +static void testcase(void) +{ + rt_size_t failures = 0; + + /* Each unit resets the framework counters, so retain earlier failures. */ + UTEST_UNIT_RUN(test_align_valid); + failures += utest_handle_get()->failed_num; + UTEST_UNIT_RUN(test_align_invalid); + failures += utest_handle_get()->failed_num; + UTEST_UNIT_RUN(test_align_overflow); + failures += utest_handle_get()->failed_num; + uassert_int_equal(failures, 0); +} + +UTEST_TC_EXPORT(testcase, "core.mem_align", RT_NULL, RT_NULL, 1000); diff --git a/src/kservice.c b/src/kservice.c index db321552616f..09f69d5eda1d 100644 --- a/src/kservice.c +++ b/src/kservice.c @@ -32,6 +32,9 @@ */ #include +#ifdef RT_USING_ASAN +#include +#endif /* include rt_hw_backtrace macro defined in cpuport.h */ #define RT_HW_INCLUDE_CPUPORT @@ -1109,6 +1112,18 @@ rt_inline void _slab_info(rt_size_t *total, #define _MEM_INFO(...) #endif +#ifdef RT_USING_ASAN +static void *_asan_heap_alloc(rt_size_t size) +{ + return _MEM_MALLOC(size); +} + +static void _asan_heap_free(void *ptr) +{ + _MEM_FREE(ptr); +} +#endif + /** * @brief This function will do the generic system heap initialization. * @@ -1157,7 +1172,11 @@ rt_weak void *rt_malloc(rt_size_t size) /* Enter critical zone */ level = _heap_lock(); /* allocate memory block from system heap */ +#ifdef RT_USING_ASAN + ptr = rt_asan_malloc(size, _asan_heap_alloc); +#else ptr = _MEM_MALLOC(size); +#endif /* Exit critical zone */ _heap_unlock(level); /* call 'rt_malloc' hook */ @@ -1185,7 +1204,11 @@ rt_weak void *rt_realloc(void *ptr, rt_size_t newsize) /* Enter critical zone */ level = _heap_lock(); /* Change the size of previously allocated memory block */ +#ifdef RT_USING_ASAN + nptr = rt_asan_realloc(ptr, newsize, _asan_heap_alloc, _asan_heap_free); +#else nptr = _MEM_REALLOC(ptr, newsize); +#endif /* Exit critical zone */ _heap_unlock(level); /* Exit hook */ @@ -1211,6 +1234,11 @@ rt_weak void *rt_calloc(rt_size_t count, rt_size_t size) { void *p; + if (size && count > (rt_size_t)-1 / size) + { + return RT_NULL; + } + /* allocate 'count' objects of size 'size' */ p = rt_malloc(count * size); /* zero the memory */ @@ -1238,7 +1266,11 @@ rt_weak void rt_free(void *ptr) if (ptr == RT_NULL) return; /* Enter critical zone */ level = _heap_lock(); +#ifdef RT_USING_ASAN + rt_asan_free(ptr, _asan_heap_free); +#else _MEM_FREE(ptr); +#endif /* Exit critical zone */ _heap_unlock(level); } @@ -1302,47 +1334,73 @@ void rt_page_free(void *addr, rt_size_t npages) * * @param size is the allocated memory block size. * - * @param align is the alignment size. + * @param align is a nonzero power-of-two alignment size. + * + * @note Zero-sized requests, invalid alignments and size overflows return RT_NULL. * * @return The memory block address was returned successfully, otherwise it was * returned empty RT_NULL. */ rt_weak void *rt_malloc_align(rt_size_t size, rt_size_t align) { - void *ptr = RT_NULL; - void *align_ptr = RT_NULL; - int uintptr_size = 0; - rt_size_t align_size = 0; - - /* sizeof pointer */ - uintptr_size = sizeof(void*); - uintptr_size -= 1; + void *ptr; +#ifdef RT_USING_ASAN + rt_base_t level; +#else + void *align_ptr; + const rt_size_t uintptr_mask = sizeof(void *) - 1; + rt_size_t align_size; +#endif - /* align the alignment size to uintptr size byte */ - align = ((align + uintptr_size) & ~uintptr_size); + if (!size || !align || (align & (align - 1))) + { + return RT_NULL; + } + if (align < sizeof(void *)) + { + align = sizeof(void *); + } - /* get total aligned size */ - align_size = ((size + uintptr_size) & ~uintptr_size) + align; - /* allocate memory block from heap */ +#ifdef RT_USING_ASAN + /* Keep the requested size rather than tracking an oversized backing block. */ + level = _heap_lock(); + ptr = rt_asan_malloc_align(size, align, _asan_heap_alloc); + _heap_unlock(level); + RT_OBJECT_HOOK_CALL(rt_malloc_hook, (&ptr, size)); +#else + if (size > (rt_size_t)-1 - uintptr_mask) + { + return RT_NULL; + } + align_size = RT_ALIGN(size, sizeof(void *)); + if (align_size > (rt_size_t)-1 - align) + { + return RT_NULL; + } + align_size += align; +#ifdef RT_USING_SLAB_AS_HEAP + if (align_size > (rt_size_t)-1 - (RT_MM_PAGE_SIZE - 1)) +#else + if (align_size > (rt_size_t)-1 - (RT_ALIGN_SIZE - 1)) +#endif + { + return RT_NULL; + } ptr = rt_malloc(align_size); if (ptr != RT_NULL) { - /* the allocated memory block is aligned */ if (((rt_uintptr_t)ptr & (align - 1)) == 0) { align_ptr = (void *)((rt_uintptr_t)ptr + align); } else { - align_ptr = (void *)(((rt_uintptr_t)ptr + (align - 1)) & ~(align - 1)); + align_ptr = (void *)RT_ALIGN((rt_uintptr_t)ptr, align); } - - /* set the pointer before alignment pointer to the real pointer */ - *((rt_uintptr_t *)((rt_uintptr_t)align_ptr - sizeof(void *))) = (rt_uintptr_t)ptr; - + *((rt_uintptr_t *)align_ptr - 1) = (rt_uintptr_t)ptr; ptr = align_ptr; } - +#endif return ptr; } RTM_EXPORT(rt_malloc_align); @@ -1355,12 +1413,20 @@ RTM_EXPORT(rt_malloc_align); */ rt_weak void rt_free_align(void *ptr) { - void *real_ptr = RT_NULL; +#ifndef RT_USING_ASAN + void *real_ptr; +#endif - /* NULL check */ if (ptr == RT_NULL) return; - real_ptr = (void *) * (rt_uintptr_t *)((rt_uintptr_t)ptr - sizeof(void *)); +#ifdef RT_USING_ASAN + /* The ASan header is read by the non-instrumented runtime under the heap + * lock. The old pointer-before-buffer layout is not used in this mode. + */ + rt_free(ptr); +#else + real_ptr = (void *)*((rt_uintptr_t *)ptr - 1); rt_free(real_ptr); +#endif } RTM_EXPORT(rt_free_align); #endif /* RT_USING_HEAP */ diff --git a/src/utest/Kconfig b/src/utest/Kconfig index 0a566a5769f4..dba4d70a872a 100644 --- a/src/utest/Kconfig +++ b/src/utest/Kconfig @@ -84,6 +84,11 @@ menu "Kernel Core" default n depends on RT_USING_MEMPOOL + config RT_UTEST_MEM_ALIGN + bool "Aligned Memory Allocation Test" + default n + depends on RT_USING_HEAP + config RT_UTEST_ASAN bool "AddressSanitizer Test" default n diff --git a/src/utest/SConscript b/src/utest/SConscript index 7768e6e6df72..03d32cd6b9d4 100644 --- a/src/utest/SConscript +++ b/src/utest/SConscript @@ -58,8 +58,13 @@ if GetDepend(['RT_UTEST_MTSAFE_KPRINT']): if GetDepend(['RT_UTEST_MEMPOOL']): src += ['mempool_tc.c'] +if GetDepend(['RT_UTEST_MEM_ALIGN']): + src += [os.path.join(cwd, '../../examples/test/mem_align_test.c')] + if GetDepend(['RT_UTEST_ASAN']): src += ['asan_tc.c'] + if GetDepend(['RT_USING_CPLUSPLUS']): + src += [os.path.join(cwd, '../../examples/test/asan_cpp_test.cpp')] # Stressful testcase for scheduler (MP/UP) if GetDepend(['RT_UTEST_SCHEDULER']): diff --git a/src/utest/asan_tc.c b/src/utest/asan_tc.c index 14cf9171240c..f3d12a30492d 100644 --- a/src/utest/asan_tc.c +++ b/src/utest/asan_tc.c @@ -20,7 +20,7 @@ * * Test Scenarios: * - **Scenario 1 (Heap Overflow Write / test_asan_overflow_write):** - * 1. Allocate a 10-byte block (redzone occupies [10, 16)) + * 1. Allocate a 10-byte block (redzone includes [10, 16)) * 2. Write at offset 12 which falls into the poisoned redzone * 3. Assert the ASan report counter increased * - **Scenario 2 (Heap Overflow Read / test_asan_overflow_read):** @@ -32,7 +32,7 @@ * 2. Write to in-bounds offsets 0 and 9 * 3. Assert the ASan report counter did not change * - **Scenario 4 (Realloc Overflow / test_asan_realloc_overflow):** - * 1. Allocate 10 bytes and realloc to 20 bytes (redzone occupies [20, 24)) + * 1. Allocate 10 bytes and realloc to 20 bytes (redzone includes [20, 24)) * 2. Write at offset 22 inside the new poisoned redzone * 3. Assert the ASan report counter increased * - **Scenario 5 (Use-After-Free Read / test_asan_uaf_read):** @@ -76,7 +76,7 @@ static void test_asan_overflow_write(void) { rt_uint32_t before; rt_uint32_t after; - char *p; + volatile char *p; p = (char *)rt_malloc(10); uassert_not_null(p); @@ -89,7 +89,7 @@ static void test_asan_overflow_write(void) p[12] = 0x41; /* heap-buffer-overflow write (redzone [10, 16)) */ after = rt_asan_report_count_get(); - rt_free(p); + rt_free((void *)p); uassert_true(after > before); } @@ -99,7 +99,7 @@ static void test_asan_overflow_read(void) rt_uint32_t before; rt_uint32_t after; volatile char v; - char *p; + volatile char *p; p = (char *)rt_malloc(10); uassert_not_null(p); @@ -113,7 +113,7 @@ static void test_asan_overflow_read(void) after = rt_asan_report_count_get(); (void)v; - rt_free(p); + rt_free((void *)p); uassert_true(after > before); } @@ -122,7 +122,7 @@ static void test_asan_no_false_positive(void) { rt_uint32_t before; rt_uint32_t after; - char *p; + volatile char *p; p = (char *)rt_malloc(10); uassert_not_null(p); @@ -136,7 +136,7 @@ static void test_asan_no_false_positive(void) p[9] = 0x02; /* last in-bounds byte */ after = rt_asan_report_count_get(); - rt_free(p); + rt_free((void *)p); uassert_int_equal(after, before); } @@ -145,8 +145,8 @@ static void test_asan_realloc_overflow(void) { rt_uint32_t before; rt_uint32_t after; - char *p; - char *q; + volatile char *p; + volatile char *q; p = (char *)rt_malloc(10); uassert_not_null(p); @@ -155,11 +155,11 @@ static void test_asan_realloc_overflow(void) return; } - q = (char *)rt_realloc(p, 20); + q = (char *)rt_realloc((void *)p, 20); uassert_not_null(q); if (!q) { - rt_free(p); + rt_free((void *)p); return; } @@ -167,7 +167,7 @@ static void test_asan_realloc_overflow(void) q[22] = 0x41; /* heap-buffer-overflow write (redzone [20, 24)) */ after = rt_asan_report_count_get(); - rt_free(q); + rt_free((void *)q); uassert_true(after > before); } @@ -178,7 +178,7 @@ static void test_asan_uaf_read(void) rt_uint32_t before; rt_uint32_t after; volatile char v; - char *p; + volatile char *p; p = (char *)rt_malloc(10); uassert_not_null(p); @@ -187,7 +187,7 @@ static void test_asan_uaf_read(void) return; } - rt_free(p); + rt_free((void *)p); before = rt_asan_report_count_get(); v = p[0]; /* use-after-free read */ @@ -202,7 +202,7 @@ static void test_asan_uaf_write(void) { rt_uint32_t before; rt_uint32_t after; - char *p; + volatile char *p; p = (char *)rt_malloc(10); uassert_not_null(p); @@ -211,7 +211,7 @@ static void test_asan_uaf_write(void) return; } - rt_free(p); + rt_free((void *)p); before = rt_asan_report_count_get(); p[0] = 0x41; /* use-after-free write */ @@ -221,16 +221,295 @@ static void test_asan_uaf_write(void) } #endif /* RT_ASAN_HAS_UAF_DETECTION */ +/* Aligned requests must have a redzone too; exercise every partial granule. */ +static void test_asan_boundaries(void) +{ + rt_size_t size; + for (size = 1; size <= 32; size++) + { + volatile char *p = (char *)rt_malloc(size); + rt_uint32_t before; + volatile char value; + uassert_not_null(p); + if (!p) + { + return; + } + uassert_true(((rt_uintptr_t)p & (RT_ALIGN_SIZE - 1)) == 0); + before = rt_asan_report_count_get(); + p[0] = 1; + p[size - 1] = 2; + uassert_int_equal(rt_asan_report_count_get(), before); + value = p[size]; + RT_UNUSED(value); + uassert_true(rt_asan_report_count_get() > before); + before = rt_asan_report_count_get(); + p[size] = 3; + uassert_true(rt_asan_report_count_get() > before); + rt_free((void *)p); + } +} + +static void test_asan_aligned_boundaries(void) +{ + static const rt_size_t sizes[] = { 1, 8, 13, 16, 31, 32 }; + static const rt_size_t aligns[] = { sizeof(void *), 16, 64, 256 }; + rt_size_t i; + rt_size_t j; + + for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) + { + for (j = 0; j < sizeof(aligns) / sizeof(aligns[0]); j++) + { + rt_uint32_t before = rt_asan_report_count_get(); + volatile char *p = (char *)rt_malloc_align(sizes[i], aligns[j]); + volatile char value; + uassert_not_null(p); + if (!p) + { + return; + } + uassert_true(((rt_uintptr_t)p & (aligns[j] - 1)) == 0); + p[0] = 1; + p[sizes[i] - 1] = 2; + uassert_int_equal(rt_asan_report_count_get(), before); + value = p[sizes[i]]; + uassert_int_equal(rt_asan_report_count_get(), before + 1); + p[sizes[i]] = 3; + uassert_int_equal(rt_asan_report_count_get(), before + 2); + /* Read the left redzone without corrupting the allocation header. */ + value = p[-1]; + RT_UNUSED(value); + uassert_int_equal(rt_asan_report_count_get(), before + 3); + before = rt_asan_report_count_get(); + rt_free_align((void *)p); + uassert_int_equal(rt_asan_report_count_get(), before); + } + } +} + +#if RT_ASAN_HAS_UAF_DETECTION +static void test_asan_aligned_uaf(void) +{ + volatile char *p = (char *)rt_malloc_align(13, 64); + volatile char value; + rt_uint32_t before; + + uassert_not_null(p); + if (!p) + { + return; + } + rt_free_align((void *)p); + before = rt_asan_report_count_get(); + value = p[0]; + RT_UNUSED(value); + uassert_int_equal(rt_asan_report_count_get(), before + 1); + p[12] = 1; + uassert_int_equal(rt_asan_report_count_get(), before + 2); +} +#endif + +static void test_asan_realloc_lifecycle(void) +{ + volatile char *p = (char *)rt_realloc(RT_NULL, 13); + volatile char *q; + rt_uint32_t before = rt_asan_report_count_get(); + rt_size_t i; + + uassert_not_null(p); + if (!p) + { + return; + } + for (i = 0; i < 13; i++) + { + p[i] = (char)i; + } + q = (char *)rt_realloc((void *)p, 64); + uassert_not_null(q); + if (!q) + { + rt_free((void *)p); + return; + } + for (i = 0; i < 13; i++) + { + uassert_int_equal(q[i], (char)i); + } + uassert_int_equal(rt_asan_report_count_get(), before); + p = (char *)rt_realloc((void *)q, 16); + uassert_true(p == q); + before = rt_asan_report_count_get(); + p[16] = 1; + uassert_true(rt_asan_report_count_get() > before); + before = rt_asan_report_count_get(); + p = (char *)rt_realloc((void *)q, 32); + uassert_true(p == q); + p[31] = 7; + uassert_null(rt_realloc((void *)p, (rt_size_t)-1)); + uassert_int_equal(p[31], 7); + uassert_int_equal(rt_asan_report_count_get(), before); + uassert_null(rt_realloc((void *)p, 0)); +#if RT_ASAN_HAS_UAF_DETECTION + before = rt_asan_report_count_get(); + { + volatile char value = p[0]; + RT_UNUSED(value); + } + uassert_true(rt_asan_report_count_get() > before); +#endif + uassert_null(rt_malloc((rt_size_t)-1)); + uassert_null(rt_malloc((rt_size_t)-16)); + uassert_null(rt_malloc((rt_size_t)-64)); + uassert_null(rt_calloc((rt_size_t)-1 / 2 + 1, 2)); + uassert_null(rt_realloc(RT_NULL, 0)); + rt_free(RT_NULL); +} + +/* Run with RT_ASAN_TRACK_MAX=1 as well: correctness must not need a free slot. */ +static void test_asan_calloc_reuse(void) +{ + rt_size_t i; + rt_uint32_t before = rt_asan_report_count_get(); + for (i = 0; i < 64; i++) + { + char *p = (char *)rt_calloc(3, 5); + uassert_not_null(p); + if (!p) + { + return; + } + uassert_int_equal(p[0], 0); + uassert_int_equal(p[14], 0); + rt_free(p); + } + uassert_int_equal(rt_asan_report_count_get(), before); +} + +#ifdef RT_USING_SEMAPHORE +static struct rt_semaphore asan_done; +static rt_uint32_t asan_worker_errors[2]; + +static void asan_worker(void *parameter) +{ + rt_size_t id = (rt_size_t)parameter; + rt_size_t i; + for (i = 0; i < 200; i++) + { + rt_size_t size = i % 63 + 1; + char *p = (char *)rt_malloc(size); + char *q; + if (!p) + { + asan_worker_errors[id]++; + break; + } + rt_memset(p, (int)(id + 1), size); + /* Let the creator start both workers even if it has lower priority. */ + rt_thread_mdelay(1); + q = (char *)rt_realloc(p, 96); + if (!q) + { + rt_free(p); + asan_worker_errors[id]++; + break; + } + if (q[0] != (char)(id + 1) || q[size - 1] != (char)(id + 1)) + { + asan_worker_errors[id]++; + } + rt_thread_yield(); + p = (char *)rt_realloc(q, 1); + if (p != q || p[0] != (char)(id + 1)) + { + asan_worker_errors[id]++; + } + rt_free(p); + p = (char *)rt_malloc_align(size, 64); + if (!p) + { + asan_worker_errors[id]++; + break; + } + rt_thread_yield(); + p[0] = (char)(id + 1); + p[size - 1] = (char)(id + 1); + rt_free_align(p); + } + rt_sem_release(&asan_done); +} + +static void test_asan_concurrent_realloc(void) +{ + rt_thread_t threads[2]; + rt_size_t i; + rt_size_t started = 0; + rt_uint32_t before = rt_asan_report_count_get(); + + rt_sem_init(&asan_done, "asan_done", 0, RT_IPC_FLAG_PRIO); + for (i = 0; i < 2; i++) + { + asan_worker_errors[i] = 0; + threads[i] = rt_thread_create("asan_work", asan_worker, (void *)i, 2048, + RT_THREAD_PRIORITY_MAX / 2, 1); + uassert_not_null(threads[i]); + if (threads[i]) + { +#ifdef RT_USING_SMP + rt_thread_control(threads[i], RT_THREAD_CTRL_BIND_CPU, (void *)(i % RT_CPUS_NR)); +#endif + rt_thread_startup(threads[i]); + started++; + } + } + for (i = 0; i < started; i++) + { + rt_sem_take(&asan_done, RT_WAITING_FOREVER); + } + rt_sem_detach(&asan_done); + uassert_int_equal(asan_worker_errors[0], 0); + uassert_int_equal(asan_worker_errors[1], 0); + uassert_int_equal(rt_asan_report_count_get(), before); +} +#endif + +#ifdef RT_USING_CPLUSPLUS +extern void test_asan_cpp(void); +#endif + +/* utest_unit_run resets its counters; retain failures from every unit. */ +#define ASAN_UNIT_RUN(unit) \ + do \ + { \ + UTEST_UNIT_RUN(unit); \ + failures += utest_handle_get()->failed_num; \ + } while (0) + static void testcase(void) { - UTEST_UNIT_RUN(test_asan_overflow_write); - UTEST_UNIT_RUN(test_asan_overflow_read); - UTEST_UNIT_RUN(test_asan_no_false_positive); - UTEST_UNIT_RUN(test_asan_realloc_overflow); + rt_size_t failures = 0; + ASAN_UNIT_RUN(test_asan_overflow_write); + ASAN_UNIT_RUN(test_asan_overflow_read); + ASAN_UNIT_RUN(test_asan_no_false_positive); + ASAN_UNIT_RUN(test_asan_realloc_overflow); + ASAN_UNIT_RUN(test_asan_boundaries); + ASAN_UNIT_RUN(test_asan_aligned_boundaries); + ASAN_UNIT_RUN(test_asan_realloc_lifecycle); + ASAN_UNIT_RUN(test_asan_calloc_reuse); +#ifdef RT_USING_SEMAPHORE + ASAN_UNIT_RUN(test_asan_concurrent_realloc); +#endif +#ifdef RT_USING_CPLUSPLUS + ASAN_UNIT_RUN(test_asan_cpp); +#endif #if RT_ASAN_HAS_UAF_DETECTION - UTEST_UNIT_RUN(test_asan_uaf_read); - UTEST_UNIT_RUN(test_asan_uaf_write); + ASAN_UNIT_RUN(test_asan_aligned_uaf); + ASAN_UNIT_RUN(test_asan_uaf_read); + ASAN_UNIT_RUN(test_asan_uaf_write); #endif + uassert_int_equal(failures, 0); } +#undef ASAN_UNIT_RUN UTEST_TC_EXPORT(testcase, "components.asan_tc", utest_tc_init, utest_tc_cleanup, 1000); diff --git a/tools/building.py b/tools/building.py index 44ea243d1ae7..ad2856d06b60 100644 --- a/tools/building.py +++ b/tools/building.py @@ -381,7 +381,8 @@ def PrepareBuilding(env, root_directory, has_libcpu=False, remove_components = [ # AddressSanitizer (kernel-address): instrument memory accesses. The # runtime is provided by components/utilities/asan and does not need libasan. if rtconfig.PLATFORM in ['gcc'] and 'RT_USING_ASAN' in BuildOptions: - env.Append(CFLAGS=' -fsanitize=kernel-address -fno-omit-frame-pointer') + env.Append(CFLAGS=' -fsanitize=kernel-address -fno-omit-frame-pointer', + CXXFLAGS=' -fsanitize=kernel-address -fno-omit-frame-pointer') env.Append(LINKFLAGS=' -fsanitize=kernel-address') attach_global_macros = GetOption('global-macros') From ebfd73cc1574fc04451e52776f137de7a4b0c13d Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Mon, 7 Sep 2026 22:24:26 +0800 Subject: [PATCH 09/11] [asan] move sanitizer flags into component SConscript --- components/utilities/asan/SConscript | 10 ++++++++++ tools/building.py | 7 ------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/components/utilities/asan/SConscript b/components/utilities/asan/SConscript index 11db7c4cbaff..34a69a03c32c 100644 --- a/components/utilities/asan/SConscript +++ b/components/utilities/asan/SConscript @@ -1,13 +1,23 @@ from building import * +Import('rtconfig') cwd = GetCurrentDir() src = Glob('*.c') CPPPATH = [cwd] +CFLAGS = '' +LINKFLAGS = '' + +# DefineGroup adds CFLAGS/CXXFLAGS/LINKFLAGS to the shared build environment. +# The runtime is provided here and does not need libasan. +if rtconfig.PLATFORM == 'gcc': + CFLAGS = ' -fsanitize=kernel-address -fno-omit-frame-pointer' + LINKFLAGS = ' -fsanitize=kernel-address' # The ASan runtime itself must not be instrumented, otherwise it would # recurse infinitely. '-fno-sanitize=kernel-address' is appended after the # global '-fsanitize=kernel-address' and therefore overrides it. group = DefineGroup('asan', src, depend=['RT_USING_ASAN'], CPPPATH=CPPPATH, + CFLAGS=CFLAGS, CXXFLAGS=CFLAGS, LINKFLAGS=LINKFLAGS, LOCAL_CFLAGS=' -fno-sanitize=kernel-address') Return('group') diff --git a/tools/building.py b/tools/building.py index ad2856d06b60..125da8521e76 100644 --- a/tools/building.py +++ b/tools/building.py @@ -378,13 +378,6 @@ def PrepareBuilding(env, root_directory, has_libcpu=False, remove_components = [ if rtconfig.PLATFORM in ['gcc'] and str(env['LINKFLAGS']).find('nano.specs') != -1: env.AppendUnique(CPPDEFINES = ['_REENT_SMALL']) - # AddressSanitizer (kernel-address): instrument memory accesses. The - # runtime is provided by components/utilities/asan and does not need libasan. - if rtconfig.PLATFORM in ['gcc'] and 'RT_USING_ASAN' in BuildOptions: - env.Append(CFLAGS=' -fsanitize=kernel-address -fno-omit-frame-pointer', - CXXFLAGS=' -fsanitize=kernel-address -fno-omit-frame-pointer') - env.Append(LINKFLAGS=' -fsanitize=kernel-address') - attach_global_macros = GetOption('global-macros') if attach_global_macros: attach_global_macros = attach_global_macros.split(',') From 17c7c9ef226b3d0baadc73d527ad359bff7659f8 Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Thu, 10 Sep 2026 09:32:51 +0800 Subject: [PATCH 10/11] [asan] override weak heap interfaces in component Move heap ownership, locking and hooks into the ASan adapter. Make hook setters, slab page APIs and test heap locks overridable, and remove ASan-specific branches from kservice. Require a cross-CPU heap lock for SMP and add hook, heap adapter and Kconfig regression coverage. Validated 11 QEMU configurations plus SMP with ISR heap locking, eight Kconfig combinations and negative regression checks. --- components/utilities/asan/Kconfig | 8 + components/utilities/asan/asan.c | 6 +- components/utilities/asan/asan.h | 9 + components/utilities/asan/asan_heap.c | 335 ++++++++++++++++++++++++++ src/kservice.c | 64 +---- src/utest/asan_tc.c | 105 ++++++++ tools/testcases/test_asan_config.py | 49 ++++ 7 files changed, 517 insertions(+), 59 deletions(-) create mode 100644 components/utilities/asan/asan_heap.c create mode 100644 tools/testcases/test_asan_config.py diff --git a/components/utilities/asan/Kconfig b/components/utilities/asan/Kconfig index 805ec7e089d3..5a59c2142417 100644 --- a/components/utilities/asan/Kconfig +++ b/components/utilities/asan/Kconfig @@ -3,6 +3,7 @@ menuconfig RT_USING_ASAN default n depends on RT_USING_HEAP && !RT_USING_USERHEAP depends on RT_USING_SMALL_MEM_AS_HEAP || RT_USING_MEMHEAP_AS_HEAP || RT_USING_SLAB_AS_HEAP + depends on !RT_USING_SMP || RT_USING_MUTEX || RT_USING_HEAP_ISR help Enable runtime AddressSanitizer (kernel-address) support. It instruments memory accesses to detect heap buffer overflow and @@ -22,6 +23,13 @@ menuconfig RT_USING_ASAN Only the system heap APIs are wrapped; direct allocator/page APIs and accesses in prebuilt, uninstrumented libraries are not checked. + The component overrides the weak system heap interfaces and owns + the underlying allocator, heap lock and allocation hooks. Do not + combine it with another implementation overriding the same APIs. + + SMP requires a mutex or the interrupt-safe heap spinlock to + serialize allocator access across CPUs. + Heap algorithm support: - small mem (RT_USING_SMALL_MEM_AS_HEAP): full support, detects both heap-buffer-overflow and use-after-free. diff --git a/components/utilities/asan/asan.c b/components/utilities/asan/asan.c index 42e38879829e..f13559a256b4 100644 --- a/components/utilities/asan/asan.c +++ b/components/utilities/asan/asan.c @@ -555,7 +555,7 @@ void *rt_asan_realloc(void *ptr, rt_size_t size, /* * Override the weak rt_system_heap_init to capture the heap range and - * initialize the shadow before the generic heap init runs. + * initialize the shadow before the component heap init runs. */ void rt_system_heap_init(void *begin_addr, void *end_addr) { @@ -577,8 +577,8 @@ void rt_system_heap_init(void *begin_addr, void *end_addr) */ rt_memset(asan_shadow, 0, sizeof(asan_shadow)); - /* run the original heap init */ - rt_system_heap_init_generic(begin_addr, end_addr); + /* Initialize the allocator owned by the ASan adapter. */ + rt_asan_heap_init(begin_addr, end_addr); } #ifdef RT_USING_FINSH diff --git a/components/utilities/asan/asan.h b/components/utilities/asan/asan.h index e0e70902fe34..44a5b49e0a55 100644 --- a/components/utilities/asan/asan.h +++ b/components/utilities/asan/asan.h @@ -29,6 +29,15 @@ extern "C" { #endif +#if defined(RT_UTEST_ASAN) && defined(RT_HOOK_USING_FUNC_PTR) +/* Test-only query: never replace hooks already installed by the application. + * Hook registration must remain quiescent while the hook unit test runs. + */ +rt_bool_t rt_asan_test_hooks_in_use(void); +#endif + +void rt_asan_heap_init(void *begin_addr, void *end_addr); + /* Internal system-heap integration. The caller must hold the heap lock. * Callbacks operate on the underlying allocator, never rt_malloc/rt_free. */ diff --git a/components/utilities/asan/asan_heap.c b/components/utilities/asan/asan_heap.c new file mode 100644 index 000000000000..800a9c9d0b4b --- /dev/null +++ b/components/utilities/asan/asan_heap.c @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * ASan system heap adapter. Override the default weak heap interfaces and + * keep the allocator, lock and hooks together. Raw allocations and shadow + * updates are serialized by the same lock, including realloc and page APIs. + */ + +#include +#include +#include "asan.h" + +#ifdef RT_USING_ASAN +#ifdef RT_USING_HOOK +static void (*rt_malloc_hook)(void **ptr, rt_size_t size); +static void (*rt_realloc_entry_hook)(void **ptr, rt_size_t size); +static void (*rt_realloc_exit_hook)(void **ptr, rt_size_t size); +static void (*rt_free_hook)(void **ptr); + +#if defined(RT_UTEST_ASAN) && defined(RT_HOOK_USING_FUNC_PTR) +rt_bool_t rt_asan_test_hooks_in_use(void) +{ + return rt_malloc_hook || rt_realloc_entry_hook || + rt_realloc_exit_hook || rt_free_hook; +} +#endif + +void rt_malloc_sethook(void (*hook)(void **ptr, rt_size_t size)) +{ + rt_malloc_hook = hook; +} + +void rt_realloc_set_entry_hook(void (*hook)(void **ptr, rt_size_t size)) +{ + rt_realloc_entry_hook = hook; +} + +void rt_realloc_set_exit_hook(void (*hook)(void **ptr, rt_size_t size)) +{ + rt_realloc_exit_hook = hook; +} + +void rt_free_sethook(void (*hook)(void **ptr)) +{ + rt_free_hook = hook; +} + +#endif /* RT_USING_HOOK */ + +#if defined(RT_USING_HEAP_ISR) +static struct rt_spinlock _heap_spinlock; +#elif defined(RT_USING_MUTEX) +static struct rt_mutex _lock; +#endif + +rt_inline void _heap_lock_init(void) +{ +#if defined(RT_USING_HEAP_ISR) + rt_spin_lock_init(&_heap_spinlock); +#elif defined(RT_USING_MUTEX) + rt_mutex_init(&_lock, "heap", RT_IPC_FLAG_PRIO); +#endif +} + +rt_inline rt_base_t _heap_lock(void) +{ +#if defined(RT_USING_HEAP_ISR) + return rt_spin_lock_irqsave(&_heap_spinlock); +#elif defined(RT_USING_MUTEX) + if (rt_thread_self()) + return rt_mutex_take(&_lock, RT_WAITING_FOREVER); + else + return RT_EOK; +#else + rt_enter_critical(); + return RT_EOK; +#endif +} + +rt_inline void _heap_unlock(rt_base_t level) +{ +#if defined(RT_USING_HEAP_ISR) + rt_spin_unlock_irqrestore(&_heap_spinlock, level); +#elif defined(RT_USING_MUTEX) + RT_ASSERT(level == RT_EOK); + if (rt_thread_self()) + rt_mutex_release(&_lock); +#else + rt_exit_critical(); +#endif +} + +#ifdef RT_USING_UTESTCASES +/* Keep heap-observation tests synchronized with the active allocator. */ +rt_base_t rt_heap_lock(void); +void rt_heap_unlock(rt_base_t level); + +rt_base_t rt_heap_lock(void) +{ + return _heap_lock(); +} + +void rt_heap_unlock(rt_base_t level) +{ + _heap_unlock(level); +} +#endif + +#if defined(RT_USING_SMALL_MEM_AS_HEAP) +static rt_smem_t system_heap; +rt_inline void _smem_info(rt_size_t *total, + rt_size_t *used, rt_size_t *max_used) +{ + if (total) + *total = system_heap->total; + if (used) + *used = system_heap->used; + if (max_used) + *max_used = system_heap->max; +} +#define _MEM_INIT(_name, _start, _size) \ + system_heap = rt_smem_init(_name, _start, _size) +#define _MEM_MALLOC(_size) \ + rt_smem_alloc(system_heap, _size) +#define _MEM_FREE(_ptr) \ + rt_smem_free(_ptr) +#define _MEM_INFO(_total, _used, _max) \ + _smem_info(_total, _used, _max) +#elif defined(RT_USING_MEMHEAP_AS_HEAP) +static struct rt_memheap system_heap; +void *_memheap_alloc(struct rt_memheap *heap, rt_size_t size); +void _memheap_free(void *rmem); +#define _MEM_INIT(_name, _start, _size) \ + do {\ + rt_memheap_init(&system_heap, _name, _start, _size); \ + system_heap.locked = RT_TRUE; \ + } while(0) +#define _MEM_MALLOC(_size) \ + _memheap_alloc(&system_heap, _size) +#define _MEM_FREE(_ptr) \ + _memheap_free(_ptr) +#define _MEM_INFO(_total, _used, _max) \ + rt_memheap_info(&system_heap, _total, _used, _max) +#elif defined(RT_USING_SLAB_AS_HEAP) +static rt_slab_t system_heap; +rt_inline void _slab_info(rt_size_t *total, + rt_size_t *used, rt_size_t *max_used) +{ + if (total) + *total = system_heap->total; + if (used) + *used = system_heap->used; + if (max_used) + *max_used = system_heap->max; +} +#define _MEM_INIT(_name, _start, _size) \ + system_heap = rt_slab_init(_name, _start, _size) +#define _MEM_MALLOC(_size) \ + rt_slab_alloc(system_heap, _size) +#define _MEM_FREE(_ptr) \ + rt_slab_free(system_heap, _ptr) +#define _MEM_INFO _slab_info +#else +#define _MEM_INIT(...) +#define _MEM_MALLOC(...) RT_NULL +#define _MEM_FREE(...) +#define _MEM_INFO(...) +#endif + +static void *_asan_heap_alloc(rt_size_t size) +{ + return _MEM_MALLOC(size); +} + +static void _asan_heap_free(void *ptr) +{ + _MEM_FREE(ptr); +} + +void rt_asan_heap_init(void *begin_addr, void *end_addr) +{ + rt_uintptr_t begin_align = RT_ALIGN((rt_uintptr_t)begin_addr, RT_ALIGN_SIZE); + rt_uintptr_t end_align = RT_ALIGN_DOWN((rt_uintptr_t)end_addr, RT_ALIGN_SIZE); + + RT_ASSERT(end_align > begin_align); + + /* Initialize system memory heap */ + _MEM_INIT("heap", (void *)begin_align, end_align - begin_align); + /* Initialize multi thread contention lock */ + _heap_lock_init(); +} + +void *rt_malloc(rt_size_t size) +{ + rt_base_t level; + void *ptr; + + /* Enter critical zone */ + level = _heap_lock(); + /* allocate memory block from system heap */ + ptr = rt_asan_malloc(size, _asan_heap_alloc); + /* Exit critical zone */ + _heap_unlock(level); + /* call 'rt_malloc' hook */ + RT_OBJECT_HOOK_CALL(rt_malloc_hook, (&ptr, size)); + return ptr; +} + +void *rt_realloc(void *ptr, rt_size_t newsize) +{ + rt_base_t level; + void *nptr; + + /* Entry hook */ + RT_OBJECT_HOOK_CALL(rt_realloc_entry_hook, (&ptr, newsize)); + /* Enter critical zone */ + level = _heap_lock(); + /* Change the size of previously allocated memory block */ + nptr = rt_asan_realloc(ptr, newsize, _asan_heap_alloc, _asan_heap_free); + /* Exit critical zone */ + _heap_unlock(level); + /* Exit hook */ + RT_OBJECT_HOOK_CALL(rt_realloc_exit_hook, (&nptr, newsize)); + return nptr; +} + +void *rt_calloc(rt_size_t count, rt_size_t size) +{ + void *p; + + if (size && count > (rt_size_t)-1 / size) + { + return RT_NULL; + } + + /* allocate 'count' objects of size 'size' */ + p = rt_malloc(count * size); + /* zero the memory */ + if (p) + { + rt_memset(p, 0, count * size); + } + return p; +} + +void rt_free(void *ptr) +{ + rt_base_t level; + + /* call 'rt_free' hook */ + RT_OBJECT_HOOK_CALL(rt_free_hook, (&ptr)); + /* NULL check */ + if (ptr == RT_NULL) return; + /* Enter critical zone */ + level = _heap_lock(); + rt_asan_free(ptr, _asan_heap_free); + /* Exit critical zone */ + _heap_unlock(level); +} + +void rt_memory_info(rt_size_t *total, + rt_size_t *used, + rt_size_t *max_used) +{ + rt_base_t level; + + /* Enter critical zone */ + level = _heap_lock(); + _MEM_INFO(total, used, max_used); + /* Exit critical zone */ + _heap_unlock(level); +} + +#if defined(RT_USING_SLAB) && defined(RT_USING_SLAB_AS_HEAP) +void *rt_page_alloc(rt_size_t npages) +{ + rt_base_t level; + void *ptr; + + /* Enter critical zone */ + level = _heap_lock(); + /* alloc page */ + ptr = rt_slab_page_alloc(system_heap, npages); + /* Exit critical zone */ + _heap_unlock(level); + return ptr; +} + +void rt_page_free(void *addr, rt_size_t npages) +{ + rt_base_t level; + + /* Enter critical zone */ + level = _heap_lock(); + /* free page */ + rt_slab_page_free(system_heap, addr, npages); + /* Exit critical zone */ + _heap_unlock(level); +} +#endif + +void *rt_malloc_align(rt_size_t size, rt_size_t align) +{ + void *ptr; + rt_base_t level; + + if (!size || !align || (align & (align - 1))) + { + return RT_NULL; + } + if (align < sizeof(void *)) + { + align = sizeof(void *); + } + + /* Keep the requested size rather than tracking an oversized backing block. */ + level = _heap_lock(); + ptr = rt_asan_malloc_align(size, align, _asan_heap_alloc); + _heap_unlock(level); + RT_OBJECT_HOOK_CALL(rt_malloc_hook, (&ptr, size)); + return ptr; +} + +void rt_free_align(void *ptr) +{ + + if (ptr == RT_NULL) return; + /* The ASan header is read by the non-instrumented runtime under the heap + * lock. The old pointer-before-buffer layout is not used in this mode. + */ + rt_free(ptr); +} +#endif /* RT_USING_ASAN */ diff --git a/src/kservice.c b/src/kservice.c index 09f69d5eda1d..2739b4231915 100644 --- a/src/kservice.c +++ b/src/kservice.c @@ -32,9 +32,6 @@ */ #include -#ifdef RT_USING_ASAN -#include -#endif /* include rt_hw_backtrace macro defined in cpuport.h */ #define RT_HW_INCLUDE_CPUPORT @@ -947,7 +944,7 @@ static void (*rt_free_hook)(void **ptr); * * @param hook the hook function. */ -void rt_malloc_sethook(void (*hook)(void **ptr, rt_size_t size)) +rt_weak void rt_malloc_sethook(void (*hook)(void **ptr, rt_size_t size)) { rt_malloc_hook = hook; } @@ -958,7 +955,7 @@ void rt_malloc_sethook(void (*hook)(void **ptr, rt_size_t size)) * * @param hook the hook function. */ -void rt_realloc_set_entry_hook(void (*hook)(void **ptr, rt_size_t size)) +rt_weak void rt_realloc_set_entry_hook(void (*hook)(void **ptr, rt_size_t size)) { rt_realloc_entry_hook = hook; } @@ -969,7 +966,7 @@ void rt_realloc_set_entry_hook(void (*hook)(void **ptr, rt_size_t size)) * * @param hook the hook function. */ -void rt_realloc_set_exit_hook(void (*hook)(void **ptr, rt_size_t size)) +rt_weak void rt_realloc_set_exit_hook(void (*hook)(void **ptr, rt_size_t size)) { rt_realloc_exit_hook = hook; } @@ -980,7 +977,7 @@ void rt_realloc_set_exit_hook(void (*hook)(void **ptr, rt_size_t size)) * * @param hook the hook function */ -void rt_free_sethook(void (*hook)(void **ptr)) +rt_weak void rt_free_sethook(void (*hook)(void **ptr)) { rt_free_hook = hook; } @@ -1038,8 +1035,8 @@ rt_inline void _heap_unlock(rt_base_t level) #define rt_heap_lock() _heap_lock() #define rt_heap_unlock() _heap_unlock() #else -rt_base_t rt_heap_lock(void) __attribute__((alias("_heap_lock"))); -void rt_heap_unlock(rt_base_t level) __attribute__((alias("_heap_unlock"))); +rt_weak rt_base_t rt_heap_lock(void) __attribute__((alias("_heap_lock"))); +rt_weak void rt_heap_unlock(rt_base_t level) __attribute__((alias("_heap_unlock"))); #endif /* _MSC_VER */ #endif @@ -1112,18 +1109,6 @@ rt_inline void _slab_info(rt_size_t *total, #define _MEM_INFO(...) #endif -#ifdef RT_USING_ASAN -static void *_asan_heap_alloc(rt_size_t size) -{ - return _MEM_MALLOC(size); -} - -static void _asan_heap_free(void *ptr) -{ - _MEM_FREE(ptr); -} -#endif - /** * @brief This function will do the generic system heap initialization. * @@ -1172,11 +1157,7 @@ rt_weak void *rt_malloc(rt_size_t size) /* Enter critical zone */ level = _heap_lock(); /* allocate memory block from system heap */ -#ifdef RT_USING_ASAN - ptr = rt_asan_malloc(size, _asan_heap_alloc); -#else ptr = _MEM_MALLOC(size); -#endif /* Exit critical zone */ _heap_unlock(level); /* call 'rt_malloc' hook */ @@ -1204,11 +1185,7 @@ rt_weak void *rt_realloc(void *ptr, rt_size_t newsize) /* Enter critical zone */ level = _heap_lock(); /* Change the size of previously allocated memory block */ -#ifdef RT_USING_ASAN - nptr = rt_asan_realloc(ptr, newsize, _asan_heap_alloc, _asan_heap_free); -#else nptr = _MEM_REALLOC(ptr, newsize); -#endif /* Exit critical zone */ _heap_unlock(level); /* Exit hook */ @@ -1266,11 +1243,7 @@ rt_weak void rt_free(void *ptr) if (ptr == RT_NULL) return; /* Enter critical zone */ level = _heap_lock(); -#ifdef RT_USING_ASAN - rt_asan_free(ptr, _asan_heap_free); -#else _MEM_FREE(ptr); -#endif /* Exit critical zone */ _heap_unlock(level); } @@ -1301,7 +1274,7 @@ rt_weak void rt_memory_info(rt_size_t *total, RTM_EXPORT(rt_memory_info); #if defined(RT_USING_SLAB) && defined(RT_USING_SLAB_AS_HEAP) -void *rt_page_alloc(rt_size_t npages) +rt_weak void *rt_page_alloc(rt_size_t npages) { rt_base_t level; void *ptr; @@ -1315,7 +1288,7 @@ void *rt_page_alloc(rt_size_t npages) return ptr; } -void rt_page_free(void *addr, rt_size_t npages) +rt_weak void rt_page_free(void *addr, rt_size_t npages) { rt_base_t level; @@ -1344,13 +1317,9 @@ void rt_page_free(void *addr, rt_size_t npages) rt_weak void *rt_malloc_align(rt_size_t size, rt_size_t align) { void *ptr; -#ifdef RT_USING_ASAN - rt_base_t level; -#else void *align_ptr; const rt_size_t uintptr_mask = sizeof(void *) - 1; rt_size_t align_size; -#endif if (!size || !align || (align & (align - 1))) { @@ -1361,13 +1330,6 @@ rt_weak void *rt_malloc_align(rt_size_t size, rt_size_t align) align = sizeof(void *); } -#ifdef RT_USING_ASAN - /* Keep the requested size rather than tracking an oversized backing block. */ - level = _heap_lock(); - ptr = rt_asan_malloc_align(size, align, _asan_heap_alloc); - _heap_unlock(level); - RT_OBJECT_HOOK_CALL(rt_malloc_hook, (&ptr, size)); -#else if (size > (rt_size_t)-1 - uintptr_mask) { return RT_NULL; @@ -1400,7 +1362,6 @@ rt_weak void *rt_malloc_align(rt_size_t size, rt_size_t align) *((rt_uintptr_t *)align_ptr - 1) = (rt_uintptr_t)ptr; ptr = align_ptr; } -#endif return ptr; } RTM_EXPORT(rt_malloc_align); @@ -1413,20 +1374,11 @@ RTM_EXPORT(rt_malloc_align); */ rt_weak void rt_free_align(void *ptr) { -#ifndef RT_USING_ASAN void *real_ptr; -#endif if (ptr == RT_NULL) return; -#ifdef RT_USING_ASAN - /* The ASan header is read by the non-instrumented runtime under the heap - * lock. The old pointer-before-buffer layout is not used in this mode. - */ - rt_free(ptr); -#else real_ptr = (void *)*((rt_uintptr_t *)ptr - 1); rt_free(real_ptr); -#endif } RTM_EXPORT(rt_free_align); #endif /* RT_USING_HEAP */ diff --git a/src/utest/asan_tc.c b/src/utest/asan_tc.c index f3d12a30492d..0b6c01a12d39 100644 --- a/src/utest/asan_tc.c +++ b/src/utest/asan_tc.c @@ -478,6 +478,107 @@ static void test_asan_concurrent_realloc(void) extern void test_asan_cpp(void); #endif +extern rt_base_t rt_heap_lock(void); +extern void rt_heap_unlock(rt_base_t level); + +/* Statistics and page APIs must use the heap owned by the overriding adapter. */ +static void test_asan_heap_adapter(void) +{ + rt_size_t total = 0, used = 0, maximum = 0; + rt_uint32_t before = rt_asan_report_count_get(); + void *p = rt_malloc(64); + rt_base_t level = rt_heap_lock(); + + rt_heap_unlock(level); + + uassert_not_null(p); + rt_memory_info(&total, &used, &maximum); + uassert_true(total > 0); + uassert_true(used >= 64); + uassert_true(maximum >= used); + uassert_true(total >= used); + rt_free(p); + +#if defined(RT_USING_SLAB_AS_HEAP) + p = rt_page_alloc(1); + uassert_not_null(p); + if (p) + { + uassert_true(((rt_uintptr_t)p & (RT_MM_PAGE_SIZE - 1)) == 0); + rt_memset(p, 0x5a, RT_MM_PAGE_SIZE); + rt_page_free(p, 1); + } +#endif + uassert_int_equal(rt_asan_report_count_get(), before); +} + +#ifdef RT_HOOK_USING_FUNC_PTR +static rt_thread_t probe_thread; +static unsigned probe_malloc, probe_entry, probe_exit, probe_free; +static void *probe_old, *probe_new; +static void probe_alloc_hook(void **ptr, rt_size_t size) +{ + if (rt_thread_self() == probe_thread && *ptr && size == 37) + probe_malloc++; +} +static void probe_entry_hook(void **ptr, rt_size_t size) +{ + if (rt_thread_self() == probe_thread && size == 73) + { + probe_old = *ptr; + probe_entry++; + } +} +static void probe_exit_hook(void **ptr, rt_size_t size) +{ + if (rt_thread_self() == probe_thread && size == 73) + { + probe_new = *ptr; + probe_exit++; + } +} +static void probe_free_hook(void **ptr) +{ + if (rt_thread_self() == probe_thread && *ptr == probe_new) + probe_free++; +} +static void test_asan_hook_probe(void) +{ + void *p, *q; + /* The public API has no getters. Leave application hooks untouched. + * Run this unit without concurrent hook registration, just like other + * tests which temporarily change global callbacks. + */ + if (rt_asan_test_hooks_in_use()) + { + rt_kprintf("[asan] hook test skipped: application hooks are installed\n"); + return; + } + probe_thread = rt_thread_self(); + probe_malloc = probe_entry = probe_exit = probe_free = 0; + rt_malloc_sethook(probe_alloc_hook); + rt_realloc_set_entry_hook(probe_entry_hook); + rt_realloc_set_exit_hook(probe_exit_hook); + rt_free_sethook(probe_free_hook); + p = rt_malloc(37); + q = rt_realloc(p, 73); + rt_free(q ? q : p); + rt_malloc_sethook(RT_NULL); + rt_realloc_set_entry_hook(RT_NULL); + rt_realloc_set_exit_hook(RT_NULL); + rt_free_sethook(RT_NULL); + uassert_false(rt_asan_test_hooks_in_use()); + uassert_not_null(p); + uassert_not_null(q); + uassert_true(probe_old == p); + uassert_true(probe_new == q); + uassert_int_equal(probe_malloc, 1); + uassert_int_equal(probe_entry, 1); + uassert_int_equal(probe_exit, 1); + uassert_int_equal(probe_free, 1); +} +#endif + /* utest_unit_run resets its counters; retain failures from every unit. */ #define ASAN_UNIT_RUN(unit) \ do \ @@ -489,6 +590,10 @@ extern void test_asan_cpp(void); static void testcase(void) { rt_size_t failures = 0; +#ifdef RT_HOOK_USING_FUNC_PTR + ASAN_UNIT_RUN(test_asan_hook_probe); +#endif + ASAN_UNIT_RUN(test_asan_heap_adapter); ASAN_UNIT_RUN(test_asan_overflow_write); ASAN_UNIT_RUN(test_asan_overflow_read); ASAN_UNIT_RUN(test_asan_no_false_positive); diff --git a/tools/testcases/test_asan_config.py b/tools/testcases/test_asan_config.py new file mode 100644 index 000000000000..a774a0b8cf35 --- /dev/null +++ b/tools/testcases/test_asan_config.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# Copyright (c) 2006-2026, RT-Thread Development Team +# SPDX-License-Identifier: Apache-2.0 + +"""Check ASan lock dependencies using the repository's real Kconfig tree. + +Run with: python -m unittest discover -s tools/testcases -p test_asan_config.py +Requires kconfiglib, also used by the configuration tools. +""" + +import itertools +import os +from pathlib import Path +import unittest + +import kconfiglib + + +class AsanConfigTest(unittest.TestCase): + def test_heap_lock_combinations(self): + bsp = Path(__file__).resolve().parents[2] / 'bsp/qemu-vexpress-a9' + previous = Path.cwd() + try: + os.chdir(bsp) + for smp, mutex, isr in itertools.product((0, 2), repeat=3): + with self.subTest(smp=smp, mutex=mutex, isr=isr): + config = kconfiglib.Kconfig('Kconfig', warn=False) + # Start without optional subsystems that select mutexes. + for symbol in config.unique_defined_syms: + if symbol.type in (kconfiglib.BOOL, kconfiglib.TRISTATE): + symbol.set_value(0) + for name in ('RT_USING_SMALL_MEM', + 'RT_USING_SMALL_MEM_AS_HEAP'): + config.syms[name].set_value(2) + for name, value in (('RT_USING_SMP', smp), + ('RT_USING_MUTEX', mutex), + ('RT_USING_HEAP_ISR', isr)): + config.syms[name].set_value(value) + self.assertEqual(config.syms[name].tri_value, value) + config.syms['RT_USING_ASAN'].set_value(2) + expected = 2 if not smp or mutex or isr else 0 + self.assertEqual(config.syms['RT_USING_ASAN'].tri_value, + expected) + finally: + os.chdir(previous) + + +if __name__ == '__main__': + unittest.main() From 6bee96855bad418bb19c4cc64b2da935ac71b4e0 Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Thu, 10 Sep 2026 10:30:31 +0800 Subject: [PATCH 11/11] [asan] fix heap adapter and hook test formatting --- components/utilities/asan/asan_heap.c | 64 ++++++++++++++++++--------- src/utest/asan_tc.c | 4 ++ 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/components/utilities/asan/asan_heap.c b/components/utilities/asan/asan_heap.c index 800a9c9d0b4b..223f9e9e2431 100644 --- a/components/utilities/asan/asan_heap.c +++ b/components/utilities/asan/asan_heap.c @@ -70,9 +70,13 @@ rt_inline rt_base_t _heap_lock(void) return rt_spin_lock_irqsave(&_heap_spinlock); #elif defined(RT_USING_MUTEX) if (rt_thread_self()) + { return rt_mutex_take(&_lock, RT_WAITING_FOREVER); + } else + { return RT_EOK; + } #else rt_enter_critical(); return RT_EOK; @@ -86,7 +90,9 @@ rt_inline void _heap_unlock(rt_base_t level) #elif defined(RT_USING_MUTEX) RT_ASSERT(level == RT_EOK); if (rt_thread_self()) + { rt_mutex_release(&_lock); + } #else rt_exit_critical(); #endif @@ -111,60 +117,73 @@ void rt_heap_unlock(rt_base_t level) #if defined(RT_USING_SMALL_MEM_AS_HEAP) static rt_smem_t system_heap; rt_inline void _smem_info(rt_size_t *total, - rt_size_t *used, rt_size_t *max_used) + rt_size_t *used, rt_size_t *max_used) { if (total) + { *total = system_heap->total; + } if (used) + { *used = system_heap->used; + } if (max_used) + { *max_used = system_heap->max; + } } #define _MEM_INIT(_name, _start, _size) \ system_heap = rt_smem_init(_name, _start, _size) -#define _MEM_MALLOC(_size) \ +#define _MEM_MALLOC(_size) \ rt_smem_alloc(system_heap, _size) #define _MEM_FREE(_ptr) \ rt_smem_free(_ptr) -#define _MEM_INFO(_total, _used, _max) \ +#define _MEM_INFO(_total, _used, _max) \ _smem_info(_total, _used, _max) #elif defined(RT_USING_MEMHEAP_AS_HEAP) static struct rt_memheap system_heap; void *_memheap_alloc(struct rt_memheap *heap, rt_size_t size); void _memheap_free(void *rmem); -#define _MEM_INIT(_name, _start, _size) \ - do {\ +#define _MEM_INIT(_name, _start, _size) \ + do \ + { \ rt_memheap_init(&system_heap, _name, _start, _size); \ - system_heap.locked = RT_TRUE; \ - } while(0) -#define _MEM_MALLOC(_size) \ + system_heap.locked = RT_TRUE; \ + } while (0) +#define _MEM_MALLOC(_size) \ _memheap_alloc(&system_heap, _size) -#define _MEM_FREE(_ptr) \ +#define _MEM_FREE(_ptr) \ _memheap_free(_ptr) -#define _MEM_INFO(_total, _used, _max) \ +#define _MEM_INFO(_total, _used, _max) \ rt_memheap_info(&system_heap, _total, _used, _max) #elif defined(RT_USING_SLAB_AS_HEAP) static rt_slab_t system_heap; rt_inline void _slab_info(rt_size_t *total, - rt_size_t *used, rt_size_t *max_used) + rt_size_t *used, rt_size_t *max_used) { if (total) + { *total = system_heap->total; + } if (used) + { *used = system_heap->used; + } if (max_used) + { *max_used = system_heap->max; + } } #define _MEM_INIT(_name, _start, _size) \ system_heap = rt_slab_init(_name, _start, _size) -#define _MEM_MALLOC(_size) \ +#define _MEM_MALLOC(_size) \ rt_slab_alloc(system_heap, _size) #define _MEM_FREE(_ptr) \ rt_slab_free(system_heap, _ptr) -#define _MEM_INFO _slab_info +#define _MEM_INFO _slab_info #else #define _MEM_INIT(...) -#define _MEM_MALLOC(...) RT_NULL +#define _MEM_MALLOC(...) RT_NULL #define _MEM_FREE(...) #define _MEM_INFO(...) #endif @@ -182,7 +201,7 @@ static void _asan_heap_free(void *ptr) void rt_asan_heap_init(void *begin_addr, void *end_addr) { rt_uintptr_t begin_align = RT_ALIGN((rt_uintptr_t)begin_addr, RT_ALIGN_SIZE); - rt_uintptr_t end_align = RT_ALIGN_DOWN((rt_uintptr_t)end_addr, RT_ALIGN_SIZE); + rt_uintptr_t end_align = RT_ALIGN_DOWN((rt_uintptr_t)end_addr, RT_ALIGN_SIZE); RT_ASSERT(end_align > begin_align); @@ -252,7 +271,10 @@ void rt_free(void *ptr) /* call 'rt_free' hook */ RT_OBJECT_HOOK_CALL(rt_free_hook, (&ptr)); /* NULL check */ - if (ptr == RT_NULL) return; + if (ptr == RT_NULL) + { + return; + } /* Enter critical zone */ level = _heap_lock(); rt_asan_free(ptr, _asan_heap_free); @@ -261,8 +283,8 @@ void rt_free(void *ptr) } void rt_memory_info(rt_size_t *total, - rt_size_t *used, - rt_size_t *max_used) + rt_size_t *used, + rt_size_t *max_used) { rt_base_t level; @@ -325,8 +347,10 @@ void *rt_malloc_align(rt_size_t size, rt_size_t align) void rt_free_align(void *ptr) { - - if (ptr == RT_NULL) return; + if (ptr == RT_NULL) + { + return; + } /* The ASan header is read by the non-instrumented runtime under the heap * lock. The old pointer-before-buffer layout is not used in this mode. */ diff --git a/src/utest/asan_tc.c b/src/utest/asan_tc.c index 0b6c01a12d39..43bb6738a73e 100644 --- a/src/utest/asan_tc.c +++ b/src/utest/asan_tc.c @@ -519,7 +519,9 @@ static void *probe_old, *probe_new; static void probe_alloc_hook(void **ptr, rt_size_t size) { if (rt_thread_self() == probe_thread && *ptr && size == 37) + { probe_malloc++; + } } static void probe_entry_hook(void **ptr, rt_size_t size) { @@ -540,7 +542,9 @@ static void probe_exit_hook(void **ptr, rt_size_t size) static void probe_free_hook(void **ptr) { if (rt_thread_self() == probe_thread && *ptr == probe_new) + { probe_free++; + } } static void test_asan_hook_probe(void) {