编辑
2025-09-26
记录知识
0

目录

slab oob right
slab oob left
pagealloc oob right
pagealloc uaf
pagealloc invalid free
pagealloc api test uaf
realloc more oob
realloc less oob
kmalloc 16-bytes oob
kmalloc 16-bytes uaf
memset oob
kmem cache oob
global oob
stack oob
alloca oob left
ksize oob
ksize uaf
slab double free
slab invalid free
memchr oob
memcmp oob
strings test uaf
strchr uaf
strrchr uaf
strcmp uaf
strncmp uaf
strlen uaf
strnlen uaf
bitops
kfree_sensitive double free
vmalloc oob
总结

本文基于《KASAN(2)-自测试模块》的模块运行来逐一进行kasan的测试分析,应用层的asan测试分析可以参考《使用ASAN调试内存问题》

为了方便介绍,下面针对不同的kasan测试示例仅输出较重要的信息。

slab oob right

右越界的方法是访问申请size外的值。代码如下

size_t size = 123; ptr = kmalloc(size, GFP_KERNEL); ptr[size] = 'x';

右越界的日志如下,为了out of bounds的检测主要是触碰到了下毒红区区域,所以可以分析日志可以主动忽略堆栈,页信息等。核心信息如下

[ 2448.899923] BUG: KASAN: slab-out-of-bounds in kmalloc_oob_right+0x94/0xb0 [test_kasan_kylin] [ 2448.899938] Write of size 1 at addr ffffff800f657f7b by task insmod/2655 [ 2448.901026] The buggy address belongs to the object at ffffff800f657f00 [ 2448.901026] which belongs to the cache kmalloc-128 of size 128 [ 2448.901040] The buggy address is located 123 bytes inside of [ 2448.901040] 128-byte region [ffffff800f657f00, ffffff800f657f80) [ 2448.901169] Memory state around the buggy address: [ 2448.901183] ffffff800f657e00: 00 00 00 fc fc fc fc fc fc fc fc fc fc fc fc fc [ 2448.901196] ffffff800f657e80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 2448.901208] >ffffff800f657f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 [ 2448.901220] ^ [ 2448.901232] ffffff800f657f80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 2448.901245] ffffff800f658000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

简单总结:kmalloc-128的slab上,位置ffffff800f657f7b写了1字节,属于slab的右越界。

slab oob left

左越界是访问申请内存的上一个字节,代码如下

char *ptr; ptr = kmalloc(15, GFP_KERNEL); *ptr = *(ptr - 1);

核心日志

[ 2994.036036] BUG: KASAN: slab-out-of-bounds in kmalloc_oob_left+0x98/0xd8 [test_kasan_kylin] [ 2994.036050] Read of size 1 at addr ffffff80726e35ff by task insmod/2747 [ 2994.037144] The buggy address belongs to the object at ffffff80726e3500 [ 2994.037144] which belongs to the cache kmalloc-128 of size 128 [ 2994.037159] The buggy address is located 127 bytes to the right of [ 2994.037159] 128-byte region [ffffff80726e3500, ffffff80726e3580) [ 2994.037301] ffffff80726e3480: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 2994.037314] ffffff80726e3500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 2994.037327] >ffffff80726e3580: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 2994.037338] ^ [ 2994.037351] ffffff80726e3600: 00 07 fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 2994.037364] ffffff80726e3680: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

简单总结:ffffff80726e35ff不在kmalloc-128的slab申请范围内,触犯到下毒为0xfc的红区

pagealloc oob right

如果内存来自伙伴系统,测试kasan是否正常检测oob,代码如下

size_t size = KMALLOC_MAX_CACHE_SIZE + 10; ptr = kmalloc(size, GFP_KERNEL); ptr[size] = 0;

可以看到kasan能够正常捕捉到来自伙伴的页面的越界访问

[ 3376.163303] Write of size 1 at addr ffffff8019d9a00a by task insmod/2830 [ 3376.163700] flags: 0x10000(head) [ 3376.163775] ffffff8019d99f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 3376.163788] ffffff8019d99f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 3376.163801] >ffffff8019d9a000: 00 02 fe fe fe fe fe fe fe fe fe fe fe fe fe fe [ 3376.163812] ^ [ 3376.163824] ffffff8019d9a080: fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe [ 3376.163837] ffffff8019d9a100: fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe

pagealloc uaf

测试kasan检测来自伙伴的use after free问题

