[PATCH v2] nvmet-fc: flush nvmet_wq twice on targetport unregister

From: Nguyen Ngoc Thang

Date: Mon Sep 21 2026 - 12:12:30 EST


nvmet_fc_delete_assoc_work() sends a Disconnect Association LS. If the
LLDD completes it asynchronously, e.g. fcloop failing it with
-ECONNREFUSED from a work item once the remote port is gone, the
completion is queued from within nvmet_fc_unregister_targetport()'s
flush_workqueue(). flush_workqueue() does not wait for work queued
while it runs, so nvmet_fc_free_pending_reqs() frees the pending lsop
before the completion runs, which then calls lsreq->done() on freed
memory:

BUG: KASAN: slab-use-after-free in fcloop_rport_lsrqst_work+0x242/0x2e0
Workqueue: nvmet-wq fcloop_tport_lsrqst_work
Allocated by nvmet_fc_xmt_disconnect_assoc
Freed by nvmet_fc_free_pending_reqs
nvmet_fc_unregister_targetport
fcloop_delete_target_port

Flush a second time so such completions have run before the remaining
pending requests are freed.

Reported-by: syzbot+77955102efac681ec73b@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=77955102efac681ec73b
Fixes: bbccbf791e6f ("nvmet-fc: free pending reqs on tgtport unregister")
Signed-off-by: Nguyen Ngoc Thang <ngocthang2710.1999@xxxxxxxxx>
---
Changes in v2:
- Drop the fcloop change; the async -ECONNREFUSED completion mimics what
an HBA does (Daniel). Instead flush nvmet_wq a second time in
nvmet_fc_unregister_targetport() so the LS completion queued by
nvmet_fc_delete_assoc_work() runs before the pending requests are freed.
- A blktest reproducing this is sent separately (nvme/071) (Hannes).
- v1: https://lore.kernel.org/all/20260920163804.67858-1-ngocthang2710.1999@xxxxxxxxx/

drivers/nvme/target/fc.c | 2 ++
1 file changed, 2 insertions(+)

diff --git a/drivers/nvme/target/fc.c b/drivers/nvme/target/fc.c
index 1b557775e033..cf9fd304a2b5 100644
--- a/drivers/nvme/target/fc.c
+++ b/drivers/nvme/target/fc.c
@@ -1646,6 +1646,8 @@ nvmet_fc_unregister_targetport(struct nvmet_fc_target_port *target_port)
/* terminate any outstanding associations */
__nvmet_fc_free_assocs(tgtport);

+ flush_workqueue(nvmet_wq);
+ /* assoc deletion sends an LS whose completion is queued while flushing */
flush_workqueue(nvmet_wq);

nvmet_fc_free_pending_reqs(tgtport);
--
2.43.0