* [PATCH 5.4 5.10 5.15 1/3] nilfs2: do not output warnings when clearing dirty buffers
2025-02-07 14:23 [PATCH 5.4 5.10 5.15 0/3] nilfs2: protect busy buffer heads from being force-cleared Ryusuke Konishi
@ 2025-02-07 14:23 ` Ryusuke Konishi
2025-02-07 22:51 ` Sasha Levin
2025-02-07 14:23 ` [PATCH 5.4 5.10 5.15 2/3] nilfs2: do not force clear folio if buffer is referenced Ryusuke Konishi
2025-02-07 14:23 ` [PATCH 5.4 5.10 5.15 3/3] nilfs2: protect access to buffers with no active references Ryusuke Konishi
2 siblings, 1 reply; 5+ messages in thread
From: Ryusuke Konishi @ 2025-02-07 14:23 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman; +Cc: Andrew Morton
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);
--
2.43.5
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 5.4 5.10 5.15 2/3] nilfs2: do not force clear folio if buffer is referenced
2025-02-07 14:23 [PATCH 5.4 5.10 5.15 0/3] nilfs2: protect busy buffer heads from being force-cleared Ryusuke Konishi
2025-02-07 14:23 ` [PATCH 5.4 5.10 5.15 1/3] nilfs2: do not output warnings when clearing dirty buffers Ryusuke Konishi
@ 2025-02-07 14:23 ` Ryusuke Konishi
2025-02-07 14:23 ` [PATCH 5.4 5.10 5.15 3/3] nilfs2: protect access to buffers with no active references Ryusuke Konishi
2 siblings, 0 replies; 5+ messages in thread
From: Ryusuke Konishi @ 2025-02-07 14:23 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman; +Cc: Andrew Morton
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);
}
--
2.43.5
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 5.4 5.10 5.15 3/3] nilfs2: protect access to buffers with no active references
2025-02-07 14:23 [PATCH 5.4 5.10 5.15 0/3] nilfs2: protect busy buffer heads from being force-cleared Ryusuke Konishi
2025-02-07 14:23 ` [PATCH 5.4 5.10 5.15 1/3] nilfs2: do not output warnings when clearing dirty buffers Ryusuke Konishi
2025-02-07 14:23 ` [PATCH 5.4 5.10 5.15 2/3] nilfs2: do not force clear folio if buffer is referenced Ryusuke Konishi
@ 2025-02-07 14:23 ` Ryusuke Konishi
2 siblings, 0 replies; 5+ messages in thread
From: Ryusuke Konishi @ 2025-02-07 14:23 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman; +Cc: Andrew Morton
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();
--
2.43.5
^ permalink raw reply related [flat|nested] 5+ messages in thread