lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <1764752179-1936-1-git-send-email-zhiguo.niu@unisoc.com>
Date: Wed, 3 Dec 2025 16:56:19 +0800
From: Zhiguo Niu <zhiguo.niu@...soc.com>
To: <jaegeuk@...nel.org>, <chao@...nel.org>
CC: <linux-f2fs-devel@...ts.sourceforge.net>, <linux-kernel@...r.kernel.org>,
        <niuzhiguo84@...il.com>, <zhiguo.niu@...soc.com>, <ke.wang@...soc.com>,
        <Hao_hao.Wang@...soc.com>
Subject: [PATCH RFC] f2fs: fix infinite foreground gc loop in f2fs_gc

I'm currently encountering the same issue as shown in
commit bbf9f7d90f21e ("f2fs: Fix indefinite loop in f2fs_gc()"),
but this commit only works when CONFIG_F2FS_CHECK_FS is enabled
and blkaddr check fails. This doesn't seem to cover all
!is_alive cases, and CONFIG_F2FS_CHECK_FS is currently disabled
on Android devices.

Here's the problem flow:
1. Some high-pressure read/write/random power-down tests corrupted
the content of the nid=4 entry in the NAT block. ino/bock_addr are
both corrupted to 0, and the mount log also indicates this.
crash_arm64> f2fs_nat_block ffffff8097bfe000 -x
      version = 0x0,
      ino = 0x3,
      block_addr = 0x86da9
    }, {
      version = 0x0,
      ino = 0x0,
      block_addr = 0x0
[    6.495406] F2FS-fs (dm-56): quota file may be corrupted, skip loading it
2. Insufficient free space triggers foreground garbage collection (GC) during boot.
crash_arm64> bt 1
PID: 1        TASK: ffffff80801cec00  CPU: 6    COMMAND: "init"
[ffffffc00806b870] rwsem_read_trylock at ffffffc00826d5c8
[ffffffc00806b980] gc_data_segment at ffffffc0088a2720
[ffffffc00806ba40] do_garbage_collect at ffffffc0088a21d8
[ffffffc00806bb60] f2fs_gc at ffffffc0088a17ac
[ffffffc00806bbf0] f2fs_balance_fs at ffffffc0088cbf44
[ffffffc00806bc20] f2fs_setattr at ffffffc00885e67c
[ffffffc00806bc70] notify_change at ffffffc008610ae0
[ffffffc00806bd30] chown_common at ffffffc0085c8a3c
[ffffffc00806bdb0] do_fchownat at ffffffc0085c9154
[ffffffc00806be00] __arm64_sys_fchownat at ffffffc0085c908c
[ffffffc00806be20] invoke_syscall at ffffffc0081221b4
[ffffffc00806be40] el0_svc_common at ffffffc008122114

Infinite GC loop causes critical processes to be blocked, preventing
the device from booting up properly.
3. The GC process enters an infinite loop because the victim segment
contains a data block belongs to nid=4, but the is_alive check fails
as the following calling flow:
is_alive->f2fs_get_node_page->__get_node_page->read_node_page return -ENOENT
This will prevent the data in this segment from being completely migraged out.
4. This segment has low cost, which is chosen for GC again in next time.

Although the problem should be addressed by finding the cause of
NAT block corruption, but this will prevent the device from booting up.
This patch records each `!is_alive` case as an invalid
segment to avoid selecting the same one in next time.
BTW, some debug information output has been enhanced in is_alive.

Cc: Sahitya Tummala <stummala@...eaurora.org>
Signed-off-by: Zhiguo Niu <zhiguo.niu@...soc.com>
---
 fs/f2fs/gc.c      | 32 +++++++++++++++-----------------
 fs/f2fs/segment.c |  6 ++----
 fs/f2fs/segment.h |  3 +--
 3 files changed, 18 insertions(+), 23 deletions(-)

diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c
index 384fa7e..a95ade9 100644
--- a/fs/f2fs/gc.c
+++ b/fs/f2fs/gc.c
@@ -872,7 +872,6 @@ int f2fs_get_victim(struct f2fs_sb_info *sbi, unsigned int *result,
 		p.offset = segno + p.ofs_unit;
 		nsearched++;
 
-#ifdef CONFIG_F2FS_CHECK_FS
 		/*
 		 * skip selecting the invalid segno (that is failed due to block
 		 * validity check failure during GC) to avoid endless GC loop in
@@ -880,7 +879,6 @@ int f2fs_get_victim(struct f2fs_sb_info *sbi, unsigned int *result,
 		 */
 		if (test_bit(segno, sm->invalid_segmap))
 			goto next;
-#endif
 
 		secno = GET_SEC_FROM_SEG(sbi, segno);
 
@@ -1145,16 +1143,19 @@ static bool is_alive(struct f2fs_sb_info *sbi, struct f2fs_summary *sum,
 	unsigned int ofs_in_node, max_addrs, base;
 	block_t source_blkaddr;
 
+	unsigned int segno = GET_SEGNO(sbi, blkaddr);
 	nid = le32_to_cpu(sum->nid);
 	ofs_in_node = le16_to_cpu(sum->ofs_in_node);
 
 	node_folio = f2fs_get_node_folio(sbi, nid, NODE_TYPE_REGULAR);
-	if (IS_ERR(node_folio))
-		return false;
+	if (IS_ERR(node_folio)) {
+		f2fs_err(sbi, "get_node_folio err(%ld) for nid(%u)", PTR_ERR(node_folio), nid);
+		goto check_invalid;
+	}
 
 	if (f2fs_get_node_info(sbi, nid, dni, false)) {
 		f2fs_folio_put(node_folio, true);
-		return false;
+		goto check_invalid;
 	}
 
 	if (sum->version != dni->version) {
@@ -1165,7 +1166,7 @@ static bool is_alive(struct f2fs_sb_info *sbi, struct f2fs_summary *sum,
 
 	if (f2fs_check_nid_range(sbi, dni->ino)) {
 		f2fs_folio_put(node_folio, true);
-		return false;
+		goto check_invalid;
 	}
 
 	if (IS_INODE(node_folio)) {
@@ -1180,7 +1181,7 @@ static bool is_alive(struct f2fs_sb_info *sbi, struct f2fs_summary *sum,
 		f2fs_err(sbi, "Inconsistent blkaddr offset: base:%u, ofs_in_node:%u, max:%u, ino:%u, nid:%u",
 			base, ofs_in_node, max_addrs, dni->ino, dni->nid);
 		f2fs_folio_put(node_folio, true);
-		return false;
+		goto check_invalid;
 	}
 
 	*nofs = ofs_of_node(node_folio);
@@ -1188,21 +1189,18 @@ static bool is_alive(struct f2fs_sb_info *sbi, struct f2fs_summary *sum,
 	f2fs_folio_put(node_folio, true);
 
 	if (source_blkaddr != blkaddr) {
-#ifdef CONFIG_F2FS_CHECK_FS
-		unsigned int segno = GET_SEGNO(sbi, blkaddr);
 		unsigned long offset = GET_BLKOFF_FROM_SEG0(sbi, blkaddr);
-
 		if (unlikely(check_valid_map(sbi, segno, offset))) {
-			if (!test_and_set_bit(segno, SIT_I(sbi)->invalid_segmap)) {
-				f2fs_err(sbi, "mismatched blkaddr %u (source_blkaddr %u) in seg %u",
-					 blkaddr, source_blkaddr, segno);
-				set_sbi_flag(sbi, SBI_NEED_FSCK);
-			}
+			f2fs_err(sbi, "mismatched blkaddr %u (source_blkaddr %u) in seg %u",
+				blkaddr, source_blkaddr, segno);
+			set_sbi_flag(sbi, SBI_NEED_FSCK);
+			goto check_invalid;
 		}
-#endif
-		return false;
 	}
 	return true;
+check_invalid:
+	set_bit(segno, SIT_I(sbi)->invalid_segmap);
+	return false;
 }
 
 static int ra_data_block(struct inode *inode, pgoff_t index)
diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c
index 8375dca..6a55e20 100644
--- a/fs/f2fs/segment.c
+++ b/fs/f2fs/segment.c
@@ -826,9 +826,7 @@ static void __remove_dirty_segment(struct f2fs_sb_info *sbi, unsigned int segno,
 		if (valid_blocks == 0) {
 			clear_bit(GET_SEC_FROM_SEG(sbi, segno),
 						dirty_i->victim_secmap);
-#ifdef CONFIG_F2FS_CHECK_FS
 			clear_bit(segno, SIT_I(sbi)->invalid_segmap);
-#endif
 		}
 		if (__is_large_section(sbi)) {
 			unsigned int secno = GET_SEC_FROM_SEG(sbi, segno);
@@ -4899,12 +4897,12 @@ static int build_sit_info(struct f2fs_sb_info *sbi)
 					sit_bitmap_size, GFP_KERNEL);
 	if (!sit_i->sit_bitmap_mir)
 		return -ENOMEM;
+#endif
 
 	sit_i->invalid_segmap = f2fs_kvzalloc(sbi,
 					main_bitmap_size, GFP_KERNEL);
 	if (!sit_i->invalid_segmap)
 		return -ENOMEM;
-#endif
 
 	sit_i->sit_base_addr = le32_to_cpu(raw_super->sit_blkaddr);
 	sit_i->sit_blocks = SEGS_TO_BLKS(sbi, sit_segs);
@@ -5862,8 +5860,8 @@ static void destroy_sit_info(struct f2fs_sb_info *sbi)
 	kfree(sit_i->sit_bitmap);
 #ifdef CONFIG_F2FS_CHECK_FS
 	kfree(sit_i->sit_bitmap_mir);
-	kvfree(sit_i->invalid_segmap);
 #endif
+	kvfree(sit_i->invalid_segmap);
 	kfree(sit_i);
 }
 
diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h
index 07dcbcb..2437a7e2 100644
--- a/fs/f2fs/segment.h
+++ b/fs/f2fs/segment.h
@@ -211,10 +211,9 @@ struct sit_info {
 	char *sit_bitmap;		/* SIT bitmap pointer */
 #ifdef CONFIG_F2FS_CHECK_FS
 	char *sit_bitmap_mir;		/* SIT bitmap mirror */
-
+#endif
 	/* bitmap of segments to be ignored by GC in case of errors */
 	unsigned long *invalid_segmap;
-#endif
 	unsigned int bitmap_size;	/* SIT bitmap size */
 
 	unsigned long *tmp_map;			/* bitmap for temporal use */
-- 
1.9.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