AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/11 04:19 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": [
    "FS_VERITY",
    "XFS_FS",
    "XFS_ONLINE_SCRUB"
  ],
  "FocusSymbols": [
    "xfs_fsverity_begin_enable",
    "xfs_fsverity_end_enable",
    "xfs_fsverity_get_descriptor",
    "xfs_fsverity_write_descriptor",
    "xfs_fsverity_delete_metadata",
    "xfs_fsverity_cancel_unwritten",
    "xfs_fsverity_read_merkle",
    "xfs_fsverity_readahead_merkle_tree",
    "xfs_fsverity_write_merkle",
    "fsverity_pagecache_read",
    "xchk_inode_setup_verity",
    "xchk_inode_verity_broken",
    "xrep_inode_verity"
  ],
  "Reasoning": "This patch series implements fs-verity support for the XFS filesystem. It introduces new metadata handling, ioctl operations, and integration with XFS's iomap and scrub infrastructure. This is a significant functional addition to a core filesystem and should be heavily fuzzed to ensure data integrity and prevent vulnerabilities.",
  "WorthFuzzing": true
}

1/1 2026/07/11 04:19 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 5822d1cb1ed828799681e63747237e3695c38741\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sat Jul 11 04:19:05 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c\nindex 983365a7354168..bd79e343ae237f 100644\n--- a/fs/btrfs/verity.c\n+++ b/fs/btrfs/verity.c\n@@ -773,11 +773,15 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode,\n  * @buf:\tMerkle tree block to write\n  * @pos:\tthe position of the block in the Merkle tree (in bytes)\n  * @size:\tthe Merkle tree block size (in bytes)\n+ * @zero_digest:\tthe hash of the all-zeroes block\n+ * @digest_size:\tsize of zero_digest, in bytes\n  *\n  * Returns 0 on success or negative error code on failure\n  */\n static int btrfs_write_merkle_tree_block(struct file *file, const void *buf,\n-\t\t\t\t\t u64 pos, unsigned int size)\n+\t\t\t\t\t u64 pos, unsigned int size,\n+\t\t\t\t\t const u8 *zero_digest,\n+\t\t\t\t\t unsigned int digest_size)\n {\n \tstruct inode *inode = file_inode(file);\n \tloff_t merkle_pos = merkle_file_pos(inode);\ndiff --git a/fs/ext4/verity.c b/fs/ext4/verity.c\nindex ca61da53f313fa..ac5c133f552925 100644\n--- a/fs/ext4/verity.c\n+++ b/fs/ext4/verity.c\n@@ -34,32 +34,6 @@ static inline loff_t ext4_verity_metadata_pos(const struct inode *inode)\n \treturn round_up(inode-\u003ei_size, 65536);\n }\n \n-/*\n- * Read some verity metadata from the inode.  __vfs_read() can't be used because\n- * we need to read beyond i_size.\n- */\n-static int pagecache_read(struct inode *inode, void *buf, size_t count,\n-\t\t\t  loff_t pos)\n-{\n-\twhile (count) {\n-\t\tstruct folio *folio;\n-\t\tsize_t n;\n-\n-\t\tfolio = read_mapping_folio(inode-\u003ei_mapping, pos \u003e\u003e PAGE_SHIFT,\n-\t\t\t\t\t NULL);\n-\t\tif (IS_ERR(folio))\n-\t\t\treturn PTR_ERR(folio);\n-\n-\t\tn = memcpy_from_file_folio(buf, folio, pos, count);\n-\t\tfolio_put(folio);\n-\n-\t\tbuf += n;\n-\t\tpos += n;\n-\t\tcount -= n;\n-\t}\n-\treturn 0;\n-}\n-\n /*\n  * Write some verity metadata to the inode for FS_IOC_ENABLE_VERITY.\n  * kernel_write() can't be used because the file descriptor is readonly.\n@@ -311,8 +285,8 @@ static int ext4_get_verity_descriptor_location(struct inode *inode,\n \t\tgoto bad;\n \tdesc_size_pos -= sizeof(desc_size_disk);\n \n-\terr = pagecache_read(inode, \u0026desc_size_disk, sizeof(desc_size_disk),\n-\t\t\t     desc_size_pos);\n+\terr = fsverity_pagecache_read(inode, \u0026desc_size_disk,\n+\t\t\t\t      sizeof(desc_size_disk), desc_size_pos);\n \tif (err)\n \t\treturn err;\n \tdesc_size = le32_to_cpu(desc_size_disk);\n@@ -352,7 +326,7 @@ static int ext4_get_verity_descriptor(struct inode *inode, void *buf,\n \tif (buf_size) {\n \t\tif (desc_size \u003e buf_size)\n \t\t\treturn -ERANGE;\n-\t\terr = pagecache_read(inode, buf, desc_size, desc_pos);\n+\t\terr = fsverity_pagecache_read(inode, buf, desc_size, desc_pos);\n \t\tif (err)\n \t\t\treturn err;\n \t}\n@@ -374,7 +348,9 @@ static void ext4_readahead_merkle_tree(struct inode *inode, pgoff_t index,\n }\n \n static int ext4_write_merkle_tree_block(struct file *file, const void *buf,\n-\t\t\t\t\tu64 pos, unsigned int size)\n+\t\t\t\t\tu64 pos, unsigned int size,\n+\t\t\t\t\tconst u8 *zero_digest,\n+\t\t\t\t\tunsigned int digest_size)\n {\n \tpos += ext4_verity_metadata_pos(file_inode(file));\n \ndiff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c\nindex 39f4825154452a..f3a4617656db9b 100644\n--- a/fs/f2fs/verity.c\n+++ b/fs/f2fs/verity.c\n@@ -37,34 +37,6 @@ static inline loff_t f2fs_verity_metadata_pos(const struct inode *inode)\n \treturn round_up(inode-\u003ei_size, 65536);\n }\n \n-/*\n- * Read some verity metadata from the inode.  __vfs_read() can't be used because\n- * we need to read beyond i_size.\n- */\n-static int pagecache_read(struct inode *inode, void *buf, size_t count,\n-\t\t\t  loff_t pos)\n-{\n-\twhile (count) {\n-\t\tsize_t n = min_t(size_t, count,\n-\t\t\t\t PAGE_SIZE - offset_in_page(pos));\n-\t\tstruct page *page;\n-\n-\t\tpage = read_mapping_page(inode-\u003ei_mapping, pos \u003e\u003e PAGE_SHIFT,\n-\t\t\t\t\t NULL);\n-\t\tif (IS_ERR(page))\n-\t\t\treturn PTR_ERR(page);\n-\n-\t\tmemcpy_from_page(buf, page, offset_in_page(pos), n);\n-\n-\t\tput_page(page);\n-\n-\t\tbuf += n;\n-\t\tpos += n;\n-\t\tcount -= n;\n-\t}\n-\treturn 0;\n-}\n-\n /*\n  * Write some verity metadata to the inode for FS_IOC_ENABLE_VERITY.\n  * kernel_write() can't be used because the file descriptor is readonly.\n@@ -250,7 +222,7 @@ static int f2fs_get_verity_descriptor(struct inode *inode, void *buf,\n \tif (buf_size) {\n \t\tif (size \u003e buf_size)\n \t\t\treturn -ERANGE;\n-\t\tres = pagecache_read(inode, buf, size, pos);\n+\t\tres = fsverity_pagecache_read(inode, buf, size, pos);\n \t\tif (res)\n \t\t\treturn res;\n \t}\n@@ -272,7 +244,9 @@ static void f2fs_readahead_merkle_tree(struct inode *inode, pgoff_t index,\n }\n \n static int f2fs_write_merkle_tree_block(struct file *file, const void *buf,\n-\t\t\t\t\tu64 pos, unsigned int size)\n+\t\t\t\t\tu64 pos, unsigned int size,\n+\t\t\t\t\tconst u8 *zero_digest,\n+\t\t\t\t\tunsigned int digest_size)\n {\n \tpos += f2fs_verity_metadata_pos(file_inode(file));\n \ndiff --git a/fs/file_attr.c b/fs/file_attr.c\nindex bfb00d256dd560..5424ec4e39493f 100644\n--- a/fs/file_attr.c\n+++ b/fs/file_attr.c\n@@ -246,6 +246,11 @@ static int fileattr_set_prepare(struct inode *inode,\n \tif (fa-\u003efsx_cowextsize == 0)\n \t\tfa-\u003efsx_xflags \u0026= ~FS_XFLAG_COWEXTSIZE;\n \n+\t/* Can not enable DAX on fsverity file */\n+\tif ((old_ma-\u003efsx_xflags \u0026 FS_XFLAG_VERITY) \u0026\u0026\n+\t\t\tfa-\u003efsx_xflags \u0026 FS_XFLAG_DAX)\n+\t\treturn -EINVAL;\n+\n \treturn 0;\n }\n \ndiff --git a/fs/verity/enable.c b/fs/verity/enable.c\nindex 42dfed1ce0ce66..ad4ff71d7dd95b 100644\n--- a/fs/verity/enable.c\n+++ b/fs/verity/enable.c\n@@ -50,7 +50,9 @@ static int write_merkle_tree_block(struct file *file, const u8 *buf,\n \tint err;\n \n \terr = inode-\u003ei_sb-\u003es_vop-\u003ewrite_merkle_tree_block(file, buf, pos,\n-\t\t\t\t\t\t\t  params-\u003eblock_size);\n+\t\t\t\t\t\t\t  params-\u003eblock_size,\n+\t\t\t\t\t\t\t  params-\u003ezero_digest,\n+\t\t\t\t\t\t\t  params-\u003edigest_size);\n \tif (err)\n \t\tfsverity_err(inode, \"Error %d writing Merkle tree block %lu\",\n \t\t\t     err, index);\ndiff --git a/fs/verity/open.c b/fs/verity/open.c\nindex d0c56a7faa3bb1..875e8850ccbace 100644\n--- a/fs/verity/open.c\n+++ b/fs/verity/open.c\n@@ -347,7 +347,24 @@ int fsverity_get_descriptor(struct inode *inode,\n \treturn 0;\n }\n \n-static int ensure_verity_info(struct inode *inode)\n+/**\n+ * fsverity_ensure_verity_info() - cache verity info if it's not already cached\n+ * @inode: the inode for which verity info should be cached\n+ *\n+ * Ensure this inode has verity info attached to it, it's assumed the inode\n+ * already has fsverity enabled. Read fsverity descriptor and creates verity\n+ * based on that.\n+ *\n+ * This needs to be called at least once before any of the inode's data\n+ * can be verified (and thus read at all) or the inode's fsverity digest\n+ * retrieved.  fsverity_file_open() calls this already, which handles\n+ * normal file accesses.  If a filesystem does any internal (i.e. not\n+ * associated with a file descriptor) reads of the file's data or\n+ * fsverity digest, it must call this explicitly before doing so.\n+ *\n+ * Return: 0 on success, -errno on failure\n+ */\n+int fsverity_ensure_verity_info(struct inode *inode)\n {\n \tstruct fsverity_info *vi = fsverity_get_info(inode), *found;\n \tstruct fsverity_descriptor *desc;\n@@ -383,12 +400,13 @@ static int ensure_verity_info(struct inode *inode)\n \tkfree(desc);\n \treturn err;\n }\n+EXPORT_SYMBOL_GPL(fsverity_ensure_verity_info);\n \n int __fsverity_file_open(struct inode *inode, struct file *filp)\n {\n \tif (filp-\u003ef_mode \u0026 FMODE_WRITE)\n \t\treturn -EPERM;\n-\treturn ensure_verity_info(inode);\n+\treturn fsverity_ensure_verity_info(inode);\n }\n EXPORT_SYMBOL_GPL(__fsverity_file_open);\n \ndiff --git a/fs/verity/pagecache.c b/fs/verity/pagecache.c\nindex 99f5f53eea985f..270abd652e48c6 100644\n--- a/fs/verity/pagecache.c\n+++ b/fs/verity/pagecache.c\n@@ -68,13 +68,64 @@ EXPORT_SYMBOL_GPL(generic_readahead_merkle_tree);\n void fsverity_fill_zerohash(struct folio *folio, size_t offset, size_t len,\n \t\t\t      struct fsverity_info *vi)\n {\n-\tsize_t off = offset;\n+\tvoid *vaddr;\n+\tvoid *to;\n+\tsize_t chunk;\n \n \tWARN_ON_ONCE(!IS_ALIGNED(offset, vi-\u003etree_params.digest_size));\n \tWARN_ON_ONCE(!IS_ALIGNED(len, vi-\u003etree_params.digest_size));\n+\tWARN_ON_ONCE(offset + len \u003e folio_size(folio));\n \n-\tfor (; off \u003c (offset + len); off += vi-\u003etree_params.digest_size)\n-\t\tmemcpy_to_folio(folio, off, vi-\u003etree_params.zero_digest,\n+\tdo {\n+\t\tvaddr = kmap_local_folio(folio, offset);\n+\t\tchunk = len;\n+\t\tto = vaddr;\n+\n+\t\tif (folio_test_partial_kmap(folio) \u0026\u0026\n+\t\t    chunk \u003e PAGE_SIZE - offset_in_page(offset))\n+\t\t\tchunk = PAGE_SIZE - offset_in_page(offset);\n+\t\tfor (; to \u003c (vaddr + chunk); to += vi-\u003etree_params.digest_size)\n+\t\t\tmemcpy(to, vi-\u003etree_params.zero_digest,\n \t\t\t\tvi-\u003etree_params.digest_size);\n+\t\tkunmap_local(vaddr);\n+\n+\t\toffset += chunk;\n+\t\tlen -= chunk;\n+\t} while (len \u003e 0);\n+\n+\tflush_dcache_folio(folio);\n }\n EXPORT_SYMBOL_GPL(fsverity_fill_zerohash);\n+\n+/**\n+ * fsverity_pagecache_read() - read page and copy data to buffer\n+ * @inode:\tcopy from this inode's address space\n+ * @buf:\tbuffer to copy to\n+ * @count:\tnumber of bytes to copy\n+ * @pos:\tposition of the folio to copy from\n+ *\n+ * Read some verity metadata from the inode.  __vfs_read() can't be used because\n+ * we need to read beyond i_size.\n+ */\n+int fsverity_pagecache_read(struct inode *inode, void *buf, size_t count,\n+\t\t\t  loff_t pos)\n+{\n+\twhile (count) {\n+\t\tstruct folio *folio;\n+\t\tsize_t n;\n+\n+\t\tfolio = read_mapping_folio(inode-\u003ei_mapping, pos \u003e\u003e PAGE_SHIFT,\n+\t\t\t\t\t NULL);\n+\t\tif (IS_ERR(folio))\n+\t\t\treturn PTR_ERR(folio);\n+\n+\t\tn = memcpy_from_file_folio(buf, folio, pos, count);\n+\t\tfolio_put(folio);\n+\n+\t\tbuf += n;\n+\t\tpos += n;\n+\t\tcount -= n;\n+\t}\n+\treturn 0;\n+}\n+EXPORT_SYMBOL_GPL(fsverity_pagecache_read);\ndiff --git a/fs/verity/verify.c b/fs/verity/verify.c\nindex 4004a1d428751f..db8c350234bb71 100644\n--- a/fs/verity/verify.c\n+++ b/fs/verity/verify.c\n@@ -9,6 +9,7 @@\n \n #include \u003clinux/bio.h\u003e\n #include \u003clinux/export.h\u003e\n+#include \u003clinux/fserror.h\u003e\n \n #define FS_VERITY_MAX_PENDING_BLOCKS 2\n \n@@ -205,6 +206,8 @@ static bool verify_data_block(struct fsverity_info *vi,\n \t\tif (memchr_inv(dblock-\u003edata, 0, params-\u003eblock_size)) {\n \t\t\tfsverity_err(inode,\n \t\t\t\t     \"FILE CORRUPTED!  Data past EOF is not zeroed\");\n+\t\t\tfserror_report_data_lost(inode, data_pos,\n+\t\t\t\t\t\t params-\u003eblock_size, GFP_NOFS);\n \t\t\treturn false;\n \t\t}\n \t\treturn true;\n@@ -312,6 +315,7 @@ static bool verify_data_block(struct fsverity_info *vi,\n \t\tdata_pos, level - 1, params-\u003ehash_alg-\u003ename, hsize, want_hash,\n \t\tparams-\u003ehash_alg-\u003ename, hsize,\n \t\tlevel == 0 ? dblock-\u003ereal_hash : real_hash);\n+\tfserror_report_data_lost(inode, data_pos, params-\u003eblock_size, GFP_NOFS);\n error:\n \tfor (; level \u003e 0; level--) {\n \t\tkunmap_local(hblocks[level - 1].addr);\ndiff --git a/fs/xfs/Makefile b/fs/xfs/Makefile\nindex 9f7133e025768d..38b7f51e5d8405 100644\n--- a/fs/xfs/Makefile\n+++ b/fs/xfs/Makefile\n@@ -149,6 +149,7 @@ xfs-$(CONFIG_XFS_POSIX_ACL)\t+= xfs_acl.o\n xfs-$(CONFIG_SYSCTL)\t\t+= xfs_sysctl.o\n xfs-$(CONFIG_COMPAT)\t\t+= xfs_ioctl32.o\n xfs-$(CONFIG_EXPORTFS_BLOCK_OPS)\t+= xfs_pnfs.o\n+xfs-$(CONFIG_FS_VERITY)\t\t+= xfs_fsverity.o\n \n # notify failure\n ifeq ($(CONFIG_MEMORY_FAILURE),y)\ndiff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c\nindex d64defeda645ed..cc48f6e20e8000 100644\n--- a/fs/xfs/libxfs/xfs_bmap.c\n+++ b/fs/xfs/libxfs/xfs_bmap.c\n@@ -41,6 +41,8 @@\n #include \"xfs_inode_util.h\"\n #include \"xfs_rtgroup.h\"\n #include \"xfs_zone_alloc.h\"\n+#include \"xfs_fsverity.h\"\n+#include \u003clinux/fsverity.h\u003e\n \n struct kmem_cache\t\t*xfs_bmap_intent_cache;\n \n@@ -4402,6 +4404,10 @@ xfs_bmapi_convert_one_delalloc(\n \t * the extent.  Just return the real extent at this offset.\n \t */\n \tif (!isnullstartblock(bma.got.br_startblock)) {\n+\t\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) \u0026\u0026\n+\t\t    XFS_FSB_TO_B(mp, bma.got.br_startoff) \u003e=\n+\t\t\t    xfs_fsverity_metadata_offset(ip))\n+\t\t\tflags |= IOMAP_F_FSVERITY;\n \t\txfs_bmbt_to_iomap(ip, iomap, \u0026bma.got, 0, flags,\n \t\t\t\txfs_iomap_inode_sequence(ip, flags));\n \t\tif (seq)\n@@ -4449,6 +4455,11 @@ xfs_bmapi_convert_one_delalloc(\n \tXFS_STATS_ADD(mp, xs_xstrat_bytes, XFS_FSB_TO_B(mp, bma.length));\n \tXFS_STATS_INC(mp, xs_xstrat_quick);\n \n+\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) \u0026\u0026\n+\t    XFS_FSB_TO_B(mp, bma.got.br_startoff) \u003e=\n+\t\t    xfs_fsverity_metadata_offset(ip))\n+\t\tflags |= IOMAP_F_FSVERITY;\n+\n \tASSERT(!isnullstartblock(bma.got.br_startblock));\n \txfs_bmbt_to_iomap(ip, iomap, \u0026bma.got, 0, flags,\n \t\t\t\txfs_iomap_inode_sequence(ip, flags));\ndiff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h\nindex dd0ed046fbe9c9..2878bc9cb6564e 100644\n--- a/fs/xfs/libxfs/xfs_format.h\n+++ b/fs/xfs/libxfs/xfs_format.h\n@@ -374,11 +374,13 @@ xfs_sb_has_compat_feature(\n #define XFS_SB_FEAT_RO_COMPAT_RMAPBT   (1 \u003c\u003c 1)\t\t/* reverse map btree */\n #define XFS_SB_FEAT_RO_COMPAT_REFLINK  (1 \u003c\u003c 2)\t\t/* reflinked files */\n #define XFS_SB_FEAT_RO_COMPAT_INOBTCNT (1 \u003c\u003c 3)\t\t/* inobt block counts */\n+#define XFS_SB_FEAT_RO_COMPAT_VERITY   (1 \u003c\u003c 4)\t\t/* fs-verity */\n #define XFS_SB_FEAT_RO_COMPAT_ALL \\\n \t\t(XFS_SB_FEAT_RO_COMPAT_FINOBT | \\\n \t\t XFS_SB_FEAT_RO_COMPAT_RMAPBT | \\\n-\t\t XFS_SB_FEAT_RO_COMPAT_REFLINK| \\\n-\t\t XFS_SB_FEAT_RO_COMPAT_INOBTCNT)\n+\t\t XFS_SB_FEAT_RO_COMPAT_REFLINK | \\\n+\t\t XFS_SB_FEAT_RO_COMPAT_INOBTCNT | \\\n+\t\t XFS_SB_FEAT_RO_COMPAT_VERITY)\n #define XFS_SB_FEAT_RO_COMPAT_UNKNOWN\t~XFS_SB_FEAT_RO_COMPAT_ALL\n static inline bool\n xfs_sb_has_ro_compat_feature(\n@@ -1230,16 +1232,21 @@ static inline void xfs_dinode_put_rdev(struct xfs_dinode *dip, xfs_dev_t rdev)\n  */\n #define XFS_DIFLAG2_METADATA_BIT\t5\n \n+/* inodes sealed with fs-verity */\n+#define XFS_DIFLAG2_VERITY_BIT\t\t6\n+\n #define XFS_DIFLAG2_DAX\t\t(1ULL \u003c\u003c XFS_DIFLAG2_DAX_BIT)\n #define XFS_DIFLAG2_REFLINK\t(1ULL \u003c\u003c XFS_DIFLAG2_REFLINK_BIT)\n #define XFS_DIFLAG2_COWEXTSIZE\t(1ULL \u003c\u003c XFS_DIFLAG2_COWEXTSIZE_BIT)\n #define XFS_DIFLAG2_BIGTIME\t(1ULL \u003c\u003c XFS_DIFLAG2_BIGTIME_BIT)\n #define XFS_DIFLAG2_NREXT64\t(1ULL \u003c\u003c XFS_DIFLAG2_NREXT64_BIT)\n #define XFS_DIFLAG2_METADATA\t(1ULL \u003c\u003c XFS_DIFLAG2_METADATA_BIT)\n+#define XFS_DIFLAG2_VERITY\t(1ULL \u003c\u003c XFS_DIFLAG2_VERITY_BIT)\n \n #define XFS_DIFLAG2_ANY \\\n \t(XFS_DIFLAG2_DAX | XFS_DIFLAG2_REFLINK | XFS_DIFLAG2_COWEXTSIZE | \\\n-\t XFS_DIFLAG2_BIGTIME | XFS_DIFLAG2_NREXT64 | XFS_DIFLAG2_METADATA)\n+\t XFS_DIFLAG2_BIGTIME | XFS_DIFLAG2_NREXT64 | XFS_DIFLAG2_METADATA | \\\n+\t XFS_DIFLAG2_VERITY)\n \n static inline bool xfs_dinode_has_bigtime(const struct xfs_dinode *dip)\n {\n@@ -2027,4 +2034,26 @@ struct xfs_acl {\n #define SGI_ACL_FILE_SIZE\t(sizeof(SGI_ACL_FILE)-1)\n #define SGI_ACL_DEFAULT_SIZE\t(sizeof(SGI_ACL_DEFAULT)-1)\n \n+/*\n+ * At maximum of 8 levels with 128 hashes per block (32 bytes SHA-256) maximum\n+ * tree size is ((128^8 − 1)/(128 − 1)) = 567*10^12 blocks. This should fit in\n+ * 53 bits address space.\n+ *\n+ * At this Merkle tree size we can cover 295EB large file. This is much larger\n+ * than the currently supported file size.\n+ *\n+ * For sha512 the largest file we can cover ends at 1 \u003c\u003c 50 offset, this is also\n+ * good.\n+ */\n+#define XFS_FSVERITY_LARGEST_FILE\t((loff_t)1ULL \u003c\u003c 53)\n+\n+/*\n+ * Alignment of the fsverity metadata placement. This is largest supported PAGE\n+ * SIZE for fsverity. This is used to space out data and metadata in page cache.\n+ * The spacing is necessary for non-exposure of metadata to userspace and\n+ * correct merkle tree synethesis in the iomap.\n+ */\n+#define XFS_FSVERITY_START_ALIGN\t(65536)\n+\n+\n #endif /* __XFS_FORMAT_H__ */\ndiff --git a/fs/xfs/libxfs/xfs_fs.h b/fs/xfs/libxfs/xfs_fs.h\nindex 185f09f327c00c..84d62b7506a9ad 100644\n--- a/fs/xfs/libxfs/xfs_fs.h\n+++ b/fs/xfs/libxfs/xfs_fs.h\n@@ -250,6 +250,7 @@ typedef struct xfs_fsop_resblks {\n #define XFS_FSOP_GEOM_FLAGS_PARENT\t(1 \u003c\u003c 25) /* linux parent pointers */\n #define XFS_FSOP_GEOM_FLAGS_METADIR\t(1 \u003c\u003c 26) /* metadata directories */\n #define XFS_FSOP_GEOM_FLAGS_ZONED\t(1 \u003c\u003c 27) /* zoned rt device */\n+#define XFS_FSOP_GEOM_FLAGS_VERITY\t(1 \u003c\u003c 28) /* fs-verity */\n \n /*\n  * Minimum and maximum sizes need for growth checks.\n@@ -421,6 +422,7 @@ struct xfs_bulkstat {\n #define XFS_BS_SICK_SYMLINK\t(1 \u003c\u003c 6)  /* symbolic link remote target */\n #define XFS_BS_SICK_PARENT\t(1 \u003c\u003c 7)  /* parent pointers */\n #define XFS_BS_SICK_DIRTREE\t(1 \u003c\u003c 8)  /* directory tree structure */\n+#define XFS_BS_SICK_FSVERITY\t(1 \u003c\u003c 9)  /* fsverity metadata */\n \n /*\n  * Project quota id helpers (previously projid was 16bit only\ndiff --git a/fs/xfs/libxfs/xfs_health.h b/fs/xfs/libxfs/xfs_health.h\nindex 1d45cf5789e864..932b447190da76 100644\n--- a/fs/xfs/libxfs/xfs_health.h\n+++ b/fs/xfs/libxfs/xfs_health.h\n@@ -104,6 +104,7 @@ struct xfs_rtgroup;\n /* Don't propagate sick status to ag health summary during inactivation */\n #define XFS_SICK_INO_FORGET\t(1 \u003c\u003c 12)\n #define XFS_SICK_INO_DIRTREE\t(1 \u003c\u003c 13)  /* directory tree structure */\n+#define XFS_SICK_INO_FSVERITY\t(1 \u003c\u003c 14)  /* fsverity metadata */\n \n /* Primary evidence of health problems in a given group. */\n #define XFS_SICK_FS_PRIMARY\t(XFS_SICK_FS_COUNTERS | \\\n@@ -140,7 +141,8 @@ struct xfs_rtgroup;\n \t\t\t\t XFS_SICK_INO_XATTR | \\\n \t\t\t\t XFS_SICK_INO_SYMLINK | \\\n \t\t\t\t XFS_SICK_INO_PARENT | \\\n-\t\t\t\t XFS_SICK_INO_DIRTREE)\n+\t\t\t\t XFS_SICK_INO_DIRTREE | \\\n+\t\t\t\t XFS_SICK_INO_FSVERITY)\n \n #define XFS_SICK_INO_ZAPPED\t(XFS_SICK_INO_BMBTD_ZAPPED | \\\n \t\t\t\t XFS_SICK_INO_BMBTA_ZAPPED | \\\ndiff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c\nindex 336ef843f2fe4f..bd80a19f2e0929 100644\n--- a/fs/xfs/libxfs/xfs_inode_buf.c\n+++ b/fs/xfs/libxfs/xfs_inode_buf.c\n@@ -760,6 +760,14 @@ xfs_dinode_verify(\n \t    !xfs_has_rtreflink(mp))\n \t\treturn __this_address;\n \n+\t/* only regular files can have fsverity */\n+\tif (flags2 \u0026 XFS_DIFLAG2_VERITY) {\n+\t\tif (!xfs_has_verity(mp))\n+\t\t\treturn __this_address;\n+\t\tif (!S_ISREG(mode))\n+\t\t\treturn __this_address;\n+\t}\n+\n \tif (xfs_has_zoned(mp) \u0026\u0026\n \t    dip-\u003edi_metatype == cpu_to_be16(XFS_METAFILE_RTRMAP)) {\n \t\tif (be32_to_cpu(dip-\u003edi_used_blocks) \u003e mp-\u003em_sb.sb_rgextents)\ndiff --git a/fs/xfs/libxfs/xfs_inode_util.c b/fs/xfs/libxfs/xfs_inode_util.c\nindex 258ac3d0d486e6..5a516bd31c3175 100644\n--- a/fs/xfs/libxfs/xfs_inode_util.c\n+++ b/fs/xfs/libxfs/xfs_inode_util.c\n@@ -74,7 +74,8 @@ xfs_flags2diflags2(\n \tuint64_t\t\tdi_flags2 =\n \t\t(ip-\u003ei_diflags2 \u0026 (XFS_DIFLAG2_REFLINK |\n \t\t\t\t   XFS_DIFLAG2_BIGTIME |\n-\t\t\t\t   XFS_DIFLAG2_NREXT64));\n+\t\t\t\t   XFS_DIFLAG2_NREXT64 |\n+\t\t\t\t   XFS_DIFLAG2_VERITY));\n \n \tif (xflags \u0026 FS_XFLAG_DAX)\n \t\tdi_flags2 |= XFS_DIFLAG2_DAX;\n@@ -126,6 +127,8 @@ xfs_ip2xflags(\n \t\t\tflags |= FS_XFLAG_DAX;\n \t\tif (ip-\u003ei_diflags2 \u0026 XFS_DIFLAG2_COWEXTSIZE)\n \t\t\tflags |= FS_XFLAG_COWEXTSIZE;\n+\t\tif (ip-\u003ei_diflags2 \u0026 XFS_DIFLAG2_VERITY)\n+\t\t\tflags |= FS_XFLAG_VERITY;\n \t}\n \n \tif (xfs_inode_has_attr_fork(ip))\ndiff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c\nindex 47322adb769059..222bbe5559df47 100644\n--- a/fs/xfs/libxfs/xfs_sb.c\n+++ b/fs/xfs/libxfs/xfs_sb.c\n@@ -165,6 +165,8 @@ xfs_sb_version_to_features(\n \t\tfeatures |= XFS_FEAT_REFLINK;\n \tif (sbp-\u003esb_features_ro_compat \u0026 XFS_SB_FEAT_RO_COMPAT_INOBTCNT)\n \t\tfeatures |= XFS_FEAT_INOBTCNT;\n+\tif (sbp-\u003esb_features_ro_compat \u0026 XFS_SB_FEAT_RO_COMPAT_VERITY)\n+\t\tfeatures |= XFS_FEAT_VERITY;\n \tif (sbp-\u003esb_features_incompat \u0026 XFS_SB_FEAT_INCOMPAT_FTYPE)\n \t\tfeatures |= XFS_FEAT_FTYPE;\n \tif (sbp-\u003esb_features_incompat \u0026 XFS_SB_FEAT_INCOMPAT_SPINODES)\n@@ -1588,6 +1590,8 @@ xfs_fs_geometry(\n \t\tgeo-\u003eflags |= XFS_FSOP_GEOM_FLAGS_METADIR;\n \tif (xfs_has_zoned(mp))\n \t\tgeo-\u003eflags |= XFS_FSOP_GEOM_FLAGS_ZONED;\n+\tif (xfs_has_verity(mp))\n+\t\tgeo-\u003eflags |= XFS_FSOP_GEOM_FLAGS_VERITY;\n \tgeo-\u003ertsectsize = sbp-\u003esb_blocksize;\n \tgeo-\u003edirblocksize = xfs_dir2_dirblock_bytes(sbp);\n \ndiff --git a/fs/xfs/scrub/common.c b/fs/xfs/scrub/common.c\nindex e5233e31abb750..1673b522244d74 100644\n--- a/fs/xfs/scrub/common.c\n+++ b/fs/xfs/scrub/common.c\n@@ -45,6 +45,8 @@\n #include \"scrub/health.h\"\n #include \"scrub/tempfile.h\"\n \n+#include \u003clinux/fsverity.h\u003e\n+\n /* Common code for the metadata scrubbers. */\n \n /*\n@@ -1754,3 +1756,54 @@ xchk_inode_count_blocks(\n \treturn xfs_bmap_count_blocks(sc-\u003etp, sc-\u003eip, whichfork, nextents,\n \t\t\tcount);\n }\n+\n+/*\n+ * If this inode has S_VERITY set on it, read the verity info. If the reading\n+ * fails with anything other than ENOMEM, the file is corrupt, which we can\n+ * detect later with fsverity_active.\n+ *\n+ * Callers must hold the IOLOCK and must not hold the ILOCK of sc-\u003eip because\n+ * activation reads inode data.\n+ */\n+int\n+xchk_inode_setup_verity(\n+\tstruct xfs_scrub\t*sc)\n+{\n+\tint\t\t\terror;\n+\n+\tif (!fsverity_active(VFS_I(sc-\u003eip)))\n+\t\treturn 0;\n+\n+\terror = fsverity_ensure_verity_info(VFS_I(sc-\u003eip));\n+\tswitch (error) {\n+\tcase 0:\n+\t\t/* fsverity is active */\n+\t\tbreak;\n+\tcase -ENODATA:\n+\tcase -EMSGSIZE:\n+\tcase -EINVAL:\n+\tcase -EFSCORRUPTED:\n+\tcase -EFBIG:\n+\t\t/*\n+\t\t * The nonzero errno codes above are the error codes that can\n+\t\t * be returned from fsverity on metadata validation errors.\n+\t\t */\n+\t\treturn 0;\n+\tdefault:\n+\t\t/* runtime errors */\n+\t\treturn error;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Is this a verity file that failed to activate?  Callers must have tried to\n+ * activate fsverity via xchk_inode_setup_verity.\n+ */\n+bool\n+xchk_inode_verity_broken(\n+\tstruct xfs_inode\t*ip)\n+{\n+\treturn fsverity_active(VFS_I(ip)) \u0026\u0026 !fsverity_get_info(VFS_I(ip));\n+}\ndiff --git a/fs/xfs/scrub/common.h b/fs/xfs/scrub/common.h\nindex 9d627fd50687e4..3de2b6009a9941 100644\n--- a/fs/xfs/scrub/common.h\n+++ b/fs/xfs/scrub/common.h\n@@ -268,6 +268,8 @@ int xchk_inode_is_allocated(struct xfs_scrub *sc, xfs_agino_t agino,\n \t\tbool *inuse);\n int xchk_inode_count_blocks(struct xfs_scrub *sc, int whichfork,\n \t\txfs_extnum_t *nextents, xfs_filblks_t *count);\n+int xchk_inode_setup_verity(struct xfs_scrub *sc);\n+bool xchk_inode_verity_broken(struct xfs_inode *ip);\n \n bool xchk_inode_is_dirtree_root(const struct xfs_inode *ip);\n bool xchk_inode_is_sb_rooted(const struct xfs_inode *ip);\ndiff --git a/fs/xfs/scrub/inode.c b/fs/xfs/scrub/inode.c\nindex 65b13e31191681..d1cdd6b445d017 100644\n--- a/fs/xfs/scrub/inode.c\n+++ b/fs/xfs/scrub/inode.c\n@@ -36,6 +36,10 @@ xchk_prepare_iscrub(\n \n \txchk_ilock(sc, XFS_IOLOCK_EXCL);\n \n+\terror = xchk_inode_setup_verity(sc);\n+\tif (error)\n+\t\treturn error;\n+\n \terror = xchk_trans_alloc(sc, 0);\n \tif (error)\n \t\treturn error;\n@@ -833,6 +837,9 @@ xchk_inode(\n \tif (S_ISREG(VFS_I(sc-\u003eip)-\u003ei_mode))\n \t\txchk_inode_check_reflink_iflag(sc, I_INO(sc-\u003eip));\n \n+\tif (xchk_inode_verity_broken(sc-\u003eip))\n+\t\txchk_ino_set_corrupt(sc, sc-\u003esm-\u003esm_ino);\n+\n \txchk_inode_check_unlinked(sc);\n \n \txchk_inode_xref(sc, I_INO(sc-\u003eip), \u0026di);\ndiff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c\nindex 493dcf5cc6c159..90f861bb4ef848 100644\n--- a/fs/xfs/scrub/inode_repair.c\n+++ b/fs/xfs/scrub/inode_repair.c\n@@ -573,6 +573,8 @@ xrep_dinode_flags(\n \t\tdip-\u003edi_nrext64_pad = 0;\n \telse if (dip-\u003edi_version \u003e= 3)\n \t\tdip-\u003edi_v3_pad = 0;\n+\tif (!xfs_has_verity(mp) || !S_ISREG(mode))\n+\t\tflags2 \u0026= ~XFS_DIFLAG2_VERITY;\n \n \tif (flags2 \u0026 XFS_DIFLAG2_METADATA) {\n \t\txfs_failaddr_t\tfa;\n@@ -1617,6 +1619,10 @@ xrep_dinode_core(\n \tif (iget_error)\n \t\treturn iget_error;\n \n+\terror = xchk_inode_setup_verity(sc);\n+\tif (error)\n+\t\treturn error;\n+\n \terror = xchk_trans_alloc(sc, 0);\n \tif (error)\n \t\treturn error;\n@@ -2035,6 +2041,27 @@ xrep_inode_unlinked(\n \treturn 0;\n }\n \n+/*\n+ * If this file is a fsverity file, xchk_prepare_iscrub or xrep_dinode_core\n+ * should have activated it.  If it's still not active, then there's something\n+ * wrong with the verity descriptor and we should turn it off.\n+ */\n+STATIC int\n+xrep_inode_verity(\n+\tstruct xfs_scrub\t*sc)\n+{\n+\tstruct inode\t\t*inode = VFS_I(sc-\u003eip);\n+\n+\tif (xchk_inode_verity_broken(sc-\u003eip)) {\n+\t\tsc-\u003eip-\u003ei_diflags2 \u0026= ~XFS_DIFLAG2_VERITY;\n+\t\tinode-\u003ei_flags \u0026= ~S_VERITY;\n+\n+\t\txfs_trans_log_inode(sc-\u003etp, sc-\u003eip, XFS_ILOG_CORE);\n+\t}\n+\n+\treturn 0;\n+}\n+\n /* Repair an inode's fields. */\n int\n xrep_inode(\n@@ -2084,6 +2111,15 @@ xrep_inode(\n \t\t\treturn error;\n \t}\n \n+\t/*\n+\t * Disable fsverity if it cannot be activated.  Activation failure\n+\t * prohibits the file from being opened, so there cannot be another\n+\t * program with an open fd to what it thinks is a verity file.\n+\t */\n+\terror = xrep_inode_verity(sc);\n+\tif (error)\n+\t\treturn error;\n+\n \t/* Reconnect incore unlinked list */\n \terror = xrep_inode_unlinked(sc);\n \tif (error)\ndiff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c\nindex 51293b6f331f0b..b0537c67e960e1 100644\n--- a/fs/xfs/xfs_aops.c\n+++ b/fs/xfs/xfs_aops.c\n@@ -22,7 +22,9 @@\n #include \"xfs_icache.h\"\n #include \"xfs_zone_alloc.h\"\n #include \"xfs_rtgroup.h\"\n+#include \"xfs_fsverity.h\"\n #include \u003clinux/bio-integrity.h\u003e\n+#include \u003clinux/fsverity.h\u003e\n \n struct xfs_writepage_ctx {\n \tstruct iomap_writepage_ctx ctx;\n@@ -181,6 +183,23 @@ xfs_end_ioend_write(\n \tmemalloc_nofs_restore(nofs_flag);\n }\n \n+/*\n+ * IO read completion.\n+ */\n+static void\n+xfs_end_ioend_read(\n+\tstruct iomap_ioend\t*ioend)\n+{\n+\tstruct xfs_inode\t*ip = XFS_I(ioend-\u003eio_inode);\n+\n+\tif (!ioend-\u003eio_bio.bi_status \u0026\u0026\n+\t\t\txfs_fsverity_is_file_data(ip, ioend-\u003eio_offset))\n+\t\tfsverity_verify_bio(ioend-\u003eio_vi,\n+\t\t\t\t    \u0026ioend-\u003eio_bio);\n+\tiomap_finish_ioends(ioend,\n+\t\tblk_status_to_errno(ioend-\u003eio_bio.bi_status));\n+}\n+\n /*\n  * Finish all pending IO completions that require transactional modifications.\n  *\n@@ -215,8 +234,7 @@ xfs_end_io(\n \t\tlist_del_init(\u0026ioend-\u003eio_list);\n \t\tiomap_ioend_try_merge(ioend, \u0026tmp);\n \t\tif (bio_op(\u0026ioend-\u003eio_bio) == REQ_OP_READ)\n-\t\t\tiomap_finish_ioends(ioend,\n-\t\t\t\tblk_status_to_errno(ioend-\u003eio_bio.bi_status));\n+\t\t\txfs_end_ioend_read(ioend);\n \t\telse\n \t\t\txfs_end_ioend_write(ioend);\n \t\tcond_resched();\n@@ -242,9 +260,14 @@ xfs_end_bio(\n \t}\n \n \tspin_lock_irqsave(\u0026ip-\u003ei_ioend_lock, flags);\n-\tif (list_empty(\u0026ip-\u003ei_ioend_list))\n-\t\tWARN_ON_ONCE(!queue_work(mp-\u003em_unwritten_workqueue,\n+\tif (list_empty(\u0026ip-\u003ei_ioend_list)) {\n+\t\tif (IS_ENABLED(CONFIG_FS_VERITY) \u0026\u0026 ioend-\u003eio_vi \u0026\u0026\n+\t\t    ioend-\u003eio_offset \u003c xfs_fsverity_metadata_offset(ip))\n+\t\t\tfsverity_enqueue_verify_work(\u0026ip-\u003ei_ioend_work);\n+\t\telse\n+\t\t\tWARN_ON_ONCE(!queue_work(mp-\u003em_unwritten_workqueue,\n \t\t\t\t\t \u0026ip-\u003ei_ioend_work));\n+\t}\n \tlist_add_tail(\u0026ioend-\u003eio_list, \u0026ip-\u003ei_ioend_list);\n \tspin_unlock_irqrestore(\u0026ip-\u003ei_ioend_lock, flags);\n }\n@@ -350,12 +373,16 @@ xfs_map_blocks(\n \tint\t\t\tretries = 0;\n \tint\t\t\terror = 0;\n \tunsigned int\t\t*seq;\n+\tunsigned int\t\tiomap_flags = 0;\n \n \tif (xfs_is_shutdown(mp))\n \t\treturn -EIO;\n \n \tXFS_ERRORTAG_DELAY(mp, XFS_ERRTAG_WB_DELAY_MS);\n \n+\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))\n+\t\tiomap_flags |= IOMAP_F_FSVERITY;\n+\n \t/*\n \t * COW fork blocks can overlap data fork blocks even if the blocks\n \t * aren't shared.  COW I/O always takes precedent, so we must always\n@@ -443,7 +470,8 @@ xfs_map_blocks(\n \t    isnullstartblock(imap.br_startblock))\n \t\tgoto allocate_blocks;\n \n-\txfs_bmbt_to_iomap(ip, \u0026wpc-\u003eiomap, \u0026imap, 0, 0, XFS_WPC(wpc)-\u003edata_seq);\n+\txfs_bmbt_to_iomap(ip, \u0026wpc-\u003eiomap, \u0026imap, 0, iomap_flags,\n+\t\t\t  XFS_WPC(wpc)-\u003edata_seq);\n \ttrace_xfs_map_blocks_found(ip, offset, count, whichfork, \u0026imap);\n \treturn 0;\n allocate_blocks:\n@@ -590,12 +618,16 @@ xfs_zoned_map_blocks(\n \txfs_filblks_t\t\tcount_fsb;\n \tstruct xfs_bmbt_irec\timap, del;\n \tstruct xfs_iext_cursor\ticur;\n+\tu16\t\t\tiomap_flags = 0;\n \n \tif (xfs_is_shutdown(mp))\n \t\treturn -EIO;\n \n \tXFS_ERRORTAG_DELAY(mp, XFS_ERRTAG_WB_DELAY_MS);\n \n+\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))\n+\t\tiomap_flags |= IOMAP_F_FSVERITY;\n+\n \t/*\n \t * All dirty data must be covered by delalloc extents.  But truncate can\n \t * remove delalloc extents underneath us or reduce their size.\n@@ -619,7 +651,7 @@ xfs_zoned_map_blocks(\n \t\timap.br_startblock = HOLESTARTBLOCK;\n \t\timap.br_state = XFS_EXT_NORM;\n \t\txfs_iunlock(ip, XFS_ILOCK_EXCL);\n-\t\txfs_bmbt_to_iomap(ip, \u0026wpc-\u003eiomap, \u0026imap, 0, 0, 0);\n+\t\txfs_bmbt_to_iomap(ip, \u0026wpc-\u003eiomap, \u0026imap, 0, iomap_flags, 0);\n \t\treturn 0;\n \t}\n \tend_fsb = min(end_fsb, imap.br_startoff + imap.br_blockcount);\n@@ -632,7 +664,7 @@ xfs_zoned_map_blocks(\n \txfs_iunlock(ip, XFS_ILOCK_EXCL);\n \n \twpc-\u003eiomap.type = IOMAP_MAPPED;\n-\twpc-\u003eiomap.flags = IOMAP_F_DIRTY;\n+\twpc-\u003eiomap.flags = iomap_flags | IOMAP_F_DIRTY;\n \twpc-\u003eiomap.bdev = mp-\u003em_rtdev_targp-\u003ebt_bdev;\n \twpc-\u003eiomap.offset = offset;\n \twpc-\u003eiomap.length = XFS_FSB_TO_B(mp, count_fsb);\n@@ -684,6 +716,22 @@ static const struct iomap_writeback_ops xfs_zoned_writeback_ops = {\n \t.writeback_submit\t= xfs_zoned_writeback_submit,\n };\n \n+static int\n+xfs_iomap_writepages(\n+\tstruct xfs_inode\t\t*ip,\n+\tstruct iomap_writepage_ctx\t*ctx)\n+{\n+\t/*\n+\t * Writeback does not work for folios past EOF, let it know that\n+\t * I/O happens for fsverity metadata and this restriction need\n+\t * to be skipped\n+\t */\n+\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))\n+\t\tctx-\u003eiomap.flags |= IOMAP_F_FSVERITY;\n+\n+\treturn iomap_writepages(ctx);\n+}\n+\n STATIC int\n xfs_vm_writepages(\n \tstruct address_space\t*mapping,\n@@ -703,7 +751,7 @@ xfs_vm_writepages(\n \t\t};\n \t\tint\t\t\t\terror;\n \n-\t\terror = iomap_writepages(\u0026xc.ctx);\n+\t\terror = xfs_iomap_writepages(ip, \u0026xc.ctx);\n \t\tif (xc.open_zone)\n \t\t\txfs_open_zone_put(xc.open_zone);\n \t\treturn error;\n@@ -716,7 +764,7 @@ xfs_vm_writepages(\n \t\t\t},\n \t\t};\n \n-\t\treturn iomap_writepages(\u0026wpc.ctx);\n+\t\treturn xfs_iomap_writepages(ip, \u0026wpc.ctx);\n \t}\n }\n \n@@ -761,9 +809,12 @@ xfs_bio_submit_read(\n \tstruct iomap_read_folio_ctx\t*ctx)\n {\n \tstruct bio\t\t\t*bio = ctx-\u003eread_ctx;\n+\tstruct iomap_ioend\t\t*ioend;\n \n \t/* defer read completions to the ioend workqueue */\n-\tiomap_init_ioend(iter-\u003einode, bio, ctx-\u003eread_ctx_file_offset, 0);\n+\tioend = iomap_init_ioend(iter-\u003einode, bio, ctx-\u003eread_ctx_file_offset,\n+\t\t\t0);\n+\tioend-\u003eio_vi = ctx-\u003evi;\n \tiomap_bio_submit_read_endio(iter, ctx, xfs_end_bio);\n }\n \n@@ -775,11 +826,13 @@ static const struct iomap_read_ops xfs_iomap_read_ops = {\n \n static inline const struct iomap_read_ops *\n xfs_get_iomap_read_ops(\n-\tconst struct address_space\t*mapping)\n+\tconst struct address_space\t*mapping,\n+\tloff_t\t\t\t\tposition)\n {\n \tstruct xfs_inode\t\t*ip = XFS_I(mapping-\u003ehost);\n \n-\tif (bdev_has_integrity_csum(xfs_inode_buftarg(ip)-\u003ebt_bdev))\n+\tif (bdev_has_integrity_csum(xfs_inode_buftarg(ip)-\u003ebt_bdev) ||\n+\t\t\txfs_fsverity_is_file_data(ip, position))\n \t\treturn \u0026xfs_iomap_read_ops;\n \treturn \u0026iomap_bio_read_ops;\n }\n@@ -791,7 +844,7 @@ xfs_vm_read_folio(\n {\n \tstruct iomap_read_folio_ctx\tctx = { .cur_folio = folio };\n \n-\tctx.ops = xfs_get_iomap_read_ops(folio-\u003emapping);\n+\tctx.ops = xfs_get_iomap_read_ops(folio-\u003emapping, folio_pos(folio));\n \tiomap_read_folio(\u0026xfs_read_iomap_ops, \u0026ctx, NULL);\n \treturn 0;\n }\n@@ -802,7 +855,7 @@ xfs_vm_readahead(\n {\n \tstruct iomap_read_folio_ctx\tctx = { .rac = rac };\n \n-\tctx.ops = xfs_get_iomap_read_ops(rac-\u003emapping),\n+\tctx.ops = xfs_get_iomap_read_ops(rac-\u003emapping, readahead_pos(rac));\n \tiomap_readahead(\u0026xfs_read_iomap_ops, \u0026ctx, NULL);\n }\n \ndiff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c\nindex 3b9f262f8e9128..b34f2f27cc93a8 100644\n--- a/fs/xfs/xfs_bmap_util.c\n+++ b/fs/xfs/xfs_bmap_util.c\n@@ -31,6 +31,7 @@\n #include \"xfs_rtbitmap.h\"\n #include \"xfs_rtgroup.h\"\n #include \"xfs_zone_alloc.h\"\n+#include \u003clinux/fsverity.h\u003e\n \n /* Kernel only BMAP related definitions and functions */\n \n@@ -553,6 +554,13 @@ xfs_can_free_eofblocks(\n \tif (last_fsb \u003c= end_fsb)\n \t\treturn false;\n \n+\t/*\n+\t * Nothing to clean on fsverity inodes as they don't use prealloc and\n+\t * there no delalloc as only written data is fsverity metadata\n+\t */\n+\tif (IS_VERITY(VFS_I(ip)))\n+\t\treturn false;\n+\n \t/*\n \t * Check if there is an post-EOF extent to free.  If there are any\n \t * delalloc blocks attached to the inode (data fork delalloc\ndiff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c\nindex 845a97c9b0630b..cba194d678fc77 100644\n--- a/fs/xfs/xfs_file.c\n+++ b/fs/xfs/xfs_file.c\n@@ -37,6 +37,7 @@\n #include \u003clinux/fadvise.h\u003e\n #include \u003clinux/mount.h\u003e\n #include \u003clinux/filelock.h\u003e\n+#include \u003clinux/fsverity.h\u003e\n \n static const struct vm_operations_struct xfs_file_vm_ops;\n \n@@ -245,6 +246,25 @@ static const struct iomap_dio_ops xfs_dio_read_bounce_ops = {\n \t.bio_set\t= \u0026iomap_ioend_bioset,\n };\n \n+STATIC ssize_t\n+xfs_file_buffered_read(\n+\tstruct kiocb\t\t*iocb,\n+\tstruct iov_iter\t\t*to)\n+{\n+\tstruct xfs_inode\t*ip = XFS_I(file_inode(iocb-\u003eki_filp));\n+\tssize_t\t\t\tret;\n+\n+\ttrace_xfs_file_buffered_read(iocb, to);\n+\n+\tret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED);\n+\tif (ret)\n+\t\treturn ret;\n+\tret = generic_file_read_iter(iocb, to);\n+\txfs_iunlock(ip, XFS_IOLOCK_SHARED);\n+\n+\treturn ret;\n+}\n+\n STATIC ssize_t\n xfs_file_dio_read(\n \tstruct kiocb\t\t*iocb,\n@@ -265,6 +285,16 @@ xfs_file_dio_read(\n \tret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED);\n \tif (ret)\n \t\treturn ret;\n+\t/*\n+\t * Re-check verity status after acquiring lock. This prevents TOCTOU in\n+\t * xfs_file_read_iter() while falling back from DIO to buffered I/O as\n+\t * now we are holding a lock\n+\t */\n+\tif (fsverity_active(VFS_I(ip))) {\n+\t\txfs_iunlock(ip, XFS_IOLOCK_SHARED);\n+\t\tiocb-\u003eki_flags \u0026= ~IOCB_DIRECT;\n+\t\treturn xfs_file_buffered_read(iocb, to);\n+\t}\n \tif (mapping_stable_writes(iocb-\u003eki_filp-\u003ef_mapping)) {\n \t\tdio_ops = \u0026xfs_dio_read_bounce_ops;\n \t\tdio_flags |= IOMAP_DIO_BOUNCE;\n@@ -281,7 +311,8 @@ xfs_file_dax_read(\n \tstruct kiocb\t\t*iocb,\n \tstruct iov_iter\t\t*to)\n {\n-\tstruct xfs_inode\t*ip = XFS_I(iocb-\u003eki_filp-\u003ef_mapping-\u003ehost);\n+\tstruct inode\t\t*inode = iocb-\u003eki_filp-\u003ef_mapping-\u003ehost;\n+\tstruct xfs_inode\t*ip = XFS_I(inode);\n \tssize_t\t\t\tret = 0;\n \n \ttrace_xfs_file_dax_read(iocb, to);\n@@ -299,25 +330,6 @@ xfs_file_dax_read(\n \treturn ret;\n }\n \n-STATIC ssize_t\n-xfs_file_buffered_read(\n-\tstruct kiocb\t\t*iocb,\n-\tstruct iov_iter\t\t*to)\n-{\n-\tstruct xfs_inode\t*ip = XFS_I(file_inode(iocb-\u003eki_filp));\n-\tssize_t\t\t\tret;\n-\n-\ttrace_xfs_file_buffered_read(iocb, to);\n-\n-\tret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED);\n-\tif (ret)\n-\t\treturn ret;\n-\tret = generic_file_read_iter(iocb, to);\n-\txfs_iunlock(ip, XFS_IOLOCK_SHARED);\n-\n-\treturn ret;\n-}\n-\n STATIC ssize_t\n xfs_file_read_iter(\n \tstruct kiocb\t\t*iocb,\n@@ -332,6 +344,14 @@ xfs_file_read_iter(\n \tif (xfs_is_shutdown(mp))\n \t\treturn -EIO;\n \n+\t/*\n+\t * In case fs-verity is enabled, we also fallback to the buffered read\n+\t * from the direct read path. Therefore, IOCB_DIRECT is set and need to\n+\t * be cleared (see generic_file_read_iter())\n+\t */\n+\tif (fsverity_active(inode))\n+\t\tiocb-\u003eki_flags \u0026= ~IOCB_DIRECT;\n+\n \tif (IS_DAX(inode))\n \t\tret = xfs_file_dax_read(iocb, to);\n \telse if (iocb-\u003eki_flags \u0026 IOCB_DIRECT)\n@@ -1700,11 +1720,18 @@ xfs_file_open(\n \tstruct inode\t*inode,\n \tstruct file\t*file)\n {\n+\tint\t\terror;\n+\n \tif (xfs_is_shutdown(XFS_M(inode-\u003ei_sb)))\n \t\treturn -EIO;\n \tfile-\u003ef_mode |= FMODE_NOWAIT | FMODE_CAN_ODIRECT;\n \tif (xfs_get_atomic_write_min(XFS_I(inode)) \u003e 0)\n \t\tfile-\u003ef_mode |= FMODE_CAN_ATOMIC_WRITE;\n+\n+\terror = fsverity_file_open(inode, file);\n+\tif (error)\n+\t\treturn error;\n+\n \treturn generic_file_open(inode, file);\n }\n \ndiff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c\nnew file mode 100644\nindex 00000000000000..9732811a1444e6\n--- /dev/null\n+++ b/fs/xfs/xfs_fsverity.c\n@@ -0,0 +1,473 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (C) 2026 Red Hat, Inc.\n+ */\n+#include \"xfs_platform.h\"\n+#include \"xfs_format.h\"\n+#include \"xfs_shared.h\"\n+#include \"xfs_trans_resv.h\"\n+#include \"xfs_mount.h\"\n+#include \"xfs_da_format.h\"\n+#include \"xfs_da_btree.h\"\n+#include \"xfs_inode.h\"\n+#include \"xfs_log_format.h\"\n+#include \"xfs_bmap_util.h\"\n+#include \"xfs_trans.h\"\n+#include \"xfs_trace.h\"\n+#include \"xfs_quota.h\"\n+#include \"xfs_fsverity.h\"\n+#include \"xfs_iomap.h\"\n+#include \"xfs_error.h\"\n+#include \"xfs_health.h\"\n+#include \"xfs_bmap.h\"\n+#include \"xfs_bmap_util.h\"\n+#include \u003clinux/fsverity.h\u003e\n+#include \u003clinux/iomap.h\u003e\n+#include \u003clinux/pagemap.h\u003e\n+\n+loff_t\n+xfs_fsverity_metadata_offset(\n+\tconst struct xfs_inode\t*ip)\n+{\n+\treturn round_up(i_size_read(VFS_IC(ip)), XFS_FSVERITY_START_ALIGN);\n+}\n+\n+bool\n+xfs_fsverity_is_file_data(\n+\tconst struct xfs_inode\t*ip,\n+\tloff_t\t\t\toffset)\n+{\n+\treturn fsverity_active(VFS_IC(ip)) \u0026\u0026\n+\t\t\toffset \u003c xfs_fsverity_metadata_offset(ip);\n+}\n+\n+/*\n+ * Retrieve the verity descriptor.\n+ */\n+static int\n+xfs_fsverity_get_descriptor(\n+\tstruct inode\t\t*inode,\n+\tvoid\t\t\t*buf,\n+\tsize_t\t\t\tbuf_size)\n+{\n+\tstruct xfs_inode\t*ip = XFS_I(inode);\n+\tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n+\t__be32\t\t\td_desc_size;\n+\tu32\t\t\tdesc_size;\n+\tu64\t\t\tdesc_size_pos;\n+\tint\t\t\terror;\n+\tu64\t\t\tdesc_pos;\n+\tstruct xfs_bmbt_irec\trec;\n+\tint\t\t\tis_empty;\n+\tuint32_t\t\tblocksize = i_blocksize(VFS_I(ip));\n+\txfs_fileoff_t\t\tlast_block_offset;\n+\n+\tASSERT(inode-\u003ei_flags \u0026 S_VERITY);\n+\txfs_ilock(ip, XFS_ILOCK_SHARED);\n+\terror = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, \u0026rec, \u0026is_empty);\n+\txfs_iunlock(ip, XFS_ILOCK_SHARED);\n+\tif (error)\n+\t\treturn error;\n+\n+\tif (is_empty)\n+\t\treturn -ENODATA;\n+\n+\tlast_block_offset =\n+\t\tXFS_FSB_TO_B(mp, rec.br_startoff + rec.br_blockcount);\n+\tif (last_block_offset \u003c= xfs_fsverity_metadata_offset(ip))\n+\t\treturn -ENODATA;\n+\n+\tdesc_size_pos = last_block_offset - sizeof(__be32);\n+\terror = fsverity_pagecache_read(inode, (char *)\u0026d_desc_size,\n+\t\t\tsizeof(d_desc_size), desc_size_pos);\n+\tif (error)\n+\t\treturn error;\n+\n+\tdesc_size = be32_to_cpu(d_desc_size);\n+\tif (XFS_IS_CORRUPT(mp, desc_size \u003e FS_VERITY_MAX_DESCRIPTOR_SIZE)) {\n+\t\txfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);\n+\t\treturn -ERANGE;\n+\t}\n+\n+\tif (XFS_IS_CORRUPT(mp, desc_size \u003e desc_size_pos)) {\n+\t\txfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);\n+\t\treturn -ERANGE;\n+\t}\n+\n+\tif (!buf_size)\n+\t\treturn desc_size;\n+\n+\tif (XFS_IS_CORRUPT(mp, desc_size \u003e buf_size)) {\n+\t\txfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);\n+\t\treturn -ERANGE;\n+\t}\n+\n+\tdesc_pos = round_down(desc_size_pos - desc_size, blocksize);\n+\tif (desc_pos \u003c xfs_fsverity_metadata_offset(ip)) {\n+\t\txfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);\n+\t\treturn -ERANGE;\n+\t}\n+\n+\terror = fsverity_pagecache_read(inode, buf, desc_size, desc_pos);\n+\tif (error)\n+\t\treturn error;\n+\n+\treturn desc_size;\n+}\n+\n+static int\n+xfs_fsverity_write_descriptor(\n+\tstruct file\t\t*file,\n+\tconst void\t\t*desc,\n+\tu32\t\t\tdesc_size,\n+\tu64\t\t\tmerkle_tree_size)\n+{\n+\tint\t\t\terror;\n+\tstruct inode\t\t*inode = file_inode(file);\n+\tstruct xfs_inode\t*ip = XFS_I(inode);\n+\tunsigned int\t\tblksize = ip-\u003ei_mount-\u003em_sb.sb_blocksize;\n+\tu64\t\t\ttree_last_block =\n+\t\t\txfs_fsverity_metadata_offset(ip) + merkle_tree_size;\n+\tu64\t\t\tdesc_pos =\n+\t\t\tround_up(tree_last_block, XFS_FSVERITY_START_ALIGN);\n+\tu64\t\t\tdesc_end = desc_pos + desc_size;\n+\t__be32\t\t\tdesc_size_disk = cpu_to_be32(desc_size);\n+\tu64\t\t\tdesc_size_pos =\n+\t\t\tround_up(desc_end + sizeof(desc_size_disk), blksize) -\n+\t\t\tsizeof(desc_size_disk);\n+\n+\terror = iomap_fsverity_write(file, desc_size_pos, sizeof(__be32),\n+\t\t\t(const void *)\u0026desc_size_disk,\n+\t\t\t\u0026xfs_buffered_write_iomap_ops,\n+\t\t\t\u0026xfs_iomap_write_ops);\n+\tif (error)\n+\t\treturn error;\n+\n+\treturn iomap_fsverity_write(file, desc_pos, desc_size, desc,\n+\t\t\t\u0026xfs_buffered_write_iomap_ops,\n+\t\t\t\u0026xfs_iomap_write_ops);\n+}\n+\n+/*\n+ * Try to remove all the fsverity metadata after a failed enablement.\n+ */\n+static int\n+xfs_fsverity_delete_metadata(\n+\tstruct xfs_inode\t*ip)\n+{\n+\tstruct xfs_trans\t*tp;\n+\tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n+\tint\t\t\terror;\n+\n+\terror = xfs_trans_alloc(mp, \u0026M_RES(mp)-\u003etr_itruncate, 0, 0, 0, \u0026tp);\n+\tif (error)\n+\t\treturn error;\n+\n+\txfs_ilock(ip, XFS_ILOCK_EXCL);\n+\txfs_trans_ijoin(tp, ip, 0);\n+\n+\t/*\n+\t * We removing post EOF data, no need to update i_size as fsverity\n+\t * didn't move i_size in the first place\n+\t */\n+\terror = xfs_itruncate_extents(\u0026tp, ip, XFS_DATA_FORK, XFS_ISIZE(ip));\n+\tif (error)\n+\t\tgoto err_cancel;\n+\n+\terror = xfs_trans_commit(tp);\n+\tif (error)\n+\t\tgoto out_unlock;\n+out_unlock:\n+\txfs_iunlock(ip, XFS_ILOCK_EXCL);\n+\treturn error;\n+\n+err_cancel:\n+\txfs_trans_cancel(tp);\n+\txfs_iunlock(ip, XFS_ILOCK_EXCL);\n+\treturn error;\n+}\n+\n+static int\n+xfs_fsverity_cancel_unwritten(\n+\tstruct xfs_inode\t*ip,\n+\tloff_t\t\t\tstart,\n+\tloff_t\t\t\tend)\n+{\n+\tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n+\tstruct xfs_trans\t*tp;\n+\txfs_fileoff_t\t\toffset_fsb = XFS_B_TO_FSB(mp, start);\n+\txfs_fileoff_t\t\tend_fsb = XFS_B_TO_FSB(mp, end);\n+\tstruct xfs_bmbt_irec\timap;\n+\tint\t\t\tnimaps;\n+\tint\t\t\terror = 0;\n+\tint\t\t\tdone;\n+\n+\n+\twhile (offset_fsb \u003c end_fsb) {\n+\t\tnimaps = 1;\n+\n+\t\terror = xfs_trans_alloc(mp, \u0026M_RES(mp)-\u003etr_write, 0, 0,\n+\t\t\t\t0, \u0026tp);\n+\t\tif (error)\n+\t\t\treturn error;\n+\n+\t\txfs_ilock(ip, XFS_ILOCK_EXCL);\n+\t\terror = xfs_bmapi_read(ip, offset_fsb, end_fsb - offset_fsb,\n+\t\t\t\t\u0026imap, \u0026nimaps, 0);\n+\t\tif (error)\n+\t\t\tgoto out_cancel;\n+\n+\t\tif (nimaps == 0)\n+\t\t\tgoto out_cancel;\n+\n+\t\tif (imap.br_state == XFS_EXT_UNWRITTEN) {\n+\t\t\txfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);\n+\n+\t\t\terror = xfs_bunmapi(tp, ip, imap.br_startoff,\n+\t\t\t\t\timap.br_blockcount, 0, 1, \u0026done);\n+\t\t\tif (error)\n+\t\t\t\tgoto out_cancel;\n+\n+\t\t\terror = xfs_trans_commit(tp);\n+\t\t\tif (error)\n+\t\t\t\treturn error;\n+\t\t} else {\n+\t\t\txfs_trans_cancel(tp);\n+\t\t\txfs_iunlock(ip, XFS_ILOCK_EXCL);\n+\t\t}\n+\n+\t\toffset_fsb = imap.br_startoff + imap.br_blockcount;\n+\t}\n+\n+\treturn error;\n+out_cancel:\n+\txfs_trans_cancel(tp);\n+\txfs_iunlock(ip, XFS_ILOCK_EXCL);\n+\treturn error;\n+}\n+\n+\n+/*\n+ * Prepare to enable fsverity by clearing old metadata.\n+ */\n+static int\n+xfs_fsverity_begin_enable(\n+\tstruct file\t\t*filp)\n+{\n+\tstruct inode\t\t*inode = file_inode(filp);\n+\tstruct xfs_inode\t*ip = XFS_I(inode);\n+\tint\t\t\terror;\n+\n+\txfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);\n+\n+\tif (IS_DAX(inode) || ip-\u003ei_diflags2 \u0026 XFS_DIFLAG2_DAX)\n+\t\treturn -EINVAL;\n+\n+\tif (inode-\u003ei_size \u003e XFS_FSVERITY_LARGEST_FILE)\n+\t\treturn -EFBIG;\n+\n+\t/*\n+\t * Flush pagecache before building Merkle tree. Inode is locked and no\n+\t * further writes will happen to the file except fsverity metadata\n+\t */\n+\terror = filemap_write_and_wait(inode-\u003ei_mapping);\n+\tif (error)\n+\t\treturn error;\n+\n+\tif (xfs_iflags_test_and_set(ip, XFS_VERITY_CONSTRUCTION))\n+\t\treturn -EBUSY;\n+\n+\terror = xfs_qm_dqattach(ip);\n+\tif (error)\n+\t\tgoto out_clear;\n+\n+\terror = xfs_fsverity_delete_metadata(ip);\n+\tif (error)\n+\t\tgoto out_clear;\n+\treturn error;\n+out_clear:\n+\txfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);\n+\treturn error;\n+}\n+\n+/*\n+ * Complete (or fail) the process of enabling fsverity.\n+ */\n+static int\n+xfs_fsverity_end_enable(\n+\tstruct file\t\t*file,\n+\tconst void\t\t*desc,\n+\tsize_t\t\t\tdesc_size,\n+\tu64\t\t\tmerkle_tree_size)\n+{\n+\tstruct inode\t\t*inode = file_inode(file);\n+\tstruct xfs_inode\t*ip = XFS_I(inode);\n+\tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n+\tstruct xfs_trans\t*tp;\n+\tint\t\t\terror = 0;\n+\tloff_t\t\t\trange_start = xfs_fsverity_metadata_offset(ip);\n+\n+\txfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);\n+\n+\t/* fs-verity failed, just cleanup */\n+\tif (desc == NULL) {\n+\t\terror = xfs_fsverity_delete_metadata(ip);\n+\t\tgoto out;\n+\t}\n+\n+\terror = xfs_fsverity_write_descriptor(file, desc, desc_size,\n+\t\t\tmerkle_tree_size);\n+\tif (error)\n+\t\tgoto out;\n+\n+\t/*\n+\t * Wait for Merkle tree get written to disk before setting on-disk inode\n+\t * flag and clearing XFS_VERITY_CONSTRUCTION\n+\t */\n+\terror = filemap_write_and_wait_range(inode-\u003ei_mapping, range_start,\n+\t\t\tLLONG_MAX);\n+\tif (error)\n+\t\tgoto out;\n+\n+\t/*\n+\t * Remove unwritten extents left by COW preallocations and write\n+\t * preallocation in the merkle tree holes and past descriptor\n+\t */\n+\terror = xfs_fsverity_cancel_unwritten(ip, range_start, LLONG_MAX);\n+\tif (error)\n+\t\tgoto out;\n+\n+\t/*\n+\t * Proactively drop any delayed allocations in COW fork, the fsverity\n+\t * files are read-only\n+\t */\n+\tif (xfs_is_cow_inode(ip)) {\n+\t\tif (!ip-\u003ei_cowfp)\n+\t\t\txfs_ifork_init_cow(ip);\n+\t\txfs_bmap_punch_delalloc_range(ip, XFS_COW_FORK, 0, LLONG_MAX,\n+\t\t\t\tNULL);\n+\t}\n+\n+\t/*\n+\t * Set fsverity inode flag\n+\t */\n+\terror = xfs_trans_alloc_inode(ip, \u0026M_RES(mp)-\u003etr_ichange,\n+\t\t\t0, 0, false, \u0026tp);\n+\tif (error)\n+\t\tgoto out;\n+\n+\t/*\n+\t * Ensure that we've persisted the verity information before we enable\n+\t * it on the inode and tell the caller we have sealed the inode.\n+\t */\n+\tip-\u003ei_diflags2 |= XFS_DIFLAG2_VERITY;\n+\n+\txfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);\n+\txfs_trans_set_sync(tp);\n+\n+\terror = xfs_trans_commit(tp);\n+\txfs_iunlock(ip, XFS_ILOCK_EXCL);\n+\n+\tif (!error)\n+\t\tinode-\u003ei_flags |= S_VERITY;\n+\n+out:\n+\tif (error) {\n+\t\tint\terror2;\n+\n+\t\terror2 = xfs_fsverity_delete_metadata(ip);\n+\t\tif (error2)\n+\t\t\txfs_alert(ip-\u003ei_mount,\n+\"ino 0x%llx failed to clean up new fsverity metadata, err %d\",\n+\t\t\t\t\tI_INO(ip), error2);\n+\t}\n+\n+\txfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);\n+\treturn error;\n+}\n+\n+/*\n+ * Retrieve a merkle tree block.\n+ */\n+static struct page *\n+xfs_fsverity_read_merkle(\n+\tstruct inode\t\t*inode,\n+\tpgoff_t\t\t\tindex)\n+{\n+\tindex += xfs_fsverity_metadata_offset(XFS_I(inode)) \u003e\u003e PAGE_SHIFT;\n+\n+\treturn generic_read_merkle_tree_page(inode, index);\n+}\n+\n+/*\n+ * Retrieve a merkle tree block.\n+ */\n+static void\n+xfs_fsverity_readahead_merkle_tree(\n+\tstruct inode\t\t*inode,\n+\tpgoff_t\t\t\tindex,\n+\tunsigned long\t\tnr_pages)\n+{\n+\tindex += xfs_fsverity_metadata_offset(XFS_I(inode)) \u003e\u003e PAGE_SHIFT;\n+\n+\tgeneric_readahead_merkle_tree(inode, index, nr_pages);\n+}\n+\n+/*\n+ * Write a merkle tree block.\n+ */\n+static int\n+xfs_fsverity_write_merkle(\n+\tstruct file\t\t*file,\n+\tconst void\t\t*buf,\n+\tu64\t\t\tpos,\n+\tunsigned int\t\tsize,\n+\tconst u8\t\t*zero_digest,\n+\tunsigned int\t\tdigest_size)\n+{\n+\tstruct inode\t\t*inode = file_inode(file);\n+\tstruct xfs_inode\t*ip = XFS_I(inode);\n+\tloff_t\t\t\tposition = pos +\n+\t\txfs_fsverity_metadata_offset(ip);\n+\n+\tif (position + size \u003e inode-\u003ei_sb-\u003es_maxbytes)\n+\t\treturn -EFBIG;\n+\n+\t/*\n+\t * If this is a block full of hashes of zeroed blocks, don't bother\n+\t * storing the block. We can synthesize them later.\n+\t *\n+\t * However, do this only in case Merkle tree block == fs block size.\n+\t * Iomap synthesizes these blocks based on holes in the merkle tree. We\n+\t * won't be able to tell if something need to be synthesizes for the\n+\t * range in the fs block. For example, for 4k filesystem block\n+\t *\n+\t *\t[ 1k | zero hashes | zero hashes | 1k ]\n+\t *\n+\t * Iomap won't know about these empty blocks.\n+\t */\n+\tif (size == ip-\u003ei_mount-\u003em_sb.sb_blocksize \u0026\u0026\n+\t\t\t/*\n+\t\t\t * First digest is zero_digest\n+\t\t\t */\n+\t\t\tmemcmp(buf, zero_digest, digest_size) == 0 \u0026\u0026\n+\t\t\t/*\n+\t\t\t * Every digest is same as previous, thus all are\n+\t\t\t * zero_digest\n+\t\t\t */\n+\t\t\tmemcmp(buf + digest_size, buf, size - digest_size) == 0)\n+\t\treturn 0;\n+\n+\treturn iomap_fsverity_write(file, position, size, buf,\n+\t\t\t\u0026xfs_buffered_write_iomap_ops,\n+\t\t\t\u0026xfs_iomap_write_ops);\n+}\n+\n+const struct fsverity_operations xfs_fsverity_ops = {\n+\t.begin_enable_verity\t\t= xfs_fsverity_begin_enable,\n+\t.end_enable_verity\t\t= xfs_fsverity_end_enable,\n+\t.get_verity_descriptor\t\t= xfs_fsverity_get_descriptor,\n+\t.read_merkle_tree_page\t\t= xfs_fsverity_read_merkle,\n+\t.readahead_merkle_tree\t\t= xfs_fsverity_readahead_merkle_tree,\n+\t.write_merkle_tree_block\t= xfs_fsverity_write_merkle,\n+};\ndiff --git a/fs/xfs/xfs_fsverity.h b/fs/xfs/xfs_fsverity.h\nnew file mode 100644\nindex 00000000000000..6a981e20a75bb8\n--- /dev/null\n+++ b/fs/xfs/xfs_fsverity.h\n@@ -0,0 +1,28 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * Copyright (C) 2026 Red Hat, Inc.\n+ */\n+#ifndef __XFS_FSVERITY_H__\n+#define __XFS_FSVERITY_H__\n+\n+#include \"xfs_platform.h\"\n+#include \u003clinux/fsverity.h\u003e\n+\n+#ifdef CONFIG_FS_VERITY\n+extern const struct fsverity_operations xfs_fsverity_ops;\n+loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip);\n+bool xfs_fsverity_is_file_data(const struct xfs_inode *ip, loff_t offset);\n+#else\n+static inline loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip)\n+{\n+\tWARN_ON_ONCE(1);\n+\treturn ULLONG_MAX;\n+}\n+static inline bool xfs_fsverity_is_file_data(const struct xfs_inode *ip,\n+\t\t\t\t\t    loff_t offset)\n+{\n+\treturn false;\n+}\n+#endif\t/* CONFIG_FS_VERITY */\n+\n+#endif\t/* __XFS_FSVERITY_H__ */\ndiff --git a/fs/xfs/xfs_health.c b/fs/xfs/xfs_health.c\nindex 239b843e83d42a..be66760fb120f7 100644\n--- a/fs/xfs/xfs_health.c\n+++ b/fs/xfs/xfs_health.c\n@@ -625,6 +625,7 @@ static const struct ioctl_sick_map ino_map[] = {\n \t{ XFS_SICK_INO_DIR_ZAPPED,\tXFS_BS_SICK_DIR },\n \t{ XFS_SICK_INO_SYMLINK_ZAPPED,\tXFS_BS_SICK_SYMLINK },\n \t{ XFS_SICK_INO_DIRTREE,\tXFS_BS_SICK_DIRTREE },\n+\t{ XFS_SICK_INO_FSVERITY,\tXFS_BS_SICK_FSVERITY },\n };\n \n /* Fill out bulkstat health info. */\ndiff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h\nindex 34c1038ebfcde1..17ce083591f408 100644\n--- a/fs/xfs/xfs_inode.h\n+++ b/fs/xfs/xfs_inode.h\n@@ -419,6 +419,12 @@ static inline bool xfs_inode_can_sw_atomic_write(const struct xfs_inode *ip)\n  */\n #define XFS_IREMAPPING\t\t(1U \u003c\u003c 15)\n \n+/*\n+ * fs-verity's Merkle tree is under construction. The file is read-only, the\n+ * only writes happening are for the fsverity metadata.\n+ */\n+#define XFS_VERITY_CONSTRUCTION\t(1U \u003c\u003c 16)\n+\n /* All inode state flags related to inode reclaim. */\n #define XFS_ALL_IRECLAIM_FLAGS\t(XFS_IRECLAIMABLE | \\\n \t\t\t\t XFS_IRECLAIM | \\\ndiff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c\nindex 1b53701bebea1a..fc7860a8b5abb1 100644\n--- a/fs/xfs/xfs_ioctl.c\n+++ b/fs/xfs/xfs_ioctl.c\n@@ -49,6 +49,7 @@\n \n #include \u003clinux/mount.h\u003e\n #include \u003clinux/fileattr.h\u003e\n+#include \u003clinux/fsverity.h\u003e\n \n /* Return 0 on success or positive error */\n int\n@@ -1466,6 +1467,19 @@ xfs_file_ioctl(\n \tcase XFS_IOC_VERIFY_MEDIA:\n \t\treturn xfs_ioc_verify_media(filp, arg);\n \n+\tcase FS_IOC_ENABLE_VERITY:\n+\t\tif (!xfs_has_verity(mp))\n+\t\t\treturn -EOPNOTSUPP;\n+\t\treturn fsverity_ioctl_enable(filp, arg);\n+\tcase FS_IOC_MEASURE_VERITY:\n+\t\tif (!xfs_has_verity(mp))\n+\t\t\treturn -EOPNOTSUPP;\n+\t\treturn fsverity_ioctl_measure(filp, arg);\n+\tcase FS_IOC_READ_VERITY_METADATA:\n+\t\tif (!xfs_has_verity(mp))\n+\t\t\treturn -EOPNOTSUPP;\n+\t\treturn fsverity_ioctl_read_metadata(filp, arg);\n+\n \tdefault:\n \t\treturn -ENOTTY;\n \t}\ndiff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c\nindex 225c3de88d03b1..fba51902416b02 100644\n--- a/fs/xfs/xfs_iomap.c\n+++ b/fs/xfs/xfs_iomap.c\n@@ -32,6 +32,8 @@\n #include \"xfs_rtbitmap.h\"\n #include \"xfs_icache.h\"\n #include \"xfs_zone_alloc.h\"\n+#include \"xfs_fsverity.h\"\n+#include \u003clinux/fsverity.h\u003e\n \n #define XFS_ALLOC_ALIGN(mp, off) \\\n \t(((off) \u003e\u003e mp-\u003em_allocsize_log) \u003c\u003c mp-\u003em_allocsize_log)\n@@ -1589,7 +1591,8 @@ xfs_zoned_buffered_write_iomap_begin(\n \tloff_t\t\t\tcount,\n \tunsigned\t\tflags,\n \tstruct iomap\t\t*iomap,\n-\tstruct iomap\t\t*srcmap)\n+\tstruct iomap\t\t*srcmap,\n+\tu16\t\t\tiomap_flags)\n {\n \tstruct iomap_iter\t*iter =\n \t\tcontainer_of(iomap, struct iomap_iter, iomap);\n@@ -1599,7 +1602,6 @@ xfs_zoned_buffered_write_iomap_begin(\n \tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n \txfs_fileoff_t\t\toffset_fsb = XFS_B_TO_FSBT(mp, offset);\n \txfs_fileoff_t\t\tend_fsb = xfs_iomap_end_fsb(mp, offset, count);\n-\tu16\t\t\tiomap_flags = IOMAP_F_SHARED;\n \tunsigned int\t\tlockmode = XFS_ILOCK_EXCL;\n \txfs_filblks_t\t\tcount_fsb;\n \txfs_extlen_t\t\tindlen;\n@@ -1607,6 +1609,8 @@ xfs_zoned_buffered_write_iomap_begin(\n \tstruct xfs_iext_cursor\ticur;\n \tint\t\t\terror = 0;\n \n+\tiomap_flags = iomap_flags | IOMAP_F_SHARED;\n+\n \tASSERT(!xfs_get_extsz_hint(ip));\n \tASSERT(!(flags \u0026 IOMAP_UNSHARE));\n \tASSERT(ac);\n@@ -1803,9 +1807,12 @@ xfs_buffered_write_iomap_begin(\n \tif (xfs_is_shutdown(mp))\n \t\treturn -EIO;\n \n+\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))\n+\t\tiomap_flags |= IOMAP_F_FSVERITY;\n+\n \tif (xfs_is_zoned_inode(ip))\n \t\treturn xfs_zoned_buffered_write_iomap_begin(inode, offset,\n-\t\t\t\tcount, flags, iomap, srcmap);\n+\t\t\t\tcount, flags, iomap, srcmap, iomap_flags);\n \n \t/* we can't use delayed allocations when using extent size hints */\n \tif (xfs_get_extsz_hint(ip))\n@@ -2191,12 +2198,17 @@ xfs_read_iomap_begin(\n \tbool\t\t\tshared = false;\n \tunsigned int\t\tlockmode = XFS_ILOCK_SHARED;\n \tu64\t\t\tseq;\n+\tunsigned int\t\tiomap_flags = 0;\n \n \tASSERT(!(flags \u0026 (IOMAP_WRITE | IOMAP_ZERO)));\n \n \tif (xfs_is_shutdown(mp))\n \t\treturn -EIO;\n \n+\tif (fsverity_active(inode) \u0026\u0026\n+\t    (offset \u003e= xfs_fsverity_metadata_offset(ip)))\n+\t\tiomap_flags |= IOMAP_F_FSVERITY;\n+\n \terror = xfs_ilock_for_iomap(ip, flags, \u0026lockmode);\n \tif (error)\n \t\treturn error;\n@@ -2210,8 +2222,9 @@ xfs_read_iomap_begin(\n \tif (error)\n \t\treturn error;\n \ttrace_xfs_iomap_found(ip, offset, length, XFS_DATA_FORK, \u0026imap);\n-\treturn xfs_bmbt_to_iomap(ip, iomap, \u0026imap, flags,\n-\t\t\t\t shared ? IOMAP_F_SHARED : 0, seq);\n+\tiomap_flags |= shared ? IOMAP_F_SHARED : 0;\n+\n+\treturn xfs_bmbt_to_iomap(ip, iomap, \u0026imap, flags, iomap_flags, seq);\n }\n \n const struct iomap_ops xfs_read_iomap_ops = {\ndiff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c\nindex 6339f4956ecb39..70637cdcc29972 100644\n--- a/fs/xfs/xfs_iops.c\n+++ b/fs/xfs/xfs_iops.c\n@@ -35,6 +35,7 @@\n #include \u003clinux/security.h\u003e\n #include \u003clinux/iversion.h\u003e\n #include \u003clinux/fiemap.h\u003e\n+#include \u003clinux/fsverity.h\u003e\n \n /*\n  * Directories have different lock order w.r.t. mmap_lock compared to regular\n@@ -580,6 +581,10 @@ xfs_report_dioalign(\n \tstruct block_device\t*bdev = target-\u003ebt_bdev;\n \n \tstat-\u003eresult_mask |= STATX_DIOALIGN | STATX_DIO_READ_ALIGN;\n+\n+\tif (fsverity_active(VFS_I(ip)))\n+\t\treturn;\n+\n \tstat-\u003edio_mem_align = bdev_dma_alignment(bdev) + 1;\n \n \t/*\n@@ -1370,6 +1375,8 @@ xfs_inode_should_enable_dax(\n \t\treturn false;\n \tif (!xfs_inode_supports_dax(ip))\n \t\treturn false;\n+\tif (ip-\u003ei_diflags2 \u0026 XFS_DIFLAG2_VERITY)\n+\t\treturn false;\n \tif (xfs_has_dax_always(ip-\u003ei_mount))\n \t\treturn true;\n \tif (ip-\u003ei_diflags2 \u0026 XFS_DIFLAG2_DAX)\n@@ -1398,12 +1405,15 @@ xfs_diflags_to_iflags(\n \t\tflags |= S_NOATIME;\n \tif (init \u0026\u0026 xfs_inode_should_enable_dax(ip))\n \t\tflags |= S_DAX;\n+\tif (xflags \u0026 FS_XFLAG_VERITY)\n+\t\tflags |= S_VERITY;\n \n \t/*\n \t * S_DAX can only be set during inode initialization and is never set by\n \t * the VFS, so we cannot mask off S_DAX in i_flags.\n \t */\n-\tinode-\u003ei_flags \u0026= ~(S_IMMUTABLE | S_APPEND | S_SYNC | S_NOATIME);\n+\tinode-\u003ei_flags \u0026=\n+\t\t~(S_IMMUTABLE | S_APPEND | S_SYNC | S_NOATIME | S_VERITY);\n \tinode-\u003ei_flags |= flags;\n }\n \ndiff --git a/fs/xfs/xfs_message.c b/fs/xfs/xfs_message.c\nindex 0243e509a46891..44b13d46049c62 100644\n--- a/fs/xfs/xfs_message.c\n+++ b/fs/xfs/xfs_message.c\n@@ -149,6 +149,10 @@ xfs_warn_experimental(\n \t\t\t.opstate\t= XFS_OPSTATE_WARNED_LARP,\n \t\t\t.name\t\t= \"logged extended attributes\",\n \t\t},\n+\t\t[XFS_EXPERIMENTAL_FSVERITY] = {\n+\t\t\t.opstate\t= XFS_OPSTATE_WARNED_FSVERITY,\n+\t\t\t.name\t\t= \"fsverity\",\n+\t\t},\n \t};\n \tASSERT(feat \u003e= 0 \u0026\u0026 feat \u003c XFS_EXPERIMENTAL_MAX);\n \tBUILD_BUG_ON(ARRAY_SIZE(features) != XFS_EXPERIMENTAL_MAX);\ndiff --git a/fs/xfs/xfs_message.h b/fs/xfs/xfs_message.h\nindex 811b885f41c35a..364fcc7e42b084 100644\n--- a/fs/xfs/xfs_message.h\n+++ b/fs/xfs/xfs_message.h\n@@ -93,6 +93,7 @@ void xfs_buf_alert_ratelimited(struct xfs_buf *bp, const char *rlmsg,\n enum xfs_experimental_feat {\n \tXFS_EXPERIMENTAL_SHRINK,\n \tXFS_EXPERIMENTAL_LARP,\n+\tXFS_EXPERIMENTAL_FSVERITY,\n \n \tXFS_EXPERIMENTAL_MAX,\n };\ndiff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h\nindex 66a02d1b9ad7a1..875c9b0f8e991f 100644\n--- a/fs/xfs/xfs_mount.h\n+++ b/fs/xfs/xfs_mount.h\n@@ -388,6 +388,7 @@ typedef struct xfs_mount {\n #define XFS_FEAT_EXCHANGE_RANGE\t(1ULL \u003c\u003c 27)\t/* exchange range */\n #define XFS_FEAT_METADIR\t(1ULL \u003c\u003c 28)\t/* metadata directory tree */\n #define XFS_FEAT_ZONED\t\t(1ULL \u003c\u003c 29)\t/* zoned RT device */\n+#define XFS_FEAT_VERITY\t\t(1ULL \u003c\u003c 30)\t/* fs-verity */\n \n /* Mount features */\n #define XFS_FEAT_NOLIFETIME\t(1ULL \u003c\u003c 47)\t/* disable lifetime hints */\n@@ -445,6 +446,7 @@ __XFS_HAS_FEAT(exchange_range, EXCHANGE_RANGE)\n __XFS_HAS_FEAT(metadir, METADIR)\n __XFS_HAS_FEAT(zoned, ZONED)\n __XFS_HAS_FEAT(nolifetime, NOLIFETIME)\n+__XFS_HAS_FEAT(verity, VERITY)\n \n static inline bool xfs_has_rtgroups(const struct xfs_mount *mp)\n {\n@@ -582,6 +584,8 @@ __XFS_HAS_FEAT(nouuid, NOUUID)\n #define XFS_OPSTATE_RESUMING_QUOTAON\t18\n /* (Zoned) GC is in progress */\n #define XFS_OPSTATE_ZONEGC_RUNNING\t20\n+/* Kernel has logged a warning about fsverity support */\n+#define XFS_OPSTATE_WARNED_FSVERITY\t21\n \n #define __XFS_IS_OPSTATE(name, NAME) \\\n static inline bool xfs_is_ ## name (struct xfs_mount *mp) \\\ndiff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c\nindex 8531d526fc4409..a7774d2d6b23fc 100644\n--- a/fs/xfs/xfs_super.c\n+++ b/fs/xfs/xfs_super.c\n@@ -30,6 +30,7 @@\n #include \"xfs_filestream.h\"\n #include \"xfs_quota.h\"\n #include \"xfs_sysfs.h\"\n+#include \"xfs_fsverity.h\"\n #include \"xfs_ondisk.h\"\n #include \"xfs_rmap_item.h\"\n #include \"xfs_refcount_item.h\"\n@@ -1689,6 +1690,9 @@ xfs_fs_fill_super(\n \tsb-\u003es_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP | QTYPE_MASK_PRJ;\n #endif\n \tsb-\u003es_op = \u0026xfs_super_operations;\n+#ifdef CONFIG_FS_VERITY\n+\tsb-\u003es_vop = \u0026xfs_fsverity_ops;\n+#endif\n \n \t/*\n \t * Delay mount work if the debug hook is set. This is debug\n@@ -1941,6 +1945,9 @@ xfs_fs_fill_super(\n \tif (error)\n \t\tgoto out_filestream_unmount;\n \n+\tif (xfs_has_verity(mp))\n+\t\txfs_warn_experimental(mp, XFS_EXPERIMENTAL_FSVERITY);\n+\n \troot = igrab(VFS_I(mp-\u003em_rootip));\n \tif (!root) {\n \t\terror = -ENOENT;\ndiff --git a/include/linux/fsverity.h b/include/linux/fsverity.h\nindex 6c467ded9751fc..f9433332c274bb 100644\n--- a/include/linux/fsverity.h\n+++ b/include/linux/fsverity.h\n@@ -124,6 +124,8 @@ struct fsverity_operations {\n \t * @buf: the Merkle tree block to write\n \t * @pos: the position of the block in the Merkle tree (in bytes)\n \t * @size: the Merkle tree block size (in bytes)\n+\t * @zero_digest: the hash of the all-zeroes block\n+\t * @digest_size: size of zero_digest, in bytes\n \t *\n \t * This is only called between -\u003ebegin_enable_verity() and\n \t * -\u003eend_enable_verity().\n@@ -131,7 +133,9 @@ struct fsverity_operations {\n \t * Return: 0 on success, -errno on failure\n \t */\n \tint (*write_merkle_tree_block)(struct file *file, const void *buf,\n-\t\t\t\t       u64 pos, unsigned int size);\n+\t\t\t\t       u64 pos, unsigned int size,\n+\t\t\t\t       const u8 *zero_digest,\n+\t\t\t\t       unsigned int digest_size);\n };\n \n #ifdef CONFIG_FS_VERITY\n@@ -317,10 +321,14 @@ static inline int fsverity_file_open(struct inode *inode, struct file *filp)\n \treturn 0;\n }\n \n+int fsverity_ensure_verity_info(struct inode *inode);\n+\n void fsverity_cleanup_inode(struct inode *inode);\n \n struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index);\n void generic_readahead_merkle_tree(struct inode *inode, pgoff_t index,\n \t\t\t\t   unsigned long nr_pages);\n+int fsverity_pagecache_read(struct inode *inode, void *buf, size_t count,\n+\t\t\t    loff_t pos);\n \n #endif\t/* _LINUX_FSVERITY_H */\n"
}

