summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Gleixner <tglx@linutronix.de>2010-05-01 16:23:13 +0200
committerThomas Gleixner <tglx@linutronix.de>2010-05-02 20:13:54 +0200
commit360c3e7265901a0231f40121a6ff3fc78172334f (patch)
tree4ea61d260337a5955480be18597ef92732ba39ee
parent0dc6b732a4543ecf6be4251fa1615bfca49ebd1a (diff)
downloadlwn-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.c48
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;
}
/**