]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/commitdiff
lock_parent() needs to recheck if dentry got __dentry_kill'ed under it
authorAl Viro <viro@zeniv.linux.org.uk>
Sat, 24 Feb 2018 01:47:17 +0000 (20:47 -0500)
committerAl Viro <viro@zeniv.linux.org.uk>
Sat, 24 Feb 2018 01:47:17 +0000 (20:47 -0500)
In case when dentry passed to lock_parent() is protected from freeing only
by the fact that it's on a shrink list and trylock of parent fails, we
could get hit by __dentry_kill() (and subsequent dentry_kill(parent))
between unlocking dentry and locking presumed parent.  We need to recheck
that dentry is alive once we lock both it and parent *and* postpone
rcu_read_unlock() until after that point.  Otherwise we could return
a pointer to struct dentry that already is rcu-scheduled for freeing, with
->d_lock held on it; caller's subsequent attempt to unlock it can end
up with memory corruption.

Cc: stable@vger.kernel.org # 3.12+, counting backports
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/dcache.c

index 7c38f39958bc371d0324197968a732d68da927d8..32aaab21e648a2024568a468c49c17abf472371b 100644 (file)
@@ -647,11 +647,16 @@ again:
                spin_unlock(&parent->d_lock);
                goto again;
        }
-       rcu_read_unlock();
-       if (parent != dentry)
+       if (parent != dentry) {
                spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
-       else
+               if (unlikely(dentry->d_lockref.count < 0)) {
+                       spin_unlock(&parent->d_lock);
+                       parent = NULL;
+               }
+       } else {
                parent = NULL;
+       }
+       rcu_read_unlock();
        return parent;
 }