size_t size = KMALLOC_MAX_CACHE_SIZE + 10; ptr = kmalloc(size, GFP_KERNEL); kfree(ptr); ptr[0] = 0;

可以看到页已经被清空,影子区域下毒为ff,代表是free过的地址,如下

[ 3983.608976] BUG: KASAN: use-after-free in kmalloc_pagealloc_uaf+0x84/0x98 [test_kasan_kylin] [ 3983.608990] Write of size 1 at addr ffffff8005acc000 by task insmod/2952 [ 3983.609357] page:000000004791c730 refcount:0 mapcount:-128 mapping:0000000000000000 index:0x0 pfn:0x5acc [ 3983.609371] flags: 0x0() [ 3983.609446] ffffff8005acbf00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 3983.609459] ffffff8005acbf80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 3983.609472] >ffffff8005acc000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 3983.609483] ^ [ 3983.609495] ffffff8005acc080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 3983.609508] ffffff8005acc100: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

pagealloc invalid free

伙伴系统的无效释放可以错误的将指针偏移,强行释放,代码如下

char *ptr; size_t size = KMALLOC_MAX_CACHE_SIZE + 10; ptr = kmalloc(size, GFP_KERNEL); kfree(ptr + 1);

此时关键日志如下

[ 4278.120279] BUG: KASAN: double-free or invalid-free in kmalloc_pagealloc_invalid_free+0x58/0x64 [test_kasan_kylin] [ 4278.120331] Call trace: [ 4278.120413] kasan_report_invalid_free+0x60/0x8c [ 4278.120426] __kasan_kfree_large+0x68/0x8c [ 4278.120439] kfree+0x3e4/0x500 [ 4278.120462] kmalloc_pagealloc_invalid_free+0x58/0x64 [test_kasan_kylin] [ 4278.120484] kmalloc_tests_init+0x68/0xba8 [test_kasan_kylin] [ 4278.120660] page:00000000ccbdcc1d refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0xe1bc [ 4278.120673] head:00000000ccbdcc1d order:2 compound_mapcount:0 compound_pincount:0 [ 4278.120688] flags: 0x10000(head) [ 4278.120764] ffffff800e1bbf00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 4278.120777] ffffff800e1bbf80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 4278.120790] >ffffff800e1bc000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 4278.120801] ^ [ 4278.120813] ffffff800e1bc080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 4278.120826] ffffff800e1bc100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

可以看到,此时因为错误的指针右移,这块并不会被kasan下毒,所以我们得从堆栈分析,堆栈得重要点在__kasan_kfree_large+0x68/0x8c

(gdb) disassemble /m __kasan_kfree_large 373 if (ptr != page_address(virt_to_head_page(ptr))) { 374 kasan_report_invalid_free(ptr, ip); 0xffffffd008654e20 <+92>: mov x1, x20 0xffffffd008654e24 <+96>: mov x0, x19 0xffffffd008654e28 <+100>: bl 0xffffffd008655454 <kasan_report_invalid_free> 375 return true; 0xffffffd008654e2c <+104>: ldp x19, x20, [sp, #16] 0xffffffd008654e30 <+108>: ldp x29, x30, [sp], #32 0xffffffd008654e34 <+112>: ret

将0x68转换则是104,则对于375行。也就知道实际存在问题得地方如下

371 static inline bool ____kasan_kfree_large(void *ptr, unsigned long ip) 372 { 373 if (ptr != page_address(virt_to_head_page(ptr))) { 374 kasan_report_invalid_free(ptr, ip); 375 return true; 376 } 389 }

根据代码可以分析出来,默认将ptr指针转换成page指针,然后传入给page_address返回page指针对应的虚拟地址,如果ptr和计算page对应的虚拟地址不相等,那么就就不是一个页起始对象。
也就是说传入的ptr并不是当前page的其实ptr,说明ptr传入错误。 代码里面也是故意将其+1

pagealloc api test uaf

和之前kmalloc申请大于slab的页不同的测试方法,这里使用伙伴系统api测试uaf,如下

pages = alloc_pages(GFP_KERNEL, 4); ptr = page_address(pages); free_pages((unsigned long)ptr, order); ptr[0] = 0;

其日志和通过kmalloc实际是一致的,如下

[ 5510.618665] BUG: KASAN: use-after-free in pagealloc_uaf+0xb4/0xcc [test_kasan_kylin] [ 5510.618679] Write of size 1 at addr ffffff80a4080000 by task insmod/3253 [ 5510.619044] page:000000009a7015d6 refcount:0 mapcount:-128 mapping:0000000000000000 index:0x0 pfn:0xa4080 [ 5510.619058] flags: 0x0() [ 5510.619133] ffffff80a407ff00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 5510.619146] ffffff80a407ff80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 5510.619159] >ffffff80a4080000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 5510.619170] ^ [ 5510.619183] ffffff80a4080080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 5510.619196] ffffff80a4080100: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

