【kernel exploit】CVE-2022-2602 UNIX_GC错误释放io_uring注册的file结构-UAF

news2024/11/26 16:24:23

本文主要参考 [漏洞分析] CVE-2022-2602 io_uring UAF内核提权详细解析 并做一些补充。

影响版本:Linux Kernel < v6.0.3。v6.0.3已修复。

测试版本:Linux-v6.0.2 (v6.0.2 测试失败,v5.18.19测试成功) exploit及测试环境下载地址—https://github.com/bsauce/kernel-exploit-factory

编译选项

CONFIG_BINFMT_MISC=y (否则启动VM时报错)

在编译时将.config中的CONFIG_E1000CONFIG_E1000E,变更为=y。参考

$ wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v6.x/linux-6.0.2.tar.xz
$ tar -xvf linux-6.0.2.tar.xz
# KASAN: 设置 make menuconfig 设置"Kernel hacking" ->"Memory Debugging" -> "KASan: runtime memory debugger"
$ make -j32
$ make all
$ make modules
# 编译出的bzImage目录:/arch/x86/boot/bzImage。

漏洞描述:io_uring组件中有个功能 IORING_REGISTER_FILES,可以将文件放入 io_uring 的 sock->receive_queue 队列中。而Linux的垃圾回收机制GC(只处理 io_uring 和 sock 文件的飞行计数)可能会将io_uring中注册的文件当做垃圾释放,io_uring 下次使用该文件时(利用writev写文件,对应IORING_OP_WRITEV功能)触发UAF。可通过userfaultfd触发该竞争漏洞。

补丁:patch unix_gc()

对于使用 io_uring_register 功能进行注册的文件,其生成的 skb 标记 scm_io_uring 位,加以区分。尽管 unix_gc() 还会对 io_uring 注册文件进行不可破循环检测,但是最后确定了垃圾队列 hitlist 之后,将所有通过 io_uring 注册的文件从 hitlist 移除。也就是说,unix_gc() 不会释放 io_uring 注册的文件,io_uring 自己会释放文件。

diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 9fcf534f2d927..7be5bb4c94b6d 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 6f88ded0e7e56..012fdb04ec238 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;		// 对于使用 `io_uring_register` 功能进行注册的文件,其生成的 skb 标记 scm_io_uring 成员位
 		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 d45d5366115a7..dc27635403932 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) {		// unix_gc() 不处理通过 `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));

保护机制:KASLR/SMEP/SMAP/KPTI

利用总结:由于UNIX_GC垃圾回收机制会错误释放 io_uring 中还在使用的文件结构体(正在往"/tmp/rwA"普通文件写入恶意数据),可以采用DirtyCred方法,打开大量"/etc/passwd"文件,覆盖刚刚释放的file结构体,这样最后就会实际往"/etc/passwd"文件写入恶意数据。

本漏洞由@kiks 和 @LukeGix 共同编写EXP。

1. 漏洞分析

1-1. Linux 垃圾回收机制

关于Linux垃圾回收机制(SCM_RIGHTS 消息发送过程、接收过程、不可破循环的识别过程)可以参见文章-【漏洞分析】CVE-2021-0920 Linux内核垃圾回收机制中的竞争UAF漏洞,该文章是采用 SOCK_STREAM socket(以发送函数 unix_stream_sendmsg() 为例来讲解SCM发送文件描述符的过程),本文采用 SOCK_DGRAM socket (以发送函数 unix_dgram_sendmsg() 为例来讲解SCM发送文件描述符的过程)。

static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
			      size_t len)
{
	struct scm_cookie scm;
	··· 
	err = scm_send(sock, msg, &scm, false);// [1] 先调用`scm_send()`获取用户传入的文件描述符对应的文件,然后初始化 scm_cookie 结构(scm_cookie->fp->fp指向待传递的`file`结构列表),此时会增加文件引用计数,表示SCM正在处理该文件,最后在[3]处调用scm_destroy()减少文件引用计数。
    ···    
	skb = sock_alloc_send_pskb(sk, len - data_len, data_len,
				   msg->msg_flags & MSG_DONTWAIT, &err,
				   PAGE_ALLOC_COSTLY_ORDER);
	if (skb == NULL)
		goto out;

	err = unix_scm_to_skb(&scm, skb, true);// [2] 调用 unix_scm_to_skb()->unix_attach_fds() 将 `scm_cookie->fp` 文件列表绑定到相应的skb对象,并增加文件引用计数(调用scm_fp_dup())和飞行计数(调用unix_inflight())
	···
    scm_destroy(&scm);// [3] 发送结束后释放scm,将刚刚初始化scm时增加的文件引用计数减少(调用fput())
    ···
    skb_queue_tail(&other->sk_receive_queue, skb);// [4] 将skb添加到对端的接收队列
}

