查看原文
其他

Linux内核io_uring Unix垃圾收集器UAF漏洞(CVE-2022-2602)分析与复现

启明星辰 ADLab 2023-03-16

更多安全资讯和分析文章请关注启明星辰ADLab微信公众号及官方网站(adlab.venustech.com.cn)












一、前 言


近期,外网公布了一个Linux内核io_uring子系统的本地提权漏洞利用,漏洞编号为CVE-2022-2602,可低权限触发,目前漏洞利用已公布,鉴于漏洞范围影响大,建议用户尽快更新系统补丁进行漏洞修复。


二、相关介绍


2.1 Unix域套接字

Unix套接字不是实际的协议族,而是在单个主机上执行客户端/服务端通信,在不同进程之间传递描述符,实现跨进程通信(IPC)。它可以在进程间传递的描述符不限类型,比如pipe,open,socket,accept等函数返回的描述符,而不限于套接字,称之为“描述符传递”。一个描述符在传递过程中,这个过程指从调用sendmsg发送到调用recvmsg接收完毕,内核会将其标记为“在飞行中”(inflight)。在这段时间内,即使发送方试图关闭该描述符,内核仍会为接收进程保持打开状态,并对其引用计数加一。对于描述符的关闭,最后由Unix GC(垃圾收集器)轮询并统一清理。

2.2 io_uring特性

io_uring是Linux 5.1加入的一个重大特性——Linux下的全新的异步I/O支持,io_uring实现异步I/O的方式其实是一个生产者-消费者模型。io_uring模型的核心部分是两个环形缓冲区,它们位于用户空间和内核共享的内存中。io_uring实例通过调用io_uring_setup系统调用来初始化。内核将返回一个文件描述符,用户空间应用程序将使用它来创建共享内存映射。为了减少系统调用和减少用户进程与内核之间的数据拷贝,io_uring使用 mmap 的方式让用户进程和内核共享SQ(Submission Queue,提交队列)和CQ(Completion Queue,完成队列)的内存空间。用户进程生产I/O请求,放入SQ中,内核消费SQ中的I/O请求,完成后将结果放入CQ,用户进程从CQ中收割I/O结果。另外,由于先提交的I/O请求不一定先完成,SQ保存的其实是一个数组索引(数据类型 uint32),真正的SQE(Submission Queue Entry)保存在一个独立的数组(SQ Array)。所以要提交一个I/O请求,得先在SQ Array中找到一个空闲的SQE,设置好之后,将其数组索引放到 SQ 中。用户进程、内核、SQ、CQ和 SQ Array之间的基本关系如下:


三、漏洞原理


该漏洞是一个条件竞争类型的UAF漏洞,发生在对一个注册文件处理io_uring请求时,Unix GC(垃圾收集器)运行并释放io_uring fd和所有注册fds,Unix GC处理inflight fds的顺序可能导致注册的fds在io_uring被释放之前被释放。当io_uring线程处理io_uring请求时,会使用到可能被提前释放的file,进而触发漏洞。

3.1 飞行状态设置

当调用sendmsg发送描述符时,描述符会被标记为“在飞行中”,以unix_dgram_sendmsg()函数为例,具体实现如下代码所示:

客户端要传递的描述符存放在msg中。行1772,调用wait_for_unix_gc()函数进行unix_gc准备,其实现如下代码所示:

行198,判断unix_tot_inflight是否大于unix套接字飞行总数,其为16000,如果大于且unix_gc为空闲的,就先进行unix_gc释放腾空。行1773,调用scm_send()函数将msg中的描述符拷贝到scm中,具体实现如下代码所示:

行136,遍历msg,如果cmsg->type为SCM_RIGHTS,其表示为描述符类型。行158,调用scm_fp_copy()函数进行拷贝,具体实现如下代码所示:

行67,fdp指向cmsg。行72,计算出传输的描述符数量。行99,循环依次从fdp中取出描述符,行104,调用fget_raw()函数获取描述符对应的file。行106,将获取的file存入fpp中。成功拷贝并返回到sendmsg函数中,具体实现如下代码所示:

行1811,分配一个skb用于数据发送。行1817,调用unix_scm_to_skb()函数将scm放入skb中,具体实现如下代码所示:

首先初始化skb->cb。行1676,将skb->destructor设置为unix_destruct_scm()函数,该函数为skb释放时的回调函数。行1674,调用unix_attach_fds()函数进行unix套接字飞行设置和fds设置,具体实现如下代码所示:

行122,循环遍历从scm中取出描述符对应的file,并调用unix_inflight()函数,具体实现如下代码所示:

行50,调用unix_get_socket()函数获取s,这里s分为三种类型,第一种为unix套接字,第二种为io_uring实列,第三种为其他类型描述符。如果s不为空,则表示s为unix套接字或io_uring实列,要进行gc飞行。行57,判断s是否处于gc飞行中,如果不存在,将s放入gc_inflight_list中,然后将unix_tot_inflight加一。行66,将其他类型描述进行user->unix_inflight加一,标记为“在飞行中”。成功返回后,便将skb放入接收队列中进行发送。

3.2 Unix GC释放过程

当释放Unix套接字时,会调用unix_gc进行释放。具体实现如下代码所示:

行605,判断unix_tot_inflight不为空,则表示有套接字在飞行中,调用unix_gc()函数进行垃圾收集,具体实现如下代码所示:

