diff options
author | Al Viro <viro@zeniv.linux.org.uk> | 2018-02-23 20:47:17 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2018-03-22 09:18:00 +0100 |
commit | 05f16fe9ae8c18f461b25fd6f31c77333c739f58 (patch) | |
tree | 5177d94c775af728d34d287bf37ffa58e38f6e31 /fs | |
parent | eaa9592678a9553ba51493bbe0257f51e6638abd (diff) |
lock_parent() needs to recheck if dentry got __dentry_kill'ed under it
commit 3b821409632ab778d46e807516b457dfa72736ed upstream.
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>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/dcache.c | 11 |
1 files changed, 8 insertions, 3 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index 67957f5b325c..c0c7fa8224ba 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -637,11 +637,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; } |