1-2. io_uring 原理

io_uring 具体原理可参考 【kernel exploit】CVE-2021-41073 内核类型混淆漏洞利用分析。

本文简单介绍下漏洞相关的 io_uring 功能。

(1)io_uring_setup()

io_uring_setup() 负责初始化io_uring的两个环形队列,然后为io_uring创建一个文件对象,将文件描述符返回给用户,用户可以使用这个文件描述符来映射出内存来访问两个队列和创建相关资源。重点注意,在 io_uring_setup() -> io_uring_create() -> io_uring_get_file() 中,初始化了一个sock结构体(之后通过io_uring注册的文件会保存到这个 sk->receive_queue 队列中)

static struct file *io_uring_get_file(struct io_ring_ctx *ctx)
{
	struct file *file;
#if defined(CONFIG_UNIX)
	int ret;

	ret = sock_create_kern(&init_net, PF_UNIX, SOCK_RAW, IPPROTO_IP,
				&ctx->ring_sock); // 给 ctx->ring_sock 初始化一个sock结构体
	if (ret)
		return ERR_PTR(ret);
#endif

	file = anon_inode_getfile("[io_uring]", &io_uring_fops, ctx,
					O_RDWR | O_CLOEXEC); // 初始化一个file对象,其fd后续给用户使用
#if defined(CONFIG_UNIX)
	if (IS_ERR(file)) {
		sock_release(ctx->ring_sock);
		ctx->ring_sock = NULL;
	} else {
		ctx->ring_sock->file = file;
	}
#endif
	return file;
}  

重点注意,io_uring初始化完,还会同步存在一个io_uring的文件对象和sock对象,这在后面漏洞触发中很重要。如果初始化io_uring时带 IORING_SETUP_SQPOLL flag的话,则会在 io_sq_offload_create() 中初始化一个内核线程轮询 io_uring 的任务队列,就不用我们主动调用 io_uring_enter() 通知 io_uring 了,这里不详细分析了。

(2)io_uring_register() - v5.18.19

io_uring注册文件对应的是 IORING_REGISTER_FILES 功能,入口函数是 io_uring_register() ,该功能允许将若干文件描述符注册进入io_uring,方便后续的io操作。

SYSCALL_DEFINE4(io_uring_register, unsigned int, fd, unsigned int, opcode,
		void __user *, arg, unsigned int, nr_args)
{
	···
	ret = __io_uring_register(ctx, opcode, arg, nr_args);
	···
}

直接调用了 __io_uring_register(),在 __io_uring_register() 中会根据opcode类型调用到 io_sqe_files_register() 函数:

  • [1] 会遍历用户传递的所有需要注册的文件描述符,找到对应的 struct file 结构体,这其中会调用 fget() 函数,该操作会对文件的引用次数+1

  • [2] 接下来会调用关键函数 io_sqe_files_scm() 对文件进行下一步注册: (v6.0.2 版本的内核有变化,实现该功能的函数是io_scm_file_account() )

static int io_sqe_files_register(struct io_ring_ctx *ctx, void __user *arg,//2602
				 unsigned nr_args, u64 __user *tags)
{
	···
    // [1] nr_user_files 用户绑定的文件数量,遍历所有用户传进来的文件描述符
	for (i = 0; i < nr_args; i++, ctx->nr_user_files++) {
		u64 tag = 0;

		if ((tags && copy_from_user(&tag, &tags[i], sizeof(tag))) ||
		    copy_from_user(&fd, &fds[i], sizeof(fd))) {// [1] 获取用户传递的文件描述符fd
			ret = -EFAULT;
			goto out_fput;
		}
		···
		file = fget(fd);					// [1] 获取文件结构体,fget会对文件引用次数+1
		···
		if (file->f_op == &io_uring_fops) {	// 不能io_uring 注册自己
			fput(file);
			goto out_fput;
		}
		ctx->file_data->tags[i] = tag;
		io_fixed_file_set(io_fixed_file_slot(&ctx->file_table, i), file);
	}

	ret = io_sqe_files_scm(ctx);			// [2] 关键函数,进一步逻辑
	···
}

