本文基于《KASAN(2)-自测试模块》的模块运行来逐一进行kasan的测试分析,应用层的asan测试分析可以参考《使用ASAN调试内存问题》
为了方便介绍,下面针对不同的kasan测试示例仅输出较重要的信息。
右越界的方法是访问申请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的右越界。
左越界是访问申请内存的上一个字节,代码如下
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的红区
如果内存来自伙伴系统,测试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
测试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
伙伴系统的无效释放可以错误的将指针偏移,强行释放,代码如下
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
和之前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
这里主要测试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也可以有效的改变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
模拟一种情况,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
思路是申请两个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
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检测到了。
使用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
思路是直接访问不在全局变量数组范围内的指针,如下
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
思路和全局变量一样,对栈区变量越界访问
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上。
这里有个小知识就是,对于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是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能求出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的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 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还是检测出来了此问题。
搜索越界的方法是故意在越界的地方搜索,让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
内存比较函数的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
这里使用一系列得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访问了第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
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会比较地址,因为地址已经释放了,所以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虽然指定了只比较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
与之前函数报错一致
[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限制了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
位操作测试的报错太多了,这里就不演示了,其原理和上述一致,只不过从操作字节变成操作位了。核心日志还是在影子下毒的标记上发现
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申请的虚拟地址的偏移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有一个记忆很深的理解。方便后面阅读代码分析。