realloc more oob

这里主要测试krealloc后的地址是否能够被kasan检测到oob,其思路是对一个内存,使用krealloc扩大内存后,尝试越界访问krealloc返回的地址的右侧,并尝试按照8字节对齐访问krealloc的右侧,确定kasan是否工作,代码如下

ptr1 = kmalloc(size1, GFP_KERNEL); ptr2 = krealloc(ptr1, size2, GFP_KERNEL); ptr2[size2] = 'x'; ptr2[round_up(size2, KASAN_GRANULE_SIZE)] = 'x';

对于第一次越界访问一个字节,日志如下

[10633.170388] BUG: KASAN: slab-out-of-bounds in krealloc_more_oob_helper+0x138/0x194 [test_kasan_kylin] [10633.170402] Write of size 1 at addr ffffff807d32a2eb by task insmod/4225 [10633.171057] The buggy address is located 235 bytes inside of [10633.171057] 256-byte region [ffffff807d32a200, ffffff807d32a300) [10633.171200] ffffff807d32a180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [10633.171213] ffffff807d32a200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [10633.171226] >ffffff807d32a280: 00 00 00 00 00 00 00 00 00 00 00 00 00 03 fc fc [10633.171237] ^ [10633.171250] ffffff807d32a300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [10633.171263] ffffff807d32a380: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

对于第二次越界访问对齐到8字节的日志如下

[10633.171415] BUG: KASAN: slab-out-of-bounds in krealloc_more_oob_helper+0x170/0x194 [test_kasan_kylin] [10633.171441] Write of size 1 at addr ffffff807d32a2f0 by task insmod/4225 [10633.172307] The buggy address is located 240 bytes inside of [10633.172307] 256-byte region [ffffff807d32a200, ffffff807d32a300) [10633.172449] ffffff807d32a180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [10633.172463] ffffff807d32a200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [10633.172476] >ffffff807d32a280: 00 00 00 00 00 00 00 00 00 00 00 00 00 03 fc fc [10633.172487] ^ [10633.172500] ffffff807d32a300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [10633.172518] ffffff807d32a380: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

上述两个日志已经非常清晰展示了错误

realloc less oob

realloc也可以有效的改变kmalloc的边界,这里主要讨论是缩小kmalloc的边界,kasan检测思路是当krealloc完成之后,右越界访问一字节,再右越界访问8字节对齐的位置,然后越界访问处于oldsize和newsize中间的位置,原oldsize边界,原oldsize的右越界一字节。那么总共应该要出现5个kasan检测到的错误
代码简要如下

ptr2[size2] = 'x'; ptr2[round_up(size2, KASAN_GRANULE_SIZE)] = 'x'; ptr2[middle] = 'x'; ptr2[size1 - 1] = 'x'; ptr2[size1] = 'x';

第一个越界,错误如下

[11065.116499] Write of size 1 at addr ffffff80198082c9 by task insmod/4313 [11065.117151] The buggy address is located 201 bytes inside of [11065.117151] 256-byte region [ffffff8019808200, ffffff8019808300) [11065.117295] ffffff8019808180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [11065.117308] ffffff8019808200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [11065.117321] >ffffff8019808280: 00 00 00 00 00 00 00 00 00 01 fc fc fc fc fc fc [11065.117332] ^ [11065.117344] ffffff8019808300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [11065.117358] ffffff8019808380: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

第二次越界,错误如下

[11065.117417] Write of size 1 at addr ffffff80198082d0 by task insmod/4313 [11065.118043] The buggy address is located 208 bytes inside of [11065.118043] 256-byte region [ffffff8019808200, ffffff8019808300) [11065.118177] ffffff8019808180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [11065.118191] ffffff8019808200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [11065.118204] >ffffff8019808280: 00 00 00 00 00 00 00 00 00 01 fc fc fc fc fc fc [11065.118215] ^ [11065.118228] ffffff8019808300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [11065.118241] ffffff8019808380: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

第三次越界,错误如下