行216,首先判断是否在进行gc操作,在gc操作中直接返回。行220,将gc_in_progress设置为ture,开始进行gc操作。接下来遍历gc_inflight_list,如下代码所示:

首先,选择候选者进行垃圾收集。只考虑飞行中的套接字,并且只考虑那些没有任何外部引用的套接字。行247,将套接字放入gc_candidates中。行248,并将u->gc_flags设置为UNIX_GC_CANDIDATE,表示为候选者。行249,将u->gc_flags设置为可循环。最后进行垃圾收集,如下代码所示:

现在gc_candidates只包含垃圾,这里的垃圾表示包含描述符数据的skb,同时也为飞行候选者恢复计数器,并删除创建循环的skbuff。行299,初始化hitlist。行301,调用scan_children()函数,具体实现如下代码所示:

行136,判断如果套接字状态不是监听状态,直接调用scan_inflight(),该函数实现如下代码所示:

行98,遍历接收队列。行100,如果存在描述符,开始遍历描述符。行110,如果发送的描述符为unix套接字。行117,将u->gc_flags的UNIX_GC_CANDIDATE清除,并调用func函数即inc_inflight()函数,将其引用计数恢复。行124,将寻找到的这个skb放入hitlist中。最后调用__skb_queue_purge()函数处理hitlist中的skb,如下代码所示:

最后会调用skb_release_head_state()函数,其实现如下代码所示:

行729,调用unix_destruct_scm()函数释放skb,具体实现代码如下所示:

行147,调用unix_detach_fds()函数进行反向操作。行151,调用scm_destroy()函数释放描述符对应的file,具体实现如下代码所示:

3.3 io_uring注册文件集

每当一个文件描述符被填入一个sqe并提交给内核时,内核必须检索一个对该文件的引用。一旦IO完成了,文件引用就会再次被丢弃。由于这个文件引用的原子性质,对于高IOPS的工作负载来说,这可能是一个明显的减慢。为了缓解这个问题,io_uring提供了一种方法来为io_uring实例预先注册一个文件集。这是通过系统调用io_uring_register完成的。如果UNIX套接字被启用,fd传递会引起引用循环,从而导致常规引用计数的中断。最后需要依靠UNIX的垃圾收集来解决这个问题。从io_sqe_files_scm()函数开始,具体实现如下代码所示:

行8150到行8158,循环调用__io_sqe_files_scm()函数处理要注册的文件集。其具体实现如下代码所示:

需要确保Unix GC知道io_uring注册的文件集,保证进程退出时,io_uring可以安全地取消注册,即使在文件中存在引用循环。行8096,分配一个scm_fp_list。行8100,分配一个skb内存。

行8110,循环依次调用io_file_from_index()函数根据注册的文件描述符获取其对应的file。行8115,调用get_file()函数进行引用计数加一,然后放入fpl_fp数组中,行8116,调用unix_inflight()函数将file设置成“在飞行中”。

设置完成后,行8120,然后初始化skb,将其放入接收队列中。行8128,循环依次对注册的文件集进行引用计数减一。

3.4 io_uring线程触发漏洞

io_uring请求的处理是由io_uring创建的内核线程进行的,这个处理过程是异步的,因此存在一个条件竞争窗口。当Unix GC在io_uring请求完成前释放了注册的文件集时,随后io_uring线程开始处理io_uring请求便可触发漏洞。


四、漏洞补丁


漏洞补丁的修复思路是不希望把io_uring的注册文件放在unix_gc()中,而是希望由io_uring自己来完成。首先是在skb_buff结构体中加入一个scm_io_uring标志,表示skb是否持有io_uring注册文件,如下代码所示:

然后在io_uring处理注册文件时,将scm_io_uring标志位置1,如下代码所示:

完成注册文件飞行设置后,在初始化skb时,将skb->scm_io_uring置为1,表示该skb持有io_uring注册文件。在unix_gc()函数进行垃圾收集时,将含有io_uring注册文件的skb从hitlist链表中移除,如下代码所示:

忽略所有io_uring起源的skbs,由io_uring清理它的注册文件。


五、漏洞复现


该漏洞释放重引用的对象为file,可使用DirtyCred利用技术,再结合userfaultfd控制时间窗口进行利用。在Ubuntu 2204 桌面系统中成功复现漏洞实现权限提升。



参考链接:


[1]https://developers.mattermost.com/blog/hands-on-iouring-go/[2]https://kernel.dk/io_uring.pdf[3]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0091bfc81741b8d3aeb3b7ab8636f911b2de6e80[4]https://seclists.org/oss-sec/2022/q4/30[5]https://github.com/kiks7/CVE-2022-2602-Kernel-Exploit





启明星辰积极防御实验室(ADLab)





ADLab成立于1999年,是中国安全行业最早成立的攻防技术研究实验室之一,微软MAPP计划核心成员,“黑雀攻击”概念首推者。截止目前,ADLab已通过CVE累计发布安全漏洞1100余个,通过 CNVD/CNNVD/NVDB累计发布安全漏洞近3000个,持续保持国际网络安全领域一流水准。实验室研究方向涵盖操作系统与应用系统安全研究、移动智能终端安全研究、物联网智能设备安全研究、Web安全研究、工控系统安全研究、云安全研究。研究成果应用于产品核心技术研究、国家重点科技项目攻关、专业安全服务等。





您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存