[PATCH 2/6] nvme-fc: fix iterator over rport->ctrl_list in nvme_fc_match_disconn_ls()
Hannes Reinecke
hare at suse.de
Thu Nov 5 23:38:44 PST 2026
On 11/6/26 2:58 AM, Guixin Liu wrote:
> Commit b71cbcf7d170e ("nvme-fc: don't hold rport lock when putting ctrl")
> switched the loop to list_for_each_entry_safe() to allow dropping the
> rport lock for the nvme_fc_ctrl_put() of a non-matching controller.
> But the safe iterator only protects the current entry within one
> iteration: the next entry is cached before the body runs, and while
> the lock is dropped that entry can run through its own final put into
> nvme_fc_ctrl_free(), which removes it from the list and frees it.
> The next iteration then dereferences freed memory.
>
> The safe variant is not needed here at all. The reference the loop
> takes on the current entry pins it on the list until the iteration
> moves on, so a plain list_for_each_entry() tolerates the lock drop:
> while the lock is held the current entry cannot be unlinked, and once
> the lock is dropped the cached ->next of the pinned entry still points
> at a valid list member or the head.
>
> Switch back to the plain iterator.
>
> Fixes: b71cbcf7d170e ("nvme-fc: don't hold rport lock when putting ctrl")
> Cc: stable at vger.kernel.org
> Signed-off-by: Guixin Liu <kanie at linux.alibaba.com>
>
> ---
>
> Reproduced under KASAN in a VM through fcloop with three or more
> controllers on one remote port, a tbkas force-reconnect racing
> concurrent controller disconnects. The lock-drop window was widened
> with a test-only mdelay(200) inserted at the same place in both the
> buggy and the fixed version so the iterator is the only variable; the
> stock microsecond window did not trigger in 400 iterations of the
> same workload.
>
> BUG: KASAN: slab-use-after-free in nvme_fc_match_disconn_ls
> Read of size 8 by task kworker
> Workqueue: events nvme_fc_handle_ls_rqst_work
> Call Trace:
> nvme_fc_match_disconn_ls
> nvme_fc_ls_disconnect_assoc
> nvme_fc_handle_ls_rqst_work
> process_one_work
>
> The object was allocated by nvme_fc_alloc_ctrl() on connect and freed
> by nvme_free_ctrl() on the concurrent teardown of the cached next
> entry. With the plain iterator the identical amplified sequence
> reports nothing and survives; the read hits the pinned current entry,
> which cannot be freed while the loop holds its reference.
> ---
> drivers/nvme/host/fc.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c
> index 0b9ff50d26ff..edf192055618 100644
> --- a/drivers/nvme/host/fc.c
> +++ b/drivers/nvme/host/fc.c
> @@ -1470,14 +1470,14 @@ nvme_fc_match_disconn_ls(struct nvme_fc_rport *rport,
> {
> struct fcnvme_ls_disconnect_assoc_rqst *rqst =
> &lsop->rqstbuf->rq_dis_assoc;
> - struct nvme_fc_ctrl *ctrl, *tmp, *ret = NULL;
> + struct nvme_fc_ctrl *ctrl, *ret = NULL;
> struct nvmefc_ls_rcv_op *oldls = NULL;
> u64 association_id = be64_to_cpu(rqst->associd.association_id);
> unsigned long flags;
>
> spin_lock_irqsave(&rport->lock, flags);
>
> - list_for_each_entry_safe(ctrl, tmp, &rport->ctrl_list, ctrl_list) {
> + list_for_each_entry(ctrl, &rport->ctrl_list, ctrl_list) {
> if (!nvme_fc_ctrl_get(ctrl))
> continue;
> spin_lock(&ctrl->lock);
Not sure here. The implication of the function is that the any
controller might've been deleted during each loop iteration
(disconnect might be triggered from both sides, either host
_or_ controller).
So while it's true that a controller might not get deleted while
a reference is held, that other process might drop a reference
to the controller while the first process is executing the loop.
Then the call to 'nvme_fc_ctrl_put()' at the end of the loop
will be the last reference, the controller gets deleted,
and the ->next element becomes invalid causing the loop
to crash.
So, no.
And the issue you've described should've caused the
nvme_fc_ctrl_get() at the start of the loop to return
'false'; why didn't it trigger here?
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare at suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
More information about the Linux-nvme
mailing list