mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Haoran Wang <haoranwangsec@gmail.com>
To: jaegeuk@kernel.org, chao@kernel.org
Cc: linux-f2fs-devel@lists.linux.dev, linux-kernel@vger.kernel.org,
	yi1.lai@intel.com, haoranwangsec@gmail.com
Subject: [PATCH v2] f2fs: verify inode data-block ownership at mount
Date: Mon, 09 Nov 2026 17:16:01 +0800	[thread overview]
Message-ID: <20261109171601.75996-1-haoranwangsec@gmail.com> (raw)
In-Reply-To: <6ad2359d.0083e9c2.97e0f.79de@mx.google.com>

At mount time, walk the NAT to enumerate live inode blocks and claim the
direct data blocks referenced by each regular inode's i_addr region in a
bitmap.  If two inodes claim the same data block, fail the mount with
-EFSCORRUPTED, since at least one of them is forged or corrupted.

The check is NAT-driven, so blocks unreachable from the NAT - such as
stale images left by earlier mkfs runs on untrimmed disks - are ignored
and cannot trigger false positives.

Reported-by: kernel test robot <yi1.lai@intel.com>
Closes: https://lore.kernel.org/oe-lkp/202610262143.a92547a9-lkp@intel.com
Signed-off-by: Haoran Wang <haoranwangsec@gmail.com>

---
Changes since v1: fix the false positive reported by the kernel test
robot: v1 scanned the whole main area with sb_bread and trusted stale
inode blocks left on disk by earlier mkfs runs, failing the mount.
Rework the scan to walk the NAT so only NAT-reachable inodes are checked.

 fs/f2fs/f2fs.h  |    1 +
 fs/f2fs/node.c  |   98 +++++++++++++++++++++++++++++++++++++++++++++++
 fs/f2fs/super.c |    5 ++
 3 files changed, 104 insertions(+)

--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -4000,6 +4000,7 @@
 bool f2fs_need_dentry_mark(struct f2fs_sb_info *sbi, nid_t nid);
 bool f2fs_is_checkpointed_node(struct f2fs_sb_info *sbi, nid_t nid);
 bool f2fs_need_inode_block_update(struct f2fs_sb_info *sbi, nid_t ino);
+int f2fs_verify_inode_data_ownership(struct f2fs_sb_info *sbi);
 int f2fs_get_node_info(struct f2fs_sb_info *sbi, nid_t nid,
 				struct node_info *ni, bool checkpoint_context);
 pgoff_t f2fs_get_next_page_offset(struct dnode_of_data *dn, pgoff_t pgofs);
--- a/fs/f2fs/node.c
+++ b/fs/f2fs/node.c
@@ -3455,6 +3455,111 @@
 	return 0;
 }
 
