简介¶
Warning
The current page still doesn't have a translation for this language.
You can read it through Google Translate.
Besides, you can also help to translate it: Contributing.
本节我们主要介绍在针对 SLUB allocator 进行利用时的一些前置补充知识与注意事项。
内核堆利用与绑核¶
slub allocator 会优先从当前核心的 kmem_cache_cpu 中进行内存分配,在多核架构下存在多个 kmem_cache_cpu ,由于进程调度算法会保持核心间的负载均衡,因此我们的 exp 进程可能会被在不同的核心上运行,这也就导致了利用过程中 kernel object 的分配有可能会来自不同的 kmem_cache_cpu ,这使得利用模型变得复杂,也降低了漏洞利用的成功率。
比如说你在 core 0 上整了个 double free,准备下一步利用时 exp 跑到 core 1去了,那就很容易让人摸不着头脑 :(
因此为了保证漏洞利用的稳定,我们需要将我们的进程绑定到特定的某个 CPU 核心上,这样 slub allocator 的模型对我们而言便简化成了单个 kmem_cache_node + 单个 kmem_cache_cpu ,我们也能更加方便地进行漏洞利用。
现笔者给出如下将 exp 进程绑定至指定核心的模板:
#include <sched.h>
/* to run the exp on the specific core only */
void bind_cpu(int core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}
通用 kmalloc flag¶
在内核对象的分配当中,最常用的函数是 kmalloc(),该函数原型如下(来自内核版本 6.14.4):
static __always_inline __alloc_size(1) void *kmalloc_noprof(size_t size, gfp_t flags)
//...
#define kmalloc(...) alloc_hooks(kmalloc_noprof(__VA_ARGS__))
其中第一个参数 size 为分配的内核对象的大小,第二个参数 flag 为 Get-Free-Page flag ,表示内核在分配过程中所采取的策略,不同的 bit 代表着不同的策略,例如当 ___GFP_KSWAPD_RECLAIM_BIT 启用时意味着在内存不足时唤醒 kswapd 进行内存回收。
GFP_KERNEL 与 GFP_KERNEL_ACCOUNT 是内核中最为常见与通用的分配 flag,由多个常用 bit 组合而成,这两种 flag 的区别主要在于 GFP_KERNEL_ACCOUNT 比 GFP_KERNEL 多启用了一个 bit ___GFP_ACCOUNT_BIT ——表示该对象使用 MEMCG 机制进行数据记录,该机制为 Memory CGroups 的一部分,主要用于统计追踪内核对象的分配,在内核启用了 CONFIG_MEMCG_KMEM=y 时生效。若未开启 CONFIG_MEMCG_KMEM ,则 GFP_KERNEL 与 GFP_KERNEL_ACCOUNT 等价。
我们主要关注当这种机制启用时内核对象分配所发生的变化:
- 常规情况下他们的分配都来自同一个
kmem_cache——即通用的kmalloc-xx。 - 对于开启了
CONFIG_MEMCG_KMEM编译选项的 kernel 而言(通常都是默认开启),其会为使用GFP_KERNEL_ACCOUNT进行分配的通用对象创建一组独立的kmem_cache——名为kmalloc-cg-*,从而导致使用这两种 flag 的 object 之间的隔离。
在5.9 版本之前
GFP_KERNEL与GFP_KERNEL_ACCOUNT存在隔离机制,在 这个 commit 中取消了隔离机制,自内核版本 5.14 起,在 这个 commit 当中又重新引入。
此外,当使用了 __GFP_ZERO 这一 flag 时,分配的内核对象会在返回给使用者之前进行数据清零的工作,即等价于 kzalloc()。
在对内核镜像或模块进行二进制逆向分析时,我们通常仅能看到 flag 为一个常量值,因此我们需要手动分析启动了那些 bit。以下是常见的分配 flag 对应的值:
GFP_KERNEL:0xCC0GFP_KERNEL | __GFP_ZERO: 0xDC0GFP_KERNEL_ACCOUNT:0x400CC0GFP_KERNEL_ACCOUNT | __GFP_ZERO: 0x400DC0
kmalloc 编译优化¶
对于分配的内核对象的大小为固定值的情况下,在编译期我们便能知道将会从哪一个 kmem_cache 进行分配,因此内核会将对 kmalloc() 的调用优化为对 kmem_cache_alloc_noprof() 的调用,该函数原型如下(来自内核版本 6.14.4):
void *kmem_cache_alloc_noprof(struct kmem_cache *cachep,
gfp_t flags) __assume_slab_alignment __malloc;
#define kmem_cache_alloc(...) alloc_hooks(kmem_cache_alloc_noprof(__VA_ARGS__))
实际上,内核原生的 kmem_cache (即 kmalloc-xx 等)的属性大致是编译期确定的,会按固定顺序存放在一个全局 kmem_cache 数组 kmalloc_caches 当中(定义于 mm/slab_common.c),因此在发生此类编译优化时你可能会在反编译器中看到形如 kmem_cache_alloc_noprof(kmalloc_caches[3], 0xCC0) 的形式,其原始定义如下(来自内核版本 6.14.4):
typedef struct kmem_cache * kmem_buckets[KMALLOC_SHIFT_HIGH + 1];
kmem_buckets kmalloc_caches[NR_KMALLOC_TYPES] __ro_after_init =
{ /* initialization for https://llvm.org/pr42570 */ };
EXPORT_SYMBOL(kmalloc_caches);
该变量实际上在内核内存初始化函数之一的 create_kmalloc_caches() 中通过 new_kmalloc_cache() 初始化,具体分析过程留给读者课后练习,这里我们直接给出 index 计算所需参考的代码位置(来自内核版本 6.14.4,mm/slab_common.c,include/linux/slab.h):
#define INIT_KMALLOC_INFO(__size, __short_size) \
{ \
.name[KMALLOC_NORMAL] = "kmalloc-" #__short_size, \
KMALLOC_RCL_NAME(__short_size) \
KMALLOC_CGROUP_NAME(__short_size) \
KMALLOC_DMA_NAME(__short_size) \
KMALLOC_RANDOM_NAME(RANDOM_KMALLOC_CACHES_NR, __short_size) \
.size = __size, \
}
/*
* kmalloc_info[] is to make slab_debug=,kmalloc-xx option work at boot time.
* kmalloc_index() supports up to 2^21=2MB, so the final entry of the table is
* kmalloc-2M.
*/
const struct kmalloc_info_struct kmalloc_info[] __initconst = {
INIT_KMALLOC_INFO(0, 0),
INIT_KMALLOC_INFO(96, 96),
INIT_KMALLOC_INFO(192, 192),
INIT_KMALLOC_INFO(8, 8),
INIT_KMALLOC_INFO(16, 16),
INIT_KMALLOC_INFO(32, 32),
INIT_KMALLOC_INFO(64, 64),
INIT_KMALLOC_INFO(128, 128),
INIT_KMALLOC_INFO(256, 256),
INIT_KMALLOC_INFO(512, 512),
INIT_KMALLOC_INFO(1024, 1k),
INIT_KMALLOC_INFO(2048, 2k),
INIT_KMALLOC_INFO(4096, 4k),
INIT_KMALLOC_INFO(8192, 8k),
INIT_KMALLOC_INFO(16384, 16k),
INIT_KMALLOC_INFO(32768, 32k),
INIT_KMALLOC_INFO(65536, 64k),
INIT_KMALLOC_INFO(131072, 128k),
INIT_KMALLOC_INFO(262144, 256k),
INIT_KMALLOC_INFO(524288, 512k),
INIT_KMALLOC_INFO(1048576, 1M),
INIT_KMALLOC_INFO(2097152, 2M)
};
enum kmalloc_cache_type {
KMALLOC_NORMAL = 0,
#ifndef CONFIG_ZONE_DMA
KMALLOC_DMA = KMALLOC_NORMAL,
#endif
#ifndef CONFIG_MEMCG
KMALLOC_CGROUP = KMALLOC_NORMAL,
#endif
KMALLOC_RANDOM_START = KMALLOC_NORMAL,
KMALLOC_RANDOM_END = KMALLOC_RANDOM_START + RANDOM_KMALLOC_CACHES_NR,
#ifdef CONFIG_SLUB_TINY
KMALLOC_RECLAIM = KMALLOC_NORMAL,
#else
KMALLOC_RECLAIM,
#endif
#ifdef CONFIG_ZONE_DMA
KMALLOC_DMA,
#endif
#ifdef CONFIG_MEMCG
KMALLOC_CGROUP,
#endif
NR_KMALLOC_TYPES
};
在内核源码中对 kmalloc_caches 的使用形式为 kmalloc_caches[type][index] , 但实际上的使用是一维指针数组 ,每个 type 占据一段连续的 index, 且大于 8k 的大小通常不启用 ,因此每个 type 一般会有 14 个 kmem_cache ,根据这个规则我们便能计算 kmalloc_caches[N] 对应的大小,以下是两个例子:
kmalloc_caches[12]: 大小范围位于KMALLOC_NORMAL对应的 index0~13,取kmalloc_info[12 - 0],得到大小 4k,对应kmem_cache为kmalloc-4k,对应 flag 为GFP_KERNEL。kmalloc_caches[54]: 大小范围位于KMALLOC_CGROUP对应的 index42~55,取kmalloc_info[54 - 42],得到大小 4k,对应kmem_cache为kmalloc-cg-4k,对应 flag 为GFP_KERNEL_ACCOUNT。
该计算启用的
kmalloc_cache_type为[KMALLOC_NORMAL,KMALLOC_RECLAIM,KMALLOC_DMA,KMALLOC_CGROUP],来自 Gentoo Linux 的默认配置。
需要注意的是, 这种计算方式仅在一般情况下正确 ,对于一些复杂的特殊情况, 超出 KMALLOC_NORMAL 范围的 index 计算未必可靠 (即 GFP_KERNEL 以外的计算都未必完全准确,因为我们有可能拿不准启用的 type 数量以及每个 type 的最大 cache 大小),因此比较准确的方式是进行动态调试查看对应的 kmem_cache::name 。
slub 合并 & 隔离¶
slab alias 机制是一种对同等/相近大小 object 的 kmem_cache 进行复用的一种机制:
- 当一个
kmem_cache在创建时,若已经存在能分配相等/近似大小的 object 的kmem_cache,则不会创建新的 kmem_cache,而是为原有的 kmem_cache 起一个 alias,作为“新的” kmem_cache 返回。
举个例子,cred_jar 是专门用以分配 cred 结构体的 kmem_cache,在 Linux 4.4 之前的版本中,其为 kmalloc-192 的 alias,即 cred 结构体与其他的 192 大小的 object 都会从同一个 kmem_cache——kmalloc-192 中分配。
对于初始化时设置了 SLAB_ACCOUNT 这一 flag 的 kmem_cache 而言,则会新建一个新的 kmem_cache 而非为原有的建立 alias,例如在新版的内核当中 cred_jar 与 kmalloc-192 便是两个独立的 kmem_cache,彼此之间互不干扰。
Reference¶
https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/
https://arttnba3.cn/2023/02/24/OS-0X04-LINUX-KERNEL-MEMORY-6.2-PART-III/
https://kernel.org/doc/html/v5.4/admin-guide/cgroup-v1/memory.html
https://lwn.net/Articles/821664/
https://github.com/torvalds/linux/commit/10befea91b61c4e2c2d1df06a2e978d182fcf792
https://github.com/torvalds/linux/commit/494c1dfe855ec1f70f89552fce5eadf4a1717552