sev1n75's Tech Blog

路漫漫其修远兮,吾将上下而求索

io_uring 学习笔记

| 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

    eentry,表示队列的一个项(元素),类似 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 的文件或用户缓冲区”(暂时没了解)
  • liburing

    图 5

| 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;
    };
  • 映射内存

    图 2
    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
    struct 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
    */
    };
    };
    图 1
  • 通讯通道(Communication Channel)

    这篇文章翻译的一点小问题,可能是复制代码错了

    放入 sqe

    sqring->sqes应该是映射的sqe数组的地址

    图 3

    取出 cqe

    图 4

| 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
2
3
4
5
6
int io_uring_queue_init(unsigned entries, struct io_uring *ring, unsigned flags);
struct io_uring ring;
io_uring_queue_init(0x10, &ring, 0);

void io_uring_queue_exit(struct io_uring *ring);
io_uring_queue_exit(&ring)
  • 准备并提交sqe
1
2
3
4
5
6
7
8
9
10
struct io_uring_sqe *sqe;
struct io_uring_sqe *cqe;

struct io_uring_sqe *io_uring_get_sqe(struct io_uring *ring);
sqe = io_uring_get_sqe(&ring);
// io_uring_prep_xxxx
io_uring_prep_openat(sqe, AT_FDCWD, "/flag", O_RDONLY, 0);

int io_uring_submit(struct io_uring *ring);
io_uring_submit(&ring);
  • 等待完成
1
2
int io_uring_wait_cqe(struct io_uring *ring, struct io_uring_cqe **cqe_ptr);
io_uring_wait_cqe(&ring, &cqe);
  • 释放cqe
1
2
3
// 可选
void io_uring_cqe_seen(struct io_uring *ring, struct io_uring_cqe *cqe);
io_uring_cqe_seen(ring, cqe);

| 2 | a3✌️【PWN.0x02】- 0x0C. io_uring 与异步 IO 相关

与内核利用相关,先收藏,后续看

| 3 | 例子

| 3.1 | ACTF2023-master-of-orw