2/1 2026/07/11 04:19 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "FS_VERITY",
    "XFS_FS",
    "XFS_ONLINE_SCRUB"
  ],
  "FocusSymbols": [
    "xfs_fsverity_begin_enable",
    "xfs_fsverity_end_enable",
    "xfs_fsverity_get_descriptor",
    "xfs_fsverity_write_descriptor",
    "xfs_fsverity_delete_metadata",
    "xfs_fsverity_cancel_unwritten",
    "xfs_fsverity_read_merkle",
    "xfs_fsverity_readahead_merkle_tree",
    "xfs_fsverity_write_merkle",
    "fsverity_pagecache_read",
    "xchk_inode_setup_verity",
    "xchk_inode_verity_broken",
    "xrep_inode_verity"
  ],
  "Reasoning": "This patch series implements fs-verity support for the XFS filesystem. It introduces new metadata handling, ioctl operations, and integration with XFS's iomap and scrub infrastructure. This is a significant functional addition to a core filesystem and should be heavily fuzzed to ensure data integrity and prevent vulnerabilities.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 5822d1cb1ed828799681e63747237e3695c38741
Author: syz-cluster <triage@syzkaller.com>
Date:   Sat Jul 11 04:19:05 2026 +0000

    syz-cluster: applied patch under review

diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c
index 983365a7354168..bd79e343ae237f 100644
--- a/fs/btrfs/verity.c
+++ b/fs/btrfs/verity.c
@@ -773,11 +773,15 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode,
  * @buf:	Merkle tree block to write
  * @pos:	the position of the block in the Merkle tree (in bytes)
  * @size:	the Merkle tree block size (in bytes)
+ * @zero_digest:	the hash of the all-zeroes block
+ * @digest_size:	size of zero_digest, in bytes
  *
  * Returns 0 on success or negative error code on failure
  */
 static int btrfs_write_merkle_tree_block(struct file *file, const void *buf,
-					 u64 pos, unsigned int size)
+					 u64 pos, unsigned int size,
+					 const u8 *zero_digest,
+					 unsigned int digest_size)
 {
 	struct inode *inode = file_inode(file);
 	loff_t merkle_pos = merkle_file_pos(inode);
diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c
index ca61da53f313fa..ac5c133f552925 100644
--- a/fs/ext4/verity.c
+++ b/fs/ext4/verity.c
@@ -34,32 +34,6 @@ static inline loff_t ext4_verity_metadata_pos(const struct inode *inode)
 	return round_up(inode->i_size, 65536);
 }
 
-/*
- * Read some verity metadata from the inode.  __vfs_read() can't be used because
- * we need to read beyond i_size.
- */
-static int pagecache_read(struct inode *inode, void *buf, size_t count,
-			  loff_t pos)
-{
-	while (count) {
-		struct folio *folio;
-		size_t n;
-
-		folio = read_mapping_folio(inode->i_mapping, pos >> PAGE_SHIFT,
-					 NULL);
-		if (IS_ERR(folio))
-			return PTR_ERR(folio);
-
-		n = memcpy_from_file_folio(buf, folio, pos, count);
-		folio_put(folio);
-
-		buf += n;
-		pos += n;
-		count -= n;
-	}
-	return 0;
-}
-
 /*
  * Write some verity metadata to the inode for FS_IOC_ENABLE_VERITY.
  * kernel_write() can't be used because the file descriptor is readonly.
@@ -311,8 +285,8 @@ static int ext4_get_verity_descriptor_location(struct inode *inode,
 		goto bad;
 	desc_size_pos -= sizeof(desc_size_disk);
 
-	err = pagecache_read(inode, &desc_size_disk, sizeof(desc_size_disk),
-			     desc_size_pos);
+	err = fsverity_pagecache_read(inode, &desc_size_disk,
+				      sizeof(desc_size_disk), desc_size_pos);
 	if (err)
 		return err;
 	desc_size = le32_to_cpu(desc_size_disk);
@@ -352,7 +326,7 @@ static int ext4_get_verity_descriptor(struct inode *inode, void *buf,
 	if (buf_size) {
 		if (desc_size > buf_size)
 			return -ERANGE;
-		err = pagecache_read(inode, buf, desc_size, desc_pos);
+		err = fsverity_pagecache_read(inode, buf, desc_size, desc_pos);
 		if (err)
 			return err;
 	}
@@ -374,7 +348,9 @@ static void ext4_readahead_merkle_tree(struct inode *inode, pgoff_t index,
 }
 
 static int ext4_write_merkle_tree_block(struct file *file, const void *buf,
-					u64 pos, unsigned int size)
+					u64 pos, unsigned int size,
+					const u8 *zero_digest,
+					unsigned int digest_size)
 {
 	pos += ext4_verity_metadata_pos(file_inode(file));
 
diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c
index 39f4825154452a..f3a4617656db9b 100644
--- a/fs/f2fs/verity.c
+++ b/fs/f2fs/verity.c
@@ -37,34 +37,6 @@ static inline loff_t f2fs_verity_metadata_pos(const struct inode *inode)
 	return round_up(inode->i_size, 65536);
 }
 
-/*
- * Read some verity metadata from the inode.  __vfs_read() can't be used because
- * we need to read beyond i_size.
- */
-static int pagecache_read(struct inode *inode, void *buf, size_t count,
-			  loff_t pos)
-{
-	while (count) {
-		size_t n = min_t(size_t, count,
-				 PAGE_SIZE - offset_in_page(pos));
-		struct page *page;
-
-		page = read_mapping_page(inode->i_mapping, pos >> PAGE_SHIFT,
-					 NULL);
-		if (IS_ERR(page))
-			return PTR_ERR(page);
-
-		memcpy_from_page(buf, page, offset_in_page(pos), n);
-
-		put_page(page);
-
-		buf += n;
-		pos += n;
-		count -= n;
-	}
-	return 0;
-}
-
 /*
  * Write some verity metadata to the inode for FS_IOC_ENABLE_VERITY.
  * kernel_write() can't be used because the file descriptor is readonly.
@@ -250,7 +222,7 @@ static int f2fs_get_verity_descriptor(struct inode *inode, void *buf,
 	if (buf_size) {
 		if (size > buf_size)
 			return -ERANGE;
-		res = pagecache_read(inode, buf, size, pos);
+		res = fsverity_pagecache_read(inode, buf, size, pos);
 		if (res)
 			return res;
 	}
@@ -272,7 +244,9 @@ static void f2fs_readahead_merkle_tree(struct inode *inode, pgoff_t index,
 }
 
 static int f2fs_write_merkle_tree_block(struct file *file, const void *buf,
-					u64 pos, unsigned int size)
+					u64 pos, unsigned int size,
+					const u8 *zero_digest,
+					unsigned int digest_size)
 {
 	pos += f2fs_verity_metadata_pos(file_inode(file));
 
diff --git a/fs/file_attr.c b/fs/file_attr.c
index bfb00d256dd560..5424ec4e39493f 100644
--- a/fs/file_attr.c
+++ b/fs/file_attr.c
@@ -246,6 +246,11 @@ static int fileattr_set_prepare(struct inode *inode,
 	if (fa->fsx_cowextsize == 0)
 		fa->fsx_xflags &= ~FS_XFLAG_COWEXTSIZE;
 
+	/* Can not enable DAX on fsverity file */
+	if ((old_ma->fsx_xflags & FS_XFLAG_VERITY) &&
+			fa->fsx_xflags & FS_XFLAG_DAX)
+		return -EINVAL;
+
 	return 0;
 }
 
