1. 简介

KASAN (Kernel Address Sanitizer) 是一个动态检测内存错误的工具,主要功能是检查内存越界访问使用已释放的内存等问题。目前具体支持以下类型错误的检出:

Item Error Type
any out-of-bounds
buddy/slab slab-out-of-bounds
buddy/slab use-after-free
buddy/slab use-after-scope
global variable global-out-of-bounds
local variable stack-out-of-bounds
any alloca-out-of-bounds

它的核心思想,是给每 8 bytes 的 data,分配 1 byte 的 shadow。用 shadow 数据来标识 data 的访问权限和状态:

需要特别注意的是,shadow 并不是用每个 bit 来表示 1 byte data 的权限,而是用整体 8bit 的值来表示 8 bytes data 的访问权限。 一个 shadow 字节的合法取值如下:

0x0                     : 标识 8 bytes data 都能被访问
0x7 : 标识前 7 bytes data 都能被访问
0x6 : 标识前 6 bytes data 都能被访问
0x5 : 标识前 5 bytes data 都能被访问
0x4 : 标识前 4 bytes data 都能被访问
0x3 : 标识前 3 bytes data 都能被访问
0x2 : 标识前 2 bytes data 都能被访问
0x1 : 标识前 1 bytes data 都能被访问
0xFF : 标识全部 8 bytes data 都不能被访问

这里存在一个假设,就是 8 bytes data 里面只会存在上图所示的可访问情况,而不会出现有空洞间插的情况。这是基于现有 linux 内存分配不会出现其他异常情况而设定的。

如果全部8 bytes data 都不能被访问,除了 0xFF 还有其他的数值来标识,这样可以精确定义出更详细的错误类型:

#define KASAN_FREE_PAGE         0xFF  /* page was freed */
#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 */ /*
* Stack redzone shadow values
* (Those are compiler's ABI, don't change them)
*/
#define KASAN_STACK_LEFT 0xF1
#define KASAN_STACK_MID 0xF2
#define KASAN_STACK_RIGHT 0xF3
#define KASAN_STACK_PARTIAL 0xF4
#define KASAN_USE_AFTER_SCOPE 0xF8 /*
* alloca redzone shadow values
*/
#define KASAN_ALLOCA_LEFT 0xCA
#define KASAN_ALLOCA_RIGHT 0xCB

2. Shadow 区域初始化

因为每 8 字节的数据需要用 1 字节的 shadow 区域来存储权限,所以开启 KASAN 功能后系统中至少 1/9 的内存需要用来做 shadow。

Linux 在系统初始化的时候分配 shadow 需要的物理内存并将它们映射到对应的虚拟地址区域:

arch\x86\mm\kasan_init_64.c:

start_kernel() → setup_arch() → kasan_init():

void __init kasan_init(void)
{
int i;
void *shadow_cpu_entry_begin, *shadow_cpu_entry_end; #ifdef CONFIG_KASAN_INLINE
register_die_notifier(&kasan_die_notifier);
#endif memcpy(early_top_pgt, init_top_pgt, sizeof(early_top_pgt)); /*
* We use the same shadow offset for 4- and 5-level paging to
* facilitate boot-time switching between paging modes.
* As result in 5-level paging mode KASAN_SHADOW_START and
* KASAN_SHADOW_END are not aligned to PGD boundary.
*
* KASAN_SHADOW_START doesn't share PGD with anything else.
* We claim whole PGD entry to make things easier.
*
* KASAN_SHADOW_END lands in the last PGD entry and it collides with
* bunch of things like kernel code, modules, EFI mapping, etc.
* We need to take extra steps to not overwrite them.
*/
if (pgtable_l5_enabled()) {
void *ptr; ptr = (void *)pgd_page_vaddr(*pgd_offset_k(KASAN_SHADOW_END));
memcpy(tmp_p4d_table, (void *)ptr, sizeof(tmp_p4d_table));
set_pgd(&early_top_pgt[pgd_index(KASAN_SHADOW_END)],
__pgd(__pa(tmp_p4d_table) | _KERNPG_TABLE));
} load_cr3(early_top_pgt);
__flush_tlb_all(); /* (1) 首先将 kasan 有效区域的 mmu 映射 pgd 全部清零 */
clear_pgds(KASAN_SHADOW_START & PGDIR_MASK, KASAN_SHADOW_END); /* (2) 区域1:内核起始地址 - 线性映射起始地址。这段区域 shadow 都指向 zero 页面 */
kasan_populate_zero_shadow((void *)(KASAN_SHADOW_START & PGDIR_MASK),
kasan_mem_to_shadow((void *)PAGE_OFFSET)); /* (3) 区域2:线性映射区域。这块是最大的有内存访问区域,分配 shadow 内存,,并建立映射 */
for (i = 0; i < E820_MAX_ENTRIES; i++) {
if (pfn_mapped[i].end == 0)
break; map_range(&pfn_mapped[i]);
} shadow_cpu_entry_begin = (void *)CPU_ENTRY_AREA_BASE;
shadow_cpu_entry_begin = kasan_mem_to_shadow(shadow_cpu_entry_begin);
shadow_cpu_entry_begin = (void *)round_down((unsigned long)shadow_cpu_entry_begin,
PAGE_SIZE); shadow_cpu_entry_end = (void *)(CPU_ENTRY_AREA_BASE +
CPU_ENTRY_AREA_MAP_SIZE);
shadow_cpu_entry_end = kasan_mem_to_shadow(shadow_cpu_entry_end);
shadow_cpu_entry_end = (void *)round_up((unsigned long)shadow_cpu_entry_end,
PAGE_SIZE); /* (4) 区域3:线性映射结束地址 - percpu 变量区域起始地址。这段区域 shadow 都指向 zero 页面 */
kasan_populate_zero_shadow(
kasan_mem_to_shadow((void *)PAGE_OFFSET + MAXMEM),
shadow_cpu_entry_begin); /* (5) 区域4:percpu 变量区域。这块有内存访问区域,分配 shadow 内存,,并建立映射 */
kasan_populate_shadow((unsigned long)shadow_cpu_entry_begin,
(unsigned long)shadow_cpu_entry_end, 0); /* (6) 区域5:percpu 变量区域结束地址 - 内核映像区域起始地址。这段区域 shadow 都指向 zero 页面 */
kasan_populate_zero_shadow(shadow_cpu_entry_end,
kasan_mem_to_shadow((void *)__START_KERNEL_map)); /* (7) 区域6:内核映像区域。这块有内存访问区域,分配 shadow 内存,,并建立映射 */
kasan_populate_shadow((unsigned long)kasan_mem_to_shadow(_stext),
(unsigned long)kasan_mem_to_shadow(_end),
early_pfn_to_nid(__pa(_stext))); /* (8) 区域7:模块区域结束地址 - shadow区域结束地址。这段区域 shadow 都指向 zero 页面 */
kasan_populate_zero_shadow(kasan_mem_to_shadow((void *)MODULES_END),
(void *)KASAN_SHADOW_END); load_cr3(init_top_pgt);
__flush_tlb_all(); /*
* kasan_zero_page has been used as early shadow memory, thus it may
* contain some garbage. Now we can clear and write protect it, since
* after the TLB flush no one should write to it.
*/
/* (9) 把 zero 页面的 pte 映射关系建立起来 */
memset(kasan_zero_page, 0, PAGE_SIZE);
for (i = 0; i < PTRS_PER_PTE; i++) {
pte_t pte;
pgprot_t prot; prot = __pgprot(__PAGE_KERNEL_RO | _PAGE_ENC);
pgprot_val(prot) &= __default_kernel_pte_mask; pte = __pte(__pa(kasan_zero_page) | pgprot_val(prot));
set_pte(&kasan_zero_pte[i], pte);
}
/* Flush TLBs again to be sure that write protection applied. */
__flush_tlb_all(); init_task.kasan_depth = 0;
pr_info("KernelAddressSanitizer initialized\n");
}

系统给 shadow 区域定义了一个基地址 KASAN_SHADOW_OFFSET,任意数据需要查询 shadow 的值的话,用offset = 数据地址/8(右移3位),然后再加上基地址 KASAN_SHADOW_OFFSET:

static inline void *kasan_mem_to_shadow(const void *addr)
{
return (void *)((unsigned long)addr >> KASAN_SHADOW_SCALE_SHIFT)
+ KASAN_SHADOW_OFFSET;
}

实际上KASAN只关心内核部分的地址,所以有效的 shadow 区域为内核地址对应区域:KASAN_SHADOW_START - KASAN_SHADOW_END。

/* 地址0的 shadow = KASAN_SHADOW_OFFSET */
#define KASAN_SHADOW_OFFSET _AC(CONFIG_KASAN_SHADOW_OFFSET, UL)
#define KASAN_SHADOW_SCALE_SHIFT 3 /*
* Compiler uses shadow offset assuming that addresses start
* from 0. Kernel addresses don't start from 0, so shadow
* for kernel really starts from compiler's shadow offset +
* 'kernel address space start' >> KASAN_SHADOW_SCALE_SHIFT
*/
/* 内核起始地址 shadow = KASAN_SHADOW_OFFSET + (0xffff800000000000 >> 3) */
#define KASAN_SHADOW_START (KASAN_SHADOW_OFFSET + \
((-1UL << __VIRTUAL_MASK_SHIFT) >> \
KASAN_SHADOW_SCALE_SHIFT))
/*
* 47 bits for kernel address -> (47 - KASAN_SHADOW_SCALE_SHIFT) bits for shadow
* 56 bits for kernel address -> (56 - KASAN_SHADOW_SCALE_SHIFT) bits for shadow
*/
/* 内核结束地址 shadow = KASAN_SHADOW_OFFSET + (0xffffffffffffffff >> 3) */
#define KASAN_SHADOW_END (KASAN_SHADOW_START + \
(1ULL << (__VIRTUAL_MASK_SHIFT - \
KASAN_SHADOW_SCALE_SHIFT)))

3. 权限的判断

它的基本操作分为两部分:

  • 1、在内存的分配、释放、初始化的时候,对 shadow 中内存访问权限进行设置。
  • 2、在读写访问内存前,先对权限进行判断,如果不能访问则报错。

本节先对读写访问时加入权限判断的流程进行阐述。

3.1 read/write

在 GCC 4.8 引入了一个新的内存错误检测工具: AddressSanitizer。使用选项 -fsanitize=address 能打开此检测器。 该检测器会对访存指令插装,帮助快速检测堆、栈以及全局的缓冲区溢出,以及use-after-free bug。

AddressSanitizer 是最初由Google开发的,用于运行时检测C/C++程序中的内存错误。它采用了CTI(CompileTime Instrumentation)技术,即在编译时进行代码插入,运行速度快。

这部分的核心机制就是 gcc 在所有读写内存的访问之前插入了一个判断权限的钩子,基本原型如下:

/* 往 0xffff800012345678 地址写 5 */

mov x0, #0x5678
movk x0, #0x1234, lsl #16
movk x0, #0x8000, lsl #32
movk x0, #0xffff, lsl #48
mov w1, #0x5
bl __asan_store1 // Gcc AddressSanitizer 插入的权限检查函数
strb w1, [x0]

可以看到 gcc AddressSanitizer 功能会在所有的内存访问前插入桩函数 __asan_storexxx()__asan_loadxxx(),kasan 只需要在内核中实现这些函数就能进行权限判断。

内核中定义了一系列的这种宏来实现不同长度数据的读写权限判断:

mm\kasan\kasan.c:

#define DEFINE_ASAN_LOAD_STORE(size)					\
void __asan_load##size(unsigned long addr) \
{ \
check_memory_region_inline(addr, size, false, _RET_IP_);\
} \
EXPORT_SYMBOL(__asan_load##size); \
__alias(__asan_load##size) \
void __asan_load##size##_noabort(unsigned long); \
EXPORT_SYMBOL(__asan_load##size##_noabort); \
void __asan_store##size(unsigned long addr) \
{ \
check_memory_region_inline(addr, size, true, _RET_IP_); \
} \
EXPORT_SYMBOL(__asan_store##size); \
__alias(__asan_store##size) \
void __asan_store##size##_noabort(unsigned long); \
EXPORT_SYMBOL(__asan_store##size##_noabort) DEFINE_ASAN_LOAD_STORE(1);
DEFINE_ASAN_LOAD_STORE(2);
DEFINE_ASAN_LOAD_STORE(4);
DEFINE_ASAN_LOAD_STORE(8);
DEFINE_ASAN_LOAD_STORE(16);

其中的核心函数是 check_memory_region_inline() 用来检查不同长度数据的访问权限:

__asan_load##size()/__asan_store##size() → check_memory_region_inline():

static __always_inline void check_memory_region_inline(unsigned long addr,
size_t size, bool write,
unsigned long ret_ip)
{
if (unlikely(size == 0))
return; /* (1) 判断传入的地址转换成 shadow 地址后,是否合法 */
if (unlikely((void *)addr <
kasan_shadow_to_mem((void *)KASAN_SHADOW_START))) {
kasan_report(addr, size, write, ret_ip);
return;
} /* (2) 根据内存地址对应的shadow 值,判断内存的访问权限 */
if (likely(!memory_is_poisoned(addr, size)))
return; /* (3) 如果没有访问权限,构造出错报告 */
kasan_report(addr, size, write, ret_ip);
} ↓ static __always_inline bool memory_is_poisoned(unsigned long addr, size_t size)
{
/* (2.1) 数据长度为 1/2/4/8/16 的 shadow 值的读取 和 判断 */
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();
}
} /* (2.2) 其他更长数据长度的 shadow 值的读取 和 判断 */
return memory_is_poisoned_n(addr, size);
} |→ static __always_inline bool memory_is_poisoned_1(unsigned long addr)
{
/* (2.1.1.1) 出具长度为1,首先取出1个字节所在的完整8字节数据,所对应的1字节的shadow值 */
s8 shadow_value = *(s8 *)kasan_mem_to_shadow((void *)addr); /* (2.1.1.2) 因为8字节的必须是连续访问的,所以如果某个字节可以访问:
必须 shadow 中的值 > 1字节数据在8个字节中的offset
*/
if (unlikely(shadow_value)) {
s8 last_accessible_byte = addr & KASAN_SHADOW_MASK;
return unlikely(last_accessible_byte >= shadow_value);
} /* (2.1.1.3) shadow 值为 0,8个字节都能被访问,其中一个字节肯定能访问 */
return false;
} |→ static __always_inline bool memory_is_poisoned_2_4_8(unsigned long addr,
unsigned long size)
{
/* (2.1.2.1) 2/4/8字节数据,有跨两个8字节的情况:
步骤1:先取第一部分字节的权限
*/
u8 *shadow_addr = (u8 *)kasan_mem_to_shadow((void *)addr); /*
* Access crosses 8(shadow size)-byte boundary. Such access maps
* into 2 shadow bytes, so we need to check them both.
*/
/* (2.1.2.2) 第一字节 > 0,必须 shadow 中的值 > 1字节数据在8个字节中的offset,第一部分数据才可以访问
再继续判断最后字节的权限情况
*/
if (unlikely(((addr + size - 1) & KASAN_SHADOW_MASK) < size - 1))
return *shadow_addr || memory_is_poisoned_1(addr + size - 1); /* (2.1.2.3) 第一字节 = 0,8个字节都能被访问,第一部分字节肯定能访问
再继续判断最后字节的权限情况
*/
return memory_is_poisoned_1(addr + size - 1);
} |→ static __always_inline bool memory_is_poisoned_16(unsigned long addr)
{
/* (2.1.3.1) 16字节数据,有跨三个8字节的情况:
步骤1:先取第一、二个8字节的权限,必须为0
*/
u16 *shadow_addr = (u16 *)kasan_mem_to_shadow((void *)addr); /* Unaligned 16-bytes access maps into 3 shadow bytes. */
/* (2.1.3.2) 如果没有8字节对应,需要继续判断第三个8字节中最后字节的权限 */
if (unlikely(!IS_ALIGNED(addr, KASAN_SHADOW_SCALE_SIZE)))
return *shadow_addr || memory_is_poisoned_1(addr + 15); /* (2.1.3.3) 如果8字节对齐,第一、二个8字节的权限为0,才可以访问 */
return *shadow_addr;
}

最后一种最复杂的任意长度的权限判断:

static __always_inline bool memory_is_poisoned_n(unsigned long addr,
size_t size)
{
unsigned long ret; /* (2.1.4.1) 判断 数据的其实和结束 shadow 值是否都为 0 */
ret = memory_is_nonzero(kasan_mem_to_shadow((void *)addr),
kasan_mem_to_shadow((void *)addr + size - 1) + 1); /* (2.1.4.2) 不全为0,判断最后一个字节的权限情况 */
if (unlikely(ret)) {
unsigned long last_byte = addr + size - 1;
s8 *last_shadow = (s8 *)kasan_mem_to_shadow((void *)last_byte); /* 必须 shadow 中的值 > 1字节数据在8个字节中的offset */
if (unlikely(ret != (unsigned long)last_shadow ||
((long)(last_byte & KASAN_SHADOW_MASK) >= *last_shadow)))
return true;
} /* (2.1.4.3) 全为0,所有数据都可以访问 */
return false;
}

3.2 memxxx()

对于一系列 mem 开头的函数也进行了替换,插入了权限检查:

#undef memset
void *memset(void *addr, int c, size_t len)
{
check_memory_region((unsigned long)addr, len, true, _RET_IP_); return __memset(addr, c, len);
} #undef memmove
void *memmove(void *dest, const void *src, size_t len)
{
check_memory_region((unsigned long)src, len, false, _RET_IP_);
check_memory_region((unsigned long)dest, len, true, _RET_IP_); return __memmove(dest, src, len);
} #undef memcpy
void *memcpy(void *dest, const void *src, size_t len)
{
check_memory_region((unsigned long)src, len, false, _RET_IP_);
check_memory_region((unsigned long)dest, len, true, _RET_IP_); return __memcpy(dest, src, len);
}

4. 权限的设置

和权限的判断一样,权限的设定也需要在各种关键时刻的钩子中插入权限设定操作。

4.1 buddy

Buddy 系统在 free 和 alloc 的时间点上插入了权限设置,所以 buddy 能检测出 use-after-free 类型的错误。

4.1.1 kasan_free_pages()

__free_pages() → free_the_page() → __free_pages_ok() → free_pages_prepare() → kasan_free_nondeferred_pages() → kasan_free_pages():

void kasan_free_pages(struct page *page, unsigned int order)
{
/* (1) 在 page free 的时候,把 shadow 设置成 0xFF (KASAN_FREE_PAGE),不能访问 */
if (likely(!PageHighMem(page)))
kasan_poison_shadow(page_address(page),
PAGE_SIZE << order,
KASAN_FREE_PAGE);
}

4.1.2 kasan_alloc_pages()

alloc_pages() → alloc_pages_current() → __alloc_pages_nodemask() → get_page_from_freelist() → prep_new_page() → post_alloc_hook() → kasan_alloc_pages():

void kasan_alloc_pages(struct page *page, unsigned int order)
{
/* (1) 在 page alloc 的时候,把 shadow 设置成 0,可以访问 */
if (likely(!PageHighMem(page)))
kasan_unpoison_shadow(page_address(page), PAGE_SIZE << order);
}

4.2 slub

slub 在 malloc 和 free 的基础上,增加了 redzone 区域的检测。这样除了 use-after-free 以外还能检测出 slab-out-of-bounds 类型的错误。

4.2.1 kasan_cache_create()

计算出每个 object 额外要分配的 redzone 区间:

__kmem_cache_create() → kasan_cache_create()

4.2.2 kasan_slab_free()

kmem_cache_free() → slab_free() → slab_free_freelist_hook() → slab_free_hook() → kasan_slab_free() → __kasan_slab_free():

static bool __kasan_slab_free(struct kmem_cache *cache, void *object,
unsigned long ip, bool quarantine)
{
s8 shadow_byte;
unsigned long rounded_up_size; if (unlikely(nearest_obj(cache, virt_to_head_page(object), object) !=
object)) {
kasan_report_invalid_free(object, ip);
return true;
} /* RCU slabs could be legally used after free within the RCU period */
if (unlikely(cache->flags & SLAB_TYPESAFE_BY_RCU))
return false; /* (1) 判断 free 的 object 的 shadow 权限是否合法 */
shadow_byte = READ_ONCE(*(s8 *)kasan_mem_to_shadow(object));
if (shadow_byte < 0 || shadow_byte >= KASAN_SHADOW_SCALE_SIZE) {
kasan_report_invalid_free(object, ip);
return true;
} /* (2) 把数据区域的 shadow 设置成 0xFB (KASAN_KMALLOC_FREE),不能访问 */
rounded_up_size = round_up(cache->object_size, KASAN_SHADOW_SCALE_SIZE);
kasan_poison_shadow(object, rounded_up_size, KASAN_KMALLOC_FREE); if (!quarantine || unlikely(!(cache->flags & SLAB_KASAN)))
return false; set_track(&get_alloc_info(cache, object)->free_track, GFP_NOWAIT);
quarantine_put(get_free_info(cache, object), cache);
return true;
}

4.2.3 kasan_slab_alloc()

kmem_cache_alloc() → kasan_slab_alloc() → kasan_kmalloc():

void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size,
gfp_t flags)
{
unsigned long redzone_start;
unsigned long redzone_end; if (gfpflags_allow_blocking(flags))
quarantine_reduce(); if (unlikely(object == NULL))
return; redzone_start = round_up((unsigned long)(object + size),
KASAN_SHADOW_SCALE_SIZE);
redzone_end = round_up((unsigned long)object + cache->object_size,
KASAN_SHADOW_SCALE_SIZE); /* (1) 把数据区域的 shadow 设置成 0,可以访问 */
kasan_unpoison_shadow(object, size);
/* (2) 把数据后 redzone 区域的 shadow 设置成 0xFC (KASAN_KMALLOC_REDZONE),不能访问 */
kasan_poison_shadow((void *)redzone_start, redzone_end - redzone_start,
KASAN_KMALLOC_REDZONE); if (cache->flags & SLAB_KASAN)
set_track(&get_alloc_info(cache, object)->alloc_track, flags);
}

4.3 kmalloc

kmalloc 的原理和 slub 类似。

4.4 global variable

对于全局变量的保护,增加了redzone,所以能检测出 global-out-of-bounds 类型的错误。

4.4.1 struct kasan_global

开启了 kasan 功能以后,对每一个全局变量会扩充成一个复杂的结构,主要是增加了 redzone 区域:

struct kasan_global {
/* 全局变量起始地址 */
const void *beg; /* Address of the beginning of the global variable. */
/* 全局变量原有 size */
size_t size; /* Size of the global variable. */
/* 全局变量加上 redzone 以后的 size */
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
};

4.4.2 __asan_register_globals()

在系统初始化的时候,在调用构造函数时会调用到 kasan 全局变量的初始化函数:

start_kernel() → rest_init() → kernel_init() → kernel_init_freeable() → do_basic_setup() → do_ctors() → __asan_register_globals() → register_global():

static void register_global(struct kasan_global *global)
{
size_t aligned_size = round_up(global->size, KASAN_SHADOW_SCALE_SIZE); /* (1) 把全局变量的数据区域的 shadow 设置成 0,可以访问 */
kasan_unpoison_shadow(global->beg, global->size); /* (2) 把全局变量的 redzone 区域的 shadow 设置成 0xFA (KASAN_GLOBAL_REDZONE),不可以访问 */
kasan_poison_shadow(global->beg + aligned_size,
global->size_with_redzone - aligned_size,
KASAN_GLOBAL_REDZONE);
}

4.5 local variable

局部变量的保护也是使用插入 redzone 的形式来进行保护。但是这部分的调用时怎么串起来的,现在还没搞清楚。

4.5.1 例子

别人举的例子:

/* (1) c 语言 */
void foo()
{
char a[328];
} ↓ void foo()
{
char rz1[32]; // 编译器添加的redzone
char a[328];
char rz2[56]; // 编译器添加的redzone
int *shadow = (&rz1 >> 3)+ KASAN_SHADOW_OFFSE;
shadow[0] = 0xffffffff;
shadow[11] = 0xffffff00;
shadow[12] = 0xffffffff;
/*------------------------使用完毕----------------------------------------*/
shadow[0] = shadow[11] = shadow[12] = 0;
}

4.5.2 相关函数

内核定义了一些相关函数,但是怎么样和栈保护编译链接起来的还没研究:

static void __kasan_unpoison_stack(struct task_struct *task, const void *sp)
{
void *base = task_stack_page(task);
size_t size = sp - base; kasan_unpoison_shadow(base, size);
} /* Unpoison the entire stack for a task. */
void kasan_unpoison_task_stack(struct task_struct *task)
{
__kasan_unpoison_stack(task, task_stack_page(task) + THREAD_SIZE);
} /* Unpoison the stack for the current task beyond a watermark sp value. */
asmlinkage void kasan_unpoison_task_stack_below(const void *watermark)
{
/*
* Calculate the task stack base address. Avoid using 'current'
* because this function is called by early resume code which hasn't
* yet set up the percpu register (%gs).
*/
void *base = (void *)((unsigned long)watermark & ~(THREAD_SIZE - 1)); kasan_unpoison_shadow(base, watermark - base);
} /*
* Clear all poison for the region between the current SP and a provided
* watermark value, as is sometimes required prior to hand-crafted asm function
* returns in the middle of functions.
*/
void kasan_unpoison_stack_above_sp_to(const void *watermark)
{
const void *sp = __builtin_frame_address(0);
size_t size = watermark - sp; if (WARN_ON(sp > watermark))
return;
kasan_unpoison_shadow(sp, size);
}

4.6 vmalloc

因为 vmalloc 的分配和释放都是以 page 为单位的,所以他的 kasan 保护沿用 buddy 的就行了。

参考文档:

1.The Kernel Address Sanitizer (KASAN)
2.KASAN实现原理
3.内存管理三 内核内存检测KASAN
4.Kasan - Linux 内核的内存检测工具
5.Linux内核内存检测工具KASAN
6.linux内核(5.4.81)——KASAN
7.asan的接口变更
8.利用Address Sanitizer工具检查内存访问错误
9.gcc address sanitizer

最新文章

  1. 解决maven下载jar慢的问题(如何更换Maven下载源)
  2. jarsigner签名报错Invalid keystore format
  3. div
  4. Java设计模式-状态模式(State)
  5. Linux字符设备和块设备的区别
  6. B题(覆盖问题)
  7. Crontab 计划任务
  8. 编写可维护的JS 03
  9. POJ1797 Heavy Transportation 【Dijkstra】
  10. 百度地图API详解之事件机制,function“闭包”解决for循环和监听器冲突的问题:
  11. Spring事务执行过程
  12. DOM事件类型总结大全
  13. jquery取出所有包含class=&#39;engineer_val&#39;的值
  14. HDU1222Wolf and Rabbit(GCD思维)
  15. ConstraintLayoutDemo【约束性布局知识梳理】【基于1.1.3】
  16. 【记录】IntelliJ IDEA—IDEA2018-2019激活
  17. o2o、c2c、b2c、b2b、b2b2c都是什么?
  18. pandas的to_csv()使用方法
  19. ascii码值
  20. 异常处理:net.sf.cglib.beans.BulkBeanException

热门文章

  1. GIS应用|快速搭建REST地图服务
  2. The art of multipropcessor programming 读书笔记-硬件基础2
  3. 洛谷 P1862 输油管道问题
  4. Midway Serverless 发布 2.0,一体化让前端研发再次提效
  5. ASP.NET Core Filter与IOC的羁绊
  6. The Data Way Vol.4|开源是创造软件诸多方法中最好的一种形式
  7. 浅析InnoDB引擎的索引和索引原理
  8. bzoj1341 名次排序问题rank sorting(dp,考虑到对未来的贡献)
  9. Java(26)集合一Collection
  10. Oracle-绑定执行计划