You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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对数组直接下标访问和指针访问的插桩逻辑存在差异,编译器的静态优化会绕过部分场景的影子内存检查:

  1. 数组直接访问a[1]无告警的原因
    编译器在编译阶段能确定a是栈上固定大小的数组,a[1]处于数组的静态合法边界内(下标0-9)。为了性能优化,ASAN会跳过这类“静态合法”的数组下标访问的影子内存检查——哪怕你手动调用__asan_poison_memory_region标记了整个数组为中毒,编译器也会认为这是开发者主动标记的特殊场景,但静态范围内的访问属于“预期内”操作,不触发检查。

  2. 指针访问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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 15:31:13