文章目录
-
重要:本系列文章内容摘自
<Linux内核深度解析>
基于ARM64架构的Linux4.x内核一书,作者余华兵。系列文章主要用于记录Linux内核的大部分机制及参数的总结说明
1 内存错误检测工具KASAN
内核地址消毒剂(Kernel Address SANitizer,KASAN)是一个动态的内存错误检查工具,为发现“释放后使用”和“越界访问”这两类缺陷提供了快速和综合的解决方案。
KASAN使用编译时插桩(compile-time instrumentation)检查每个内存访问,要求GCC编译器的版本至少是4.9.2,检查栈或全局变量的越界访问需要GCC编译器的版本至少是5.0。
内核支持KASAN的进展如下:
(1)4.0版本引入KASAN,仅x86_64架构支持,只有SLUB分配器支持KASAN。
(2)4.4版本的ARM64架构支持KASAN。
(3)4.6版本的SLAB分配器支持KASAN。
1.1 使用方法
编译内核时需要开启以下配置宏:
(1)有些版本要求先开启SLUB分配器的调试配置宏CONFIG_SLUB_DEBUG,才能看到KASAN的配置菜单项。最新版本已经不需要了,但是建议开启,因为可以打印更多有用的信息。
(2)开启配置宏CONFIG_KASAN。
(3)选择编译时插桩类型:配置宏CONFIG_KASAN_OUTLINE启用外联插桩,配置宏CONFIG_KASAN_INLINE启用内联插桩。外联插桩编译生成的程序小,内联插桩编译生成的程序运行速度快。内联手段需要GCC编译器的版本至少是5.0。
(4)为了更好地缺陷检查和报告,开启配置宏CONFIG_STACKTRACE。
如果需要为特定文件或目录禁止编译时插桩,在内核的Makefile文件中添加。
(1)为单个文件,例如main.o:KASAN_SANITIZE_main.o := n。
(2)为某个目录里面的所有文件:KASAN_SANITIZE := n。
1.2 技术原理
KASAN使用影子内存(shadow memory)记录内存的每个字节是否可以安全访问,使用编译时插桩在每次访问内存时检查影子内存。
KASAN使用内核地址空间的1/8作为影子内存,影子内存的每个字节记录内存连续8字节的状态。
(1)如果8字节都可以访问,那么影子内存的值是0。
(2)如果连续n(1n7)字节可以访问,那么影子内存的值是n。
(3)如果8字节都不能访问,那么影子内存的值是负数,使用不同的负数区分不同类型的不可访问内存,其代码如下:
#define KASAN_FREE_PAGE 0xFF /* 页已被释放 */
#define KASAN_PAGE_REDZONE 0xFE /* kmalloc_large()分配的红色区域 */
#define KASAN_KMALLOC_REDZONE 0xFC /* SLAB对象里面的红色区域 */
#define KASAN_KMALLOC_FREE 0xFB /* SLAB对象已被释放 */
#define KASAN_GLOBAL_REDZONE 0xFA /* 全局变量的红色区域 */
把内存的内核虚拟地址转换成影子地址的方法如下:
static inline void *kasan_mem_to_shadow(const void *addr)
{
return (void *)((unsigned long)addr >> KASAN_SHADOW_SCALE_SHIFT)
+ KASAN_SHADOW_OFFSET;
}
其中KASAN_SHADOW_SCALE_SHIFT的值是3。
KASAN使用编译时插桩检查内存访问,编译内核时需要指定编译选项“-fsanitize=kernel- address”。编译器在加载指令的前面插入函数调用“__asan_loadN(addr)”来检查内存访问是否合法,在存储指令的前面插入函数调用“__asan_storeN(addr)”来检查内存访问是否合法,N是字节数,可能是1、2、4、8或16。如果加载指令从内存加载1字节,那么编译器在加载指令的前面插入函数调用“__asan_load1(addr)”。如果存储指令存储1字节到内存中,那么编译器在存储指令的前面插入函数调用“__asan_store1(addr)”。