io_sqe_files_scm() 直接调用 __io_sqe_files_scm():

  • [1] sk是io_uring在初始化时候创建的struct sock结构

  • [2] 先申请一个skb(可以保存多个用户传入的 file 结构,存于skb->fp->fp列表),这很重要;然后遍历所有文件,对他们使用 get_file() 引用计数会+1;然后将这些文件使用 unix_inflight() 发送,如果发送的文件是sock类型则会增加飞行计数添加到全局飞行列表gc_inflight_list,普通文件不受影响;最后将文件列表fpl赋值给 skb->cb.fp,并把新分配的skb添加到io_uring sock的接收队列(``sk->receive_queue )之中(漏洞点!!!)。

  • [3] 最后处理完之后会对所有文件使用 fput()文件引用计数会-1,相当于平衡了最开始的 fget()

static int __io_sqe_files_scm(struct io_ring_ctx *ctx, int nr, int offset)//ctx,thisfiles total
{
	struct sock *sk = ctx->ring_sock->sk;	// [1] sock 是最开始 io_uring 初始化的时候创建的
	struct scm_fp_list *fpl;
	struct sk_buff *skb;
	int i, nr_files;

	fpl = kzalloc(sizeof(*fpl), GFP_KERNEL);
	···
	skb = alloc_skb(0, GFP_KERNEL);			// [2] 申请一个sk_buff,可以保存多个用户传入的 `file` 结构
	···
	skb->sk = sk;							// skb->sk 指向 io_uring 的 sk

	nr_files = 0;
	fpl->user = get_uid(current_user());
	for (i = 0; i < nr; i++) {				// [2] 遍历所有文件
		struct file *file = io_file_from_index(ctx, i + offset);// 获得文件结构体

		if (!file)
			continue;
		fpl->fp[nr_files] = get_file(file);	// [2] get_file同样会使file引用次数+1,把文件注册到fpl中
		unix_inflight(fpl->user, fpl->fp[nr_files]);	// [2] 把文件添加到发送队列,会增加sock类型文件的inflight飞行计数
		nr_files++;
	}

	if (nr_files) {
		fpl->max = SCM_MAX_FD;
		fpl->count = nr_files;
		UNIXCB(skb).fp = fpl;				// [2] fpl 给skb
		skb->destructor = unix_destruct_scm;
		refcount_add(skb->truesize, &sk->sk_wmem_alloc);
		skb_queue_head(&sk->sk_receive_queue, skb);// [2] 将新分配的skb添加到sk->sk_receive_queue中 !!!!!! 漏洞点 !!!!!!

		for (i = 0; i < nr_files; i++)
			fput(fpl->fp[i]);				// [3] 对这些文件使用fput,平衡刚刚使用的get_file
	} else {
		kfree_skb(skb);
		kfree(fpl);
	}

	return 0;
}

文章 分析过 unix_inflight()sk_receive_queue 等,这里的代码比较奇怪,将注册到io_uring中的sock文件通过 unix_inflight() 增加飞行计数,这没有问题,但是后面又将注册文件添加到io_uring文件的 sk_receive_queue 队列中sk_receive_queue 代表一个socket还未接收的消息队列,socket总是成对出现,而io_uring只有一个。(推测,这里加入到io_uring文件的 sk_receive_queue 队列中,表示还未被io_uring处理过的文件,而io_uring文件也有可能会被关闭,可以被看作是socket,也需要识别不可破循环以进行垃圾处理,这样便于UNIX_GC统一识别不可破循环)

(3)io_uring_register() - v6.0.2

不同点:负责进一步注册的函数有变化,从 io_sqe_files_scm() 变为 io_scm_file_account(),且在遍历用户传入file的循环内部调用了注册处理函数,后续不需要再遍历用户传入的file了。

io_uring_register() -> __io_uring_register() -> io_sqe_files_register()

  • [1] 遍历用户传递的所有需要注册的文件描述符,找到对应的 file 结构体,其中会调用 fget()文件引用次数+1
  • [2] 接下来调用 io_scm_file_account() 函数对文件进行下一步注册。
int io_sqe_files_register(struct io_ring_ctx *ctx, void __user *arg,
			  unsigned nr_args, u64 __user *tags)
{
	...
    ret = io_rsrc_data_alloc(ctx, io_rsrc_file_put, tags, nr_args,	// 读取 tags
				 &ctx->file_data);
    ...
	for (i = 0; i < nr_args; i++, ctx->nr_user_files++) {	// [1] 遍历 nr_args 个用户传进来的文件描述符
		struct io_fixed_file *file_slot;

		if (fds && copy_from_user(&fd, &fds[i], sizeof(fd))) {	// 获取文件描述符 fd
			ret = -EFAULT;
			goto fail;
		}
		...
		file = fget(fd);										// 获取 file 结构体,fget对文件引用次数+1
		...
		if (io_is_uring_fops(file)) {							// 判断 file->f_op == &io_uring_fops
			fput(file);
			goto fail;
		}
		ret = io_scm_file_account(ctx, file);					// [2] 关键漏洞函数
		if (ret) {
			fput(file);
			goto fail;
		}
		file_slot = io_fixed_file_slot(&ctx->file_table, i);
		io_fixed_file_set(file_slot, file);
		io_file_bitmap_set(&ctx->file_table, i);
	}
	...
}

