Home The Kernel Address Sanitizer (KASAN)
Post
Cancel

The Kernel Address Sanitizer (KASAN)

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

image-20210802154937981

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;  //记录变量的列号
};

5)栈分配变量的readzone是如何分配的?
This post is licensed under CC BY 4.0 by the author.