影响版本
linux kernel version < v5.13.4
测试环境
v5.13 config默认
背景
fsconfig
函数原型:
1 | long fsconfig(int fs_fd, unsigned int cmd, const char *key, |
fs_fd为文件描述符,cmd为操作命令,key为设置操作参数的参数名称,value为操作参数值,aux提供有关该值的更多信息。
常用cmd参数:
1 | (*) FSCONFIG_SET_FLAG:未指定值。该参数本质上必须是 |
用法示例:
1 | fd = fsopen("ext4", FSOPEN_CLOEXEC); |
漏洞修复
查看patch
1 | diff --git a/kernel/cgroup/cgroup-v1.c b/kernel/cgroup/cgroup-v1.c |
增加了一条类型判断
当我们执行fsconfig用source去处理fd的时候,会失败
POC
1 | int fscontext_fd = fsopen("cgroup"); |
漏洞分析
fsconfig定义于 /fs/fsopen.c
1 | struct fs_parameter { |
vfs_fsconfig_locked
1 | static int vfs_fsconfig_locked(struct fs_context *fc, int cmd, |
vfs_parse_fs_param
1 | /* |
cgroup1_parse_param
1 | //此函数为解析param参数 |
漏洞利用
通过漏洞,我们可以将一个file赋值给fc->source,如何利用呢?
关注一些 struct fs_context相关的函数,如释放:fscontext_release()
1 | static int fscontext_release(struct inode *inode, struct file *file) |
释放函数
1 | void put_fs_context(struct fs_context *fc) |
可以看到最终的 kfree使用到了fc->source,既然是free,我们就可以以此造出UAF的利用了
在这里浅提一下打开的文件结构存储:
每个进程用一个 files_struct 结构来记录文件描述符的使用情况,这个 files_struct 结构称为用户打开文件表,它是进程的私有数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct files_struct
{
atomic_t count; /* 共享该表的进程数 */
rwlock_t file_lock; /* 保护以下的所有域,以免在tsk->alloc_lock中的嵌套*/
int max_fds; /*当前文件对象的最大数*/
int max_fdset; /*当前文件描述符的最大数*/
int next_fd;
/*已分配的文件描述符加1 */
struct file **fd; /* 指向文件对象指针数组的指针 */
fd_set *close_on_exec; /*指向执行exec( )时需要关闭的文件描述符*/
fd_set *open_fds; /*指向打开文件描述符的指针*/
fd_set close_on_exec_init; /* 执行exec( )时需要关闭的文件描述符的初 值集合*/
fd_set open_fds_init; /*文件描述符的初值集合*/
struct file *fd_array[32]; /* 文件对象指针的初始化数组*/
};文件描述符fd的本质就是struct file **fd的索引,用来查找stuct file(该结构用于描述一个打开的文件)
假设我们已经打开了一个文件,获得其描述符fd,我们利用这个漏洞,free掉我们打开的file,此时我们的fd仍然指向该file,也就是指向了一个被释放的位置,实现了UAF,如果我们打开别的文件,就有可能申请一个新的file结构体到这个被释放的位置,从而替换了file,也就类似DirtyCred中替换内核凭证对象的想法。
本着来学习dirtycred利用的想法,试一下
dirtycred的细节不在这里提了,因为是5.13,userfaultfd默认禁用了,所以考虑使用文件系统锁的特性来实现延长时间窗口
为了确保同步,文件系统不允许两个进程同时写入一个文件,通过锁机制实现。
ext4文件系统执行写入操作的简化代码:
可以看到第六行开始尝试请求索引节点锁,如果别人持有锁就要一直等待锁被释放。尽管它确实实现了写操作的同步,但也给dirtycred留下了延长时间窗口执行对象交换的时间。
具体的:
DirtyCred可以生成两个进程AB,这两个进程同时向一个文件写入数据,假如进程A持有锁,并写入大量数据,此时进程B不得不等待较长时间,直至锁被释放。然而在调用generic_perform_write()函数之前,进程B已经完成了文件权限的检查,锁等待的时间完全足以我们来完成文件对象交换。根据论文的数据,将4GB写入硬盘时,过程大概需要几十秒的时间,在这个时间内,就可以完成出发漏洞和执行内存操作,而不会在利用过程中引发任何不稳定的问题。
还是看一下源码吧,毕竟一开始只是看了论文里的图🤐
ext4 writev写流程
writev系统调用底层会使用到do_writev
1 | static ssize_t do_writev(unsigned long fd, const struct iovec __user *vec, |
比较需要注意的是fdget_pos函数
这里是看了 @bsause 师傅的文章才知道的,之前写exp的时候一直没注意到
1 | unsigned long __fdget_pos(unsigned int fd) |
当我们设置了 FMODE_ATOMIC_POS 时,如果 file_count(file) >1 ,就会去竞争互斥锁。我们需要至少两个线程去打开同一个文件,count无疑是>1的。如果在这里stuck,就无法达到我们想要的利用的,因为此时还未进行权限检查,恶意线程stuck在这里毫无意义。所以我们需要去掉 FMODE_ATOMIC_POS 位来避免这种情况。
当我们打开一个文件时,open函数会最终调用到 do_dentry_open
该函数中有这样一句判断
1 | /* POSIX.1-2008/SUSv4 Section XSI 2.9.7 */ |
只要是常规的文件或者目录(reg/dir ),都会设置该 FMODE_ATOMIC_POS
查阅man手册,看看还有什么 i_mode 类型
1 | POSIX refers to the stat.st_mode bits corresponding to the mask |
里面还有管道,链接,sock,字符设备等类型,这里我们选用链接类型,只要创建一个我们想要open的文件的符号链接,去打开这个符号链接文件就不会设置 FMODE_ATOMIC_POS,从而避免了__fdget_pos处的阻塞等待
vfs_writev 函数
1 | static ssize_t vfs_writev(struct file *file, const struct iovec __user *vec, |
file_start_write内部会调用__sb_end_write
1 | static inline void __sb_end_write(struct super_block *sb, int level) |
而file_end_write 也会调用__sb_end_write
1 | static inline void file_end_write(struct file *file) |
这两个函数应该是对读写信号量进行的操作
do_iter_write进行实际的写操作
1 | static ssize_t do_iter_write(struct file *file, struct iov_iter *iter, |
接下来走文件系统提供的写操作
1 | static ssize_t ext4_buffered_write_iter(struct kiocb *iocb, |
检查权限在写之前,因此大量写入数据是可以实现stuck的,然后另一个线程就可以偷偷触发漏洞,同时又能通过权限检查。
*记得得用ext4文件系统,自己制作一下 至于别的文件系统,🤒,俺也还不会
我们一共需要3个线程(因为stuct file 对进程而言是独立的,线程才会共用stuct file)
- 一个线程A用来执行大量写入数据的文件操作
- 线程B也近乎同时以可写权限打开该文件,执行写操作
- 线程B要写入的数据是evildata,也就是恶意数据
- 线程B完成权限检查,但此时仍需等待进程A释放锁,故进程B在阻塞。
- 线程C触发漏洞,释放已经打开的文件的struct file
- 线程C多次打开只读的文件(攻击目标,如/etc/passwd,我简易的文件系统没有这玩意,所以搞了个只读的flag),堆喷大量的struct file 结构体
- 原先的struct file结构体被我们想攻击的文件覆盖
- 线程A写入完成,线程B写入,此时写入的目标是我们想攻击的目标
- 完成写入。
好,开始瞎几把编写exp
exp
run.sh
1 | !/bin/sh |
exp.c
1 |
|
运行结果:
参考资料
http://kernsec.org/pipermail/linux-security-module-archive/2019-February/011442.html
https://github.com/Markakd/CVE-2021-4154/blob/master/exp.c