-
简介
基于 Dirty Pipe(见前篇)创建出来的新型漏洞利用方式,可以将其他 LInux 内核漏洞转化为逻辑漏洞,进而绕过当前所有的内核缓解机制(包括 CFI 控制流完整性保护)。
核心思路是使用高权限 credential 对象来交换低权限 credential 对象。
-
利用方式
内核中对权限 / 身份的校验发生在具体操作之前,而校验身份的动作往往不会被锁限制,那么可以卡在身份验证和操作之间将目标结构替换掉以实现权限提升。这中间需要一些手段将非内核凭证 slab 的漏洞转换为内核凭证的 slab 区域的漏洞,以及一些演唱身份验证时间窗的手段。
-
CVE-2021-4154 & CVE-2022-2588
在文件权限检查和写入数据之间进行竞争,实现 credential / file 结构体的替换(低权限 -> 高权限,可写 -> 不可写)。
实现的难点在于将写入操作阻塞在权限校验和实际写入之间。
-
漏洞原语转化
越界写
越界写下一个结构体的字段,将该结构体原先指向低权限的 credential 结构体指针修改为指向高权限的 credential 结构体指针,具体方式是往指针低 2 个字节写入 0(0x0000),这样做是希望将原指针修改当前页所在首部的 privileged credentials。攻击者可以通过频繁创建 privileged credentials 对象来占据新页面的首部位置,为后续修改指针做准备。
UAF
- 如果 UAF 的地方在 credential dedicated cache(内核常用对象) 上,释放掉原先的 unprivileged credential 然后使用新创建的 privileged credential 对象来占据这里就可以完成置换。
- 如果 UAF 的地方在 generic cache(普通对象)上,需要这个 UAF 有 invalid-write 能力。先释放出一个内存空洞,然后用再有 credential pointer 的可利用对象来占据它,然后利用 UAF 来改这个 credential pointer。
double free
- 在 vulnerable object 所在的 cache 中大量分配对,使得其至少占据一个页面的内存空间。为了使某个内存页面的被回收时机可控。
- 尝试触发两次 double free,使某个被释放内存块上有两个垂悬指针。
- 释放该页上所有对象,使得该页面被回收进分配器中,并被用于 credential 的内存分配,成为 dedicated cache。
- 在这块 credential 的页面上分配大量 credential 结构体,使其占据该页面的内存空间。
- 因为这两个垂悬指针可能不会与 credential object 对齐,因此需要用到一个来释放出一块 credential object 的空洞。
- 分配新的 credential object 占据这个空洞,使两个指针共同指向一个 credential object 后续就可以转化为一个 UAF 来使用了。
-
延长竞争窗口
userfaultfd
经典的延长缺页中断的时间,但在 5.11 之后内核禁止了非特权用户的 userfaultfd 注册(但还是可以通过 fuse 文件系统来注册 userfaultfd)
在 4.13 之前,系统调用 writev 的实现方式如下:
先进行权限检查,然后在 import_iovec 时触发页错误,从而利用 userfaultfd 机制来暂停内核的执行。
在 4.13 之后,import_iovec 函数的调用提前,
这是需要找到更底层的 generic_perform_write 函数,该函数会主动触发一次 page fault,同样可以利用 userfaultfd 实现延时:
文件系统 lock
在 ext4 文件系统写入数据的过程中,在执行 generic_perform_write 写入之前都会对 inode 进行一次 lock 操作:
那么一个进程先对某个文件进行大量数据写入,另一个进程对相同文件执行写入操作,就会一直等待 inode 锁的释放。
类似 CVE-2022-2588 中的操作:
FUSE
用户层文件框架,允许用户实现自己的文件系统。用户可以在该框架中注册 handler 来指定对文件操作请求,进而暂停内核执行。
-
分配特权对象
在用户层中,有两种方式可以分配 privilege credential:
- 大量执行 set-uid 程序(sudo)或者频繁的创建特权级守护进程(sshd)。
- 使用 ReadOnly 的方式打开特权文件(/etc/passwd)
在内核层中,当内核创建新的 kernel thread 时会 copy 当前的 thread 中的 privileged cred 结构,那么需要能稳定创建 kernel thread:
- 在 kernel workqueue 中填充大量任务
- 调用 username helper
可利用的内核对象:
-
参考文献