diff options
| author | npiggin@suse.de <npiggin@suse.de> | 2010-06-23 23:02:14 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-06-29 13:38:22 -0400 |
| commit | 57439f878afafefad8836ebf5c49da2a0a746105 (patch) | |
| tree | ec11dd35704aa37d77a5fd8404fda57f297a3834 | |
| parent | 5904b3b81d25166e5e39b9727645bb47937618e3 (diff) | |
fs: fix superblock iteration race
list_for_each_entry_safe is not suitable to protect against concurrent
modification of the list. 6754af6 introduced a race in sb walking.
list_for_each_entry can use the trick of pinning the current entry in
the list before we drop and retake the lock because it subsequently
follows cur->next. However list_for_each_entry_safe saves n=cur->next
for following before entering the loop body, so when the lock is
dropped, n may be deleted.
Signed-off-by: Nick Piggin <npiggin@suse.de>
Cc: Christoph Hellwig <hch@infradead.org>
Cc: John Stultz <johnstul@us.ibm.com>
Cc: Frank Mayhar <fmayhar@google.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
| -rw-r--r-- | fs/dcache.c | 2 | ||||
| -rw-r--r-- | fs/super.c | 6 | ||||
| -rw-r--r-- | include/linux/list.h | 15 |
3 files changed, 23 insertions, 0 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index d96047b4a633..c8c78ba07827 100644 --- a/fs/dcache.c +++ b/fs/dcache.c | |||
| @@ -590,6 +590,8 @@ static void prune_dcache(int count) | |||
| 590 | up_read(&sb->s_umount); | 590 | up_read(&sb->s_umount); |
| 591 | } | 591 | } |
| 592 | spin_lock(&sb_lock); | 592 | spin_lock(&sb_lock); |
| 593 | /* lock was dropped, must reset next */ | ||
| 594 | list_safe_reset_next(sb, n, s_list); | ||
| 593 | count -= pruned; | 595 | count -= pruned; |
| 594 | __put_super(sb); | 596 | __put_super(sb); |
| 595 | /* more work left to do? */ | 597 | /* more work left to do? */ |
diff --git a/fs/super.c b/fs/super.c index 5c35bc7a499e..938119ab8dcb 100644 --- a/fs/super.c +++ b/fs/super.c | |||
| @@ -374,6 +374,8 @@ void sync_supers(void) | |||
| 374 | up_read(&sb->s_umount); | 374 | up_read(&sb->s_umount); |
| 375 | 375 | ||
| 376 | spin_lock(&sb_lock); | 376 | spin_lock(&sb_lock); |
| 377 | /* lock was dropped, must reset next */ | ||
| 378 | list_safe_reset_next(sb, n, s_list); | ||
| 377 | __put_super(sb); | 379 | __put_super(sb); |
| 378 | } | 380 | } |
| 379 | } | 381 | } |
| @@ -405,6 +407,8 @@ void iterate_supers(void (*f)(struct super_block *, void *), void *arg) | |||
| 405 | up_read(&sb->s_umount); | 407 | up_read(&sb->s_umount); |
| 406 | 408 | ||
| 407 | spin_lock(&sb_lock); | 409 | spin_lock(&sb_lock); |
| 410 | /* lock was dropped, must reset next */ | ||
| 411 | list_safe_reset_next(sb, n, s_list); | ||
| 408 | __put_super(sb); | 412 | __put_super(sb); |
| 409 | } | 413 | } |
| 410 | spin_unlock(&sb_lock); | 414 | spin_unlock(&sb_lock); |
| @@ -585,6 +589,8 @@ static void do_emergency_remount(struct work_struct *work) | |||
| 585 | } | 589 | } |
| 586 | up_write(&sb->s_umount); | 590 | up_write(&sb->s_umount); |
| 587 | spin_lock(&sb_lock); | 591 | spin_lock(&sb_lock); |
| 592 | /* lock was dropped, must reset next */ | ||
| 593 | list_safe_reset_next(sb, n, s_list); | ||
| 588 | __put_super(sb); | 594 | __put_super(sb); |
| 589 | } | 595 | } |
| 590 | spin_unlock(&sb_lock); | 596 | spin_unlock(&sb_lock); |
diff --git a/include/linux/list.h b/include/linux/list.h index 8392884a2977..5d57a3a1fa1b 100644 --- a/include/linux/list.h +++ b/include/linux/list.h | |||
| @@ -544,6 +544,21 @@ static inline void list_splice_tail_init(struct list_head *list, | |||
| 544 | &pos->member != (head); \ | 544 | &pos->member != (head); \ |
| 545 | pos = n, n = list_entry(n->member.prev, typeof(*n), member)) | 545 | pos = n, n = list_entry(n->member.prev, typeof(*n), member)) |
| 546 | 546 | ||
| 547 | /** | ||
| 548 | * list_safe_reset_next - reset a stale list_for_each_entry_safe loop | ||
| 549 | * @pos: the loop cursor used in the list_for_each_entry_safe loop | ||
| 550 | * @n: temporary storage used in list_for_each_entry_safe | ||
| 551 | * @member: the name of the list_struct within the struct. | ||
| 552 | * | ||
| 553 | * list_safe_reset_next is not safe to use in general if the list may be | ||
| 554 | * modified concurrently (eg. the lock is dropped in the loop body). An | ||
| 555 | * exception to this is if the cursor element (pos) is pinned in the list, | ||
| 556 | * and list_safe_reset_next is called after re-taking the lock and before | ||
| 557 | * completing the current iteration of the loop body. | ||
| 558 | */ | ||
| 559 | #define list_safe_reset_next(pos, n, member) \ | ||
| 560 | n = list_entry(pos->member.next, typeof(*pos), member) | ||
| 561 | |||
| 547 | /* | 562 | /* |
| 548 | * Double linked lists with a single pointer list head. | 563 | * Double linked lists with a single pointer list head. |
| 549 | * Mostly useful for hash tables where the two pointer list head is | 564 | * Mostly useful for hash tables where the two pointer list head is |