io_scm_file_account() 直接调用 __io_scm_file_account():

  • [1] sk是io_uring在初始化时候创建的struct sock结构

  • [2] 先申请一个skb,或者从 io_uring 文件的 sk->sk_receive_queue 中取出现有的skb,这很重要;然后遍历所有文件,对他们使用 get_file() 引用计数会+1;然后将这些文件使用 unix_inflight() 发送,如果发送的文件是sock类型则会增加飞行计数添加到全局飞行列表gc_inflight_list,普通文件不受影响;最后将文件列表fpl赋值给 skb->cb.fp,并把新分配的skb添加到io_uring sock的接收队列(``sk->receive_queue )之中(漏洞点!!!)。

  • [3] 最后处理完之后会对所有文件使用 fput()文件引用计数会-1,相当于平衡了最开始的 fget()

int __io_scm_file_account(struct io_ring_ctx *ctx, struct file *file)
{
#if defined(CONFIG_UNIX)
	struct sock *sk = ctx->ring_sock->sk;			// [1] sock 是最开始 io_uring 初始化的时候创建的
	struct sk_buff_head *head = &sk->sk_receive_queue;
	struct scm_fp_list *fpl;
	struct sk_buff *skb;

    // 看能否将本 file 整合到现有的 skb SCM_RIGHTS file 集合(sk->sk_receive_queue)中,如果没有则分配新的skb
	spin_lock_irq(&head->lock);
	skb = skb_peek(head);
	if (skb && UNIXCB(skb).fp->count < SCM_MAX_FD)
		__skb_unlink(skb, head);
	else
		skb = NULL;
	spin_unlock_irq(&head->lock);

	if (!skb) {
		fpl = kzalloc(sizeof(*fpl), GFP_KERNEL);
		if (!fpl)
			return -ENOMEM;

		skb = alloc_skb(0, GFP_KERNEL);				// [2] 每注册一个文件,都会申请一个 sk_buff (skb)

		fpl->user = get_uid(current_user());
		fpl->max = SCM_MAX_FD;
		fpl->count = 0;

		UNIXCB(skb).fp = fpl;
		skb->sk = sk;								// skb->sk 指向 io_uring 的 sk        
		skb->destructor = unix_destruct_scm;
		refcount_add(skb->truesize, &sk->sk_wmem_alloc);
	}

	fpl = UNIXCB(skb).fp;
	fpl->fp[fpl->count++] = get_file(file);			// 文件引用次数+1,将用户传入的文件加入到 sk->fp->fp 中
	unix_inflight(fpl->user, file);					// 把文件添加到发送队列,会增加sock类型文件的inflight飞行计数
	skb_queue_head(head, skb);						// 将新分配的skb添加到sk->sk_receive_queue中 !!!!!! 漏洞点 !!!!!!
	fput(file);										// [3] 文件引用次数-1
#endif
	return 0;
}

(4)io_submit_sqe() - 提交读写任务

io_submit_sqe()函数是在我们向io_uring中提交任务之后(不管是不是 IORING_SETUP_SQPOLL 模式都会走到这里),io_uring准备完成这个sqe的任务的时候会触发,这里简单看一下io_uring如何完成 IORING_OP_WRITEV 类型任务,也就是writev写任务。

static int io_submit_sqe(struct io_ring_ctx *ctx, struct io_kiocb *req,
			 const struct io_uring_sqe *sqe)
{
	struct io_submit_link *link = &ctx->submit_state.link;
	int ret;

	ret = io_init_req(ctx, req, sqe);	// 初始化调用任务
	···
	ret = io_req_prep(req, sqe);		// 准备调用任务,这里会进行文件权限的判断
	···
		} else {
			io_queue_sqe(req);			// 尝试执行
	···
}

主要分为三步,在 io_init_req() 中进行初始化,然后调用 io_req_prep() 函数准备调用任务(注意,v5.18.19版本中是在io_init_req()函数末尾直接调用 io_req_prep()),对于writev任务会在这里先调用 io_write_prep() 进行文件权限校验

