Forwarded: [PATCH] nvmet-fc: fix use-after-free of nvmet_fc_ls_req_op

From: syzbot

Date: Sun Sep 20 2026 - 10:09:33 EST


For archival purposes, forwarding an incoming command email to
linux-kernel@xxxxxxxxxxxxxxx, syzkaller-bugs@xxxxxxxxxxxxxxxx.

***

Subject: [PATCH] nvmet-fc: fix use-after-free of nvmet_fc_ls_req_op
Author: kartikey406@xxxxxxxxx

#syz test: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git master


nvmet_fc_free_pending_reqs() unconditionally frees each queued
nvmet_fc_ls_req_op (lsop) when a targetport is unregistered, but an
LLDD (e.g. fcloop) may still hold a pointer to that request queued on
its own async work, with no way for the target core to know.

If the targetport is torn down while such a request is still in
flight, the LLDD later dereferences the freed lsop when its completion
work runs, causing a slab-use-after-free.

Fix this by refcounting nvmet_fc_ls_req_op: one reference for the
core's ls_req_list, and one for the in-flight request handed to the
LLDD via ->ls_req(). The object is only freed once both the normal
completion path (__nvmet_fc_finish_ls_req) and teardown
(nvmet_fc_free_pending_reqs) have dropped their reference, whichever
runs last.

Reported-by: syzbot+77955102efac681ec73b@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=77955102efac681ec73b
Signed-off-by: Deepanshu kartikey <kartikey406@xxxxxxxxx>
---
drivers/nvme/target/fc.c | 38 ++++++++++++++++++++++++--------------
1 file changed, 24 insertions(+), 14 deletions(-)

diff --git a/drivers/nvme/target/fc.c b/drivers/nvme/target/fc.c
index 1b557775e033..a25f7579049d 100644
--- a/drivers/nvme/target/fc.c
+++ b/drivers/nvme/target/fc.c
@@ -56,6 +56,7 @@ struct nvmet_fc_ls_req_op { /* for an LS RQST XMT */
bool req_queued;

struct work_struct put_work;
+ struct kref ref;
};


@@ -339,6 +340,22 @@ fc_dma_unmap_sg(struct device *dev, struct scatterlist *sg, int nents,
dma_unmap_sg(dev, sg, nents, dir);
}

+static void
+nvmet_fc_ls_req_op_release(struct kref *ref)
+{
+ struct nvmet_fc_ls_req_op *lsop =
+ container_of(ref, struct nvmet_fc_ls_req_op, ref);
+ struct nvmet_fc_tgtport *tgtport = lsop->tgtport;
+ struct nvmefc_ls_req *lsreq = &lsop->ls_req;
+
+ if (lsop->req_queued)
+ fc_dma_unmap_single(tgtport->dev, lsreq->rqstdma,
+ (lsreq->rqstlen + lsreq->rsplen),
+ DMA_BIDIRECTIONAL);
+ queue_work(nvmet_wq, &lsop->put_work);
+}
+
+

/* ********************** FC-NVME LS XMT Handling ************************* */

@@ -347,7 +364,6 @@ static void
__nvmet_fc_finish_ls_req(struct nvmet_fc_ls_req_op *lsop)
{
struct nvmet_fc_tgtport *tgtport = lsop->tgtport;
- struct nvmefc_ls_req *lsreq = &lsop->ls_req;
unsigned long flags;

spin_lock_irqsave(&tgtport->lock, flags);
@@ -363,12 +379,8 @@ __nvmet_fc_finish_ls_req(struct nvmet_fc_ls_req_op *lsop)

spin_unlock_irqrestore(&tgtport->lock, flags);

- fc_dma_unmap_single(tgtport->dev, lsreq->rqstdma,
- (lsreq->rqstlen + lsreq->rsplen),
- DMA_BIDIRECTIONAL);
-
out_putwork:
- queue_work(nvmet_wq, &lsop->put_work);
+ kref_put(&lsop->ref, nvmet_fc_ls_req_op_release);
}

static int
@@ -406,12 +418,16 @@ __nvmet_fc_send_ls_req(struct nvmet_fc_tgtport *tgtport,

lsop->req_queued = true;

+ kref_init(&lsop->ref);
+ kref_get(&lsop->ref);
spin_unlock_irqrestore(&tgtport->lock, flags);

ret = tgtport->ops->ls_req(&tgtport->fc_target_port, lsop->hosthandle,
lsreq);
- if (ret)
+ if (ret) {
+ kref_put(&lsop->ref, nvmet_fc_ls_req_op_release);
goto out_unlink;
+ }

return 0;

@@ -1591,7 +1607,6 @@ static void
nvmet_fc_free_pending_reqs(struct nvmet_fc_tgtport *tgtport)
{
struct nvmet_fc_ls_req_op *lsop;
- struct nvmefc_ls_req *lsreq;
struct nvmet_fc_ls_iod *iod;
int i;

@@ -1611,12 +1626,7 @@ nvmet_fc_free_pending_reqs(struct nvmet_fc_tgtport *tgtport)
if (!lsop->req_queued)
continue;

- lsreq = &lsop->ls_req;
- fc_dma_unmap_single(tgtport->dev, lsreq->rqstdma,
- (lsreq->rqstlen + lsreq->rsplen),
- DMA_BIDIRECTIONAL);
- nvmet_fc_tgtport_put(tgtport);
- kfree(lsop);
+ kref_put(&lsop->ref, nvmet_fc_ls_req_op_release);
}
}

--
2.43.0