[rh7,10/14] lock_parent() needs to recheck if dentry got __dentry_kill'ed under it

Submitted by Andrey Ryabinin on June 10, 2019, 3:13 p.m.

Details

Message ID 20190610151400.12820-10-aryabinin@virtuozzo.com
State New
Series "Series without cover letter"
Headers show

Commit Message

Andrey Ryabinin June 10, 2019, 3:13 p.m.
From: Al Viro <viro@zeniv.linux.org.uk>

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>
(cherry picked from commit 3b821409632ab778d46e807516b457dfa72736ed)
Signed-off-by: Andrey Ryabinin <aryabinin@virtuozzo.com>
---
 fs/dcache.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

Patch hide | download patch | download mbox

diff --git a/fs/dcache.c b/fs/dcache.c
index e24229628475..833f5772f616 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -627,11 +627,16 @@  static inline struct dentry *lock_parent(struct dentry *dentry)
 		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;
 }