[11065.118441] Write of size 1 at addr ffffff80198082da by task insmod/4313 [11065.119436] The buggy address is located 218 bytes inside of [11065.119436] 256-byte region [ffffff8019808200, ffffff8019808300) [11065.119576] ffffff8019808180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [11065.119590] ffffff8019808200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [11065.119602] >ffffff8019808280: 00 00 00 00 00 00 00 00 00 01 fc fc fc fc fc fc [11065.119613] ^ [11065.119626] ffffff8019808300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [11065.119639] ffffff8019808380: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

第四次越界,错误如下

[11065.119703] Write of size 1 at addr ffffff80198082ea by task insmod/4313 [11065.120638] The buggy address is located 234 bytes inside of [11065.120638] 256-byte region [ffffff8019808200, ffffff8019808300) [11065.120772] ffffff8019808180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [11065.120785] ffffff8019808200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [11065.120798] >ffffff8019808280: 00 00 00 00 00 00 00 00 00 01 fc fc fc fc fc fc [11065.120809] ^ [11065.120828] ffffff8019808300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [11065.120841] ffffff8019808380: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

第五次越界,错误如下

[11065.120900] Write of size 1 at addr ffffff80198082eb by task insmod/4313 [11065.121776] The buggy address is located 235 bytes inside of [11065.121776] 256-byte region [ffffff8019808200, ffffff8019808300) [11065.121940] ffffff8019808180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [11065.121953] ffffff8019808200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [11065.121966] >ffffff8019808280: 00 00 00 00 00 00 00 00 00 01 fc fc fc fc fc fc [11065.121977] ^ [11065.121990] ffffff8019808300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [11065.122012] ffffff8019808380: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

kmalloc 16-bytes oob

模拟一种情况,a结构体13个字节,而b结构体16个字节,将16个字节赋值给13个字节的结构体,kasan是否能够检测出来

ptr1 = kmalloc(sizeof(*ptr1) - 3, GFP_KERNEL); ptr2 = kmalloc(sizeof(*ptr2), GFP_KERNEL); *ptr1 = *ptr2;

主要日志如下,可以通过00 05计算得到可访问区间是13字节,但是实际写了16个字节

Write of size 16 at addr ffffff8013200c00 by task insmod/4538 [12154.453553] ffffff8013200b00: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [12154.453566] ffffff8013200b80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [12154.453579] >ffffff8013200c00: 00 05 fc fc fc fc fc fc fc fc fc fc fc fc fc fc [12154.453590] ^ [12154.453603] ffffff8013200c80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [12154.453616] ffffff8013200d00: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb

kmalloc 16-bytes uaf

思路是申请两个16字节的数据结构,释放一个后,重新访问释放的那个结构,如下

ptr1 = kmalloc(sizeof(*ptr1), GFP_KERNEL); ptr2 = kmalloc(sizeof(*ptr2), GFP_KERNEL); kfree(ptr2); *ptr1 = *ptr2;

所以关键日志在page info,如下

[15838.074594] BUG: KASAN: use-after-free in kmalloc_uaf_16+0xf4/0x114 [test_kasan_kylin] [15838.074609] Read of size 16 at addr ffffff807c6ad800 by task insmod/5384 [15838.075503] page:0000000077849ddd refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x7c6ac [15838.075617] ffffff807c6ad700: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [15838.075629] ffffff807c6ad780: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [15838.075642] >ffffff807c6ad800: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [15838.075653] ^ [15838.075665] ffffff807c6ad880: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [15838.075678] ffffff807c6ad900: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb

memset oob

思路是越界memset

ptr = kmalloc(666, GFP_KERNEL); memset(ptr, 0, 666 + 5);

这类错误会在memset的时候触碰到malloc的红区,所以日志如下

[16003.042017] Write of size 671 at addr ffffff801769a800 by task insmod/5416 [16003.042973] ffffff801769a980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [16003.042986] ffffff801769aa00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [16003.042999] >ffffff801769aa80: 00 00 00 02 fc fc fc fc fc fc fc fc fc fc fc fc [16003.043010] ^ [16003.043023] ffffff801769ab00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [16003.043036] ffffff801769ab80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

可以看到02正好是664+2的可以访问区域,因为memset是逐个字节设置的,所以在667的时候就已经被kasan检测到了。

kmem cache oob

使用slab的api创建本地cache,然后创建越界,使kasan检测这个越界

