diff options
author | Thomas Gleixner <tglx@linutronix.de> | 2010-05-01 10:23:13 -0400 |
---|---|---|
committer | Thomas Gleixner <tglx@linutronix.de> | 2010-05-02 14:13:54 -0400 |
commit | 360c3e7265901a0231f40121a6ff3fc78172334f (patch) | |
tree | 4ea61d260337a5955480be18597ef92732ba39ee | |
parent | 0dc6b732a4543ecf6be4251fa1615bfca49ebd1a (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.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: | |||
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 | ||
364 | relock1: | ||
365 | spin_lock(&dentry->d_lock); | ||
366 | kill_it: | 364 | kill_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; |
370 | relock2: | 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 | |||
383 | retry: | ||
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 | /** |