[PATCH] f2fs: fix to roll back SIT update on segment allocation failure
From: lizhijun3
Date: Thu Oct 22 2026 - 08:09:04 EST
In f2fs_allocate_data_block(), SIT is updated before segment rotation
since SSR needs the latest valid block information:
update_sit_entry(sbi, *new_blkaddr, 1);
update_sit_entry(sbi, old_blkaddr, -1);
If the subsequent segment rotation (new_curseg(), change_curseg() or
get_atssr_segment()) fails, it jumps to out_err and clears *new_blkaddr,
however, it does not roll back the two SIT updates, so that SIT may
record that:
- the new block is valid even if no data was written to it;
- the old block is free even if a node still refers to it.
Not every failure stops the checkpoint. For example, change_curseg()
returns PTR_ERR() if f2fs_get_sum_folio() fails (e.g. -ENOMEM), and this
path does not call f2fs_stop_checkpoint(), so the filesystem keeps
running. A later checkpoint can then persist the inconsistent SIT, and
the old block may be reallocated while being referenced, causing data
corruption. The same stale SIT state can also be exposed when pinned
allocation returns -EAGAIN and the caller retries the allocation.
Fix it by tracking whether SIT was updated and rolling back the two
update_sit_entry() calls in the error path.
Fixes: 7d009e048d7c ("f2fs: fix to handle segment allocation failure correctly")
Cc: stable@xxxxxxxxxxxxxxx
Co-developed-by: Chen Zhangqi <chenzhangqi@xxxxxxxxxx>
Signed-off-by: Chen Zhangqi <chenzhangqi@xxxxxxxxxx>
Signed-off-by: lizhijun3 <lizhijun3@xxxxxxxxxx>
---
fs/f2fs/segment.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c
index 63b712d3d599..033a3b8bf426 100644
--- a/fs/f2fs/segment.c
+++ b/fs/f2fs/segment.c
@@ -3908,6 +3908,7 @@ int f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct folio *folio,
bool from_gc = (type == CURSEG_ALL_DATA_ATGC);
struct seg_entry *se = NULL;
bool segment_full = false;
+ bool sit_updated = false;
int ret = 0;
f2fs_down_read(&SM_I(sbi)->curseg_lock);
@@ -3958,6 +3959,7 @@ int f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct folio *folio,
*/
update_sit_entry(sbi, *new_blkaddr, 1);
update_sit_entry(sbi, old_blkaddr, -1);
+ sit_updated = true;
/*
* If the current segment is full, flush it out and replace it with a
@@ -4027,6 +4029,14 @@ int f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct folio *folio,
return 0;
out_err:
+ /*
+ * SIT is updated before segment rotation for SSR. Roll it back if
+ * segment rotation fails.
+ */
+ if (sit_updated) {
+ update_sit_entry(sbi, *new_blkaddr, -1);
+ update_sit_entry(sbi, old_blkaddr, 1);
+ }
*new_blkaddr = NULL_ADDR;
up_write(&sit_i->sentry_lock);
mutex_unlock(&curseg->curseg_mutex);