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

Linux mem 2.7 内存错误检测 (KASAN) 详解的更多相关文章

  1. Linux内存管理之mmap详解

    转发之:http://blog.chinaunix.net/uid-26669729-id-3077015.html Linux内存管理之mmap详解 一. mmap系统调用 1. mmap系统调用  ...

  2. Linux 内存管理之mmap详解

    找了好多,最后发现下面这篇时讲的比较通俗易懂的. Linux内存管理之mmap详解-heavent2010-ChinaUnix博客 http://blog.chinaunix.net/uid-2666 ...

  3. Linux下的I/O复用与epoll详解(转载)

    Linux下的I/O复用与epoll详解 转载自:https://www.cnblogs.com/lojunren/p/3856290.html  前言 I/O多路复用有很多种实现.在linux上,2 ...

  4. linux管道命令grep命令参数及用法详解---附使用案例|grep

    功能说明:查找文件里符合条件的字符串. 语 法:grep [-abcEFGhHilLnqrsvVwxy][-A<显示列数>][-B<显示列数>][-C<显示列数>] ...

  5. Linux下nginx编译安装教程和编译参数详解

    这篇文章主要介绍了Linux下nginx编译安装教程和编译参数详解,需要的朋友可以参考下 一.必要软件准备1.安装pcre 为了支持rewrite功能,我们需要安装pcre 复制代码代码如下: # y ...

  6. 黑马-----内存模型和volatile详解

    黑马程序员:Java培训.Android培训.iOS培训..Net培训 JAVA线程-内存模型和volatile详解 一.单核内存模型 1.程序运行时,将临时数据存放到Cache中 2.将CPU计算所 ...

  7. 【转】linux之cp/scp命令+scp命令详解

    linux之cp/scp命令+scp命令详解   名称:cp 使用权限:所有使用者 使用方式: cp [options] source dest cp [options] source... dire ...

  8. linux ssh使用深度解析(key登录详解)

    linux ssh使用深度解析(key登录详解) SSH全称Secure SHell,顾名思义就是非常安全的shell的意思,SSH协议是IETF(Internet Engineering Task ...

  9. Java内存模型相关原则详解

    在<Java内存模型(JMM)详解>一文中我们已经讲到了Java内存模型的基本结构以及相关操作和规则.而Java内存模型又是围绕着在并发过程中如何处理原子性.可见性以及有序性这三个特征来构 ...

随机推荐

  1. 鸿蒙内核源码分析(构建工具篇) | 顺瓜摸藤调试鸿蒙构建过程 | 百篇博客分析OpenHarmony源码 | v59.01

    百篇博客系列篇.本篇为: v59.xx 鸿蒙内核源码分析(构建工具篇) | 顺瓜摸藤调试鸿蒙构建过程 | 51.c.h.o 编译构建相关篇为: v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿 ...

  2. P7443-加边【博弈论】

    正题 题目链接:https://www.luogu.com.cn/problem/P7443?contestId=41429 题目大意 \(n\)个点的一棵有根树,两个人从一号点开始进行有向图博弈. ...

  3. Apache Struts2 S2-013远程代码执行漏洞复现

    墨者学院开的靶场 进入环境 Struts2-013好家伙,框架直接写脸上,怕人看不出来= = 看了看源码什么的啥都没发现= = 去了解了一下这个漏洞,爬回来继续做 漏洞原理 struts2的标签中&l ...

  4. 11.4.4 LVS-Fullnat

    lvs-fullnat(双向转换) 通过请求报文的源地址为DIP,目标为RIP来实现转发:对于响应报文而言,修改源地址为VIP,目标地址为CIP来实现转发: CIP --> DIP VIP -- ...

  5. 安全通信协议SSH应用与分析

    一.实验简介: 本次实验属于安全协议应用与分析系列 二 实验环境: Windows server 2003 server windows xp 做client 三.实验目的 通过该实验了解SSH服务器 ...

  6. Windows下的程序及热键监视神器——Spy++

    Windows下的程序及热键监视神器--Spy++ 背景 在使用Windows的时候,偶尔会发现某些应用程序的热键不生效了:又或是桌面弹出了弹框却并不知道这个弹框来自何处.例如,本人最近使用Vim的时 ...

  7. Mysql读写分离集群的搭建且与MyCat进行整合

    1. 概述 老话说的好:不熟悉的东西不要不懂装懂,做人要坦诚,知道就是知道,不知道就是不知道. 言归正传,今天我们来聊聊 Mysql主从读写分离集群是如何搭建的,并且聊一下如何用 MyCat 去访问这 ...

  8. Filter防火墙

    实验简介 实验属于防火墙系列 实验目的 了解个人防火墙的基本工作原理: 掌握Filter防火墙的配置. 实验环境 一台安装了win7操作系统的主机. 预备知识 防火墙 防火墙(Firewall)是一项 ...

  9. 2020.5.4-ICPC Pacific Northwest Regional Contest 2019

    A. Radio Prize All boring tree-shaped lands are alike, while all exciting tree-shaped lands are exci ...

  10. 以太坊web3开发初步学习

    以太坊web3开发初步学习 此文是对https://learnblockchain.cn/2018/04/15/web3-html/的学习再理解. 以太坊智能合约通过使用web3.js前端和智能合约交 ...