| 0 | 前言
这是我学习 io_uring
的笔记。
2023ACTF
比赛中用io_uring
编写shellcode
绕过沙盒读/flag
。
io_uring
正在火热迭代,本文的结构体源码取自内核版本 6.6
| 1 | 【译】高性能异步 IO —— io_uring (Effecient IO with io_uring)
| 1.1 | 总结
io_uring:Linux 内核的高性能异步 I/O 框架
io_uring 特点:完全异步,支持任意类型的 I/O,灵活可拓展
io_uring 基本原理:
提供两个接口,一个将 I/O 请求提交到内核,一个从内核接收完成事件。
⚠️核心在于⚠️,io_uring 在当前进程和内核之间共享内存 (通过
mmap
在当前进程映射) ,通过操控共享内存与内核交互,避免了频繁调用syscall
。struct io_uring_cqe
&struct io_uring_sqe
e
指entry
,表示队列的一个项(元素),类似tcache_entry
。这两个结构体定义了从
CQ
能取出什么数据,应该放什么数据进SQ
io_uring 系统调用 API:
io_uring_setup(2)
,io_uring_enter(2)
,io_uring_register(2)
io_uring_setup()
: 设置上下文io_uring_enter()
: 通知内核,有请求需要处理io_uring_register()
: “注册用于异步 I/O 的文件或用户缓冲区”(暂时没了解)
| 1.2 | 细节补充
| 1.2.1 | io_uring_setup()
1 | int io_uring_setup(u32 entries, struct io_uring_params *p); |
返回值
fd
: 用于映射内核-当前程序共享内存来操作SQ
,CQ
,SQE
entries
: 队列中的项(元素)数量struct io_uring_params *p
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27/*
* Passed in for io_uring_setup(2). Copied back with updated info on success
*/
struct io_uring_params {
__u32 sq_entries;
__u32 cq_entries;
__u32 flags;
__u32 sq_thread_cpu;
__u32 sq_thread_idle;
__u32 features;
__u32 wq_fd; // I/O uring 的工作队列(Work Queue)文件描述符。
__u32 resv[3];// 保留字段,用于填充对齐和扩展结构体大小。
struct io_sqring_offsets sq_off; // 用于存储 Submission Queue (SQ) 的偏移量信息的结构体。
struct io_cqring_offsets cq_off;
};
struct io_sqring_offsets {
__u32 head;
__u32 tail;
__u32 ring_mask;
__u32 ring_entries;
__u32 flags;
__u32 dropped; // 没有提交的 sqe 数量
__u32 array; // sqe 索引数组
__u32 resv1;
__u64 user_addr;
};映射内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25struct app_sq_ring { // 定义结构体,保存sq_ring的各成员的地址
unsigned *head;
unsigned *tail;
unsigned *ring_mask;
unsigned *ring_entries;
unsigned *flags;
unsigned *dropped;
unsigned *array;
};
struct app_sq_ring app_setup_sq_ring(int ring_fd, struct io_uring_params *p){
struct app_sq_ring sqring;
// IORING_OFF_SQ_RING,可换成上图另外两个来映射内存
void *ptr = mmap(0, p->sq_off.array + p->sq_entries * sizeof(__u32), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, ring_fd, IORING_OFF_SQ_RING);
sqring.head = ptr + p->sq_off.head;
sqring.tail = ptr + p->sq_off.tail;
sqring.ring_mask = ptr + p->sq_off.ring_mask;
sqring.ring_entries = ptr + p->sq_off.ring_entries;
sqring.flags = ptr + p->sq_off.flags;
sqring.dropped = ptr + p->sq_off.dropped;
sqring.array = ptr + p->sq_off.array;
return sqring;
}
| 1.2.2 | 如何与共享内存交互
struct io_uring_cqe
1
2
3
4
5
6
7
8
9
10
11
12
13
14/*
* IO completion data structure (Completion Queue Entry)
*/
struct io_uring_cqe {
__u64 user_data; /* sqe->data submission passed back */
__s32 res; /* */
__u32 flags; // 有 4 个选项,详情看 | 0 | 附的源码
/*
* If the ring is initialized with IORING_SETUP_CQE32, then this field
* contains 16-bytes of padding, doubling the size of the CQE.
*/
__u64 big_cqe[];
};struct io_uring_sqe
有点复杂,省略了部分1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/*
* IO submission data structure (Submission Queue Entry)
*/
struct io_uring_sqe {
__u8 opcode; /* type of operation for this sqe */
__u8 flags; /* IOSQE_ flags */
__u16 ioprio; /* ioprio for the request */
__s32 fd; /* file descriptor to do IO on */
union {
__u64 off; /* offset into file */
// ...
};
union {
__u64 addr; /* pointer to buffer or iovecs */
// ...
};
__u32 len; /* buffer size or number of iovecs */
// ...
__u64 user_data; /* data to be passed back at completion time */
/* pack this to avoid bogus arm OABI complaints */
// ...
union {
// ...
/*
* If the ring is initialized with IORING_SETUP_SQE128, then
* this field is used for 80 bytes of arbitrary command data
*/
};
};通讯通道(Communication Channel)
这篇文章翻译的一点小问题,可能是复制代码错了
放入
sqe
。sqring->sqes
应该是映射的sqe
数组的地址取出
cqe
| 1.2.3 | io_uring_enter()
1 | int io_uring_enter(unsigned int fd, unsigned int to_submit, unsigned int min_complete, unsigned int flags, sigset_t *sig); |
to_submit
: 告诉内核准备消费的提交的sqe数量min_complete
: 要求内核等待请求完成数量sig
: 指向signal mask
其余的文章解释的很清楚
| 1.2.3 | liburing
- 初始化及释放
1 | int io_uring_queue_init(unsigned entries, struct io_uring *ring, unsigned flags); |
- 准备并提交
sqe
1 | struct io_uring_sqe *sqe; |
- 等待完成
1 | int io_uring_wait_cqe(struct io_uring *ring, struct io_uring_cqe **cqe_ptr); |
- 释放
cqe
1 | // 可选 |
| 2 | a3✌️【PWN.0x02】- 0x0C. io_uring 与异步 IO 相关
与内核利用相关,先收藏,后续看