From: sashiko-bot@kernel.org
To: "Mohamed Khalfella" <mkhalfella@purestorage.com>
Cc: linux-nvme@lists.infradead.org
Subject: Re: [PATCH v7 15/17] nvme-fc: Hold inflight requests while in FENCING state
Date: Sat, 14 Nov 2026 04:06:48 +0000 [thread overview]
Message-ID: <sashiko-outbox-214135@kernel.org> (raw)
In-Reply-To: <20261114030454.2836543-16-mkhalfella@purestorage.com>
Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [High] nvme-fc: use-after-free on ctrl in nvme_fc_fcpio_done() after
completing rq
- [High] nvme-fc: missing drain of completing ops in
nvme_fc_fcpio_done()
--
commit 37b80917265ea84d22dcb27e9731ac42994f67a4
Author: Mohamed Khalfella <mkhalfella@purestorage.com>
nvme-fc: Hold inflight requests while in FENCING state
This commit splits nvme_fc_delete_association() into teardown and request
flushing helpers, tracks held requests with FCPOP_STATE_HELD, and delays
completing in-flight requests until controller fencing finishes.
> diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c
> index 095adc9011ccd..5e8977ad51cf8 100644
> --- a/drivers/nvme/host/fc.c
> +++ b/drivers/nvme/host/fc.c
[ ... ]
> @@ -2113,22 +2125,58 @@ nvme_fc_fcpio_done(struct nvmefc_fcp_req *req)
> terminate_assoc = false;
>
> done:
> + /*
> + * Start recovery before completing or holding anything. A held
> + * request is only completed by nvme_fc_flush_held_requests(), which
> + * fencing_work runs once the fence is over, so a request may only be
> + * held once FENCING has been claimed and fencing_work is guaranteed
> + * to run. If the claim fails because a reset or a delete already owns
> + * the controller, the request is completed below and that owner
> + * tears the association down.
> + */
> + if (terminate_assoc) {
> + nvme_fc_start_ioerr_recovery(ctrl, "io error");
> + hold = nvme_ctrl_state(&ctrl->ctrl) == NVME_CTRL_FENCING;
> + }
> +
> if (op->flags & FCOP_FLAGS_AEN) {
> nvme_complete_async_event(&queue->ctrl->ctrl, status, &result);
> - __nvme_fc_fcpop_chk_teardowns(ctrl, op, opstate);
> + if (__nvme_fc_fcpop_chk_teardowns(ctrl, op, opstate))
> + __nvme_fc_fcpop_count_one_down(ctrl);
> atomic_set(&op->state, FCPOP_STATE_IDLE);
> op->flags = FCOP_FLAGS_AEN; /* clear other flags */
> nvme_fc_ctrl_put(ctrl);
> - goto check_error;
> + return;
> }
>
> - __nvme_fc_fcpop_chk_teardowns(ctrl, op, opstate);
> + /*
> + * We can not access op after the request is completed because it can
> + * be reused immediately. At the same time we want to wakeup the thread
> + * waiting for ongoing IOs _after_ requests are completed. This is
> + * necessary because that thread will start canceling inflight IOs
> + * and we want to avoid request completion racing with cancellation.
> + */
> + op_term = __nvme_fc_fcpop_chk_teardowns(ctrl, op, opstate);
> +
> + /*
> + * If the controller is FENCING, do not complete this request now.
> + * Move the op to HELD and let nvme_fc_cancel_held_requests() cancel
> + * it when it is safe to do so. HELD is only reachable from COMPLETE
> + * through this cmpxchg: if the cancellation has already walked the
> + * tagset it moved the op out of COMPLETE, the cmpxchg fails and the
> + * request is completed here instead. Either way exactly one path
> + * completes it.
> + */
> + if (hold && atomic_cmpxchg(&op->state, FCPOP_STATE_COMPLETE,
> + FCPOP_STATE_HELD) == FCPOP_STATE_COMPLETE)
> + goto check_op_term;
> +
> if (!nvme_try_complete_req(rq, status, result))
> nvme_fc_complete_rq(rq);
>
> -check_error:
> - if (terminate_assoc)
> - nvme_fc_start_ioerr_recovery(ctrl, "io error");
> +check_op_term:
> + if (op_term)
> + __nvme_fc_fcpop_count_one_down(ctrl);
> }
[Severity: High]
Can __nvme_fc_fcpop_count_one_down(ctrl) cause a use-after-free on ctrl
when a non-held aborted request completes?
nvme_fc_complete_rq(rq) drops the FCP op's reference via
nvme_fc_ctrl_put(ctrl) before check_op_term runs. When
__nvme_fc_fcpop_count_one_down() then decrements ctrl->iocnt to 0, a
concurrent controller deletion can finish teardown and free ctrl before
wake_up(&ctrl->ioabort_wait) runs:
CPU0 CPU1
nvme_fc_fcpio_done() nvme_do_delete_ctrl()
nvme_fc_complete_rq(rq) nvme_fc_delete_ctrl()
nvme_fc_ctrl_put(ctrl) ...
__nvme_fc_fcpop_count_one_down() wait_event(..., iocnt == 0)
atomic_dec_return(&ctrl->iocnt)
...
nvme_uninit_ctrl()
nvme_put_ctrl()
...
kfree(ctrl)
wake_up(&ctrl->ioabort_wait) <-- use-after-free
Acquiring an extra reference on ctrl around
__nvme_fc_fcpop_count_one_down(ctrl), or dropping the op's ctrl reference
only after check_op_term when the request is completed, would keep ctrl
alive across the wakeup.
[ ... ]
> @@ -3323,13 +3372,71 @@ nvme_fc_delete_association(struct nvme_fc_ctrl *ctrl)
> __nvme_fc_delete_hw_queue(ctrl, &ctrl->queues[0], 0);
> nvme_fc_free_queue(&ctrl->queues[0]);
>
> + nvme_fc_ctlr_inactive_on_rport(ctrl);
> +}
> +
> +static bool
> +nvme_fc_cancel_held_request(struct request *req, void *data)
> +{
> + struct nvme_fc_fcp_op *op = blk_mq_rq_to_pdu(req);
> + struct nvme_ctrl *ctrl = data;
> +
> + /*
> + * A single cmpxchg decides who completes this request. If the op is
> + * HELD, nvme_fc_fcpio_done() is done with it and we cancel it. If it
> + * is still COMPLETE, nvme_fc_fcpio_done() has not decided yet: move
> + * it to IDLE so its own cmpxchg to HELD fails and it completes the
> + * request itself (nvme_fc_complete_rq() sets IDLE anyway). Doing this
> + * as read-then-cmpxchg would let the op become HELD in between and
> + * leave it with no one to complete it.
> + */
> + if (atomic_cmpxchg(&op->state, FCPOP_STATE_COMPLETE,
> + FCPOP_STATE_IDLE) == FCPOP_STATE_HELD)
> + nvme_cancel_request(req, ctrl);
> +
> + return true;
> +}
> +
> +static void
> +nvme_fc_cancel_held_requests(struct nvme_ctrl *ctrl, struct blk_mq_tag_set *set)
> +{
> + if (!set)
> + return;
> +
> + blk_mq_tagset_busy_iter(set, nvme_fc_cancel_held_request, ctrl);
> + blk_mq_tagset_wait_completed_request(set);
> +}
[Severity: High]
Does nvme_fc_cancel_held_requests() return without waiting when it wins
the cmpxchg against an in-progress nvme_fc_fcpio_done()?
When nvme_fc_fcpio_done() exchanges op->state to FCPOP_STATE_COMPLETE
before __nvme_fc_abort_op() runs, ctrl->iocnt is not incremented for that
op. If nvme_fc_cancel_held_request() then runs before nvme_fc_fcpio_done()
reaches the hold cmpxchg, it moves op->state from FCPOP_STATE_COMPLETE to
FCPOP_STATE_IDLE without calling nvme_cancel_request(). Because
blk_mq_tagset_wait_completed_request() only waits for requests in
MQ_RQ_COMPLETE state, and rq is still MQ_RQ_IN_FLIGHT until
nvme_fc_fcpio_done() calls nvme_try_complete_req(), the flush and fence
can finish while nvme_fc_fcpio_done() is still executing:
CPU0 CPU1
nvme_fc_fcpio_done()
atomic_xchg(&op->state, COMPLETE)
dev_info(...)
nvme_fc_fencing_work()
__nvme_fc_teardown_association()
nvme_fence_ctrl()
nvme_fc_flush_held_requests()
nvme_fc_cancel_held_request()
cmpxchg(COMPLETE -> IDLE)
blk_mq_tagset_wait_completed_request()
...
/* reconnects to NVME_CTRL_LIVE */
nvme_fc_start_ioerr_recovery()
/* enters NVME_CTRL_FENCING */
cmpxchg(COMPLETE -> HELD) /* fails */
nvme_try_complete_req(rq, ...) <-- completes rq during FENCING
A late nvme_fc_fcpio_done() from the old association can then trigger
nvme_fc_start_ioerr_recovery() on the new association and complete rq
immediately while in NVME_CTRL_FENCING because op->state is already
FCPOP_STATE_IDLE.
In addition, for AEN ops in nvme_fc_fcpio_done(), calling
__nvme_fc_fcpop_count_one_down() before resetting op->state and op->flags
lets __nvme_fc_teardown_association() proceed to nvme_fc_term_aen_ops() ->
__nvme_fc_exit_request() (which unmaps DMA and sets FCPOP_STATE_UNINIT)
while nvme_fc_fcpio_done() is still accessing the AEN op and subsequently
overwrites op->state back to FCPOP_STATE_IDLE.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261114030454.2836543-1-mkhalfella@purestorage.com?part=15
next prev parent reply other threads:[~2026-11-14 4:06 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-14 3:04 [PATCH v7 00/17] TP8028 Rapid Path Failure Recovery Mohamed Khalfella
2026-11-14 3:04 ` [PATCH v7 01/17] nvmet: Rapid Path Failure Recovery set controller identify fields Mohamed Khalfella
2026-11-14 3:17 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 02/17] nvmet/debugfs: Export controller CIU and CIRN via debugfs Mohamed Khalfella
2026-11-14 3:12 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 03/17] nvmet: Implement CCR nvme command Mohamed Khalfella
2026-11-14 3:30 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 04/17] nvmet: Implement CCR logpage Mohamed Khalfella
2026-11-14 3:20 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 05/17] nvmet: Send an AEN on CCR completion Mohamed Khalfella
2026-11-14 3:04 ` [PATCH v7 06/17] nvme: Rapid Path Failure Recovery read controller identify fields Mohamed Khalfella
2026-11-14 3:04 ` [PATCH v7 07/17] nvme: Introduce FENCING and FENCED controller states Mohamed Khalfella
2026-11-14 3:29 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 08/17] nvme: Implement cross-controller reset recovery Mohamed Khalfella
2026-11-14 3:26 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 09/17] nvme: Implement cross-controller reset completion Mohamed Khalfella
2026-11-14 3:29 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 10/17] nvme-tcp: Use CCR to recover controller that hits an error Mohamed Khalfella
2026-11-14 3:38 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 11/17] nvme-rdma: " Mohamed Khalfella
2026-11-14 3:04 ` [PATCH v7 12/17] nvme-fc: start error recovery instead of aborting timed out IOs Mohamed Khalfella
2026-11-14 3:46 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 13/17] nvme-fc: perform error recovery directly from ioerr_work Mohamed Khalfella
2026-11-14 3:31 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 14/17] nvme-fc: Use CCR to recover controller that hits an error Mohamed Khalfella
2026-11-14 3:38 ` sashiko-bot
2026-11-14 3:04 ` [PATCH v7 15/17] nvme-fc: Hold inflight requests while in FENCING state Mohamed Khalfella
2026-11-14 4:06 ` sashiko-bot [this message]
2026-11-14 3:04 ` [PATCH v7 16/17] nvmet: Add support for CQT to nvme target Mohamed Khalfella
2026-11-14 3:04 ` [PATCH v7 17/17] nvme: Add support for CQT to nvme host Mohamed Khalfella
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=sashiko-outbox-214135@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-nvme@lists.infradead.org \
--cc=mkhalfella@purestorage.com \
--cc=sashiko-reviews@lists.linux.dev \
/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.