kernel heap 1: slab & 内核heap防御机制 & kernel heap常见漏洞类型

2025-10-25

写在前面

slab的相关文章https://wsxk.github.io/linux_kernel_basic_one/#slab-alloctor
以及linux内核基础 三 slub分配器详解其实已经写了一部分,也相当于是故地重游哩~

1. slab allocators

内核的内存分配机制和glibc不同的主要原因有:

1. 性能:内核对于内存分配的性能要求是比较高的,不能用复杂的glibc机制来维护内核内存
2. 效率:glibc管理内存用到的元数据(metadata)太多了,内核不适用。

想了解slab的详细原理可以参考我的前2篇文章,我觉得我那2篇文章写的还真不赖,这里不用特地重复写了。

1.1 slab逻辑管理结构

总的来说,在kernel中,专门维护某个特定大小的内存的结构体叫做kmem_cache

kmem_cache//专门维护某个大小的内核内存
    kmem_cache_cpu //维护某个slab(通俗的说,是buddy system申请来的pageblock)
        void **freelist;      // 当前 CPU slab 的空闲对象链表//维护当前使用的slab当中的objects
        struct page *page;    // 当前 CPU 正在使用的 slab
        struct page *partial; // 当前 CPU 局部保存的一些“部分填满”的 slab 列表,当前 CPU正在使用的slab满了时,会优先从这里取slab替换

    kmem_cache_node //统一管理多个slab,基于内存控制器的数量的数组(通常来说,一根内存就代表有一个内存控制器)
        struct list_head slabs_partial // 部分使用的slab
        struct list_head slabs_full //已经无剩余容量的slab
        struct list_head slabs_free //完全没用到的slab

    struct list_head list//系统全局的slab_caches链表,所有的kmem_cache都会挂入此链表。

一个slab当中的object的名称叫做slot,下图中每个256 bytes都是一个slot

1.2 kernel申请内存方法

void *kmalloc(size_t size, gfp_t flags) 
// 默认情况下会在 通用目的的kmem_cahce中申请堆块,比如 kmalloc-8k
// 如果 GFP_KERNEL_ACCOUNT被设置,此时说明这块内存是不受信任的(通常用在从用户态里获得数据),此时会从 kmalloc-cg-8k 中申请内存

1.3 观测方法

cat /proc/slabinfo非常有用

2. kernel heap protections

跟用户态heap一样,内核heap也有很多很多保护机制。

2.1 Freelist Randomization

freelist randomization指的是在申请一个新slab后,其中的slot是并不是按顺序排序的,而是乱序排序,示意图如下:

配置了 CONFIG_SLAB_FREELIST_RANDOMIZATION 后开启。
在这个配置下kmem_cache中会添加一个数组结构,用来存放随机排序的序列。(只确定了刚开始申请到slab中的freelist序列)

#ifdef CONFIG_SLAB_FREELIST_RANDOM
    unsigned int *random_seq;
#endif

被释放的slot会被放入链表的头部。
随机顺序只在初始化的时候有效。
因为freelist是单向链表(后进先出),黑客在申请多个slot后,能够按照他想要的顺序释放它。(如果知道顺序的话)

2.2 Hardened Freelist

Hardened Freelist其实可以类比tcache中的safelinking机制,本质上就是freelist中的指针不再单纯指向另一个slot,而是再异或上两个其他值:ptr ^ s->random ^ &ptr,这里的&ptr指存放ptr的slot的地址
配置了CONFIG_SLAB_FREELIST_HARDENED后开启。

static inline freeptr_t freelist_ptr_encode(const struct kmem_cache *s,
					    void *ptr, unsigned long ptr_addr) {
	unsigned long encoded;
#ifdef CONFIG_SLAB_FREELIST_HARDENED
	encoded = (unsigned long)ptr ^ s->random ^ swab(ptr_addr);
#else
	encoded = (unsigned long)ptr;
#endif
	return (freeptr_t){.v = encoded};
}

当然,如果有uaf漏洞或者oob漏洞,有办法泄露其值。如何解呢?

2.3 Hardened Usercopy

Hardened Usercopy本质上就是限制从用户态传过来的数据能在内核态中存储的空间位置及其大小。
通过配置了CONFIG_HARDENED_USERCOPY后开启
配置成功后,在内核中存储用户态值除了传入指针外,还要传入其偏移和大小才行。
但是这个机制也有缺点:
这个机制实现在如下函数中:

kmem_cache_create_usercopy
__check_heap_object

copy_from_usercopy_to_user里会运用上述2个函数,但是memcpy没有!!!

2.4 kaslr

aslr类似。要想破解它首先需要泄露基址:
如果内核发生了非致命性错误(oops),那么内核会继续运行,并打印出错信息(所有寄存器),可以通过dmesg查看泄露内容
但是如果内核发生了致命错误(panic),那么内核会直接崩溃,这时候就有信息了。

3. 常见的kernel heap利用技巧

3.1 kernel heap 漏洞

kernel heap的漏洞跟用户态heap漏洞如出一辙

oob: 堆溢出问题,顾名思义
uaf: use after free
  -泄露freelist
  -破坏metadata(freelist)
  -任意地址读写原语
overlapping allocations: 堆块重叠

我们能够利用这些漏洞来创造条件,帮助我们成功提权!但是仅仅识别这些漏洞还是不够的,尝试利用它们非常重要。而利用它们,就需要对linux kernel的机制有充分的了解,并掌握一些利用trick