| From 0aa678b0d45423315f67e93355254ee38d9c23a8 Mon Sep 17 00:00:00 2001 |
| From: "zhangyi (F)" <yi.zhang@huawei.com> |
| Date: Thu, 13 Feb 2020 14:38:21 +0800 |
| Subject: [PATCH] jbd2: do not clear the BH_Mapped flag when forgetting a |
| metadata buffer |
| |
| commit c96dceeabf765d0b1b1f29c3bf50a5c01315b820 upstream. |
| |
| Commit 904cdbd41d74 ("jbd2: clear dirty flag when revoking a buffer from |
| an older transaction") set the BH_Freed flag when forgetting a metadata |
| buffer which belongs to the committing transaction, it indicate the |
| committing process clear dirty bits when it is done with the buffer. But |
| it also clear the BH_Mapped flag at the same time, which may trigger |
| below NULL pointer oops when block_size < PAGE_SIZE. |
| |
| rmdir 1 kjournald2 mkdir 2 |
| jbd2_journal_commit_transaction |
| commit transaction N |
| jbd2_journal_forget |
| set_buffer_freed(bh1) |
| jbd2_journal_commit_transaction |
| commit transaction N+1 |
| ... |
| clear_buffer_mapped(bh1) |
| ext4_getblk(bh2 ummapped) |
| ... |
| grow_dev_page |
| init_page_buffers |
| bh1->b_private=NULL |
| bh2->b_private=NULL |
| jbd2_journal_put_journal_head(jh1) |
| __journal_remove_journal_head(hb1) |
| jh1 is NULL and trigger oops |
| |
| *) Dir entry block bh1 and bh2 belongs to one page, and the bh2 has |
| already been unmapped. |
| |
| For the metadata buffer we forgetting, we should always keep the mapped |
| flag and clear the dirty flags is enough, so this patch pick out the |
| these buffers and keep their BH_Mapped flag. |
| |
| Link: https://lore.kernel.org/r/20200213063821.30455-3-yi.zhang@huawei.com |
| Fixes: 904cdbd41d74 ("jbd2: clear dirty flag when revoking a buffer from an older transaction") |
| Reviewed-by: Jan Kara <jack@suse.cz> |
| Signed-off-by: zhangyi (F) <yi.zhang@huawei.com> |
| Signed-off-by: Theodore Ts'o <tytso@mit.edu> |
| Cc: stable@kernel.org |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/fs/jbd2/commit.c b/fs/jbd2/commit.c |
| index 9177b3490ef1..d55c373daa80 100644 |
| --- a/fs/jbd2/commit.c |
| +++ b/fs/jbd2/commit.c |
| @@ -983,12 +983,29 @@ void jbd2_journal_commit_transaction(journal_t *journal) |
| * pagesize and it is attached to the last partial page. |
| */ |
| if (buffer_freed(bh) && !jh->b_next_transaction) { |
| + struct address_space *mapping; |
| + |
| clear_buffer_freed(bh); |
| clear_buffer_jbddirty(bh); |
| - clear_buffer_mapped(bh); |
| - clear_buffer_new(bh); |
| - clear_buffer_req(bh); |
| - bh->b_bdev = NULL; |
| + |
| + /* |
| + * Block device buffers need to stay mapped all the |
| + * time, so it is enough to clear buffer_jbddirty and |
| + * buffer_freed bits. For the file mapping buffers (i.e. |
| + * journalled data) we need to unmap buffer and clear |
| + * more bits. We also need to be careful about the check |
| + * because the data page mapping can get cleared under |
| + * out hands, which alse need not to clear more bits |
| + * because the page and buffers will be freed and can |
| + * never be reused once we are done with them. |
| + */ |
| + mapping = READ_ONCE(bh->b_page->mapping); |
| + if (mapping && !sb_is_blkdev_sb(mapping->host->i_sb)) { |
| + clear_buffer_mapped(bh); |
| + clear_buffer_new(bh); |
| + clear_buffer_req(bh); |
| + bh->b_bdev = NULL; |
| + } |
| } |
| |
| if (buffer_jbddirty(bh)) { |
| -- |
| 2.7.4 |
| |