]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
client: fix Dentry UAF by holding DentryRef across unlink 69021/head
authorIgor Golikov <igolikov@ibm.com>
Wed, 27 May 2026 13:19:25 +0000 (16:19 +0300)
committerVenky Shankar <vshankar@redhat.com>
Wed, 1 Jul 2026 05:34:38 +0000 (11:04 +0530)
Hold a function-scoped DentryRef in Client::unlink() to keep the dentry
alive through the entire teardown sequence.

Without the O_DIRECTORY dentry pin (PR #60909, merged after v19.2.1),
opening a directory does not call _ll_get(), so the dentry stays at
ref=1 while inode->dir is still active. When trim_cache() evicts this
dentry, Dentry::unlink() calls put() for the dir pin, ref drops to 0,
and the dentry is freed while Client::unlink() still needs it for
detach/lru_remove.

A function-scoped DentryRef guarantees the dentry survives until after
it has been properly removed from the dir and LRU, regardless of pin
state.

Signed-off-by: Igor Golikov <igolikov@ibm.com>
Fixes: https://tracker.ceph.com/issues/74625
src/client/Client.cc

index 5d1464b3a3bc02f8eeb41146eaefcdd5bca383b9..af7449e72200f6822475ddc4b60bdded0372efc3 100644 (file)
@@ -3791,6 +3791,15 @@ Dentry* Client::link(Dir *dir, const string& name, Inode *in, Dentry *dn)
 void Client::unlink(Dentry *dn, bool keepdir, bool keepdentry)
 {
   InodeRef in(dn->inode);
+  // Keep the dentry alive for the duration of this function.
+  // Without the O_DIRECTORY dentry pin (PR #60909), a directory dentry can
+  // remain at ref=1 while its inode->dir is still active. When trim_cache()
+  // evicts it, Dentry::unlink() calls put() for the dir pin, dropping ref
+  // to 0 and freeing the dentry while we still need it for detach/lru_remove.
+  // The DentryRef guard prevents use-after-free regardless of pin state.
+  // See: https://tracker.ceph.com/issues/74625
+  DentryRef dnref(dn);
+
   ldout(cct, 15) << "unlink dir " << dn->dir->parent_inode << " '" << dn->name << "' dn " << dn
                 << " inode " << dn->inode << dendl;
 
@@ -3800,7 +3809,7 @@ void Client::unlink(Dentry *dn, bool keepdir, bool keepdentry)
     dec_dentry_nr();
     ldout(cct, 20) << "unlink  inode " << in << " parents now " << in->dentries << dendl;
   }
-  ceph_assert(dn->ref > 0);
+  ceph_assert(dn->ref > 1);
 
   if (keepdentry) {
     dn->lease_mds = -1;