From: Max Kellermann <max.kellermann@ionos.com>
To: idryomov@gmail.com, amarkuze@redhat.com, xiubo.li@clyso.com,
ceph-devel@vger.kernel.org, linux-kernel@vger.kernel.org
Cc: Max Kellermann <max.kellermann@ionos.com>, stable@vger.kernel.org
Subject: [PATCH] ceph: fix d_lock leak in debugfs mdsc_show()
Date: Wed, 11 Nov 2026 12:30:29 +0100 [thread overview]
Message-ID: <20261111113029.959595-1-max.kellermann@ionos.com> (raw)
mdsc_show() walks the request tree under `mdsc->mutex` and prints the
request's dentries like this:
spin_lock(&req->r_old_dentry->d_lock);
seq_printf(...);
spin_unlock(&req->r_old_dentry->d_lock);
(same for `r_dentry`). The pointer is loaded again for the
unlock, but ceph_fill_trace() can change it in the meantime:
ceph_fill_trace() runs with req->r_fill_mutex held, but not
mdsc->mutex, and:
- after d_move() for a rename, it swaps `r_dentry` and `r_old_dentry`
- splice_dentry() replaces `r_dentry` with the spliced alias and drops
the request's reference to the old dentry
- a lookup without a locked parent assigns a new `r_dentry`
If an unsafe rename reply is being processed while mdsc_show() is
between spin_lock() and spin_unlock(), mdsc_show() unlocks the other
dentry, and the `d_lock` of the one it locked is never released. The
next attempt to lock it spins forever. I have seen this on a
production server, where our monitoring reads the debugfs "mdsc" file
periodically:
watchdog: BUG: soft lockup - CPU#67 stuck for 48s! [kworker/67:2:3746175]
Workqueue: ceph-msgr ceph_con_workfn
RIP: 0010:queued_spin_lock_slowpath+0x4a/0x240
Call Trace:
<TASK>
ceph_fill_trace+0x7a4/0x910
mds_dispatch+0x9f4/0x1ac0
ceph_con_process_message+0x66/0x80
ceph_con_v1_try_read+0x392/0x17e0
ceph_con_workfn+0x329/0x590
process_one_work+0x146/0x2b0
worker_thread+0x182/0x2f0
kthread+0xe4/0x1d0
watchdog: BUG: soft lockup - CPU#55 stuck for 49s! [cm4all-kernel-e:16346]
RIP: 0010:queued_spin_lock_slowpath+0x1a2/0x240
Call Trace:
<TASK>
lockref_get+0x47/0x60
ceph_mdsc_build_path.part.0+0x85/0x2a0
mdsc_show+0x239/0x3e0
seq_read_iter+0x11f/0x480
seq_read+0xfd/0x130
full_proxy_read+0x50/0x80
vfs_read+0xa4/0x340
Both CPUs were waiting for the `d_lock` of the same dentry, which
nobody is locked but nobody will unlock it. The first CPU was in
update_dentry_lease(), called after the rename swap. The second one
was the next read of the debugfs file. Since the ceph-msgr worker was
blocked, all requests to that MDS hung.
Replacing `r_dentry` with a spliced alias also means mdsc_show() can
call dget() on a dentry whose last reference has just been dropped.
Fix this by holding `r_fill_mutex` while mdsc_show() uses `r_dentry`
and `r_old_dentry`. Taking r_fill_mutex with mdsc->mutex held is the
established lock order; __do_request(), handle_forward(),
ceph_mdsc_wait_request() and ceph_lock_wait_for_completion() do the
same.
Fixes: 76aa844d5b2f ("ceph: debugfs")
Cc: stable@vger.kernel.org
Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
---
fs/ceph/debugfs.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/fs/ceph/debugfs.c b/fs/ceph/debugfs.c
index 6a9a5551d9d7..83506d9207fa 100644
--- a/fs/ceph/debugfs.c
+++ b/fs/ceph/debugfs.c
@@ -109,6 +109,14 @@ static int mdsc_show(struct seq_file *s, void *p)
else
seq_puts(s, "\t");
+ /*
+ * ceph_fill_trace() may replace r_dentry (dropping the
+ * request's reference to the old one) or swap r_dentry
+ * and r_old_dentry; it does so while holding
+ * r_fill_mutex, so hold it while using them.
+ */
+ mutex_lock(&req->r_fill_mutex);
+
if (req->r_inode) {
seq_printf(s, " #%llx", ceph_ino(req->r_inode));
} else if (req->r_dentry) {
@@ -151,6 +159,8 @@ static int mdsc_show(struct seq_file *s, void *p)
seq_printf(s, " %s", req->r_path2);
}
+ mutex_unlock(&req->r_fill_mutex);
+
seq_puts(s, "\n");
}
mutex_unlock(&mdsc->mutex);
--
2.53.0
reply other threads:[~2026-11-11 11:30 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
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=20261111113029.959595-1-max.kellermann@ionos.com \
--to=max.kellermann@ionos.com \
--cc=amarkuze@redhat.com \
--cc=ceph-devel@vger.kernel.org \
--cc=idryomov@gmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=stable@vger.kernel.org \
--cc=xiubo.li@clyso.com \
/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
all inboxes | Powered by JetHome®