Commit 981fcc56 authored by Zhang Yi's avatar Zhang Yi Committed by Theodore Ts'o
Browse files

jbd2: fix deadlock in jbd2_journal_cancel_revoke()



Commit f76d4c28 ("fs/jbd2: use sleeping version of
__find_get_block()") changed jbd2_journal_cancel_revoke() to use
__find_get_block_nonatomic() which holds the folio lock instead of
i_private_lock. This breaks the lock ordering (folio -> buffer) and
causes an ABBA deadlock when the filesystem blocksize < pagesize:

     T1                                T2
ext4_mkdir()
 ext4_init_new_dir()
  ext4_append()
   ext4_getblk()
    lock_buffer()    <- A
                                   sync_blockdev()
                                    blkdev_writepages()
                                     writeback_iter()
                                      writeback_get_folio()
                                       folio_lock()   <- B
     ext4_journal_get_create_access()
      jbd2_journal_cancel_revoke()
       __find_get_block_nonatomic()
        folio_lock()  <- B
                                     block_write_full_folio()
                                      lock_buffer()   <- A

This can occasionally cause generic/013 to hang.

Fix by only calling __find_get_block_nonatomic() when the passed
buffer_head doesn't belong to the bdev, which is the only case that we
need to look up its bdev alias. Otherwise, the lookup is redundant since
the found buffer_head is equal to the one we passed in.

Fixes: f76d4c28 ("fs/jbd2: use sleeping version of __find_get_block()")
Signed-off-by: default avatarZhang Yi <yi.zhang@huawei.com>
Link: https://patch.msgid.link/20260409114204.917154-1-yi.zhang@huaweicloud.com


Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
Cc: stable@kernel.org
parent 77d05951
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -428,6 +428,7 @@ void jbd2_journal_cancel_revoke(handle_t *handle, struct journal_head *jh)
	journal_t *journal = handle->h_transaction->t_journal;
	int need_cancel;
	struct buffer_head *bh = jh2bh(jh);
	struct address_space *bh_mapping = bh->b_folio->mapping;

	jbd2_debug(4, "journal_head %p, cancelling revoke\n", jh);

@@ -464,12 +465,13 @@ void jbd2_journal_cancel_revoke(handle_t *handle, struct journal_head *jh)
	 * buffer_head?  If so, we'd better make sure we clear the
	 * revoked status on any hashed alias too, otherwise the revoke
	 * state machine will get very upset later on. */
	if (need_cancel) {
	if (need_cancel && !sb_is_blkdev_sb(bh_mapping->host->i_sb)) {
		struct buffer_head *bh2;

		bh2 = __find_get_block_nonatomic(bh->b_bdev, bh->b_blocknr,
						 bh->b_size);
		if (bh2) {
			if (bh2 != bh)
			WARN_ON_ONCE(bh2 == bh);
			clear_buffer_revoked(bh2);
			__brelse(bh2);
		}