static int io_write_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
{
	if (unlikely(!(req->file->f_mode & FMODE_WRITE)))
		return -EBADF;
	return io_prep_rw(req, sqe);
}

最后会在 io_queue_sqe() -> __io_queue_sqe() -> io_issue_sqe() -> io_write() 中完成实际写入工作。

所以虽然是writev任务,但并没有直接去调用系统调用writev相关内容,而且整个过程中也没有改变文件的引用计数。

注意:v5.18.19版本内核中是直接在 io_write() -> io_rw_init_file() 函数中进行文件权限校验。

static int io_write(struct io_kiocb *req, unsigned int issue_flags)
{
    ...
    ret = io_rw_init_file(req, FMODE_WRITE);
    ...
}

static int io_rw_init_file(struct io_kiocb *req, fmode_t mode)
{
    ...
    if (unlikely(!file || !(file->f_mode & mode)))		// 检查文件写权限
		return -EBADF;
    ...
}

1-3. 漏洞的触发

触发:重点关注相关文件的引用计数变化和飞行计数变化(只有发送 io_uring 和 sock类型的文件描述符才涉及飞行计数的变化)。

  • (1)准备一对 socket,文件描述符记为s[0]s[1],准备好之后默认的引用计数均为1;

  • (2)初始化io_uring,获取一个文件描述符记为fd,初始状态引用计数为1;

  • (3)打开一个普通可读写文件(文件描述符rfd[1] "/tmp/rwA"),初始状态引用计数为1;

  • (4)使用 io_uring_register 的注册功能注册 s[1]rfd[1](注册到了fd中,且 s[1]rfd[1] 生成对应的skb保存到了fd的sk->sk_receive_queue队列中),在这期间会将文件的引用计数+1,并且 io_uring_register 中会对注册的文件调用unix_inflight() 函数,sock类型的 s[1] 的飞行计数+1,并会将 s[1]rfd[1] 放入同一个skb中;

    • s[1]:引用计数2;飞行计数1;
    • rfd[1]:引用计数2;不涉及飞行计数;
  • (5)关闭 rfd[1],引用计数-1,变为1(因为前面已经使用 io_uring_register 注册到了fd中,rfd[1]对应生成的skb保存到了fd的sk->sk_receive_queue中,所以还存在引用计数1);

  • (6)使用 s[0] 将fd发送给 s[1],这期间会使 fd 的引用计数+1,并且由于发送过程中调用 unix_inflight() 函数,io_uring类型的fd 同样会使飞行计数+1;

    • fd:引用计数2;飞行计数1;
  • (7)分别关闭s[0]和s[1],他们的引用计数都会减1;

    • s[0]:引用计数0,被释放;
    • s[1]:引用计数1,飞行计数1,暂时不会被释放;
  • (8)先往"/tmp/rwA"文件写入大量数据,占据inode文件锁(参考Dirty Cred分析中的方法);再向 fd(io_uring) 提交一个文件写(writev - IORING_OP_WRITEV)的任务,往"/tmp/rwA"文件写入恶意数据(新的root账户和密码),这个任务就会阻塞在文件权限检查之后,实际写之前;

  • (9)调用 io_uring_queue_exit 关闭fd,fd的引用计数-1;

    • fd:引用计数1;飞行计数1,暂时不会被释放;
  • (10)创建一个socket,并且关闭,触发垃圾回收 unix_gc() (在socket 关闭过程中触发);

  • (11)s[1]fd(io_uring) 都满足 引用计数==飞行计数 的条件,并且**s[1]fd都在对方的 sk_receive_queue** 中,属于不可破循环

    • unix_gc() 会将他们从对方的 sk_receive_queue 中取下并且加入hitlist中调用 __skb_queue_purge() 完成释放(释放的是skb),并且减少飞行计数;
  • (12)释放 skb 过程中会对 skb 中的 file 调用 fput()

    • fd所在的skb中只有fd自己,对 fd(io_uring) 使用 fput(),文件引用计数-1,变为0成功释放;
    • s[1] 所在的skb中有 s[1] 自己和 rfd[1],对这两个文件使用 fput();(fd的 sk->sk_receive_queue 中保存的skb的sk->fp->fp列表包含了 s[1]rfd[1] 两个文件描述符);
      • s[1] 经过 fput() 之后引用计数变为0,被释放,没问题;
      • rfd[1] 经过 fput() 之后引用计数变为0,被释放,但上面的(8)步还有因阻塞而没有完成的任务,所以导致任务阻塞完毕之后尝试写入时访问了已经被释放的文件结构体,造成UAF

