-
-
[原创]模糊测试 ASan 模式下有关堆的 ASan Redzone 和 Check 的源码解析
-
发表于: 2024-10-4 15:29 6521
-
模糊测试(Fuzz Testing)是一种自动化的软件测试技术,旨在通过向程序提供随机或异常输入来检测其漏洞与崩溃。它能够有效捕捉输入处理中的异常,特别是与内存相关的问题。模糊测试在寻找未知错误方面尤为有用,因为它无需预定义输入模式,通过生成各种异常数据,增加程序失效的可能性。
AddressSanitizer(ASan)是一种强大的内存错误检测工具,主要用于检测运行时的内存问题,如缓冲区溢出、内存越界、未初始化内存的使用以及内存泄漏。ASan 在模糊测试中至关重要,因为它可以捕获许多模糊测试过程中暴露出的内存错误,帮助开发者更快、更有效地定位潜在问题。
本篇文章的目的是深入解析 ASan 模式下,堆内存的保护机制,尤其是 Redzone 和 Check 的相关源码。文章将通过对源码的详细解读,揭示 ASan 如何通过 Redzone 和 Check 机制标记和监控内存的使用状态,进而检测越界访问和内存泄漏等问题。
文章主要大纲:
写了一个Makefile 定义了两种不同版本的内存错误检测程序的编译流程,分别是带有 ASAN 插桩的版本和普通版本。ASAN 插桩版本用于动态检测程序中的内存错误(如使用释放的内存、越界访问等),而未插桩版本则是用于对比执行的正常程序版本。源码可以直接让GPT生成这里就不给出了代码文件了,就给出makefile文件.
Makefile文件:
以asan模式手动编译的命令:
以asan模式手动编译的命令:
ASAN 可以通过编译时的插桩(instrumentation)和运行时的动态检查,帮助开发者检测和调试内存相关的错误。它在编译期间会为每个内存分配和释放操作添加额外的代码,确保在运行过程中对内存的每次读写都经过 ASAN 的验证,从而发现内存问题。
在程序运行时,ASAN 会为每个内存块设置边界标记。每次内存分配时,ASAN 在正常的可用内存区域周围插入一些不可访问的区域,称为 redzone。这些区域主要用于检测越界访问。
这些 redzone 通常不会引发程序立即崩溃,而是被 ASAN 用来记录和标记潜在的内存问题。这样,当发生越界访问时,ASAN 能够识别并报告这些问题,帮助开发者找到问题所在。
ASAN 引入了一种称为 影子内存 (shadow memory) 的技术,用于跟踪主内存的可访问性状态。影子内存和正常内存的比例是 1:8,即每 1 字节的影子内存可以描述 8 字节的正常内存。这意味着,影子内存中的每个字节对应主内存中的 8 个字节,用于标记这些字节是否可访问。
影子内存的具体实现与内存的映射有关。ASAN 会通过影子内存中的字节位信息来判断内存的状态:
这是heap_out_of_bounds.c正常编译出来的IDA伪代码:
这个代码实现的功能是向堆块内写入6个int整数,占用0x20个字节,但是堆块的大小只有0x14个字节存在堆溢出漏洞.正常情况下是不会导致程序奔溃的.
这是heap_out_of_bounds.c以asan模式编译后的IDA伪代码:
这是堆溢出漏洞经过Asan模式插桩以后的样子,被插入了一段校验代码,每次在向堆写入数据时都会检查是否发生堆溢出.
我们这部分主要是讲解堆内存是如何被标记Redzone即不可访问的!这要引入一个之前提到的影子内存(Shadow Memory)概念,这片内存放了真实内存可以被访问的内存数.通过伪代码可以发现并未实现堆块内存的redzone设置,因为这些操作被隐藏在了malloc函数中,源码如下:
当程序通过 malloc
分配内存时,ASan 会相应地在影子内存中标记该内存区域为可用,并且设置两个 redzone 区域,来防止越界访问。
相关源码位置:llvm-project\compiler-rt\lib\asan\asan_allocator.cpp
源码位置:llvm-project/compiler-rt/lib/asan/asan_allocator.cpp at main · llvm/llvm-project (github.com)
首先我们使用的malloc函数不是原来glibc里面的函数了,而是Asan实现的asan_malloc函数!可以通过源码查找发现他具体进行的操作,继续往内部追寻就可以发现!
在上述代码中,PoisonShadow()
函数用于将影子内存设为特殊的“redzone”值,这些 redzone 区域不可访问,旨在保护已分配内存的边界。
其中kAsanHeapLeftRedzoneMagic的值是:const int kAsanHeapLeftRedzoneMagic = 0xfa;
这是use_after_free.c正常编译出来的IDA伪代码:
单纯在堆块释放后依旧对堆内存进行输出也就是UAF漏洞!正常情况下是不会导致程序奔溃的.
这是use_after_free.c以asan模式编译出来的IDA伪代码:
这段代码展示了一个简单的使用后释放(Use-After-Free, UAF)漏洞,其中堆块在释放后继续访问,导致Asan检测到错误从而报错。在每次对堆块内存进行操作时,都会进行影子内存检查.
可以发现Asan插桩的条件是对内存进行操作时就会针对性插桩,比如向堆块内存赋值时后就会进行Asan检测,在printf输出堆块内存时候也会对内存进行检测.
我们这部分主要是讲解堆内存的Redzone即不可访问标记是如何被取消的!这里的实现就涉及到了free函数了,源码如下:
free和malloc同样被拦截器函数替代了原有函数的功能!
相关源码位置:llvm-project\compiler-rt\lib\asan\asan_allocator.cpp
源码位置:llvm-project/compiler-rt/lib/asan/asan_allocator.cpp at main · llvm/llvm-project (github.com)
可以发现free函数最后都会调用instance.Deallocate(ptr, 0, 0, stack, alloc_type);继续往后看源码!
这是Allocator结构体的定义,里面存在一个变量隔离区缓存:QuarantineCache fallback_quarantine_cache;
当内存被释放时,ASan 并不会立即将内存归还给操作系统,而是将其放入一个隔离区(quarantine)中,以便更有效地检测 “use-after-free” 问题。QuarantineChunk()
函数会毒化释放后的内存区域,防止程序继续访问已经释放的内存,典型的影子内存毒化过程如下:
最后整体梳理一下,在这段代码中,PoisonShadow()
被调用来将影子内存标记为已释放状态,以 kAsanHeapFreeMagic
值毒化内存。这一过程确保了即使程序错误地访问已释放的内存,ASan 也能检测到并报错。
在前面的堆溢出案例分析和UAF案例分析中都提供了一个Asan模式的插桩版本,这里被插入的代码就是堆的ASan Check,检测是否存在非法的内存操作.
这些插入的代码都是我们将要堆对对内存进行操作时就会插入的代码,比如输出堆块内存信息和对堆块内存进行赋值时,下面就是之前案例中频繁出现的伪代码插桩片段:
由于这个代码操作是对数组的每个单元进行赋值所以每次赋值到要进行检测.
这里只需要操作一次所以也就只会检查一次.
我们可以在插入的代码中发现一个特殊的值:0x7FFF8000,这个值就是影子内存的基地址,也就是影子内存的起始地址。这个值并不固定,但在之前的案例中,都是以 0x7FFF8000
作为起始地址。
从这地址开始就存放了所有内存的可访问性:
ASAN_SHADOW_START(影子内存)的不同值代表以下含义:
如果你想获取 0x1000
到 0x1007
这段内存的字节可访问性,可以通过 0x1000 / 8
得到一个影子内存的索引:shadow_idx = 512
(即 0x200
)。
通过影子内存的偏移地址 *(ASAN_SHADOW_START + shadow_idx)
,你可以得知对应的 8 个字节中,有 3 个字节是可访问的。
在之前的案例中,表达式 *((v3 >> 3) + 0x7FFF8000)
执行的正是这个操作:由于每 8 个字节的内存对应影子内存中的 1 个字节,因此使用 ptr >> 3
,等同于除以 8。
再使用 -fsanitize=address这个编译选项的时候,编译器就会将目标代码插入程序中.
首先是解析代码会进行几个字节的操作就会插入相对于的汇编代码,比如之前案例中出现的((ptr & 7) + 3)
就是表明要向堆块内写入4个字节,如果直接从未代码层面去理解的画会比较麻烦,所以接下来直接从汇编层代码去理解.
根据前面案例提供的伪代码,查看其汇编代码就会如下:
简单的识别这段代码的意思就是:
源码位置:https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/asan/asan_rtl_x86_64.S
大概总结以下上面的汇编代码,就是根据操作的内存字节数不同插入不同的Check汇编代码!
列举出来重要的就是:
之前我举出的案例都是操作4个字节的案例,所以插入的汇编代码就是:ASAN_MEMORY_ACCESS_CALLBACK_ADD_4(reg, op)
按照伪代码来对比一下:
对应完整的IDA伪代码就是:
对应的伪代码:*((v3 >> 3) + 0x7FFF8000) != 0
对应的伪代码:
通过汇编代码检测4字节的内存访问。主要步骤包括:
以asan模式编译的memory_leak.c,ida伪代码:
示例中,__asan_report_store4(v3)
是 AddressSanitizer (ASan) 进行的一个内存访问检测,但这段代码本身并不会直接导致内存泄漏的检测。ASan 专门负责检测内存访问的错误,例如越界访问、未初始化的内存访问、双重释放等。
然而,检测堆内存泄漏(如你的程序中分配了内存却未释放的情况),并不是 ASan 本身完成的,而是由 ASan 与 LeakSanitizer (LSan) 共同合作实现的。LSan 是专门用于检测内存泄漏的工具,通常与 ASan 一起使用。
比如在程序运行快结束的时候就会爆出错误:
我们可以通过字符串来搜索源码知道堆内存泄漏检查的位置!
下面是添加注释以后的源码:llvm-project/compiler-rt/lib/asan/asan_memory_profile.cpp at main · llvm/llvm-project (github.com)
这段代码的作用就是在程序结束后来检查释放存在已经申请的堆块是否都被释放,如果没有全部释放就爆出内存泄漏的漏洞!
具体来说,以下是 LSan 如何检测到堆内存泄漏的过程:
ASan原理概述:
相关资料:
# 定义编译器和编译选项
CC = gcc
CFLAGS = -g -Wall
ASAN_FLAGS = -fsanitize=address
# 要编译的源文件
SRCS = use_after_free.c heap_out_of_bounds.c stack_out_of_bounds.c \
global_out_of_bounds.c return_local_variable.c memory_leak.c
# 生成的可执行文件 (插桩和未插桩)
INSTRUMENTED_TARGETS = use_after_free_asan heap_out_of_bounds_asan \
memory_leak_asan
NON_INSTRUMENTED_TARGETS = use_after_free heap_out_of_bounds \
memory_leak
# 默认目标: 编译插桩和未插桩版本
all: instrumented non_instrumented
# 编译插桩版本
instrumented: $(INSTRUMENTED_TARGETS)
use_after_free_asan: use_after_free.c
$(CC) $(CFLAGS) $(ASAN_FLAGS) -o $@ $<
heap_out_of_bounds_asan: heap_out_of_bounds.c
$(CC) $(CFLAGS) $(ASAN_FLAGS) -o $@ $<
memory_leak_asan: memory_leak.c
$(CC) $(CFLAGS) $(ASAN_FLAGS) -o $@ $<
# 编译未插桩版本
non_instrumented: $(NON_INSTRUMENTED_TARGETS)
use_after_free: use_after_free.c
$(CC) $(CFLAGS) -o $@ $<
heap_out_of_bounds: heap_out_of_bounds.c
$(CC) $(CFLAGS) -o $@ $<
memory_leak: memory_leak.c
$(CC) $(CFLAGS) -o $@ $<
# 清理编译生成的文件
clean:
rm -f $(INSTRUMENTED_TARGETS) $(NON_INSTRUMENTED_TARGETS)
.PHONY: all clean instrumented non_instrumented
# 定义编译器和编译选项
CC = gcc
CFLAGS = -g -Wall
ASAN_FLAGS = -fsanitize=address
# 要编译的源文件
SRCS = use_after_free.c heap_out_of_bounds.c stack_out_of_bounds.c \
global_out_of_bounds.c return_local_variable.c memory_leak.c
# 生成的可执行文件 (插桩和未插桩)
INSTRUMENTED_TARGETS = use_after_free_asan heap_out_of_bounds_asan \
memory_leak_asan
NON_INSTRUMENTED_TARGETS = use_after_free heap_out_of_bounds \
memory_leak
# 默认目标: 编译插桩和未插桩版本
all: instrumented non_instrumented
# 编译插桩版本
instrumented: $(INSTRUMENTED_TARGETS)
use_after_free_asan: use_after_free.c
$(CC) $(CFLAGS) $(ASAN_FLAGS) -o $@ $<
heap_out_of_bounds_asan: heap_out_of_bounds.c
$(CC) $(CFLAGS) $(ASAN_FLAGS) -o $@ $<
memory_leak_asan: memory_leak.c
$(CC) $(CFLAGS) $(ASAN_FLAGS) -o $@ $<
# 编译未插桩版本
non_instrumented: $(NON_INSTRUMENTED_TARGETS)
use_after_free: use_after_free.c
$(CC) $(CFLAGS) -o $@ $<
heap_out_of_bounds: heap_out_of_bounds.c
$(CC) $(CFLAGS) -o $@ $<
memory_leak: memory_leak.c
$(CC) $(CFLAGS) -o $@ $<
# 清理编译生成的文件
clean:
rm -f $(INSTRUMENTED_TARGETS) $(NON_INSTRUMENTED_TARGETS)
.PHONY: all clean instrumented non_instrumented
int
__cdecl main(
int
argc,
const
char
**argv,
const
char
**envp)
{
int
i;
// [rsp+4h] [rbp-Ch]
int
*arr;
// [rsp+8h] [rbp-8h]
arr = (
int
*)
malloc
(0x14uLL);
for
( i = 0; i <= 5; ++i )
arr[i] = i;
free
(arr);
return
0;
}
int
__cdecl main(
int
argc,
const
char
**argv,
const
char
**envp)
{
int
i;
// [rsp+4h] [rbp-Ch]
int
*arr;
// [rsp+8h] [rbp-8h]
arr = (
int
*)
malloc
(0x14uLL);
for
( i = 0; i <= 5; ++i )
arr[i] = i;
free
(arr);
return
0;
}
int
__cdecl main(
int
argc,
const
char
**argv,
const
char
**envp)
{
int
*v3;
// rcx
int
i;
// [rsp+4h] [rbp-Ch]
int
*arr;
// [rsp+8h] [rbp-8h]
arr =
malloc
(0x14uLL);
for
( i = 0; i <= 5; ++i )
{
v3 = &arr[i];
//在进行赋值前检测内存的可访问性
if
( *((v3 >> 3) + 0x7FFF8000) != 0 && (((4 * i + arr) & 7) + 3) >= *((v3 >> 3) + 0x7FFF8000) )
__asan_report_store4(&arr[i]);
//发现不可以访问就报错
*v3 = i;
}
free
(arr);
return
0;
}
int
__cdecl main(
int
argc,
const
char
**argv,
const
char
**envp)
{
int
*v3;
// rcx
int
i;
// [rsp+4h] [rbp-Ch]
int
*arr;
// [rsp+8h] [rbp-8h]
arr =
malloc
(0x14uLL);
for
( i = 0; i <= 5; ++i )
{
v3 = &arr[i];
//在进行赋值前检测内存的可访问性
if
( *((v3 >> 3) + 0x7FFF8000) != 0 && (((4 * i + arr) & 7) + 3) >= *((v3 >> 3) + 0x7FFF8000) )
__asan_report_store4(&arr[i]);
//发现不可以访问就报错
*v3 = i;
}
free
(arr);
return
0;
}
//源码:asan_allocator.cpp
...
static
Allocator instance(LINKER_INITIALIZED);
...
void
*asan_malloc(uptr size, BufferedStackTrace *stack) {
return
SetErrnoOnNull(instance.Allocate(size, 8, stack, FROM_MALLOC,
true
));
}
...
// 拦截器函数:替代 malloc 函数的实现
INTERCEPTOR(
void
*,
malloc
, uptr size) {
if
(DlsymAlloc::Use())
return
DlsymAlloc::Allocate(size);
// 使用自定义分配逻辑
GET_STACK_TRACE_MALLOC;
// 获取堆栈信息
return
asan_malloc(size, &stack);
// 使用 ASan 的内存分配逻辑
}
...
//源码:asan_allocator.cpp
...
static
Allocator instance(LINKER_INITIALIZED);
...
void
*asan_malloc(uptr size, BufferedStackTrace *stack) {
return
SetErrnoOnNull(instance.Allocate(size, 8, stack, FROM_MALLOC,
true
));
}
...
// 拦截器函数:替代 malloc 函数的实现
INTERCEPTOR(
void
*,
malloc
, uptr size) {
if
(DlsymAlloc::Use())
return
DlsymAlloc::Allocate(size);
// 使用自定义分配逻辑
GET_STACK_TRACE_MALLOC;
// 获取堆栈信息
return
asan_malloc(size, &stack);
// 使用 ASan 的内存分配逻辑
}
...
// -------------------- 分配/释放例程 ---------------
void
*Allocate(uptr size, uptr alignment, BufferedStackTrace *stack,
AllocType alloc_type,
bool
can_fill) {
// 如果AddressSanitizer (ASan) 未初始化,则进行初始化
// UNLIKELY 用于优化分支预测,表示该条件不太可能为真
if
(UNLIKELY(!AsanInited()))
AsanInitFromRtl();
// 初始化 ASan 运行时库
...
// 如果使用的是次分配器(from_primary为false)或影子内存尚未被污染(即影子内存值为0),则对内存进行标记(污染)
// MEM_TO_SHADOW将分配的实际内存地址映射到影子内存地址
if
(!from_primary || *(u8 *)MEM_TO_SHADOW((uptr)allocated) == 0) {
// 计算分配的用户区域结束地址,向上对齐到ASAN_SHADOW_GRANULARITY的倍数(通常为8字节)
uptr tail_beg = RoundUpTo(user_end, ASAN_SHADOW_GRANULARITY);
// 计算实际分配的内存块结束地址,包括分配器的管理开销
uptr tail_end = alloc_beg + allocator.GetActuallyAllocatedSize(allocated);
// 对分配的左侧redzone区域进行毒化,防止越界写入左侧内存区域
PoisonShadow(alloc_beg, user_beg - alloc_beg, kAsanHeapLeftRedzoneMagic);
// 对分配的右侧redzone区域进行毒化,防止越界写入右侧内存区域
PoisonShadow(tail_beg, tail_end - tail_beg, kAsanHeapLeftRedzoneMagic);
}
// 计算对齐后的用户区域大小,向下对齐到ASAN_SHADOW_GRANULARITY的倍数
uptr size_rounded_down_to_granularity = RoundDownTo(size, ASAN_SHADOW_GRANULARITY);
// 如果对齐后的大小不为0,则将用户实际可用的内存标记为可访问状态(即影子内存设置为0)
if
(size_rounded_down_to_granularity)
PoisonShadow(user_beg, size_rounded_down_to_granularity, 0);
...
// 运行malloc钩子,通常用于调试或分析内存分配行为
RunMallocHooks(res, size);
// 返回分配的内存地址
return
res;
}
// -------------------- 分配/释放例程 ---------------
void
*Allocate(uptr size, uptr alignment, BufferedStackTrace *stack,
AllocType alloc_type,
bool
can_fill) {
// 如果AddressSanitizer (ASan) 未初始化,则进行初始化
// UNLIKELY 用于优化分支预测,表示该条件不太可能为真
if
(UNLIKELY(!AsanInited()))
AsanInitFromRtl();
// 初始化 ASan 运行时库
...
// 如果使用的是次分配器(from_primary为false)或影子内存尚未被污染(即影子内存值为0),则对内存进行标记(污染)
// MEM_TO_SHADOW将分配的实际内存地址映射到影子内存地址
if
(!from_primary || *(u8 *)MEM_TO_SHADOW((uptr)allocated) == 0) {
// 计算分配的用户区域结束地址,向上对齐到ASAN_SHADOW_GRANULARITY的倍数(通常为8字节)
uptr tail_beg = RoundUpTo(user_end, ASAN_SHADOW_GRANULARITY);
// 计算实际分配的内存块结束地址,包括分配器的管理开销
uptr tail_end = alloc_beg + allocator.GetActuallyAllocatedSize(allocated);
// 对分配的左侧redzone区域进行毒化,防止越界写入左侧内存区域
PoisonShadow(alloc_beg, user_beg - alloc_beg, kAsanHeapLeftRedzoneMagic);
// 对分配的右侧redzone区域进行毒化,防止越界写入右侧内存区域
PoisonShadow(tail_beg, tail_end - tail_beg, kAsanHeapLeftRedzoneMagic);
}
// 计算对齐后的用户区域大小,向下对齐到ASAN_SHADOW_GRANULARITY的倍数
uptr size_rounded_down_to_granularity = RoundDownTo(size, ASAN_SHADOW_GRANULARITY);
// 如果对齐后的大小不为0,则将用户实际可用的内存标记为可访问状态(即影子内存设置为0)
if
(size_rounded_down_to_granularity)
PoisonShadow(user_beg, size_rounded_down_to_granularity, 0);
...
// 运行malloc钩子,通常用于调试或分析内存分配行为
RunMallocHooks(res, size);
// 返回分配的内存地址
return
res;
}
int
__cdecl main(
int
argc,
const
char
**argv,
const
char
**envp)
{
unsigned
int
*ptr;
// [rsp+8h] [rbp-8h]
ptr =
malloc
(4uLL);
*ptr = 42;
free
(ptr);
printf
(
"%d\n"
, *ptr);
return
0;
}
int
__cdecl main(
int
argc,
const
char
**argv,
const
char
**envp)
{
unsigned
int
*ptr;
// [rsp+8h] [rbp-8h]
ptr =
malloc
(4uLL);
*ptr = 42;
free
(ptr);
printf
(
"%d\n"
, *ptr);
return
0;
}
int
__cdecl main(
int
argc,
const
char
**argv,
const
char
**envp)
{
unsigned
int
*v3;
// rax
unsigned
int
*ptr;
// [rsp+8h] [rbp-8h]
v3 =
malloc
(4uLL);
ptr = v3;
// 检查影子内存
if
(*((v3 >> 3) + 0x7FFF8000) != 0 && ((v3 & 7) + 3) >= *((v3 >> 3) + 0x7FFF8000)) {
// 报告堆内存越界
__asan_report_store4(v3);
}
*ptr = 42;
free
(ptr);
// 释放堆块
// 检查访问已释放的堆块
if
(*((ptr >> 3) + 0x7FFF8000) != 0 && ((ptr & 7) + 3) >= *((ptr >> 3) + 0x7FFF8000)) {
// 报告堆内存越界
__asan_report_load4(ptr);
}
printf
(
"%d\n"
, *ptr);
return
0;
}
int
__cdecl main(
int
argc,
const
char
**argv,
const
char
**envp)
{
unsigned
int
*v3;
// rax
unsigned
int
*ptr;
// [rsp+8h] [rbp-8h]
v3 =
malloc
(4uLL);
ptr = v3;
// 检查影子内存
if
(*((v3 >> 3) + 0x7FFF8000) != 0 && ((v3 & 7) + 3) >= *((v3 >> 3) + 0x7FFF8000)) {
// 报告堆内存越界
__asan_report_store4(v3);
}
*ptr = 42;
free
(ptr);
// 释放堆块
// 检查访问已释放的堆块
if
(*((ptr >> 3) + 0x7FFF8000) != 0 && ((ptr & 7) + 3) >= *((ptr >> 3) + 0x7FFF8000)) {
// 报告堆内存越界
__asan_report_load4(ptr);
}
printf
(
"%d\n"
, *ptr);
return
0;
}
// 拦截器函数:替代 free 函数的实现
INTERCEPTOR(
void
,
free
,
void
*ptr) {
// 如果指针来自自定义分配器,则使用自定义释放逻辑
if
(DlsymAlloc::PointerIsMine(ptr))
return
DlsymAlloc::Free(ptr);
GET_STACK_TRACE_FREE;
// 获取堆栈信息
asan_free(ptr, &stack, FROM_MALLOC);
// 使用 ASan 的内存释放逻辑
}
// 拦截器函数:替代 free 函数的实现
INTERCEPTOR(
void
,
free
,
void
*ptr) {
// 如果指针来自自定义分配器,则使用自定义释放逻辑
if
(DlsymAlloc::PointerIsMine(ptr))
return
DlsymAlloc::Free(ptr);
GET_STACK_TRACE_FREE;
// 获取堆栈信息
asan_free(ptr, &stack, FROM_MALLOC);
// 使用 ASan 的内存释放逻辑
}
//源码:asan_allocator.cpp
...
static
Allocator instance(LINKER_INITIALIZED);
...
void
asan_free(
void
*ptr, BufferedStackTrace *stack, AllocType alloc_type) {
instance.Deallocate(ptr, 0, 0, stack, alloc_type);
}
...
//源码:asan_allocator.cpp
...
static
Allocator instance(LINKER_INITIALIZED);
...
void
asan_free(
void
*ptr, BufferedStackTrace *stack, AllocType alloc_type) {
instance.Deallocate(ptr, 0, 0, stack, alloc_type);
}
...
struct
Allocator {
// 最大允许的 malloc 分配大小
static
const
uptr kMaxAllowedMallocSize = FIRST_32_SECOND_64(
3UL << 30, 1ULL << 40);
// 32 位系统为 3GB,64 位系统为 1TB
// ASan 内存分配器,用于实际执行内存分配和释放操作
AsanAllocator allocator;
// ASan 的隔离区,用于延迟释放内存以检测 UAF(Use After Free)
AsanQuarantine quarantine;
// 互斥锁,用于保护分配器的回退操作
StaticSpinMutex fallback_mutex;
// 分配器缓存,用于优化分配操作
AllocatorCache fallback_allocator_cache;
// 隔离区缓存,用于优化隔离区操作
QuarantineCache fallback_quarantine_cache;
// 用户定义的最大 malloc 分配大小
uptr max_user_defined_malloc_size;
...
}
struct
Allocator {
// 最大允许的 malloc 分配大小
static
const
uptr kMaxAllowedMallocSize = FIRST_32_SECOND_64(
3UL << 30, 1ULL << 40);
// 32 位系统为 3GB,64 位系统为 1TB
// ASan 内存分配器,用于实际执行内存分配和释放操作
AsanAllocator allocator;
// ASan 的隔离区,用于延迟释放内存以检测 UAF(Use After Free)
AsanQuarantine quarantine;
// 互斥锁,用于保护分配器的回退操作
StaticSpinMutex fallback_mutex;
// 分配器缓存,用于优化分配操作
AllocatorCache fallback_allocator_cache;
// 隔离区缓存,用于优化隔离区操作
QuarantineCache fallback_quarantine_cache;
// 用户定义的最大 malloc 分配大小
uptr max_user_defined_malloc_size;
...
}
void
Deallocate(
void
*ptr, uptr delete_size, uptr delete_alignment,
BufferedStackTrace *stack, AllocType alloc_type) {
...
// 将内存块放入隔离区
QuarantineChunk(m, ptr, stack);
}
void
Deallocate(
void
*ptr, uptr delete_size, uptr delete_alignment,
BufferedStackTrace *stack, AllocType alloc_type) {
...
// 将内存块放入隔离区
QuarantineChunk(m, ptr, stack);
}
#include "asan_mapping.h" // 包含 AddressSanitizer (ASan) 的内存映射定义
#include "sanitizer_common/sanitizer_asm.h" // 包含常见的 Sanitizer 汇编工具
// 如果是 x86_64 架构
#if defined(__x86_64__)
#include "sanitizer_common/sanitizer_platform.h" // 包含平台相关的工具
// 指定汇编文件名称
.file
"asan_rtl_x86_64.S"
// 定义宏,用于生成函数、返回标签、检查标签、失败标签的名称
#define NAME(n, reg, op, s, i) n##_##op##_##i##_##s##_##reg
// FNAME 是用于生成 ASan 检查函数名称的宏
#define FNAME(reg, op, s, i) NAME(__asan_check, reg, op, s, i)
// RLABEL 是用于生成返回标签的宏
#define RLABEL(reg, op, s, i) NAME(.return, reg, op, s, i)
// CLABEL 是用于生成检查标签的宏
#define CLABEL(reg, op, s, i) NAME(.check, reg, op, s, i)
// FLABEL 是用于生成失败标签的宏
#define FLABEL(reg, op, s, i) NAME(.fail, reg, op, s, i)
// BEGINF 定义一个函数的开始,它会生成相应的代码段、全局标签、隐藏标签、函数类型和开始指令
#define BEGINF(reg, op, s, i) \
.section .text.FNAME(reg, op, s, i),
"ax"
,@progbits ;\
.globl FNAME(reg, op, s, i) ;\
.hidden FNAME(reg, op, s, i) ;\
ASM_TYPE_FUNCTION(FNAME(reg, op, s, i)) ;\
.cfi_startproc ;\
FNAME(reg, op, s, i): ;\
// ENDF 宏定义了函数的结束,它标记了调试信息的结束
#define ENDF .cfi_endproc ;\
// ASAN_MEMORY_ACCESS_INITIAL_CHECK_ADD 用于初始化内存访问检查
#define ASAN_MEMORY_ACCESS_INITIAL_CHECK_ADD(reg, op, s) \
mov %##reg,%r10 ;\
// 将寄存器值移动到 r10
shr $0x3,%r10 ;\
// 将 r10 右移 3 位
.
if
ASAN_SHADOW_OFFSET_CONST < 0x80000000 ;\
movsbl ASAN_SHADOW_OFFSET_CONST(%r10),%r10d ;\
// 进行 1 字节符号扩展并加载
.
else
;\
movabsq $ASAN_SHADOW_OFFSET_CONST,%r11 ;\
// 将常量加载到 r11 中
movsbl (%r10,%r11),%r10d ;\
// 进行内存访问,加载偏移量
.endif ;\
test %r10d,%r10d ;\
// 测试 r10d 是否为 0
jne CLABEL(reg, op, s, add) ;\
// 如果不为 0,跳转到检查标签
RLABEL(reg, op, s, add): ;\
// 返回标签
retq ;\
// 返回指令
// 额外的内存访问检查,用于 1 字节的访问
#define ASAN_MEMORY_ACCESS_EXTRA_CHECK_1(reg, op, i) \
CLABEL(reg, op, 1, i): ;\
mov %##reg,%r11 ;\
// 将 reg 的值移动到 r11
and $0x7,%r11d ;\
// r11d 与 0x7 进行按位与操作
cmp %r10d,%r11d ;\
// 比较 r10d 和 r11d
jl RLABEL(reg, op, 1, i);\
// 如果 r11d 小于 r10d,跳转到返回标签
mov %##reg,%rdi ;\
// 将 reg 移动到 rdi
jmp __asan_report_##op##1_asm ;\
// 跳转到 ASan 错误报告函数
// 额外的内存访问检查,用于 2 字节的访问
#define ASAN_MEMORY_ACCESS_EXTRA_CHECK_2(reg, op, i) \
CLABEL(reg, op, 2, i): ;\
mov %##reg,%r11 ;\
and $0x7,%r11d ;\
add $0x1,%r11d ;\
// r11d 加 1
cmp %r10d,%r11d ;\
jl RLABEL(reg, op, 2, i);\
mov %##reg,%rdi ;\
jmp __asan_report_##op##2_asm ;\
// 跳转到 2 字节的 ASan 错误报告
// 额外的内存访问检查,用于 4 字节的访问
#define ASAN_MEMORY_ACCESS_EXTRA_CHECK_4(reg, op, i) \
CLABEL(reg, op, 4, i): ;\
mov %##reg,%r11 ;\
and $0x7,%r11d ;\
add $0x3,%r11d ;\
// r11d 加 3
cmp %r10d,%r11d ;\
jl RLABEL(reg, op, 4, i);\
mov %##reg,%rdi ;\
jmp __asan_report_##op##4_asm ;\
// 跳转到 4 字节的 ASan 错误报告
// 定义 1 字节的加载和存储回调函数
#define ASAN_MEMORY_ACCESS_CALLBACK_ADD_1(reg, op) \
BEGINF(reg, op, 1, add) ;\
ASAN_MEMORY_ACCESS_INITIAL_CHECK_ADD(reg, op, 1) ;\
ASAN_MEMORY_ACCESS_EXTRA_CHECK_1(reg, op, add) ;\
ENDF
// 定义 2 字节的加载和存储回调函数
#define ASAN_MEMORY_ACCESS_CALLBACK_ADD_2(reg, op) \
BEGINF(reg, op, 2, add) ;\
ASAN_MEMORY_ACCESS_INITIAL_CHECK_ADD(reg, op, 2) ;\
ASAN_MEMORY_ACCESS_EXTRA_CHECK_2(reg, op, add) ;\
ENDF
// 定义 4 字节的加载和存储回调函数
#define ASAN_MEMORY_ACCESS_CALLBACK_ADD_4(reg, op) \
BEGINF(reg, op, 4, add) ;\
ASAN_MEMORY_ACCESS_INITIAL_CHECK_ADD(reg, op, 4) ;\
ASAN_MEMORY_ACCESS_EXTRA_CHECK_4(reg, op, add) ;\
ENDF
// 定义 8 字节的内存访问检查函数,不需要额外的检查
#define ASAN_MEMORY_ACCESS_CHECK_ADD(reg, op, s, c) \
mov %##reg,%r10 ;\
shr $0x3,%r10 ;\
.
if
ASAN_SHADOW_OFFSET_CONST < 0x80000000 ;\
##c $0x0,ASAN_SHADOW_OFFSET_CONST(%r10) ;\
.
else
;\
movabsq $ASAN_SHADOW_OFFSET_CONST,%r11 ;\
##c $0x0,(%r10,%r11) ;\
.endif ;\
jne FLABEL(reg, op, s, add) ;\
// 如果检查不通过,跳转到失败标签
retq ;\
// 返回指令
// 失败处理函数,用于内存访问失败的情况
#define ASAN_MEMORY_ACCESS_FAIL(reg, op, s, i) \
FLABEL(reg, op, s, i): ;\
mov %##reg,%rdi ;\
// 将 reg 移动到 rdi
jmp __asan_report_##op##s##_asm;\
// 跳转到 ASan 错误报告函数
// 定义 8 字节的回调函数
#define ASAN_MEMORY_ACCESS_CALLBACK_ADD_8(reg, op) \
BEGINF(reg, op, 8, add) ;\
ASAN_MEMORY_ACCESS_CHECK_ADD(reg, op, 8, cmpb) ;\
ASAN_MEMORY_ACCESS_FAIL(reg, op, 8, add) ;\
ENDF
// 定义 16 字节的回调函数
#define ASAN_MEMORY_ACCESS_CALLBACK_ADD_16(reg, op) \
BEGINF(reg, op, 16, add) ;\
ASAN_MEMORY_ACCESS_CHECK_ADD(reg, op, 16, cmpw) ;\
ASAN_MEMORY_ACCESS_FAIL(reg, op, 16, add) ;\
ENDF
// 定义所有类型的加载和存储回调函数,包括 1 字节、2 字节、4 字节、8 字节、16 字节
#define ASAN_MEMORY_ACCESS_CALLBACKS_ADD(reg) \
ASAN_MEMORY_ACCESS_CALLBACK_ADD_1(reg, load) \
ASAN_MEMORY_ACCESS_CALLBACK_ADD_1(reg, store) \
ASAN_MEMORY_ACCESS_CALLBACK_ADD_2(reg, load) \
ASAN_MEMORY_ACCESS_CALLBACK_ADD_2(reg, store) \
ASAN_MEMORY_ACCESS_CALLBACK_ADD_4(reg, load) \
ASAN_MEMORY_ACCESS_CALLBACK_ADD_4(reg, store) \
ASAN_MEMORY_ACCESS_CALLBACK_ADD_8(reg, load) \
ASAN_MEMORY_ACCESS_CALLBACK_ADD_8(reg, store) \
ASAN_MEMORY_ACCESS_CALLBACK_ADD_16(reg, load) \
ASAN_MEMORY_ACCESS_CALLBACK_ADD_16(reg, store) \
// 为除 R10 和 R11 外的所有寄存器实例化内存访问回调函数
ASAN_MEMORY_ACCESS_CALLBACKS_ADD(RAX)
ASAN_MEMORY_ACCESS_CALLBACKS_ADD(RBX)
ASAN_MEMORY_ACCESS_CALLBACKS_ADD(RCX)
ASAN_MEMORY_ACCESS_CALLBACKS_ADD(RDX)
ASAN_MEMORY_ACCESS_CALLBACKS_ADD(RSI)
ASAN_MEMORY_ACCESS_CALLBACKS_ADD(RDI)
ASAN_MEMORY_ACCESS_CALLBACKS_ADD(RBP)
ASAN_MEMORY_ACCESS_CALLBACKS_ADD(R8)
ASAN_MEMORY_ACCESS_CALLBACKS_ADD(R9)
ASAN_MEMORY_ACCESS_CALLBACKS_ADD(R12)
ASAN_MEMORY_ACCESS_CALLBACKS_ADD(R13)
ASAN_MEMORY_ACCESS_CALLBACKS_ADD(R14)
ASAN_MEMORY_ACCESS_CALLBACKS_ADD(R15)
#endif
// 指定不可执行堆栈
NO_EXEC_STACK_DIRECTIVE
#include "asan_mapping.h" // 包含 AddressSanitizer (ASan) 的内存映射定义
#include "sanitizer_common/sanitizer_asm.h" // 包含常见的 Sanitizer 汇编工具
// 如果是 x86_64 架构
#if defined(__x86_64__)
#include "sanitizer_common/sanitizer_platform.h" // 包含平台相关的工具
// 指定汇编文件名称
.file
"asan_rtl_x86_64.S"
// 定义宏,用于生成函数、返回标签、检查标签、失败标签的名称
#define NAME(n, reg, op, s, i) n##_##op##_##i##_##s##_##reg
// FNAME 是用于生成 ASan 检查函数名称的宏
#define FNAME(reg, op, s, i) NAME(__asan_check, reg, op, s, i)
// RLABEL 是用于生成返回标签的宏
#define RLABEL(reg, op, s, i) NAME(.return, reg, op, s, i)
// CLABEL 是用于生成检查标签的宏
#define CLABEL(reg, op, s, i) NAME(.check, reg, op, s, i)
// FLABEL 是用于生成失败标签的宏
#define FLABEL(reg, op, s, i) NAME(.fail, reg, op, s, i)