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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox