jbd2: Fix oops in jbd2_journal_remove_journal_head()
jbd2_journal_remove_journal_head() can oops when trying to access journal_head returned by bh2jh(). This is caused for example by the following race: TASK1 TASK2 jbd2_journal_commit_transaction() ... processing t_forget list __jbd2_journal_refile_buffer(jh); if (!jh->b_transaction) { jbd_unlock_bh_state(bh); jbd2_journal_try_to_free_buffers() jbd2_journal_grab_journal_head(bh) jbd_lock_bh_state(bh) __journal_try_to_free_buffer() jbd2_journal_put_journal_head(jh) jbd2_journal_remove_journal_head(bh); jbd2_journal_put_journal_head() in TASK2 sees that b_jcount == 0 and buffer is not part of any transaction and thus frees journal_head before TASK1 gets to doing so. Note that even buffer_head can be released by try_to_free_buffers() after jbd2_journal_put_journal_head() which adds even larger opportunity for oops (but I didn't see this happen in reality). Fix the problem by making transactions hold their own journal_head reference (in b_jcount). That way we don't have to remove journal_head explicitely via jbd2_journal_remove_journal_head() and instead just remove journal_head when b_jcount drops to zero. The result of this is that [__]jbd2_journal_refile_buffer(), [__]jbd2_journal_unfile_buffer(), and __jdb2_journal_remove_checkpoint() can free journal_head which needs modification of a few callers. Also we have to be careful because once journal_head is removed, buffer_head might be freed as well. So we have to get our own buffer_head reference where it matters. Signed-off-by: Jan Kara <jack@suse.cz> Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
This commit is contained in:
@@ -2078,10 +2078,9 @@ static void journal_free_journal_head(struct journal_head *jh)
|
||||
* When a buffer has its BH_JBD bit set it is immune from being released by
|
||||
* core kernel code, mainly via ->b_count.
|
||||
*
|
||||
* A journal_head may be detached from its buffer_head when the journal_head's
|
||||
* b_transaction, b_cp_transaction and b_next_transaction pointers are NULL.
|
||||
* Various places in JBD call jbd2_journal_remove_journal_head() to indicate that the
|
||||
* journal_head can be dropped if needed.
|
||||
* A journal_head is detached from its buffer_head when the journal_head's
|
||||
* b_jcount reaches zero. Running transaction (b_transaction) and checkpoint
|
||||
* transaction (b_cp_transaction) hold their references to b_jcount.
|
||||
*
|
||||
* Various places in the kernel want to attach a journal_head to a buffer_head
|
||||
* _before_ attaching the journal_head to a transaction. To protect the
|
||||
@@ -2094,17 +2093,16 @@ static void journal_free_journal_head(struct journal_head *jh)
|
||||
* (Attach a journal_head if needed. Increments b_jcount)
|
||||
* struct journal_head *jh = jbd2_journal_add_journal_head(bh);
|
||||
* ...
|
||||
* (Get another reference for transaction)
|
||||
* jbd2_journal_grab_journal_head(bh);
|
||||
* jh->b_transaction = xxx;
|
||||
* (Put original reference)
|
||||
* jbd2_journal_put_journal_head(jh);
|
||||
*
|
||||
* Now, the journal_head's b_jcount is zero, but it is safe from being released
|
||||
* because it has a non-zero b_transaction.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Give a buffer_head a journal_head.
|
||||
*
|
||||
* Doesn't need the journal lock.
|
||||
* May sleep.
|
||||
*/
|
||||
struct journal_head *jbd2_journal_add_journal_head(struct buffer_head *bh)
|
||||
@@ -2168,61 +2166,29 @@ static void __journal_remove_journal_head(struct buffer_head *bh)
|
||||
struct journal_head *jh = bh2jh(bh);
|
||||
|
||||
J_ASSERT_JH(jh, jh->b_jcount >= 0);
|
||||
|
||||
get_bh(bh);
|
||||
if (jh->b_jcount == 0) {
|
||||
if (jh->b_transaction == NULL &&
|
||||
jh->b_next_transaction == NULL &&
|
||||
jh->b_cp_transaction == NULL) {
|
||||
J_ASSERT_JH(jh, jh->b_jlist == BJ_None);
|
||||
J_ASSERT_BH(bh, buffer_jbd(bh));
|
||||
J_ASSERT_BH(bh, jh2bh(jh) == bh);
|
||||
BUFFER_TRACE(bh, "remove journal_head");
|
||||
if (jh->b_frozen_data) {
|
||||
printk(KERN_WARNING "%s: freeing "
|
||||
"b_frozen_data\n",
|
||||
__func__);
|
||||
jbd2_free(jh->b_frozen_data, bh->b_size);
|
||||
}
|
||||
if (jh->b_committed_data) {
|
||||
printk(KERN_WARNING "%s: freeing "
|
||||
"b_committed_data\n",
|
||||
__func__);
|
||||
jbd2_free(jh->b_committed_data, bh->b_size);
|
||||
}
|
||||
bh->b_private = NULL;
|
||||
jh->b_bh = NULL; /* debug, really */
|
||||
clear_buffer_jbd(bh);
|
||||
__brelse(bh);
|
||||
journal_free_journal_head(jh);
|
||||
} else {
|
||||
BUFFER_TRACE(bh, "journal_head was locked");
|
||||
}
|
||||
J_ASSERT_JH(jh, jh->b_transaction == NULL);
|
||||
J_ASSERT_JH(jh, jh->b_next_transaction == NULL);
|
||||
J_ASSERT_JH(jh, jh->b_cp_transaction == NULL);
|
||||
J_ASSERT_JH(jh, jh->b_jlist == BJ_None);
|
||||
J_ASSERT_BH(bh, buffer_jbd(bh));
|
||||
J_ASSERT_BH(bh, jh2bh(jh) == bh);
|
||||
BUFFER_TRACE(bh, "remove journal_head");
|
||||
if (jh->b_frozen_data) {
|
||||
printk(KERN_WARNING "%s: freeing b_frozen_data\n", __func__);
|
||||
jbd2_free(jh->b_frozen_data, bh->b_size);
|
||||
}
|
||||
if (jh->b_committed_data) {
|
||||
printk(KERN_WARNING "%s: freeing b_committed_data\n", __func__);
|
||||
jbd2_free(jh->b_committed_data, bh->b_size);
|
||||
}
|
||||
bh->b_private = NULL;
|
||||
jh->b_bh = NULL; /* debug, really */
|
||||
clear_buffer_jbd(bh);
|
||||
journal_free_journal_head(jh);
|
||||
}
|
||||
|
||||
/*
|
||||
* jbd2_journal_remove_journal_head(): if the buffer isn't attached to a transaction
|
||||
* and has a zero b_jcount then remove and release its journal_head. If we did
|
||||
* see that the buffer is not used by any transaction we also "logically"
|
||||
* decrement ->b_count.
|
||||
*
|
||||
* We in fact take an additional increment on ->b_count as a convenience,
|
||||
* because the caller usually wants to do additional things with the bh
|
||||
* after calling here.
|
||||
* The caller of jbd2_journal_remove_journal_head() *must* run __brelse(bh) at some
|
||||
* time. Once the caller has run __brelse(), the buffer is eligible for
|
||||
* reaping by try_to_free_buffers().
|
||||
*/
|
||||
void jbd2_journal_remove_journal_head(struct buffer_head *bh)
|
||||
{
|
||||
jbd_lock_bh_journal_head(bh);
|
||||
__journal_remove_journal_head(bh);
|
||||
jbd_unlock_bh_journal_head(bh);
|
||||
}
|
||||
|
||||
/*
|
||||
* Drop a reference on the passed journal_head. If it fell to zero then try to
|
||||
* Drop a reference on the passed journal_head. If it fell to zero then
|
||||
* release the journal_head from the buffer_head.
|
||||
*/
|
||||
void jbd2_journal_put_journal_head(struct journal_head *jh)
|
||||
@@ -2232,11 +2198,12 @@ void jbd2_journal_put_journal_head(struct journal_head *jh)
|
||||
jbd_lock_bh_journal_head(bh);
|
||||
J_ASSERT_JH(jh, jh->b_jcount > 0);
|
||||
--jh->b_jcount;
|
||||
if (!jh->b_jcount && !jh->b_transaction) {
|
||||
if (!jh->b_jcount) {
|
||||
__journal_remove_journal_head(bh);
|
||||
jbd_unlock_bh_journal_head(bh);
|
||||
__brelse(bh);
|
||||
}
|
||||
jbd_unlock_bh_journal_head(bh);
|
||||
} else
|
||||
jbd_unlock_bh_journal_head(bh);
|
||||
}
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user