[PATCH] ceph: fix inode and i_filelock_ref leak on file lock rollback

From: zhangdaolong

Date: Thu Nov 05 2026 - 00:55:15 EST


When ceph_lock() or ceph_flock() succeeds in acquiring a file lock on
the MDS via ceph_lock_message() but fails to install the lock locally in
VFS (for example when posix_lock_file() returns -EAGAIN or -EDEADLK, or
locks_lock_file_wait() is interrupted), it sends a CEPH_LOCK_UNLOCK
rollback request to the MDS reusing the same struct file_lock instance.

Because ceph_lock_message() unconditionally invokes
fl->fl_ops->fl_copy_lock(fl, NULL) for every CEPH_MDS_OP_SETFILELOCK
operation, the rollback call increments ci->i_filelock_ref a second time
and overwrites fl->fl_u.ceph.inode with a second igrab(inode) reference.
When the caller subsequently frees or releases the request file_lock,
ceph_fl_release_lock() only runs once, permanently leaking one
i_filelock_ref count and one inode reference per rollback and causing
'VFS: Busy inodes after unmount of ceph' on unmount.

Fix this by:
1. Skipping fl_copy_lock() in ceph_lock_message() when cmd is
CEPH_LOCK_UNLOCK, since unlocking does not add a file_lock to the
inode.
2. Guarding ceph_fl_copy_lock() against being called when
dst->fl_u.ceph.inode is already set, ensuring a single file_lock
never acquires multiple inode references across retries or rollbacks.

Fixes: 89aa59301013 ("ceph: keep auth cap when inode has flocks or posix locks")
Fixes: 8e1858710d9a ("ceph: avoid use-after-free in ceph_fl_release_lock()")
Link: https://tracker.ceph.com/issues/81372
Signed-off-by: zhangdaolong <dlzhangak@xxxxxxxxxxxxxx>
---
fs/ceph/locks.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/fs/ceph/locks.c b/fs/ceph/locks.c
index 677221bd64e0..17b893c234d3 100644
--- a/fs/ceph/locks.c
+++ b/fs/ceph/locks.c
@@ -34,6 +34,10 @@ void __init ceph_flock_init(void)
static void ceph_fl_copy_lock(struct file_lock *dst, struct file_lock *src)
{
struct inode *inode = file_inode(dst->c.flc_file);
+
+ if (dst->fl_u.ceph.inode)
+ return;
+
atomic_inc(&ceph_inode(inode)->i_filelock_ref);
dst->fl_u.ceph.inode = igrab(inode);
}
@@ -81,7 +85,7 @@ static int ceph_lock_message(u8 lock_type, u16 operation, struct inode *inode,
u64 length = 0;
u64 owner;

- if (operation == CEPH_MDS_OP_SETFILELOCK) {
+ if (operation == CEPH_MDS_OP_SETFILELOCK && cmd != CEPH_LOCK_UNLOCK) {
/*
* increasing i_filelock_ref closes race window between
* handling request reply and adding file_lock struct to
--
2.48.1