KASAN基本原理
KernelAddressSANitizer(KASAN)是一个动态检测内存错误的工具。它为找到use-after-free和out-of-bounds问题提供了一个快速和全面的解决方案。
KASAN本质是一个编译插桩的工作,其将八分之一的内存拿出来当作影子内存,然后每一个字节表示8字节是否可以被访问,其中每个影子内存的字节可以被填入以下值,成为”红区“:
具体一点讲,就是再内存分配和释放的节点插桩,生成所谓的”红区“,然后在所有访存的地方也插桩,检查是否访问到了”红区“,如果访问到了”红区“就会报错。
内核虚拟地址空间最低的位置;
KASAN检测
KASAN填充
对于buddy-system,分配page后找到对应的shadow memory,系统将他们全部设置为0,表示可以访问;对于释放page,会填充对应的shadow memory填充为0xFF,这样就可以判断是否发生了UAF;
对于slab分配和释放,SLAB_DEBUG和KASAN这两个篇配置选项会导致obj的内存增加很多,就是有很多其他的防止溢出的区域;当我们第一次创建slab缓存池的时候,系统会调用kasan_poison_slab()函数初始化shadow memory,整个slab对应的shadow memory都填充0xFC;
kmalloc的时候会初始化object,比如kmalloc(20),就会有32-20 == 12字节的多余空间,32字节对应4字节的shadow memory,前16字节可以访问,所以前两个字节的shadow memory是0,第三个shadow memory的字节比较复杂,所以要04;object对应的4 bytes shadow memory分别填充00 00 04 FC;
kfree的时候,就将对应的shadow memory的空间填充为0xFB表示不可访问。
对于全局变量,编译器会在定义全局变量的时候添加一个redzone:
编译器会为每一个全局变量创建一个函数,函数名称是:_GLOBAL__sub_I_65535_1_##global_variable_name,这个函数中通过调用__asan_register_globals()函数完成shadow memory标记。并且将自动生成的这个函数的首地址放在.init_array段。在kernel启动阶段,通过以下代调用关系最终调用所有全局变量的构造函数。kernel_init_freeable()->do_basic_setup() ->do_ctors()。
栈分配变量:
编译选项
CONFIG_SLUB_DEBUG=y
CONFIG_KASAN=y
效果
下面是笔者针对CVE-2023-0461的double-free,访问已经被释放的空间,被KASAN捕获的结果: