1
2
Reference:
https://cloud.tencent.com/developer/article/1381068 KASAN实现原理
一、KASAN概念
KASAN是一个动态检测内存错误的工具。KASAN可以 。功能比SLUB DEBUG齐全并且支持实时检测。
1
2
CONFIG_SLUB_DEBUG=y
CONFIG_KASAN=y
二、KASAN检测机制
在代码运行时,每一次memory access都会检测对应的shawdow memory的值是否valid。这就需要编译器为我们做些工作。编译的时候,在每一次memory access前编译器会帮我们插入__asan_load##size()或者__asan_store##size()函数调用(size是访问内存字节的数量)。这也是要求更新版本gcc的原因,只有更新的版本才支持自动插入。
1
2
3
4
5
6
7
mov x0, #0x5678
movk x0, #0x1234, lsl #16
movk x0, #0x8000, lsl #32
movk x0, #0xffff, lsl #48
mov w1, #0x5
bl __asan_store1 //对x0进行check
strb w1, [x0]
1)如何根据shadow memory的值判断内存访问操作是否valid?
1
2
__asan_load1/2/4/8/16 __asan_store1/2/4/8/16
->|check_memory_region_inline //write=false/true
1
2
3
check_memory_region_inline
-> addr < kasan_shadow_start? kasan_report : continue;
-> memory_is_poisoned(addr, size) ? kasan_report: return;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static __always_inline bool memory_is_poisoned(unsigned long addr, size_t size)
{
if (__builtin_constant_p(size)) { // 判断一个值是否为编译时常量
switch (size) {
case 1:
return memory_is_poisoned_1(addr);
case 2: case 4: case 8:
return memory_is_poisoned_2_4_8(addr, size);
case 16:
return memory_is_poisoned_16(addr);
default:
BUILD_BUG();
}
}
return memory_is_poisoned_n(addr, size); //编译时非常量
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static __always_inline bool memory_is_poisoned_n(unsigned long addr, size_t size)
{
unsigned long ret;
ret = memory_is_nonzero(kasan_mem_to_shadow((void *)addr), kasan_mem_to_shadow((void *)addr + size - 1) + 1);
if (unlikely(ret)) {
unsigned long last_byte = addr + size - 1;
s8 *last_shadow = (s8 *)kasan_mem_to_shadow((void *)last_byte);
//KASAN_SHADOW_MASK = 2^KASAN_SHADOW_SCALE_SHIFT(3) - 1 = 7
//1. 前面整8bytes非全0, 不可访问,直接true, report
//2. if (*shadow && *shadow < ((unsigned long)addr & 7) + N); //N = 1, 校验最后1bytes是否可访问? true repore
if (unlikely(ret != (unsigned long)last_shadow ||
((long)(last_byte & KASAN_SHADOW_MASK) >= *last_shadow)))
return true;
}
return false;
}
memory_is_poisoned_16
-> addr aligned 8 ? *(u16*)shadow_addr : (*shadow_addr || memory_is_poisoned_1(addr + 15)) //addr 8对齐,2个shadow memory。不对齐,3个shadow memory
memory_is_poisoned_2_4_8
-> (addr + size - 1)&KASAN_SHADOW_MASK >= size - 1 ? memory_is_poisoned_1(addr + size - 1) : *shadow_addr || memory_is_poisoned_1(addr + size - 1) // 如果end_addr & 7 > size, 说明不跨shadow memory.
memory_is_poisoned_1
->shadow_value ? (addr&KASAN_SHADOW_MASK > shadow_value) : false //shadow_value = 0, ok; shadow_memory < addr&KASAN_SHADOW_MASK, report; 当前free的小于 addr的地址, 表明该地址used状态,不能load/store, kasan_report
2) shadow memory内存如何分配?
1 arm64虚拟地址布局
VA_BITS配置成48。那么kernel space空间大小是256TB,因此shadow memory的内存需要32TB。我们需要在虚拟地址空间为KASAN shadow memory分配地址空间.
2 如何建立shadow memory的映射关系
shadow_addr = (kaddr >> 3) + KASAN_SHADOW_OFFSE (KASAN_SHADOW_OFFSET=0xdfff200000000000)
3) 伙伴系统shadow memory填充什么数据检查?
1 伙伴系统alloc后填充0
伙伴系统alloc后填充0,表明该区域可读写。
2 伙伴系统free
#define KASAN_FREE_PAGE 0xFF /* page was freed */
伙伴系统free后填充ff,表明该区域已经free,依然访问内存的话,此时KASAN根据shadow memory的值是0xFF就可以断use-after-free问题。
4)slab对象shadow memory填充什么数据检查?
#define KASAN_PAGE_REDZONE 0xFE /* redzone for kmalloc_large allocations */
#define KASAN_KMALLOC_REDZONE 0xFC /* redzone inside slub object */
#define KASAN_KMALLOC_FREE 0xFB /* object was freed (kmem_cache_free/kfree) */
#define KASAN_GLOBAL_REDZONE 0xFA /* redzone for global variable */
1 打开slab_debug
2 kmem_cache_create填充FC
当我们第一次创建slab缓存池的时候,系统会调用kasan_poison_slab()函数初始化shadow memory为下图的模样。整个slab对应的shadow memory都填充0xFC。
3 kmem_cache_alloc填充0
kmem_cache_alloc填充0,表明该内存访问vaild。
kmalloc(20) – 00 00 04 FC 32bytes中20bytes可访问。
4 kmem_cache_free填充FB
kmem_cache_free填充FB,表明该内存已经free。
5) 全局变量填充FA
方便定位全局变量越界写的,增加额外信息记录全局变量的模块、文件、行号等信息。
__asan_register_globals
->register_global
/* The layout of struct dictated by compiler */
struct kasan_global {
const void *beg; /* Address of the beginning of the global variable. */
size_t size; /* Size of the global variable. */
size_t size_with_redzone; /* Size of the variable + size of the red zone. 32 bytes aligned */
const void *name;
const void *module_name; /* Name of the module where the global variable is declared. */
unsigned long has_dynamic_init; /* This needed for C++ */
#if KASAN_ABI_VERSION >= 4
struct kasan_source_location *location;
#endif
#if KASAN_ABI_VERSION >= 5
char *odr_indicator;
#endif
};
/* The layout of struct dictated by compiler */
struct kasan_source_location {
const char *filename; //记录变量的文件
int line_no; //记录变量的行号
int column_no; //记录变量的列号
};