本文主要介绍一种名为Pspray的侧信道攻击手法,由 Yoochan Lee 在Black Hat 2022 Europe 提出。Pspray的核心思路是使用 SLAB 分配时间来了解内存分配状态,从而提高漏洞利用成功几率。
Linux 在内存管理子系统中实现了 SLAB、SLUB 和SLOB 三种分配器,它们主要用于小内存对象分配,设计的目的是为了解决伙伴系统分配带来的内存碎片化问题。
在操作系统管理的虚拟内存中,用于内存管理的最小单位是页,大多数传统的架构是4KB。由于进程每次申请分配4KB是不现实的,比如分配几个字节或几十个字节,这时需要中间机制来管理页面的微型内存。
为此,内核实现了一个分配器来管理页中碎片内存的分配和回收。可以把分配器理解为一个零售供应商:它收购大量的库存(4KB大小的页),然后在模块需要时分成小块出售。这种分配的基本版就是SLAB。
1.1 SLAB
-
empty:SLAB 中所有对象都是空闲的 -
partial:SLAB 中包含被分配的对象和空闲对象 -
full:SLAB 中所有对象都被分配
1.2 SLUB
objsize
表示对象自身的大小,offset
是next指针之前的空间大小,size
是总大小。所有的这些信息,以及更多的信息,都存储在一个kmem_cache
结构体中,它的结构体定义如下:/*
* Slab cache management.
*/
struct kmem_cache {
struct kmem_cache_cpu __percpu *cpu_slab;
/* Used for retrieving partial slabs, etc. */
slab_flags_t flags;
unsigned long min_partial;
unsigned int size; /* The size of an object including metadata */
unsigned int object_size;/* The size of an object without metadata */
unsigned int offset; /* Free pointer offset */
......
struct kmem_cache_node *node[MAX_NUMNODES];
}
kmem_cache
,并且该对象的所有slab都由相同的kmem_cache
管理,这些结构体通过双向链表互相链接,可以通过导出的slab_caches
变量从内核中的任何位置访问。slab_caches
定义如下:extern struct list_head slab_caches; // list_head用于管理双向链表
kmem_cache
结构体中,存储了两种指针以跟踪对象:一个kmem_cache_node
数组,是结构体最后一个成员struct kmem_cache_node *node[MAX_NUMNODES]
;另一个是指向kmem_cache_cpu
的指针,结构体第一个成员struct kmem_cache_cpu __percpu *cpu_slab
。-
以下是 kmem_cache_node
结构体定义。它跟踪不活动的 partial 和 full 的对象,在空闲的情况被访问,或者当活动的 SLAB 被填满时用另一个 partial 替换它。
/*
* The slab lists for all objects.
*/
struct kmem_cache_node {
spinlock_t list_lock;
#ifdef CONFIG_SLAB
struct list_head slabs_partial; /* partial list first, better asm code */
struct list_head slabs_full;
struct list_head slabs_free;
unsigned long total_slabs; /* length of all slab lists */
unsigned long free_slabs; /* length of free slab list only */
unsigned long free_objects;
unsigned int free_limit;
unsigned int colour_next; /* Per-node cache coloring */
struct array_cache *shared; /* shared per node */
struct alien_cache **alien; /* on other nodes */
unsigned long next_reap; /* updated without locking */
int free_touched; /* updated without locking */
#endif
#ifdef CONFIG_SLUB
unsigned long nr_partial;
struct list_head partial;
#ifdef CONFIG_SLUB_DEBUG
atomic_long_t nr_slabs;
atomic_long_t total_objects;
struct list_head full;
#endif
#endif
};
-
kmem_cache_cpu
结构体管理活动的SLAB,它只有一个,并且与当前的CPU相关(不同的处理器有不同的缓存)。下一次申请始终由freelist
字段指向的 SLAB 返回:
struct kmem_cache_cpu {
void **freelist; /* Pointer to next available object */
unsigned long tid; /* Globally unique transaction id */
struct page *page; /* The slab from which we are allocating */
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct page *partial; /* Partially allocated frozen slabs */
#endif
#ifdef CONFIG_SLUB_STATS
unsigned stat[NR_SLUB_STAT_ITEMS];
#endif
};
kmem_cache
结构体成员的关系图:
1.3 SLUB的分配过程
kmem_cache_malloc()
。其分配object的流程大概如下:-
首先在kmem_cache_cpu所使用的 SLAB 中查找 free object,如果当前 SLAB 中有 free object,则返回这个object。 -
如果当前 SLAB 没有free object,就要看 SLUB 是否开启了kmem_cache_cpu的partial队列,如果开启了partial队列,就在partial队列中查看有没有free object的 SLAB,如果有的话就选定这个SLAB,并返回其free object。 -
如果kmem_cache_cpu的partial链表中也没有拥有free object的SLAB,则在kmem_cache_node中查找。 -
如果kmem_cache_node中的SLAB有free object,则选定这个SLAB并返回free object。 -
如果kmem_cache_node中也没有free object,则需要向伙伴系统申请内存,使用 kmem_cache_create()
制作新的SLAB。
1.4 分配的随机性
Out-Of-Bounds
UAF和DF
-
fast path 表示从 freelist 直接分配返回的路径,是最快的分配方式。 -
medium path 表示从 partial 分配的路径,如上图中 medium path # 1、 # 2 和 # 3,表示分配速度中等。 -
slow path 表示从伙伴系统算法创建新的 SLAB 进行分配的路径,因为要创建新的 SLAB,所以分配速度最慢。
Out-Of-Bounds
-
假如现在有一个正在使用的SLAB A,其中的分配情况我们无法得知,但我们可以利用slow path的特点,通过分配的快慢速度来判断当前SLAB是否已满,当满时将会创建新的SLAB B。 -
我们通过在SLAB B中堆喷与目标大小相同的对象,来统计得到一个新的SLAB的数量N。SLAB B满之后会创建一个新的SLAB C。 -
在SLAB C中,我们分配N-1个受害对象,并在其中创建一个用于OOB的对象,只要这个OOB对象不在SLAB的末尾,那么就可以修改到其中一个受害对象。 -
最后就是常规的OOB利用。
UAF和DF
-
首先也是通过slow path的特点,通过分配的快慢来判断当前 SLAB 是否已满,满时会创建新的 SLAB B。 -
在 SLAB 中创建受害对象和用于 UAF 的对象,就保证了它们处于同一个 SLAB 中,之后是正常的 UAF 或 DF 利用手法。
Pspray利用评估
-
CPU 切换:CPU切换时,CPU page 将更改为另一个 CPU page,从而阻碍分配器将两个对象放置在相邻的位置或将两个对象放置在同一地址。 但是可以使用 Linux 系统调用 pthread_setaffinity_np来阻止它。 这个系统调用固定进程,限制它只在 CPU set 上运行。 也就是说使用固定 CPU 可以减轻 CPU 切换引起的噪音。 -
上下文切换:上下文切换时可能由其他进程分配了一个对象,然后在利用 OOB 漏洞时,会发现两个对象不相邻。可以通过更高的调度优先级来缓解,但这种做法也很难完全抑制上下文切换。
Reference
https://zhuanlan.zhihu.com/p/382056680
https://www.cnblogs.com/unr4v31/p/15815505.html
https://www.usenix.org/system/files/sec23summer_79-lee-prepub.pdf
原文始发于微信公众号(山石网科安全技术研究院):Linux内核中的侧信道攻击利用方式研究
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论