专栏名称: 看雪学苑
致力于移动与安全研究的开发者社区,看雪学院(kanxue.com)官方微信公众帐号。
目录
相关文章推荐
看雪学苑  ·  Linux内核SLUB机制介绍 ·  2 天前  
风动幡动还是心动  ·  利好兑现 ·  昨天  
风动幡动还是心动  ·  利好兑现 ·  昨天  
天津广播  ·  最新 | 韵达回应! ·  昨天  
天津广播  ·  最新 | 韵达回应! ·  昨天  
51好读  ›  专栏  ›  看雪学苑

Linux内核SLUB机制介绍

看雪学苑  · 公众号  · 互联网安全  · 2025-03-19 17:59

正文

本篇文章是学习内核堆利用时的视频笔记,视频源链接在最后。





01



基本概念


Slab分配器:是用来管理内核堆内存的基础设施


目前linux内核提供三种主流的实现:SLOB,SLAB,SLUB,这三种提供相同的接口供外部使用。其中SLUB是linux默认启用的,也可以在编译前通过修改编译配置文件,换成其他两种。


objects:slab可以分配出去小内存区域,也是管理的基本对象。


slabs:是保存objects的大内存区域,其上区域被切分成大小相同的内存区域称为object slots。这片内存是通过page_alloc分配的。


slot:是Slab分配器中预定义的 固定大小的内存块区。


(slot和objects其实指代的东西相同,因为它们在内存上是重叠的,但是只是在不同场合他们的称呼不一样。区分不开问题也不大,理解工作流程即可。)





02



Slab bugs


与用户空间的堆一样,典型的动态内存bugs:


◆Out-of-bounds(OOB)越界读写

◆Use-after-free(UAF)

◆Double-free,invalid-free


攻击方式:

利用上述bug,可以达到overwrite和泄漏的目的。


因为free的object slot中存在元数据,我们可以通过覆盖链表的next指针,控制下一次的分配对象,获得任意地址读写,可以提权或者泄漏内核地址。堆上的内容也可能包含函数指针,我们可以控制它达成任意代码执行或者泄漏内核地址。具体的攻击措施还要看特定的漏洞详情。





03



内核堆上的防护措施


下一个free slot的指针被保存在free slot的中间附近,这样可以防止小范围的溢出破坏指针


cache->offset = ALIGN_DOWN(cache->object_size / 2, sizeof(void *));
freeptr_addr = (unsigned long)object + cache->offset;


通过一个 CONFIG_SLAB_FREELIST_HARDENED=y 的编译配置选项,freelist指针会被加密保存。


cache->random = get_random_long();

freelist_ptr = (void *)((unsigned long)ptr ^ cache->random ^ swab(ptr_addr));
// ptr — actual value of freelist pointer
// ptr_addr — location where freelist pointer is stored
// swab() — exchanges adjacent even and odd bytes


ptr是freelist pointer的值,ptr_addr是freelist pointer被保存的地址,swab交换奇偶byte字节序。


所以要利用只能先泄漏 cache->random 和 =ptr_addr=,让利用更加困难。大多数现代 Slab 漏洞利用的是覆盖对象或者通过跨分配器攻击覆盖其他类型的内存。


通过 CONFIG_SLAB_FREELIST_RANDOM=y 配置,当分配新的 slab 时,SLUB 会打乱空闲列表中对象的顺序,这样让分配的地址更难预测。





04



slab关键数据结构


1.struct kmem_cache

struct kmem_cache {    // Per-CPU cache data:    struct kmem_cache_cpu __percpu *cpu_slab;    // Per-node cache data:    struct kmem_cache_node *node[MAX_NUMNODES];    ...    const char *name; // Cache name    slab_flags_t flags; // Cache flags    unsigned int object_size; // Size of objects    unsigned int offset; // Freelist pointer offset    unsigned long min_partial;    unsigned int cpu_partial_slabs;};

比较关键的几个成员变量:


name: 内核有许多不同的caches,可以通过 cat /proc/slabinfo 查看其中name就是第一列的名字,该name通过kmem_cache_create的参数指定

object_size: 也是通过kmem_cache_create的参数指定,每一个cache只可以分配固定大小的内存。

cpu_slab:
SLUB分配器为每个CPU核心分配独立的kmem_cache_cpu结构,保存系统内特定cpu绑定的slab信息,目的是避免多核并发访问时的锁竞争。每个核心通过自己的kmem_cache_cpu直接从本地缓存分配内存对象。其内的slabs是绑定到特定CPU上的slab。在6.8版本以前也被称为froze slabs,当CPU分配内存的时候,首先会从这些slabs中分配。

node:是为每个NUMA节点保存slab信息。NUMA的核心思想是把CPU分组,来简化资源的分配的复杂性。相当于拥有一个全局的slabs列表,尚未绑定到任何CPU,但是也仍然属于cache,也会包含已经分配的objects。

结构体详情:

struct kmem_cache_cpu {    struct slab *slab;    // Active slab    struct slab *partial; // Partial slabs    ...};struct kmem_cache_node {    struct list_head partial; // Slabs    ...};


2.per-CPU


对于 struct slab 的简化信息:

