Commit 86f938333e for qemu.org

commit 86f938333e2faae7c60ca66c0809f5495c59aa4b
Author: Minwoo Im <minwoo.im.dev@gmail.com>
Date:   Wed Jul 29 19:34:54 2026 +0900

    hw/nvme: cancel inflight requests on controller reset

    nvme_ctrl_reset() freed every SQ/CQ right after nvme_ns_drain(), which
    only waits out requests on a per-namespace BlockBackend. That is safe
    as long as the guest first tore down I/O queues gracefully (Delete
    I/O SQ/CQ), since nvme_del_sq() already cancels and waits for
    anything left on a queue before freeing it.

    A reset that happens without that graceful sequence first (e.g. an
    abrupt/asynchronous controller reset) can still have commands
    inflight on blk_aio_*. Freeing sq/cq before those complete leaves
    their completion callbacks (nvme_rw_cb() and friends) to run against
    already-freed NvmeRequest/NvmeSQueue/NvmeCQueue memory via
    nvme_enqueue_req_completion(), causing a use-after-free/segfault.

    Run nvme_sq_cancel_inflight() over every queue in nvme_ctrl_reset()
    before the free loops, so no in-flight blk_aio_* callback can fire
    after sq/cq memory is freed.

    Cc: qemu-stable@nongnu.org
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3398
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3883
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4068
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4072
    Signed-off-by: Minwoo Im <minwoo.im@samsung.com>
    Signed-off-by: Klaus Jensen <k.jensen@samsung.com>

diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c
index e355c256a5..8461b6fbde 100644
--- a/hw/nvme/ctrl.c
+++ b/hw/nvme/ctrl.c
@@ -8040,6 +8040,18 @@ static void nvme_ctrl_reset(NvmeCtrl *n, NvmeResetType rst)
         nvme_ns_drain(ns);
     }

+    /*
+     * Cancel and wait out every inflight command on every queue first. A
+     * reset is not required to be preceded by the guest's graceful
+     * Delete I/O SQ/CQ sequence, so sq/cq must not be freed below while a
+     * blk_aio_* completion for them could still be in flight.
+     */
+    for (i = 0; i < n->num_queues; i++) {
+        if (n->sq[i] != NULL) {
+            nvme_sq_cancel_inflight(n->sq[i], NVME_CMD_ABORT_SQ_DEL);
+        }
+    }
+
     for (i = 0; i < n->num_queues; i++) {
         if (n->sq[i] != NULL) {
             nvme_free_sq(n->sq[i], n);