diff options
| author | Jan Kara <jack@suse.cz> | 2009-06-09 19:26:26 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-06-09 19:59:03 -0400 |
| commit | a61d90d75d0f9e86432c45b496b4b0fbf0fd03dc (patch) | |
| tree | 85d557e9d67cbad0347c6f12f7a60c474e485f7c | |
| parent | 463aea1a1c49f1a7d4b50656dfd6c8bb33358b1b (diff) | |
jbd: fix race in buffer processing in commit code
In commit code, we scan buffers attached to a transaction. During this
scan, we sometimes have to drop j_list_lock and then we recheck whether
the journal buffer head didn't get freed by journal_try_to_free_buffers().
But checking for buffer_jbd(bh) isn't enough because a new journal head
could get attached to our buffer head. So add a check whether the journal
head remained the same and whether it's still at the same transaction and
list.
This is a nasty bug and can cause problems like memory corruption (use after
free) or trigger various assertions in JBD code (observed).
Signed-off-by: Jan Kara <jack@suse.cz>
Cc: <stable@kernel.org>
Cc: <linux-ext4@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
| -rw-r--r-- | fs/jbd/commit.c | 6 |
1 files changed, 4 insertions, 2 deletions
diff --git a/fs/jbd/commit.c b/fs/jbd/commit.c index 06560c520f49..618e21c0b7a3 100644 --- a/fs/jbd/commit.c +++ b/fs/jbd/commit.c | |||
| @@ -241,7 +241,7 @@ write_out_data: | |||
| 241 | spin_lock(&journal->j_list_lock); | 241 | spin_lock(&journal->j_list_lock); |
| 242 | } | 242 | } |
| 243 | /* Someone already cleaned up the buffer? */ | 243 | /* Someone already cleaned up the buffer? */ |
| 244 | if (!buffer_jbd(bh) | 244 | if (!buffer_jbd(bh) || bh2jh(bh) != jh |
| 245 | || jh->b_transaction != commit_transaction | 245 | || jh->b_transaction != commit_transaction |
| 246 | || jh->b_jlist != BJ_SyncData) { | 246 | || jh->b_jlist != BJ_SyncData) { |
| 247 | jbd_unlock_bh_state(bh); | 247 | jbd_unlock_bh_state(bh); |
| @@ -478,7 +478,9 @@ void journal_commit_transaction(journal_t *journal) | |||
| 478 | spin_lock(&journal->j_list_lock); | 478 | spin_lock(&journal->j_list_lock); |
| 479 | continue; | 479 | continue; |
| 480 | } | 480 | } |
| 481 | if (buffer_jbd(bh) && jh->b_jlist == BJ_Locked) { | 481 | if (buffer_jbd(bh) && bh2jh(bh) == jh && |
| 482 | jh->b_transaction == commit_transaction && | ||
| 483 | jh->b_jlist == BJ_Locked) { | ||
| 482 | __journal_unfile_buffer(jh); | 484 | __journal_unfile_buffer(jh); |
| 483 | jbd_unlock_bh_state(bh); | 485 | jbd_unlock_bh_state(bh); |
| 484 | journal_remove_journal_head(bh); | 486 | journal_remove_journal_head(bh); |
