From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 28CED22D7B9; Thu, 2 Jul 2026 16:50:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783011006; cv=none; b=Kz2s7Da9RzH6aQULmhWOBGM+xk7tRzH6UqvzWrDdNcbmvwqUfdpQcJH5YoGdaBuj7Y5Nc/C2OX42xXJf1D0gBAym6Kw9snzZ/OquhHvmVrawGMRGzCvBX2W6CzrGO+HUTkJeY6F7IDeu+GsLVu5og+bsNLhgzSJEXuBAUwinhXI= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783011006; c=relaxed/simple; bh=kR3MrdM0PGArVJDBvqlobJhU9Abj05wP3lZ9dRd/Veg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SlSGxBhHxBJqzQSyNxRy6el0dx5ijGXPBEZZYSq4W06nsjouNXIiEEsufxRid6PnWi3gj/9JUTUCkQokybMPsvyt4+5Rr1mGbRo6KW4rsrSFQnRisQnJjp3r1gsvx5QO15/8ntCcX8s2aBJ0kz/HqdiTWkQyRriglNIFrTgUI88= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=mQm5chJb; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="mQm5chJb" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 94B581F000E9; Thu, 2 Jul 2026 16:50:04 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linuxfoundation.org; s=korg; t=1783011005; bh=BGdHJ2SQtg+2sDzEjQNf2xmcHnrqDQcMMVmY/9FDNms=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=mQm5chJbTrHvK58vi27Yg8J4jRcNuuIwBFgAH7c0EKLdll/SFywKfq1RBaggk+Pc+ ZgX3ylKazICwyBYpZQALeqX26WvKi7dv7UXi2dpfLF/HE0yzJDMsGJxLQB2J/4xR4h sjJDQ1xc4MYgsfet2C4U+JqODh8Gq0c5dCYV2AXc= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Shivam Kumar , Shivam Kumar , Chaitanya Kulkarni , Keith Busch , Philo Lu , Sasha Levin Subject: [PATCH 6.6 120/175] nvmet-tcp: fix race between ICReq handling and queue teardown Date: Thu, 2 Jul 2026 18:20:21 +0200 Message-ID: <20260702155118.281651385@linuxfoundation.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260702155115.766838875@linuxfoundation.org> References: <20260702155115.766838875@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: patches@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 6.6-stable review patch. If anyone has any objections, please let me know. ------------------ From: Chaitanya Kulkarni commit 5293a8882c549fab4a878bc76b0b6c951f980a61 upstream. nvmet_tcp_handle_icreq() updates queue->state after sending an Initialization Connection Response (ICResp), but it does so without serializing against target-side queue teardown. If an NVMe/TCP host sends an Initialization Connection Request (ICReq) and immediately closes the connection, target-side teardown may start in softirq context before io_work drains the already buffered ICReq. In that case, nvmet_tcp_schedule_release_queue() sets queue->state to NVMET_TCP_Q_DISCONNECTING and drops the queue reference under state_lock. If io_work later processes that ICReq, nvmet_tcp_handle_icreq() can still overwrite the state back to NVMET_TCP_Q_LIVE. That defeats the DISCONNECTING-state guard in nvmet_tcp_schedule_release_queue() and allows a later socket state change to re-enter teardown and issue a second kref_put() on an already released queue. The ICResp send failure path has the same problem. If teardown has already moved the queue to DISCONNECTING, a send error can still overwrite the state with NVMET_TCP_Q_FAILED, again reopening the window for a second teardown path to drop the queue reference. Fix this by serializing both post-send state transitions with state_lock and bailing out if teardown has already started. Use -ESHUTDOWN as an internal sentinel for that bail-out path rather than propagating it as a transport error like -ECONNRESET. Keep nvmet_tcp_socket_error() setting rcv_state to NVMET_TCP_RECV_ERR before honoring that sentinel so receive-side parsing stays quiesced until the existing release path completes. Fixes: c46a6465bac2 ("nvmet-tcp: add NVMe over TCP target driver") Cc: stable@vger.kernel.org Reported-by: Shivam Kumar Tested-by: Shivam Kumar Signed-off-by: Chaitanya Kulkarni Signed-off-by: Keith Busch [ context diff adaptation: drop `queue->state = NVMET_TCP_Q_FAILED` since the enum introduced in 6.7, 675b453e0241 ("nvmet-tcp: enable TLS handshake upcall" ] Signed-off-by: Philo Lu Signed-off-by: Sasha Levin --- drivers/nvme/target/tcp.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index 5f85c4a812abcd..4174fef03eac7f 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -380,6 +380,19 @@ static int nvmet_tcp_build_pdu_iovec(struct nvmet_tcp_cmd *cmd) static void nvmet_tcp_fatal_error(struct nvmet_tcp_queue *queue) { + /* + * Keep rcv_state at RECV_ERR even for the internal -ESHUTDOWN path. + * nvmet_tcp_handle_icreq() can return -ESHUTDOWN after the ICReq has + * already been consumed and queue teardown has started. + * + * If nvmet_tcp_data_ready() or nvmet_tcp_write_space() queues + * nvmet_tcp_io_work() again before nvmet_tcp_release_queue_work() + * cancels it, the queue must not keep that old receive state. + * Otherwise the next nvmet_tcp_io_work() run can reach + * nvmet_tcp_done_recv_pdu() and try to handle the same ICReq again. + * + * That is why queue->rcv_state needs to be updated before we return. + */ queue->rcv_state = NVMET_TCP_RECV_ERR; if (queue->nvme_sq.ctrl) nvmet_ctrl_fatal_error(queue->nvme_sq.ctrl); @@ -935,10 +948,24 @@ static int nvmet_tcp_handle_icreq(struct nvmet_tcp_queue *queue) iov.iov_base = icresp; iov.iov_len = sizeof(*icresp); ret = kernel_sendmsg(queue->sock, &msg, &iov, 1, iov.iov_len); - if (ret < 0) + if (ret < 0) { + spin_lock_bh(&queue->state_lock); + if (queue->state == NVMET_TCP_Q_DISCONNECTING) { + spin_unlock_bh(&queue->state_lock); + return -ESHUTDOWN; + } + spin_unlock_bh(&queue->state_lock); return ret; /* queue removal will cleanup */ + } + spin_lock_bh(&queue->state_lock); + if (queue->state == NVMET_TCP_Q_DISCONNECTING) { + spin_unlock_bh(&queue->state_lock); + /* Tell nvmet_tcp_socket_error() teardown is in progress. */ + return -ESHUTDOWN; + } queue->state = NVMET_TCP_Q_LIVE; + spin_unlock_bh(&queue->state_lock); nvmet_prepare_receive_pdu(queue); return 0; } -- 2.53.0