Re: [PATCH v2] f2fs: verify inode data-block ownership at mount
From: Chao Yu
Date: Wed Nov 11 2026 - 02:20:36 EST
Hi Haoran,
Thanks for the contribution.
On 11/9/26 17:16, Haoran Wang wrote:
> 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.
It seems quite heavy to load all valid nodes for verification whenever mount(),
it may slow down bootup time of filesystem significantly.
If storage device in special environment is not reliable, what do you think of
running fsck.f2fs to do a full check before mount()?
Thanks,
>
> 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@xxxxxxxxx>
> Closes: https://lore.kernel.org/oe-lkp/202610262143.a92547a9-lkp@xxxxxxxxx
> Signed-off-by: Haoran Wang <haoranwangsec@xxxxxxxxx>
>
> ---
> 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;