diff --git a/fs/verity/enable.c b/fs/verity/enable.c
index 42dfed1ce0ce66..ad4ff71d7dd95b 100644
--- a/fs/verity/enable.c
+++ b/fs/verity/enable.c
@@ -50,7 +50,9 @@ static int write_merkle_tree_block(struct file *file, const u8 *buf,
 	int err;
 
 	err = inode->i_sb->s_vop->write_merkle_tree_block(file, buf, pos,
-							  params->block_size);
+							  params->block_size,
+							  params->zero_digest,
+							  params->digest_size);
 	if (err)
 		fsverity_err(inode, "Error %d writing Merkle tree block %lu",
 			     err, index);
diff --git a/fs/verity/open.c b/fs/verity/open.c
index d0c56a7faa3bb1..875e8850ccbace 100644
--- a/fs/verity/open.c
+++ b/fs/verity/open.c
@@ -347,7 +347,24 @@ int fsverity_get_descriptor(struct inode *inode,
 	return 0;
 }
 
-static int ensure_verity_info(struct inode *inode)
+/**
+ * fsverity_ensure_verity_info() - cache verity info if it's not already cached
+ * @inode: the inode for which verity info should be cached
+ *
+ * Ensure this inode has verity info attached to it, it's assumed the inode
+ * already has fsverity enabled. Read fsverity descriptor and creates verity
+ * based on that.
+ *
+ * This needs to be called at least once before any of the inode's data
+ * can be verified (and thus read at all) or the inode's fsverity digest
+ * retrieved.  fsverity_file_open() calls this already, which handles
+ * normal file accesses.  If a filesystem does any internal (i.e. not
+ * associated with a file descriptor) reads of the file's data or
+ * fsverity digest, it must call this explicitly before doing so.
+ *
+ * Return: 0 on success, -errno on failure
+ */
+int fsverity_ensure_verity_info(struct inode *inode)
 {
 	struct fsverity_info *vi = fsverity_get_info(inode), *found;
 	struct fsverity_descriptor *desc;
@@ -383,12 +400,13 @@ static int ensure_verity_info(struct inode *inode)
 	kfree(desc);
 	return err;
 }