漏洞利用:在非法释放之后,文件写阻塞结束之前,使用堆喷射喷射其他 file 结构体(打开大量的"/etc/passwd"文件)覆盖这个被释放的结构体所在内存,就可以写到"/etc/passwd"文件了。

1-4. 漏洞修复

修复方法:在 unix_gc() 确定了垃圾队列 hitlist 之后,从 hitlist 队列中移除通过 io_uring 注册的文件,因为 io_uring 自己会处理释放文件。

问题:为什么不直接让 unix_gc() 不处理 io_uring 相关的飞行计数?因为从以上EXP示例可以看出,fd 是需要被释放清空的(属于不可破循环中的情况,socket s[1] 和 fd都已经被关闭,但是fd仍然位于 s[1] 的接收队列中),如果不处理 io_uring 相关的飞行计数,就不会将 fd 识别为垃圾。

2. 漏洞利用

原理:在 io_uring 执行IO任务之前,利用漏洞将文件释放掉,然后采用 DirtyCred 利用方法。

io_uring 虽然是无系统调用的IO操作,但本质上还是完成的对应系统调用的功能,比如可以给 io_uring 下发writev系统调用的任务,接着直接套用DirtyCred方法。

进程A进程B(启动比A慢)
打开"/tmp/rwA"文件,写入大量数据(0x80000 * 0x1000 字节);
通过文件权限校验,并获取inode文件锁;打开"/tmp/rwA"文件,尝试写入恶意数据(新的root账号和密码),提交写任务到io_uring;
长时间写入…通过文件权限校验,等待获取inode文件锁;
释放inode文件锁;
触发漏洞,释放本file结构体;
打开大量"/etc/passwd"文件,覆盖刚刚释放的file结构体;
获得inode文件锁,但实际会写入"/etc/passwd"文件。

本漏洞和传统DirtyCred相关利用的漏洞不同的点在于,该漏洞的io_uring自带了写功能,并且逻辑和writev类似,无需自己再writev。

3. 测试结果

如果ssh连进去执行expoit时报格式错误,重传一遍exploit即可。v5.18.19版本的内核测试成功,但v6.0.2测试失败。

请添加图片描述

4. 常用命令

参考 CVE-2022-34918

liburing 安装:

# 安装 liburing   生成 liburing.a / liburing.so.2.2
$ make
$ sudo make install

常用命令

# ssh连接与测试
$ ssh -p 10021 hi@localhost             # password: lol
$ ./exploit

# 编译exp
$ make CFLAGS="-I /home/hi/lib/libnftnl-1.2.2/include"
$ gcc -static ./get_root.c -o ./get_root
$ gcc -no-pie -static -pthread ./exploit.c -o ./exploit

# scp 传文件
$ scp -P 10021 ./exploit hi@localhost:/home/hi      # 传文件
$ scp -P 10021 hi@localhost:/home/hi/trace.txt ./   # 下载文件
$ scp -P 10021 ./exploit.c ./get_root.c ./exploit ./get_root  hi@localhost:/home/hi

问题:原来的 ext4文件系统空间太小,很多包无法安装,现在换syzkaller中的 stretch.img 试试。

# 服务端添加用户
$ useradd hi && echo lol | passwd --stdin hi
# ssh连接
$ sudo chmod 0600 ./stretch.id_rsa
$ ssh -i stretch.id_rsa -p 10021 -o "StrictHostKeyChecking no" root@localhost
$ ssh -p 10021 hi@localhost
# 问题: Host key verification failed.
# 删除ip对应的相关rsa信息即可登录 $ sudo nano ~/.ssh/known_hosts
# https://blog.csdn.net/ouyang_peng/article/details/83115290

ftrace调试:注意,QEMU启动时需加上 no_hash_pointers 启动选项,否则打印出来的堆地址是hash之后的值。trace中只要用 %p 打印出来的数据都会被hash,所以可以修改 TP_printk() 处输出时的格式符,%p -> %lx

# host端, 需具备root权限
cd /sys/kernel/debug/tracing
echo 1 > events/kmem/kmalloc/enable
echo 1 > events/kmem/kmalloc_node/enable
echo 1 > events/kmem/kfree/enable

# ssh 连进去执行 exploit

cat /sys/kernel/debug/tracing/trace > /home/hi/trace.txt

# 下载 trace
scp -P 10021 hi@localhost:/home/hi/trace.txt ./ 	# 下载文件

