All of lore.kernel.org
 help / color / mirror / Atom feed
From: Christoph Hellwig <hch@lst.de>
To: Keith Busch <kbusch@meta.com>
Cc: linux-nvme@lists.infradead.org, axboe@kernel.dk,
	Chris Mason <mason@kernel.org>, Sagi Grimberg <sagi@grimberg.me>,
	Chaitanya Kulkarni <kch@nvidia.com>,
	Keith Busch <kbusch@kernel.org>,
	Guixin Liu <kanie@linux.alibaba.com>
Subject: Re: [PATCH 09/19] nvmet: fix pc_ref use-after-free in preempt and abort
Date: Tue, 3 Nov 2026 07:04:14 +0100	[thread overview]
Message-ID: <20261103060414.GE1862@lst.de> (raw)
In-Reply-To: <20261029155252.2332266-10-kbusch@meta.com>

Adding Guixin for the PR code.

On Thu, Oct 29, 2026 at 08:52:42AM -0700, Keith Busch wrote:
> From: Chris Mason <mason@kernel.org>
> 
> With reservations enabled on a namespace, the target keeps a pc_ref per
> controller, a percpu_ref counting that controller's I/O on the
> namespace. "Preempt and abort" kills the pc_refs of the preempted
> controllers and waits for the kill to be confirmed in
> nvmet_pr_set_ctrl_to_abort(), then waits for the count to drop to zero
> and resurrects them in nvmet_pr_do_abort(). Neither holds a reference
> to the pc_ref, only pr_sem. nvmet_ctrl_destroy_pr() frees the pc_refs
> under subsys->lock when the controller goes away, so a host that
> registers a key, disconnects, and sends preempt and abort with that key
> over a second connection frees a pc_ref that the abort is sleeping on:
> 
>     preempt and abort on C1            last put of C2
>     nvmet_pr_set_ctrl_to_abort()
>       percpu_ref_kill_and_confirm()
>       wait_for_completion()
>                                        nvmet_ctrl_destroy_pr()
>                                          percpu_ref_exit()
>                                          kfree(pc_ref)
>     percpu_ref_switch_to_atomic_rcu()  /* use after free */
> 
> Put the pc_refs that an abort has killed on a per-namespace list.
> nvmet_ctrl_destroy_pr() only flags a pc_ref on that list, and
> nvmet_pr_do_abort() frees flagged ones once it's done with them. The
> list and the flag are written under the xarray lock.
> 
> Fixes: 5a47c2080a73 ("nvmet: support reservation feature")
> Cc: Christoph Hellwig <hch@lst.de>
> Cc: Sagi Grimberg <sagi@grimberg.me>
> Cc: Chaitanya Kulkarni <kch@nvidia.com>
> Signed-off-by: Chris Mason <mason@kernel.org>
> Signed-off-by: Jens Axboe <axboe@kernel.dk>
> Signed-off-by: Keith Busch <kbusch@kernel.org>
> ---
>  drivers/nvme/target/nvmet.h |  5 ++
>  drivers/nvme/target/pr.c    | 92 ++++++++++++++++++++++++++-----------
>  2 files changed, 69 insertions(+), 28 deletions(-)
> 
> diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
> index d7804bf91aaa8..a89b384c4bbc9 100644
> --- a/drivers/nvme/target/nvmet.h
> +++ b/drivers/nvme/target/nvmet.h
> @@ -88,6 +88,8 @@ struct nvmet_pr {
>  	 */
>  	struct semaphore	pr_sem;
>  	struct list_head	registrant_list;
> +	/* The pc_refs that a running "preempt and abort" has killed. */
> +	struct list_head	abort_list;
>  };
>  
>  struct nvmet_pr_per_ctrl_ref {
> @@ -95,6 +97,9 @@ struct nvmet_pr_per_ctrl_ref {
>  	struct completion	free_done;
>  	struct completion	confirm_done;
>  	uuid_t			hostid;
> +	/* Both are written under the lock of ns->pr_per_ctrl_refs. */
> +	struct list_head	abort_entry;
> +	bool			ctrl_gone;
>  };
>  
>  struct nvmet_ns {
> diff --git a/drivers/nvme/target/pr.c b/drivers/nvme/target/pr.c
> index 14eaa99f98fa3..1c0d6cac1a6b3 100644
> --- a/drivers/nvme/target/pr.c
> +++ b/drivers/nvme/target/pr.c
> @@ -468,18 +468,54 @@ static void nvmet_pr_confirm_ns_pc_ref(struct percpu_ref *ref)
>  	complete(&pc_ref->confirm_done);
>  }
>  
> +static void nvmet_pr_free_pc_ref(struct nvmet_pr_per_ctrl_ref *pc_ref)
> +{
> +	percpu_ref_exit(&pc_ref->ref);
> +	kfree(pc_ref);
> +}
> +
> +/*
> + * The controller goes away. "Preempt and abort" sleeps on the pc_refs on its
> + * list, so leave the free of such a pc_ref to nvmet_pr_do_abort().
> + */
> +static void nvmet_pr_erase_pc_ref(struct nvmet_ns *ns, unsigned long cntlid)
> +{
> +	struct nvmet_pr_per_ctrl_ref *pc_ref;
> +
> +	xa_lock(&ns->pr_per_ctrl_refs);
> +	pc_ref = __xa_erase(&ns->pr_per_ctrl_refs, cntlid);
> +	if (pc_ref && !list_empty(&pc_ref->abort_entry)) {
> +		pc_ref->ctrl_gone = true;
> +		pc_ref = NULL;
> +	}
> +	xa_unlock(&ns->pr_per_ctrl_refs);
> +
> +	if (pc_ref)
> +		nvmet_pr_free_pc_ref(pc_ref);
> +}
> +
>  static void nvmet_pr_set_ctrl_to_abort(struct nvmet_req *req, uuid_t *hostid)
>  {
>  	struct nvmet_pr_per_ctrl_ref *pc_ref;
>  	struct nvmet_ns *ns = req->ns;
>  	unsigned long idx;
>  
> +	xa_lock(&ns->pr_per_ctrl_refs);
>  	xa_for_each(&ns->pr_per_ctrl_refs, idx, pc_ref) {
> -		if (uuid_equal(&pc_ref->hostid, hostid)) {
> -			percpu_ref_kill_and_confirm(&pc_ref->ref,
> -						nvmet_pr_confirm_ns_pc_ref);
> -			wait_for_completion(&pc_ref->confirm_done);
> -		}
> +		if (uuid_equal(&pc_ref->hostid, hostid) &&
> +		    list_empty(&pc_ref->abort_entry))
> +			list_add_tail(&pc_ref->abort_entry,
> +				      &ns->pr.abort_list);
> +	}
> +	xa_unlock(&ns->pr_per_ctrl_refs);
> +
> +	list_for_each_entry(pc_ref, &ns->pr.abort_list, abort_entry) {
> +		/* Killed for another host that this command preempts. */
> +		if (percpu_ref_is_dying(&pc_ref->ref))
> +			continue;
> +		percpu_ref_kill_and_confirm(&pc_ref->ref,
> +					    nvmet_pr_confirm_ns_pc_ref);
> +		wait_for_completion(&pc_ref->confirm_done);
>  	}
>  }
>  
> @@ -618,20 +654,27 @@ static u16 nvmet_pr_preempt(struct nvmet_req *req,
>  static void nvmet_pr_do_abort(struct work_struct *w)
>  {
>  	struct nvmet_req *req = container_of(w, struct nvmet_req, r.abort_work);
> -	struct nvmet_pr_per_ctrl_ref *pc_ref;
> +	struct nvmet_pr_per_ctrl_ref *pc_ref, *tmp;
>  	struct nvmet_ns *ns = req->ns;
> -	unsigned long idx;
> +	bool ctrl_gone;
>  
>  	/*
>  	 * The target does not support abort, just wait per-controller ref to 0.
>  	 */
> -	xa_for_each(&ns->pr_per_ctrl_refs, idx, pc_ref) {
> -		if (percpu_ref_is_dying(&pc_ref->ref)) {
> -			wait_for_completion(&pc_ref->free_done);
> -			reinit_completion(&pc_ref->confirm_done);
> -			reinit_completion(&pc_ref->free_done);
> -			percpu_ref_resurrect(&pc_ref->ref);
> -		}
> +	list_for_each_entry_safe(pc_ref, tmp, &ns->pr.abort_list, abort_entry) {
> +		wait_for_completion(&pc_ref->free_done);
> +		reinit_completion(&pc_ref->confirm_done);
> +		reinit_completion(&pc_ref->free_done);
> +		percpu_ref_resurrect(&pc_ref->ref);
> +
> +		/* Off the list, the pc_ref belongs to its controller again. */
> +		xa_lock(&ns->pr_per_ctrl_refs);
> +		list_del_init(&pc_ref->abort_entry);
> +		ctrl_gone = pc_ref->ctrl_gone;
> +		xa_unlock(&ns->pr_per_ctrl_refs);
> +
> +		if (ctrl_gone)
> +			nvmet_pr_free_pc_ref(pc_ref);
>  	}
>  
>  	up(&ns->pr.pr_sem);
> @@ -1052,6 +1095,8 @@ static int nvmet_pr_alloc_and_insert_pc_ref(struct nvmet_ns *ns,
>  	init_completion(&pc_ref->free_done);
>  	init_completion(&pc_ref->confirm_done);
>  	uuid_copy(&pc_ref->hostid, hostid);
> +	INIT_LIST_HEAD(&pc_ref->abort_entry);
> +	pc_ref->ctrl_gone = false;
>  
>  	ret = xa_insert(&ns->pr_per_ctrl_refs, idx, pc_ref, GFP_KERNEL);
>  	if (ret)
> @@ -1067,7 +1112,6 @@ static int nvmet_pr_alloc_and_insert_pc_ref(struct nvmet_ns *ns,
>  int nvmet_ctrl_init_pr(struct nvmet_ctrl *ctrl)
>  {
>  	struct nvmet_subsys *subsys = ctrl->subsys;
> -	struct nvmet_pr_per_ctrl_ref *pc_ref;
>  	struct nvmet_ns *ns = NULL;
>  	unsigned long idx;
>  	int ret;
> @@ -1095,19 +1139,14 @@ int nvmet_ctrl_init_pr(struct nvmet_ctrl *ctrl)
>  
>  free_per_ctrl_refs:
>  	nvmet_for_each_enabled_ns(&subsys->namespaces, idx, ns) {
> -		if (ns->pr.enable) {
> -			pc_ref = xa_erase(&ns->pr_per_ctrl_refs, ctrl->cntlid);
> -			if (pc_ref)
> -				percpu_ref_exit(&pc_ref->ref);
> -			kfree(pc_ref);
> -		}
> +		if (ns->pr.enable)
> +			nvmet_pr_erase_pc_ref(ns, ctrl->cntlid);
>  	}
>  	return ret;
>  }
>  
>  void nvmet_ctrl_destroy_pr(struct nvmet_ctrl *ctrl)
>  {
> -	struct nvmet_pr_per_ctrl_ref *pc_ref;
>  	struct nvmet_ns *ns;
>  	unsigned long idx;
>  
> @@ -1115,12 +1154,8 @@ void nvmet_ctrl_destroy_pr(struct nvmet_ctrl *ctrl)
>  	mutex_destroy(&ctrl->pr_log_mgr.lock);
>  
>  	nvmet_for_each_enabled_ns(&ctrl->subsys->namespaces, idx, ns) {
> -		if (ns->pr.enable) {
> -			pc_ref = xa_erase(&ns->pr_per_ctrl_refs, ctrl->cntlid);
> -			if (pc_ref)
> -				percpu_ref_exit(&pc_ref->ref);
> -			kfree(pc_ref);
> -		}
> +		if (ns->pr.enable)
> +			nvmet_pr_erase_pc_ref(ns, ctrl->cntlid);
>  	}
>  }
>  
> @@ -1136,6 +1171,7 @@ int nvmet_pr_init_ns(struct nvmet_ns *ns)
>  	atomic_set(&ns->pr.generation, 0);
>  	sema_init(&ns->pr.pr_sem, 1);
>  	INIT_LIST_HEAD(&ns->pr.registrant_list);
> +	INIT_LIST_HEAD(&ns->pr.abort_list);
>  	ns->pr.notify_mask = 0;
>  
>  	xa_init(&ns->pr_per_ctrl_refs);
> -- 
> 2.52.0
---end quoted text---


  reply	other threads:[~2026-11-03  6:04 UTC|newest]

Thread overview: 36+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-29 15:52 [PATCH 00/19] nvme patch pile Keith Busch
2026-10-29 15:52 ` [PATCH 01/19] nvme-loop: delete the controller if a reset fails Keith Busch
2026-11-03  5:50   ` Christoph Hellwig
2026-10-29 15:52 ` [PATCH 02/19] nvmet-tcp: ask for the TLS record type on every receive Keith Busch
2026-10-29 15:52 ` [PATCH 03/19] nvme-fabrics: reconnect if the association is gone Keith Busch
2026-11-03  5:58   ` Christoph Hellwig
2026-10-29 15:52 ` [PATCH 04/19] nvmet-auth: keep the old keys if the new ones fail Keith Busch
2026-11-03  5:59   ` Christoph Hellwig
2026-10-29 15:52 ` [PATCH 05/19] nvmet: accept I/O queues on the controller's port only Keith Busch
2026-11-03  6:00   ` Christoph Hellwig
2026-10-29 15:52 ` [PATCH 06/19] nvmet: check the host NQN against the TLS PSK identity Keith Busch
2026-11-03  6:01   ` Christoph Hellwig
2026-10-29 15:52 ` [PATCH 07/19] nvmet-tcp: let the accept work come to an end Keith Busch
2026-11-03  6:02   ` Christoph Hellwig
2026-10-29 15:52 ` [PATCH 08/19] nvme-tcp: start error recovery when a send fails Keith Busch
2026-10-29 16:17   ` Maurizio Lombardi
2026-10-29 15:52 ` [PATCH 09/19] nvmet: fix pc_ref use-after-free in preempt and abort Keith Busch
2026-11-03  6:04   ` Christoph Hellwig [this message]
2026-10-29 15:52 ` [PATCH 10/19] nvmet: move preempt and abort work off nvmet-wq Keith Busch
2026-10-29 15:52 ` [PATCH 11/19] nvmet-rdma: treat a failed connect as a disconnect Keith Busch
2026-11-03  6:06   ` Christoph Hellwig
2026-10-29 15:52 ` [PATCH 12/19] nvmet-rdma: disconnect only established queues Keith Busch
2026-11-03  6:06   ` Christoph Hellwig
2026-10-29 15:52 ` [PATCH 13/19] nvmet-rdma: take a device off the list last Keith Busch
2026-11-03  6:09   ` Christoph Hellwig
2026-10-29 15:52 ` [PATCH 14/19] nvmet-rdma: queue the release work under the mutex Keith Busch
2026-11-03  6:10   ` Christoph Hellwig
2026-10-29 15:52 ` [PATCH 15/19] nvmet-rdma: disconnect a queue on device removal Keith Busch
2026-11-03  6:12   ` Christoph Hellwig
2026-10-29 15:52 ` [PATCH 16/19] nvmet-rdma: reject queues larger than max_cqe Keith Busch
2026-11-03  6:14   ` Christoph Hellwig
2026-10-29 15:52 ` [PATCH 17/19] nvmet-rdma: don't run the wait list recursively Keith Busch
2026-10-29 15:52 ` [PATCH 18/19] nvmet-rdma: fix a lost run of the wait list Keith Busch
2026-10-29 15:52 ` [PATCH 19/19] nvmet: wait only for executing I/O in preempt and abort Keith Busch
2026-11-03  6:15   ` Christoph Hellwig
2026-11-04 20:37 ` (subset) [PATCH 00/19] nvme patch pile Keith Busch

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261103060414.GE1862@lst.de \
    --to=hch@lst.de \
    --cc=axboe@kernel.dk \
    --cc=kanie@linux.alibaba.com \
    --cc=kbusch@kernel.org \
    --cc=kbusch@meta.com \
    --cc=kch@nvidia.com \
    --cc=linux-nvme@lists.infradead.org \
    --cc=mason@kernel.org \
    --cc=sagi@grimberg.me \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.