Re: [PATCH 3/3] hw/nvme: cancel inflight requests on controller reset

Michael Tokarev <[email protected]> Wed, 5 Aug 2026 08:56:34 +0300
Newsgroups gmane.comp.emulators.qemu.block,gmane.comp.emulators.qemu
Message-ID <[email protected]>
On 7/29/26 13:34, Minwoo Im wrote:
> 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.
> 
> Signed-off-by: Minwoo Im <[email protected]>
> ---
>   hw/nvme/ctrl.c | 12 ++++++++++++
>   1 file changed, 12 insertions(+)
> 
> diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c
> index 86b86b2a0fbb..22b1b9636878 100644
> --- a/hw/nvme/ctrl.c
> +++ b/hw/nvme/ctrl.c
> @@ -8035,6 +8035,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);

BTW, can't the newly added loop be fold into the existing loop, like

    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);
            nvme_free_sq(n->sq[i], n);
        }
    }

Thanks,

/mjt