参考

  1. 【漏洞分析】CVE-2021-0920 Linux内核垃圾回收机制中的竞争UAF漏洞

  2. CVE-2022-2602: DirtyCred File Exploitation applied on an io_uring UAF —— 英文博客1

  3. DirtyCred Remastered: how to turn an UAF into Privilege Escalation —— 英文博客2:UAF 转化为DirtyCred

  4. https://github.com/kiks7/CVE-2022-2602-Kernel-Exploit

  5. https://github.com/LukeGix/CVE-2022-2602

  6. https://seclists.org/oss-sec/2022/q4/57 —— 漏洞披露

  7. [漏洞分析] CVE-2022-2602 io_uring UAF内核提权详细解析 —— 中文翻译

  8. io_uring, SCM_RIGHTS, and reference-count cycles —— file引用计数知识

  9. The quantum state of Linux kernel garbage collection CVE-2021-0920 (Part I) —— Linux垃圾回收知识

  10. 【bsauce读论文】2022-CCS-DirtyCred: Escalating Privilege in Linux Kernel

  11. 【kernel exploit】CVE-2021-41073 内核类型混淆漏洞利用分析 —— io_uring 相关漏洞

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React Fiber 使用 MessageChannel + requestAnimationFrame 模拟实现 requestIdleCallback

由于 requestIdleCallback 兼容性较差且不支持 Safari&#xff0c;React Fiber 需要实现一个 requestIdleCallback polyfill 做浏览器兼容&#xff1b; MDN RequestIdleCallbackMDN RequestAnimationFrameMDN MessageChannel 以下为其使用 MessageChannel requestAnimationF…

vivado中ila的使用方法记录

ILA工具生成方法 一、 ILA工具介绍 在FPGA的开发中&#xff0c;当完成代码设计后&#xff0c;为了验证代码的准确性和各种不同条件下的可靠性&#xff0c;往往需要优先想到通过逻辑仿真进行相关验证。使用逻辑仿真进行验证虽然可以周密的考虑给出不同输入条件下的输出结果或交…

“智慧赋能 强链塑链”—— 汽车行业供应链管理数字化应用探讨

01车企供应链数字化的必要性 汽车供应链是一个复杂的系统&#xff0c;很多汽车企业因为供应链管理不当&#xff0c;造成资源浪费、成本高、客户满意度低等一系列问题&#xff1b;而汽车行业规模技术门槛高、配合协同复杂的特性&#xff0c;决定了其供应链缺口无法在短时间内填…

Three.js系列-报错export ‘Geometry‘ (imported as ‘THREE‘) was not found in ‘three‘