struct kmem_cache *cache = kmem_cache_create("test_cache", 200, 0, 0, NULL); p = kmem_cache_alloc(cache, GFP_KERNEL); *p = p[size];

当访问p[size]的时候出现oob,关键日志如下

Read of size 1 at addr ffffff80174d40c8 by task insmod/5664 [16922.406532] ffffff80174d3f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [16922.406546] ffffff80174d4000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [16922.406559] >ffffff80174d4080: 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc [16922.406571] ^ [16922.406585] ffffff80174d4100: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [16922.406598] ffffff80174d4180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

global oob

思路是直接访问不在全局变量数组范围内的指针,如下

char *p = &array[ARRAY_SIZE(global_array) + 3]; *(volatile char *)p;

日志主要通过影子区域下毒情况来判断,,当然通过虚拟地址估算可以,如下

[17406.920518] Read of size 1 at addr ffffffd00381708d by task insmod/5754 [17406.920942] ffffffd003816f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [17406.920955] ffffffd003817000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [17406.920969] >ffffffd003817080: 00 02 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 [17406.920981] ^ [17406.920994] ffffffd003817100: 00 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00 [17406.921008] ffffffd003817180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

这里f9是全局变量oob的poison值KASAN_GLOBAL_REDZONE

stack oob

思路和全局变量一样,对栈区变量越界访问

char stack_array[10]; char *volatile array = stack_array; char *p = &array[ARRAY_SIZE(stack_array)]; *(volatile char *)p;

栈的oob需要开启CONFIG_KASAN_STACK,其主要日志信息如下

[17484.917211] BUG: KASAN: stack-out-of-bounds in kasan_stack_oob+0xb0/0xec [test_kasan_kylin] [17484.917227] Read of size 1 at addr ffffffd010c076fa by task insmod/5779 [17484.917602] addr ffffffd010c076fa is located in stack of task insmod/5779 at offset 74 in frame: [17484.917626] kasan_stack_oob+0x0/0xec [test_kasan_kylin] [17484.917648] this frame has 2 objects: [17484.917660] [32, 40) 'array' [17484.917667] [64, 74) 'stack_array' [17484.917707] ffffffd010c07580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [17484.917721] ffffffd010c07600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [17484.917735] >ffffffd010c07680: 00 00 00 00 00 00 f1 f1 f1 f1 00 f2 f2 f2 00 02 [17484.917747] ^ [17484.917761] ffffffd010c07700: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [17484.917775] ffffffd010c07780: 00 00 00 00 00 00 f1 f1 f1 f1 00 00 00 00 00 00

这个和asan的上报方式很像,首先打印了当前栈帧上的变量array和stack_array,可以看到f1,f2,f3分别是栈相关的下毒值栈帧左,栈帧中,栈帧右,而我们错误访问的是p实际上是stack_array的第10个字节,故触发了f3的栈右下毒部分。当然关于array的位置也能看到在ffffffd010c0768a那个00上。

alloca oob left

这里有个小知识就是,对于c语言而言,如果使用char array[i]来栈上申请内存,因为i是不确定的,编译器因为不清楚i的值,所以会被alloca实现,也就是这是栈数组的另一种情况,越需要被kasan检测到,那么其思路就是通过申请一个变长数组,然后访问其左一个字节的越界,如下

volatile int i = 10; char alloca_array[i]; char *volatile array = alloca_array; char *p = array - 1; *(volatile char *)p;

为了检测变长数组,kasan提供了0xca和0xcb的两个下毒值用于检测,核心日志还是在影子区域,如下

[17916.214144] Read of size 1 at addr ffffffd01498763f by task insmod/5856 [17916.214543] ffffffd014987500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [17916.214557] ffffffd014987580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [17916.214571] >ffffffd014987600: 00 00 00 00 ca ca ca ca 00 02 cb cb cb cb cb cb [17916.214583] ^ [17916.214596] ffffffd014987680: 00 00 00 00 00 00 f1 f1 f1 f1 f1 f1 04 f2 00 f3 [17916.214609] ffffffd014987700: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00

这里ca是alloca的左,cb是alloca的右,可以看到ca和cb包裹的就是变成数组,因为i是10,所以其值是00 02

ksize oob

ksize是slab的api,他返回当前cache的size,也就说申请123字节,按照slab会给你128字节,那么如果访问第123字节,并不会被kasan检测,这是正常的,但是如果访问超过这个slab的区域,那么kasan应该检测到,代码如下

