All of lore.kernel.org
 help / color / mirror / Atom feed
From: Daniel Wagner <dwagner@suse.de>
To: Keith Busch <kbusch@kernel.org>, Christoph Hellwig <hch@lst.de>,
	 Sagi Grimberg <sagi@grimberg.me>
Cc: Chris Leech <cleech@redhat.com>,
	linux-nvme@lists.infradead.org,  linux-kernel@vger.kernel.org,
	Dmitry Bogdanov <d.bogdanov@yadro.com>,
	 Daniel Wagner <wagi@kernel.org>
Subject: [PATCH] nvme-tcp: fix usage of page_frag_cache
Date: Thu, 16 Jul 2026 16:42:19 +0200	[thread overview]
Message-ID: <20260716-nvme-tcp-page_fraq_cache-v1-1-ea660828cf93@kernel.org> (raw)

From: Dmitry Bogdanov <d.bogdanov@yadro.com>

nvme uses page_frag_cache to preallocate PDU for each preallocated request
of block device. Block devices are created in parallel threads,
consequently page_frag_cache is used in not thread-safe manner.
That leads to incorrect refcounting of backstore pages and premature free.

That can be catched by !sendpage_ok inside network stack:

WARNING: CPU: 7 PID: 467 at ../net/core/skbuff.c:6931 skb_splice_from_iter+0xfa/0x310.
	tcp_sendmsg_locked+0x782/0xce0
	tcp_sendmsg+0x27/0x40
	sock_sendmsg+0x8b/0xa0
	nvme_tcp_try_send_cmd_pdu+0x149/0x2a0
Then random panic may occur.

Fix that by serializing the usage of page_frag_cache.

Fixes: 4e893ca81170 ("nvme_core: scan namespaces asynchronously")
Signed-off-by: Dmitry Bogdanov <d.bogdanov@yadro.com>
Signed-off-by: Daniel Wagner <wagi@kernel.org>
---
If the target exposes many namespaces (>1000) and the host has many CPUs (>80),
it is trivial to trigger the allocation race condition in nvme_tcp_init_request
which results in the logs below:

WARNING: CPU: XX PID: XXXX at net/core/skbuff.c:XXXX skb_splice_from_iter+0xfa/0x310
nvme nvme22: failed to send request -5
nvme nvme23: failed to send request -5
nvme nvme24: failed to send request -5
[... repeating for all controllers, thousands of times ...]

RDX: 00000000000005e8 RSI: 0000000000000010 RDI: 0000000000000000
RBP: 000000000000004c R08: ff57ae906085bd78 R09: 000000000000004c
R10: 000000000000004c R11: 00000000000003ef R12: 0000000000000000
R13: ff4f98b626e30c00 R14: ff57ae906085bbf0 R15: ff4f98b626e30c00
FS:  0000000000000000(0000) GS:ff4f98e2abc00000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f0f167ff000 CR3: 0000000382a18003 CR4: 0000000000f71ee0
Call Trace:
 <TASK>
 ? __warn+0x86/0x150
 ? skb_splice_from_iter+0xfa/0x310
 ? report_bug+0xfb/0x1e0
 ? handle_bug+0x44/0x80
 ? exc_invalid_op+0x13/0x60
 ? asm_exc_invalid_op+0x16/0x20
 ? skb_splice_from_iter+0xfa/0x310
 ? __alloc_skb+0xd5/0x190
 tcp_sendmsg_locked+0x782/0xcd0
 tcp_sendmsg+0x27/0x40
 sock_sendmsg+0x98/0xc0
 nvme_tcp_try_send_cmd_pdu+0x149/0x2a0 [nvme_tcp]
 nvme_tcp_try_send+0xbb/0x2c0 [nvme_tcp]
 nvme_tcp_io_work+0x37/0xb0 [nvme_tcp]
 process_one_work+0x223/0x460
 ? __pfx_worker_thread+0x10/0x10
 worker_thread+0x2a/0x3b0
 ? __pfx_worker_thread+0x10/0x10
 kthread+0xdf/0x120
 ? __pfx_kthread+0x10/0x10
 ret_from_fork+0x29/0x50
 </TASK>

The above excerpt is from our customers log. I was able to reproduce this on the
latest nvme-7.3 branch. Dmitry's patch [1] looks correct to me. All the
allocation happens in the slow path and the context can sleep, thus a mutex
seems to be a good choice.

