赞
踩
linux kernel 内存踩踏之KASAN(一)_kasan版本跟hasan版本区别-CSDN博客
上一篇简单介绍了标准版本的KASAN使用方法和实现,这里将介绍KASAN_SW_TAGS和KASAN_HW_TAGS
的使用和背后基本原理,下图是三种方式的对比:
Overhead type | MTE | KASAN_SW_TAG(kernel)/HWASan(userspace) | KASAN(kernel)/ASan(userspace) |
---|---|---|---|
RAM | 3%-5% | 10%-35% | ~2x |
CPU | 0%-5% | ~2x | ~2x |
Code size | 2%-4% | 40%-50% | 50%-2x |
上表数据来源google的 userspace下MTE、HWASAN和ASAN的测试数据,内核的部分没有找到准确的对比数据,应该也差不多,套用上表。
关键差异:CONFIG_KASAN_SW_TAGS=y
- /sys/kernel/debug # zcat /proc/config.gz | grep -i kasan
- CONFIG_KASAN_SHADOW_OFFSET=0xefff800000000000 //这个offset和普通版本kasan有差异
- CONFIG_DRIVER_KASAN_TEST=m
- CONFIG_HAVE_ARCH_KASAN=y
- CONFIG_HAVE_ARCH_KASAN_SW_TAGS=y
- CONFIG_HAVE_ARCH_KASAN_HW_TAGS=y
- CONFIG_HAVE_ARCH_KASAN_VMALLOC=y
- CONFIG_CC_HAS_KASAN_GENERIC=y
- CONFIG_CC_HAS_KASAN_SW_TAGS=y
- CONFIG_KASAN=y
- CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX=y
- # CONFIG_KASAN_GENERIC is not set
- CONFIG_KASAN_SW_TAGS=y //SW_TAGS 版本kasan
- # CONFIG_KASAN_HW_TAGS is not set
- CONFIG_KASAN_OUTLINE=y
- # CONFIG_KASAN_INLINE is not set
- CONFIG_KASAN_STACK=y //stack kasan检测,如局部变量,局部数组等操作引起的内存踩踏
- CONFIG_KASAN_VMALLOC=y //vmalloc kasan检测,使用vmalloc申请内存的内存踩踏
SW_TAG shadow的原理就是利用ARM64的TBI(Top Byte Ignore)特性,在最高byte存储指针存储能访问内存区域的shadow标记,利用指针操作地址时就会检查指针的shadow和操作地址的的shadow是否一致,不一致则触发内存异常并报告原因。
- #define KASAN_PAGE_FREE KASAN_TAG_INVALID
- #define KASAN_PAGE_REDZONE KASAN_TAG_INVALID
- #define KASAN_SLAB_REDZONE KASAN_TAG_INVALID
- #define KASAN_SLAB_FREE KASAN_TAG_INVALID
- #define KASAN_VMALLOC_INVALID KASAN_TAG_INVALID /* only used for SW_TAGS */
-
- #define KASAN_TAG_KERNEL 0xFF /* native kernel pointers tag */
- #define KASAN_TAG_INVALID 0xFE /* inaccessible memory tag */
- #define KASAN_TAG_MAX 0xFD /* maximum value for random tags */
-
- #ifdef CONFIG_KASAN_HW_TAGS
- #define KASAN_TAG_MIN 0xF0 /* minimum value for random tags */
- #else
- #define KASAN_TAG_MIN 0x00 /* minimum value for random tags */
- #endif
SW_TAG的在指针内存分配时指定,内存有效时随机生成的有效值范围:0x00 ~ 0xFD, 0xFE用来表示free或者redzone等标记;
下图是arm64 48位 pagesize 4K的内存映射图,shadow的16TB映射整个内核空间:
CONFIG_KASAN_SHADOW_OFFSET=0xefff800000000000
计算方法:
CONFIG_KASAN_SHADOW_OFFSET= KASAN_SHADOW_START - KERNEL_ADDR_START >>4
= 0xffff700000000000 - ( 0xffff000000000000 >> 4) = 0xefff800000000000
有了这个kasan_shadow_offset, 后面我们需要获取一个内核地址对应的shadow 位置,只需要通过公式:
kernel_addr >> 4 + CONFIG_KASAN_SHADOW_OFFSET = kernel_addr对应的shadow_addr
还是用kmalloc为例:
- kmalloc
- -->kmalloc_trace
- -->__kmem_cache_alloc_node
- -->slab_alloc_node
- -->slab_post_alloc_hook
- -->kasan_slab_alloc
-
- void * __must_check __kasan_slab_alloc(struct kmem_cache *cache,
- void *object, gfp_t flags, bool init)
- {
- ....
-
- /*
- * Generate and assign random tag for tag-based modes.
- * Tag is ignored in set_tag() for the generic mode.
- */
- tag = assign_tag(cache, object, false); // 1、随机数分配tag
- tagged_object = set_tag(object, tag); // 2、设置tag 到指针
-
- /*
- * Unpoison the whole object.
- * For kmalloc() allocations, kasan_kmalloc() will do precise poisoning.
- */
- kasan_unpoison(tagged_object, cache->object_size, init);
- //3、从分配地址和size确认tag是否需要更新,如果和上面新分配的tag值不同,则更新tag
-
- /* Save alloc info (if possible) for non-kmalloc() allocations. */
- if (kasan_stack_collection_enabled() && !is_kmalloc_cache(cache))
- kasan_save_alloc_info(cache, tagged_object, flags);
- //4、存储分配stack
-
- return tagged_object;
- }
-
- #if defined(CONFIG_KASAN_SW_TAGS) || defined(CONFIG_KASAN_HW_TAGS)
- #define __tag_shifted(tag) ((u64)(tag) << 56)
- #define __tag_reset(addr) __untagged_addr(addr)
- #define __tag_get(addr) (__u8)((u64)(addr) >> 56)
流程如下:
1、分配tag随机数(0x00~0xFD)
2、给指针最高byte存储新 tag
3、根据指针tag和分配的长度,检查 ptr>>4 + shadow_offset处存储的tag值是否一致,不一致则更新
4、返回指针(高byte为tag)
检查指针即使是在kasan_check_range中进行的,
- (gdb) disassemble __hwasan_store1_noabort
- Dump of assembler code for function __hwasan_store1_noabort:
- 0xffff8000803d6f08 <+0>: paciasp
- 0xffff8000803d6f0c <+4>: stp x29, x30, [sp, #-16]!
- 0xffff8000803d6f10 <+8>: xpaclri
- 0xffff8000803d6f14 <+12>: mov w2, #0x1 // #1
- 0xffff8000803d6f18 <+16>: mov x29, sp
- 0xffff8000803d6f1c <+20>: mov x3, x30
- 0xffff8000803d6f20 <+24>: mov x1, #0x1 // #1
- 0xffff8000803d6f24 <+28>: bl 0xffff8000803d6e38 <kasan_check_range>
- 0xffff8000803d6f28 <+32>: ldp x29, x30, [sp], #16
- 0xffff8000803d6f2c <+36>: autiasp
- 0xffff8000803d6f30 <+40>: ret
-
- bool kasan_check_range(const void *addr, size_t size, bool write,
- unsigned long ret_ip)
- {
- u8 tag;
- u8 *shadow_first, *shadow_last, *shadow;
- void *untagged_addr;
-
- if (unlikely(size == 0))
- return true;
-
- if (unlikely(addr + size < addr))
- return !kasan_report(addr, size, write, ret_ip);
-
- tag = get_tag((const void *)addr); //1、获取指针tag
-
- /*
- * Ignore accesses for pointers tagged with 0xff (native kernel
- * pointer tag) to suppress false positives caused by kmap.
- *
- * Some kernel code was written to account for archs that don't keep
- * high memory mapped all the time, but rather map and unmap particular
- * pages when needed. Instead of storing a pointer to the kernel memory,
- * this code saves the address of the page structure and offset within
- * that page for later use. Those pages are then mapped and unmapped
- * with kmap/kunmap when necessary and virt_to_page is used to get the
- * virtual address of the page. For arm64 (that keeps the high memory
- * mapped all the time), kmap is turned into a page_address call.
- * The issue is that with use of the page_address + virt_to_page
- * sequence the top byte value of the original pointer gets lost (gets
- * set to KASAN_TAG_KERNEL (0xFF)).
- */
- if (tag == KASAN_TAG_KERNEL)
- return true;
- untagged_addr = kasan_reset_tag((const void *)addr); //2、将带tag指针转换成指针
- if (unlikely(!addr_has_metadata(untagged_addr)))
- return !kasan_report(addr, size, write, ret_ip);
- shadow_first = kasan_mem_to_shadow(untagged_addr); //3、提取对应地址的sw_tag shadow值
- shadow_last = kasan_mem_to_shadow(untagged_addr + size - 1); //4、提取访问地址尾部的sw_tag shadow值
- for (shadow = shadow_first; shadow <= shadow_last; shadow++) {
- if (*shadow != tag) { //5、遍历检查shadow tag和指针tag是否匹配
- return !kasan_report(addr, size, write, ret_ip);
- }
- }
- return true;
- }
如上面代码逻辑,检查tag的流程如下:
1、传入指针和内存操作的长度
2、获取指针tag
3、将带tag指针转换成指针
4、提取对应地址的sw_tag shadow值
5、提取访问地址尾部的sw_tag shadow值
6、遍历检查shadow tag和指针tag是否匹配
还是上一篇的例子(linux kernel 内存踩踏之KASAN(一)_kasan版本跟hasan版本区别-CSDN博客):
例子日志:
- /test # echo 0 > /dev/kasan_test
- [ 150.681333] kmalloc_oob_right d2ff000003de9c00
- [ 150.691414] ==================================================================
- [ 150.693254] BUG: KASAN: invalid-access in kmalloc_oob_right.constprop.0+0x4c/0x6c [kasan_driver]
- [ 150.695503] Write of size 1 at addr d2ff000003de9c81 by task sh/181
- [ 150.696332] Pointer tag: [d2], memory tag: [fe]
- [ 150.696848]
- [ 150.697599] CPU: 1 PID: 181 Comm: sh Tainted: G B N 6.6.1-g00ad0b878692 #18
- [ 150.698596] Hardware name: linux,dummy-virt (DT)
- [ 150.699352] Call trace:
- [ 150.699744] dump_backtrace+0x90/0xe8
- [ 150.700697] show_stack+0x18/0x24
- [ 150.701221] dump_stack_lvl+0x48/0x60
- [ 150.701716] print_report+0x15c/0x54c
- [ 150.702204] kasan_report+0xc4/0x108
- [ 150.702678] kasan_check_range+0x80/0xa4
- [ 150.703198] __hwasan_store1_noabort+0x20/0x2c
- [ 150.703749] kmalloc_oob_right.constprop.0+0x4c/0x6c [kasan_driver]
- [ 150.704593] kasan_test_case+0x40/0xc0 [kasan_driver]
- [ 150.705354] kasan_testcase_write+0x88/0x130 [kasan_driver]
- [ 150.706170] vfs_write+0x144/0x4d8
- [ 150.706667] ksys_write+0xe0/0x1b0
- [ 150.707166] __arm64_sys_write+0x44/0x58
- [ 150.707729] invoke_syscall+0x60/0x17c
- [ 150.708246] el0_svc_common.constprop.0+0x78/0x13c
- [ 150.708842] do_el0_svc+0x30/0x40
- [ 150.709462] el0_svc+0x40/0x100
- [ 150.709973] el0t_64_sync_handler+0x120/0x12c
- [ 150.710410] el0t_64_sync+0x190/0x194
- [ 150.710946]
- [ 150.711219] The buggy address belongs to the object at ffff000003de9c80
- [ 150.711219] which belongs to the cache kmalloc-128 of size 128
- [ 150.712055] The buggy address is located 1 bytes inside of
- [ 150.712055] 128-byte region [ffff000003de9c80, ffff000003de9d00)
- [ 150.712749]
- [ 150.713093] The buggy address belongs to the physical page:
- [ 150.713741] page:(____ptrval____) refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x43de9
- [ 150.714943] flags: 0x3fffc0000000800(slab|node=0|zone=0|lastcpupid=0xffff|kasantag=0x0)
- [ 150.715955] page_type: 0xffffffff()
- [ 150.716752] raw: 03fffc0000000800 82ff000003402600 dead000000000122 0000000000000000
- [ 150.717349] raw: 0000000000000000 0000000080200020 00000001ffffffff 0000000000000000
- [ 150.717938] page dumped because: kasan: bad access detected
- [ 150.718358]
- [ 150.718602] Memory state around the buggy address:
- [ 150.719208] ffff000003de9a00: 2c 2c 2c 2c 2c 2c 2c fe 28 28 28 28 28 28 28 28
- [ 150.719744] ffff000003de9b00: 66 66 66 66 66 66 66 66 f8 f8 f8 f8 f8 f8 f8 f8
- [ 150.720267] >ffff000003de9c00: d2 d2 d2 d2 d2 d2 d2 d2 fe fe fe fe fe fe fe fe
- [ 150.720886] ^
- [ 150.721635] ffff000003de9d00: fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe
- [ 150.722291] ffff000003de9e00: fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe
- [ 150.722978] ==================================================================
- [ 150.724556] kasan_test_case type 0
调试:
- (gdb) disassemble
- Dump of assembler code for function kmalloc_oob_right:
- 0xffff80007b160300 <+0>: paciasp
- 0xffff80007b160304 <+4>: adrp x0, 0xffff8000822ce000 <cpu_ops+432>
- 0xffff80007b160308 <+8>: stp x29, x30, [sp, #-32]!
- 0xffff80007b16030c <+12>: mov x2, #0x80 // #128
- 0xffff80007b160310 <+16>: mov w1, #0xcc0 // #3264
- 0xffff80007b160314 <+20>: mov x29, sp
- 0xffff80007b160318 <+24>: ldr x0, [x0, #3648]
- 0xffff80007b16031c <+28>: str x19, [sp, #16]
- 0xffff80007b160320 <+32>: bl 0xffff80008033c920 <kmalloc_trace> //1.指针设置sw tag
- => 0xffff80007b160324 <+36>: mov x2, x0 //断点
- 0xffff80007b160328 <+40>: adrp x1, 0xffff80007b164000
- 0xffff80007b16032c <+44>: add x1, x1, #0x110
- 0xffff80007b160330 <+48>: add x1, x1, #0x48
- 0xffff80007b160334 <+52>: mov x19, x0
- 0xffff80007b160338 <+56>: adrp x0, 0xffff80007b164000
- 0xffff80007b16033c <+60>: add x0, x0, #0x50
- 0xffff80007b160340 <+64>: bl 0xffff80008015d280 <_printk>
- 0xffff80007b160344 <+68>: add x0, x19, #0x81
- 0xffff80007b160348 <+72>: bl 0xffff8000803d6f08 <__hwasan_store1_noabort>
-
- //2.检查指针访问的内存是否合法
- 0xffff80007b16034c <+76>: mov w1, #0x79 // #121
- 0xffff80007b160350 <+80>: strb w1, [x19, #129]
- 0xffff80007b160354 <+84>: mov x0, x19
- 0xffff80007b160358 <+88>: bl 0xffff80008033da7c <kfree>
- 0xffff80007b16035c <+92>: ldr x19, [sp, #16]
- 0xffff80007b160360 <+96>: ldp x29, x30, [sp], #32
- 0xffff80007b160364 <+100>: autiasp
- 0xffff80007b160368 <+104>: ret
1、在上图断点处检查kmalloc_trace分配的指针值
(gdb) p /x $x0
$7 = 0xd2ff000003de9c00
2、利用计算公式,寻找对应指针地址存储的sw_tag shadow值:
ptr >> 4 + kasan_offset = kasan sw shadow
计算时记得将指针头替换成0xff
即:0xffff000003de9c00 >> 4 + 0xefff800000000000 = 0xFFFF7000003DE9C0
(gdb) x /30b 0xFFFF7000003DE9C0
0xffff7000003de9c0: 0xd2 0xd2 0xd2 0xd2 0xd2 0xd2 0xd2 0xd2
0xffff7000003de9c8: 0xfe 0xfe 0xfe 0xfe 0xfe 0xfe 0xfe 0xfe
0xffff7000003de9d0: 0xfe 0xfe 0xfe 0xfe 0xfe 0xfe 0xfe 0xfe
0xffff7000003de9d8: 0xfe 0xfe 0xfe 0xfe 0xfe 0xfe
上面的0xd2代表指针指向有效空间的范围,8 个0xd2, 由于sw_tag是每16个byte对应一个byte, 这里表示这个指针有效的范围是8*16 =128字节,正好和测试用例 kmalloc(128) 对应;
3、kasan report原因是我们访问的指针对应地址长度为0x81, 访问到了129字节处,这里对应的tag为0xfe,最后上报异常如下:
[ 150.695503] Write of size 1 at addr d2ff000003de9c81 by task sh/181
[ 150.696332] Pointer tag: [d2], memory tag: [fe]
从KASAN 和 KASAN_SW_TAGS的对比来看
类型 | shadow内存占用 | cpu占用 | 优缺点 |
---|---|---|---|
KASAN | 1/8 | 复杂,每次内存访问,需要计算对比shadow值 | 定位准确,8byte内的踩踏也能检测;32位/64位均能使用 |
KASAN_SW_TAGS | 1/16 | 每次内存访问,需要计算对比shadow值 | 16 byte内的踩踏无法区分, 仅64才能使用(因为依赖arm64 TBI feature) |
缺点1:16byte内的踩踏无法检测
KASAN_SW_TAGS的tag标记范围是16byte, 打一个比方:
ptr = kmalloc(129);
ptr[129] = 0; // 此时不会报错,无法检测到越界,实际上 pt[129] ~ ptr[128 + 16 -1] 内存越界操作都无法检测出来,因为这16字节的tag都是一样的,tag本身没有16byte內分配大小的记录;
缺点2:tag虽然是随机值,但是连续内存存在随机tag值一致导致漏检测可能
比如,
ptr1= kmalloc(128);
ptr2= kmalloc(128);
假如ptr1的tag是0x12, ptr1的tag也是0x12, 同时它们的内存连续,那么ptr1[128] = 0的操作就不会报错;
漏检测概率:由于0xfe和0xff两个值不会作为tag随机数, 连续内存生成重复tag的概率为1/254 * 1/254。
参考:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。