+EXPORT_SYMBOL_GPL(fsverity_ensure_verity_info);
 
 int __fsverity_file_open(struct inode *inode, struct file *filp)
 {
 	if (filp->f_mode & FMODE_WRITE)
 		return -EPERM;
-	return ensure_verity_info(inode);
+	return fsverity_ensure_verity_info(inode);
 }
 EXPORT_SYMBOL_GPL(__fsverity_file_open);
 
diff --git a/fs/verity/pagecache.c b/fs/verity/pagecache.c
index 99f5f53eea985f..270abd652e48c6 100644
--- a/fs/verity/pagecache.c
+++ b/fs/verity/pagecache.c
@@ -68,13 +68,64 @@ EXPORT_SYMBOL_GPL(generic_readahead_merkle_tree);
 void fsverity_fill_zerohash(struct folio *folio, size_t offset, size_t len,
 			      struct fsverity_info *vi)
 {
-	size_t off = offset;
+	void *vaddr;
+	void *to;
+	size_t chunk;
 
 	WARN_ON_ONCE(!IS_ALIGNED(offset, vi->tree_params.digest_size));
 	WARN_ON_ONCE(!IS_ALIGNED(len, vi->tree_params.digest_size));
+	WARN_ON_ONCE(offset + len > folio_size(folio));
 
-	for (; off < (offset + len); off += vi->tree_params.digest_size)
-		memcpy_to_folio(folio, off, vi->tree_params.zero_digest,
+	do {
+		vaddr = kmap_local_folio(folio, offset);
+		chunk = len;
+		to = vaddr;
+
+		if (folio_test_partial_kmap(folio) &&
+		    chunk > PAGE_SIZE - offset_in_page(offset))
+			chunk = PAGE_SIZE - offset_in_page(offset);
+		for (; to < (vaddr + chunk); to += vi->tree_params.digest_size)
+			memcpy(to, vi->tree_params.zero_digest,
 				vi->tree_params.digest_size);
+		kunmap_local(vaddr);
+
+		offset += chunk;
+		len -= chunk;
+	} while (len > 0);
+
+	flush_dcache_folio(folio);
 }
 EXPORT_SYMBOL_GPL(fsverity_fill_zerohash);
+
+/**
+ * fsverity_pagecache_read() - read page and copy data to buffer
+ * @inode:	copy from this inode's address space
+ * @buf:	buffer to copy to
+ * @count:	number of bytes to copy
+ * @pos:	position of the folio to copy from
+ *
+ * Read some verity metadata from the inode.  __vfs_read() can't be used because
+ * we need to read beyond i_size.
+ */
+int fsverity_pagecache_read(struct inode *inode, void *buf, size_t count,
+			  loff_t pos)
+{
+	while (count) {
+		struct folio *folio;
+		size_t n;
+
+		folio = read_mapping_folio(inode->i_mapping, pos >> PAGE_SHIFT,
+					 NULL);
+		if (IS_ERR(folio))
+			return PTR_ERR(folio);
+
+		n = memcpy_from_file_folio(buf, folio, pos, count);
+		folio_put(folio);
+
+		buf += n;
+		pos += n;
+		count -= n;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(fsverity_pagecache_read);
diff --git a/fs/verity/verify.c b/fs/verity/verify.c
index 4004a1d428751f..db8c350234bb71 100644
--- a/fs/verity/verify.c
+++ b/fs/verity/verify.c
@@ -9,6 +9,7 @@
 
 #include <linux/bio.h>
 #include <linux/export.h>
+#include <linux/fserror.h>
 
 #define FS_VERITY_MAX_PENDING_BLOCKS 2
 
@@ -205,6 +206,8 @@ static bool verify_data_block(struct fsverity_info *vi,
 		if (memchr_inv(dblock->data, 0, params->block_size)) {
 			fsverity_err(inode,
 				     "FILE CORRUPTED!  Data past EOF is not zeroed");
+			fserror_report_data_lost(inode, data_pos,
+						 params->block_size, GFP_NOFS);
 			return false;
 		}
 		return true;
@@ -312,6 +315,7 @@ static bool verify_data_block(struct fsverity_info *vi,
 		data_pos, level - 1, params->hash_alg->name, hsize, want_hash,
 		params->hash_alg->name, hsize,
 		level == 0 ? dblock->real_hash : real_hash);
+	fserror_report_data_lost(inode, data_pos, params->block_size, GFP_NOFS);
 error:
 	for (; level > 0; level--) {
 		kunmap_local(hblocks[level - 1].addr);
diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
index 9f7133e025768d..38b7f51e5d8405 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -149,6 +149,7 @@ xfs-$(CONFIG_XFS_POSIX_ACL)	+= xfs_acl.o
 xfs-$(CONFIG_SYSCTL)		+= xfs_sysctl.o
 xfs-$(CONFIG_COMPAT)		+= xfs_ioctl32.o
 xfs-$(CONFIG_EXPORTFS_BLOCK_OPS)	+= xfs_pnfs.o
+xfs-$(CONFIG_FS_VERITY)		+= xfs_fsverity.o
 
 # notify failure
 ifeq ($(CONFIG_MEMORY_FAILURE),y)
diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c
index d64defeda645ed..cc48f6e20e8000 100644
--- a/fs/xfs/libxfs/xfs_bmap.c
+++ b/fs/xfs/libxfs/xfs_bmap.c
@@ -41,6 +41,8 @@
 #include "xfs_inode_util.h"
 #include "xfs_rtgroup.h"
 #include "xfs_zone_alloc.h"
+#include "xfs_fsverity.h"
+#include <linux/fsverity.h>
 
 struct kmem_cache		*xfs_bmap_intent_cache;
 
@@ -4402,6 +4404,10 @@ xfs_bmapi_convert_one_delalloc(
 	 * the extent.  Just return the real extent at this offset.
 	 */
 	if (!isnullstartblock(bma.got.br_startblock)) {
+		if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) &&
+		    XFS_FSB_TO_B(mp, bma.got.br_startoff) >=
+			    xfs_fsverity_metadata_offset(ip))
+			flags |= IOMAP_F_FSVERITY;
 		xfs_bmbt_to_iomap(ip, iomap, &bma.got, 0, flags,
 				xfs_iomap_inode_sequence(ip, flags));
 		if (seq)
@@ -4449,6 +4455,11 @@ xfs_bmapi_convert_one_delalloc(
 	XFS_STATS_ADD(mp, xs_xstrat_bytes, XFS_FSB_TO_B(mp, bma.length));
 	XFS_STATS_INC(mp, xs_xstrat_quick);
 
+	if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) &&
+	    XFS_FSB_TO_B(mp, bma.got.br_startoff) >=
+		    xfs_fsverity_metadata_offset(ip))
+		flags |= IOMAP_F_FSVERITY;
+
 	ASSERT(!isnullstartblock(bma.got.br_startblock));
 	xfs_bmbt_to_iomap(ip, iomap, &bma.got, 0, flags,
 				xfs_iomap_inode_sequence(ip, flags));
diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h
index dd0ed046fbe9c9..2878bc9cb6564e 100644
--- a/fs/xfs/libxfs/xfs_format.h
+++ b/fs/xfs/libxfs/xfs_format.h
@@ -374,11 +374,13 @@ xfs_sb_has_compat_feature(
 #define XFS_SB_FEAT_RO_COMPAT_RMAPBT   (1 << 1)		/* reverse map btree */
 #define XFS_SB_FEAT_RO_COMPAT_REFLINK  (1 << 2)		/* reflinked files */
 #define XFS_SB_FEAT_RO_COMPAT_INOBTCNT (1 << 3)		/* inobt block counts */
+#define XFS_SB_FEAT_RO_COMPAT_VERITY   (1 << 4)		/* fs-verity */
 #define XFS_SB_FEAT_RO_COMPAT_ALL \
 		(XFS_SB_FEAT_RO_COMPAT_FINOBT | \
 		 XFS_SB_FEAT_RO_COMPAT_RMAPBT | \
-		 XFS_SB_FEAT_RO_COMPAT_REFLINK| \
-		 XFS_SB_FEAT_RO_COMPAT_INOBTCNT)
+		 XFS_SB_FEAT_RO_COMPAT_REFLINK | \
+		 XFS_SB_FEAT_RO_COMPAT_INOBTCNT | \
+		 XFS_SB_FEAT_RO_COMPAT_VERITY)
 #define XFS_SB_FEAT_RO_COMPAT_UNKNOWN	~XFS_SB_FEAT_RO_COMPAT_ALL
 static inline bool
 xfs_sb_has_ro_compat_feature(
@@ -1230,16 +1232,21 @@ static inline void xfs_dinode_put_rdev(struct xfs_dinode *dip, xfs_dev_t rdev)
  */
 #define XFS_DIFLAG2_METADATA_BIT	5
 
+/* inodes sealed with fs-verity */
+#define XFS_DIFLAG2_VERITY_BIT		6
+
 #define XFS_DIFLAG2_DAX		(1ULL << XFS_DIFLAG2_DAX_BIT)
 #define XFS_DIFLAG2_REFLINK	(1ULL << XFS_DIFLAG2_REFLINK_BIT)
 #define XFS_DIFLAG2_COWEXTSIZE	(1ULL << XFS_DIFLAG2_COWEXTSIZE_BIT)
 #define XFS_DIFLAG2_BIGTIME	(1ULL << XFS_DIFLAG2_BIGTIME_BIT)
 #define XFS_DIFLAG2_NREXT64	(1ULL << XFS_DIFLAG2_NREXT64_BIT)
 #define XFS_DIFLAG2_METADATA	(1ULL << XFS_DIFLAG2_METADATA_BIT)
+#define XFS_DIFLAG2_VERITY	(1ULL << XFS_DIFLAG2_VERITY_BIT)
 
 #define XFS_DIFLAG2_ANY \
 	(XFS_DIFLAG2_DAX | XFS_DIFLAG2_REFLINK | XFS_DIFLAG2_COWEXTSIZE | \
-	 XFS_DIFLAG2_BIGTIME | XFS_DIFLAG2_NREXT64 | XFS_DIFLAG2_METADATA)
+	 XFS_DIFLAG2_BIGTIME | XFS_DIFLAG2_NREXT64 | XFS_DIFLAG2_METADATA | \
+	 XFS_DIFLAG2_VERITY)
 
 static inline bool xfs_dinode_has_bigtime(const struct xfs_dinode *dip)
 {
@@ -2027,4 +2034,26 @@ struct xfs_acl {
 #define SGI_ACL_FILE_SIZE	(sizeof(SGI_ACL_FILE)-1)
 #define SGI_ACL_DEFAULT_SIZE	(sizeof(SGI_ACL_DEFAULT)-1)
 
+/*
+ * At maximum of 8 levels with 128 hashes per block (32 bytes SHA-256) maximum
+ * tree size is ((128^8 − 1)/(128 − 1)) = 567*10^12 blocks. This should fit in
+ * 53 bits address space.
+ *
+ * At this Merkle tree size we can cover 295EB large file. This is much larger
+ * than the currently supported file size.
+ *
+ * For sha512 the largest file we can cover ends at 1 << 50 offset, this is also
+ * good.
+ */
+#define XFS_FSVERITY_LARGEST_FILE	((loff_t)1ULL << 53)
+
+/*
+ * Alignment of the fsverity metadata placement. This is largest supported PAGE
+ * SIZE for fsverity. This is used to space out data and metadata in page cache.
+ * The spacing is necessary for non-exposure of metadata to userspace and
+ * correct merkle tree synethesis in the iomap.
+ */
+#define XFS_FSVERITY_START_ALIGN	(65536)
+
+
 #endif /* __XFS_FORMAT_H__ */
diff --git a/fs/xfs/libxfs/xfs_fs.h b/fs/xfs/libxfs/xfs_fs.h
index 185f09f327c00c..84d62b7506a9ad 100644
--- a/fs/xfs/libxfs/xfs_fs.h
+++ b/fs/xfs/libxfs/xfs_fs.h
@@ -250,6 +250,7 @@ typedef struct xfs_fsop_resblks {
 #define XFS_FSOP_GEOM_FLAGS_PARENT	(1 << 25) /* linux parent pointers */
 #define XFS_FSOP_GEOM_FLAGS_METADIR	(1 << 26) /* metadata directories */
 #define XFS_FSOP_GEOM_FLAGS_ZONED	(1 << 27) /* zoned rt device */
+#define XFS_FSOP_GEOM_FLAGS_VERITY	(1 << 28) /* fs-verity */
 
 /*
  * Minimum and maximum sizes need for growth checks.
@@ -421,6 +422,7 @@ struct xfs_bulkstat {
 #define XFS_BS_SICK_SYMLINK	(1 << 6)  /* symbolic link remote target */
 #define XFS_BS_SICK_PARENT	(1 << 7)  /* parent pointers */
 #define XFS_BS_SICK_DIRTREE	(1 << 8)  /* directory tree structure */
+#define XFS_BS_SICK_FSVERITY	(1 << 9)  /* fsverity metadata */
 
 /*
  * Project quota id helpers (previously projid was 16bit only
diff --git a/fs/xfs/libxfs/xfs_health.h b/fs/xfs/libxfs/xfs_health.h
index 1d45cf5789e864..932b447190da76 100644
--- a/fs/xfs/libxfs/xfs_health.h
+++ b/fs/xfs/libxfs/xfs_health.h
@@ -104,6 +104,7 @@ struct xfs_rtgroup;
 /* Don't propagate sick status to ag health summary during inactivation */
 #define XFS_SICK_INO_FORGET	(1 << 12)
 #define XFS_SICK_INO_DIRTREE	(1 << 13)  /* directory tree structure */
+#define XFS_SICK_INO_FSVERITY	(1 << 14)  /* fsverity metadata */
 
 /* Primary evidence of health problems in a given group. */
 #define XFS_SICK_FS_PRIMARY	(XFS_SICK_FS_COUNTERS | \
@@ -140,7 +141,8 @@ struct xfs_rtgroup;
 				 XFS_SICK_INO_XATTR | \
 				 XFS_SICK_INO_SYMLINK | \
 				 XFS_SICK_INO_PARENT | \
-				 XFS_SICK_INO_DIRTREE)
+				 XFS_SICK_INO_DIRTREE | \
+				 XFS_SICK_INO_FSVERITY)
 
 #define XFS_SICK_INO_ZAPPED	(XFS_SICK_INO_BMBTD_ZAPPED | \
 				 XFS_SICK_INO_BMBTA_ZAPPED | \
diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c
index 336ef843f2fe4f..bd80a19f2e0929 100644
--- a/fs/xfs/libxfs/xfs_inode_buf.c
+++ b/fs/xfs/libxfs/xfs_inode_buf.c
@@ -760,6 +760,14 @@ xfs_dinode_verify(
 	    !xfs_has_rtreflink(mp))
 		return __this_address;
 
+	/* only regular files can have fsverity */
+	if (flags2 & XFS_DIFLAG2_VERITY) {
+		if (!xfs_has_verity(mp))
+			return __this_address;
+		if (!S_ISREG(mode))
+			return __this_address;
+	}
+
 	if (xfs_has_zoned(mp) &&
 	    dip->di_metatype == cpu_to_be16(XFS_METAFILE_RTRMAP)) {
 		if (be32_to_cpu(dip->di_used_blocks) > mp->m_sb.sb_rgextents)
diff --git a/fs/xfs/libxfs/xfs_inode_util.c b/fs/xfs/libxfs/xfs_inode_util.c
index 258ac3d0d486e6..5a516bd31c3175 100644
--- a/fs/xfs/libxfs/xfs_inode_util.c
+++ b/fs/xfs/libxfs/xfs_inode_util.c
@@ -74,7 +74,8 @@ xfs_flags2diflags2(
 	uint64_t		di_flags2 =
 		(ip->i_diflags2 & (XFS_DIFLAG2_REFLINK |
 				   XFS_DIFLAG2_BIGTIME |
-				   XFS_DIFLAG2_NREXT64));
+				   XFS_DIFLAG2_NREXT64 |
+				   XFS_DIFLAG2_VERITY));
 
 	if (xflags & FS_XFLAG_DAX)
 		di_flags2 |= XFS_DIFLAG2_DAX;
@@ -126,6 +127,8 @@ xfs_ip2xflags(
 			flags |= FS_XFLAG_DAX;
 		if (ip->i_diflags2 & XFS_DIFLAG2_COWEXTSIZE)
 			flags |= FS_XFLAG_COWEXTSIZE;
+		if (ip->i_diflags2 & XFS_DIFLAG2_VERITY)
+			flags |= FS_XFLAG_VERITY;
 	}
 
 	if (xfs_inode_has_attr_fork(ip))
diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c
index 47322adb769059..222bbe5559df47 100644
--- a/fs/xfs/libxfs/xfs_sb.c
+++ b/fs/xfs/libxfs/xfs_sb.c
@@ -165,6 +165,8 @@ xfs_sb_version_to_features(
 		features |= XFS_FEAT_REFLINK;
 	if (sbp->sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_INOBTCNT)
 		features |= XFS_FEAT_INOBTCNT;
+	if (sbp->sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_VERITY)
+		features |= XFS_FEAT_VERITY;
 	if (sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_FTYPE)
 		features |= XFS_FEAT_FTYPE;
 	if (sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_SPINODES)
@@ -1588,6 +1590,8 @@ xfs_fs_geometry(
 		geo->flags |= XFS_FSOP_GEOM_FLAGS_METADIR;
 	if (xfs_has_zoned(mp))
 		geo->flags |= XFS_FSOP_GEOM_FLAGS_ZONED;
+	if (xfs_has_verity(mp))
+		geo->flags |= XFS_FSOP_GEOM_FLAGS_VERITY;
 	geo->rtsectsize = sbp->sb_blocksize;
 	geo->dirblocksize = xfs_dir2_dirblock_bytes(sbp);
 
diff --git a/fs/xfs/scrub/common.c b/fs/xfs/scrub/common.c
index e5233e31abb750..1673b522244d74 100644
--- a/fs/xfs/scrub/common.c
+++ b/fs/xfs/scrub/common.c
@@ -45,6 +45,8 @@
 #include "scrub/health.h"
 #include "scrub/tempfile.h"
 
+#include <linux/fsverity.h>
+
 /* Common code for the metadata scrubbers. */
 
 /*
@@ -1754,3 +1756,54 @@ xchk_inode_count_blocks(
 	return xfs_bmap_count_blocks(sc->tp, sc->ip, whichfork, nextents,
 			count);
 }
+
+/*
+ * If this inode has S_VERITY set on it, read the verity info. If the reading
+ * fails with anything other than ENOMEM, the file is corrupt, which we can
+ * detect later with fsverity_active.
+ *
+ * Callers must hold the IOLOCK and must not hold the ILOCK of sc->ip because
+ * activation reads inode data.
+ */
+int
+xchk_inode_setup_verity(
+	struct xfs_scrub	*sc)
+{
+	int			error;
+
+	if (!fsverity_active(VFS_I(sc->ip)))
+		return 0;
+
+	error = fsverity_ensure_verity_info(VFS_I(sc->ip));
+	switch (error) {
+	case 0:
+		/* fsverity is active */
+		break;
+	case -ENODATA:
+	case -EMSGSIZE:
+	case -EINVAL:
+	case -EFSCORRUPTED:
+	case -EFBIG:
+		/*
+		 * The nonzero errno codes above are the error codes that can
+		 * be returned from fsverity on metadata validation errors.
+		 */
+		return 0;
+	default:
+		/* runtime errors */
+		return error;
+	}
+
+	return 0;
+}
+
+/*
+ * Is this a verity file that failed to activate?  Callers must have tried to
+ * activate fsverity via xchk_inode_setup_verity.
+ */
+bool
+xchk_inode_verity_broken(
+	struct xfs_inode	*ip)
+{
+	return fsverity_active(VFS_I(ip)) && !fsverity_get_info(VFS_I(ip));
+}
diff --git a/fs/xfs/scrub/common.h b/fs/xfs/scrub/common.h
index 9d627fd50687e4..3de2b6009a9941 100644
--- a/fs/xfs/scrub/common.h
+++ b/fs/xfs/scrub/common.h
@@ -268,6 +268,8 @@ int xchk_inode_is_allocated(struct xfs_scrub *sc, xfs_agino_t agino,
 		bool *inuse);
 int xchk_inode_count_blocks(struct xfs_scrub *sc, int whichfork,
 		xfs_extnum_t *nextents, xfs_filblks_t *count);
+int xchk_inode_setup_verity(struct xfs_scrub *sc);
+bool xchk_inode_verity_broken(struct xfs_inode *ip);
 
 bool xchk_inode_is_dirtree_root(const struct xfs_inode *ip);
 bool xchk_inode_is_sb_rooted(const struct xfs_inode *ip);
diff --git a/fs/xfs/scrub/inode.c b/fs/xfs/scrub/inode.c
index 65b13e31191681..d1cdd6b445d017 100644
--- a/fs/xfs/scrub/inode.c
+++ b/fs/xfs/scrub/inode.c
@@ -36,6 +36,10 @@ xchk_prepare_iscrub(
 
 	xchk_ilock(sc, XFS_IOLOCK_EXCL);
 
+	error = xchk_inode_setup_verity(sc);
+	if (error)
+		return error;
+
 	error = xchk_trans_alloc(sc, 0);
 	if (error)
 		return error;
@@ -833,6 +837,9 @@ xchk_inode(
 	if (S_ISREG(VFS_I(sc->ip)->i_mode))
 		xchk_inode_check_reflink_iflag(sc, I_INO(sc->ip));
 
+	if (xchk_inode_verity_broken(sc->ip))
+		xchk_ino_set_corrupt(sc, sc->sm->sm_ino);
+
 	xchk_inode_check_unlinked(sc);
 
 	xchk_inode_xref(sc, I_INO(sc->ip), &di);
diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c
index 493dcf5cc6c159..90f861bb4ef848 100644
--- a/fs/xfs/scrub/inode_repair.c
+++ b/fs/xfs/scrub/inode_repair.c
@@ -573,6 +573,8 @@ xrep_dinode_flags(
 		dip->di_nrext64_pad = 0;
 	else if (dip->di_version >= 3)
 		dip->di_v3_pad = 0;
+	if (!xfs_has_verity(mp) || !S_ISREG(mode))
+		flags2 &= ~XFS_DIFLAG2_VERITY;
 
 	if (flags2 & XFS_DIFLAG2_METADATA) {
 		xfs_failaddr_t	fa;
@@ -1617,6 +1619,10 @@ xrep_dinode_core(
 	if (iget_error)
 		return iget_error;
 
+	error = xchk_inode_setup_verity(sc);
+	if (error)
+		return error;
+
 	error = xchk_trans_alloc(sc, 0);
 	if (error)
 		return error;
@@ -2035,6 +2041,27 @@ xrep_inode_unlinked(
 	return 0;
 }
 
+/*
+ * If this file is a fsverity file, xchk_prepare_iscrub or xrep_dinode_core
+ * should have activated it.  If it's still not active, then there's something
+ * wrong with the verity descriptor and we should turn it off.
+ */
+STATIC int
+xrep_inode_verity(
+	struct xfs_scrub	*sc)
+{
+	struct inode		*inode = VFS_I(sc->ip);
+
+	if (xchk_inode_verity_broken(sc->ip)) {
+		sc->ip->i_diflags2 &= ~XFS_DIFLAG2_VERITY;
+		inode->i_flags &= ~S_VERITY;
+
+		xfs_trans_log_inode(sc->tp, sc->ip, XFS_ILOG_CORE);
+	}
+
+	return 0;
+}
+
 /* Repair an inode's fields. */
 int
 xrep_inode(
@@ -2084,6 +2111,15 @@ xrep_inode(
 			return error;
 	}
 
+	/*
+	 * Disable fsverity if it cannot be activated.  Activation failure
+	 * prohibits the file from being opened, so there cannot be another
+	 * program with an open fd to what it thinks is a verity file.
+	 */
+	error = xrep_inode_verity(sc);
+	if (error)
+		return error;
+
 	/* Reconnect incore unlinked list */
 	error = xrep_inode_unlinked(sc);
 	if (error)
diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
index 51293b6f331f0b..b0537c67e960e1 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -22,7 +22,9 @@
 #include "xfs_icache.h"
 #include "xfs_zone_alloc.h"
 #include "xfs_rtgroup.h"
+#include "xfs_fsverity.h"
 #include <linux/bio-integrity.h>
+#include <linux/fsverity.h>
 
 struct xfs_writepage_ctx {
 	struct iomap_writepage_ctx ctx;
@@ -181,6 +183,23 @@ xfs_end_ioend_write(
 	memalloc_nofs_restore(nofs_flag);
 }
 
+/*
+ * IO read completion.
+ */
+static void
+xfs_end_ioend_read(
+	struct iomap_ioend	*ioend)
+{
+	struct xfs_inode	*ip = XFS_I(ioend->io_inode);
+
+	if (!ioend->io_bio.bi_status &&
+			xfs_fsverity_is_file_data(ip, ioend->io_offset))
+		fsverity_verify_bio(ioend->io_vi,
+				    &ioend->io_bio);
+	iomap_finish_ioends(ioend,
+		blk_status_to_errno(ioend->io_bio.bi_status));
+}
+
 /*
  * Finish all pending IO completions that require transactional modifications.
  *
@@ -215,8 +234,7 @@ xfs_end_io(
 		list_del_init(&ioend->io_list);
 		iomap_ioend_try_merge(ioend, &tmp);
 		if (bio_op(&ioend->io_bio) == REQ_OP_READ)
-			iomap_finish_ioends(ioend,
-				blk_status_to_errno(ioend->io_bio.bi_status));
+			xfs_end_ioend_read(ioend);
 		else
 			xfs_end_ioend_write(ioend);
 		cond_resched();
@@ -242,9 +260,14 @@ xfs_end_bio(
 	}
 
 	spin_lock_irqsave(&ip->i_ioend_lock, flags);
-	if (list_empty(&ip->i_ioend_list))
-		WARN_ON_ONCE(!queue_work(mp->m_unwritten_workqueue,
+	if (list_empty(&ip->i_ioend_list)) {
+		if (IS_ENABLED(CONFIG_FS_VERITY) && ioend->io_vi &&
+		    ioend->io_offset < xfs_fsverity_metadata_offset(ip))
+			fsverity_enqueue_verify_work(&ip->i_ioend_work);
+		else
+			WARN_ON_ONCE(!queue_work(mp->m_unwritten_workqueue,
 					 &ip->i_ioend_work));
+	}
 	list_add_tail(&ioend->io_list, &ip->i_ioend_list);
 	spin_unlock_irqrestore(&ip->i_ioend_lock, flags);
 }
@@ -350,12 +373,16 @@ xfs_map_blocks(
 	int			retries = 0;
 	int			error = 0;
 	unsigned int		*seq;
+	unsigned int		iomap_flags = 0;
 
 	if (xfs_is_shutdown(mp))
 		return -EIO;
 
 	XFS_ERRORTAG_DELAY(mp, XFS_ERRTAG_WB_DELAY_MS);
 
+	if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+		iomap_flags |= IOMAP_F_FSVERITY;
+
 	/*
 	 * COW fork blocks can overlap data fork blocks even if the blocks
 	 * aren't shared.  COW I/O always takes precedent, so we must always
@@ -443,7 +470,8 @@ xfs_map_blocks(
 	    isnullstartblock(imap.br_startblock))
 		goto allocate_blocks;
 
-	xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, 0, XFS_WPC(wpc)->data_seq);
+	xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, iomap_flags,
+			  XFS_WPC(wpc)->data_seq);
 	trace_xfs_map_blocks_found(ip, offset, count, whichfork, &imap);
 	return 0;
 allocate_blocks:
@@ -590,12 +618,16 @@ xfs_zoned_map_blocks(
 	xfs_filblks_t		count_fsb;
 	struct xfs_bmbt_irec	imap, del;
 	struct xfs_iext_cursor	icur;
+	u16			iomap_flags = 0;
 
 	if (xfs_is_shutdown(mp))
 		return -EIO;
 
 	XFS_ERRORTAG_DELAY(mp, XFS_ERRTAG_WB_DELAY_MS);
 
+	if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+		iomap_flags |= IOMAP_F_FSVERITY;
+
 	/*
 	 * All dirty data must be covered by delalloc extents.  But truncate can
 	 * remove delalloc extents underneath us or reduce their size.
@@ -619,7 +651,7 @@ xfs_zoned_map_blocks(
 		imap.br_startblock = HOLESTARTBLOCK;
 		imap.br_state = XFS_EXT_NORM;
 		xfs_iunlock(ip, XFS_ILOCK_EXCL);
-		xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, 0, 0);
+		xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, iomap_flags, 0);
 		return 0;
 	}
 	end_fsb = min(end_fsb, imap.br_startoff + imap.br_blockcount);
@@ -632,7 +664,7 @@ xfs_zoned_map_blocks(
 	xfs_iunlock(ip, XFS_ILOCK_EXCL);
 
 	wpc->iomap.type = IOMAP_MAPPED;
-	wpc->iomap.flags = IOMAP_F_DIRTY;
+	wpc->iomap.flags = iomap_flags | IOMAP_F_DIRTY;
 	wpc->iomap.bdev = mp->m_rtdev_targp->bt_bdev;
 	wpc->iomap.offset = offset;
 	wpc->iomap.length = XFS_FSB_TO_B(mp, count_fsb);
@@ -684,6 +716,22 @@ static const struct iomap_writeback_ops xfs_zoned_writeback_ops = {
 	.writeback_submit	= xfs_zoned_writeback_submit,
 };
 
+static int
+xfs_iomap_writepages(
+	struct xfs_inode		*ip,
+	struct iomap_writepage_ctx	*ctx)
+{
+	/*
+	 * Writeback does not work for folios past EOF, let it know that
+	 * I/O happens for fsverity metadata and this restriction need
+	 * to be skipped
+	 */
+	if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+		ctx->iomap.flags |= IOMAP_F_FSVERITY;
+
+	return iomap_writepages(ctx);
+}
+
 STATIC int
 xfs_vm_writepages(
 	struct address_space	*mapping,
@@ -703,7 +751,7 @@ xfs_vm_writepages(
 		};
 		int				error;
 
-		error = iomap_writepages(&xc.ctx);
+		error = xfs_iomap_writepages(ip, &xc.ctx);
 		if (xc.open_zone)
 			xfs_open_zone_put(xc.open_zone);
 		return error;
@@ -716,7 +764,7 @@ xfs_vm_writepages(
 			},
 		};
 
-		return iomap_writepages(&wpc.ctx);
+		return xfs_iomap_writepages(ip, &wpc.ctx);
 	}
 }
 
@@ -761,9 +809,12 @@ xfs_bio_submit_read(
 	struct iomap_read_folio_ctx	*ctx)
 {
 	struct bio			*bio = ctx->read_ctx;
+	struct iomap_ioend		*ioend;
 
 	/* defer read completions to the ioend workqueue */
-	iomap_init_ioend(iter->inode, bio, ctx->read_ctx_file_offset, 0);
+	ioend = iomap_init_ioend(iter->inode, bio, ctx->read_ctx_file_offset,
+			0);
+	ioend->io_vi = ctx->vi;
 	iomap_bio_submit_read_endio(iter, ctx, xfs_end_bio);
 }
 
@@ -775,11 +826,13 @@ static const struct iomap_read_ops xfs_iomap_read_ops = {
 
 static inline const struct iomap_read_ops *
 xfs_get_iomap_read_ops(
-	const struct address_space	*mapping)
+	const struct address_space	*mapping,
+	loff_t				position)
 {
 	struct xfs_inode		*ip = XFS_I(mapping->host);
 
-	if (bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev))
+	if (bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev) ||
+			xfs_fsverity_is_file_data(ip, position))
 		return &xfs_iomap_read_ops;
 	return &iomap_bio_read_ops;
 }
@@ -791,7 +844,7 @@ xfs_vm_read_folio(
 {
 	struct iomap_read_folio_ctx	ctx = { .cur_folio = folio };
 
-	ctx.ops = xfs_get_iomap_read_ops(folio->mapping);
+	ctx.ops = xfs_get_iomap_read_ops(folio->mapping, folio_pos(folio));
 	iomap_read_folio(&xfs_read_iomap_ops, &ctx, NULL);
 	return 0;
 }
@@ -802,7 +855,7 @@ xfs_vm_readahead(
 {
 	struct iomap_read_folio_ctx	ctx = { .rac = rac };
 
-	ctx.ops = xfs_get_iomap_read_ops(rac->mapping),
+	ctx.ops = xfs_get_iomap_read_ops(rac->mapping, readahead_pos(rac));
 	iomap_readahead(&xfs_read_iomap_ops, &ctx, NULL);
 }
 
diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c
index 3b9f262f8e9128..b34f2f27cc93a8 100644
--- a/fs/xfs/xfs_bmap_util.c
+++ b/fs/xfs/xfs_bmap_util.c
@@ -31,6 +31,7 @@
 #include "xfs_rtbitmap.h"
 #include "xfs_rtgroup.h"
 #include "xfs_zone_alloc.h"
+#include <linux/fsverity.h>
 
 /* Kernel only BMAP related definitions and functions */
 
@@ -553,6 +554,13 @@ xfs_can_free_eofblocks(
 	if (last_fsb <= end_fsb)
 		return false;
 
+	/*
+	 * Nothing to clean on fsverity inodes as they don't use prealloc and
+	 * there no delalloc as only written data is fsverity metadata
+	 */
+	if (IS_VERITY(VFS_I(ip)))
+		return false;
+
 	/*
 	 * Check if there is an post-EOF extent to free.  If there are any
 	 * delalloc blocks attached to the inode (data fork delalloc
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index 845a97c9b0630b..cba194d678fc77 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -37,6 +37,7 @@
 #include <linux/fadvise.h>
 #include <linux/mount.h>
 #include <linux/filelock.h>
+#include <linux/fsverity.h>
 
 static const struct vm_operations_struct xfs_file_vm_ops;
 
@@ -245,6 +246,25 @@ static const struct iomap_dio_ops xfs_dio_read_bounce_ops = {
 	.bio_set	= &iomap_ioend_bioset,
 };
 
+STATIC ssize_t
+xfs_file_buffered_read(
+	struct kiocb		*iocb,
+	struct iov_iter		*to)
+{
+	struct xfs_inode	*ip = XFS_I(file_inode(iocb->ki_filp));
+	ssize_t			ret;
+
+	trace_xfs_file_buffered_read(iocb, to);
+
+	ret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED);
+	if (ret)
+		return ret;
+	ret = generic_file_read_iter(iocb, to);
+	xfs_iunlock(ip, XFS_IOLOCK_SHARED);
+
+	return ret;
+}
+
 STATIC ssize_t
 xfs_file_dio_read(
 	struct kiocb		*iocb,
@@ -265,6 +285,16 @@ xfs_file_dio_read(
 	ret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED);
 	if (ret)
 		return ret;
+	/*
+	 * Re-check verity status after acquiring lock. This prevents TOCTOU in
+	 * xfs_file_read_iter() while falling back from DIO to buffered I/O as
+	 * now we are holding a lock
+	 */
+	if (fsverity_active(VFS_I(ip))) {
+		xfs_iunlock(ip, XFS_IOLOCK_SHARED);
+		iocb->ki_flags &= ~IOCB_DIRECT;
+		return xfs_file_buffered_read(iocb, to);
+	}
 	if (mapping_stable_writes(iocb->ki_filp->f_mapping)) {
 		dio_ops = &xfs_dio_read_bounce_ops;
 		dio_flags |= IOMAP_DIO_BOUNCE;
@@ -281,7 +311,8 @@ xfs_file_dax_read(
 	struct kiocb		*iocb,
 	struct iov_iter		*to)
 {
-	struct xfs_inode	*ip = XFS_I(iocb->ki_filp->f_mapping->host);
+	struct inode		*inode = iocb->ki_filp->f_mapping->host;
+	struct xfs_inode	*ip = XFS_I(inode);
 	ssize_t			ret = 0;
 
 	trace_xfs_file_dax_read(iocb, to);
@@ -299,25 +330,6 @@ xfs_file_dax_read(
 	return ret;
 }
 
-STATIC ssize_t
-xfs_file_buffered_read(
-	struct kiocb		*iocb,
-	struct iov_iter		*to)
-{
-	struct xfs_inode	*ip = XFS_I(file_inode(iocb->ki_filp));
-	ssize_t			ret;
-
-	trace_xfs_file_buffered_read(iocb, to);
-
-	ret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED);
-	if (ret)
-		return ret;
-	ret = generic_file_read_iter(iocb, to);
-	xfs_iunlock(ip, XFS_IOLOCK_SHARED);
-
-	return ret;
-}
-
 STATIC ssize_t
 xfs_file_read_iter(
 	struct kiocb		*iocb,
@@ -332,6 +344,14 @@ xfs_file_read_iter(
 	if (xfs_is_shutdown(mp))
 		return -EIO;
 
+	/*
+	 * In case fs-verity is enabled, we also fallback to the buffered read
+	 * from the direct read path. Therefore, IOCB_DIRECT is set and need to
+	 * be cleared (see generic_file_read_iter())
+	 */
+	if (fsverity_active(inode))
+		iocb->ki_flags &= ~IOCB_DIRECT;
+
 	if (IS_DAX(inode))
 		ret = xfs_file_dax_read(iocb, to);
 	else if (iocb->ki_flags & IOCB_DIRECT)
@@ -1700,11 +1720,18 @@ xfs_file_open(
 	struct inode	*inode,
 	struct file	*file)
 {
+	int		error;
+
 	if (xfs_is_shutdown(XFS_M(inode->i_sb)))
 		return -EIO;
 	file->f_mode |= FMODE_NOWAIT | FMODE_CAN_ODIRECT;
 	if (xfs_get_atomic_write_min(XFS_I(inode)) > 0)
 		file->f_mode |= FMODE_CAN_ATOMIC_WRITE;
+
+	error = fsverity_file_open(inode, file);
+	if (error)
+		return error;
+
 	return generic_file_open(inode, file);
 }
 
diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c
new file mode 100644
index 00000000000000..9732811a1444e6
--- /dev/null
+++ b/fs/xfs/xfs_fsverity.c
@@ -0,0 +1,473 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ */
+#include "xfs_platform.h"
+#include "xfs_format.h"
+#include "xfs_shared.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_da_format.h"
+#include "xfs_da_btree.h"
+#include "xfs_inode.h"
+#include "xfs_log_format.h"
+#include "xfs_bmap_util.h"
+#include "xfs_trans.h"
+#include "xfs_trace.h"
+#include "xfs_quota.h"
+#include "xfs_fsverity.h"
+#include "xfs_iomap.h"
+#include "xfs_error.h"
+#include "xfs_health.h"
+#include "xfs_bmap.h"
+#include "xfs_bmap_util.h"
+#include <linux/fsverity.h>
+#include <linux/iomap.h>
+#include <linux/pagemap.h>
+
+loff_t
+xfs_fsverity_metadata_offset(
+	const struct xfs_inode	*ip)
+{
+	return round_up(i_size_read(VFS_IC(ip)), XFS_FSVERITY_START_ALIGN);
+}
+
+bool
+xfs_fsverity_is_file_data(
+	const struct xfs_inode	*ip,
+	loff_t			offset)
+{
+	return fsverity_active(VFS_IC(ip)) &&
+			offset < xfs_fsverity_metadata_offset(ip);
+}
+
+/*
+ * Retrieve the verity descriptor.
+ */
+static int
+xfs_fsverity_get_descriptor(
+	struct inode		*inode,
+	void			*buf,
+	size_t			buf_size)
+{
+	struct xfs_inode	*ip = XFS_I(inode);
+	struct xfs_mount	*mp = ip->i_mount;
+	__be32			d_desc_size;
+	u32			desc_size;
+	u64			desc_size_pos;
+	int			error;
+	u64			desc_pos;
+	struct xfs_bmbt_irec	rec;
+	int			is_empty;
+	uint32_t		blocksize = i_blocksize(VFS_I(ip));
+	xfs_fileoff_t		last_block_offset;
+
+	ASSERT(inode->i_flags & S_VERITY);
+	xfs_ilock(ip, XFS_ILOCK_SHARED);
+	error = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, &rec, &is_empty);
+	xfs_iunlock(ip, XFS_ILOCK_SHARED);
+	if (error)
+		return error;
+
+	if (is_empty)
+		return -ENODATA;
+
+	last_block_offset =
+		XFS_FSB_TO_B(mp, rec.br_startoff + rec.br_blockcount);
+	if (last_block_offset <= xfs_fsverity_metadata_offset(ip))
+		return -ENODATA;
+
+	desc_size_pos = last_block_offset - sizeof(__be32);
+	error = fsverity_pagecache_read(inode, (char *)&d_desc_size,
+			sizeof(d_desc_size), desc_size_pos);
+	if (error)
+		return error;
+
+	desc_size = be32_to_cpu(d_desc_size);
+	if (XFS_IS_CORRUPT(mp, desc_size > FS_VERITY_MAX_DESCRIPTOR_SIZE)) {
+		xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);
+		return -ERANGE;
+	}
+
+	if (XFS_IS_CORRUPT(mp, desc_size > desc_size_pos)) {
+		xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);
+		return -ERANGE;
+	}
+
+	if (!buf_size)
+		return desc_size;
+
+	if (XFS_IS_CORRUPT(mp, desc_size > buf_size)) {
+		xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);
+		return -ERANGE;
+	}
+
+	desc_pos = round_down(desc_size_pos - desc_size, blocksize);
+	if (desc_pos < xfs_fsverity_metadata_offset(ip)) {
+		xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);
+		return -ERANGE;
+	}
+
+	error = fsverity_pagecache_read(inode, buf, desc_size, desc_pos);
+	if (error)
+		return error;
+
+	return desc_size;
+}
+
+static int
+xfs_fsverity_write_descriptor(
+	struct file		*file,
+	const void		*desc,
+	u32			desc_size,
+	u64			merkle_tree_size)
+{
+	int			error;
+	struct inode		*inode = file_inode(file);
+	struct xfs_inode	*ip = XFS_I(inode);
+	unsigned int		blksize = ip->i_mount->m_sb.sb_blocksize;
+	u64			tree_last_block =
+			xfs_fsverity_metadata_offset(ip) + merkle_tree_size;
+	u64			desc_pos =
+			round_up(tree_last_block, XFS_FSVERITY_START_ALIGN);
+	u64			desc_end = desc_pos + desc_size;
+	__be32			desc_size_disk = cpu_to_be32(desc_size);
+	u64			desc_size_pos =
+			round_up(desc_end + sizeof(desc_size_disk), blksize) -
+			sizeof(desc_size_disk);
+
+	error = iomap_fsverity_write(file, desc_size_pos, sizeof(__be32),
+			(const void *)&desc_size_disk,
+			&xfs_buffered_write_iomap_ops,
+			&xfs_iomap_write_ops);
+	if (error)
+		return error;
+
+	return iomap_fsverity_write(file, desc_pos, desc_size, desc,
+			&xfs_buffered_write_iomap_ops,
+			&xfs_iomap_write_ops);
+}
+
+/*
+ * Try to remove all the fsverity metadata after a failed enablement.
+ */
+static int
+xfs_fsverity_delete_metadata(
+	struct xfs_inode	*ip)
+{
+	struct xfs_trans	*tp;
+	struct xfs_mount	*mp = ip->i_mount;
+	int			error;
+
+	error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp);
+	if (error)
+		return error;
+
+	xfs_ilock(ip, XFS_ILOCK_EXCL);
+	xfs_trans_ijoin(tp, ip, 0);
+
+	/*
+	 * We removing post EOF data, no need to update i_size as fsverity
+	 * didn't move i_size in the first place
+	 */
+	error = xfs_itruncate_extents(&tp, ip, XFS_DATA_FORK, XFS_ISIZE(ip));
+	if (error)
+		goto err_cancel;
+
+	error = xfs_trans_commit(tp);
+	if (error)
+		goto out_unlock;
+out_unlock:
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	return error;
+
+err_cancel:
+	xfs_trans_cancel(tp);
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	return error;
+}
+
+static int
+xfs_fsverity_cancel_unwritten(
+	struct xfs_inode	*ip,
+	loff_t			start,
+	loff_t			end)
+{
+	struct xfs_mount	*mp = ip->i_mount;
+	struct xfs_trans	*tp;
+	xfs_fileoff_t		offset_fsb = XFS_B_TO_FSB(mp, start);
+	xfs_fileoff_t		end_fsb = XFS_B_TO_FSB(mp, end);
+	struct xfs_bmbt_irec	imap;
+	int			nimaps;
+	int			error = 0;
+	int			done;
+
+
+	while (offset_fsb < end_fsb) {
+		nimaps = 1;
+
+		error = xfs_trans_alloc(mp, &M_RES(mp)->tr_write, 0, 0,
+				0, &tp);
+		if (error)
+			return error;
+
+		xfs_ilock(ip, XFS_ILOCK_EXCL);
+		error = xfs_bmapi_read(ip, offset_fsb, end_fsb - offset_fsb,
+				&imap, &nimaps, 0);
+		if (error)
+			goto out_cancel;
+
+		if (nimaps == 0)
+			goto out_cancel;
+
+		if (imap.br_state == XFS_EXT_UNWRITTEN) {
+			xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
+
+			error = xfs_bunmapi(tp, ip, imap.br_startoff,
+					imap.br_blockcount, 0, 1, &done);
+			if (error)
+				goto out_cancel;
+
+			error = xfs_trans_commit(tp);
+			if (error)
+				return error;
+		} else {
+			xfs_trans_cancel(tp);
+			xfs_iunlock(ip, XFS_ILOCK_EXCL);
+		}
+
+		offset_fsb = imap.br_startoff + imap.br_blockcount;
+	}
+
+	return error;
+out_cancel:
+	xfs_trans_cancel(tp);
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	return error;
+}
+
+
+/*
+ * Prepare to enable fsverity by clearing old metadata.
+ */
+static int
+xfs_fsverity_begin_enable(
+	struct file		*filp)
+{
+	struct inode		*inode = file_inode(filp);
+	struct xfs_inode	*ip = XFS_I(inode);
+	int			error;
+
+	xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);
+
+	if (IS_DAX(inode) || ip->i_diflags2 & XFS_DIFLAG2_DAX)
+		return -EINVAL;
+
+	if (inode->i_size > XFS_FSVERITY_LARGEST_FILE)
+		return -EFBIG;
+
+	/*
+	 * Flush pagecache before building Merkle tree. Inode is locked and no
+	 * further writes will happen to the file except fsverity metadata
+	 */
+	error = filemap_write_and_wait(inode->i_mapping);
+	if (error)
+		return error;
+
+	if (xfs_iflags_test_and_set(ip, XFS_VERITY_CONSTRUCTION))
+		return -EBUSY;
+
+	error = xfs_qm_dqattach(ip);
+	if (error)
+		goto out_clear;
+
+	error = xfs_fsverity_delete_metadata(ip);
+	if (error)
+		goto out_clear;
+	return error;
+out_clear:
+	xfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);
+	return error;
+}
+
+/*
+ * Complete (or fail) the process of enabling fsverity.
+ */
+static int
+xfs_fsverity_end_enable(
+	struct file		*file,
+	const void		*desc,
+	size_t			desc_size,
+	u64			merkle_tree_size)
+{
+	struct inode		*inode = file_inode(file);
+	struct xfs_inode	*ip = XFS_I(inode);
+	struct xfs_mount	*mp = ip->i_mount;
+	struct xfs_trans	*tp;
+	int			error = 0;
+	loff_t			range_start = xfs_fsverity_metadata_offset(ip);
+
+	xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);
+
+	/* fs-verity failed, just cleanup */
+	if (desc == NULL) {
+		error = xfs_fsverity_delete_metadata(ip);
+		goto out;
+	}
+
+	error = xfs_fsverity_write_descriptor(file, desc, desc_size,
+			merkle_tree_size);
+	if (error)
+		goto out;
+
+	/*
+	 * Wait for Merkle tree get written to disk before setting on-disk inode
+	 * flag and clearing XFS_VERITY_CONSTRUCTION
+	 */
+	error = filemap_write_and_wait_range(inode->i_mapping, range_start,
+			LLONG_MAX);
+	if (error)
+		goto out;
+
+	/*
+	 * Remove unwritten extents left by COW preallocations and write
+	 * preallocation in the merkle tree holes and past descriptor
+	 */
+	error = xfs_fsverity_cancel_unwritten(ip, range_start, LLONG_MAX);
+	if (error)
+		goto out;
+
+	/*
+	 * Proactively drop any delayed allocations in COW fork, the fsverity
+	 * files are read-only
+	 */
+	if (xfs_is_cow_inode(ip)) {
+		if (!ip->i_cowfp)
+			xfs_ifork_init_cow(ip);
+		xfs_bmap_punch_delalloc_range(ip, XFS_COW_FORK, 0, LLONG_MAX,
+				NULL);
+	}
+
+	/*
+	 * Set fsverity inode flag
+	 */
+	error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_ichange,
+			0, 0, false, &tp);
+	if (error)
+		goto out;
+
+	/*
+	 * Ensure that we've persisted the verity information before we enable
+	 * it on the inode and tell the caller we have sealed the inode.
+	 */
+	ip->i_diflags2 |= XFS_DIFLAG2_VERITY;
+
+	xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
+	xfs_trans_set_sync(tp);
+
+	error = xfs_trans_commit(tp);
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+
+	if (!error)
+		inode->i_flags |= S_VERITY;
+
+out:
+	if (error) {
+		int	error2;
+
+		error2 = xfs_fsverity_delete_metadata(ip);
+		if (error2)
+			xfs_alert(ip->i_mount,
+"ino 0x%llx failed to clean up new fsverity metadata, err %d",
+					I_INO(ip), error2);
+	}
+
+	xfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);
+	return error;
+}
+
+/*
+ * Retrieve a merkle tree block.
+ */
+static struct page *
+xfs_fsverity_read_merkle(
+	struct inode		*inode,
+	pgoff_t			index)
+{
+	index += xfs_fsverity_metadata_offset(XFS_I(inode)) >> PAGE_SHIFT;
+
+	return generic_read_merkle_tree_page(inode, index);
+}
+
+/*
+ * Retrieve a merkle tree block.
+ */
+static void
+xfs_fsverity_readahead_merkle_tree(
+	struct inode		*inode,
+	pgoff_t			index,
+	unsigned long		nr_pages)
+{
+	index += xfs_fsverity_metadata_offset(XFS_I(inode)) >> PAGE_SHIFT;
+
+	generic_readahead_merkle_tree(inode, index, nr_pages);
+}
+
+/*
+ * Write a merkle tree block.
+ */
+static int
+xfs_fsverity_write_merkle(
+	struct file		*file,
+	const void		*buf,
+	u64			pos,
+	unsigned int		size,
+	const u8		*zero_digest,
+	unsigned int		digest_size)
+{
+	struct inode		*inode = file_inode(file);
+	struct xfs_inode	*ip = XFS_I(inode);
+	loff_t			position = pos +
+		xfs_fsverity_metadata_offset(ip);
+
+	if (position + size > inode->i_sb->s_maxbytes)
+		return -EFBIG;
+
+	/*
+	 * If this is a block full of hashes of zeroed blocks, don't bother
+	 * storing the block. We can synthesize them later.
+	 *
+	 * However, do this only in case Merkle tree block == fs block size.
+	 * Iomap synthesizes these blocks based on holes in the merkle tree. We
+	 * won't be able to tell if something need to be synthesizes for the
+	 * range in the fs block. For example, for 4k filesystem block
+	 *
+	 *	[ 1k | zero hashes | zero hashes | 1k ]
+	 *
+	 * Iomap won't know about these empty blocks.
+	 */
+	if (size == ip->i_mount->m_sb.sb_blocksize &&
+			/*
+			 * First digest is zero_digest
+			 */
+			memcmp(buf, zero_digest, digest_size) == 0 &&
+			/*
+			 * Every digest is same as previous, thus all are
+			 * zero_digest
+			 */
+			memcmp(buf + digest_size, buf, size - digest_size) == 0)
+		return 0;
+
+	return iomap_fsverity_write(file, position, size, buf,
+			&xfs_buffered_write_iomap_ops,
+			&xfs_iomap_write_ops);
+}
+
+const struct fsverity_operations xfs_fsverity_ops = {
+	.begin_enable_verity		= xfs_fsverity_begin_enable,
+	.end_enable_verity		= xfs_fsverity_end_enable,
+	.get_verity_descriptor		= xfs_fsverity_get_descriptor,
+	.read_merkle_tree_page		= xfs_fsverity_read_merkle,
+	.readahead_merkle_tree		= xfs_fsverity_readahead_merkle_tree,
+	.write_merkle_tree_block	= xfs_fsverity_write_merkle,
+};
diff --git a/fs/xfs/xfs_fsverity.h b/fs/xfs/xfs_fsverity.h
new file mode 100644
index 00000000000000..6a981e20a75bb8
--- /dev/null
+++ b/fs/xfs/xfs_fsverity.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ */
+#ifndef __XFS_FSVERITY_H__
+#define __XFS_FSVERITY_H__
+
+#include "xfs_platform.h"
+#include <linux/fsverity.h>
+
+#ifdef CONFIG_FS_VERITY
+extern const struct fsverity_operations xfs_fsverity_ops;
+loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip);
+bool xfs_fsverity_is_file_data(const struct xfs_inode *ip, loff_t offset);
+#else
+static inline loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip)
+{
+	WARN_ON_ONCE(1);
+	return ULLONG_MAX;
+}
+static inline bool xfs_fsverity_is_file_data(const struct xfs_inode *ip,
+					    loff_t offset)
+{
+	return false;
+}
+#endif	/* CONFIG_FS_VERITY */
+
+#endif	/* __XFS_FSVERITY_H__ */
diff --git a/fs/xfs/xfs_health.c b/fs/xfs/xfs_health.c
index 239b843e83d42a..be66760fb120f7 100644
--- a/fs/xfs/xfs_health.c
+++ b/fs/xfs/xfs_health.c
@@ -625,6 +625,7 @@ static const struct ioctl_sick_map ino_map[] = {
 	{ XFS_SICK_INO_DIR_ZAPPED,	XFS_BS_SICK_DIR },
 	{ XFS_SICK_INO_SYMLINK_ZAPPED,	XFS_BS_SICK_SYMLINK },
 	{ XFS_SICK_INO_DIRTREE,	XFS_BS_SICK_DIRTREE },
+	{ XFS_SICK_INO_FSVERITY,	XFS_BS_SICK_FSVERITY },
 };
 
 /* Fill out bulkstat health info. */
diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h
index 34c1038ebfcde1..17ce083591f408 100644
--- a/fs/xfs/xfs_inode.h
+++ b/fs/xfs/xfs_inode.h
@@ -419,6 +419,12 @@ static inline bool xfs_inode_can_sw_atomic_write(const struct xfs_inode *ip)
  */
 #define XFS_IREMAPPING		(1U << 15)
 
+/*
+ * fs-verity's Merkle tree is under construction. The file is read-only, the
+ * only writes happening are for the fsverity metadata.
+ */
+#define XFS_VERITY_CONSTRUCTION	(1U << 16)
+
 /* All inode state flags related to inode reclaim. */
 #define XFS_ALL_IRECLAIM_FLAGS	(XFS_IRECLAIMABLE | \
 				 XFS_IRECLAIM | \
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index 1b53701bebea1a..fc7860a8b5abb1 100644
--- a/fs/xfs/xfs_ioctl.c
+++ b/fs/xfs/xfs_ioctl.c
@@ -49,6 +49,7 @@
 
 #include <linux/mount.h>
 #include <linux/fileattr.h>
+#include <linux/fsverity.h>
 
 /* Return 0 on success or positive error */
 int
@@ -1466,6 +1467,19 @@ xfs_file_ioctl(
 	case XFS_IOC_VERIFY_MEDIA:
 		return xfs_ioc_verify_media(filp, arg);
 
+	case FS_IOC_ENABLE_VERITY:
+		if (!xfs_has_verity(mp))
+			return -EOPNOTSUPP;
+		return fsverity_ioctl_enable(filp, arg);
+	case FS_IOC_MEASURE_VERITY:
+		if (!xfs_has_verity(mp))
+			return -EOPNOTSUPP;
+		return fsverity_ioctl_measure(filp, arg);
+	case FS_IOC_READ_VERITY_METADATA:
+		if (!xfs_has_verity(mp))
+			return -EOPNOTSUPP;
+		return fsverity_ioctl_read_metadata(filp, arg);
+
 	default:
 		return -ENOTTY;
 	}
diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c
index 225c3de88d03b1..fba51902416b02 100644
--- a/fs/xfs/xfs_iomap.c
+++ b/fs/xfs/xfs_iomap.c
@@ -32,6 +32,8 @@
 #include "xfs_rtbitmap.h"
 #include "xfs_icache.h"
 #include "xfs_zone_alloc.h"
+#include "xfs_fsverity.h"
+#include <linux/fsverity.h>
 
 #define XFS_ALLOC_ALIGN(mp, off) \
 	(((off) >> mp->m_allocsize_log) << mp->m_allocsize_log)
@@ -1589,7 +1591,8 @@ xfs_zoned_buffered_write_iomap_begin(
 	loff_t			count,
 	unsigned		flags,
 	struct iomap		*iomap,
-	struct iomap		*srcmap)
+	struct iomap		*srcmap,
+	u16			iomap_flags)
 {
 	struct iomap_iter	*iter =
 		container_of(iomap, struct iomap_iter, iomap);
@@ -1599,7 +1602,6 @@ xfs_zoned_buffered_write_iomap_begin(
 	struct xfs_mount	*mp = ip->i_mount;
 	xfs_fileoff_t		offset_fsb = XFS_B_TO_FSBT(mp, offset);
 	xfs_fileoff_t		end_fsb = xfs_iomap_end_fsb(mp, offset, count);
-	u16			iomap_flags = IOMAP_F_SHARED;
 	unsigned int		lockmode = XFS_ILOCK_EXCL;
 	xfs_filblks_t		count_fsb;
 	xfs_extlen_t		indlen;
@@ -1607,6 +1609,8 @@ xfs_zoned_buffered_write_iomap_begin(
 	struct xfs_iext_cursor	icur;
 	int			error = 0;
 
+	iomap_flags = iomap_flags | IOMAP_F_SHARED;
+
 	ASSERT(!xfs_get_extsz_hint(ip));
 	ASSERT(!(flags & IOMAP_UNSHARE));
 	ASSERT(ac);
@@ -1803,9 +1807,12 @@ xfs_buffered_write_iomap_begin(
 	if (xfs_is_shutdown(mp))
 		return -EIO;
 
+	if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+		iomap_flags |= IOMAP_F_FSVERITY;
+
 	if (xfs_is_zoned_inode(ip))
 		return xfs_zoned_buffered_write_iomap_begin(inode, offset,
-				count, flags, iomap, srcmap);
+				count, flags, iomap, srcmap, iomap_flags);
 
 	/* we can't use delayed allocations when using extent size hints */
 	if (xfs_get_extsz_hint(ip))
@@ -2191,12 +2198,17 @@ xfs_read_iomap_begin(
 	bool			shared = false;
 	unsigned int		lockmode = XFS_ILOCK_SHARED;
 	u64			seq;
+	unsigned int		iomap_flags = 0;
 
 	ASSERT(!(flags & (IOMAP_WRITE | IOMAP_ZERO)));
 
 	if (xfs_is_shutdown(mp))
 		return -EIO;
 
+	if (fsverity_active(inode) &&
+	    (offset >= xfs_fsverity_metadata_offset(ip)))
+		iomap_flags |= IOMAP_F_FSVERITY;
+
 	error = xfs_ilock_for_iomap(ip, flags, &lockmode);
 	if (error)
 		return error;
@@ -2210,8 +2222,9 @@ xfs_read_iomap_begin(
 	if (error)
 		return error;
 	trace_xfs_iomap_found(ip, offset, length, XFS_DATA_FORK, &imap);
-	return xfs_bmbt_to_iomap(ip, iomap, &imap, flags,
-				 shared ? IOMAP_F_SHARED : 0, seq);
+	iomap_flags |= shared ? IOMAP_F_SHARED : 0;
+
+	return xfs_bmbt_to_iomap(ip, iomap, &imap, flags, iomap_flags, seq);
 }
 
 const struct iomap_ops xfs_read_iomap_ops = {
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 6339f4956ecb39..70637cdcc29972 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -35,6 +35,7 @@
 #include <linux/security.h>
 #include <linux/iversion.h>
 #include <linux/fiemap.h>
+#include <linux/fsverity.h>
 
 /*
  * Directories have different lock order w.r.t. mmap_lock compared to regular
@@ -580,6 +581,10 @@ xfs_report_dioalign(
 	struct block_device	*bdev = target->bt_bdev;
 
 	stat->result_mask |= STATX_DIOALIGN | STATX_DIO_READ_ALIGN;
+
+	if (fsverity_active(VFS_I(ip)))
+		return;
+
 	stat->dio_mem_align = bdev_dma_alignment(bdev) + 1;
 
 	/*
@@ -1370,6 +1375,8 @@ xfs_inode_should_enable_dax(
 		return false;
 	if (!xfs_inode_supports_dax(ip))
 		return false;
+	if (ip->i_diflags2 & XFS_DIFLAG2_VERITY)
+		return false;
 	if (xfs_has_dax_always(ip->i_mount))
 		return true;
 	if (ip->i_diflags2 & XFS_DIFLAG2_DAX)
@@ -1398,12 +1405,15 @@ xfs_diflags_to_iflags(
 		flags |= S_NOATIME;
 	if (init && xfs_inode_should_enable_dax(ip))
 		flags |= S_DAX;
+	if (xflags & FS_XFLAG_VERITY)
+		flags |= S_VERITY;
 
 	/*
 	 * S_DAX can only be set during inode initialization and is never set by
 	 * the VFS, so we cannot mask off S_DAX in i_flags.
 	 */
-	inode->i_flags &= ~(S_IMMUTABLE | S_APPEND | S_SYNC | S_NOATIME);
+	inode->i_flags &=
+		~(S_IMMUTABLE | S_APPEND | S_SYNC | S_NOATIME | S_VERITY);
 	inode->i_flags |= flags;
 }
 
diff --git a/fs/xfs/xfs_message.c b/fs/xfs/xfs_message.c
index 0243e509a46891..44b13d46049c62 100644
--- a/fs/xfs/xfs_message.c
+++ b/fs/xfs/xfs_message.c
@@ -149,6 +149,10 @@ xfs_warn_experimental(
 			.opstate	= XFS_OPSTATE_WARNED_LARP,
 			.name		= "logged extended attributes",
 		},
+		[XFS_EXPERIMENTAL_FSVERITY] = {
+			.opstate	= XFS_OPSTATE_WARNED_FSVERITY,
+			.name		= "fsverity",
+		},
 	};
 	ASSERT(feat >= 0 && feat < XFS_EXPERIMENTAL_MAX);
 	BUILD_BUG_ON(ARRAY_SIZE(features) != XFS_EXPERIMENTAL_MAX);
diff --git a/fs/xfs/xfs_message.h b/fs/xfs/xfs_message.h
index 811b885f41c35a..364fcc7e42b084 100644
--- a/fs/xfs/xfs_message.h
+++ b/fs/xfs/xfs_message.h
@@ -93,6 +93,7 @@ void xfs_buf_alert_ratelimited(struct xfs_buf *bp, const char *rlmsg,
 enum xfs_experimental_feat {
 	XFS_EXPERIMENTAL_SHRINK,
 	XFS_EXPERIMENTAL_LARP,
+	XFS_EXPERIMENTAL_FSVERITY,
 
 	XFS_EXPERIMENTAL_MAX,
 };
diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h
index 66a02d1b9ad7a1..875c9b0f8e991f 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -388,6 +388,7 @@ typedef struct xfs_mount {
 #define XFS_FEAT_EXCHANGE_RANGE	(1ULL << 27)	/* exchange range */
 #define XFS_FEAT_METADIR	(1ULL << 28)	/* metadata directory tree */
 #define XFS_FEAT_ZONED		(1ULL << 29)	/* zoned RT device */
+#define XFS_FEAT_VERITY		(1ULL << 30)	/* fs-verity */
 
 /* Mount features */
 #define XFS_FEAT_NOLIFETIME	(1ULL << 47)	/* disable lifetime hints */
@@ -445,6 +446,7 @@ __XFS_HAS_FEAT(exchange_range, EXCHANGE_RANGE)
 __XFS_HAS_FEAT(metadir, METADIR)
 __XFS_HAS_FEAT(zoned, ZONED)
 __XFS_HAS_FEAT(nolifetime, NOLIFETIME)
+__XFS_HAS_FEAT(verity, VERITY)
 
 static inline bool xfs_has_rtgroups(const struct xfs_mount *mp)
 {
@@ -582,6 +584,8 @@ __XFS_HAS_FEAT(nouuid, NOUUID)
 #define XFS_OPSTATE_RESUMING_QUOTAON	18
 /* (Zoned) GC is in progress */
 #define XFS_OPSTATE_ZONEGC_RUNNING	20
+/* Kernel has logged a warning about fsverity support */
+#define XFS_OPSTATE_WARNED_FSVERITY	21
 
 #define __XFS_IS_OPSTATE(name, NAME) \
 static inline bool xfs_is_ ## name (struct xfs_mount *mp) \
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index 8531d526fc4409..a7774d2d6b23fc 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -30,6 +30,7 @@
 #include "xfs_filestream.h"
 #include "xfs_quota.h"
 #include "xfs_sysfs.h"
+#include "xfs_fsverity.h"
 #include "xfs_ondisk.h"
 #include "xfs_rmap_item.h"
 #include "xfs_refcount_item.h"
@@ -1689,6 +1690,9 @@ xfs_fs_fill_super(
 	sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP | QTYPE_MASK_PRJ;
 #endif
 	sb->s_op = &xfs_super_operations;
+#ifdef CONFIG_FS_VERITY
+	sb->s_vop = &xfs_fsverity_ops;
+#endif
 
 	/*
 	 * Delay mount work if the debug hook is set. This is debug
@@ -1941,6 +1945,9 @@ xfs_fs_fill_super(
 	if (error)
 		goto out_filestream_unmount;
 
+	if (xfs_has_verity(mp))
+		xfs_warn_experimental(mp, XFS_EXPERIMENTAL_FSVERITY);
+
 	root = igrab(VFS_I(mp->m_rootip));
 	if (!root) {
 		error = -ENOENT;
diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h
index 6c467ded9751fc..f9433332c274bb 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -124,6 +124,8 @@ struct fsverity_operations {
 	 * @buf: the Merkle tree block to write
 	 * @pos: the position of the block in the Merkle tree (in bytes)
 	 * @size: the Merkle tree block size (in bytes)
+	 * @zero_digest: the hash of the all-zeroes block
+	 * @digest_size: size of zero_digest, in bytes
 	 *
 	 * This is only called between ->begin_enable_verity() and
 	 * ->end_enable_verity().
@@ -131,7 +133,9 @@ struct fsverity_operations {
 	 * Return: 0 on success, -errno on failure
 	 */
 	int (*write_merkle_tree_block)(struct file *file, const void *buf,
-				       u64 pos, unsigned int size);
+				       u64 pos, unsigned int size,
+				       const u8 *zero_digest,
+				       unsigned int digest_size);
 };
 
 #ifdef CONFIG_FS_VERITY
@@ -317,10 +321,14 @@ static inline int fsverity_file_open(struct inode *inode, struct file *filp)
 	return 0;
 }
 
+int fsverity_ensure_verity_info(struct inode *inode);
+
 void fsverity_cleanup_inode(struct inode *inode);
 
 struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index);
 void generic_readahead_merkle_tree(struct inode *inode, pgoff_t index,
 				   unsigned long nr_pages);
+int fsverity_pagecache_read(struct inode *inode, void *buf, size_t count,
+			    loff_t pos);
 
 #endif	/* _LINUX_FSVERITY_H */

3/2 2026/07/11 04:19 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 27946
							output: 286
							thoughts: 627
						

Thoughts:
**Analyzing the XFS fs-verity Patch for Fuzzing**

Okay, so I've got this patch series that brings fs-verity to XFS. It's a significant change, touching core kernel logic in both XFS itself and the `fs/verity` subsystem. Definitely fuzz-worthy.

First, let's nail down the configuration options we need. It's a no-brainer that `CONFIG_FS_VERITY` is essential. Naturally, `CONFIG_XFS_FS` is also required for XFS support. Digging into the `fs/xfs/Makefile` and `fs/xfs/Kconfig`, I see that `xfs-$(CONFIG_FS_VERITY) += xfs_fsverity.o` confirms that `CONFIG_FS_VERITY` is the main enabler.

Next, I need to identify the key symbols to focus on during fuzzing. In `fs/xfs/xfs_fsverity.c`, the functions that stick out are: `xfs_fsverity_get_descriptor`, `xfs_fsverity_write_descriptor`, `xfs_fsverity_delete_metadata`, `xfs_fsverity_cancel_unwritten`, `xfs_fsverity_begin_enable`, `xfs_fsverity_end_enable`, `xfs_fsverity_read_merkle`, `xfs_fsverity_readahead_merkle_tree`, and `xfs_fsverity_write_merkle`. Then, there's `fsverity_pagecache_read` in `fs/verity/pagecache.c`. And finally, in the XFS scrub code – `fs/xfs/scrub/inode.c`, `fs/xfs/scrub/inode_repair.c`, `fs/xfs/scrub/common.c` – I see `xchk_inode_setup_verity`, `xchk_inode_verity_broken`, and `xrep_inode_verity`. I confirmed the xfs ones are reachable through `fsverity_operations xfs_fsverity_ops`.

Alright, a final check on the config options: `CONFIG_FS_VERITY`, `CONFIG_XFS_FS`, and given the involvement of the scrub code, let's include `CONFIG_XFS_ONLINE_SCRUB` to be thorough.

Now, let's get the results.



4/2 2026/07/11 04:19 tool set-results
0m Args:
{
  "EnableConfigs": [
    "FS_VERITY",
    "XFS_FS",
    "XFS_ONLINE_SCRUB"
  ],
  "FocusSymbols": [
    "xfs_fsverity_begin_enable",
    "xfs_fsverity_end_enable",
    "xfs_fsverity_get_descriptor",
    "xfs_fsverity_write_descriptor",
    "xfs_fsverity_delete_metadata",
    "xfs_fsverity_cancel_unwritten",
    "xfs_fsverity_read_merkle",
    "xfs_fsverity_readahead_merkle_tree",
    "xfs_fsverity_write_merkle",
    "fsverity_pagecache_read",
    "xchk_inode_setup_verity",
    "xchk_inode_verity_broken",
    "xrep_inode_verity"
  ],
  "Reasoning": "This patch series implements fs-verity support for the XFS filesystem. It introduces new metadata handling, ioctl operations, and integration with XFS's iomap and scrub infrastructure. This is a significant functional addition to a core filesystem and should be heavily fuzzed to ensure data integrity and prevent vulnerabilities.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "FS_VERITY",
    "XFS_FS",
    "XFS_ONLINE_SCRUB"
  ],
  "FocusSymbols": [
    "xfs_fsverity_begin_enable",
    "xfs_fsverity_end_enable",
    "xfs_fsverity_get_descriptor",
    "xfs_fsverity_write_descriptor",
    "xfs_fsverity_delete_metadata",
    "xfs_fsverity_cancel_unwritten",
    "xfs_fsverity_read_merkle",
    "xfs_fsverity_readahead_merkle_tree",
    "xfs_fsverity_write_merkle",
    "fsverity_pagecache_read",
    "xchk_inode_setup_verity",
    "xchk_inode_verity_broken",
    "xrep_inode_verity"
  ],
  "Reasoning": "This patch series implements fs-verity support for the XFS filesystem. It introduces new metadata handling, ioctl operations, and integration with XFS's iomap and scrub infrastructure. This is a significant functional addition to a core filesystem and should be heavily fuzzed to ensure data integrity and prevent vulnerabilities.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)