[components][asan] Add runtime AddressSanitizer for heap - #11772
[components][asan] Add runtime AddressSanitizer for heap#11772RogerNext wants to merge 10 commits into
Conversation
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.
|
👋 感谢您对 RT-Thread 的贡献!Thank you for your contribution to RT-Thread! 为确保代码符合 RT-Thread 的编码规范,请在你的仓库中执行以下步骤运行代码格式化工作流(如果格式化CI运行失败)。 🛠 操作步骤 | Steps
完成后,提交将自动更新至 如有问题欢迎联系我们,再次感谢您的贡献!💐 |
📌 Code Review Assignment🏷️ Tag: componentsReviewers: @Maihuanyi Changed Files (Click to expand)
🏷️ Tag: kernelReviewers: @GorrayLi @ReviewSun @hamburger-os @lianux-mm @wdfk-prog @xu18838022837 Changed Files (Click to expand)
🏷️ Tag: workflowReviewers: @Rbb666 @kurisaW @supperthomas Changed Files (Click to expand)
📊 Current Review Status (Last Updated: 2026-09-10 09:33 CST)
📝 Review Instructions
|
|
|
这部分以什么方式加入?直接加也并不合适,不应该把它做为一个独立的组件放到 components 目录下,可以考虑的方式是 |
感谢反馈,我理解后觉得放 关于「软件包方式」,有一点技术约束想说明:ASan 依赖 GCC 的 |
|
感谢PR,看到了描述中支持了ARM和RV,作者能否添加下代表性的BSP(qemu-a9)进行编译看护,并添加utest使用utest-auto-run ci看护起来? |
好的,我处理一下 |
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.
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.
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)
|
感谢建议,已补充 utest 测试用例和 CI 看护。 新增测试用例(
CI 看护:
过程中发现并修复的一个问题: 已在 qemu-vexpress-a9 上本地验证:6 个测试单元全部 PASSED。 |
| # 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') |
There was a problem hiding this comment.
参数只追加到 CFLAGS,但 C++ 命令没有,需要同步设置下 CXXFLAGS。
There was a problem hiding this comment.
已在 7010717 中同步向 CXXFLAGS 添加 -fsanitize=kernel-address -fno-omit-frame-pointer,并新增 C++ 越界回归用例。
| 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) |
There was a problem hiding this comment.
这里有点问题,就是这两个 hook 均位于堆锁之外。若线程 A 进入 realloc 后被抢占,B 完成 realloc,再恢复 A,就会用 B 的指针处理 A 的释放操作。
There was a problem hiding this comment.
确实存在这个问题,已在 7010717 中移除全局旧指针和成对 hook 的实现,将 ASan 分配状态更新接入堆锁保护范围。同时处理了旧块释放后、shadow 更新前被其他线程复用的窗口。
| asan_track_add(p, size); | ||
| asan_unpoison_range(p, size); | ||
| if (aligned > size) | ||
| { | ||
| asan_poison_range(p + size, aligned - size); |
There was a problem hiding this comment.
这块是只把向上对齐后多出来的字节设为了保护区,没有额外预留保护空间。
如果申请16 字节,就不存在被标记为不可访问的区域了吧?如果 rt_malloc(16),p[16] = 1 这种是不是就检测不到了?
There was a problem hiding this comment.
是的,原实现只标记对齐填充,16 字节这类请求会漏报。已在 7010717 中实际预留右侧保护区,并按用户请求长度设置 shadow;普通分配和对齐分配均已覆盖。补充了整齐/非整齐大小、对齐分配及 realloc 缩小后的边界测试
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.
在 |
感谢指出,我确认了 DefineGroup 的实现,SConscript 中的 CFLAGS/CXXFLAGS 确实可以追加到全局编译环境。之前的表述不准确,也不能据此认定软件包只能实现不插桩的版本。 |
已在 ebfd73c 中完成上述调整,将全局 CFLAGS/CXXFLAGS/LINKFLAGS 配置收回 components/utilities/asan/SConscript,移除了 tools/building.py 中的 ASan 专用逻辑。分配器和运行时的局部排除插桩设置保持有效。 |
| * | ||
| * @return the pointer to allocated memory or NULL if no free memory was found. | ||
| */ | ||
| rt_weak void *rt_malloc(rt_size_t size) |
There was a problem hiding this comment.
其实在这里是专门留了weak的,这样可以在需要重载系统动态内存分配器的地方重新实现这些函数,这样就可以做到对内核的无侵入修改了。
There was a problem hiding this comment.
明白了,当前直接在 kservice.c 中接入 ASan 的方式没有充分利用weak这个机制。我会按组件内覆盖弱符号的方向调整,并核对底层分配器接入、锁和原有接口行为,尽量消除 ASan 对内核源码的直接修改。
There was a problem hiding this comment.
我已在本地按覆盖 weak 接口的方式调整:由 ASan 组件提供分配、释放、对齐分配及内存统计等接口,移除了 kservice.c 中的 ASan 专用分支。
调整过程中发现,四个 hook 设置接口、两个 slab 页分配接口,以及两个测试用堆锁接口目前不是 weak,而且依赖 kservice.c 内部的静态状态。为保持这些接口与组件接管后的堆一致,当前方案为这八个接口补充了 weak 声明。分配器排除插桩的构建设置暂时保留。
这个方案保留了现有功能,代价是仍有少量通用内核改动,且组件需要维护一套堆适配和锁管理逻辑。想请教您是否接受补齐这些通用 weak 扩展点,还是更倾向于严格保持内核不变、相应限定首版组件的支持范围?我再按确定的方向整理提交。
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.
Description / 描述
Add a heap-only runtime AddressSanitizer (kernel-address) for RT-Thread. It instruments memory accesses via GCC's
-fsanitize=kernel-addressand detects heap-buffer-overflow and use-after-free at runtime, with a FinSHasan_infocommand for interactive diagnosis.为 RT-Thread 新增仅针对堆的运行时 AddressSanitizer(kernel-address)。通过 GCC 的
-fsanitize=kernel-address对内存访问插桩,在运行时检测 堆缓冲区越界(heap-buffer-overflow) 和 释放后使用(use-after-free),并提供 FinSHasan_info命令进行交互式诊断。Why / 为什么需要
RT-Thread 在 MCU 目标上缺乏运行时内存安全检查(ASan 之前仅存在于 x86 模拟器)。堆越界与 use-after-free 是最常见的嵌入式内存 bug,本组件提供目标机上的运行时检测,且报告包含触发线程名与内存块归属信息。
What / 修改了哪些文件
components/asan/Kconfig:注册RT_USING_ASAN(含 shadow 大小 / track 表大小 / backtrace 开关)components/asan/SConscript:runtime 自身以-fno-sanitize=kernel-address编译避免递归components/asan/asan.c:shadow memory + 插桩访问检查 + 报告 + malloc/free/realloc hookcomponents/Kconfig:rsource "asan/Kconfig"src/SConscript:将 mem/memheap/slab 分配器移入非插桩编译组tools/building.py:GCC 下注入-fsanitize=kernel-addressHeap algorithm support / 堆算法支持
RT_USING_SMALL_MEM_AS_HEAP)RT_USING_SLAB_AS_HEAP)RT_USING_MEMHEAP_AS_HEAP)RT_USING_USERHEAP)Verification / 验证
asan_test_overflow(overflow by 2 bytes)、asan_test_uaf(USE-AFTER-FREE, offset +0)、asan_info全部命中kernel-address