赞
踩
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
因为每 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)))
它的基本操作分为两部分:
本节先对读写访问时加入权限判断的流程进行阐述。
在 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; }
对于一系列 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); }
和权限的判断一样,权限的设定也需要在各种关键时刻的钩子中插入权限设定操作。
Buddy 系统在 free 和 alloc 的时间点上插入了权限设置,所以 buddy 能检测出 use-after-free
类型的错误。
__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);
}
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);
}
slub 在 malloc 和 free 的基础上,增加了 redzone 区域的检测。这样除了 use-after-free
以外还能检测出 slab-out-of-bounds
类型的错误。
计算出每个 object 额外要分配的 redzone 区间:
__kmem_cache_create() → kasan_cache_create()
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; }
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); }
kmalloc 的原理和 slub 类似。
对于全局变量的保护,增加了redzone,所以能检测出 global-out-of-bounds
类型的错误。
开启了 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 };
在系统初始化的时候,在调用构造函数时会调用到 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);
}
局部变量的保护也是使用插入 redzone 的形式来进行保护。但是这部分的调用时怎么串起来的,现在还没搞清楚。
别人举的例子:
/* (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; }
内核定义了一些相关函数,但是怎么样和栈保护编译链接起来的还没研究:
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); }
因为 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。