jbd: fix race in buffer processing in commit code
authorJan Kara <jack@suse.cz>
Tue, 9 Jun 2009 23:26:26 +0000 (16:26 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Thu, 2 Jul 2009 23:31:33 +0000 (16:31 -0700)
commit a61d90d75d0f9e86432c45b496b4b0fbf0fd03dc upstream.

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: <linux-ext4@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
fs/jbd/commit.c

index ae08c057e751454ad2f0d922faa9be08d3c6d80d..558088d537783253753a3382a9db5f90809f357e 100644 (file)
@@ -238,7 +238,7 @@ write_out_data:
                        spin_lock(&journal->j_list_lock);
                }
                /* Someone already cleaned up the buffer? */
-               if (!buffer_jbd(bh)
+               if (!buffer_jbd(bh) || bh2jh(bh) != jh
                        || jh->b_transaction != commit_transaction
                        || jh->b_jlist != BJ_SyncData) {
                        jbd_unlock_bh_state(bh);
@@ -463,7 +463,9 @@ void journal_commit_transaction(journal_t *journal)
                        spin_lock(&journal->j_list_lock);
                        continue;
                }
-               if (buffer_jbd(bh) && jh->b_jlist == BJ_Locked) {
+               if (buffer_jbd(bh) && bh2jh(bh) == jh &&
+                   jh->b_transaction == commit_transaction &&
+                   jh->b_jlist == BJ_Locked) {
                        __journal_unfile_buffer(jh);
                        jbd_unlock_bh_state(bh);
                        journal_remove_journal_head(bh);