Please apply this series to the stable trees indicated by the subject prefix.
This series makes it possible to backport the latter two patches (fixing some syzbot issues and a use-after-free issue) that could not be backported as-is.
To achieve this, one dependent patch (patch 1/3) is included, and each patch is tailored to avoid extensive page/folio conversion. Both adjustments are specific to nilfs2 and do not include tree-wide changes.
It has also been tested against the latest versions of each tree.
Thanks, Ryusuke Konishi
Ryusuke Konishi (3): nilfs2: do not output warnings when clearing dirty buffers nilfs2: do not force clear folio if buffer is referenced nilfs2: protect access to buffers with no active references
fs/nilfs2/inode.c | 4 ++-- fs/nilfs2/mdt.c | 6 ++--- fs/nilfs2/page.c | 55 ++++++++++++++++++++++++++------------------- fs/nilfs2/page.h | 4 ++-- fs/nilfs2/segment.c | 4 +++- 5 files changed, 42 insertions(+), 31 deletions(-)
commit 299910dcb4525ac0274f3efa9527876315ba4f67 upstream.
After detecting file system corruption and degrading to a read-only mount, dirty folios and buffers in the page cache are cleared, and a large number of warnings are output at that time, often filling up the kernel log.
In this case, since the degrading to a read-only mount is output to the kernel log, these warnings are not very meaningful, and are rather a nuisance in system management and debugging.
The related nilfs2-specific page/folio routines have a silent argument that suppresses the warning output, but since it is not currently used meaningfully, remove both the silent argument and the warning output.
[konishi.ryusuke@gmail.com: adjusted for page/folio conversion] Link: https://lkml.kernel.org/r/20240816090128.4561-1-konishi.ryusuke@gmail.com Signed-off-by: Ryusuke Konishi konishi.ryusuke@gmail.com Signed-off-by: Andrew Morton akpm@linux-foundation.org Stable-dep-of: ca76bb226bf4 ("nilfs2: do not force clear folio if buffer is referenced") --- fs/nilfs2/inode.c | 4 ++-- fs/nilfs2/mdt.c | 6 +++--- fs/nilfs2/page.c | 20 +++----------------- fs/nilfs2/page.h | 4 ++-- 4 files changed, 10 insertions(+), 24 deletions(-)
diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c index 97c1beb00637..78d11e3275b3 100644 --- a/fs/nilfs2/inode.c +++ b/fs/nilfs2/inode.c @@ -162,7 +162,7 @@ static int nilfs_writepages(struct address_space *mapping, int err = 0;
if (sb_rdonly(inode->i_sb)) { - nilfs_clear_dirty_pages(mapping, false); + nilfs_clear_dirty_pages(mapping); return -EROFS; }
@@ -185,7 +185,7 @@ static int nilfs_writepage(struct page *page, struct writeback_control *wbc) * have dirty pages that try to be flushed in background. * So, here we simply discard this dirty page. */ - nilfs_clear_dirty_page(page, false); + nilfs_clear_dirty_page(page); unlock_page(page); return -EROFS; } diff --git a/fs/nilfs2/mdt.c b/fs/nilfs2/mdt.c index bd3e0f9144ff..8156d2b5ec6c 100644 --- a/fs/nilfs2/mdt.c +++ b/fs/nilfs2/mdt.c @@ -410,7 +410,7 @@ nilfs_mdt_write_page(struct page *page, struct writeback_control *wbc) * have dirty pages that try to be flushed in background. * So, here we simply discard this dirty page. */ - nilfs_clear_dirty_page(page, false); + nilfs_clear_dirty_page(page); unlock_page(page); return -EROFS; } @@ -632,10 +632,10 @@ void nilfs_mdt_restore_from_shadow_map(struct inode *inode) if (mi->mi_palloc_cache) nilfs_palloc_clear_cache(inode);
- nilfs_clear_dirty_pages(inode->i_mapping, true); + nilfs_clear_dirty_pages(inode->i_mapping); nilfs_copy_back_pages(inode->i_mapping, shadow->inode->i_mapping);
- nilfs_clear_dirty_pages(ii->i_assoc_inode->i_mapping, true); + nilfs_clear_dirty_pages(ii->i_assoc_inode->i_mapping); nilfs_copy_back_pages(ii->i_assoc_inode->i_mapping, NILFS_I(shadow->inode)->i_assoc_inode->i_mapping);
diff --git a/fs/nilfs2/page.c b/fs/nilfs2/page.c index d2d6d5c761e8..93f24fa3ab10 100644 --- a/fs/nilfs2/page.c +++ b/fs/nilfs2/page.c @@ -354,9 +354,8 @@ void nilfs_copy_back_pages(struct address_space *dmap, /** * nilfs_clear_dirty_pages - discard dirty pages in address space * @mapping: address space with dirty pages for discarding - * @silent: suppress [true] or print [false] warning messages */ -void nilfs_clear_dirty_pages(struct address_space *mapping, bool silent) +void nilfs_clear_dirty_pages(struct address_space *mapping) { struct pagevec pvec; unsigned int i; @@ -377,7 +376,7 @@ void nilfs_clear_dirty_pages(struct address_space *mapping, bool silent) * was acquired. Skip processing in that case. */ if (likely(page->mapping == mapping)) - nilfs_clear_dirty_page(page, silent); + nilfs_clear_dirty_page(page);
unlock_page(page); } @@ -389,19 +388,11 @@ void nilfs_clear_dirty_pages(struct address_space *mapping, bool silent) /** * nilfs_clear_dirty_page - discard dirty page * @page: dirty page that will be discarded - * @silent: suppress [true] or print [false] warning messages */ -void nilfs_clear_dirty_page(struct page *page, bool silent) +void nilfs_clear_dirty_page(struct page *page) { - struct inode *inode = page->mapping->host; - struct super_block *sb = inode->i_sb; - BUG_ON(!PageLocked(page));
- if (!silent) - nilfs_warn(sb, "discard dirty page: offset=%lld, ino=%lu", - page_offset(page), inode->i_ino); - ClearPageUptodate(page); ClearPageMappedToDisk(page); ClearPageChecked(page); @@ -417,11 +408,6 @@ void nilfs_clear_dirty_page(struct page *page, bool silent) bh = head = page_buffers(page); do { lock_buffer(bh); - if (!silent) - nilfs_warn(sb, - "discard dirty block: blocknr=%llu, size=%zu", - (u64)bh->b_blocknr, bh->b_size); - set_mask_bits(&bh->b_state, clear_bits, 0); unlock_buffer(bh); } while (bh = bh->b_this_page, bh != head); diff --git a/fs/nilfs2/page.h b/fs/nilfs2/page.h index 62b9bb469e92..a5b9b5a457ab 100644 --- a/fs/nilfs2/page.h +++ b/fs/nilfs2/page.h @@ -41,8 +41,8 @@ void nilfs_page_bug(struct page *);
int nilfs_copy_dirty_pages(struct address_space *, struct address_space *); void nilfs_copy_back_pages(struct address_space *, struct address_space *); -void nilfs_clear_dirty_page(struct page *, bool); -void nilfs_clear_dirty_pages(struct address_space *, bool); +void nilfs_clear_dirty_page(struct page *page); +void nilfs_clear_dirty_pages(struct address_space *mapping); void nilfs_mapping_init(struct address_space *mapping, struct inode *inode); unsigned int nilfs_page_count_clean_buffers(struct page *, unsigned int, unsigned int);
[ Sasha's backport helper bot ]
Hi,
The upstream commit SHA1 provided is correct: 299910dcb4525ac0274f3efa9527876315ba4f67
Status in newer kernel trees: 6.13.y | Present (exact SHA1) 6.12.y | Present (exact SHA1) 6.6.y | Not found 6.1.y | Not found 5.15.y | Not found
Note: The patch differs from the upstream commit: --- 1: 299910dcb4525 ! 1: 3d3719013c717 nilfs2: do not output warnings when clearing dirty buffers @@ Metadata ## Commit message ## nilfs2: do not output warnings when clearing dirty buffers
+ commit 299910dcb4525ac0274f3efa9527876315ba4f67 upstream. + After detecting file system corruption and degrading to a read-only mount, dirty folios and buffers in the page cache are cleared, and a large number of warnings are output at that time, often filling up the kernel log. @@ Commit message that suppresses the warning output, but since it is not currently used meaningfully, remove both the silent argument and the warning output.
+ [konishi.ryusuke@gmail.com: adjusted for page/folio conversion] Link: https://lkml.kernel.org/r/20240816090128.4561-1-konishi.ryusuke@gmail.com Signed-off-by: Ryusuke Konishi konishi.ryusuke@gmail.com Signed-off-by: Andrew Morton akpm@linux-foundation.org + Stable-dep-of: ca76bb226bf4 ("nilfs2: do not force clear folio if buffer is referenced")
## fs/nilfs2/inode.c ## @@ fs/nilfs2/inode.c: static int nilfs_writepages(struct address_space *mapping, @@ fs/nilfs2/inode.c: static int nilfs_writepage(struct page *page, struct writebac * have dirty pages that try to be flushed in background. * So, here we simply discard this dirty page. */ -- nilfs_clear_folio_dirty(folio, false); -+ nilfs_clear_folio_dirty(folio); - folio_unlock(folio); +- nilfs_clear_dirty_page(page, false); ++ nilfs_clear_dirty_page(page); + unlock_page(page); return -EROFS; }
## fs/nilfs2/mdt.c ## @@ fs/nilfs2/mdt.c: nilfs_mdt_write_page(struct page *page, struct writeback_control *wbc) - * have dirty folios that try to be flushed in background. - * So, here we simply discard this dirty folio. + * have dirty pages that try to be flushed in background. + * So, here we simply discard this dirty page. */ -- nilfs_clear_folio_dirty(folio, false); -+ nilfs_clear_folio_dirty(folio); - folio_unlock(folio); +- nilfs_clear_dirty_page(page, false); ++ nilfs_clear_dirty_page(page); + unlock_page(page); return -EROFS; } @@ fs/nilfs2/mdt.c: void nilfs_mdt_restore_from_shadow_map(struct inode *inode) @@ fs/nilfs2/page.c: void nilfs_copy_back_pages(struct address_space *dmap, -void nilfs_clear_dirty_pages(struct address_space *mapping, bool silent) +void nilfs_clear_dirty_pages(struct address_space *mapping) { - struct folio_batch fbatch; + struct pagevec pvec; unsigned int i; @@ fs/nilfs2/page.c: void nilfs_clear_dirty_pages(struct address_space *mapping, bool silent) * was acquired. Skip processing in that case. */ - if (likely(folio->mapping == mapping)) -- nilfs_clear_folio_dirty(folio, silent); -+ nilfs_clear_folio_dirty(folio); + if (likely(page->mapping == mapping)) +- nilfs_clear_dirty_page(page, silent); ++ nilfs_clear_dirty_page(page);
- folio_unlock(folio); + unlock_page(page); } @@ fs/nilfs2/page.c: void nilfs_clear_dirty_pages(struct address_space *mapping, bool silent) /** - * nilfs_clear_folio_dirty - discard dirty folio - * @folio: dirty folio that will be discarded + * nilfs_clear_dirty_page - discard dirty page + * @page: dirty page that will be discarded - * @silent: suppress [true] or print [false] warning messages */ --void nilfs_clear_folio_dirty(struct folio *folio, bool silent) -+void nilfs_clear_folio_dirty(struct folio *folio) +-void nilfs_clear_dirty_page(struct page *page, bool silent) ++void nilfs_clear_dirty_page(struct page *page) { -- struct inode *inode = folio->mapping->host; +- struct inode *inode = page->mapping->host; - struct super_block *sb = inode->i_sb; - struct buffer_head *bh, *head; - - BUG_ON(!folio_test_locked(folio)); +- + BUG_ON(!PageLocked(page));
- if (!silent) - nilfs_warn(sb, "discard dirty page: offset=%lld, ino=%lu", -- folio_pos(folio), inode->i_ino); +- page_offset(page), inode->i_ino); - - folio_clear_uptodate(folio); - folio_clear_mappedtodisk(folio); - -@@ fs/nilfs2/page.c: void nilfs_clear_folio_dirty(struct folio *folio, bool silent) - bh = head; + ClearPageUptodate(page); + ClearPageMappedToDisk(page); + ClearPageChecked(page); +@@ fs/nilfs2/page.c: void nilfs_clear_dirty_page(struct page *page, bool silent) + bh = head = page_buffers(page); do { lock_buffer(bh); - if (!silent) @@ fs/nilfs2/page.c: void nilfs_clear_folio_dirty(struct folio *folio, bool silent) } while (bh = bh->b_this_page, bh != head);
## fs/nilfs2/page.h ## -@@ fs/nilfs2/page.h: void nilfs_folio_bug(struct folio *); +@@ fs/nilfs2/page.h: void nilfs_page_bug(struct page *);
int nilfs_copy_dirty_pages(struct address_space *, struct address_space *); void nilfs_copy_back_pages(struct address_space *, struct address_space *); --void nilfs_clear_folio_dirty(struct folio *, bool); +-void nilfs_clear_dirty_page(struct page *, bool); -void nilfs_clear_dirty_pages(struct address_space *, bool); -+void nilfs_clear_folio_dirty(struct folio *folio); ++void nilfs_clear_dirty_page(struct page *page); +void nilfs_clear_dirty_pages(struct address_space *mapping); + void nilfs_mapping_init(struct address_space *mapping, struct inode *inode); unsigned int nilfs_page_count_clean_buffers(struct page *, unsigned int, unsigned int); - unsigned long nilfs_find_uncommitted_extent(struct inode *inode, ---
Results of testing on various branches:
| Branch | Patch Apply | Build Test | |---------------------------|-------------|------------| | stable/linux-5.15.y | Success | Failed | | stable/linux-5.10.y | Success | Failed | | stable/linux-5.4.y | Success | Failed |
Build Errors: Build error for stable/linux-5.15.y: ssh: connect to host 192.168.1.58 port 22: No route to host
Build error for stable/linux-5.10.y: ssh: connect to host 192.168.1.58 port 22: No route to host
Build error for stable/linux-5.4.y: ssh: connect to host 192.168.1.58 port 22: No route to host
commit ca76bb226bf47ff04c782cacbd299f12ddee1ec1 upstream.
Patch series "nilfs2: protect busy buffer heads from being force-cleared".
This series fixes the buffer head state inconsistency issues reported by syzbot that occurs when the filesystem is corrupted and falls back to read-only, and the associated buffer head use-after-free issue.
This patch (of 2):
Syzbot has reported that after nilfs2 detects filesystem corruption and falls back to read-only, inconsistencies in the buffer state may occur.
One of the inconsistencies is that when nilfs2 calls mark_buffer_dirty() to set a data or metadata buffer as dirty, but it detects that the buffer is not in the uptodate state:
WARNING: CPU: 0 PID: 6049 at fs/buffer.c:1177 mark_buffer_dirty+0x2e5/0x520 fs/buffer.c:1177 ... Call Trace: <TASK> nilfs_palloc_commit_alloc_entry+0x4b/0x160 fs/nilfs2/alloc.c:598 nilfs_ifile_create_inode+0x1dd/0x3a0 fs/nilfs2/ifile.c:73 nilfs_new_inode+0x254/0x830 fs/nilfs2/inode.c:344 nilfs_mkdir+0x10d/0x340 fs/nilfs2/namei.c:218 vfs_mkdir+0x2f9/0x4f0 fs/namei.c:4257 do_mkdirat+0x264/0x3a0 fs/namei.c:4280 __do_sys_mkdirat fs/namei.c:4295 [inline] __se_sys_mkdirat fs/namei.c:4293 [inline] __x64_sys_mkdirat+0x87/0xa0 fs/namei.c:4293 do_syscall_x64 arch/x86/entry/common.c:52 [inline] do_syscall_64+0xf3/0x230 arch/x86/entry/common.c:83 entry_SYSCALL_64_after_hwframe+0x77/0x7f
The other is when nilfs_btree_propagate(), which propagates the dirty state to the ancestor nodes of a b-tree that point to a dirty buffer, detects that the origin buffer is not dirty, even though it should be:
WARNING: CPU: 0 PID: 5245 at fs/nilfs2/btree.c:2089 nilfs_btree_propagate+0xc79/0xdf0 fs/nilfs2/btree.c:2089 ... Call Trace: <TASK> nilfs_bmap_propagate+0x75/0x120 fs/nilfs2/bmap.c:345 nilfs_collect_file_data+0x4d/0xd0 fs/nilfs2/segment.c:587 nilfs_segctor_apply_buffers+0x184/0x340 fs/nilfs2/segment.c:1006 nilfs_segctor_scan_file+0x28c/0xa50 fs/nilfs2/segment.c:1045 nilfs_segctor_collect_blocks fs/nilfs2/segment.c:1216 [inline] nilfs_segctor_collect fs/nilfs2/segment.c:1540 [inline] nilfs_segctor_do_construct+0x1c28/0x6b90 fs/nilfs2/segment.c:2115 nilfs_segctor_construct+0x181/0x6b0 fs/nilfs2/segment.c:2479 nilfs_segctor_thread_construct fs/nilfs2/segment.c:2587 [inline] nilfs_segctor_thread+0x69e/0xe80 fs/nilfs2/segment.c:2701 kthread+0x2f0/0x390 kernel/kthread.c:389 ret_from_fork+0x4b/0x80 arch/x86/kernel/process.c:147 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:244 </TASK>
Both of these issues are caused by the callbacks that handle the page/folio write requests, forcibly clear various states, including the working state of the buffers they hold, at unexpected times when they detect read-only fallback.
Fix these issues by checking if the buffer is referenced before clearing the page/folio state, and skipping the clear if it is.
[konishi.ryusuke@gmail.com: adjusted for page/folio conversion] Link: https://lkml.kernel.org/r/20250107200202.6432-1-konishi.ryusuke@gmail.com Link: https://lkml.kernel.org/r/20250107200202.6432-2-konishi.ryusuke@gmail.com Signed-off-by: Ryusuke Konishi konishi.ryusuke@gmail.com Reported-by: syzbot+b2b14916b77acf8626d7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b2b14916b77acf8626d7 Reported-by: syzbot+d98fd19acd08b36ff422@syzkaller.appspotmail.com Link: https://syzkaller.appspot.com/bug?extid=d98fd19acd08b36ff422 Fixes: 8c26c4e2694a ("nilfs2: fix issue with flush kernel thread after remount in RO mode because of driver's internal error or metadata corruption") Tested-by: syzbot+b2b14916b77acf8626d7@syzkaller.appspotmail.com Signed-off-by: Andrew Morton akpm@linux-foundation.org --- fs/nilfs2/page.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-)
diff --git a/fs/nilfs2/page.c b/fs/nilfs2/page.c index 93f24fa3ab10..ce5947cf4bd5 100644 --- a/fs/nilfs2/page.c +++ b/fs/nilfs2/page.c @@ -388,24 +388,44 @@ void nilfs_clear_dirty_pages(struct address_space *mapping) /** * nilfs_clear_dirty_page - discard dirty page * @page: dirty page that will be discarded + * + * nilfs_clear_dirty_page() clears working states including dirty state for + * the page and its buffers. If the page has buffers, clear only if it is + * confirmed that none of the buffer heads are busy (none have valid + * references and none are locked). */ void nilfs_clear_dirty_page(struct page *page) { BUG_ON(!PageLocked(page));
- ClearPageUptodate(page); - ClearPageMappedToDisk(page); - ClearPageChecked(page); - if (page_has_buffers(page)) { - struct buffer_head *bh, *head; + struct buffer_head *bh, *head = page_buffers(page); const unsigned long clear_bits = (BIT(BH_Uptodate) | BIT(BH_Dirty) | BIT(BH_Mapped) | BIT(BH_Async_Write) | BIT(BH_NILFS_Volatile) | BIT(BH_NILFS_Checked) | BIT(BH_NILFS_Redirected) | BIT(BH_Delay)); + bool busy, invalidated = false;
- bh = head = page_buffers(page); +recheck_buffers: + busy = false; + bh = head; + do { + if (atomic_read(&bh->b_count) | buffer_locked(bh)) { + busy = true; + break; + } + } while (bh = bh->b_this_page, bh != head); + + if (busy) { + if (invalidated) + return; + invalidate_bh_lrus(); + invalidated = true; + goto recheck_buffers; + } + + bh = head; do { lock_buffer(bh); set_mask_bits(&bh->b_state, clear_bits, 0); @@ -413,6 +433,9 @@ void nilfs_clear_dirty_page(struct page *page) } while (bh = bh->b_this_page, bh != head); }
+ ClearPageUptodate(page); + ClearPageMappedToDisk(page); + ClearPageChecked(page); __nilfs_clear_page_dirty(page); }
commit 367a9bffabe08c04f6d725032cce3d891b2b9e1a upstream.
nilfs_lookup_dirty_data_buffers(), which iterates through the buffers attached to dirty data folios/pages, accesses the attached buffers without locking the folios/pages.
For data cache, nilfs_clear_folio_dirty() may be called asynchronously when the file system degenerates to read only, so nilfs_lookup_dirty_data_buffers() still has the potential to cause use after free issues when buffers lose the protection of their dirty state midway due to this asynchronous clearing and are unintentionally freed by try_to_free_buffers().
Eliminate this race issue by adjusting the lock section in this function.
[konishi.ryusuke@gmail.com: adjusted for page/folio conversion] Link: https://lkml.kernel.org/r/20250107200202.6432-3-konishi.ryusuke@gmail.com Signed-off-by: Ryusuke Konishi konishi.ryusuke@gmail.com Fixes: 8c26c4e2694a ("nilfs2: fix issue with flush kernel thread after remount in RO mode because of driver's internal error or metadata corruption") Signed-off-by: Andrew Morton akpm@linux-foundation.org --- fs/nilfs2/segment.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/fs/nilfs2/segment.c b/fs/nilfs2/segment.c index 75fd6e86f18a..2f778234ecf2 100644 --- a/fs/nilfs2/segment.c +++ b/fs/nilfs2/segment.c @@ -732,7 +732,6 @@ static size_t nilfs_lookup_dirty_data_buffers(struct inode *inode, } if (!page_has_buffers(page)) create_empty_buffers(page, i_blocksize(inode), 0); - unlock_page(page);
bh = head = page_buffers(page); do { @@ -742,11 +741,14 @@ static size_t nilfs_lookup_dirty_data_buffers(struct inode *inode, list_add_tail(&bh->b_assoc_buffers, listp); ndirties++; if (unlikely(ndirties >= nlimit)) { + unlock_page(page); pagevec_release(&pvec); cond_resched(); return ndirties; } } while (bh = bh->b_this_page, bh != head); + + unlock_page(page); } pagevec_release(&pvec); cond_resched();
linux-stable-mirror@lists.linaro.org