aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen C. Tweedie <sct@redhat.com>2005-03-28 04:24:26 -0800
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-03-28 04:24:26 -0800
commitb489072eefdaaf41fb6a25cb940c40d2410bb3f1 (patch)
tree80b8504f9d6d8ab24704ca706601a201f58483fb
parent3406d7beb747a164f6d4dc819dc9df5d13c96340 (diff)
downloadhistory-b489072eefdaaf41fb6a25cb940c40d2410bb3f1.tar.gz
[PATCH] ext3: fix journal_unmap_buffer race
There is a race condition in jbd between journal_unmap_buffer() and journal_commit_transaction(). This is leading to corruption of buffers on the transaction's t_locked_list, leading to a variety of symptoms usually involving an oops in kjournald. The problem is that various special-case exit paths in journal_unmap_buffer() call journal_put_journal_head without any locking. This is racing against a refiling of the same journal_head in journal_commit_transaction(): __journal_unfile_buffer(jh); __journal_file_buffer(jh, commit_transaction, BJ_Locked); The way these functions work, this leaves the jh temporarily with b_transaction==NULL; and if journal_unmap_buffer()'s call to journal_put_journal_head() hits this window, it sees the NULL transaction and frees the journal_head which is just about to get refiled on the locked list. The main exit path on journal_unmap_buffer() performs its journal_put_journal_head() before dropping the j_list_lock, so is not vulnerable to this race. The fix is to move the other similar calls on special-case exit branches in that function so that they also release the journal_head before dropping that lock. This is low-risk since the new order has already been tested as the normal exit path from this function. The change has had extensive testing and has been shown to fix the problem with no regressions found. Signed-off-by: Peter Keilty <Peter.Keilty@hp.com> Signed-off-by: Nicholas Dokos <nicholas.dokos@hp.com> Signed-off-by: Stephen Tweedie <sct@redhat.com> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r--fs/jbd/transaction.c6
1 files changed, 3 insertions, 3 deletions
diff --git a/fs/jbd/transaction.c b/fs/jbd/transaction.c
index c64b8c03aa4bf..932e7c1ef4a1c 100644
--- a/fs/jbd/transaction.c
+++ b/fs/jbd/transaction.c
@@ -1785,10 +1785,10 @@ static int journal_unmap_buffer(journal_t *journal, struct buffer_head *bh)
JBUFFER_TRACE(jh, "checkpointed: add to BJ_Forget");
ret = __dispose_buffer(jh,
journal->j_running_transaction);
+ journal_put_journal_head(jh);
spin_unlock(&journal->j_list_lock);
jbd_unlock_bh_state(bh);
spin_unlock(&journal->j_state_lock);
- journal_put_journal_head(jh);
return ret;
} else {
/* There is no currently-running transaction. So the
@@ -1799,10 +1799,10 @@ static int journal_unmap_buffer(journal_t *journal, struct buffer_head *bh)
JBUFFER_TRACE(jh, "give to committing trans");
ret = __dispose_buffer(jh,
journal->j_committing_transaction);
+ journal_put_journal_head(jh);
spin_unlock(&journal->j_list_lock);
jbd_unlock_bh_state(bh);
spin_unlock(&journal->j_state_lock);
- journal_put_journal_head(jh);
return ret;
} else {
/* The orphan record's transaction has
@@ -1823,10 +1823,10 @@ static int journal_unmap_buffer(journal_t *journal, struct buffer_head *bh)
journal->j_running_transaction);
jh->b_next_transaction = NULL;
}
+ journal_put_journal_head(jh);
spin_unlock(&journal->j_list_lock);
jbd_unlock_bh_state(bh);
spin_unlock(&journal->j_state_lock);
- journal_put_journal_head(jh);
return 0;
} else {
/* Good, the buffer belongs to the running transaction.