VYPR
Unrated severityNVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-46061

CVE-2026-46061

Description

In the Linux kernel, the following vulnerability has been resolved:

jbd2: fix deadlock in jbd2_journal_cancel_revoke()

Commit f76d4c28a46a ("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.

Affected products

2

Patches

8
bbd943d6a2d5

jbd2: fix deadlock in jbd2_journal_cancel_revoke()

2 files changed · +10 8
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index 9016ddb824474d..e4c2fbd381f123 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -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,13 +465,14 @@ 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index 9016ddb824474d..e4c2fbd381f123 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -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,13 +465,14 @@ 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
dff07cc98fdf

jbd2: fix deadlock in jbd2_journal_cancel_revoke()

2 files changed · +10 8
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index f68fc8c255f007..5a557f6f921aa7 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -429,6 +429,7 @@ int jbd2_journal_cancel_revoke(handle_t *handle, struct journal_head *jh)
     	int need_cancel;
     	int did_revoke = 0;	/* akpm: debug */
     	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);
     
    @@ -466,13 +467,14 @@ int 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index f68fc8c255f007..5a557f6f921aa7 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -429,6 +429,7 @@ int jbd2_journal_cancel_revoke(handle_t *handle, struct journal_head *jh)
     	int need_cancel;
     	int did_revoke = 0;	/* akpm: debug */
     	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);
     
    @@ -466,13 +467,14 @@ int 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
2b2fee890250

jbd2: fix deadlock in jbd2_journal_cancel_revoke()

2 files changed · +10 8
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index 1467f6790747d8..c3a18061ce11bc 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -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,13 +465,14 @@ 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index 1467f6790747d8..c3a18061ce11bc 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -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,13 +465,14 @@ 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
981fcc5674e6

jbd2: fix deadlock in jbd2_journal_cancel_revoke()

2 files changed · +10 8
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index 9016ddb824474d..e4c2fbd381f123 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -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,13 +465,14 @@ 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index 9016ddb824474d..e4c2fbd381f123 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -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,13 +465,14 @@ 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
2b2fee890250

jbd2: fix deadlock in jbd2_journal_cancel_revoke()

2 files changed · +10 8
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index 1467f6790747d8..c3a18061ce11bc 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -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,13 +465,14 @@ 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index 1467f6790747d8..c3a18061ce11bc 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -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,13 +465,14 @@ 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
981fcc5674e6

jbd2: fix deadlock in jbd2_journal_cancel_revoke()

2 files changed · +10 8
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index 9016ddb824474d..e4c2fbd381f123 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -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,13 +465,14 @@ 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index 9016ddb824474d..e4c2fbd381f123 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -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,13 +465,14 @@ 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
bbd943d6a2d5

jbd2: fix deadlock in jbd2_journal_cancel_revoke()

2 files changed · +10 8
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index 9016ddb824474d..e4c2fbd381f123 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -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,13 +465,14 @@ 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index 9016ddb824474d..e4c2fbd381f123 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -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,13 +465,14 @@ 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
dff07cc98fdf

jbd2: fix deadlock in jbd2_journal_cancel_revoke()

2 files changed · +10 8
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index f68fc8c255f007..5a557f6f921aa7 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -429,6 +429,7 @@ int jbd2_journal_cancel_revoke(handle_t *handle, struct journal_head *jh)
     	int need_cancel;
     	int did_revoke = 0;	/* akpm: debug */
     	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);
     
    @@ -466,13 +467,14 @@ int 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/jbd2/revoke.c+5 4 modified
    diff --git a/fs/jbd2/revoke.c b/fs/jbd2/revoke.c
    index f68fc8c255f007..5a557f6f921aa7 100644
    --- a/fs/jbd2/revoke.c
    +++ b/fs/jbd2/revoke.c
    @@ -429,6 +429,7 @@ int jbd2_journal_cancel_revoke(handle_t *handle, struct journal_head *jh)
     	int need_cancel;
     	int did_revoke = 0;	/* akpm: debug */
     	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);
     
    @@ -466,13 +467,14 @@ int 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)
    -				clear_buffer_revoked(bh2);
    +			WARN_ON_ONCE(bh2 == bh);
    +			clear_buffer_revoked(bh2);
     			__brelse(bh2);
     		}
     	}
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Lock-ordering violation in jbd2_journal_cancel_revoke(): __find_get_block_nonatomic() acquires the folio lock while the caller may already hold a buffer lock, creating an ABBA deadlock when filesystem blocksize < pagesize."

Attack vector

An attacker who can trigger concurrent ext4 metadata operations (e.g., mkdir) and block-device writeback on a filesystem where blocksize &lt; pagesize can cause a deadlock. Thread T1 holds buffer lock A (via ext4_getblk) then enters jbd2_journal_cancel_revoke, which calls __find_get_block_nonatomic and tries to acquire folio lock B. Concurrently, thread T2 holds folio lock B (via writeback_get_folio) then tries to acquire buffer lock A (via block_write_full_folio). The cross-locking results in an ABBA deadlock that hangs the system [patch_id=2660043].

Affected code

The vulnerable function is `jbd2_journal_cancel_revoke()` in `fs/jbd2/revoke.c` [patch_id=2660043]. The fault lies in the unconditional call to `__find_get_block_nonatomic()` which acquires the folio lock, breaking the established lock ordering (folio → buffer).

What the fix does

The patch adds a guard condition `!sb_is_blkdev_sb(bh_mapping->host->i_sb)` before calling `__find_get_block_nonatomic()` [patch_id=2660043]. When the buffer_head belongs to the block device (bdev), the lookup is redundant because the found buffer_head would be the same one already held, so skipping it avoids acquiring the folio lock entirely. This restores the correct lock ordering (folio → buffer) and eliminates the ABBA deadlock. The old `if (bh2 != bh)` check is replaced with `WARN_ON_ONCE(bh2 == bh)` and `clear_buffer_revoked(bh2)` is now unconditional on the non-bdev path.

Preconditions

  • configFilesystem blocksize must be smaller than the system page size (e.g., 4K blocks on 64K pages)
  • inputConcurrent ext4 metadata operations (e.g., mkdir) and block-device writeback (sync_blockdev) must race

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.