ASAN指针解引用时影子内存检查时机及示例异常咨询
ASAN内存中毒检查的原理与疑问解析
示例代码
#include <stdio.h> #include <stdint.h> void __asan_poison_memory_region(void const volatile *addr, size_t size); void __asan_unpoison_memory_region(void const volatile *addr, size_t size); int main(void) { uint64_t a[10] = {0}; __asan_poison_memory_region(a, 10*sizeof(uint64_t)); uint64_t* b = a; __asan_poison_memory_region(b, 10*sizeof(uint64_t)); a[1] = 4; // 无告警 b[1] = 4; // 触发use-after-poison告警 return 0; }
编译命令
clang -fsanitize=address asan.c -o asan.exe
问题描述
编译运行后发现,尽管内存被两次标记为中毒,ASAN仅报告b[1]=4的访问违规,未报告a[1]=4的访问。疑问如下:
- ASAN是否仅在使用指针b时检查影子内存?
- ASAN何时会检查内存是否中毒,何时不会?
- 原本以为可标记任意内存区域为中毒并确保ASAN报告非法访问,但实际不符,希望了解其中原理;预期两个访问操作都应触发告警。
原理解答
核心原因是ASAN对数组直接下标访问和指针访问的插桩逻辑存在差异,编译器的静态优化会绕过部分场景的影子内存检查:
数组直接访问
a[1]无告警的原因
编译器在编译阶段能确定a是栈上固定大小的数组,a[1]处于数组的静态合法边界内(下标0-9)。为了性能优化,ASAN会跳过这类“静态合法”的数组下标访问的影子内存检查——哪怕你手动调用__asan_poison_memory_region标记了整个数组为中毒,编译器也会认为这是开发者主动标记的特殊场景,但静态范围内的访问属于“预期内”操作,不触发检查。指针访问
b[1]触发告警的原因
指针b是一个栈上变量,编译器无法在编译阶段确定它指向的内存范围(哪怕它实际指向a)。对于这类无静态边界信息的指针访问,ASAN会严格查询影子内存的状态,发现该区域已被标记为中毒,就会触发use-after-poison告警。
ASAN的检查时机规则
触发影子内存检查的场景:
- 通过非静态指针的内存访问(包括堆指针、栈上指针变量、函数参数传递的指针等)
- 超出数组静态边界的访问(比如
a[10],哪怕未手动中毒,也会触发越界检查) - 手动中毒/解除中毒后,通过指针访问该内存区域
- 编译器无法确定访问安全性的内存操作(比如volatile修饰的数组访问)
跳过影子内存检查的场景:
- 在数组静态合法边界内的直接下标访问,哪怕手动标记为中毒
- 编译器能确定绝对安全的静态内存操作(比如全局数组的合法下标访问)
验证方案
如果要让数组直接访问a[1]也触发中毒检查,可以通过以下方式避免编译器的静态优化:
- 用
volatile修饰数组:volatile uint64_t a[10] = {0};,这样编译器无法确定访问的安全性,会插入ASAN检查 - 将数组地址通过函数传递,打破编译器的静态分析:比如写一个函数接收数组指针,在函数内访问
a[1]
内容的提问来源于stack exchange,提问作者OetkenPurveyorOfCode
相关产品推荐
相关产品推荐

