[PATCH 4/4] nvme: harden local IOPOLL user copyback

From: 王 颖杰

Date: Fri Oct 16 2026 - 02:20:16 EST


The local NVMe IOPOLL completion path performs blk_rq_unmap_user()
inline, ignores a possible copyback failure, and relies on the block
layer to free the request only after the callback returns.

Free the completed request explicitly before touching userspace, return
RQ_END_IO_NONE to transfer that ownership, and report an unmap error
when no earlier command error exists. This keeps the inline path
consistent with task-work completion without adding task-work overhead.

The completion PDU is stored in io_uring_cmd rather than the request,
so its saved bio, status, and result remain valid after
blk_mq_free_request().

With a real NVMe poll queue and the task-work path already fixed,
1,280/1,280 commands into an unaligned PROT_NONE destination still
posted success before this change. All 1,280 posted -EFAULT afterward,
while writable controls remained successful.

Fixes: f7bc22ca0d55 ("nvme/io_uring: optimize IOPOLL completions for local ring context")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Signed-off-by: Yingjie Wang <x-professor@xxxxxxxxxxx>
---
drivers/nvme/host/ioctl.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/drivers/nvme/host/ioctl.c b/drivers/nvme/host/ioctl.c
index 06c3f3232ba..e4ea30e9129 100644
--- a/drivers/nvme/host/ioctl.c
+++ b/drivers/nvme/host/ioctl.c
@@ -474,12 +474,18 @@ static enum rq_end_io_ret nvme_uring_cmd_end_io(struct request *req,
*/
if (blk_rq_is_poll(req) && iob &&
iob->poll_ctx == io_uring_cmd_ctx_handle(ioucmd)) {
+ int ret = 0;
+
+ blk_mq_free_request(req);
if (pdu->bio)
- blk_rq_unmap_user(pdu->bio);
+ ret = blk_rq_unmap_user(pdu->bio);
+ if (!pdu->status)
+ pdu->status = ret;
io_uring_cmd_done32(ioucmd, pdu->status, pdu->result, 0);
- } else {
- io_uring_cmd_do_in_task_lazy(ioucmd, nvme_uring_task_cb);
+ return RQ_END_IO_NONE;
}
+
+ io_uring_cmd_do_in_task_lazy(ioucmd, nvme_uring_task_cb);
return RQ_END_IO_FREE;
}

--
2.43.0