size_t size = 123, real_size; ptr = kmalloc(size, GFP_KERNEL); real_size = ksize(ptr); /* This access doesn't trigger an error. */ ptr[size] = 'x'; /* This one must. */ ptr[real_size] = 'y';

关键日志如下

[180947.395161] The buggy address belongs to the object at ffffff8074b66500 [180947.395161] which belongs to the cache kmalloc-128 of size 128 [180947.395176] The buggy address is located 0 bytes to the right of [180947.395176] 128-byte region [ffffff8074b66500, ffffff8074b66580) [180947.395326] ffffff8074b66480: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [180947.395340] ffffff8074b66500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [180947.395354] >ffffff8074b66580: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [180947.395364] ^ [180947.395378] ffffff8074b66600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [180947.395391] ffffff8074b66680: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

可以看到,如果访问123位置,其实代表00也就是可以访问,但是如果访问128的位置,其被下毒为fc,也就是slab的红区。

ksize uaf

ksize的uaf的思路是,ksize能求出slab的object的当前size,所以,如果malloc一个对象,free之后,再用ksize求出的大小去偏移指针访问,从而触发kasan的uaf日志,代码如下

int size = 128 - KASAN_GRANULE_SIZE; ptr = kmalloc(size, GFP_KERNEL); kfree(ptr); ksize(ptr); kasan_int_result = *ptr; kasan_int_result = *(ptr + size);

重要日志如下,这里有两个uaf的日志,我合并了一下

[181408.278921] 128-byte region [ffffff8092df8400, ffffff8092df8480) [181408.279070] ffffff8092df8300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [181408.279084] ffffff8092df8380: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [181408.279098] >ffffff8092df8400: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [181408.279109] ^ [181408.279122] ffffff8092df8480: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [181408.279136] ffffff8092df8500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [181408.280324] 128-byte region [ffffff8092df8400, ffffff8092df8480) [181408.280463] ffffff8092df8300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [181408.280476] ffffff8092df8380: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [181408.280490] >ffffff8092df8400: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [181408.280502] ^ [181408.280515] ffffff8092df8480: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [181408.280528] ffffff8092df8500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

第一个kasan,是因为访问了释放的ptr开始,也就是地址ffffff8092df8400,第二个kasan检测,是因为访问了ptr+size-8,也就是ffffff8092df8478,这里已经被下毒成fb了。

slab double free

通过slab的api来触发double free,代码如下

cache = kmem_cache_create("test_cache", size, 0, 0, NULL); p = kmem_cache_alloc(cache, GFP_KERNEL); kmem_cache_free(cache, p); kmem_cache_free(cache, p);

关键日志还是在page info上,如下

[182001.213218] The buggy address belongs to the object at ffffff80a4fe6000 [182001.213218] which belongs to the cache test_cache of size 200 [182001.213245] The buggy address belongs to the page: [182001.213263] page:00000000afb3c9f6 refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0xa4fe6 [182001.213276] head:00000000afb3c9f6 order:1 compound_mapcount:0 [182001.213291] flags: 0x10200(slab|head) [182001.213370] ffffff80a4fe5f00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [182001.213384] ffffff80a4fe5f80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [182001.213397] >ffffff80a4fe6000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [182001.213409] ^ [182001.213422] ffffff80a4fe6080: fb fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc [182001.213435] ffffff80a4fe6100: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

slab invalid free

其思路是创建一个slab cache,然后free其指针偏差1的地方,不过invalid free的问题的分析还是的基于堆栈分析代码,这个在pagealloc invalid free已经介绍了

cache = kmem_cache_create("test_cache", size, 0, SLAB_TYPESAFE_BY_RCU, NULL); p = kmem_cache_alloc(cache, GFP_KERNEL); kmem_cache_free(cache, p + 1);

关键日志在堆栈和页信息,堆栈可以查看pagealloc invalid free,这里贴出页信息

[182230.856324] The buggy address belongs to the object at ffffff80a42e4000 [182230.856324] which belongs to the cache test_cache of size 200 [182230.856339] The buggy address is located 1 bytes inside of [182230.856339] 200-byte region [ffffff80a42e4000, ffffff80a42e40c8) [182230.856368] page:00000000291b9f1d refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0xa42e4 [182230.856381] head:00000000291b9f1d order:1 compound_mapcount:0 [182230.856397] flags: 0x10200(slab|head) [182230.856476] ffffff80a42e3f00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [182230.856490] ffffff80a42e3f80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [182230.856503] >ffffff80a42e4000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [182230.856515] ^ [182230.856528] ffffff80a42e4080: 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc [182230.856541] ffffff80a42e4100: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

