jbd2: fix use after free in jbd2_log_do_checkpoint()
authorJan Kara <jack@suse.cz>
Fri, 5 Oct 2018 22:44:40 +0000 (18:44 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 13 Nov 2018 19:16:55 +0000 (11:16 -0800)
commit ccd3c4373eacb044eb3832966299d13d2631f66f upstream.

The code cleaning transaction's lists of checkpoint buffers has a bug
where it increases bh refcount only after releasing
journal->j_list_lock. Thus the following race is possible:

CPU0 CPU1
jbd2_log_do_checkpoint()
jbd2_journal_try_to_free_buffers()
  __journal_try_to_free_buffer(bh)
  ...
  while (transaction->t_checkpoint_io_list)
  ...
    if (buffer_locked(bh)) {

<-- IO completes now, buffer gets unlocked -->

      spin_unlock(&journal->j_list_lock);
    spin_lock(&journal->j_list_lock);
    __jbd2_journal_remove_checkpoint(jh);
    spin_unlock(&journal->j_list_lock);
  try_to_free_buffers(page);
      get_bh(bh) <-- accesses freed bh

Fix the problem by grabbing bh reference before unlocking
journal->j_list_lock.

Fixes: dc6e8d669cf5 ("jbd2: don't call get_bh() before calling __jbd2_journal_remove_checkpoint()")
Fixes: be1158cc615f ("jbd2: fold __process_buffer() into jbd2_log_do_checkpoint()")
Reported-by: syzbot+7f4a27091759e2fe7453@syzkaller.appspotmail.com
CC: stable@vger.kernel.org
Reviewed-by: Lukas Czerner <lczerner@redhat.com>
Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/jbd2/checkpoint.c

index 684996c8a3a4a2df646dc44c4ee4e90904470989..4d5a5a4cc017cd4cf9b21627be97ba7189e9b1a2 100644 (file)
@@ -254,8 +254,8 @@ restart:
                bh = jh2bh(jh);
 
                if (buffer_locked(bh)) {
-                       spin_unlock(&journal->j_list_lock);
                        get_bh(bh);
+                       spin_unlock(&journal->j_list_lock);
                        wait_on_buffer(bh);
                        /* the journal_head may have gone by now */
                        BUFFER_TRACE(bh, "brelse");
@@ -336,8 +336,8 @@ restart2:
                jh = transaction->t_checkpoint_io_list;
                bh = jh2bh(jh);
                if (buffer_locked(bh)) {
-                       spin_unlock(&journal->j_list_lock);
                        get_bh(bh);
+                       spin_unlock(&journal->j_list_lock);
                        wait_on_buffer(bh);
                        /* the journal_head may have gone by now */
                        BUFFER_TRACE(bh, "brelse");