io_uring_cmd() treats any uring_cmd carrying IORING_URING_CMD_MULTISHOT that returns >= 0 as "multishot armed, completion deferred" and returns IOU_ISSUE_SKIP_COMPLETE, expecting the provider to complete the request later. But the flag is user-controlled and validated only against buffer select, not against provider capability. A ->uring_cmd() handler that does not implement multishot and returns a normal >= 0 result then has its request skipped and never completed, leaking the io_kiocb and its io_async_cmd: BUG: memory leak unreferenced object (size 248): kmem_cache_alloc_bulk_noprof+0x272/0x3f0 __io_alloc_req_refill+0x4a/0x150 io_submit_sqes.cold+0x16e/0x20b __do_sys_io_uring_enter+0x56d/0xd60 syzbot hit this via ublk UBLK_U_CMD_ADD_DEV, but it is kernel-wide: the same leak reproduces with SOCKET_URING_OP_SIOCINQ on any socket fd, which returns the queued byte count and never inspects cmd->flags. No in-tree provider of multishot actually returns >= 0. Both io_cmd_poll_multishot() and ublk_handle_batch_fetch_cmd return -EIOCBQUEUED, Only skip completion when the command is really multishot, i.e. REQ_F_APOLL_MULTISHOT is set. Otherwise fall through and complete the request normally with its result. Fixes: 620a50c92700 ("io_uring: uring_cmd: add multishot support") Reported-by: syzbot+a4ccdd7ebf452e4d4701@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a4ccdd7ebf452e4d4701 Tested-by: syzbot+a4ccdd7ebf452e4d4701@syzkaller.appspotmail.com Signed-off-by: Vasileios Almpanis --- Questions / Notes: - I put this in the core io_uring_cmd() rather than in provider specific code because the leak is independent of the provider. From my research so far no ->uring_cmd() rejects IORING_URING_CMD_MULTISHOT, they just ignore it. The flag is only validated against buffer-select in io_uring_cmd_prep(). So any handler that returns a plain >= 0 result with the user-set flag leaks. - Instead of checking if it has been really armed should we just drop the >= 0 check? The in-tree code that supports multishot returns -EIOCBQUEUED from what I have seen so far. --- io_uring/uring_cmd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/io_uring/uring_cmd.c b/io_uring/uring_cmd.c index c14c22cff49e..a2899a852879 100644 --- a/io_uring/uring_cmd.c +++ b/io_uring/uring_cmd.c @@ -269,7 +269,8 @@ int io_uring_cmd(struct io_kiocb *req, unsigned int issue_flags) } ret = file->f_op->uring_cmd(ioucmd, issue_flags); - if (ioucmd->flags & IORING_URING_CMD_MULTISHOT) { + if ((ioucmd->flags & IORING_URING_CMD_MULTISHOT) && + (req->flags & REQ_F_APOLL_MULTISHOT)) { if (ret >= 0) return IOU_ISSUE_SKIP_COMPLETE; } --- base-commit: d58772d8520c7ef247c4b95c9bd76d3a25da9ff5 change-id: 20260811-io_uring-169337c74617 Best regards, -- Vasileios Almpanis