可以看到,kasan的poison并没有在ffffff80a42e4000生效(位置的下毒值是00),但是kasan还是检测出来了此问题。

memchr oob

搜索越界的方法是故意在越界的地方搜索,让kasan检测触发oob,代码如下

ptr = kmalloc(size, GFP_KERNEL | __GFP_ZERO); kasan_ptr_result = memchr(ptr, '1', size + 1);

这个关键日志在kasan提示的地址信息上,如下

[182946.638955] BUG: KASAN: slab-out-of-bounds in memchr+0x70/0x80 [182946.638971] Read of size 1 at addr ffffff8052001a18 by task insmod/39472 [182946.640115] The buggy address belongs to the object at ffffff8052001a00 [182946.640279] ffffff8052001900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [182946.640293] ffffff8052001980: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [182946.640306] >ffffff8052001a00: 00 00 00 fc fc fc fc fc fc fc fc fc fc fc fc fc [182946.640317] ^ [182946.640330] ffffff8052001a80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [182946.640343] ffffff8052001b00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb

memcmp oob

内存比较函数的oob检测思路是对比两个内存,但是size让内存越界,代码如下

int arr[9]; ptr = kmalloc(size, GFP_KERNEL | __GFP_ZERO); memset(arr, 0, sizeof(arr)); kasan_int_result = memcmp(ptr, arr, size+1);

这种oob错误还是看poison的位置,简单易懂

[183256.768172] Read of size 1 at addr ffffff80936e2118 by task insmod/39539 [183256.769479] ffffff80936e2000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [183256.769492] ffffff80936e2080: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [183256.769505] >ffffff80936e2100: 00 00 00 fc fc fc fc fc fc fc fc fc fc fc fc fc [183256.769517] ^ [183256.769530] ffffff80936e2180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [183256.769543] ffffff80936e2200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

strings test uaf

这里使用一系列得str操作接口,逐步测试uaf的情况,其代码如下

ptr = kmalloc(24, GFP_KERNEL | __GFP_ZERO); ptr += 16; kasan_ptr_result = strchr(ptr, '1'); kasan_ptr_result = strrchr(ptr, '1'); kasan_int_result = strcmp(ptr, "2"); kasan_int_result = strncmp(ptr, "2", 1); kasan_int_result = strlen(ptr); kasan_int_result = strnlen(ptr, 1);

但是kasan并没有全部打印日志,所以我们逐一测试,解析每个uaf的问题

strchr uaf

strchr访问了第16个字节,所以出错在第三个fb下毒位置,也就是地址ffffff804a7c9c10

[185797.324613] BUG: KASAN: use-after-free in strchr+0xc0/0xd0 [185797.324627] Read of size 1 at addr ffffff804a7c9c10 by task insmod/40154 [185797.325538] 128-byte region [ffffff804a7c9c00, ffffff804a7c9c80) [185797.325687] ffffff804a7c9b00: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc [185797.325701] ffffff804a7c9b80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [185797.325715] >ffffff804a7c9c00: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [185797.325726] ^ [185797.325739] ffffff804a7c9c80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [185797.325752] ffffff804a7c9d00: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc

strrchr uaf

strchr和strrchr的uaf完全同理,日志一样

[186067.990421] BUG: KASAN: use-after-free in strrchr+0x68/0x74 [186067.990436] Read of size 1 at addr ffffff807c784910 by task insmod/40246 [186067.992009] ffffff807c784800: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [186067.992023] ffffff807c784880: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [186067.992036] >ffffff807c784900: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [186067.992047] ^ [186067.992061] ffffff807c784980: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [186067.992074] ffffff807c784a00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb

strcmp uaf

strcmp会比较地址,因为地址已经释放了,所以kasan检测情况和strchr以及strrchr一致

[186164.534115] BUG: KASAN: use-after-free in strcmp+0xb4/0xc0 [186164.534131] Read of size 1 at addr ffffff8005396110 by task insmod/40297 [186164.535710] ffffff8005396000: 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc [186164.535723] ffffff8005396080: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [186164.535737] >ffffff8005396100: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [186164.535748] ^ [186164.535761] ffffff8005396180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [186164.535775] ffffff8005396200: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb

strncmp uaf

strncmp虽然指定了只比较1个字节,但是仍可以被kasan检测,值得注意的是,汇编宏会被展开,所以打印不会出现汇编宏的内容

