[PATCH] ext4: cancel dirty accounting for folios without buffers

From: Zhu Jia

Date: Tue Jun 23 2026 - 05:53:44 EST


Since commit cc5095747edf ("ext4: don't BUG if someone dirty pages
without asking ext4 first"), mpage_prepare_extent_to_map() handles dirty
folios without buffer heads by warning, clearing PG_dirty, and skipping
them. ext4 cannot write these folios because there are no buffer heads to
map and submit.

That recovery leaves dirty accounting behind: folio_clear_dirty() clears
PG_dirty but does not undo the accounting charged when the folio was
dirtied. We have seen this in production as Dirty/nr_dirty staying high
while Writeback/nr_writeback and device write IO stayed near zero, with
many writer tasks blocked in balance_dirty_pages() throttling. Thus the
warning-and-skip recovery can still become a dirty-throttle DoS.

Use folio_cancel_dirty() so dropping PG_dirty also cancels the dirty
accounting.

Fixes: cc5095747edf ("ext4: don't BUG if someone dirty pages without asking ext4 first")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Zhu Jia <zhujia.zj@xxxxxxxxxxxxx>
---
fs/ext4/inode.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index c2c2d6ac7f3d1..7ea280e70c06e 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -2715,7 +2715,13 @@ static int mpage_prepare_extent_to_map(struct mpage_da_data *mpd)
*/
if (!folio_buffers(folio)) {
ext4_warning_inode(mpd->inode, "page %lu does not have buffers attached", folio->index);
- folio_clear_dirty(folio);
+ /*
+ * folio_cancel_dirty() pairs the dropped dirty
+ * state with dirty accounting, but leaves stale
+ * PAGECACHE_TAG_DIRTY/TOWRITE tags behind. Later
+ * writeback may rescan this clean folio.
+ */
+ folio_cancel_dirty(folio);
folio_unlock(folio);
continue;
}
--
2.20.1