[1] https://lore.kernel.org/linux-nvme/20250929111951.6961-1-d.bogdanov@yadro.com/
---
 drivers/nvme/host/tcp.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c
index ba5c7b3e2a7c..dd40798bc248 100644
--- a/drivers/nvme/host/tcp.c
+++ b/drivers/nvme/host/tcp.c
@@ -108,6 +108,7 @@ struct nvme_tcp_queue {
 
 	struct mutex		queue_lock;
 	struct mutex		send_mutex;
+	struct mutex		pf_cache_lock;
 	struct llist_head	req_list;
 	struct list_head	send_list;
 
@@ -550,9 +551,11 @@ static int nvme_tcp_init_request(struct blk_mq_tag_set *set,
 	struct nvme_tcp_queue *queue = &ctrl->queues[queue_idx];
 	u8 hdgst = nvme_tcp_hdgst_len(queue);
 
+	mutex_lock(&queue->pf_cache_lock);
 	req->pdu = page_frag_alloc(&queue->pf_cache,
 		sizeof(struct nvme_tcp_cmd_pdu) + hdgst,
 		GFP_KERNEL | __GFP_ZERO);
+	mutex_unlock(&queue->pf_cache_lock);
 	if (!req->pdu)
 		return -ENOMEM;
 
@@ -1417,9 +1420,11 @@ static int nvme_tcp_alloc_async_req(struct nvme_tcp_ctrl *ctrl)
 	struct nvme_tcp_request *async = &ctrl->async_req;
 	u8 hdgst = nvme_tcp_hdgst_len(queue);
 
+	mutex_lock(&queue->pf_cache_lock);
 	async->pdu = page_frag_alloc(&queue->pf_cache,
 		sizeof(struct nvme_tcp_cmd_pdu) + hdgst,
 		GFP_KERNEL | __GFP_ZERO);
+	mutex_unlock(&queue->pf_cache_lock);
 	if (!async->pdu)
 		return -ENOMEM;
 
@@ -1461,6 +1466,7 @@ static void nvme_tcp_free_queue(struct nvme_ctrl *nctrl, int qid)
 	kfree(queue->pdu);
 	mutex_destroy(&queue->send_mutex);
 	mutex_destroy(&queue->queue_lock);
+	mutex_destroy(&queue->pf_cache_lock);
 
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 	lockdep_unregister_key(&queue->nvme_tcp_sk_key);
@@ -1788,6 +1794,7 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid,
 	INIT_LIST_HEAD(&queue->send_list);
 	mutex_init(&queue->send_mutex);
 	INIT_WORK(&queue->io_work, nvme_tcp_io_work);
+	mutex_init(&queue->pf_cache_lock);
 
 	if (qid > 0)
 		queue->cmnd_capsule_len = nctrl->ioccsz * 16;
@@ -1928,6 +1935,7 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid,
 err_destroy_mutex:
 	mutex_destroy(&queue->send_mutex);
 	mutex_destroy(&queue->queue_lock);
+	mutex_destroy(&queue->pf_cache_lock);
 	return ret;
 }
 

---
base-commit: 29261f8bb41662f2a660c479e5cf592942b53f78
change-id: 20260716-nvme-tcp-page_fraq_cache-d04f6226f927

Best regards,
--  
Daniel Wagner <wagi@kernel.org>



             reply	other threads:[~2026-07-16 14:42 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-16 14:42 Daniel Wagner [this message]
2026-08-06 12:46 ` [PATCH] nvme-tcp: fix usage of page_frag_cache Daniel Wagner
2026-08-10 23:40 ` Keith Busch
  -- strict thread matches above, loose matches on Subject: below --
2025-09-29 11:19 Dmitry Bogdanov
2025-10-01  6:31 ` Chris Leech
2025-10-01 16:41   ` Dmitry Bogdanov

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=20260716-nvme-tcp-page_fraq_cache-v1-1-ea660828cf93@kernel.org \
    --to=dwagner@suse.de \
    --cc=cleech@redhat.com \
    --cc=d.bogdanov@yadro.com \
    --cc=hch@lst.de \
    --cc=kbusch@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-nvme@lists.infradead.org \
    --cc=sagi@grimberg.me \
    --cc=wagi@kernel.org \
    /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.