From: Chao Yu <chao@kernel.org>
To: Haoran Wang <haoranwangsec@gmail.com>, jaegeuk@kernel.org
Cc: chao@kernel.org, linux-f2fs-devel@lists.linux.dev,
linux-kernel@vger.kernel.org, yi1.lai@intel.com
Subject: Re: [PATCH v2] f2fs: verify inode data-block ownership at mount
Date: Wed, 11 Nov 2026 15:14:34 +0800 [thread overview]
Message-ID: <8dd16c29-ed2a-418a-bd9a-4a9949cd53f1@kernel.org> (raw)
In-Reply-To: <20261109171601.75996-1-haoranwangsec@gmail.com>
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@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;
prev parent reply other threads:[~2026-11-11 7:14 UTC|newest]
Thread overview: 4+ 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 ` [PATCH v2] " Haoran Wang
2026-11-11 7:14 ` Chao Yu [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=8dd16c29-ed2a-418a-bd9a-4a9949cd53f1@kernel.org \
--to=chao@kernel.org \
--cc=haoranwangsec@gmail.com \
--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®