jbd: Fix oops in journal_remove_journal_head()
commitbb189247f35688a3353545902c56290fb7d7754a
authorJan Kara <jack@suse.cz>
Fri, 24 Jun 2011 21:11:59 +0000 (24 23:11 +0200)
committerJan Kara <jack@suse.cz>
Mon, 27 Jun 2011 09:44:37 +0000 (27 11:44 +0200)
tree02f93da7f642f3e59050d1c2837a7a8a8e61b3aa
parent2c2ea9451fc2a12ee57c8346f0da26969d07ee7f
jbd: Fix oops in journal_remove_journal_head()

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
  journal_commit_transaction()
    ...
    processing t_forget list
      __journal_refile_buffer(jh);
      if (!jh->b_transaction) {
        jbd_unlock_bh_state(bh);
journal_try_to_free_buffers()
  journal_grab_journal_head(bh)
  jbd_lock_bh_state(bh)
  __journal_try_to_free_buffer()
  journal_put_journal_head(jh)
        journal_remove_journal_head(bh);

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
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
journal_remove_journal_head() and instead just remove journal_head when
b_jcount drops to zero. The result of this is that [__]journal_refile_buffer(),
[__]journal_unfile_buffer(), and __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>
fs/jbd/checkpoint.c
fs/jbd/commit.c
fs/jbd/journal.c
fs/jbd/transaction.c
include/linux/jbd.h