diff options
author | Thomas Gleixner <tglx@linutronix.de> | 2010-05-01 16:23:13 +0200 |
---|---|---|
committer | Thomas Gleixner <tglx@linutronix.de> | 2010-05-02 20:13:54 +0200 |
commit | 360c3e7265901a0231f40121a6ff3fc78172334f (patch) | |
tree | 4ea61d260337a5955480be18597ef92732ba39ee | |
parent | 0dc6b732a4543ecf6be4251fa1615bfca49ebd1a (diff) | |
download | lwn-360c3e7265901a0231f40121a6ff3fc78172334f.tar.gz lwn-360c3e7265901a0231f40121a6ff3fc78172334f.zip |
fs: Prevent dput race
dput() drops dentry->d_lock when it fails to lock inode->i_lock or
parent->d_lock. dentry->d_count is 0 at this point so dentry kann be
killed and freed by someone else. This leaves dput with a stale
pointer in the retry code which results in interesting kernel crashes.
Prevent this by incrementing dentry->d_count before dropping the
lock. Go back to start after dropping the lock so d_count is
decremented again.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
-rw-r--r-- | fs/dcache.c | 48 |
1 files changed, 22 insertions, 26 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index 89b9d1f14871..23a3401af2fb 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -352,48 +352,44 @@ repeat: } } /* Unreachable? Get rid of it */ - if (d_unhashed(dentry)) + if (d_unhashed(dentry)) goto kill_it; - if (list_empty(&dentry->d_lru)) { - dentry->d_flags |= DCACHE_REFERENCED; + if (list_empty(&dentry->d_lru)) { + dentry->d_flags |= DCACHE_REFERENCED; dentry_lru_add(dentry); - } + } spin_unlock(&dentry->d_lock); return; -relock1: - spin_lock(&dentry->d_lock); kill_it: inode = dentry->d_inode; - if (inode) { - if (!spin_trylock(&inode->i_lock)) { -relock2: - spin_unlock(&dentry->d_lock); - goto relock1; - } - } + if (inode && !spin_trylock(&inode->i_lock)) + goto retry; + parent = dentry->d_parent; - if (parent && parent != dentry) { - if (!spin_trylock(&parent->d_lock)) { - if (inode) - spin_unlock(&inode->i_lock); - goto relock2; - } - } - if (atomic_read(&dentry->d_count)) { - /* This case should be fine */ - spin_unlock(&dentry->d_lock); - if (parent && parent != dentry) - spin_unlock(&parent->d_lock); + if (parent && parent != dentry && !spin_trylock(&parent->d_lock)) { if (inode) spin_unlock(&inode->i_lock); - return; + goto retry; } + /* if dentry was on the d_lru list delete it from there */ dentry_lru_del(dentry); dentry = d_kill(dentry); if (dentry) goto repeat; + return; + +retry: + /* + * We are about to drop dentry->d_lock. dentry->d_count is 0 + * so it could be freed by someone else and leave us with a + * stale pointer. Prevent this by increasing d_count before + * dropping d_lock. + */ + atomic_inc(&dentry->d_count); + spin_unlock(&dentry->d_lock); + goto repeat; } /** |