[186285.661609] BUG: KASAN: use-after-free in kasan_strings+0x98/0xb4 [test_kasan_kylin] [186285.661624] Read of size 1 at addr ffffff8016e73710 by task insmod/40339 [186285.663187] ffffff8016e73600: 00 00 00 fc fc fc fc fc fc fc fc fc fc fc fc fc [186285.663201] ffffff8016e73680: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [186285.663214] >ffffff8016e73700: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [186285.663225] ^ [186285.663238] ffffff8016e73780: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [186285.663252] ffffff8016e73800: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc

strlen uaf

与之前函数报错一致

[186502.347215] BUG: KASAN: use-after-free in strlen+0x9c/0xa0 [186502.347230] Read of size 1 at addr ffffff808a56f510 by task insmod/40406 [186502.348807] ffffff808a56f400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [186502.348820] ffffff808a56f480: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [186502.348833] >ffffff808a56f500: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [186502.348845] ^ [186502.348858] ffffff808a56f580: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [186502.348871] ffffff808a56f600: 00 00 fc fc fc fc fc fc fc fc fc fc fc fc fc fc

strnlen uaf

虽然通过strnlen限制了1个字节,但是错误仍一致

[186669.425314] BUG: KASAN: use-after-free in strnlen+0x78/0x80 [186669.425330] Read of size 1 at addr ffffff808949ce10 by task insmod/40457 [186669.426390] ffffff808949cd00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [186669.426404] ffffff808949cd80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [186669.426417] >ffffff808949ce00: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [186669.426428] ^ [186669.426441] ffffff808949ce80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [186669.426455] ffffff808949cf00: 00 00 00 fc fc fc fc fc fc fc fc fc fc fc fc fc

bitops

位操作测试的报错太多了,这里就不演示了,其原理和上述一致,只不过从操作字节变成操作位了。核心日志还是在影子下毒的标记上发现

kfree_sensitive double free

kfree_sensitive的函数会清零后free,所以kfree_sensitive的double会触发两次kasan的检测,一次是read的时候出现uaf,另一次是访问的时候出现double free,代码如下

ptr = kmalloc(size, GFP_KERNEL); kfree_sensitive(ptr); kfree_sensitive(ptr);

两个日志合并,关键日志如下

[187075.458749] BUG: KASAN: use-after-free in kfree_sensitive+0x1c/0x60 [187075.458765] Read of size 1 at addr ffffff80740c2e00 by task insmod/40542 [187075.460368] ffffff80740c2d00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [187075.460381] ffffff80740c2d80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [187075.460395] >ffffff80740c2e00: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [187075.460405] ^ [187075.460419] ffffff80740c2e80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [187075.460432] ffffff80740c2f00: 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc [187075.460576] BUG: KASAN: double-free or invalid-free in kfree_sensitive+0x28/0x60 [187075.460779] kasan_report_invalid_free+0x60/0x8c [187075.462825] ffffff80740c2d00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [187075.462839] ffffff80740c2d80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [187075.462852] >ffffff80740c2e00: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [187075.462863] ^ [187075.462876] ffffff80740c2e80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [187075.462890] ffffff80740c2f00: 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc

vmalloc oob

vmalloc测试oob的思路是先申请一个地址,然后访问vmalloc申请的虚拟地址的偏移100上,只要没触碰到保护页即可。代码如下

area = vmalloc(3000); ((volatile char *)area)[3100];

对应日志如下

[187341.513714] BUG: KASAN: vmalloc-out-of-bounds in vmalloc_oob+0x74/0x8c [test_kasan_kylin] [187341.514119] ffffffd00ed2fb00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [187341.514133] ffffffd00ed2fb80: 00 00 00 00 00 00 00 f8 f8 f8 f8 f8 f8 f8 f8 f8 [187341.514147] >ffffffd00ed2fc00: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 [187341.514158] ^ [187341.514171] ffffffd00ed2fc80: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 [187341.514185] ffffffd00ed2fd00: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8

可以看到vmalloc的poison值f8正好命中了,所以kasan上报了错误。往上推,可以发现正好差100个字节f8到00

总结

本文尽可能详尽的解析了内核提供的所有kasan的示例情况,其主要目的还是为了更深入的了解kasan,kasan的内核测试有42项,我还是不厌其烦的一步一步测试验证,就是为了对kasan有一个记忆很深的理解。方便后面阅读代码分析。