  struct slab {  // Aliased with struct page      struct kmem_cache *slab_cache; // Cache this slab belongs to      struct slab *next; // Next slab in per-cpu list      int slabs; // Slabs left in per-cpu list      struct list_head slab_list; // List links in per-node list      void *freelist; // Per-slab freelist      ...};


slab是一个 struct slab 的结构体,上述是简化的版本,struct slab 别名为struct page,提到这就不得不提一下历史了,在Linux内核5.17版本中,struct slab被引入,目的是将slab相关的字段从struct page中分离出来。struct page(每一个物理页面都有一个相应的page对应)之前包含了很多不同用途的字段,使用union来适应不同场景,导致结构复杂。现在struct slab作为struct page的一个overlay,共享同一块内存,但隐藏了struct page的细节,这样slab分配器只需要处理自己的结构。

slab_cache指向自己属于的cache。

每一个slab都有后备内存,后备内存是通过page_alloc想buddy system分配。不需要指针指向它,struct slab本身就是一个struct page

包含object slots,size是基于objects大小计算出来的。

freelist指针指向第一个slab中free的slot,下一个free slot的指针被保存在free slot中。freelist最后一个指针是NULL,objects都是从链表头分配,free也是插入链表头。

full slabs是指没有free slot的slab,此时它的freelist 指针是NULL。

多个slab可以用链表结构串联在一起。per-CPU的是单链表, struct slab 中的 next 指针,per-node的是双链表, struct slab 中的 list_head slab_list


3.active slab


先来看下kmem_cache_cpu的active slab,per-CPU的slabs的其中之一被设计成激活的,并把slab成员指针赋值为该slab。分配object的时候会首先从这个slab中分配。

active slab有两个freelists。 kmem_cache_cpu->freelist kmem_cache_cpu->slab->freelist 都指向它的slots。但是两个链表并不相交,
kmem_cache_cpu->freelist 用来给绑定的CPU分配释放内存的。

kmem_cache_cpu->slab->freelist 被用来给其他CPUs分配释放内存的(这个模块的代码有可能不只在一个cpu上运行,可能会在任务切换过程中跑到其他cpu上执行了)。


4.partial slabs


partial意思是这些slab有空闲slot(至少有一个,也有可能是fully free)。

每个partial slabs都有后备内存。

只有一个freelist,

只在active slab变为full后被使用。

per-CPU partial slabs的列表最大数量是有限的,这个大小是由kmem_cache->cpu_partial_slabs字段指定,这个值是根据object和slab的大小计算出来的link 用户空间是无法查看这个字段值的,只能查看 /sys/kernel/slab/$CACHE/cpu_partial ,然后自己计算出cpu_partial_slabs。


5.per-node


kmem_cache_node 有一个per-node partial slabs的列表。这就意味这每一个都至少有一个free slots。

每一个都有后备内存和一个freelist。

一旦per-CPU中的slabs都用完都变成full后他们就会被使用。

per-node slabs 的最小数量也是有限制的。由kmem_cache->min_partial指定, 计算也是基于object的大小link

可以在用户空间中查看 /sys/kernel/slab/$CACHE/min_partial


6.full slabs


full slabs 不会被tracked。没有指针指向full slabs(除非开启slub_debug),一旦任意一个object被释放到full slab中,分配器会获得指向该slab的指针。我们只需使用 virt_to_slab 计算。





05



分配过程


为了方便介绍,这里分为五个不同层次的分配过程:


1、allocating from lockless per-CPU freelist kmem_cache_cpu->freelist

当无锁的该cpu slab的freelist是不为空,那么就会分配该freelist的第一个object


如果为空,goto 2。


2、allocating from active slab (kmem_cache_cpu->slab->freelist)

如果active slab freelist不是空的,


首先move active slab freelist到 lockless per-CPU freelist;link


然后从这个lockless的per-CPU freelist分配第一个object。link并更新这个freelistlink


如果这个active slab freelist为空。goto 3link


3、allocating from per-CPU partial slabs (kmem_cache_cpu->partial)

如果有per-CPU的partial slabs:


首先将链表中的第一个脱链,并指定为active slabs link


goto 2link


如果per-CPU的partial slabs是空的


goto 4link


4、allocating from per-node partial slabs (kmem_cache_node->partial)


如果有per-node的partial slabs: 首先将链表中的第一个脱链,并指定为active slabslink; 然后移动一些(最多cpu_partial_slabs / 2link)per-node的slabs到per-CPU的partial listlink; 再去active slab重新分配。 link


如果per-node partial list 为空,goto 5


5、Create new slab

allocate from new slab的过程:


首先从page_alloc中分配新的slab,并放进freelist中,并指定为active slab,然后从该slab中分配对象。







请到「今天看啥」查看全文


推荐文章
看雪学苑  ·  Linux内核SLUB机制介绍
2 天前
风动幡动还是心动  ·  利好兑现
昨天
风动幡动还是心动  ·  利好兑现
昨天
天津广播  ·  最新 | 韵达回应!
昨天
天津广播  ·  最新 | 韵达回应!
昨天
热辣动态图  ·  女女穿成这样上街,骂惨了!
7 年前
24小时全球守望祷告中心  ·  全了,圣经里难读的字!弟兄姊妹收藏吧!
7 年前