[PATCH v2 2/2] nvmet: fix use-after-free in passthru I/O hotpath

Nilay Shroff nilay at linux.ibm.com
Mon Nov 2 17:19:03 PST 2026


Hi Shin'ichiro,

On 11/2/26 4:19 PM, Shin'ichiro Kawasaki wrote:
> Hi, Nilay
> 
> I ran blktests with the kernel v7.4-rc1, and found the test case nvme/037 hangs
> when it runs for rdma transport and siw driver [1]. I bisected and found this
> patch as the commit 5e12d81874a1 is the trigger. If this han is a new issue, fix
> actions will be appreciated.

Thanks for report!

I tried recreating this issue on my system running nvme/037 multiple times but
I couldn't recreate it.

Looking through the code carefully, it seems that we release the passthru request
reference in __nvmet_req_complete() after ops->queue_repsonse() returns. However once
ops->queue_response() is called, the transport may complete and recycle the request
on another CPU. If the request is reused, its req->p.ref_held field may be reset
to false before __nvmet_req_complete() checks it in nvmet_put_passthru_ref().
Consequently, the passthru reference may not be released, leaving
wait_for_completion(&passthru->disable_done) in __nvmet_passthru_ctrl_disable()
blocked indefinitely.

Based on the above, I prepared a patch[1] to fix this issue. May I ask you to
validate this patch on your system? If this works for you then I'll send out a
formal patch.

[1]
diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
index a8171b88035c..714ac8add4c8 100644
--- a/drivers/nvme/target/core.c
+++ b/drivers/nvme/target/core.c
@@ -804,6 +804,7 @@ static void __nvmet_req_complete(struct nvmet_req *req, u16 status)
  {
  	struct nvmet_ns *ns = req->ns;
  	struct nvmet_pr_per_ctrl_ref *pc_ref = req->pc_ref;
+	struct nvmet_passthru *passthru = nvmet_passthru_ref_held(req);

  	if (!req->sq->sqhd_disabled)
  		nvmet_update_sq_head(req);
@@ -822,7 +823,8 @@ static void __nvmet_req_complete(struct nvmet_req *req, u16 status)
  	if (ns)
  		nvmet_put_namespace(ns);

-	nvmet_put_passthru_ref(req);
+	if (passthru)
+		nvmet_put_passthru_ref(passthru);
  }

  void nvmet_req_complete(struct nvmet_req *req, u16 status)
diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
index d7804bf91aaa..f8205fbfee6e 100644
--- a/drivers/nvme/target/nvmet.h
+++ b/drivers/nvme/target/nvmet.h
@@ -824,6 +824,15 @@ static inline struct nvmet_passthru *nvmet_subsys_passthru(
  	return subsys->passthru;
  }

+static inline struct nvmet_passthru *nvmet_passthru_ref_held(
+		struct nvmet_req *req)
+{
+	if (req->p.ref_held)
+		return nvmet_req_subsys(req)->passthru;
+
+	return NULL;
+}
+
  static inline bool nvmet_get_passthru_ref(struct nvmet_req *req)
  {
  	if (!percpu_ref_tryget_live(&nvmet_req_subsys(req)->passthru->ref))
@@ -833,13 +842,9 @@ static inline bool nvmet_get_passthru_ref(struct nvmet_req *req)
  	return true;
  }

-static inline void nvmet_put_passthru_ref(struct nvmet_req *req)
+static inline void nvmet_put_passthru_ref(struct nvmet_passthru *passthru)
  {
-	if (!req->p.ref_held)
-		return;
-
-	req->p.ref_held = false;
-	percpu_ref_put(&nvmet_req_subsys(req)->passthru->ref);
+	percpu_ref_put(&passthru->ref);
  }
  #else /* CONFIG_NVME_TARGET_PASSTHRU */
  static inline void nvmet_passthru_subsys_free(struct nvmet_subsys *subsys)
@@ -865,11 +870,16 @@ static inline struct nvmet_passthru *nvmet_subsys_passthru(
  {
  	return NULL;
  }
+static inline struct nvmet_passthru *nvmet_passthru_ref_held(
+		struct nvmet_req *req)
+{
+	return NULL;
+}
  static inline bool nvmet_get_passthru_ref(struct nvmet_req *req)
  {
  	return false;
  }
-static inline void nvmet_put_passthru_ref(struct nvmet_req *req)
+static inline void nvmet_put_passthru_ref(struct nvmet_passthru *passthru)
  {
  }
  #endif /* CONFIG_NVME_TARGET_PASSTHRU */




More information about the Linux-nvme mailing list