aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Gleixner <tglx@linutronix.de>2010-05-01 10:23:13 -0400
committerThomas Gleixner <tglx@linutronix.de>2010-05-02 14:13:54 -0400
commit360c3e7265901a0231f40121a6ff3fc78172334f (patch)
tree4ea61d260337a5955480be18597ef92732ba39ee
parent0dc6b732a4543ecf6be4251fa1615bfca49ebd1a (diff)
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:
352 } 352 }
353 } 353 }
354 /* Unreachable? Get rid of it */ 354 /* Unreachable? Get rid of it */
355 if (d_unhashed(dentry)) 355 if (d_unhashed(dentry))
356 goto kill_it; 356 goto kill_it;
357 if (list_empty(&dentry->d_lru)) { 357 if (list_empty(&dentry->d_lru)) {
358 dentry->d_flags |= DCACHE_REFERENCED; 358 dentry->d_flags |= DCACHE_REFERENCED;
359 dentry_lru_add(dentry); 359 dentry_lru_add(dentry);
360 } 360 }
361 spin_unlock(&dentry->d_lock); 361 spin_unlock(&dentry->d_lock);
362 return; 362 return;
363 363
364relock1:
365 spin_lock(&dentry->d_lock);
366kill_it: 364kill_it:
367 inode = dentry->d_inode; 365 inode = dentry->d_inode;
368 if (inode) { 366 if (inode && !spin_trylock(&inode->i_lock))
369 if (!spin_trylock(&inode->i_lock)) { 367 goto retry;
370relock2: 368
371 spin_unlock(&dentry->d_lock);
372 goto relock1;
373 }
374 }
375 parent = dentry->d_parent; 369 parent = dentry->d_parent;
376 if (parent && parent != dentry) { 370 if (parent && parent != dentry && !spin_trylock(&parent->d_lock)) {
377 if (!spin_trylock(&parent->d_lock)) {
378 if (inode)
379 spin_unlock(&inode->i_lock);
380 goto relock2;
381 }
382 }
383 if (atomic_read(&dentry->d_count)) {
384 /* This case should be fine */
385 spin_unlock(&dentry->d_lock);
386 if (parent && parent != dentry)
387 spin_unlock(&parent->d_lock);
388 if (inode) 371 if (inode)
389 spin_unlock(&inode->i_lock); 372 spin_unlock(&inode->i_lock);
390 return; 373 goto retry;
391 } 374 }
375
392 /* if dentry was on the d_lru list delete it from there */ 376 /* if dentry was on the d_lru list delete it from there */
393 dentry_lru_del(dentry); 377 dentry_lru_del(dentry);
394 dentry = d_kill(dentry); 378 dentry = d_kill(dentry);
395 if (dentry) 379 if (dentry)
396 goto repeat; 380 goto repeat;
381 return;
382
383retry:
384 /*
385 * We are about to drop dentry->d_lock. dentry->d_count is 0
386 * so it could be freed by someone else and leave us with a
387 * stale pointer. Prevent this by increasing d_count before
388 * dropping d_lock.
389 */
390 atomic_inc(&dentry->d_count);
391 spin_unlock(&dentry->d_lock);
392 goto repeat;
397} 393}
398 394
399/** 395/**