+
+static int f2fs_claim_data_addr(unsigned long *claimed, block_t main_b,
+		block_t total, block_t blkaddr)
+{
+	if (blkaddr == NULL_ADDR || blkaddr == NEW_ADDR)
+		return 0;
+	if (blkaddr < main_b || blkaddr >= total)
+		return 0;
+	if (test_and_set_bit(blkaddr - main_b, claimed))
+		return -EFSCORRUPTED;
+	return 0;
+}
+
+/*
+ * Verify that no data block in the main area is claimed by more than one
+ * regular inode.  Walk the NAT (the authoritative index of live node
+ * blocks) instead of scanning the raw main area, so stale node blocks
+ * left on disk by previous filesystem generations are ignored.  Only
+ * inodes reachable from the NAT are checked; inline-data files carry no
+ * separate data block and are out of scope.
+ */
+int f2fs_verify_inode_data_ownership(struct f2fs_sb_info *sbi)
+{
+	struct f2fs_nm_info *nm_i = NM_I(sbi);
+	struct f2fs_super_block *raw = F2FS_RAW_SUPER(sbi);
+	block_t main_b = le32_to_cpu(raw->main_blkaddr);
+	block_t total = le64_to_cpu(raw->block_count);
+	unsigned long *claimed;
+	struct f2fs_node *fn;
+	struct f2fs_inode *ri;
+	struct folio *folio;
+	unsigned long *bitmap_bits;
+	nid_t nid;
+	int err = 0;
+
+	bitmap_bits = kvzalloc_objs(*bitmap_bits,
+				      BITS_TO_LONGS(total - main_b),
+				      GFP_KERNEL);
+	if (!bitmap_bits)
+		return -ENOMEM;
+	claimed = bitmap_bits;
+
+	/* skip the f2fs-internal metadata inodes (NODE_INO, META_INO):
+	 * their NAT entries are not user data and trip
+	 * "inconsistent nat entry" on every lookup.
+	 */
+	for (nid = F2FS_ROOT_INO(sbi); nid < nm_i->max_nid; nid++) {
+		struct node_info ni;
+		int base, end, i;
+
+		/* only live inode blocks: NAT entry valid and nid == ino */
+		if (f2fs_get_node_info(sbi, nid, &ni, false))
+			continue;
+		if (ni.blk_addr == NULL_ADDR || ni.blk_addr == NEW_ADDR)
+			continue;
+		if (ni.ino != nid)
+			continue;
+
+		folio = f2fs_get_node_folio(sbi, nid, NODE_TYPE_INODE);
+		if (IS_ERR(folio)) {
+			err = PTR_ERR(folio);
+			if (err == -ENOENT)
+				err = 0;	/* node gone, skip */
+			else
+				break;
+			continue;
+		}
+		fn = F2FS_NODE(folio);
+		ri = &fn->i;
+		if (!S_ISREG(le16_to_cpu(ri->i_mode)))
+			goto next;
+		if (ri->i_inline & (F2FS_INLINE_DATA | F2FS_INLINE_DENTRY))
+			goto next;
+		if (le32_to_cpu(ri->i_flags) & F2FS_COMPR_FL)
+			goto next;
+		base = (ri->i_inline & F2FS_EXTRA_ATTR)
+			? le16_to_cpu(ri->i_extra_isize) / 4 : 0;
+		end = DEF_ADDRS_PER_INODE;
+		if (ri->i_inline & F2FS_INLINE_XATTR) {
+			if (f2fs_sb_has_flexible_inline_xattr(sbi) &&
+					(ri->i_inline & F2FS_EXTRA_ATTR))
+				end -= le16_to_cpu(ri->i_inline_xattr_size);
+			else
+				end -= DEFAULT_INLINE_XATTR_ADDRS;
+		}
+		if (base > end || end > DEF_ADDRS_PER_INODE)
+			goto next;
+		for (i = base; i < end; i++) {
+			err = f2fs_claim_data_addr(claimed, main_b, total,
+					le32_to_cpu(ri->i_addr[i]));
+			if (err) {
+				f2fs_err(sbi, "inode %u claims data block %u already owned by another inode",
+					 nid, le32_to_cpu(ri->i_addr[i]));
+				f2fs_folio_put(folio, true);
+				goto out;
+			}
+		}
+next:
+		f2fs_folio_put(folio, true);
+	}
+out:
+	kvfree(claimed);
+	return err;
+}
+
 int f2fs_build_node_manager(struct f2fs_sb_info *sbi)
 {
 	int err;
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -5372,6 +5372,12 @@
 	if (err)
 		goto free_root_inode;
 
+	err = f2fs_verify_inode_data_ownership(sbi);
+	if (err) {
+		f2fs_err(sbi, "data block referenced by multiple inodes");
+		goto free_root_inode;
+	}
+
 	err = f2fs_register_sysfs(sbi);
 	if (err)
 		goto free_compress_inode;

      parent reply	other threads:[~2026-11-09  9:16 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-16 14:32 [PATCH] " Haoran Wang
2026-10-27  2:14 ` kernel test robot
2026-11-09  9:16 ` Haoran Wang [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261109171601.75996-1-haoranwangsec@gmail.com \
    --to=haoranwangsec@gmail.com \
    --cc=chao@kernel.org \
    --cc=jaegeuk@kernel.org \
    --cc=linux-f2fs-devel@lists.linux.dev \
    --cc=linux-kernel@vger.kernel.org \
    --cc=yi1.lai@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®