今天遇到报错export ‘Geometry’ (imported as ‘THREE’) was not found in ‘three’ port Geometry (imported as THREE) was not found in three (possible exports: ACESFilmicToneMapping, AddEquation, AddOperation, AdditiveAnimationBlendMode, AdditiveBlending, …

为什么大家都不用postman而选择 Apifox呢?

丢掉 Postman&#xff0c;Apifox 更香 作为开发者&#xff0c;丢掉 Postman 和 Jmeter吧&#xff0c;这款国产 API 工具更香&#xff0c;更安全&#xff01;一键即可导入 Postman 数据&#xff01; 一、Apifox 是什么&#xff1f; 1、Apifox 定位 Apifox Postman Swagger …

Altium Designer(AD)局域网内使用解冲突

1. Altium Designer 版本 AD15.0.8&#xff0c;电路设计软件&#xff0c;硬件工攻城狮必备技能&#xff0c;软件攻城狮也要会一点点 2. AD软件出现“Your license is already used on computer “LAPTOP-F99R6OR1” using product “AltiumDesigner” 用同事的安装包解压安装的…

Mysql 索引详细解析——底层->应用

1、索引的数据结构 1.1 概述 索引&#xff08;index&#xff09;是帮助Mysql高效获取数据的数据结构。 索引的本质&#xff1a; 索引是数据结构。简单理解为“排好序的快速查找数据结构”&#xff0c;满足特定查找算法。这些数据结构以某种方式指向数据&#xff0c; 这样就可…

期末计算机网络知识点总结

开篇提示: 因为博主图床使用的是GitHub进行存储的&#xff0c;因此当出现图片无法加载&#xff0c;可以尝试更换网络&#xff0c;或者用其他上网方法。当时想用 Gitee进行存储&#xff0c;但一直失败&#xff0c;过一段时间再试几次。目前这篇博文会一直更新&#xff0c;补充新…

Python的内置函数和保留字(关键字)

目录 内置函数 关键字&#xff08;保留字&#xff09; 内置函数 Python 解释器自带的函数叫做内置函数&#xff0c;这些函数可以直接使用&#xff0c;不需要导入某个模块。 将使用频繁的代码段封装起来&#xff0c;并给它起一个名字&#xff0c;以后使用的时候只要知道名字就可…

虹科分享 | 《面向金融行业的商业智能解决方案》白皮书

在以数据为中心的商业环境中&#xff0c;金融业面临着众多挑战&#xff0c;包括瞬息万变的市场动态、监管要求以及客户期望。金融行业从各种来源生成大量数据&#xff0c;包括交易、客户互动、市场数据和内部运营等。然而&#xff0c;如果缺乏有效的数据管理和分析&#xff0c;…

算法修炼之筑基篇——筑基二层后期(初步理解解决贪心算法)

✨博主&#xff1a;命运之光 &#x1f984;专栏&#xff1a;算法修炼之练气篇 &#x1f353;专栏&#xff1a;算法修炼之筑基篇 ✨博主的其他文章&#xff1a;点击进入博主的主页 前言&#xff1a;学习了算法修炼之练气篇想必各位蒟蒻们的基础已经非常的扎实了&#xff0c;下来…

【opencv】无法打开源文件opencv.hpp | bug记录

问题描述 今天这个bug其实比较搞笑&#xff1a; 原因分析&#xff1a; 那就是我们的VS软件没有识别到安装的库。肯定是下面三个环节哪里出错了&#xff1a; 1&#xff09;项目属性里的包含目录&#xff08;include&#xff09; 2&#xff09;项目属性里的库目录(lib) 3&…

随笔记录阿里云开发者社区Java开发高级技能自测20道题

目录 【单选】1.MyBatis中&#xff0c;主要使用哪个Java 接口来执行SQL命令&#xff1f;【单选】2.Spring中ApplicationContext的主要用法是&#xff1f;【单选】3.MySQL中&#xff0c;使用正则表达式查找news表中title以S或Q或L字母开头的所有数据&#xff0c;语句是&#xff…

【MySql】MySql的数据类型

文章目录 数据类型分类数值类型tinyint类型bit类型小数类型floatdecimal 字符串类型charvarchar日期和时间类型enum和set 数据类型分类 对于数据类型分类&#xff0c;这里简单分为数值类型&#xff08;如BIT,BOOL,INT&#xff09;&#xff0c;文本、二进制类型&#xff08;如CH…

JMeter 测试 ActiveMq

JMeter 测试 ActiveMq 的资料非常少&#xff0c; 我花了大量的时间才研究出来 关于ActiveMq 的文章请参考我另外的文章。 版本号: ActiveMq 版本号: 5.91 Jmeter 版本号: 1.13 添加ActiveMq 的jar包 将 ActiveMq 下的 "activemq-all-5.9.1.jar" 复制…

labelme安装与打包为独立exe程序(超级详细版!!!!)

文章目录 labelme安装与打包为独立exe安装打包以上全部命令直接复制粘贴就行&#xff01;&#xff01;&#xff01;&#xff01; labelme安装与打包为独立exe 按照官网给出的详细教程即可&#xff0c;不用去看一些博客&#xff08;都过时了&#xff09;。。。label官网 简要说…

Linux操作

Linux操作 一、Linux操作 1.安装yum包&#xff1a; $ yum install PACKAGE_NAME yum install mysql2.取出yum包装&#xff1a; $ yum remove PACKAGE_NAME yum remove mysql3.重新安装一个yum包&#xff1a; $ yum reinstall PACKAGE_NAME yum reinstall mysql4.搜索yum包…

tb6612电机驱动软件开发(cubeide工程调试,引脚等设置)

tb6612电机驱动软件开发(cubeide工程调试&#xff0c;引脚等设置&#xff09; 文章目录 tb6612电机驱动软件开发(cubeide工程调试&#xff0c;引脚等设置&#xff09;CLOCK(RCC)SYSGPIONVICTimer定时器配置PWM输出设置电机转速定时器编码模式普通定时器 UART串口设置 CLOCK(RCC…

unittest测试框架详解

单元测试的定义 1. 什么是单元测试&#xff1f; ​ 单元测试是指&#xff0c;对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作&#xff0c;这里的最小可测试单元通常是指函数或者类&#xff0c;一般是开发来做的&#xff0c;按照测试阶段来分&a…

Vue实现锁屏功能

这两天刚做了个新需求&#xff0c;要做个系统锁屏&#xff08;当然锁的是当前的系统&#xff09;&#xff0c;就类似于电脑锁屏似的。 共两种情况下锁屏&#xff0c;一种是无操作一定时间后自动锁屏&#xff1b;第二种是可以按下组合键&#xff08;快捷键&#xff09;主动进行锁…