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. SAP Shared Object 01 (共享对象)

    介绍 共享对象是在共享内存中的一个对象.共享内存是应用服务器中的一个内存区域,可以被应用服务器中的所有程序访问. 在共享对象出现之前,ABAP使用EXPORT 和 IMPORT语句实现内存区域中内容的 ...

  2. YbtOJ#493-最大分数【斜率优化dp,分治】

    正题 题目链接:http://www.ybtoj.com.cn/contest/117/problem/1 题目大意 \(n\)个数的一个序列,给其中的一些数打上标记. 一个标记方案的贡献为\(s_1 ...

  3. 密码学系列之:1Password的加密基础PBKDF2

    目录 简介 PBKDF2和PBKDF1 PBKDF2的工作流程 详解PBKDF2的key生成流程 HMAC密码碰撞 PBKDF2的缺点 总结 简介 1password是一个非常优秀的密码管理软件,有了 ...

  4. 支持remote write和exemplar的prometheus服务

    最近项目组在做Prometheus指标采集和告警,其中用到了Prometheus的exemplar特性,由于该特性比较新,当前支持该特性的存储有比较少.因此需要自行实现exemplar功能. 我在gi ...

  5. 计算机网络-4-2-ARP地址解析协议以及IP数据报不可变组成部分

    地址解析协议ARP ​ 在实际的应用中,我们会经常遇见这样的一个问题:我们已知一个机器(主机或者路由器的),我们怎么获取相应的硬件地址?,地址解析协议就是用来解决这个问题的. ARP协议的作用: 由上 ...

  6. Ubuntu 20.04上安装MySQL教程,ubuntu安装mysql

    在Ubuntu 20.04上安装MySQL教程 先决条件 确保您以具有sudo特权的用户身份登录. 在Ubuntu上安装MySQL 在撰写本文时,Ubuntu存储库中可用的MySQL的最新版本是MyS ...

  7. 搭建hexo博客遇到的问题

    搭建hexo博客遇到的问题 常用命令 hexo clean 清除hexo缓存 hexo generate 生成文章 hexo deploy 部署 hexo new post name 新建文章名 he ...

  8. AutoCAD云产品平台ForgeViewer格式离线部署思路分析

    背景 在上一篇博文中CAD图DWG解析WebGIS可视化技术分析总结提到,利用AutoCAD的自有云产品 Autodesk Forge,能在浏览器中渲染 3D 和 2D 模型数据,实现DWG图形的We ...

  9. SpringBoot-MVC自动配置原理

    SpringBoot对SpringMVC做了哪些配置,如何扩展,如何定制? 文档地址 :https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/re ...

  10. 7-Zip

    7-Zip https://www.7-zip.org/