| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/10/03 09:23 | flow | patch-triage |
2mArgs:null Results: {
"EnableConfigs": [
"XFS_FS",
"FS_VERITY",
"XFS_ONLINE_SCRUB",
"XFS_ONLINE_REPAIR"
],
"FocusSymbols": [
"xfs_fsverity_begin_enable",
"xfs_fsverity_end_enable",
"xfs_fsverity_write_merkle",
"xfs_fsverity_read_merkle",
"xfs_fsverity_get_descriptor",
"xfs_end_fsverity_io_read",
"xchk_inode_setup_verity",
"xrep_inode_verity",
"fsverity_ensure_verity_info",
"fsverity_pagecache_read"
],
"KMSANReasoning": "The patch series implements fs-verity support for XFS, optimizes/refactors Merkle tree block zero-digest handling across filesystems (ext4, btrfs, f2fs, xfs), unifies pagecache metadata reading via `fsverity_pagecache_read()`, and exposes the STATX_ATTR_VERITY flag centrally in VFS stat.\n\nAnalysis of memory safety and sanitizer applicability:\n1. Kernel-to-userspace copies and info-leaks:\n - `vfs_getattr_nosec` sets `STATX_ATTR_VERITY` on `stat`, which is explicitly zeroed via `memset(stat, 0, sizeof(*stat))` at the start of the function.\n - `xfs_report_dioalign` conditionally skips populating `dio_mem_align` for fsverity files; the underlying `kstat` struct was already cleared with `memset`.\n - `xfs_fs_geometry` sets `XFS_FSOP_GEOM_FLAGS_VERITY` on `geo`, which is cleared via `memset(geo, 0, sizeof(*geo))` in `xfs_fs_geometry`.\n - Bulkstat health reporting (`xfs_bulkstat_health`) explicitly initializes `bs-\u003ebs_sick = 0` and `bs-\u003ebs_checked = 0` prior to bitmask assignment.\n - Verity ioctls (`FS_IOC_ENABLE_VERITY`, `FS_IOC_MEASURE_VERITY`, `FS_IOC_READ_VERITY_METADATA`) use existing fsverity core handlers.\n\n2. Structure allocation and initialization:\n - `xfs_fsverity_ioend` allocated from `xfs_fsverity_ioend_pool` has both of its member fields (`ioend` and `work`) explicitly initialized before use or queueing.\n - Designated initializers (`struct iomap_read_folio_ctx ctx = { .cur_folio = folio };`) guarantee all other fields (`vi`, `read_ctx`, etc.) are zero-initialized.\n - Merkle tree buffers in `build_merkle_tree` are zero-allocated with `kzalloc`.\n\n3. Primary risk profile:\n - Potential failure modes in this patch series involve extent mapping and truncation logic (`xfs_bmapi_convert_one_delalloc`, `__xfs_bunmapi`, `xfs_free_eofblocks`), iomap folio iterations, race conditions/locking around verity enablement, and bio completion workqueue handling.\n - These risks (out-of-bounds folio accesses, buffer overflows, use-after-free, and lock inversion) are effectively detected by KASAN and LOCKDEP. There are no uninitialized memory reads or information leak risks that would require dedicated KMSAN fuzzing.",
"NeedsKMSAN": false,
"Reasoning": "This patch series introduces fs-verity support to XFS, exposes fs-verity ioctls (FS_IOC_ENABLE_VERITY, FS_IOC_MEASURE_VERITY, FS_IOC_READ_VERITY_METADATA) on XFS files, adds Merkle tree construction, reading, and metadata handling beyond EOF, wires up async read verification, adds online scrub and repair checks for fs-verity inodes, and exports common fs-verity helper functions. All of these paths are reachable from userspace and warrant fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/10/03 09:23 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 94f00f55bd2265b867b7373031319902086b56af\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sat Oct 3 09:23:39 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c\nindex 81a9af9582827..b9a1b629a5333 100644\n--- a/fs/btrfs/inode.c\n+++ b/fs/btrfs/inode.c\n@@ -8236,7 +8236,6 @@ static int btrfs_getattr(const struct mnt_idmap *idmap,\n \tstruct inode *inode = d_inode(path-\u003edentry);\n \tu32 blocksize = btrfs_sb(inode-\u003ei_sb)-\u003esectorsize;\n \tu32 bi_flags = BTRFS_I(inode)-\u003eflags;\n-\tu32 bi_ro_flags = BTRFS_I(inode)-\u003ero_flags;\n \n \tstat-\u003eresult_mask |= STATX_BTIME;\n \tstat-\u003ebtime.tv_sec = BTRFS_I(inode)-\u003ei_otime_sec;\n@@ -8249,8 +8248,6 @@ static int btrfs_getattr(const struct mnt_idmap *idmap,\n \t\tstat-\u003eattributes |= STATX_ATTR_IMMUTABLE;\n \tif (bi_flags \u0026 BTRFS_INODE_NODUMP)\n \t\tstat-\u003eattributes |= STATX_ATTR_NODUMP;\n-\tif (bi_ro_flags \u0026 BTRFS_INODE_RO_VERITY)\n-\t\tstat-\u003eattributes |= STATX_ATTR_VERITY;\n \n \tstat-\u003eattributes_mask |= (STATX_ATTR_APPEND |\n \t\t\t\t STATX_ATTR_COMPRESSED |\ndiff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c\nindex 4e0ab58422741..c572acacb4d41 100644\n--- a/fs/btrfs/verity.c\n+++ b/fs/btrfs/verity.c\n@@ -779,11 +779,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/inode.c b/fs/ext4/inode.c\nindex cb68bf50a3d6a..9a9ec02dce9fd 100644\n--- a/fs/ext4/inode.c\n+++ b/fs/ext4/inode.c\n@@ -6318,15 +6318,12 @@ int ext4_getattr(const struct mnt_idmap *idmap, const struct path *path,\n \t\tstat-\u003eattributes |= STATX_ATTR_IMMUTABLE;\n \tif (flags \u0026 EXT4_NODUMP_FL)\n \t\tstat-\u003eattributes |= STATX_ATTR_NODUMP;\n-\tif (flags \u0026 EXT4_VERITY_FL)\n-\t\tstat-\u003eattributes |= STATX_ATTR_VERITY;\n \n \tstat-\u003eattributes_mask |= (STATX_ATTR_APPEND |\n \t\t\t\t STATX_ATTR_COMPRESSED |\n \t\t\t\t STATX_ATTR_ENCRYPTED |\n \t\t\t\t STATX_ATTR_IMMUTABLE |\n-\t\t\t\t STATX_ATTR_NODUMP |\n-\t\t\t\t STATX_ATTR_VERITY);\n+\t\t\t\t STATX_ATTR_NODUMP);\n \n \tgeneric_fillattr(idmap, request_mask, inode, stat);\n \treturn 0;\ndiff --git a/fs/ext4/verity.c b/fs/ext4/verity.c\nindex ca61da53f313f..ac5c133f55292 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/file.c b/fs/f2fs/file.c\nindex 8596c3bc4704a..0c759cc990546 100644\n--- a/fs/f2fs/file.c\n+++ b/fs/f2fs/file.c\n@@ -1075,15 +1075,12 @@ int f2fs_getattr(const struct mnt_idmap *idmap, const struct path *path,\n \t\tstat-\u003eattributes |= STATX_ATTR_IMMUTABLE;\n \tif (flags \u0026 F2FS_NODUMP_FL)\n \t\tstat-\u003eattributes |= STATX_ATTR_NODUMP;\n-\tif (IS_VERITY(inode))\n-\t\tstat-\u003eattributes |= STATX_ATTR_VERITY;\n \n \tstat-\u003eattributes_mask |= (STATX_ATTR_COMPRESSED |\n \t\t\t\t STATX_ATTR_APPEND |\n \t\t\t\t STATX_ATTR_ENCRYPTED |\n \t\t\t\t STATX_ATTR_IMMUTABLE |\n-\t\t\t\t STATX_ATTR_NODUMP |\n-\t\t\t\t STATX_ATTR_VERITY);\n+\t\t\t\t STATX_ATTR_NODUMP);\n \n \tgeneric_fillattr(idmap, request_mask, inode, stat);\n \ndiff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c\nindex 39f4825154452..f3a4617656db9 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 81af4364e33ae..833e03810b24c 100644\n--- a/fs/file_attr.c\n+++ b/fs/file_attr.c\n@@ -235,10 +235,15 @@ static int fileattr_set_prepare(struct inode *inode,\n \t/*\n \t * It is only valid to set the DAX flag on regular files and\n \t * directories on filesystems.\n+\t *\n+\t * DAX and fsverity are incompatible.\n \t */\n-\tif ((fa-\u003efsx_xflags \u0026 FS_XFLAG_DAX) \u0026\u0026\n-\t !(S_ISREG(inode-\u003ei_mode) || S_ISDIR(inode-\u003ei_mode)))\n-\t\treturn -EINVAL;\n+\tif (fa-\u003efsx_xflags \u0026 FS_XFLAG_DAX) {\n+\t\tif (!(S_ISREG(inode-\u003ei_mode) || S_ISDIR(inode-\u003ei_mode)))\n+\t\t\treturn -EINVAL;\n+\t\tif (old_ma-\u003efsx_xflags \u0026 FS_XFLAG_VERITY)\n+\t\t\treturn -EINVAL;\n+\t}\n \n \t/* Extent size hints of zero turn off the flags. */\n \tif (fa-\u003efsx_extsize == 0)\ndiff --git a/fs/stat.c b/fs/stat.c\nindex a9b7383d538da..a2e63c6c45b77 100644\n--- a/fs/stat.c\n+++ b/fs/stat.c\n@@ -203,8 +203,12 @@ int vfs_getattr_nosec(const struct path *path, struct kstat *stat,\n \tif (IS_DAX(inode))\n \t\tstat-\u003eattributes |= STATX_ATTR_DAX;\n \n+\tif (IS_VERITY(inode))\n+\t\tstat-\u003eattributes |= STATX_ATTR_VERITY;\n+\n \tstat-\u003eattributes_mask |= (STATX_ATTR_AUTOMOUNT |\n-\t\t\t\t STATX_ATTR_DAX);\n+\t\t\t\t STATX_ATTR_DAX |\n+\t\t\t\t STATX_ATTR_VERITY);\n \n \tidmap = mnt_idmap(path-\u003emnt);\n \tif (inode-\u003ei_op-\u003egetattr) {\ndiff --git a/fs/verity/enable.c b/fs/verity/enable.c\nindex 42dfed1ce0ce6..ad4ff71d7dd95 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 d0c56a7faa3bb..4f06697f5bf4d 100644\n--- a/fs/verity/open.c\n+++ b/fs/verity/open.c\n@@ -347,7 +347,28 @@ 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+ * In case filesystem supports both fscrypt and fsverity, this should be called\n+ * after fscrypt's encryption key is set up. Otherwise, the fsverity metadata is\n+ * still encrypted. See fscrypt_file_open().\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 +404,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 99f5f53eea985..9d82e6b74ba12 100644\n--- a/fs/verity/pagecache.c\n+++ b/fs/verity/pagecache.c\n@@ -78,3 +78,36 @@ void fsverity_fill_zerohash(struct folio *folio, size_t offset, size_t len,\n \t\t\t\tvi-\u003etree_params.digest_size);\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 4004a1d428751..db8c350234bb7 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 399a207f2d0e7..dd712c5218625 100644\n--- a/fs/xfs/Makefile\n+++ b/fs/xfs/Makefile\n@@ -150,6 +150,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 d64defeda645e..6f0094445cea5 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,9 @@ 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 offset \u003e= 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 +4454,10 @@ 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 offset \u003e= 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));\n@@ -5269,6 +5278,10 @@ __xfs_bunmapi(\n \t\tif (del.br_startoff + del.br_blockcount \u003e end + 1)\n \t\t\tdel.br_blockcount = end + 1 - del.br_startoff;\n \n+\t\tif ((flags \u0026 XFS_BMAPI_UNWRITTEN) \u0026\u0026\n+\t\t del.br_state != XFS_EXT_UNWRITTEN)\n+\t\t\tgoto skip;\n+\n \t\tif (!isrt || (flags \u0026 XFS_BMAPI_REMAP))\n \t\t\tgoto delete;\n \n@@ -5403,7 +5416,7 @@ __xfs_bunmapi(\n \t\t\tif (error)\n \t\t\t\tgoto error0;\n \t\t}\n-\n+skip:\n \t\tend = del.br_startoff - 1;\n nodelete:\n \t\t/*\ndiff --git a/fs/xfs/libxfs/xfs_bmap.h b/fs/xfs/libxfs/xfs_bmap.h\nindex d5f2729305fad..0f36431d99368 100644\n--- a/fs/xfs/libxfs/xfs_bmap.h\n+++ b/fs/xfs/libxfs/xfs_bmap.h\n@@ -90,6 +90,9 @@ struct xfs_bmalloca {\n /* Try to align allocations to the extent size hint */\n #define XFS_BMAPI_EXTSZALIGN\t(1u \u003c\u003c 11)\n \n+/* Process unwritten extents only. Used for unmapping */\n+#define XFS_BMAPI_UNWRITTEN\t(1u \u003c\u003c 12)\n+\n #define XFS_BMAPI_FLAGS \\\n \t{ XFS_BMAPI_ENTIRE,\t\"ENTIRE\" }, \\\n \t{ XFS_BMAPI_METADATA,\t\"METADATA\" }, \\\n@@ -102,7 +105,8 @@ struct xfs_bmalloca {\n \t{ XFS_BMAPI_COWFORK,\t\"COWFORK\" }, \\\n \t{ XFS_BMAPI_NODISCARD,\t\"NODISCARD\" }, \\\n \t{ XFS_BMAPI_NORMAP,\t\"NORMAP\" },\\\n-\t{ XFS_BMAPI_EXTSZALIGN,\t\"EXTSZALIGN\" }\n+\t{ XFS_BMAPI_EXTSZALIGN,\t\"EXTSZALIGN\" }, \\\n+\t{ XFS_BMAPI_UNWRITTEN,\t\"UNWRITTEN\" }\n \n \n static inline int xfs_bmapi_aflag(int w)\ndiff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h\nindex dd0ed046fbe9c..8be1b101b1969 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 synthesis 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 185f09f327c00..84d62b7506a9a 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 1d45cf5789e86..932b447190da7 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 e4c3f7b24e952..b4cc673adc8ab 100644\n--- a/fs/xfs/libxfs/xfs_inode_buf.c\n+++ b/fs/xfs/libxfs/xfs_inode_buf.c\n@@ -761,6 +761,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 258ac3d0d486e..5a516bd31c317 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 f0341adbb8790..fd06c3d6cc113 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@@ -1585,6 +1587,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 e5233e31abb75..ebbef3afb7d6e 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 -EFSCORRUPTED:\n+\tcase -ERANGE:\n+\tcase -EBADMSG:\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 f0f073a934133..676b4afd0be4d 100644\n--- a/fs/xfs/scrub/common.h\n+++ b/fs/xfs/scrub/common.h\n@@ -267,6 +267,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 65b13e3119168..d1cdd6b445d01 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 8bc508336aa5f..6c0b7130e227e 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_set_flags(inode, 0, 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 c30e688cfc9f6..a36f840884b49 100644\n--- a/fs/xfs/xfs_aops.c\n+++ b/fs/xfs/xfs_aops.c\n@@ -23,6 +23,8 @@\n #include \"xfs_ioend.h\"\n #include \"xfs_zone_alloc.h\"\n #include \"xfs_rtgroup.h\"\n+#include \"xfs_fsverity.h\"\n+#include \u003clinux/fsverity.h\u003e\n \n struct xfs_writepage_ctx {\n \tstruct iomap_writepage_ctx ctx;\n@@ -172,12 +174,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@@ -265,7 +271,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@@ -412,12 +419,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@@ -441,7 +452,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@@ -454,7 +465,7 @@ xfs_zoned_map_blocks(\n \txfs_iunlock(ip, XFS_ILOCK_EXCL);\n \n \txfs_iomap_set_anon_write(ip, \u0026wpc-\u003eiomap, offset,\n-\t\t\tXFS_FSB_TO_B(mp, count_fsb));\n+\t\t\tXFS_FSB_TO_B(mp, count_fsb), iomap_flags);\n \ttrace_xfs_zoned_map_blocks(ip, offset, wpc-\u003eiomap.length);\n \treturn 0;\n }\n@@ -507,6 +518,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@@ -526,7 +553,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@@ -539,7 +566,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@@ -585,7 +612,7 @@ xfs_bio_submit_read(\n {\n \txfs_ioend_submit_read(iter-\u003einode, ctx-\u003eread_ctx,\n \t\t\tctx-\u003eread_ctx_file_offset,\n-\t\t\tiomap_ioend_flags(\u0026iter-\u003eiomap));\n+\t\t\tiomap_ioend_flags(\u0026iter-\u003eiomap), ctx-\u003evi);\n \tctx-\u003eread_ctx = NULL;\n }\n \n@@ -597,11 +624,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\tpos)\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 xfs_fsverity_is_file_data(ip, pos))\n \t\treturn \u0026xfs_iomap_read_ops;\n \treturn \u0026iomap_bio_read_ops;\n }\n@@ -613,7 +642,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@@ -624,7 +653,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 268d159339d06..74e99d0bee7a1 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,15 @@ xfs_can_free_eofblocks(\n \tif (last_fsb \u003c= end_fsb)\n \t\treturn false;\n \n+\t/*\n+\t * Don't clean fsverity inodes as they already have been cleaned up and\n+\t * are read-only. Skip inodes which have merkle tree being built, the\n+\t * merkle tree is written beyond EOF\n+\t */\n+\tif (fsverity_active(VFS_IC(ip)) ||\n+\t xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))\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\n@@ -579,6 +589,9 @@ xfs_free_eofblocks(\n \tstruct xfs_trans\t*tp;\n \tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n \tint\t\t\terror;\n+\tint\t\t\tbmapi_flags = XFS_BMAPI_NODISCARD;\n+\tbool\t\t\thas_verity =\n+\t\t\tip-\u003ei_diflags2 \u0026 XFS_DIFLAG2_VERITY;\n \n \t/* Attach the dquots to the inode up front. */\n \terror = xfs_qm_dqattach(ip);\n@@ -593,15 +606,20 @@ xfs_free_eofblocks(\n \t *\n \t * Note that this means we also leave speculative preallocations in\n \t * place for preallocated files.\n+\t *\n+\t * Clean up delalloc reservations for fsverity too as those won't be\n+\t * used\n \t */\n-\tif (ip-\u003ei_diflags \u0026 (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND)) {\n+\tif (ip-\u003ei_diflags \u0026 (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND) ||\n+\t\t\thas_verity) {\n \t\tif (ip-\u003ei_delayed_blks) {\n \t\t\txfs_bmap_punch_delalloc_range(ip, XFS_DATA_FORK,\n \t\t\t\tround_up(XFS_ISIZE(ip), mp-\u003em_sb.sb_blocksize),\n \t\t\t\tLLONG_MAX, NULL);\n \t\t}\n \t\txfs_inode_clear_eofblocks_tag(ip);\n-\t\treturn 0;\n+\t\tif (!has_verity)\n+\t\t\treturn 0;\n \t}\n \n \terror = xfs_trans_alloc(mp, \u0026M_RES(mp)-\u003etr_itruncate, 0, 0, 0, \u0026tp);\n@@ -613,6 +631,13 @@ xfs_free_eofblocks(\n \txfs_ilock(ip, XFS_ILOCK_EXCL);\n \txfs_trans_ijoin(tp, ip, 0);\n \n+\t/*\n+\t * While fs-verity writes metadata after EOF, it can leave unwritten\n+\t * preallocations. Clear all that out.\n+\t */\n+\tif (has_verity)\n+\t\tbmapi_flags |= XFS_BMAPI_UNWRITTEN;\n+\n \t/*\n \t * Do not update the on-disk file size. If we update the on-disk file\n \t * size and then the system crashes before the contents of the file are\n@@ -620,7 +645,7 @@ xfs_free_eofblocks(\n \t * bug).\n \t */\n \terror = xfs_itruncate_extents_flags(\u0026tp, ip, XFS_DATA_FORK,\n-\t\t\t\tXFS_ISIZE(ip), XFS_BMAPI_NODISCARD);\n+\t\t\t\tXFS_ISIZE(ip), bmapi_flags);\n \tif (error)\n \t\tgoto err_cancel;\n \ndiff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c\nindex dd3782e9af938..e13390539f042 100644\n--- a/fs/xfs/xfs_file.c\n+++ b/fs/xfs/xfs_file.c\n@@ -38,6 +38,7 @@\n #include \u003clinux/mount.h\u003e\n #include \u003clinux/filelock.h\u003e\n #include \u003clinux/bio-integrity.h\u003e\n+#include \u003clinux/fsverity.h\u003e\n \n static const struct vm_operations_struct xfs_file_vm_ops;\n \n@@ -224,7 +225,8 @@ xfs_dio_read_bounce_submit_io(\n \tloff_t\t\t\tfile_offset)\n {\n \txfs_ioend_submit_read(iter-\u003einode, bio, file_offset,\n-\t\t\tiomap_ioend_flags(\u0026iter-\u003eiomap) | IOMAP_IOEND_DIRECT);\n+\t\t\tiomap_ioend_flags(\u0026iter-\u003eiomap) | IOMAP_IOEND_DIRECT,\n+\t\t\tNULL);\n }\n \n static const struct iomap_dio_ops xfs_dio_read_bounce_ops = {\n@@ -232,6 +234,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@@ -250,6 +271,17 @@ xfs_file_dio_read(\n \tret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED);\n \tif (ret)\n \t\treturn ret;\n+\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\tret = iomap_dio_rw(iocb, to, \u0026xfs_read_iomap_ops,\n \t\t\t\t\u0026xfs_dio_read_bounce_ops, 0, NULL, 0);\n@@ -269,7 +301,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@@ -287,25 +320,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@@ -320,6 +334,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@@ -1772,11 +1794,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 0000000000000..7174b36c8cd3d\n--- /dev/null\n+++ b/fs/xfs/xfs_fsverity.c\n@@ -0,0 +1,489 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Copyright (C) 2026 Red Hat, Inc.\n+ */\n+#include \"xfs_platform.h\"\n+#include \"xfs_fs.h\"\n+#include \"xfs_shared.h\"\n+#include \"xfs_format.h\"\n+#include \"xfs_trans_resv.h\"\n+#include \"xfs_mount.h\"\n+#include \"xfs_inode.h\"\n+#include \"xfs_log_format.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_util.h\"\n+#include \"xfs_icache.h\"\n+#include \u003clinux/fsverity.h\u003e\n+#include \u003clinux/iomap.h\u003e\n+#include \u003clinux/pagemap.h\u003e\n+\n+struct kmem_cache *xfs_fsverity_ioend_cache;\n+mempool_t xfs_fsverity_ioend_pool;\n+\n+#define XFS_FSVERITY_IOEND_POOL_MIN\t128\n+\n+/*\n+ * Back the fsverity ioend allocations with a mempool so that read I/O\n+ * completion always makes forward progress and cannot deadlock\n+ */\n+int\n+xfs_fsverity_init(void)\n+{\n+\treturn mempool_init_slab_pool(\u0026xfs_fsverity_ioend_pool,\n+\t\t\tXFS_FSVERITY_IOEND_POOL_MIN, xfs_fsverity_ioend_cache);\n+}\n+\n+void\n+xfs_fsverity_exit(void)\n+{\n+\tmempool_exit(\u0026xfs_fsverity_ioend_pool);\n+}\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+\txfs_ilock(ip, XFS_ILOCK_EXCL);\n+\t/*\n+\t * Serialize reading of inode-\u003ei_flags with xfs_scrub clearing of this\n+\t * flag if inode is corrupted.\n+\t */\n+\tif (!(inode-\u003ei_flags \u0026 S_VERITY)) {\n+\t\txfs_iunlock(ip, XFS_ILOCK_EXCL);\n+\t\treturn -ENODATA;\n+\t}\n+\terror = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, \u0026rec, \u0026is_empty);\n+\txfs_iunlock(ip, XFS_ILOCK_EXCL);\n+\tif (error)\n+\t\treturn error;\n+\n+\tif (is_empty) {\n+\t\txfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);\n+\t\treturn -ENODATA;\n+\t}\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\txfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);\n+\t\treturn -ENODATA;\n+\t}\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+\txfs_ilock(ip, XFS_MMAPLOCK_EXCL);\n+\terror = xfs_trans_alloc(mp, \u0026M_RES(mp)-\u003etr_itruncate, 0, 0, 0, \u0026tp);\n+\tif (error) {\n+\t\txfs_iunlock(ip, XFS_MMAPLOCK_EXCL);\n+\t\treturn error;\n+\t}\n+\n+\ttruncate_inode_pages(VFS_I(ip)-\u003ei_mapping, XFS_ISIZE(ip));\n+\n+\txfs_ilock(ip, XFS_ILOCK_EXCL);\n+\txfs_trans_ijoin(tp, ip, 0);\n+\n+\t/*\n+\t * We remove 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+\txfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_ILOCK_EXCL);\n+\treturn error;\n+err_cancel:\n+\txfs_trans_cancel(tp);\n+\txfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_ILOCK_EXCL);\n+\treturn error;\n+}\n+\n+static int\n+xfs_fsverity_reset_inode(\n+\tstruct xfs_inode\t*ip)\n+{\n+\tint\t\t\terror;\n+\tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n+\tstruct xfs_trans\t*tp;\n+\n+\terror = xfs_fsverity_delete_metadata(ip);\n+\tif (error)\n+\t\treturn error;\n+\n+\t/*\n+\t * First, let's clean in-memory fsverity flag and then reset it on the\n+\t * disk\n+\t */\n+\tinode_set_flags(VFS_I(ip), 0, S_VERITY);\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\treturn error;\n+\n+\tip-\u003ei_diflags2 \u0026= ~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+\treturn error;\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+\t/*\n+\t * fs-verity stores the Merkle tree past EOF in units of the filesystem\n+\t * block size, so it cannot support realtime files whose allocation unit\n+\t * (extent size) is larger than the block size.\n+\t */\n+\tif (xfs_inode_has_bigrtalloc(ip))\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\tgoto out;\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 * 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+\tif (error)\n+\t\tgoto out;\n+\n+\tinode_set_flags(inode, S_VERITY, S_VERITY);\n+\n+\t/*\n+\t * Proactively remove unwritten extents left by COW preallocations and\n+\t * write preallocation in the merkle tree holes and past descriptor,\n+\t * and any delayed preallocations\n+\t */\n+\terror = xfs_free_eofblocks(ip);\n+out:\n+\tif (error) {\n+\t\tint\terror2;\n+\n+\t\terror2 = xfs_fsverity_reset_inode(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+static inline bool\n+xfs_fsverity_is_hashes_of_zeroed_blocks(\n+\tstruct xfs_inode\t*ip,\n+\tconst void\t\t*buf,\n+\tunsigned int\t\tsize,\n+\tconst u8\t\t*zero_digest,\n+\tunsigned int\t\tdigest_size)\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)\n+\t\treturn false;\n+\t/*\n+\t * First digest is zero_digest\n+\t */\n+\tif (memcmp(buf, zero_digest, digest_size))\n+\t\treturn false;\n+\t/*\n+\t * Every digest is same as previous, thus all are\n+\t * zero_digest\n+\t */\n+\treturn memcmp(buf + digest_size, buf, size - digest_size) == 0;\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+\tif (xfs_fsverity_is_hashes_of_zeroed_blocks(ip, buf, size, zero_digest,\n+\t\t\t\t\t\t digest_size))\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 0000000000000..7a3aee6d45fca\n--- /dev/null\n+++ b/fs/xfs/xfs_fsverity.h\n@@ -0,0 +1,47 @@\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/iomap.h\u003e\n+#include \u003clinux/fsverity.h\u003e\n+#include \u003clinux/mempool.h\u003e\n+\n+#ifdef CONFIG_FS_VERITY\n+extern const struct fsverity_operations xfs_fsverity_ops;\n+int xfs_fsverity_init(void);\n+void xfs_fsverity_exit(void);\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 int xfs_fsverity_init(void)\n+{\n+\treturn 0;\n+}\n+static inline void xfs_fsverity_exit(void)\n+{\n+}\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+struct xfs_fsverity_ioend {\n+\tstruct iomap_ioend\t*ioend;\n+\tstruct work_struct\twork;\n+};\n+\n+extern struct kmem_cache *xfs_fsverity_ioend_cache;\n+extern mempool_t xfs_fsverity_ioend_pool;\n+\n+#endif\t/* __XFS_FSVERITY_H__ */\ndiff --git a/fs/xfs/xfs_health.c b/fs/xfs/xfs_health.c\nindex 239b843e83d42..be66760fb120f 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 ca96ba096359a..2a0e21f0679d5 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 5d023d7aee237..6fef879bb1375 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_ioend.c b/fs/xfs/xfs_ioend.c\nindex e70be5b86f0b7..f4c1bbc03714f 100644\n--- a/fs/xfs/xfs_ioend.c\n+++ b/fs/xfs/xfs_ioend.c\n@@ -18,7 +18,26 @@\n #include \"xfs_ioend.h\"\n #include \"xfs_error.h\"\n #include \"xfs_errortag.h\"\n+#include \"xfs_fsverity.h\"\n #include \u003clinux/bio-integrity.h\u003e\n+#include \u003clinux/fsverity.h\u003e\n+\n+static void\n+xfs_end_fsverity_io_read(\n+\tstruct work_struct\t\t*work)\n+{\n+\tstruct xfs_fsverity_ioend\t*fsv_ioend =\n+\t\t\tcontainer_of(work, struct xfs_fsverity_ioend, work);\n+\tstruct iomap_ioend\t\t*ioend = fsv_ioend-\u003eioend;\n+\tstruct bio\t\t\t*bio = \u0026ioend-\u003eio_bio;\n+\n+\tmempool_free(fsv_ioend, \u0026xfs_fsverity_ioend_pool);\n+\n+\tif (!bio-\u003ebi_status)\n+\t\tfsverity_verify_bio(ioend-\u003eio_vi, bio);\n+\n+\tiomap_finish_ioends(ioend, blk_status_to_errno(bio-\u003ebi_status));\n+}\n \n static void\n xfs_dio_bounce_end_io(\n@@ -93,12 +112,14 @@ xfs_read_bounce_and_resubmit(\n \n static void\n xfs_end_io_read(\n-\tstruct bio\t\t*bio)\n+\tstruct bio\t\t\t*bio)\n {\n-\tstruct iomap_ioend\t*ioend = iomap_ioend_from_bio(bio);\n-\tstruct xfs_inode\t*ip = XFS_I(ioend-\u003eio_inode);\n-\tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n-\tint\t\t\terror = blk_status_to_errno(bio-\u003ebi_status);\n+\tstruct iomap_ioend\t\t*ioend = iomap_ioend_from_bio(bio);\n+\tstruct xfs_inode\t\t*ip = XFS_I(ioend-\u003eio_inode);\n+\tstruct xfs_mount\t\t*mp = ip-\u003ei_mount;\n+\tint\t\t\t\terror =\n+\t\t\tblk_status_to_errno(bio-\u003ebi_status);\n+\tstruct xfs_fsverity_ioend\t*fsv_ioend;\n \n \tif (!error \u0026\u0026 (ioend-\u003eio_flags \u0026 IOMAP_IOEND_INTEGRITY)) {\n \t\terror = iomap_ioend_integrity_verify(ioend);\n@@ -117,6 +138,24 @@ xfs_end_io_read(\n \t\t}\n \t}\n \n+\t/*\n+\t * If we have fsverity on this bio, we need to run fsverity verification\n+\t * of data folios from a separate fsverity workqueue. This is necessary\n+\t * to avoid deadlocking due to fsverity issuing more reads of fsverity\n+\t * metadata which would be processed by the same worker in the BIO\n+\t * completion workqueue.\n+\t */\n+\tif (IS_ENABLED(CONFIG_FS_VERITY) \u0026\u0026 !error \u0026\u0026 ioend-\u003eio_vi \u0026\u0026\n+\t\t\txfs_fsverity_is_file_data(ip, ioend-\u003eio_offset)) {\n+\t\tfsv_ioend = mempool_alloc(\u0026xfs_fsverity_ioend_pool,\n+\t\t\t\tGFP_NOFS);\n+\t\tfsv_ioend-\u003eioend = ioend;\n+\t\tINIT_WORK(\u0026fsv_ioend-\u003ework, xfs_end_fsverity_io_read);\n+\n+\t\tfsverity_enqueue_verify_work(\u0026fsv_ioend-\u003ework);\n+\t\treturn;\n+\t}\n+\n \tiomap_finish_ioends(ioend, error);\n }\n \n@@ -125,13 +164,15 @@ xfs_ioend_submit_read(\n \tstruct inode\t\t*inode,\n \tstruct bio\t\t*bio,\n \tloff_t\t\t\tfile_offset,\n-\tu16\t\t\tioend_flags)\n+\tu16\t\t\tioend_flags,\n+\tstruct fsverity_info\t*vi)\n {\n \tstruct xfs_inode\t*ip = XFS_I(inode);\n \tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n \tstruct iomap_ioend\t*ioend;\n \n \tioend = iomap_init_ioend(inode, bio, file_offset, ioend_flags);\n+\tioend-\u003eio_vi = vi;\n \tif ((ioend_flags \u0026 IOMAP_IOEND_DIRECT) \u0026\u0026\n \t READ_ONCE(mp-\u003em_read_bounce) == XFS_READ_BOUNCE_ALWAYS) {\n \t\tiomap_bounce_read(ioend, bdev_logical_block_size(bio-\u003ebi_bdev),\ndiff --git a/fs/xfs/xfs_ioend.h b/fs/xfs/xfs_ioend.h\nindex 7c2a1ea3e6edd..992c248a693aa 100644\n--- a/fs/xfs/xfs_ioend.h\n+++ b/fs/xfs/xfs_ioend.h\n@@ -2,6 +2,8 @@\n #ifndef __XFS_IOEND_H\n #define __XFS_IOEND_H\n \n+#include \u003clinux/fsverity.h\u003e\n+\n /*\n * Fast and loose check if this write could update the on-disk inode size.\n */\n@@ -13,6 +15,6 @@ static inline bool xfs_ioend_is_append(struct iomap_ioend *ioend)\n \n void xfs_end_bio(struct bio *bio);\n void xfs_ioend_submit_read(struct inode *inode, struct bio *bio,\n-\t\tloff_t file_offset, u16 ioend_flags);\n+\t\tloff_t file_offset, u16 ioend_flags, struct fsverity_info *vi);\n \n #endif /* __XFS_IOEND_H */\ndiff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c\nindex 7c6238fed61e4..ccc8b8fabab3f 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@@ -883,6 +885,9 @@ xfs_direct_write_iomap_begin(\n \tif (flags \u0026 IOMAP_ATOMIC)\n \t\tiomap_flags |= IOMAP_F_ATOMIC_BIO;\n \n+\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))\n+\t\tiomap_flags |= IOMAP_F_FSVERITY;\n+\n \t/*\n \t * COW writes may allocate delalloc space or convert unwritten COW\n \t * extents, so we need to make sure to take the lock exclusively here.\n@@ -1023,7 +1028,8 @@ xfs_direct_write_iomap_begin(\n \ttrace_xfs_iomap_found(ip, offset, length - offset, XFS_COW_FORK, \u0026cmap);\n \tif (imap.br_startblock != HOLESTARTBLOCK) {\n \t\tseq = xfs_iomap_inode_sequence(ip, 0);\n-\t\terror = xfs_bmbt_to_iomap(ip, srcmap, \u0026imap, flags, 0, seq);\n+\t\terror = xfs_bmbt_to_iomap(ip, srcmap, \u0026imap, flags,\n+\t\t\t\tiomap_flags \u0026 IOMAP_F_FSVERITY, seq);\n \t\tif (error)\n \t\t\tgoto out_unlock;\n \t}\n@@ -1083,7 +1089,7 @@ xfs_zoned_direct_write_iomap_begin(\n \t\t\treturn error;\n \t}\n \n-\txfs_iomap_set_anon_write(ip, iomap, offset, length);\n+\txfs_iomap_set_anon_write(ip, iomap, offset, length, 0);\n \treturn 0;\n }\n \n@@ -1595,7 +1601,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@@ -1605,7 +1612,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@@ -1668,7 +1674,8 @@ xfs_zoned_buffered_write_iomap_begin(\n \t\t\t\tsmap.br_startoff + smap.br_blockcount);\n \t\t\txfs_trim_extent(\u0026smap, offset_fsb,\n \t\t\t\t\tend_fsb - offset_fsb);\n-\t\t\terror = xfs_bmbt_to_iomap(ip, srcmap, \u0026smap, flags, 0,\n+\t\t\terror = xfs_bmbt_to_iomap(ip, srcmap, \u0026smap, flags,\n+\t\t\t\t\tiomap_flags,\n \t\t\t\t\txfs_iomap_inode_sequence(ip, 0));\n \t\t\tif (error)\n \t\t\t\tgoto out_unlock;\n@@ -1678,6 +1685,8 @@ xfs_zoned_buffered_write_iomap_begin(\n \tif (!ip-\u003ei_cowfp)\n \t\txfs_ifork_init_cow(ip);\n \n+\tiomap_flags |= IOMAP_F_SHARED;\n+\n \tif (!xfs_iext_lookup_extent(ip, ip-\u003ei_cowfp, offset_fsb, \u0026icur, \u0026got))\n \t\tgot.br_startoff = end_fsb;\n \tif (got.br_startoff \u003c= offset_fsb) {\n@@ -1809,9 +1818,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@@ -2095,7 +2107,8 @@ xfs_buffered_write_iomap_begin(\n \n found_cow:\n \tif (imap.br_startoff \u003c= offset_fsb) {\n-\t\terror = xfs_bmbt_to_iomap(ip, srcmap, \u0026imap, flags, 0,\n+\t\terror = xfs_bmbt_to_iomap(ip, srcmap, \u0026imap, flags,\n+\t\t\t\tiomap_flags \u0026 IOMAP_F_FSVERITY,\n \t\t\t\txfs_iomap_inode_sequence(ip, 0));\n \t\tif (error)\n \t\t\tgoto out_unlock;\n@@ -2199,12 +2212,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@@ -2218,8 +2236,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 static DEFINE_IOMAP_ITER_NEXT(xfs_read_iomap_next, xfs_read_iomap_begin);\ndiff --git a/fs/xfs/xfs_iomap.h b/fs/xfs/xfs_iomap.h\nindex f2520a9b3a13e..d3ca0f2afc9f7 100644\n--- a/fs/xfs/xfs_iomap.h\n+++ b/fs/xfs/xfs_iomap.h\n@@ -34,13 +34,14 @@ xfs_iomap_set_anon_write(\n \tstruct xfs_inode\t\t*ip,\n \tstruct iomap\t\t\t*iomap,\n \tloff_t\t\t\t\toffset,\n-\tloff_t\t\t\t\tlength)\n+\tloff_t\t\t\t\tlength,\n+\tu16\t\t\t\tiomap_flags)\n {\n \tiomap-\u003etype = IOMAP_MAPPED;\n \tiomap-\u003ebdev = ip-\u003ei_mount-\u003em_rtdev_targp-\u003ebt_bdev;\n \tiomap-\u003eoffset = offset;\n \tiomap-\u003elength = length;\n-\tiomap-\u003eflags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY;\n+\tiomap-\u003eflags = iomap_flags | IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY;\n \tif (bdev_has_integrity_csum(iomap-\u003ebdev))\n \t\tiomap-\u003eflags |= IOMAP_F_INTEGRITY;\n }\ndiff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c\nindex f67a541ef35a0..8c4a979cd66e8 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@@ -579,6 +580,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@@ -1369,6 +1374,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@@ -1397,12 +1404,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 0243e509a4689..44b13d46049c6 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 811b885f41c35..364fcc7e42b08 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 894ff2f4ecbd9..a3074f060928a 100644\n--- a/fs/xfs/xfs_mount.h\n+++ b/fs/xfs/xfs_mount.h\n@@ -403,6 +403,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@@ -460,6 +461,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@@ -597,6 +599,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 fce1d2905c940..560493e2b78c0 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@@ -1736,6 +1737,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@@ -1988,6 +1992,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;\n@@ -2516,8 +2523,20 @@ xfs_init_caches(void)\n \tif (!xfs_parent_args_cache)\n \t\tgoto out_destroy_xmi_cache;\n \n+#ifdef CONFIG_FS_VERITY\n+\txfs_fsverity_ioend_cache = kmem_cache_create(\"xfs_fsverity_ioend\",\n+\t\t\t\t\t sizeof(struct xfs_fsverity_ioend),\n+\t\t\t\t\t 0, 0, NULL);\n+\tif (!xfs_fsverity_ioend_cache)\n+\t\tgoto out_destroy_parent_args_cache;\n+#endif\n+\n \treturn 0;\n \n+#ifdef CONFIG_FS_VERITY\n+ out_destroy_parent_args_cache:\n+\tkmem_cache_destroy(xfs_parent_args_cache);\n+#endif\n out_destroy_xmi_cache:\n \tkmem_cache_destroy(xfs_xmi_cache);\n out_destroy_xmd_cache:\n@@ -2580,6 +2599,9 @@ xfs_destroy_caches(void)\n \t * destroy caches.\n \t */\n \trcu_barrier();\n+#ifdef CONFIG_FS_VERITY\n+\tkmem_cache_destroy(xfs_fsverity_ioend_cache);\n+#endif\n \tkmem_cache_destroy(xfs_parent_args_cache);\n \tkmem_cache_destroy(xfs_xmd_cache);\n \tkmem_cache_destroy(xfs_xmi_cache);\n@@ -2660,10 +2682,14 @@ init_xfs_fs(void)\n \tif (error)\n \t\tgoto out;\n \n-\terror = xfs_init_workqueues();\n+\terror = xfs_fsverity_init();\n \tif (error)\n \t\tgoto out_destroy_caches;\n \n+\terror = xfs_init_workqueues();\n+\tif (error)\n+\t\tgoto out_fsverity_exit;\n+\n \terror = xfs_mru_cache_init();\n \tif (error)\n \t\tgoto out_destroy_wq;\n@@ -2740,6 +2766,8 @@ init_xfs_fs(void)\n \txfs_mru_cache_uninit();\n out_destroy_wq:\n \txfs_destroy_workqueues();\n+ out_fsverity_exit:\n+\txfs_fsverity_exit();\n out_destroy_caches:\n \txfs_destroy_caches();\n out:\n@@ -2763,6 +2791,7 @@ exit_xfs_fs(void)\n \txfs_cleanup_procfs();\n \txfs_mru_cache_uninit();\n \txfs_destroy_workqueues();\n+\txfs_fsverity_exit();\n \txfs_destroy_caches();\n \txfs_uuid_table_free();\n }\ndiff --git a/include/linux/fsverity.h b/include/linux/fsverity.h\nindex 6c467ded9751f..f9433332c274b 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/10/03 09:23 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": [
"XFS_FS",
"FS_VERITY",
"XFS_ONLINE_SCRUB",
"XFS_ONLINE_REPAIR"
],
"FocusSymbols": [
"xfs_fsverity_begin_enable",
"xfs_fsverity_end_enable",
"xfs_fsverity_write_merkle",
"xfs_fsverity_read_merkle",
"xfs_fsverity_get_descriptor",
"xfs_end_fsverity_io_read",
"xchk_inode_setup_verity",
"xrep_inode_verity",
"fsverity_ensure_verity_info",
"fsverity_pagecache_read"
],
"Reasoning": "This patch series introduces fs-verity support to XFS, exposes fs-verity ioctls (FS_IOC_ENABLE_VERITY, FS_IOC_MEASURE_VERITY, FS_IOC_READ_VERITY_METADATA) on XFS files, adds Merkle tree construction, reading, and metadata handling beyond EOF, wires up async read verification, adds online scrub and repair checks for fs-verity inodes, and exports common fs-verity helper functions. All of these paths are reachable from userspace and warrant fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any '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: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 94f00f55bd2265b867b7373031319902086b56af
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Oct 3 09:23:39 2026 +0000
syz-cluster: applied patch under review
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 81a9af9582827..b9a1b629a5333 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -8236,7 +8236,6 @@ static int btrfs_getattr(const struct mnt_idmap *idmap,
struct inode *inode = d_inode(path->dentry);
u32 blocksize = btrfs_sb(inode->i_sb)->sectorsize;
u32 bi_flags = BTRFS_I(inode)->flags;
- u32 bi_ro_flags = BTRFS_I(inode)->ro_flags;
stat->result_mask |= STATX_BTIME;
stat->btime.tv_sec = BTRFS_I(inode)->i_otime_sec;
@@ -8249,8 +8248,6 @@ static int btrfs_getattr(const struct mnt_idmap *idmap,
stat->attributes |= STATX_ATTR_IMMUTABLE;
if (bi_flags & BTRFS_INODE_NODUMP)
stat->attributes |= STATX_ATTR_NODUMP;
- if (bi_ro_flags & BTRFS_INODE_RO_VERITY)
- stat->attributes |= STATX_ATTR_VERITY;
stat->attributes_mask |= (STATX_ATTR_APPEND |
STATX_ATTR_COMPRESSED |
diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c
index 4e0ab58422741..c572acacb4d41 100644
--- a/fs/btrfs/verity.c
+++ b/fs/btrfs/verity.c
@@ -779,11 +779,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/inode.c b/fs/ext4/inode.c
index cb68bf50a3d6a..9a9ec02dce9fd 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -6318,15 +6318,12 @@ int ext4_getattr(const struct mnt_idmap *idmap, const struct path *path,
stat->attributes |= STATX_ATTR_IMMUTABLE;
if (flags & EXT4_NODUMP_FL)
stat->attributes |= STATX_ATTR_NODUMP;
- if (flags & EXT4_VERITY_FL)
- stat->attributes |= STATX_ATTR_VERITY;
stat->attributes_mask |= (STATX_ATTR_APPEND |
STATX_ATTR_COMPRESSED |
STATX_ATTR_ENCRYPTED |
STATX_ATTR_IMMUTABLE |
- STATX_ATTR_NODUMP |
- STATX_ATTR_VERITY);
+ STATX_ATTR_NODUMP);
generic_fillattr(idmap, request_mask, inode, stat);
return 0;
diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c
index ca61da53f313f..ac5c133f55292 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/file.c b/fs/f2fs/file.c
index 8596c3bc4704a..0c759cc990546 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -1075,15 +1075,12 @@ int f2fs_getattr(const struct mnt_idmap *idmap, const struct path *path,
stat->attributes |= STATX_ATTR_IMMUTABLE;
if (flags & F2FS_NODUMP_FL)
stat->attributes |= STATX_ATTR_NODUMP;
- if (IS_VERITY(inode))
- stat->attributes |= STATX_ATTR_VERITY;
stat->attributes_mask |= (STATX_ATTR_COMPRESSED |
STATX_ATTR_APPEND |
STATX_ATTR_ENCRYPTED |
STATX_ATTR_IMMUTABLE |
- STATX_ATTR_NODUMP |
- STATX_ATTR_VERITY);
+ STATX_ATTR_NODUMP);
generic_fillattr(idmap, request_mask, inode, stat);
diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c
index 39f4825154452..f3a4617656db9 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 81af4364e33ae..833e03810b24c 100644
--- a/fs/file_attr.c
+++ b/fs/file_attr.c
@@ -235,10 +235,15 @@ static int fileattr_set_prepare(struct inode *inode,
/*
* It is only valid to set the DAX flag on regular files and
* directories on filesystems.
+ *
+ * DAX and fsverity are incompatible.
*/
- if ((fa->fsx_xflags & FS_XFLAG_DAX) &&
- !(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)))
- return -EINVAL;
+ if (fa->fsx_xflags & FS_XFLAG_DAX) {
+ if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)))
+ return -EINVAL;
+ if (old_ma->fsx_xflags & FS_XFLAG_VERITY)
+ return -EINVAL;
+ }
/* Extent size hints of zero turn off the flags. */
if (fa->fsx_extsize == 0)
diff --git a/fs/stat.c b/fs/stat.c
index a9b7383d538da..a2e63c6c45b77 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -203,8 +203,12 @@ int vfs_getattr_nosec(const struct path *path, struct kstat *stat,
if (IS_DAX(inode))
stat->attributes |= STATX_ATTR_DAX;
+ if (IS_VERITY(inode))
+ stat->attributes |= STATX_ATTR_VERITY;
+
stat->attributes_mask |= (STATX_ATTR_AUTOMOUNT |
- STATX_ATTR_DAX);
+ STATX_ATTR_DAX |
+ STATX_ATTR_VERITY);
idmap = mnt_idmap(path->mnt);
if (inode->i_op->getattr) {
diff --git a/fs/verity/enable.c b/fs/verity/enable.c
index 42dfed1ce0ce6..ad4ff71d7dd95 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 d0c56a7faa3bb..4f06697f5bf4d 100644
--- a/fs/verity/open.c
+++ b/fs/verity/open.c
@@ -347,7 +347,28 @@ 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.
+ *
+ * In case filesystem supports both fscrypt and fsverity, this should be called
+ * after fscrypt's encryption key is set up. Otherwise, the fsverity metadata is
+ * still encrypted. See fscrypt_file_open().
+ *
+ * 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 +404,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 99f5f53eea985..9d82e6b74ba12 100644
--- a/fs/verity/pagecache.c
+++ b/fs/verity/pagecache.c
@@ -78,3 +78,36 @@ void fsverity_fill_zerohash(struct folio *folio, size_t offset, size_t len,
vi->tree_params.digest_size);
}
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 4004a1d428751..db8c350234bb7 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 399a207f2d0e7..dd712c5218625 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -150,6 +150,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 d64defeda645e..6f0094445cea5 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,9 @@ 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) &&
+ offset >= 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 +4454,10 @@ 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) &&
+ offset >= 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));
@@ -5269,6 +5278,10 @@ __xfs_bunmapi(
if (del.br_startoff + del.br_blockcount > end + 1)
del.br_blockcount = end + 1 - del.br_startoff;
+ if ((flags & XFS_BMAPI_UNWRITTEN) &&
+ del.br_state != XFS_EXT_UNWRITTEN)
+ goto skip;
+
if (!isrt || (flags & XFS_BMAPI_REMAP))
goto delete;
@@ -5403,7 +5416,7 @@ __xfs_bunmapi(
if (error)
goto error0;
}
-
+skip:
end = del.br_startoff - 1;
nodelete:
/*
diff --git a/fs/xfs/libxfs/xfs_bmap.h b/fs/xfs/libxfs/xfs_bmap.h
index d5f2729305fad..0f36431d99368 100644
--- a/fs/xfs/libxfs/xfs_bmap.h
+++ b/fs/xfs/libxfs/xfs_bmap.h
@@ -90,6 +90,9 @@ struct xfs_bmalloca {
/* Try to align allocations to the extent size hint */
#define XFS_BMAPI_EXTSZALIGN (1u << 11)
+/* Process unwritten extents only. Used for unmapping */
+#define XFS_BMAPI_UNWRITTEN (1u << 12)
+
#define XFS_BMAPI_FLAGS \
{ XFS_BMAPI_ENTIRE, "ENTIRE" }, \
{ XFS_BMAPI_METADATA, "METADATA" }, \
@@ -102,7 +105,8 @@ struct xfs_bmalloca {
{ XFS_BMAPI_COWFORK, "COWFORK" }, \
{ XFS_BMAPI_NODISCARD, "NODISCARD" }, \
{ XFS_BMAPI_NORMAP, "NORMAP" },\
- { XFS_BMAPI_EXTSZALIGN, "EXTSZALIGN" }
+ { XFS_BMAPI_EXTSZALIGN, "EXTSZALIGN" }, \
+ { XFS_BMAPI_UNWRITTEN, "UNWRITTEN" }
static inline int xfs_bmapi_aflag(int w)
diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h
index dd0ed046fbe9c..8be1b101b1969 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 synthesis 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 185f09f327c00..84d62b7506a9a 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 1d45cf5789e86..932b447190da7 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 e4c3f7b24e952..b4cc673adc8ab 100644
--- a/fs/xfs/libxfs/xfs_inode_buf.c
+++ b/fs/xfs/libxfs/xfs_inode_buf.c
@@ -761,6 +761,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 258ac3d0d486e..5a516bd31c317 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 f0341adbb8790..fd06c3d6cc113 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)
@@ -1585,6 +1587,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 e5233e31abb75..ebbef3afb7d6e 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 -EFSCORRUPTED:
+ case -ERANGE:
+ case -EBADMSG:
+ /*
+ * 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 f0f073a934133..676b4afd0be4d 100644
--- a/fs/xfs/scrub/common.h
+++ b/fs/xfs/scrub/common.h
@@ -267,6 +267,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 65b13e3119168..d1cdd6b445d01 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 8bc508336aa5f..6c0b7130e227e 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_set_flags(inode, 0, 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 c30e688cfc9f6..a36f840884b49 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -23,6 +23,8 @@
#include "xfs_ioend.h"
#include "xfs_zone_alloc.h"
#include "xfs_rtgroup.h"
+#include "xfs_fsverity.h"
+#include <linux/fsverity.h>
struct xfs_writepage_ctx {
struct iomap_writepage_ctx ctx;
@@ -172,12 +174,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
@@ -265,7 +271,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:
@@ -412,12 +419,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.
@@ -441,7 +452,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);
@@ -454,7 +465,7 @@ xfs_zoned_map_blocks(
xfs_iunlock(ip, XFS_ILOCK_EXCL);
xfs_iomap_set_anon_write(ip, &wpc->iomap, offset,
- XFS_FSB_TO_B(mp, count_fsb));
+ XFS_FSB_TO_B(mp, count_fsb), iomap_flags);
trace_xfs_zoned_map_blocks(ip, offset, wpc->iomap.length);
return 0;
}
@@ -507,6 +518,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,
@@ -526,7 +553,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;
@@ -539,7 +566,7 @@ xfs_vm_writepages(
},
};
- return iomap_writepages(&wpc.ctx);
+ return xfs_iomap_writepages(ip, &wpc.ctx);
}
}
@@ -585,7 +612,7 @@ xfs_bio_submit_read(
{
xfs_ioend_submit_read(iter->inode, ctx->read_ctx,
ctx->read_ctx_file_offset,
- iomap_ioend_flags(&iter->iomap));
+ iomap_ioend_flags(&iter->iomap), ctx->vi);
ctx->read_ctx = NULL;
}
@@ -597,11 +624,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 pos)
{
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, pos))
return &xfs_iomap_read_ops;
return &iomap_bio_read_ops;
}
@@ -613,7 +642,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;
}
@@ -624,7 +653,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 268d159339d06..74e99d0bee7a1 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,15 @@ xfs_can_free_eofblocks(
if (last_fsb <= end_fsb)
return false;
+ /*
+ * Don't clean fsverity inodes as they already have been cleaned up and
+ * are read-only. Skip inodes which have merkle tree being built, the
+ * merkle tree is written beyond EOF
+ */
+ if (fsverity_active(VFS_IC(ip)) ||
+ xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+ 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
@@ -579,6 +589,9 @@ xfs_free_eofblocks(
struct xfs_trans *tp;
struct xfs_mount *mp = ip->i_mount;
int error;
+ int bmapi_flags = XFS_BMAPI_NODISCARD;
+ bool has_verity =
+ ip->i_diflags2 & XFS_DIFLAG2_VERITY;
/* Attach the dquots to the inode up front. */
error = xfs_qm_dqattach(ip);
@@ -593,15 +606,20 @@ xfs_free_eofblocks(
*
* Note that this means we also leave speculative preallocations in
* place for preallocated files.
+ *
+ * Clean up delalloc reservations for fsverity too as those won't be
+ * used
*/
- if (ip->i_diflags & (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND)) {
+ if (ip->i_diflags & (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND) ||
+ has_verity) {
if (ip->i_delayed_blks) {
xfs_bmap_punch_delalloc_range(ip, XFS_DATA_FORK,
round_up(XFS_ISIZE(ip), mp->m_sb.sb_blocksize),
LLONG_MAX, NULL);
}
xfs_inode_clear_eofblocks_tag(ip);
- return 0;
+ if (!has_verity)
+ return 0;
}
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp);
@@ -613,6 +631,13 @@ xfs_free_eofblocks(
xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_trans_ijoin(tp, ip, 0);
+ /*
+ * While fs-verity writes metadata after EOF, it can leave unwritten
+ * preallocations. Clear all that out.
+ */
+ if (has_verity)
+ bmapi_flags |= XFS_BMAPI_UNWRITTEN;
+
/*
* Do not update the on-disk file size. If we update the on-disk file
* size and then the system crashes before the contents of the file are
@@ -620,7 +645,7 @@ xfs_free_eofblocks(
* bug).
*/
error = xfs_itruncate_extents_flags(&tp, ip, XFS_DATA_FORK,
- XFS_ISIZE(ip), XFS_BMAPI_NODISCARD);
+ XFS_ISIZE(ip), bmapi_flags);
if (error)
goto err_cancel;
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index dd3782e9af938..e13390539f042 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -38,6 +38,7 @@
#include <linux/mount.h>
#include <linux/filelock.h>
#include <linux/bio-integrity.h>
+#include <linux/fsverity.h>
static const struct vm_operations_struct xfs_file_vm_ops;
@@ -224,7 +225,8 @@ xfs_dio_read_bounce_submit_io(
loff_t file_offset)
{
xfs_ioend_submit_read(iter->inode, bio, file_offset,
- iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT);
+ iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT,
+ NULL);
}
static const struct iomap_dio_ops xfs_dio_read_bounce_ops = {
@@ -232,6 +234,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,
@@ -250,6 +271,17 @@ 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)) {
ret = iomap_dio_rw(iocb, to, &xfs_read_iomap_ops,
&xfs_dio_read_bounce_ops, 0, NULL, 0);
@@ -269,7 +301,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);
@@ -287,25 +320,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,
@@ -320,6 +334,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)
@@ -1772,11 +1794,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 0000000000000..7174b36c8cd3d
--- /dev/null
+++ b/fs/xfs/xfs_fsverity.c
@@ -0,0 +1,489 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ */
+#include "xfs_platform.h"
+#include "xfs_fs.h"
+#include "xfs_shared.h"
+#include "xfs_format.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_inode.h"
+#include "xfs_log_format.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_util.h"
+#include "xfs_icache.h"
+#include <linux/fsverity.h>
+#include <linux/iomap.h>
+#include <linux/pagemap.h>
+
+struct kmem_cache *xfs_fsverity_ioend_cache;
+mempool_t xfs_fsverity_ioend_pool;
+
+#define XFS_FSVERITY_IOEND_POOL_MIN 128
+
+/*
+ * Back the fsverity ioend allocations with a mempool so that read I/O
+ * completion always makes forward progress and cannot deadlock
+ */
+int
+xfs_fsverity_init(void)
+{
+ return mempool_init_slab_pool(&xfs_fsverity_ioend_pool,
+ XFS_FSVERITY_IOEND_POOL_MIN, xfs_fsverity_ioend_cache);
+}
+
+void
+xfs_fsverity_exit(void)
+{
+ mempool_exit(&xfs_fsverity_ioend_pool);
+}
+
+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;
+
+ xfs_ilock(ip, XFS_ILOCK_EXCL);
+ /*
+ * Serialize reading of inode->i_flags with xfs_scrub clearing of this
+ * flag if inode is corrupted.
+ */
+ if (!(inode->i_flags & S_VERITY)) {
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ return -ENODATA;
+ }
+ error = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, &rec, &is_empty);
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ if (error)
+ return error;
+
+ if (is_empty) {
+ xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);
+ 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)) {
+ xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);
+ 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;
+
+ xfs_ilock(ip, XFS_MMAPLOCK_EXCL);
+ error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp);
+ if (error) {
+ xfs_iunlock(ip, XFS_MMAPLOCK_EXCL);
+ return error;
+ }
+
+ truncate_inode_pages(VFS_I(ip)->i_mapping, XFS_ISIZE(ip));
+
+ xfs_ilock(ip, XFS_ILOCK_EXCL);
+ xfs_trans_ijoin(tp, ip, 0);
+
+ /*
+ * We remove 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);
+ xfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_ILOCK_EXCL);
+ return error;
+err_cancel:
+ xfs_trans_cancel(tp);
+ xfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_ILOCK_EXCL);
+ return error;
+}
+
+static int
+xfs_fsverity_reset_inode(
+ struct xfs_inode *ip)
+{
+ int error;
+ struct xfs_mount *mp = ip->i_mount;
+ struct xfs_trans *tp;
+
+ error = xfs_fsverity_delete_metadata(ip);
+ if (error)
+ return error;
+
+ /*
+ * First, let's clean in-memory fsverity flag and then reset it on the
+ * disk
+ */
+ inode_set_flags(VFS_I(ip), 0, S_VERITY);
+
+ /*
+ * Set fsverity inode flag
+ */
+ error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_ichange,
+ 0, 0, false, &tp);
+ if (error)
+ return error;
+
+ 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);
+ 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;
+
+ /*
+ * fs-verity stores the Merkle tree past EOF in units of the filesystem
+ * block size, so it cannot support realtime files whose allocation unit
+ * (extent size) is larger than the block size.
+ */
+ if (xfs_inode_has_bigrtalloc(ip))
+ 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)
+ 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;
+
+ /*
+ * 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)
+ goto out;
+
+ inode_set_flags(inode, S_VERITY, S_VERITY);
+
+ /*
+ * Proactively remove unwritten extents left by COW preallocations and
+ * write preallocation in the merkle tree holes and past descriptor,
+ * and any delayed preallocations
+ */
+ error = xfs_free_eofblocks(ip);
+out:
+ if (error) {
+ int error2;
+
+ error2 = xfs_fsverity_reset_inode(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);
+}
+
+static inline bool
+xfs_fsverity_is_hashes_of_zeroed_blocks(
+ struct xfs_inode *ip,
+ const void *buf,
+ unsigned int size,
+ const u8 *zero_digest,
+ unsigned int digest_size)
+{
+ /*
+ * 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)
+ return false;
+ /*
+ * First digest is zero_digest
+ */
+ if (memcmp(buf, zero_digest, digest_size))
+ return false;
+ /*
+ * Every digest is same as previous, thus all are
+ * zero_digest
+ */
+ return memcmp(buf + digest_size, buf, size - digest_size) == 0;
+}
+
+/*
+ * 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 (xfs_fsverity_is_hashes_of_zeroed_blocks(ip, buf, size, zero_digest,
+ digest_size))
+ 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 0000000000000..7a3aee6d45fca
--- /dev/null
+++ b/fs/xfs/xfs_fsverity.h
@@ -0,0 +1,47 @@
+// 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/iomap.h>
+#include <linux/fsverity.h>
+#include <linux/mempool.h>
+
+#ifdef CONFIG_FS_VERITY
+extern const struct fsverity_operations xfs_fsverity_ops;
+int xfs_fsverity_init(void);
+void xfs_fsverity_exit(void);
+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 int xfs_fsverity_init(void)
+{
+ return 0;
+}
+static inline void xfs_fsverity_exit(void)
+{
+}
+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 */
+
+struct xfs_fsverity_ioend {
+ struct iomap_ioend *ioend;
+ struct work_struct work;
+};
+
+extern struct kmem_cache *xfs_fsverity_ioend_cache;
+extern mempool_t xfs_fsverity_ioend_pool;
+
+#endif /* __XFS_FSVERITY_H__ */
diff --git a/fs/xfs/xfs_health.c b/fs/xfs/xfs_health.c
index 239b843e83d42..be66760fb120f 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 ca96ba096359a..2a0e21f0679d5 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 5d023d7aee237..6fef879bb1375 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_ioend.c b/fs/xfs/xfs_ioend.c
index e70be5b86f0b7..f4c1bbc03714f 100644
--- a/fs/xfs/xfs_ioend.c
+++ b/fs/xfs/xfs_ioend.c
@@ -18,7 +18,26 @@
#include "xfs_ioend.h"
#include "xfs_error.h"
#include "xfs_errortag.h"
+#include "xfs_fsverity.h"
#include <linux/bio-integrity.h>
+#include <linux/fsverity.h>
+
+static void
+xfs_end_fsverity_io_read(
+ struct work_struct *work)
+{
+ struct xfs_fsverity_ioend *fsv_ioend =
+ container_of(work, struct xfs_fsverity_ioend, work);
+ struct iomap_ioend *ioend = fsv_ioend->ioend;
+ struct bio *bio = &ioend->io_bio;
+
+ mempool_free(fsv_ioend, &xfs_fsverity_ioend_pool);
+
+ if (!bio->bi_status)
+ fsverity_verify_bio(ioend->io_vi, bio);
+
+ iomap_finish_ioends(ioend, blk_status_to_errno(bio->bi_status));
+}
static void
xfs_dio_bounce_end_io(
@@ -93,12 +112,14 @@ xfs_read_bounce_and_resubmit(
static void
xfs_end_io_read(
- struct bio *bio)
+ struct bio *bio)
{
- struct iomap_ioend *ioend = iomap_ioend_from_bio(bio);
- struct xfs_inode *ip = XFS_I(ioend->io_inode);
- struct xfs_mount *mp = ip->i_mount;
- int error = blk_status_to_errno(bio->bi_status);
+ struct iomap_ioend *ioend = iomap_ioend_from_bio(bio);
+ struct xfs_inode *ip = XFS_I(ioend->io_inode);
+ struct xfs_mount *mp = ip->i_mount;
+ int error =
+ blk_status_to_errno(bio->bi_status);
+ struct xfs_fsverity_ioend *fsv_ioend;
if (!error && (ioend->io_flags & IOMAP_IOEND_INTEGRITY)) {
error = iomap_ioend_integrity_verify(ioend);
@@ -117,6 +138,24 @@ xfs_end_io_read(
}
}
+ /*
+ * If we have fsverity on this bio, we need to run fsverity verification
+ * of data folios from a separate fsverity workqueue. This is necessary
+ * to avoid deadlocking due to fsverity issuing more reads of fsverity
+ * metadata which would be processed by the same worker in the BIO
+ * completion workqueue.
+ */
+ if (IS_ENABLED(CONFIG_FS_VERITY) && !error && ioend->io_vi &&
+ xfs_fsverity_is_file_data(ip, ioend->io_offset)) {
+ fsv_ioend = mempool_alloc(&xfs_fsverity_ioend_pool,
+ GFP_NOFS);
+ fsv_ioend->ioend = ioend;
+ INIT_WORK(&fsv_ioend->work, xfs_end_fsverity_io_read);
+
+ fsverity_enqueue_verify_work(&fsv_ioend->work);
+ return;
+ }
+
iomap_finish_ioends(ioend, error);
}
@@ -125,13 +164,15 @@ xfs_ioend_submit_read(
struct inode *inode,
struct bio *bio,
loff_t file_offset,
- u16 ioend_flags)
+ u16 ioend_flags,
+ struct fsverity_info *vi)
{
struct xfs_inode *ip = XFS_I(inode);
struct xfs_mount *mp = ip->i_mount;
struct iomap_ioend *ioend;
ioend = iomap_init_ioend(inode, bio, file_offset, ioend_flags);
+ ioend->io_vi = vi;
if ((ioend_flags & IOMAP_IOEND_DIRECT) &&
READ_ONCE(mp->m_read_bounce) == XFS_READ_BOUNCE_ALWAYS) {
iomap_bounce_read(ioend, bdev_logical_block_size(bio->bi_bdev),
diff --git a/fs/xfs/xfs_ioend.h b/fs/xfs/xfs_ioend.h
index 7c2a1ea3e6edd..992c248a693aa 100644
--- a/fs/xfs/xfs_ioend.h
+++ b/fs/xfs/xfs_ioend.h
@@ -2,6 +2,8 @@
#ifndef __XFS_IOEND_H
#define __XFS_IOEND_H
+#include <linux/fsverity.h>
+
/*
* Fast and loose check if this write could update the on-disk inode size.
*/
@@ -13,6 +15,6 @@ static inline bool xfs_ioend_is_append(struct iomap_ioend *ioend)
void xfs_end_bio(struct bio *bio);
void xfs_ioend_submit_read(struct inode *inode, struct bio *bio,
- loff_t file_offset, u16 ioend_flags);
+ loff_t file_offset, u16 ioend_flags, struct fsverity_info *vi);
#endif /* __XFS_IOEND_H */
diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c
index 7c6238fed61e4..ccc8b8fabab3f 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)
@@ -883,6 +885,9 @@ xfs_direct_write_iomap_begin(
if (flags & IOMAP_ATOMIC)
iomap_flags |= IOMAP_F_ATOMIC_BIO;
+ if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+ iomap_flags |= IOMAP_F_FSVERITY;
+
/*
* COW writes may allocate delalloc space or convert unwritten COW
* extents, so we need to make sure to take the lock exclusively here.
@@ -1023,7 +1028,8 @@ xfs_direct_write_iomap_begin(
trace_xfs_iomap_found(ip, offset, length - offset, XFS_COW_FORK, &cmap);
if (imap.br_startblock != HOLESTARTBLOCK) {
seq = xfs_iomap_inode_sequence(ip, 0);
- error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags, 0, seq);
+ error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags,
+ iomap_flags & IOMAP_F_FSVERITY, seq);
if (error)
goto out_unlock;
}
@@ -1083,7 +1089,7 @@ xfs_zoned_direct_write_iomap_begin(
return error;
}
- xfs_iomap_set_anon_write(ip, iomap, offset, length);
+ xfs_iomap_set_anon_write(ip, iomap, offset, length, 0);
return 0;
}
@@ -1595,7 +1601,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);
@@ -1605,7 +1612,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;
@@ -1668,7 +1674,8 @@ xfs_zoned_buffered_write_iomap_begin(
smap.br_startoff + smap.br_blockcount);
xfs_trim_extent(&smap, offset_fsb,
end_fsb - offset_fsb);
- error = xfs_bmbt_to_iomap(ip, srcmap, &smap, flags, 0,
+ error = xfs_bmbt_to_iomap(ip, srcmap, &smap, flags,
+ iomap_flags,
xfs_iomap_inode_sequence(ip, 0));
if (error)
goto out_unlock;
@@ -1678,6 +1685,8 @@ xfs_zoned_buffered_write_iomap_begin(
if (!ip->i_cowfp)
xfs_ifork_init_cow(ip);
+ iomap_flags |= IOMAP_F_SHARED;
+
if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &got))
got.br_startoff = end_fsb;
if (got.br_startoff <= offset_fsb) {
@@ -1809,9 +1818,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))
@@ -2095,7 +2107,8 @@ xfs_buffered_write_iomap_begin(
found_cow:
if (imap.br_startoff <= offset_fsb) {
- error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags, 0,
+ error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags,
+ iomap_flags & IOMAP_F_FSVERITY,
xfs_iomap_inode_sequence(ip, 0));
if (error)
goto out_unlock;
@@ -2199,12 +2212,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;
@@ -2218,8 +2236,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);
}
static DEFINE_IOMAP_ITER_NEXT(xfs_read_iomap_next, xfs_read_iomap_begin);
diff --git a/fs/xfs/xfs_iomap.h b/fs/xfs/xfs_iomap.h
index f2520a9b3a13e..d3ca0f2afc9f7 100644
--- a/fs/xfs/xfs_iomap.h
+++ b/fs/xfs/xfs_iomap.h
@@ -34,13 +34,14 @@ xfs_iomap_set_anon_write(
struct xfs_inode *ip,
struct iomap *iomap,
loff_t offset,
- loff_t length)
+ loff_t length,
+ u16 iomap_flags)
{
iomap->type = IOMAP_MAPPED;
iomap->bdev = ip->i_mount->m_rtdev_targp->bt_bdev;
iomap->offset = offset;
iomap->length = length;
- iomap->flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY;
+ iomap->flags = iomap_flags | IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY;
if (bdev_has_integrity_csum(iomap->bdev))
iomap->flags |= IOMAP_F_INTEGRITY;
}
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index f67a541ef35a0..8c4a979cd66e8 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
@@ -579,6 +580,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;
/*
@@ -1369,6 +1374,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)
@@ -1397,12 +1404,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 0243e509a4689..44b13d46049c6 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 811b885f41c35..364fcc7e42b08 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 894ff2f4ecbd9..a3074f060928a 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -403,6 +403,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 */
@@ -460,6 +461,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)
{
@@ -597,6 +599,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 fce1d2905c940..560493e2b78c0 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"
@@ -1736,6 +1737,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
@@ -1988,6 +1992,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;
@@ -2516,8 +2523,20 @@ xfs_init_caches(void)
if (!xfs_parent_args_cache)
goto out_destroy_xmi_cache;
+#ifdef CONFIG_FS_VERITY
+ xfs_fsverity_ioend_cache = kmem_cache_create("xfs_fsverity_ioend",
+ sizeof(struct xfs_fsverity_ioend),
+ 0, 0, NULL);
+ if (!xfs_fsverity_ioend_cache)
+ goto out_destroy_parent_args_cache;
+#endif
+
return 0;
+#ifdef CONFIG_FS_VERITY
+ out_destroy_parent_args_cache:
+ kmem_cache_destroy(xfs_parent_args_cache);
+#endif
out_destroy_xmi_cache:
kmem_cache_destroy(xfs_xmi_cache);
out_destroy_xmd_cache:
@@ -2580,6 +2599,9 @@ xfs_destroy_caches(void)
* destroy caches.
*/
rcu_barrier();
+#ifdef CONFIG_FS_VERITY
+ kmem_cache_destroy(xfs_fsverity_ioend_cache);
+#endif
kmem_cache_destroy(xfs_parent_args_cache);
kmem_cache_destroy(xfs_xmd_cache);
kmem_cache_destroy(xfs_xmi_cache);
@@ -2660,10 +2682,14 @@ init_xfs_fs(void)
if (error)
goto out;
- error = xfs_init_workqueues();
+ error = xfs_fsverity_init();
if (error)
goto out_destroy_caches;
+ error = xfs_init_workqueues();
+ if (error)
+ goto out_fsverity_exit;
+
error = xfs_mru_cache_init();
if (error)
goto out_destroy_wq;
@@ -2740,6 +2766,8 @@ init_xfs_fs(void)
xfs_mru_cache_uninit();
out_destroy_wq:
xfs_destroy_workqueues();
+ out_fsverity_exit:
+ xfs_fsverity_exit();
out_destroy_caches:
xfs_destroy_caches();
out:
@@ -2763,6 +2791,7 @@ exit_xfs_fs(void)
xfs_cleanup_procfs();
xfs_mru_cache_uninit();
xfs_destroy_workqueues();
+ xfs_fsverity_exit();
xfs_destroy_caches();
xfs_uuid_table_free();
}
diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h
index 6c467ded9751f..f9433332c274b 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/10/03 09:23 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 34761 output: 29 thoughts: 937 Thoughts: **Patch Series Evaluation: fs-verity Implementation** Okay, so they want me to review this kernel patch series, targeting amd64. I need to dive into this diff. It looks like it's all about fs-verity support, which is interesting. First, let's look at the touched files: the changes involve removing `STATX_ATTR_VERITY` from various `_getattr` functions in `btrfs`, `ext4`, and `f2fs`, because that's now handled generically in `fs/stat.c`. The `fs/stat.c` file itself is adding generic handling for `STATX_ATTR_VERITY`. Then, updates to the `write_merkle_tree_block` signatures in `btrfs`, `ext4`, and `f2fs` to include `zero_digest` and `digest_size`, with replacement of `pagecache_read` with `fsverity_pagecache_read` in some of them. There's a check for DAX and fsverity incompatibility in `fs/file_attr.c`, and `fs/verity/enable.c` is passing those new parameters. They're also exporting `fsverity_ensure_verity_info()` in `fs/verity/open.c` and adding `fsverity_pagecache_read()` in `fs/verity/pagecache.c`. I see a call to `fserror_report_data_lost()` in `fs/verity/verify.c`. Okay, but the big one... a new `xfs_fsverity.o` in the `xfs` Makefile and a whole slew of new files related to `xfs` and `fsverity`! This *is* what I was expecting. They are adding fs-verity support to XFS! And refactoring the fsverity code for other filesystems. This is a significant addition. Now, the important question: Is this reachable code? YES! This is definitely worth fuzzing. XFS fs-verity support, plus the core filesystem code in `fs/verity`, VFS stat, and the changes for btrfs, ext4, and f2fs. This is accessible from userspace through ioctls like `FS_IOC_ENABLE_VERITY`, `FS_IOC_MEASURE_VERITY`, `FS_IOC_READ_VERITY_METADATA` and also read/write operations on these filesystems. I'm putting a big "WorthFuzzing = True" on this. Next, I need to check the required configurations. It looks like we're relying on `XFS_FS` (or maybe just `FS_VERITY`, but I'll need to double check with the Kconfig). I'll use grepper to clarify that. |
| 4/2 | 2026/10/03 09:23 | tool | grepper |
0mArgs:{
"Expression": "config XFS_FS",
"PathPrefix": "fs/xfs"
}Results: {
"Output": "fs/xfs/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/xfs/Kconfig:2:config XFS_FS\nfs/xfs/Kconfig-3-\ttristate \"XFS filesystem support\"\n"
} |
| 5/2 | 2026/10/03 09:23 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 36413 output: 29 thoughts: 14 Thoughts: Here's my thought process, as if I were considering this myself: **Digging Deeper into Verity Configuration** Okay, so we're looking at configurations related to file system verity, right? My initial focus was likely on the standard places, but now I'm thinking, "Hold on, let's not be too hasty." To ensure I'm covering all bases, I need to make a more thorough investigation. My experience tells me that dependencies and related settings can often be tucked away in unexpected corners. Therefore, before I jump to any conclusions, I really should examine `fs/verity/Kconfig` too. I need to make absolutely certain that I haven't missed a crucial flag or a hidden configuration option that might be relevant. It's that kind of thoroughness that separates a good analysis from a merely adequate one. This will help ensure I understand the complete picture, and that I don't overlook a potential issue or connection. |
| 6/2 | 2026/10/03 09:23 | tool | grepper |
0mArgs:{
"Expression": "config FS_VERITY",
"PathPrefix": "fs/verity"
}Results: {
"Output": "fs/verity/Kconfig-2-\nfs/verity/Kconfig:3:config FS_VERITY\nfs/verity/Kconfig-4-\tbool \"FS Verity (read-only file-based authenticity protection)\"\n--\nfs/verity/Kconfig-33-\nfs/verity/Kconfig:34:config FS_VERITY_BUILTIN_SIGNATURES\nfs/verity/Kconfig-35-\tbool \"FS Verity builtin signature support\"\n"
} |
| 7/2 | 2026/10/03 09:23 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 36785 output: 28 thoughts: 22 Thoughts: Here's my summary, as if I were thinking these thoughts: **XFS and Integrity Considerations** Okay, time to dig into the XFS code again. Specifically, I need to get a better handle on how file system integrity is handled within this codebase. I need to refresh my understanding of how `FS_VERITY` and potentially the `SCRUB` features are managed within XFS. My gut tells me the `Kconfig` files are going to be key here. They'll tell me what configurations are available, what features are compiled in, and potentially expose any dependencies or relationships between these components. So, I need to check `fs/xfs/Kconfig`, right around any declarations related to `FS_VERITY` or the `SCRUB` subsystem. That's the most likely place to start. Let's see what we can unearth. |
| 8/2 | 2026/10/03 09:23 | tool | grepper |
0mArgs:{
"Expression": "FS_VERITY",
"PathPrefix": "fs/xfs"
}Results: {
"Output": "fs/xfs/Makefile=152=xfs-$(CONFIG_EXPORTFS_BLOCK_OPS)\t+= xfs_pnfs.o\nfs/xfs/Makefile:153:xfs-$(CONFIG_FS_VERITY)\t\t+= xfs_fsverity.o\nfs/xfs/Makefile-154-\n--\nfs/xfs/libxfs/xfs_bmap.c=4355=xfs_bmapi_convert_one_delalloc(\n--\nfs/xfs/libxfs/xfs_bmap.c-4406-\tif (!isnullstartblock(bma.got.br_startblock)) {\nfs/xfs/libxfs/xfs_bmap.c:4407:\t\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) \u0026\u0026\nfs/xfs/libxfs/xfs_bmap.c-4408-\t\t offset \u003e= xfs_fsverity_metadata_offset(ip))\n--\nfs/xfs/libxfs/xfs_bmap.c-4456-\nfs/xfs/libxfs/xfs_bmap.c:4457:\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) \u0026\u0026\nfs/xfs/libxfs/xfs_bmap.c-4458-\t offset \u003e= xfs_fsverity_metadata_offset(ip))\n--\nfs/xfs/xfs_aops.c=160=xfs_map_blocks(\n--\nfs/xfs/xfs_aops.c-183-\nfs/xfs/xfs_aops.c:184:\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))\nfs/xfs/xfs_aops.c-185-\t\tiomap_flags |= IOMAP_F_FSVERITY;\n--\nfs/xfs/xfs_aops.c=410=xfs_zoned_map_blocks(\n--\nfs/xfs/xfs_aops.c-428-\nfs/xfs/xfs_aops.c:429:\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))\nfs/xfs/xfs_aops.c-430-\t\tiomap_flags |= IOMAP_F_FSVERITY;\n--\nfs/xfs/xfs_aops.c=522=xfs_iomap_writepages(\n--\nfs/xfs/xfs_aops.c-530-\t */\nfs/xfs/xfs_aops.c:531:\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))\nfs/xfs/xfs_aops.c-532-\t\tctx-\u003eiomap.flags |= IOMAP_F_FSVERITY;\n--\nfs/xfs/xfs_bmap_util.c=504=xfs_can_free_eofblocks(\n--\nfs/xfs/xfs_bmap_util.c-562-\tif (fsverity_active(VFS_IC(ip)) ||\nfs/xfs/xfs_bmap_util.c:563:\t xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))\nfs/xfs/xfs_bmap_util.c-564-\t\treturn false;\n--\nfs/xfs/xfs_fsverity.c=68=xfs_fsverity_get_descriptor(\n--\nfs/xfs/xfs_fsverity.c-117-\tdesc_size = be32_to_cpu(d_desc_size);\nfs/xfs/xfs_fsverity.c:118:\tif (XFS_IS_CORRUPT(mp, desc_size \u003e FS_VERITY_MAX_DESCRIPTOR_SIZE)) {\nfs/xfs/xfs_fsverity.c-119-\t\txfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);\n--\nfs/xfs/xfs_fsverity.c=262=xfs_fsverity_begin_enable(\n--\nfs/xfs/xfs_fsverity.c-292-\nfs/xfs/xfs_fsverity.c:293:\tif (xfs_iflags_test_and_set(ip, XFS_VERITY_CONSTRUCTION))\nfs/xfs/xfs_fsverity.c-294-\t\treturn -EBUSY;\n--\nfs/xfs/xfs_fsverity.c-304-out_clear:\nfs/xfs/xfs_fsverity.c:305:\txfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);\nfs/xfs/xfs_fsverity.c-306-\treturn error;\n--\nfs/xfs/xfs_fsverity.c=313=xfs_fsverity_end_enable(\n--\nfs/xfs/xfs_fsverity.c-338-\t * Wait for Merkle tree get written to disk before setting on-disk inode\nfs/xfs/xfs_fsverity.c:339:\t * flag and clearing XFS_VERITY_CONSTRUCTION\nfs/xfs/xfs_fsverity.c-340-\t */\n--\nfs/xfs/xfs_fsverity.c-386-\nfs/xfs/xfs_fsverity.c:387:\txfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);\nfs/xfs/xfs_fsverity.c-388-\treturn error;\n--\nfs/xfs/xfs_fsverity.h-12-\nfs/xfs/xfs_fsverity.h:13:#ifdef CONFIG_FS_VERITY\nfs/xfs/xfs_fsverity.h-14-extern const struct fsverity_operations xfs_fsverity_ops;\n--\nfs/xfs/xfs_fsverity.h=32=static inline bool xfs_fsverity_is_file_data(const struct xfs_inode *ip,\n--\nfs/xfs/xfs_fsverity.h-36-}\nfs/xfs/xfs_fsverity.h:37:#endif\t/* CONFIG_FS_VERITY */\nfs/xfs/xfs_fsverity.h-38-\n--\nfs/xfs/xfs_inode.h=371=static inline bool xfs_inode_can_sw_atomic_write(const struct xfs_inode *ip)\n--\nfs/xfs/xfs_inode.h-425- */\nfs/xfs/xfs_inode.h:426:#define XFS_VERITY_CONSTRUCTION\t(1U \u003c\u003c 16)\nfs/xfs/xfs_inode.h-427-\n--\nfs/xfs/xfs_ioend.c=114=xfs_end_io_read(\n--\nfs/xfs/xfs_ioend.c-147-\t */\nfs/xfs/xfs_ioend.c:148:\tif (IS_ENABLED(CONFIG_FS_VERITY) \u0026\u0026 !error \u0026\u0026 ioend-\u003eio_vi \u0026\u0026\nfs/xfs/xfs_ioend.c-149-\t\t\txfs_fsverity_is_file_data(ip, ioend-\u003eio_offset)) {\n--\nfs/xfs/xfs_iomap.c=850=xfs_direct_write_iomap_begin(\n--\nfs/xfs/xfs_iomap.c-887-\nfs/xfs/xfs_iomap.c:888:\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))\nfs/xfs/xfs_iomap.c-889-\t\tiomap_flags |= IOMAP_F_FSVERITY;\n--\nfs/xfs/xfs_iomap.c=1792=xfs_buffered_write_iomap_begin(\n--\nfs/xfs/xfs_iomap.c-1820-\nfs/xfs/xfs_iomap.c:1821:\tif (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))\nfs/xfs/xfs_iomap.c-1822-\t\tiomap_flags |= IOMAP_F_FSVERITY;\n--\nfs/xfs/xfs_super.c=1703=xfs_fs_fill_super(\n--\nfs/xfs/xfs_super.c-1739-\tsb-\u003es_op = \u0026xfs_super_operations;\nfs/xfs/xfs_super.c:1740:#ifdef CONFIG_FS_VERITY\nfs/xfs/xfs_super.c-1741-\tsb-\u003es_vop = \u0026xfs_fsverity_ops;\n--\nfs/xfs/xfs_super.c=2361=xfs_init_caches(void)\n--\nfs/xfs/xfs_super.c-2525-\nfs/xfs/xfs_super.c:2526:#ifdef CONFIG_FS_VERITY\nfs/xfs/xfs_super.c-2527-\txfs_fsverity_ioend_cache = kmem_cache_create(\"xfs_fsverity_ioend\",\n--\nfs/xfs/xfs_super.c-2535-\nfs/xfs/xfs_super.c:2536:#ifdef CONFIG_FS_VERITY\nfs/xfs/xfs_super.c-2537- out_destroy_parent_args_cache:\n--\nfs/xfs/xfs_super.c=2595=xfs_destroy_caches(void)\n--\nfs/xfs/xfs_super.c-2601-\trcu_barrier();\nfs/xfs/xfs_super.c:2602:#ifdef CONFIG_FS_VERITY\nfs/xfs/xfs_super.c-2603-\tkmem_cache_destroy(xfs_fsverity_ioend_cache);\n"
} |
| 9/2 | 2026/10/03 09:23 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 39632 output: 30 thoughts: 82 Thoughts: Okay, let's see, my mental checklist begins... **Examining XFS Scrub Configuration and Repair Options** Right, I need to make sure I haven't missed anything related to scrub functionality. My first instinct is to dive into the XFS source code and see what I can turn up. I should probably focus on files that seem relevant to both scrubbing and repairs. Specifically, the code I should reference is `fs/xfs/scrub/inode_repair.c` and `common.c`. I have a feeling the functions I need to look into are `xchk_inode_setup_verity`, `xchk_inode_verity_broken`, and `xrep_inode_verity`, those should provide some helpful information, potentially clues about how the relevant options are enabled. Next, I'll need to double check the kernel configuration files. Let me see what the `Kconfig` files say about XFS scrub and repair configurations. That's usually where I find the conditional compilation options, features, and dependencies. I need to make certain all the options are properly set and that nothing is disabled that I need to be enabled for my test. |
| 10/2 | 2026/10/03 09:23 | tool | grepper |
0mArgs:{
"Expression": "config XFS_ONLINE_",
"PathPrefix": "fs/xfs"
}Results: {
"Output": "fs/xfs/Kconfig=144=config XFS_BTREE_IN_MEM\n--\nfs/xfs/Kconfig-146-\nfs/xfs/Kconfig:147:config XFS_ONLINE_SCRUB\nfs/xfs/Kconfig-148-\tbool \"XFS online metadata check support\"\n--\nfs/xfs/Kconfig-163-\nfs/xfs/Kconfig:164:config XFS_ONLINE_SCRUB_STATS\nfs/xfs/Kconfig-165-\tbool \"XFS online metadata check usage data collection\"\n--\nfs/xfs/Kconfig-178-\nfs/xfs/Kconfig:179:config XFS_ONLINE_REPAIR\nfs/xfs/Kconfig-180-\tbool \"XFS online metadata repair support\"\n"
} |
| 11/2 | 2026/10/03 09:23 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 40206 output: 33 thoughts: 90 Thoughts: Okay, let's break this down. **Configuration Requirements for XFS and Verity Integration** Alright, I need to figure out the necessary configurations. First, I know I'll be working with `XFS_FS` and `FS_VERITY`. Now, if I'm going to be putting the online scrub and repair functionality through its paces, I should also include `XFS_ONLINE_SCRUB` and `XFS_ONLINE_REPAIR` in the mix. Now, I'm pretty sure `FS_VERITY` has some dependencies. To confirm, I'll dive into the Kconfig files, specifically `fs/verity/Kconfig`. Time to get reading. |
| 12/2 | 2026/10/03 09:24 | tool | read-file |
0mArgs:{
"File": "fs/verity/Kconfig",
"FirstLine": 1,
"LineCount": 40
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0\n 2:\t\n 3:\tconfig FS_VERITY\n 4:\t\tbool \"FS Verity (read-only file-based authenticity protection)\"\n 5:\t\t# Filesystems cache the Merkle tree at a 64K aligned offset in the\n 6:\t\t# pagecache. That approach assumes the page size is at most 64K.\n 7:\t\tdepends on PAGE_SHIFT \u003c= 16\n 8:\t\tselect CRYPTO_HASH_INFO\n 9:\t\tselect CRYPTO_LIB_SHA256\n 10:\t\tselect CRYPTO_LIB_SHA512\n 11:\t\thelp\n 12:\t\t This option enables fs-verity. fs-verity is the dm-verity\n 13:\t\t mechanism implemented at the file level. On supported\n 14:\t\t filesystems (currently ext4, f2fs, and btrfs), userspace can\n 15:\t\t use an ioctl to enable verity for a file, which causes the\n 16:\t\t filesystem to build a Merkle tree for the file. The filesystem\n 17:\t\t will then transparently verify any data read from the file\n 18:\t\t against the Merkle tree. The file is also made read-only.\n 19:\t\n 20:\t\t This serves as an integrity check, but the availability of the\n 21:\t\t Merkle tree root hash also allows efficiently supporting\n 22:\t\t various use cases where normally the whole file would need to\n 23:\t\t be hashed at once, such as: (a) auditing (logging the file's\n 24:\t\t hash), or (b) authenticity verification (comparing the hash\n 25:\t\t against a known good value, e.g. from a digital signature).\n 26:\t\n 27:\t\t fs-verity is especially useful on large files where not all\n 28:\t\t the contents may actually be needed. Also, fs-verity verifies\n 29:\t\t data each time it is paged back in, which provides better\n 30:\t\t protection against malicious disks vs. an ahead-of-time hash.\n 31:\t\n 32:\t\t If unsure, say N.\n 33:\t\n 34:\tconfig FS_VERITY_BUILTIN_SIGNATURES\n 35:\t\tbool \"FS Verity builtin signature support\"\n 36:\t\tdepends on FS_VERITY\n 37:\t\tselect SYSTEM_DATA_VERIFICATION\n 38:\t\thelp\n 39:\t\t This option adds support for in-kernel verification of\n 40:\t\t fs-verity builtin signatures.\n"
} |
| 13/2 | 2026/10/03 09:24 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 41144 output: 31 thoughts: 1307 Thoughts: **Analyzing FS-VERITY Implementation for Focused Fuzzing** Okay, so FS_VERITY has a dependency, `PAGE_SHIFT <= 16`, which is easily satisfied on amd64 with our standard 4KB pages. Good. Let's dig into the code changes to identify good fuzzing targets. I've gone through the patch and identified the newly added or modified functions across various XFS and the generic FS-VERITY files. Within `fs/xfs/xfs_fsverity.c`, there are quite a few: initialization, metadata manipulation, descriptor handling, Merkle tree operations, and enabling/disabling FS-VERITY. Looking at `fs/xfs/xfs_ioend.c` there is additional IO related functions. Also the `xfs_ioctl.c` code now includes FS_IOC_ENABLE_VERITY, and the rest of the relevant functions are in the xfs, fs/verity and scrub code. The goal is to pinpoint specific, non-ubiquitous kernel functions to narrow down our fuzzing scope. I've compiled a list of promising candidates: `xfs_fsverity_begin_enable`, `xfs_fsverity_end_enable`, `xfs_fsverity_read_merkle`, `xfs_fsverity_write_merkle`, `xfs_fsverity_get_descriptor`, `xfs_end_fsverity_io_read`, `xchk_inode_setup_verity`, `xrep_inode_verity`, `fsverity_pagecache_read`, and `fsverity_ensure_verity_info`. After checking, none of these functions are ubiquitous. Now, a key question: can Syzkaller focus on static functions? Syzkaller typically uses `kcov` or `trace-pc`, but `FocusSymbols` relies on symbol names from `/proc/kallsyms`. The good news is, static functions *do* appear in `/proc/kallsyms`, unless the compiler inlines them. To be safe, we should target both exported/global entry points and the crucial functions unlikely to be inlined. Aha! `xfs_fsverity_ops` is a struct of function pointers. This is excellent! Since the compiler has to store their addresses, those functions *cannot* be inlined and will definitely be in `/proc/kallsyms`. The functions assigned to the struct are: `xfs_fsverity_begin_enable`, `xfs_fsverity_end_enable`, `xfs_fsverity_get_descriptor`, `xfs_fsverity_read_merkle`, `xfs_fsverity_readahead_merkle_tree`, and `xfs_fsverity_write_merkle`. Therefore, in addition to the functions in the struct, we have `fsverity_ensure_verity_info` and `fsverity_pagecache_read`, which are exported symbols, and `xchk_inode_setup_verity` which is non-static, and `xrep_inode_verity` which is static and called by `xrep_inode`, and `xfs_end_fsverity_io_read`, a static `work_func_t` that will be included in the kallsyms, and `xfs_fsverity_metadata_offset` and `xfs_fsverity_is_file_data`, which are non-static. Finally, I need to understand how verity is enabled on XFS. I should grep for `xfs_has_verity` to see if there are on-disk feature flags or mount options to consider for fuzzing. |
| 14/2 | 2026/10/03 09:24 | tool | grepper |
0mArgs:{
"Expression": "xfs_has_verity",
"PathPrefix": "fs/xfs"
}Results: {
"Output": "fs/xfs/libxfs/xfs_inode_buf.c=594=xfs_dinode_verify(\n--\nfs/xfs/libxfs/xfs_inode_buf.c-765-\tif (flags2 \u0026 XFS_DIFLAG2_VERITY) {\nfs/xfs/libxfs/xfs_inode_buf.c:766:\t\tif (!xfs_has_verity(mp))\nfs/xfs/libxfs/xfs_inode_buf.c-767-\t\t\treturn __this_address;\n--\nfs/xfs/libxfs/xfs_sb.c=1506=xfs_fs_geometry(\n--\nfs/xfs/libxfs/xfs_sb.c-1589-\t\tgeo-\u003eflags |= XFS_FSOP_GEOM_FLAGS_ZONED;\nfs/xfs/libxfs/xfs_sb.c:1590:\tif (xfs_has_verity(mp))\nfs/xfs/libxfs/xfs_sb.c-1591-\t\tgeo-\u003eflags |= XFS_FSOP_GEOM_FLAGS_VERITY;\n--\nfs/xfs/scrub/inode_repair.c=542=xrep_dinode_flags(\n--\nfs/xfs/scrub/inode_repair.c-575-\t\tdip-\u003edi_v3_pad = 0;\nfs/xfs/scrub/inode_repair.c:576:\tif (!xfs_has_verity(mp) || !S_ISREG(mode))\nfs/xfs/scrub/inode_repair.c-577-\t\tflags2 \u0026= ~XFS_DIFLAG2_VERITY;\n--\nfs/xfs/xfs_ioctl.c=1220=xfs_file_ioctl(\n--\nfs/xfs/xfs_ioctl.c-1470-\tcase FS_IOC_ENABLE_VERITY:\nfs/xfs/xfs_ioctl.c:1471:\t\tif (!xfs_has_verity(mp))\nfs/xfs/xfs_ioctl.c-1472-\t\t\treturn -EOPNOTSUPP;\n--\nfs/xfs/xfs_ioctl.c-1474-\tcase FS_IOC_MEASURE_VERITY:\nfs/xfs/xfs_ioctl.c:1475:\t\tif (!xfs_has_verity(mp))\nfs/xfs/xfs_ioctl.c-1476-\t\t\treturn -EOPNOTSUPP;\n--\nfs/xfs/xfs_ioctl.c-1478-\tcase FS_IOC_READ_VERITY_METADATA:\nfs/xfs/xfs_ioctl.c:1479:\t\tif (!xfs_has_verity(mp))\nfs/xfs/xfs_ioctl.c-1480-\t\t\treturn -EOPNOTSUPP;\n--\nfs/xfs/xfs_super.c=1703=xfs_fs_fill_super(\n--\nfs/xfs/xfs_super.c-1994-\nfs/xfs/xfs_super.c:1995:\tif (xfs_has_verity(mp))\nfs/xfs/xfs_super.c-1996-\t\txfs_warn_experimental(mp, XFS_EXPERIMENTAL_FSVERITY);\n"
} |
| 15/2 | 2026/10/03 09:24 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 44150 output: 307 thoughts: 993 Thoughts: **XFS FS-Verity Integration: A Prime Fuzzing Target**
Alright, let's break this down. My initial observation is that `xfs_has_verity` is a key indicator, checked in `xfs_file_ioctl` for the fsverity-related ioctls. The presence of `XFS_SB_FEAT_RO_COMPAT_VERITY` in the superblock, meaning the filesystem was formatted with it enabled, triggers `XFS_FEAT_VERITY`. This tells me we're dealing with an on-disk feature enabled at mkfs time.
Now, let's move onto the configuration side of things. I see we need `XFS_FS`, obviously, along with `FS_VERITY` to get fsverity support compiled in and the ioctls active. `XFS_ONLINE_SCRUB` and `XFS_ONLINE_REPAIR` are crucial, enabling `xchk_inode_setup_verity` and `xrep_inode_verity` respectively.
This patch, hands down, is `WorthFuzzing`. It's introducing fs-verity support for XFS, which includes new ioctl handling, intricate disk layout metadata, page cache interaction with Merkle tree writing/reading, as well as fsverity integration within the online scrub and repair functionality. This is a significant surface area in the filesystem layer.
Let's confirm:
1. `WorthFuzzing`: Absolutely.
2. `Reasoning`: The patch introduces a comprehensive integration of fsverity into XFS, impacting the superblock, file operations, ioctls, online scrub, and repair, all reachable through standard fsverity operations.
3. `EnableConfigs`: `["XFS_FS", "FS_VERITY", "XFS_ONLINE_SCRUB", "XFS_ONLINE_REPAIR"]`
4. `FocusSymbols`: After carefully verifying the presence and correct spelling of the key symbols. We need to be able to reach these functions:
* `xfs_fsverity_begin_enable`
* `xfs_fsverity_end_enable`
* `xfs_fsverity_write_merkle`
* `xfs_fsverity_read_merkle`
* `xfs_fsverity_get_descriptor`
* `xfs_end_fsverity_io_read`
* `xchk_inode_setup_verity`
* `xrep_inode_verity`
* `fsverity_ensure_verity_info`
* `fsverity_pagecache_read`
All the symbols seem to exist and are spelled correctly.
With everything confirmed, I'm ready to call `set-results`. This is a solid target!
|
| 16/2 | 2026/10/03 09:24 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"XFS_FS",
"FS_VERITY",
"XFS_ONLINE_SCRUB",
"XFS_ONLINE_REPAIR"
],
"FocusSymbols": [
"xfs_fsverity_begin_enable",
"xfs_fsverity_end_enable",
"xfs_fsverity_write_merkle",
"xfs_fsverity_read_merkle",
"xfs_fsverity_get_descriptor",
"xfs_end_fsverity_io_read",
"xchk_inode_setup_verity",
"xrep_inode_verity",
"fsverity_ensure_verity_info",
"fsverity_pagecache_read"
],
"Reasoning": "This patch series introduces fs-verity support to XFS, exposes fs-verity ioctls (FS_IOC_ENABLE_VERITY, FS_IOC_MEASURE_VERITY, FS_IOC_READ_VERITY_METADATA) on XFS files, adds Merkle tree construction, reading, and metadata handling beyond EOF, wires up async read verification, adds online scrub and repair checks for fs-verity inodes, and exports common fs-verity helper functions. All of these paths are reachable from userspace and warrant fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"XFS_FS",
"FS_VERITY",
"XFS_ONLINE_SCRUB",
"XFS_ONLINE_REPAIR"
],
"FocusSymbols": [
"xfs_fsverity_begin_enable",
"xfs_fsverity_end_enable",
"xfs_fsverity_write_merkle",
"xfs_fsverity_read_merkle",
"xfs_fsverity_get_descriptor",
"xfs_end_fsverity_io_read",
"xchk_inode_setup_verity",
"xrep_inode_verity",
"fsverity_ensure_verity_info",
"fsverity_pagecache_read"
],
"Reasoning": "This patch series introduces fs-verity support to XFS, exposes fs-verity ioctls (FS_IOC_ENABLE_VERITY, FS_IOC_MEASURE_VERITY, FS_IOC_READ_VERITY_METADATA) on XFS files, adds Merkle tree construction, reading, and metadata handling beyond EOF, wires up async read verification, adds online scrub and repair checks for fs-verity inodes, and exports common fs-verity helper functions. All of these paths are reachable from userspace and warrant fuzzing.",
"WorthFuzzing": true
} |
| 17/1 | 2026/10/03 09:24 | action | If |
2mArgs:{
"WorthFuzzing": true
}Results: null |
| 18/2 | 2026/10/03 09:24 | agent | kmsan-evaluator |
2mModel:core Args: null Results: {
"KMSANReasoning": "The patch series implements fs-verity support for XFS, optimizes/refactors Merkle tree block zero-digest handling across filesystems (ext4, btrfs, f2fs, xfs), unifies pagecache metadata reading via `fsverity_pagecache_read()`, and exposes the STATX_ATTR_VERITY flag centrally in VFS stat.\n\nAnalysis of memory safety and sanitizer applicability:\n1. Kernel-to-userspace copies and info-leaks:\n - `vfs_getattr_nosec` sets `STATX_ATTR_VERITY` on `stat`, which is explicitly zeroed via `memset(stat, 0, sizeof(*stat))` at the start of the function.\n - `xfs_report_dioalign` conditionally skips populating `dio_mem_align` for fsverity files; the underlying `kstat` struct was already cleared with `memset`.\n - `xfs_fs_geometry` sets `XFS_FSOP_GEOM_FLAGS_VERITY` on `geo`, which is cleared via `memset(geo, 0, sizeof(*geo))` in `xfs_fs_geometry`.\n - Bulkstat health reporting (`xfs_bulkstat_health`) explicitly initializes `bs-\u003ebs_sick = 0` and `bs-\u003ebs_checked = 0` prior to bitmask assignment.\n - Verity ioctls (`FS_IOC_ENABLE_VERITY`, `FS_IOC_MEASURE_VERITY`, `FS_IOC_READ_VERITY_METADATA`) use existing fsverity core handlers.\n\n2. Structure allocation and initialization:\n - `xfs_fsverity_ioend` allocated from `xfs_fsverity_ioend_pool` has both of its member fields (`ioend` and `work`) explicitly initialized before use or queueing.\n - Designated initializers (`struct iomap_read_folio_ctx ctx = { .cur_folio = folio };`) guarantee all other fields (`vi`, `read_ctx`, etc.) are zero-initialized.\n - Merkle tree buffers in `build_merkle_tree` are zero-allocated with `kzalloc`.\n\n3. Primary risk profile:\n - Potential failure modes in this patch series involve extent mapping and truncation logic (`xfs_bmapi_convert_one_delalloc`, `__xfs_bunmapi`, `xfs_free_eofblocks`), iomap folio iterations, race conditions/locking around verity enablement, and bio completion workqueue handling.\n - These risks (out-of-bounds folio accesses, buffer overflows, use-after-free, and lock inversion) are effectively detected by KASAN and LOCKDEP. There are no uninitialized memory reads or information leak risks that would require dedicated KMSAN fuzzing.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
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: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 94f00f55bd2265b867b7373031319902086b56af
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Oct 3 09:23:39 2026 +0000
syz-cluster: applied patch under review
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 81a9af9582827..b9a1b629a5333 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -8236,7 +8236,6 @@ static int btrfs_getattr(const struct mnt_idmap *idmap,
struct inode *inode = d_inode(path->dentry);
u32 blocksize = btrfs_sb(inode->i_sb)->sectorsize;
u32 bi_flags = BTRFS_I(inode)->flags;
- u32 bi_ro_flags = BTRFS_I(inode)->ro_flags;
stat->result_mask |= STATX_BTIME;
stat->btime.tv_sec = BTRFS_I(inode)->i_otime_sec;
@@ -8249,8 +8248,6 @@ static int btrfs_getattr(const struct mnt_idmap *idmap,
stat->attributes |= STATX_ATTR_IMMUTABLE;
if (bi_flags & BTRFS_INODE_NODUMP)
stat->attributes |= STATX_ATTR_NODUMP;
- if (bi_ro_flags & BTRFS_INODE_RO_VERITY)
- stat->attributes |= STATX_ATTR_VERITY;
stat->attributes_mask |= (STATX_ATTR_APPEND |
STATX_ATTR_COMPRESSED |
diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c
index 4e0ab58422741..c572acacb4d41 100644
--- a/fs/btrfs/verity.c
+++ b/fs/btrfs/verity.c
@@ -779,11 +779,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/inode.c b/fs/ext4/inode.c
index cb68bf50a3d6a..9a9ec02dce9fd 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -6318,15 +6318,12 @@ int ext4_getattr(const struct mnt_idmap *idmap, const struct path *path,
stat->attributes |= STATX_ATTR_IMMUTABLE;
if (flags & EXT4_NODUMP_FL)
stat->attributes |= STATX_ATTR_NODUMP;
- if (flags & EXT4_VERITY_FL)
- stat->attributes |= STATX_ATTR_VERITY;
stat->attributes_mask |= (STATX_ATTR_APPEND |
STATX_ATTR_COMPRESSED |
STATX_ATTR_ENCRYPTED |
STATX_ATTR_IMMUTABLE |
- STATX_ATTR_NODUMP |
- STATX_ATTR_VERITY);
+ STATX_ATTR_NODUMP);
generic_fillattr(idmap, request_mask, inode, stat);
return 0;
diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c
index ca61da53f313f..ac5c133f55292 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/file.c b/fs/f2fs/file.c
index 8596c3bc4704a..0c759cc990546 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -1075,15 +1075,12 @@ int f2fs_getattr(const struct mnt_idmap *idmap, const struct path *path,
stat->attributes |= STATX_ATTR_IMMUTABLE;
if (flags & F2FS_NODUMP_FL)
stat->attributes |= STATX_ATTR_NODUMP;
- if (IS_VERITY(inode))
- stat->attributes |= STATX_ATTR_VERITY;
stat->attributes_mask |= (STATX_ATTR_COMPRESSED |
STATX_ATTR_APPEND |
STATX_ATTR_ENCRYPTED |
STATX_ATTR_IMMUTABLE |
- STATX_ATTR_NODUMP |
- STATX_ATTR_VERITY);
+ STATX_ATTR_NODUMP);
generic_fillattr(idmap, request_mask, inode, stat);
diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c
index 39f4825154452..f3a4617656db9 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 81af4364e33ae..833e03810b24c 100644
--- a/fs/file_attr.c
+++ b/fs/file_attr.c
@@ -235,10 +235,15 @@ static int fileattr_set_prepare(struct inode *inode,
/*
* It is only valid to set the DAX flag on regular files and
* directories on filesystems.
+ *
+ * DAX and fsverity are incompatible.
*/
- if ((fa->fsx_xflags & FS_XFLAG_DAX) &&
- !(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)))
- return -EINVAL;
+ if (fa->fsx_xflags & FS_XFLAG_DAX) {
+ if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)))
+ return -EINVAL;
+ if (old_ma->fsx_xflags & FS_XFLAG_VERITY)
+ return -EINVAL;
+ }
/* Extent size hints of zero turn off the flags. */
if (fa->fsx_extsize == 0)
diff --git a/fs/stat.c b/fs/stat.c
index a9b7383d538da..a2e63c6c45b77 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -203,8 +203,12 @@ int vfs_getattr_nosec(const struct path *path, struct kstat *stat,
if (IS_DAX(inode))
stat->attributes |= STATX_ATTR_DAX;
+ if (IS_VERITY(inode))
+ stat->attributes |= STATX_ATTR_VERITY;
+
stat->attributes_mask |= (STATX_ATTR_AUTOMOUNT |
- STATX_ATTR_DAX);
+ STATX_ATTR_DAX |
+ STATX_ATTR_VERITY);
idmap = mnt_idmap(path->mnt);
if (inode->i_op->getattr) {
diff --git a/fs/verity/enable.c b/fs/verity/enable.c
index 42dfed1ce0ce6..ad4ff71d7dd95 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 d0c56a7faa3bb..4f06697f5bf4d 100644
--- a/fs/verity/open.c
+++ b/fs/verity/open.c
@@ -347,7 +347,28 @@ 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.
+ *
+ * In case filesystem supports both fscrypt and fsverity, this should be called
+ * after fscrypt's encryption key is set up. Otherwise, the fsverity metadata is
+ * still encrypted. See fscrypt_file_open().
+ *
+ * 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 +404,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 99f5f53eea985..9d82e6b74ba12 100644
--- a/fs/verity/pagecache.c
+++ b/fs/verity/pagecache.c
@@ -78,3 +78,36 @@ void fsverity_fill_zerohash(struct folio *folio, size_t offset, size_t len,
vi->tree_params.digest_size);
}
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 4004a1d428751..db8c350234bb7 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 399a207f2d0e7..dd712c5218625 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -150,6 +150,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 d64defeda645e..6f0094445cea5 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,9 @@ 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) &&
+ offset >= 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 +4454,10 @@ 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) &&
+ offset >= 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));
@@ -5269,6 +5278,10 @@ __xfs_bunmapi(
if (del.br_startoff + del.br_blockcount > end + 1)
del.br_blockcount = end + 1 - del.br_startoff;
+ if ((flags & XFS_BMAPI_UNWRITTEN) &&
+ del.br_state != XFS_EXT_UNWRITTEN)
+ goto skip;
+
if (!isrt || (flags & XFS_BMAPI_REMAP))
goto delete;
@@ -5403,7 +5416,7 @@ __xfs_bunmapi(
if (error)
goto error0;
}
-
+skip:
end = del.br_startoff - 1;
nodelete:
/*
diff --git a/fs/xfs/libxfs/xfs_bmap.h b/fs/xfs/libxfs/xfs_bmap.h
index d5f2729305fad..0f36431d99368 100644
--- a/fs/xfs/libxfs/xfs_bmap.h
+++ b/fs/xfs/libxfs/xfs_bmap.h
@@ -90,6 +90,9 @@ struct xfs_bmalloca {
/* Try to align allocations to the extent size hint */
#define XFS_BMAPI_EXTSZALIGN (1u << 11)
+/* Process unwritten extents only. Used for unmapping */
+#define XFS_BMAPI_UNWRITTEN (1u << 12)
+
#define XFS_BMAPI_FLAGS \
{ XFS_BMAPI_ENTIRE, "ENTIRE" }, \
{ XFS_BMAPI_METADATA, "METADATA" }, \
@@ -102,7 +105,8 @@ struct xfs_bmalloca {
{ XFS_BMAPI_COWFORK, "COWFORK" }, \
{ XFS_BMAPI_NODISCARD, "NODISCARD" }, \
{ XFS_BMAPI_NORMAP, "NORMAP" },\
- { XFS_BMAPI_EXTSZALIGN, "EXTSZALIGN" }
+ { XFS_BMAPI_EXTSZALIGN, "EXTSZALIGN" }, \
+ { XFS_BMAPI_UNWRITTEN, "UNWRITTEN" }
static inline int xfs_bmapi_aflag(int w)
diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h
index dd0ed046fbe9c..8be1b101b1969 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 synthesis 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 185f09f327c00..84d62b7506a9a 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 1d45cf5789e86..932b447190da7 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 e4c3f7b24e952..b4cc673adc8ab 100644
--- a/fs/xfs/libxfs/xfs_inode_buf.c
+++ b/fs/xfs/libxfs/xfs_inode_buf.c
@@ -761,6 +761,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 258ac3d0d486e..5a516bd31c317 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 f0341adbb8790..fd06c3d6cc113 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)
@@ -1585,6 +1587,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 e5233e31abb75..ebbef3afb7d6e 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 -EFSCORRUPTED:
+ case -ERANGE:
+ case -EBADMSG:
+ /*
+ * 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 f0f073a934133..676b4afd0be4d 100644
--- a/fs/xfs/scrub/common.h
+++ b/fs/xfs/scrub/common.h
@@ -267,6 +267,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 65b13e3119168..d1cdd6b445d01 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 8bc508336aa5f..6c0b7130e227e 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_set_flags(inode, 0, 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 c30e688cfc9f6..a36f840884b49 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -23,6 +23,8 @@
#include "xfs_ioend.h"
#include "xfs_zone_alloc.h"
#include "xfs_rtgroup.h"
+#include "xfs_fsverity.h"
+#include <linux/fsverity.h>
struct xfs_writepage_ctx {
struct iomap_writepage_ctx ctx;
@@ -172,12 +174,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
@@ -265,7 +271,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:
@@ -412,12 +419,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.
@@ -441,7 +452,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);
@@ -454,7 +465,7 @@ xfs_zoned_map_blocks(
xfs_iunlock(ip, XFS_ILOCK_EXCL);
xfs_iomap_set_anon_write(ip, &wpc->iomap, offset,
- XFS_FSB_TO_B(mp, count_fsb));
+ XFS_FSB_TO_B(mp, count_fsb), iomap_flags);
trace_xfs_zoned_map_blocks(ip, offset, wpc->iomap.length);
return 0;
}
@@ -507,6 +518,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,
@@ -526,7 +553,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;
@@ -539,7 +566,7 @@ xfs_vm_writepages(
},
};
- return iomap_writepages(&wpc.ctx);
+ return xfs_iomap_writepages(ip, &wpc.ctx);
}
}
@@ -585,7 +612,7 @@ xfs_bio_submit_read(
{
xfs_ioend_submit_read(iter->inode, ctx->read_ctx,
ctx->read_ctx_file_offset,
- iomap_ioend_flags(&iter->iomap));
+ iomap_ioend_flags(&iter->iomap), ctx->vi);
ctx->read_ctx = NULL;
}
@@ -597,11 +624,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 pos)
{
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, pos))
return &xfs_iomap_read_ops;
return &iomap_bio_read_ops;
}
@@ -613,7 +642,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;
}
@@ -624,7 +653,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 268d159339d06..74e99d0bee7a1 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,15 @@ xfs_can_free_eofblocks(
if (last_fsb <= end_fsb)
return false;
+ /*
+ * Don't clean fsverity inodes as they already have been cleaned up and
+ * are read-only. Skip inodes which have merkle tree being built, the
+ * merkle tree is written beyond EOF
+ */
+ if (fsverity_active(VFS_IC(ip)) ||
+ xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+ 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
@@ -579,6 +589,9 @@ xfs_free_eofblocks(
struct xfs_trans *tp;
struct xfs_mount *mp = ip->i_mount;
int error;
+ int bmapi_flags = XFS_BMAPI_NODISCARD;
+ bool has_verity =
+ ip->i_diflags2 & XFS_DIFLAG2_VERITY;
/* Attach the dquots to the inode up front. */
error = xfs_qm_dqattach(ip);
@@ -593,15 +606,20 @@ xfs_free_eofblocks(
*
* Note that this means we also leave speculative preallocations in
* place for preallocated files.
+ *
+ * Clean up delalloc reservations for fsverity too as those won't be
+ * used
*/
- if (ip->i_diflags & (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND)) {
+ if (ip->i_diflags & (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND) ||
+ has_verity) {
if (ip->i_delayed_blks) {
xfs_bmap_punch_delalloc_range(ip, XFS_DATA_FORK,
round_up(XFS_ISIZE(ip), mp->m_sb.sb_blocksize),
LLONG_MAX, NULL);
}
xfs_inode_clear_eofblocks_tag(ip);
- return 0;
+ if (!has_verity)
+ return 0;
}
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp);
@@ -613,6 +631,13 @@ xfs_free_eofblocks(
xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_trans_ijoin(tp, ip, 0);
+ /*
+ * While fs-verity writes metadata after EOF, it can leave unwritten
+ * preallocations. Clear all that out.
+ */
+ if (has_verity)
+ bmapi_flags |= XFS_BMAPI_UNWRITTEN;
+
/*
* Do not update the on-disk file size. If we update the on-disk file
* size and then the system crashes before the contents of the file are
@@ -620,7 +645,7 @@ xfs_free_eofblocks(
* bug).
*/
error = xfs_itruncate_extents_flags(&tp, ip, XFS_DATA_FORK,
- XFS_ISIZE(ip), XFS_BMAPI_NODISCARD);
+ XFS_ISIZE(ip), bmapi_flags);
if (error)
goto err_cancel;
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index dd3782e9af938..e13390539f042 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -38,6 +38,7 @@
#include <linux/mount.h>
#include <linux/filelock.h>
#include <linux/bio-integrity.h>
+#include <linux/fsverity.h>
static const struct vm_operations_struct xfs_file_vm_ops;
@@ -224,7 +225,8 @@ xfs_dio_read_bounce_submit_io(
loff_t file_offset)
{
xfs_ioend_submit_read(iter->inode, bio, file_offset,
- iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT);
+ iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT,
+ NULL);
}
static const struct iomap_dio_ops xfs_dio_read_bounce_ops = {
@@ -232,6 +234,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,
@@ -250,6 +271,17 @@ 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)) {
ret = iomap_dio_rw(iocb, to, &xfs_read_iomap_ops,
&xfs_dio_read_bounce_ops, 0, NULL, 0);
@@ -269,7 +301,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);
@@ -287,25 +320,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,
@@ -320,6 +334,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)
@@ -1772,11 +1794,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 0000000000000..7174b36c8cd3d
--- /dev/null
+++ b/fs/xfs/xfs_fsverity.c
@@ -0,0 +1,489 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ */
+#include "xfs_platform.h"
+#include "xfs_fs.h"
+#include "xfs_shared.h"
+#include "xfs_format.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_inode.h"
+#include "xfs_log_format.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_util.h"
+#include "xfs_icache.h"
+#include <linux/fsverity.h>
+#include <linux/iomap.h>
+#include <linux/pagemap.h>
+
+struct kmem_cache *xfs_fsverity_ioend_cache;
+mempool_t xfs_fsverity_ioend_pool;
+
+#define XFS_FSVERITY_IOEND_POOL_MIN 128
+
+/*
+ * Back the fsverity ioend allocations with a mempool so that read I/O
+ * completion always makes forward progress and cannot deadlock
+ */
+int
+xfs_fsverity_init(void)
+{
+ return mempool_init_slab_pool(&xfs_fsverity_ioend_pool,
+ XFS_FSVERITY_IOEND_POOL_MIN, xfs_fsverity_ioend_cache);
+}
+
+void
+xfs_fsverity_exit(void)
+{
+ mempool_exit(&xfs_fsverity_ioend_pool);
+}
+
+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;
+
+ xfs_ilock(ip, XFS_ILOCK_EXCL);
+ /*
+ * Serialize reading of inode->i_flags with xfs_scrub clearing of this
+ * flag if inode is corrupted.
+ */
+ if (!(inode->i_flags & S_VERITY)) {
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ return -ENODATA;
+ }
+ error = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, &rec, &is_empty);
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ if (error)
+ return error;
+
+ if (is_empty) {
+ xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);
+ 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)) {
+ xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);
+ 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;
+
+ xfs_ilock(ip, XFS_MMAPLOCK_EXCL);
+ error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp);
+ if (error) {
+ xfs_iunlock(ip, XFS_MMAPLOCK_EXCL);
+ return error;
+ }
+
+ truncate_inode_pages(VFS_I(ip)->i_mapping, XFS_ISIZE(ip));
+
+ xfs_ilock(ip, XFS_ILOCK_EXCL);
+ xfs_trans_ijoin(tp, ip, 0);
+
+ /*
+ * We remove 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);
+ xfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_ILOCK_EXCL);
+ return error;
+err_cancel:
+ xfs_trans_cancel(tp);
+ xfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_ILOCK_EXCL);
+ return error;
+}
+
+static int
+xfs_fsverity_reset_inode(
+ struct xfs_inode *ip)
+{
+ int error;
+ struct xfs_mount *mp = ip->i_mount;
+ struct xfs_trans *tp;
+
+ error = xfs_fsverity_delete_metadata(ip);
+ if (error)
+ return error;
+
+ /*
+ * First, let's clean in-memory fsverity flag and then reset it on the
+ * disk
+ */
+ inode_set_flags(VFS_I(ip), 0, S_VERITY);
+
+ /*
+ * Set fsverity inode flag
+ */
+ error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_ichange,
+ 0, 0, false, &tp);
+ if (error)
+ return error;
+
+ 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);
+ 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;
+
+ /*
+ * fs-verity stores the Merkle tree past EOF in units of the filesystem
+ * block size, so it cannot support realtime files whose allocation unit
+ * (extent size) is larger than the block size.
+ */
+ if (xfs_inode_has_bigrtalloc(ip))
+ 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)
+ 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;
+
+ /*
+ * 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)
+ goto out;
+
+ inode_set_flags(inode, S_VERITY, S_VERITY);
+
+ /*
+ * Proactively remove unwritten extents left by COW preallocations and
+ * write preallocation in the merkle tree holes and past descriptor,
+ * and any delayed preallocations
+ */
+ error = xfs_free_eofblocks(ip);
+out:
+ if (error) {
+ int error2;
+
+ error2 = xfs_fsverity_reset_inode(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);
+}
+
+static inline bool
+xfs_fsverity_is_hashes_of_zeroed_blocks(
+ struct xfs_inode *ip,
+ const void *buf,
+ unsigned int size,
+ const u8 *zero_digest,
+ unsigned int digest_size)
+{
+ /*
+ * 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)
+ return false;
+ /*
+ * First digest is zero_digest
+ */
+ if (memcmp(buf, zero_digest, digest_size))
+ return false;
+ /*
+ * Every digest is same as previous, thus all are
+ * zero_digest
+ */
+ return memcmp(buf + digest_size, buf, size - digest_size) == 0;
+}
+
+/*
+ * 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 (xfs_fsverity_is_hashes_of_zeroed_blocks(ip, buf, size, zero_digest,
+ digest_size))
+ 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 0000000000000..7a3aee6d45fca
--- /dev/null
+++ b/fs/xfs/xfs_fsverity.h
@@ -0,0 +1,47 @@
+// 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/iomap.h>
+#include <linux/fsverity.h>
+#include <linux/mempool.h>
+
+#ifdef CONFIG_FS_VERITY
+extern const struct fsverity_operations xfs_fsverity_ops;
+int xfs_fsverity_init(void);
+void xfs_fsverity_exit(void);
+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 int xfs_fsverity_init(void)
+{
+ return 0;
+}
+static inline void xfs_fsverity_exit(void)
+{
+}
+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 */
+
+struct xfs_fsverity_ioend {
+ struct iomap_ioend *ioend;
+ struct work_struct work;
+};
+
+extern struct kmem_cache *xfs_fsverity_ioend_cache;
+extern mempool_t xfs_fsverity_ioend_pool;
+
+#endif /* __XFS_FSVERITY_H__ */
diff --git a/fs/xfs/xfs_health.c b/fs/xfs/xfs_health.c
index 239b843e83d42..be66760fb120f 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 ca96ba096359a..2a0e21f0679d5 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 5d023d7aee237..6fef879bb1375 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_ioend.c b/fs/xfs/xfs_ioend.c
index e70be5b86f0b7..f4c1bbc03714f 100644
--- a/fs/xfs/xfs_ioend.c
+++ b/fs/xfs/xfs_ioend.c
@@ -18,7 +18,26 @@
#include "xfs_ioend.h"
#include "xfs_error.h"
#include "xfs_errortag.h"
+#include "xfs_fsverity.h"
#include <linux/bio-integrity.h>
+#include <linux/fsverity.h>
+
+static void
+xfs_end_fsverity_io_read(
+ struct work_struct *work)
+{
+ struct xfs_fsverity_ioend *fsv_ioend =
+ container_of(work, struct xfs_fsverity_ioend, work);
+ struct iomap_ioend *ioend = fsv_ioend->ioend;
+ struct bio *bio = &ioend->io_bio;
+
+ mempool_free(fsv_ioend, &xfs_fsverity_ioend_pool);
+
+ if (!bio->bi_status)
+ fsverity_verify_bio(ioend->io_vi, bio);
+
+ iomap_finish_ioends(ioend, blk_status_to_errno(bio->bi_status));
+}
static void
xfs_dio_bounce_end_io(
@@ -93,12 +112,14 @@ xfs_read_bounce_and_resubmit(
static void
xfs_end_io_read(
- struct bio *bio)
+ struct bio *bio)
{
- struct iomap_ioend *ioend = iomap_ioend_from_bio(bio);
- struct xfs_inode *ip = XFS_I(ioend->io_inode);
- struct xfs_mount *mp = ip->i_mount;
- int error = blk_status_to_errno(bio->bi_status);
+ struct iomap_ioend *ioend = iomap_ioend_from_bio(bio);
+ struct xfs_inode *ip = XFS_I(ioend->io_inode);
+ struct xfs_mount *mp = ip->i_mount;
+ int error =
+ blk_status_to_errno(bio->bi_status);
+ struct xfs_fsverity_ioend *fsv_ioend;
if (!error && (ioend->io_flags & IOMAP_IOEND_INTEGRITY)) {
error = iomap_ioend_integrity_verify(ioend);
@@ -117,6 +138,24 @@ xfs_end_io_read(
}
}
+ /*
+ * If we have fsverity on this bio, we need to run fsverity verification
+ * of data folios from a separate fsverity workqueue. This is necessary
+ * to avoid deadlocking due to fsverity issuing more reads of fsverity
+ * metadata which would be processed by the same worker in the BIO
+ * completion workqueue.
+ */
+ if (IS_ENABLED(CONFIG_FS_VERITY) && !error && ioend->io_vi &&
+ xfs_fsverity_is_file_data(ip, ioend->io_offset)) {
+ fsv_ioend = mempool_alloc(&xfs_fsverity_ioend_pool,
+ GFP_NOFS);
+ fsv_ioend->ioend = ioend;
+ INIT_WORK(&fsv_ioend->work, xfs_end_fsverity_io_read);
+
+ fsverity_enqueue_verify_work(&fsv_ioend->work);
+ return;
+ }
+
iomap_finish_ioends(ioend, error);
}
@@ -125,13 +164,15 @@ xfs_ioend_submit_read(
struct inode *inode,
struct bio *bio,
loff_t file_offset,
- u16 ioend_flags)
+ u16 ioend_flags,
+ struct fsverity_info *vi)
{
struct xfs_inode *ip = XFS_I(inode);
struct xfs_mount *mp = ip->i_mount;
struct iomap_ioend *ioend;
ioend = iomap_init_ioend(inode, bio, file_offset, ioend_flags);
+ ioend->io_vi = vi;
if ((ioend_flags & IOMAP_IOEND_DIRECT) &&
READ_ONCE(mp->m_read_bounce) == XFS_READ_BOUNCE_ALWAYS) {
iomap_bounce_read(ioend, bdev_logical_block_size(bio->bi_bdev),
diff --git a/fs/xfs/xfs_ioend.h b/fs/xfs/xfs_ioend.h
index 7c2a1ea3e6edd..992c248a693aa 100644
--- a/fs/xfs/xfs_ioend.h
+++ b/fs/xfs/xfs_ioend.h
@@ -2,6 +2,8 @@
#ifndef __XFS_IOEND_H
#define __XFS_IOEND_H
+#include <linux/fsverity.h>
+
/*
* Fast and loose check if this write could update the on-disk inode size.
*/
@@ -13,6 +15,6 @@ static inline bool xfs_ioend_is_append(struct iomap_ioend *ioend)
void xfs_end_bio(struct bio *bio);
void xfs_ioend_submit_read(struct inode *inode, struct bio *bio,
- loff_t file_offset, u16 ioend_flags);
+ loff_t file_offset, u16 ioend_flags, struct fsverity_info *vi);
#endif /* __XFS_IOEND_H */
diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c
index 7c6238fed61e4..ccc8b8fabab3f 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)
@@ -883,6 +885,9 @@ xfs_direct_write_iomap_begin(
if (flags & IOMAP_ATOMIC)
iomap_flags |= IOMAP_F_ATOMIC_BIO;
+ if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+ iomap_flags |= IOMAP_F_FSVERITY;
+
/*
* COW writes may allocate delalloc space or convert unwritten COW
* extents, so we need to make sure to take the lock exclusively here.
@@ -1023,7 +1028,8 @@ xfs_direct_write_iomap_begin(
trace_xfs_iomap_found(ip, offset, length - offset, XFS_COW_FORK, &cmap);
if (imap.br_startblock != HOLESTARTBLOCK) {
seq = xfs_iomap_inode_sequence(ip, 0);
- error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags, 0, seq);
+ error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags,
+ iomap_flags & IOMAP_F_FSVERITY, seq);
if (error)
goto out_unlock;
}
@@ -1083,7 +1089,7 @@ xfs_zoned_direct_write_iomap_begin(
return error;
}
- xfs_iomap_set_anon_write(ip, iomap, offset, length);
+ xfs_iomap_set_anon_write(ip, iomap, offset, length, 0);
return 0;
}
@@ -1595,7 +1601,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);
@@ -1605,7 +1612,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;
@@ -1668,7 +1674,8 @@ xfs_zoned_buffered_write_iomap_begin(
smap.br_startoff + smap.br_blockcount);
xfs_trim_extent(&smap, offset_fsb,
end_fsb - offset_fsb);
- error = xfs_bmbt_to_iomap(ip, srcmap, &smap, flags, 0,
+ error = xfs_bmbt_to_iomap(ip, srcmap, &smap, flags,
+ iomap_flags,
xfs_iomap_inode_sequence(ip, 0));
if (error)
goto out_unlock;
@@ -1678,6 +1685,8 @@ xfs_zoned_buffered_write_iomap_begin(
if (!ip->i_cowfp)
xfs_ifork_init_cow(ip);
+ iomap_flags |= IOMAP_F_SHARED;
+
if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &got))
got.br_startoff = end_fsb;
if (got.br_startoff <= offset_fsb) {
@@ -1809,9 +1818,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))
@@ -2095,7 +2107,8 @@ xfs_buffered_write_iomap_begin(
found_cow:
if (imap.br_startoff <= offset_fsb) {
- error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags, 0,
+ error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags,
+ iomap_flags & IOMAP_F_FSVERITY,
xfs_iomap_inode_sequence(ip, 0));
if (error)
goto out_unlock;
@@ -2199,12 +2212,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;
@@ -2218,8 +2236,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);
}
static DEFINE_IOMAP_ITER_NEXT(xfs_read_iomap_next, xfs_read_iomap_begin);
diff --git a/fs/xfs/xfs_iomap.h b/fs/xfs/xfs_iomap.h
index f2520a9b3a13e..d3ca0f2afc9f7 100644
--- a/fs/xfs/xfs_iomap.h
+++ b/fs/xfs/xfs_iomap.h
@@ -34,13 +34,14 @@ xfs_iomap_set_anon_write(
struct xfs_inode *ip,
struct iomap *iomap,
loff_t offset,
- loff_t length)
+ loff_t length,
+ u16 iomap_flags)
{
iomap->type = IOMAP_MAPPED;
iomap->bdev = ip->i_mount->m_rtdev_targp->bt_bdev;
iomap->offset = offset;
iomap->length = length;
- iomap->flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY;
+ iomap->flags = iomap_flags | IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY;
if (bdev_has_integrity_csum(iomap->bdev))
iomap->flags |= IOMAP_F_INTEGRITY;
}
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index f67a541ef35a0..8c4a979cd66e8 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
@@ -579,6 +580,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;
/*
@@ -1369,6 +1374,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)
@@ -1397,12 +1404,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 0243e509a4689..44b13d46049c6 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 811b885f41c35..364fcc7e42b08 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 894ff2f4ecbd9..a3074f060928a 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -403,6 +403,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 */
@@ -460,6 +461,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)
{
@@ -597,6 +599,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 fce1d2905c940..560493e2b78c0 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"
@@ -1736,6 +1737,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
@@ -1988,6 +1992,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;
@@ -2516,8 +2523,20 @@ xfs_init_caches(void)
if (!xfs_parent_args_cache)
goto out_destroy_xmi_cache;
+#ifdef CONFIG_FS_VERITY
+ xfs_fsverity_ioend_cache = kmem_cache_create("xfs_fsverity_ioend",
+ sizeof(struct xfs_fsverity_ioend),
+ 0, 0, NULL);
+ if (!xfs_fsverity_ioend_cache)
+ goto out_destroy_parent_args_cache;
+#endif
+
return 0;
+#ifdef CONFIG_FS_VERITY
+ out_destroy_parent_args_cache:
+ kmem_cache_destroy(xfs_parent_args_cache);
+#endif
out_destroy_xmi_cache:
kmem_cache_destroy(xfs_xmi_cache);
out_destroy_xmd_cache:
@@ -2580,6 +2599,9 @@ xfs_destroy_caches(void)
* destroy caches.
*/
rcu_barrier();
+#ifdef CONFIG_FS_VERITY
+ kmem_cache_destroy(xfs_fsverity_ioend_cache);
+#endif
kmem_cache_destroy(xfs_parent_args_cache);
kmem_cache_destroy(xfs_xmd_cache);
kmem_cache_destroy(xfs_xmi_cache);
@@ -2660,10 +2682,14 @@ init_xfs_fs(void)
if (error)
goto out;
- error = xfs_init_workqueues();
+ error = xfs_fsverity_init();
if (error)
goto out_destroy_caches;
+ error = xfs_init_workqueues();
+ if (error)
+ goto out_fsverity_exit;
+
error = xfs_mru_cache_init();
if (error)
goto out_destroy_wq;
@@ -2740,6 +2766,8 @@ init_xfs_fs(void)
xfs_mru_cache_uninit();
out_destroy_wq:
xfs_destroy_workqueues();
+ out_fsverity_exit:
+ xfs_fsverity_exit();
out_destroy_caches:
xfs_destroy_caches();
out:
@@ -2763,6 +2791,7 @@ exit_xfs_fs(void)
xfs_cleanup_procfs();
xfs_mru_cache_uninit();
xfs_destroy_workqueues();
+ xfs_fsverity_exit();
xfs_destroy_caches();
xfs_uuid_table_free();
}
diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h
index 6c467ded9751f..f9433332c274b 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 */
|
| 19/3 | 2026/10/03 09:24 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 33973 output: 31 thoughts: 2464 Thoughts: **KMSAN Evaluation for fsverity XFS Support** Okay, let's break this down. My goal is to determine if this patch series warrants a dedicated KMSAN fuzzing session. The key is identifying if these changes introduce *unique* risks that KMSAN is designed to catch – specifically, uninitialized memory reads or information leaks to userspace – and that other tools like KASAN, LOCKDEP, or UBSAN wouldn't already flag. The general rules are clear: if a bug *can* be caught by a standard detector, no KMSAN session is needed. KMSAN is for the gaps. I need to focus on potential scenarios where uninitialized memory *escapes* into user space, or where logic branches on undefined values. So, let's look at this patch series, which brings fsverity support to XFS and refactors existing fsverity code. I'm seeing changes across a range of files, so I have to be thorough. First, I see several files in btrfs, ext4, f2fs, verity and stat.c are being updated. It appears that the filesystems are just getting the benefit of fsverity changes and removal of duplicate checks. But now, I'm focusing on the XFS-specific code changes. The XFS changes are extensive, involving many files: `xfs_bmap.c`, `xfs_format.h`, `xfs_fs.h`, health tracking, inode verification, scrub/repair, AOPs, BMAP utilities, file operations, `xfs_fsverity.c`, and IOCTLs. So far, the XFS code changes look quite complex. Now I'm looking for info-leaks and uninitialized reads. I see IOCTLs are involved, which immediately raises a flag. We need to check those closely! Specifically, the `FS_IOC_ENABLE_VERITY`, `FS_IOC_MEASURE_VERITY`, and `FS_IOC_READ_VERITY_METADATA` ioctls. These are generic fsverity ioctls. I need to understand what data is being passed *to* and *from* these ioctls, especially the `FS_IOC_READ_VERITY_METADATA` ioctl, because this *could* involve copying kernel data into userspace. If uninitialized memory is part of that descriptor data, it's a potential KMSAN target. I should check how `fsverity_ioctl_read_metadata`, `fsverity_ioctl_measure`, and the internals of `xfs_fsverity_get_descriptor` handle the descriptor and what it contains. A quick use of `grepper` or `read-file` to zero in on these functions, and then more close inspection of the data flow, is what I need. |
| 20/3 | 2026/10/03 09:24 | tool | grepper |
0mArgs:{
"Expression": "fsverity_ioctl_",
"PathPrefix": "fs/verity/"
}Results: {
"Output": "fs/verity/enable.c=181=static int enable_verity(struct file *filp,\n--\nfs/verity/enable.c-318-/**\nfs/verity/enable.c:319: * fsverity_ioctl_enable() - enable verity on a file\nfs/verity/enable.c-320- * @filp: file to enable verity on\n--\nfs/verity/enable.c-327- */\nfs/verity/enable.c:328:int fsverity_ioctl_enable(struct file *filp, const void __user *uarg)\nfs/verity/enable.c-329-{\n--\nfs/verity/enable.c-414-}\nfs/verity/enable.c:415:EXPORT_SYMBOL_GPL(fsverity_ioctl_enable);\n--\nfs/verity/measure.c-15-/**\nfs/verity/measure.c:16: * fsverity_ioctl_measure() - get a verity file's digest\nfs/verity/measure.c-17- * @filp: file to get digest of\n--\nfs/verity/measure.c-25- */\nfs/verity/measure.c:26:int fsverity_ioctl_measure(struct file *filp, void __user *_uarg)\nfs/verity/measure.c-27-{\n--\nfs/verity/measure.c-61-}\nfs/verity/measure.c:62:EXPORT_SYMBOL_GPL(fsverity_ioctl_measure);\nfs/verity/measure.c-63-\n--\nfs/verity/read_metadata.c=128=static int fsverity_read_signature(struct inode *inode,\n--\nfs/verity/read_metadata.c-154-/**\nfs/verity/read_metadata.c:155: * fsverity_ioctl_read_metadata() - read verity metadata from a file\nfs/verity/read_metadata.c-156- * @filp: file to read the metadata from\n--\nfs/verity/read_metadata.c-160- */\nfs/verity/read_metadata.c:161:int fsverity_ioctl_read_metadata(struct file *filp, const void __user *uarg)\nfs/verity/read_metadata.c-162-{\n--\nfs/verity/read_metadata.c-203-}\nfs/verity/read_metadata.c:204:EXPORT_SYMBOL_GPL(fsverity_ioctl_read_metadata);\n"
} |
| 21/3 | 2026/10/03 09:24 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 37728 output: 32 thoughts: 2827 Thoughts: **Analyzing XFS fs-verity Descriptor Retrieval and Potential Vulnerabilities** Okay, let's break down this `xfs_fsverity_get_descriptor` function. My initial thought is to ensure I fully understand the logic here. I'm focusing on how the descriptor is located and read. I need to make sure I don't miss anything that might lead to inconsistencies. First, I see the code checks for the `S_VERITY` flag. Good, we're confirming this is a verity-enabled inode before doing anything else. Next, I observe the use of `xfs_bmap_last_extent` to find the last extent and from this calculate `last_block_offset`. I need to remember the purpose of using the last extent, it seems critical for locating the descriptor. Now, the calculations start: `desc_size_pos` is calculated from the `last_block_offset`. This position is where the descriptor size itself is stored. I want to pay attention to that `sizeof(__be32)`. Then, the descriptor size (`desc_size`) is read. I need to ensure that the read operation itself is robust, and the check for `XFS_IS_CORRUPT` is crucial. The size is being checked and any inconsistencies must be caught, and errors returned. This is where I start to see a potential problem. `desc_pos` is calculated as `round_down(desc_size_pos - desc_size, blocksize)`. This seems correct as it finds the position where we will read the data, down aligned to `blocksize`. However, this is where the potential for a vulnerability, or data corruption, could lie. This is a point where a difference in the code could potentially cause issues. The problem comes down to the discrepancy between how the descriptor is written, and how it is read. Specifically, looking at `xfs_fsverity_write_descriptor`, `desc_pos` is calculated by a `round_up` operation: `desc_pos = round_up(tree_last_block, XFS_FSVERITY_START_ALIGN)`. This differs from the read calculation where we `round_down`. Also, `desc_size_pos = round_up(desc_end + sizeof(desc_size_disk), blksize) - sizeof(desc_size_disk);`. This is where the file system block size is introduced as part of the equation, to account for how and where things are written to disk. The important thing is that these calculations must be congruent and match in order to read and write the descriptor from disk, otherwise, the verity check can be bypassed. I need to confirm if `desc_size_pos - desc_size` is ever not equal to `desc_pos`. I'm starting to think about how this affects the `fsverity_pagecache_read` function. Is it robust against inconsistencies? So, I dig deeper and examine `fsverity_pagecache_read`. I confirm that it uses `read_mapping_folio` which, for regular files and metadata, reads from disk. This is good to know, confirming the read operations are directly interacting with the physical data. The data could be corrupt. I need to consider scenarios of maliciously crafted images or potential disk errors. Then, I see `memcpy_from_file_folio`. That's where the actual data transfer happens, but also any inconsistencies. Then I note the patch replaces `pagecache_read` with `fsverity_pagecache_read`, so there is a unification across file systems and thus a possible inconsistency. The older implementations for ext4 and f2fs are slightly different than what is used. I must confirm the new implementation works with these older implementations, as well, or if this is the source of a subtle vulnerability. Finally, I notice the addition of `STATX_ATTR_VERITY` in `fs/stat.c`. This is good, ensuring the verity attribute is properly reflected in the file system's metadata. This must be present in the user-accessible system calls to confirm file systems have verity enabled. |
| 22/3 | 2026/10/03 09:24 | tool | grepper |
0mArgs:{
"Expression": "vfs_getattr_nosec",
"PathPrefix": "fs/stat.c"
}Results: {
"Output": "fs/stat.c=166=EXPORT_SYMBOL_GPL(generic_fill_statx_atomic_writes);\n--\nfs/stat.c-168-/**\nfs/stat.c:169: * vfs_getattr_nosec - getattr without security checks\nfs/stat.c-170- * @path: file to get attributes from\n--\nfs/stat.c-180- */\nfs/stat.c:181:int vfs_getattr_nosec(const struct path *path, struct kstat *stat,\nfs/stat.c-182-\t\t u32 request_mask, unsigned int query_flags)\n--\nfs/stat.c-234-}\nfs/stat.c:235:EXPORT_SYMBOL(vfs_getattr_nosec);\nfs/stat.c-236-\n--\nfs/stat.c=258=int vfs_getattr(const struct path *path, struct kstat *stat,\n--\nfs/stat.c-265-\t\treturn retval;\nfs/stat.c:266:\treturn vfs_getattr_nosec(path, stat, request_mask, query_flags);\nfs/stat.c-267-}\n"
} |
| 23/3 | 2026/10/03 09:24 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 41777 output: 33 thoughts: 21 Thoughts: Okay, here's my take on those lines in `fs/stat.c`, as if they're my internal monologue: **Diving into fs/stat.c: Lines 180-234** Alright, time to get my hands dirty. Let's zero in on lines 180 through 234 within `fs/stat.c`. I have a feeling something interesting is happening in this segment. I need to understand exactly how the file statistics are being handled here. What's the context? What calls are being made? Are we dealing with the core stat system calls themselves, or are we deep within a particular file system's implementation? I should start by getting a high-level view and noting any function calls or loops that immediately stand out. I'll need to remember to pay attention to any potential error paths or special cases handled in this region, as this kind of code can get quite dense. The goal is to determine the critical data access, transformations, and control flow relevant to this block of code. From the surrounding code, I'm expecting to be dealing with gathering and structuring file metadata. I have an inkling it's not a generic `stat` function, but likely one that implements the mechanics for certain file systems. Time to dive in and get specific. |
| 24/3 | 2026/10/03 09:24 | tool | read-file |
0mArgs:{
"File": "fs/stat.c",
"FirstLine": 180,
"LineCount": 55
}Results: {
"Contents": " 180:\t */\n 181:\tint vfs_getattr_nosec(const struct path *path, struct kstat *stat,\n 182:\t\t\t u32 request_mask, unsigned int query_flags)\n 183:\t{\n 184:\t\tconst struct mnt_idmap *idmap;\n 185:\t\tstruct inode *inode = d_backing_inode(path-\u003edentry);\n 186:\t\n 187:\t\tmemset(stat, 0, sizeof(*stat));\n 188:\t\tstat-\u003eresult_mask |= STATX_BASIC_STATS;\n 189:\t\tquery_flags \u0026= AT_STATX_SYNC_TYPE;\n 190:\t\n 191:\t\t/* allow the fs to override these if it really wants to */\n 192:\t\t/* SB_NOATIME means filesystem supplies dummy atime value */\n 193:\t\tif (inode-\u003ei_sb-\u003es_flags \u0026 SB_NOATIME)\n 194:\t\t\tstat-\u003eresult_mask \u0026= ~STATX_ATIME;\n 195:\t\n 196:\t\t/*\n 197:\t\t * Note: If you add another clause to set an attribute flag, please\n 198:\t\t * update attributes_mask below.\n 199:\t\t */\n 200:\t\tif (IS_AUTOMOUNT(inode))\n 201:\t\t\tstat-\u003eattributes |= STATX_ATTR_AUTOMOUNT;\n 202:\t\n 203:\t\tif (IS_DAX(inode))\n 204:\t\t\tstat-\u003eattributes |= STATX_ATTR_DAX;\n 205:\t\n 206:\t\tif (IS_VERITY(inode))\n 207:\t\t\tstat-\u003eattributes |= STATX_ATTR_VERITY;\n 208:\t\n 209:\t\tstat-\u003eattributes_mask |= (STATX_ATTR_AUTOMOUNT |\n 210:\t\t\t\t\t STATX_ATTR_DAX |\n 211:\t\t\t\t\t STATX_ATTR_VERITY);\n 212:\t\n 213:\t\tidmap = mnt_idmap(path-\u003emnt);\n 214:\t\tif (inode-\u003ei_op-\u003egetattr) {\n 215:\t\t\tint ret;\n 216:\t\n 217:\t\t\tret = inode-\u003ei_op-\u003egetattr(idmap, path, stat, request_mask,\n 218:\t\t\t\t\tquery_flags);\n 219:\t\t\tif (ret)\n 220:\t\t\t\treturn ret;\n 221:\t\t} else {\n 222:\t\t\tgeneric_fillattr(idmap, request_mask, inode, stat);\n 223:\t\t}\n 224:\t\n 225:\t\t/*\n 226:\t\t * If this is a block device inode, override the filesystem attributes\n 227:\t\t * with the block device specific parameters that need to be obtained\n 228:\t\t * from the bdev backing inode.\n 229:\t\t */\n 230:\t\tif (S_ISBLK(stat-\u003emode))\n 231:\t\t\tbdev_statx(path, stat, request_mask);\n 232:\t\n 233:\t\treturn 0;\n 234:\t}\n"
} |
| 25/3 | 2026/10/03 09:24 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 42931 output: 28 thoughts: 1439 Thoughts: **Analyzing XFS fsverity Implementation** Okay, let's break this down. First, line 187 zeroes `stat` before any attributes are set. That's good to see, ensuring a clean slate. Now, I need to understand the interaction of `xfs_fsverity.c` with the broader XFS changes. I'm focusing on the fsverity integration. Right, `xfs_fsverity_ioend_cache` and `xfs_fsverity_ioend_pool` are declared. The mempool is used to back fsverity ioend allocations to prevent deadlocks in read I/O completion, a very smart design choice. `xfs_fsverity_init` and `xfs_fsverity_exit` look straightforward: `mempool_init_slab_pool` and `mempool_exit`, respectively. Wait, looking at `xfs_init_caches` in `xfs_super.c` is important. This is where `xfs_fsverity_ioend_cache` is created using `kmem_cache_create`. I see that it's allocated with `GFP_NOFS` which is essential in an interrupt context. Then the mempool is populated with `mempool_alloc` within `xfs_end_io_read`. Here's how it works: I see that `xfs_end_io_read` checks `CONFIG_FS_VERITY`, checks for no errors, and then calls `xfs_fsverity_is_file_data` for a given file offset. If this file is verity enabled, a `xfs_fsverity_ioend` struct is allocated from the mempool and its `ioend` and `work` fields are populated: `fsv_ioend->ioend = ioend;` and `INIT_WORK(&fsv_ioend->work, xfs_end_fsverity_io_read);`. This is then enqueued with `fsverity_enqueue_verify_work`. The `xfs_fsverity_ioend` struct is straightforward: it contains the original `iomap_ioend` and a work queue entry. `xfs_end_fsverity_io_read` then retrieves the `xfs_fsverity_ioend` from the work queue, and crucially calls `mempool_free(fsv_ioend, &xfs_fsverity_ioend_pool)`. It then checks for I/O errors and calls `fsverity_verify_bio` and finishes with `iomap_finish_ioends`. There are no uninitialized fields accessed, which is good. Okay, let's now investigate `xfs_fsverity_is_hashes_of_zeroed_blocks`. The implementation looks like it performs an optimization for a block full of hashes of zeroed blocks. It avoids storing the block by synthesizing it later, but only when the Merkle tree block size equals the FS block size. It checks if the block starts with `zero_digest` and then verifies that the rest of the block contains the same digest. This is a nice optimization. Now the question is: where do `zero_digest` and `digest_size` come from? They are passed into `xfs_fsverity_is_hashes_of_zeroed_blocks`. The code shows they come from `fs/verity/enable.c` in the `write_merkle_tree_block` function. Now, I need to see how they are initialized. I need to check `fs/verity/enable.c` and find the `params->zero_digest` initialization. |
| 26/3 | 2026/10/03 09:25 | tool | grepper |
0mArgs:{
"Expression": "zero_digest",
"PathPrefix": "fs/verity/"
}Results: {
"Output": "fs/verity/enable.c=44=static int write_merkle_tree_block(struct file *file, const u8 *buf,\n--\nfs/verity/enable.c-53-\t\t\t\t\t\t\t params-\u003eblock_size,\nfs/verity/enable.c:54:\t\t\t\t\t\t\t params-\u003ezero_digest,\nfs/verity/enable.c-55-\t\t\t\t\t\t\t params-\u003edigest_size);\n--\nfs/verity/fsverity_private.h=40=struct merkle_tree_params {\n--\nfs/verity/fsverity_private.h-56-\t/* the hash of an all-zeroes block */\nfs/verity/fsverity_private.h:57:\tu8 zero_digest[FS_VERITY_MAX_DIGEST_SIZE];\nfs/verity/fsverity_private.h-58-\n--\nfs/verity/open.c=38=int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,\n--\nfs/verity/open.c-156-\tfsverity_hash_block(params, page_address(ZERO_PAGE(0)),\nfs/verity/open.c:157:\t\t\t params-\u003ezero_digest);\nfs/verity/open.c-158-\n--\nfs/verity/pagecache.c=68=void fsverity_fill_zerohash(struct folio *folio, size_t offset, size_t len,\n--\nfs/verity/pagecache.c-76-\tfor (; off \u003c (offset + len); off += vi-\u003etree_params.digest_size)\nfs/verity/pagecache.c:77:\t\tmemcpy_to_folio(folio, off, vi-\u003etree_params.zero_digest,\nfs/verity/pagecache.c-78-\t\t\t\tvi-\u003etree_params.digest_size);\n"
} |
| 27/3 | 2026/10/03 09:25 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 45691 output: 33 thoughts: 87 Thoughts: Here's my thought process, as it stands: **Analyzing `fsverity` Merkle Tree Initialization** Okay, so I'm digging into how `fsverity` is setting up its Merkle trees. I've got a specific question in mind. I know that `zero_digest` gets calculated and fully initialized within `fsverity_hash_block`, which is called during `fsverity_init_merkle_tree_params`. Good, that seems straightforward. But, I'm now wondering about the `buf` parameter that's passed to `write_merkle_tree_block`. I need to understand how *that* is being handled and initialized. Where does `buf` originate? What does it contain? My next step is to trace back the calls to `write_merkle_tree_block`. Let's jump into `fs/verity/enable.c`. I need to see the contexts in which it's being used. Hopefully, this will shed some light on the lifecycle of `buf` and how it's being managed in this initialization process. |
| 28/3 | 2026/10/03 09:25 | tool | grepper |
0mArgs:{
"Expression": "write_merkle_tree_block",
"PathPrefix": "fs/verity/"
}Results: {
"Output": "fs/verity/enable.c=22=static int hash_one_block(const struct merkle_tree_params *params,\n--\nfs/verity/enable.c-43-\nfs/verity/enable.c:44:static int write_merkle_tree_block(struct file *file, const u8 *buf,\nfs/verity/enable.c-45-\t\t\t\t unsigned long index,\n--\nfs/verity/enable.c-51-\nfs/verity/enable.c:52:\terr = inode-\u003ei_sb-\u003es_vop-\u003ewrite_merkle_tree_block(file, buf, pos,\nfs/verity/enable.c-53-\t\t\t\t\t\t\t params-\u003eblock_size,\n--\nfs/verity/enable.c-66- * The tree is written to a filesystem-specific location as determined by the\nfs/verity/enable.c:67: * -\u003ewrite_merkle_tree_block() method. However, the blocks that comprise the\nfs/verity/enable.c-68- * tree are the same for all filesystems.\n--\nfs/verity/enable.c=70=static int build_merkle_tree(struct file *filp,\n--\nfs/verity/enable.c-140-\t\t\t\tgoto out;\nfs/verity/enable.c:141:\t\t\terr = write_merkle_tree_block(filp,\nfs/verity/enable.c-142-\t\t\t\t\t\t buffers[level].data,\n--\nfs/verity/enable.c-160-\t\t\t\tgoto out;\nfs/verity/enable.c:161:\t\t\terr = write_merkle_tree_block(filp,\nfs/verity/enable.c-162-\t\t\t\t\t\t buffers[level].data,\n"
} |
| 29/3 | 2026/10/03 09:25 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 46495 output: 35 thoughts: 25 Thoughts: Okay, here's my summary, presented as a first-person account, focusing on the allocation and population of `buffers[level].data` within the context of Merkle tree construction, and tailored to someone with a strong technical background: **Analyzing the Data Allocation in Merkle Tree Construction** Alright, let's dive into the core of how this Merkle tree is being built, specifically focusing on the critical allocation and population of the `buffers[level].data` structure within the `build_merkle_tree` function. I need to understand this part precisely, as memory management and data structures are the backbone of performance and correctness here. So, `buffers` is clearly an array (or vector, depending on the implementation) representing different levels of the tree. The index `level` tells me which level we're currently working on, probably ranging from the leaf level up to the root. The key is how `buffers[level].data` is being treated. Is it pre-allocated with a fixed size, or is it dynamically resized? How is the sizing logic handled? I need to look closely at the memory allocation function; is it `malloc`, `new`, or something more specific to the framework, and how is it connected to a memory pool or other memory management strategies? Next, I need to see how the data within `buffers[level].data` is actually *populated*. Is it being filled with pre-computed hashes from the lower levels? Are we reading data directly from storage at the leaf level and then hashing it? I expect some sort of iterative process where the data at the current level is derived from the data at the lower level(s), like `hash(left_child, right_child)`. The order of operations is crucial here, since any reordering could violate the tree's integrity. Are we operating on blocks, chunks, or individual bytes? How is the padding or edge case handling managed for odd numbers of nodes at any level? Furthermore, I'll be particularly interested in how we're avoiding recomputation of hashes or data duplication. If the data is being re-hashed many times, that would be very inefficient. Caching or memoization, if implemented correctly, would be a huge optimization. I will watch how the data pointer is manipulated, or if there is the possibility of any pointer arithmetic errors. I also need to verify that there are no race conditions or concurrency issues. I will carefully trace if the data is protected and consistent. In short, I'm going to look for the memory allocation strategy, how the tree structure is represented in memory, how the data flows between the levels, and any optimizations used to minimize memory and computational overhead. These are the main points I need to ensure that the process is efficient, correct, and secure. |
| 30/3 | 2026/10/03 09:25 | tool | read-file |
0mArgs:{
"File": "fs/verity/enable.c",
"FirstLine": 70,
"LineCount": 50
}Results: {
"Contents": " 70:\tstatic int build_merkle_tree(struct file *filp,\n 71:\t\t\t\t const struct merkle_tree_params *params,\n 72:\t\t\t\t u8 *root_hash)\n 73:\t{\n 74:\t\tstruct inode *inode = file_inode(filp);\n 75:\t\tconst u64 data_size = inode-\u003ei_size;\n 76:\t\tconst int num_levels = params-\u003enum_levels;\n 77:\t\tstruct block_buffer _buffers[1 + FS_VERITY_MAX_LEVELS + 1] = {};\n 78:\t\tstruct block_buffer *buffers = \u0026_buffers[1];\n 79:\t\tunsigned long level_offset[FS_VERITY_MAX_LEVELS];\n 80:\t\tint level;\n 81:\t\tu64 offset;\n 82:\t\tint err;\n 83:\t\n 84:\t\tif (data_size == 0) {\n 85:\t\t\t/* Empty file is a special case; root hash is all 0's */\n 86:\t\t\tmemset(root_hash, 0, params-\u003edigest_size);\n 87:\t\t\treturn 0;\n 88:\t\t}\n 89:\t\n 90:\t\t/*\n 91:\t\t * Allocate the block buffers. Buffer \"-1\" is for data blocks.\n 92:\t\t * Buffers 0 \u003c= level \u003c num_levels are for the actual tree levels.\n 93:\t\t * Buffer 'num_levels' is for the root hash.\n 94:\t\t */\n 95:\t\tfor (level = -1; level \u003c num_levels; level++) {\n 96:\t\t\tbuffers[level].data = kzalloc(params-\u003eblock_size, GFP_KERNEL);\n 97:\t\t\tif (!buffers[level].data) {\n 98:\t\t\t\terr = -ENOMEM;\n 99:\t\t\t\tgoto out;\n 100:\t\t\t}\n 101:\t\t}\n 102:\t\tbuffers[num_levels].data = root_hash;\n 103:\t\tbuffers[num_levels].is_root_hash = true;\n 104:\t\n 105:\t\tBUILD_BUG_ON(sizeof(level_offset) != sizeof(params-\u003elevel_start));\n 106:\t\tmemcpy(level_offset, params-\u003elevel_start, sizeof(level_offset));\n 107:\t\n 108:\t\t/* Hash each data block, also hashing the tree blocks as they fill up */\n 109:\t\tfor (offset = 0; offset \u003c data_size; offset += params-\u003eblock_size) {\n 110:\t\t\tssize_t bytes_read;\n 111:\t\t\tloff_t pos = offset;\n 112:\t\n 113:\t\t\tbuffers[-1].filled = min_t(u64, params-\u003eblock_size,\n 114:\t\t\t\t\t\t data_size - offset);\n 115:\t\t\tbytes_read = __kernel_read(filp, buffers[-1].data,\n 116:\t\t\t\t\t\t buffers[-1].filled, \u0026pos);\n 117:\t\t\tif (bytes_read \u003c 0) {\n 118:\t\t\t\terr = bytes_read;\n 119:\t\t\t\tfsverity_err(inode, \"Error %d reading file data\", err);\n"
} |
| 31/3 | 2026/10/03 09:25 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 47962 output: 28 thoughts: 779 Thoughts: **Analyzing Verity Implementation and Potential Memory Safety Concerns** Okay, so the `kzalloc(params->block_size, GFP_KERNEL);` call confirms the buffers are zero-initialized. Good, that's the first hurdle. Now, I need to verify how `fsverity` is integrated within the XFS filesystem code, specifically focusing on potential uninitialized memory usage during scrubbing and repair operations. I've examined `xchk_inode_setup_verity`, `xchk_inode_verity_broken`, and `xrep_inode_verity`. These functions primarily manipulate flags and pointers related to the `fsverity` status within inodes (`inode_set_flags`, `sc->ip->i_diflags2`). From what I can tell, I'm not seeing any immediate red flags, no obvious use of uninitialized memory here. They seem to use boolean flags which would not be sensitive. However, I just spotted something that demands closer inspection: `xfs_fs_geometry`. It retrieves and returns filesystem geometry information. Specifically, it populates a `struct xfs_fsop_geom *geo` structure, which gets passed to userspace via the `xfs_ioc_fsgeometry` ioctl. It sets a `XFS_FSOP_GEOM_FLAGS_VERITY` flag if `xfs_has_verity` is set on the mounted filesystem. The crucial question is: is this `geo` struct completely initialized before being returned? I have to check the `xfs_fs_geometry` code to make sure. I must examine where `geo` is allocated, initialized, and how it is later used within the ioctl. I'll need to confirm that all of its fields are properly set to prevent information leakage, or other memory safety issues. |
| 32/3 | 2026/10/03 09:25 | tool | grepper |
0mArgs:{
"Expression": "memset.*geo",
"PathPrefix": "fs/xfs/"
}Results: {
"Output": "fs/xfs/libxfs/xfs_ag.c=954=xfs_ag_get_geometry(\n--\nfs/xfs/libxfs/xfs_ag.c-973-\t/* Fill out form. */\nfs/xfs/libxfs/xfs_ag.c:974:\tmemset(ageo, 0, sizeof(*ageo));\nfs/xfs/libxfs/xfs_ag.c-975-\tageo-\u003eag_number = pag_agno(pag);\n--\nfs/xfs/libxfs/xfs_attr_leaf.c=1223=xfs_attr3_leaf_to_shortform(\n--\nfs/xfs/libxfs/xfs_attr_leaf.c-1247-\t/* XXX (dgc): buffer is about to be marked stale - why zero it? */\nfs/xfs/libxfs/xfs_attr_leaf.c:1248:\tmemset(bp-\u003eb_addr, 0, args-\u003egeo-\u003eblksize);\nfs/xfs/libxfs/xfs_attr_leaf.c-1249-\n--\nfs/xfs/libxfs/xfs_attr_leaf.c=1383=xfs_attr3_leaf_create(\n--\nfs/xfs/libxfs/xfs_attr_leaf.c-1403-\tleaf = bp-\u003eb_addr;\nfs/xfs/libxfs/xfs_attr_leaf.c:1404:\tmemset(leaf, 0, args-\u003egeo-\u003eblksize);\nfs/xfs/libxfs/xfs_attr_leaf.c-1405-\n--\nfs/xfs/libxfs/xfs_attr_leaf.c=1757=xfs_attr3_leaf_compact(\n--\nfs/xfs/libxfs/xfs_attr_leaf.c-1771-\tmemcpy(tmpbuffer, bp-\u003eb_addr, args-\u003egeo-\u003eblksize);\nfs/xfs/libxfs/xfs_attr_leaf.c:1772:\tmemset(bp-\u003eb_addr, 0, args-\u003egeo-\u003eblksize);\nfs/xfs/libxfs/xfs_attr_leaf.c-1773-\tleaf_src = (xfs_attr_leafblock_t *)tmpbuffer;\n--\nfs/xfs/libxfs/xfs_dir2_data.c=723=xfs_dir3_data_init(\n--\nfs/xfs/libxfs/xfs_dir2_data.c-753-\thdr = bp-\u003eb_addr;\nfs/xfs/libxfs/xfs_dir2_data.c:754:\tmemset(hdr, 0, geo-\u003edata_entry_offset);\nfs/xfs/libxfs/xfs_dir2_data.c-755-\n--\nfs/xfs/libxfs/xfs_rtgroup.c=272=xfs_rtgroup_get_geometry(\n--\nfs/xfs/libxfs/xfs_rtgroup.c-276-\t/* Fill out form. */\nfs/xfs/libxfs/xfs_rtgroup.c:277:\tmemset(rgeo, 0, sizeof(*rgeo));\nfs/xfs/libxfs/xfs_rtgroup.c-278-\trgeo-\u003erg_number = rtg_rgno(rtg);\n--\nfs/xfs/libxfs/xfs_sb.c=1506=xfs_fs_geometry(\n--\nfs/xfs/libxfs/xfs_sb.c-1512-\nfs/xfs/libxfs/xfs_sb.c:1513:\tmemset(geo, 0, sizeof(struct xfs_fsop_geom));\nfs/xfs/libxfs/xfs_sb.c-1514-\n"
} |
| 33/3 | 2026/10/03 09:25 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 50137 output: 28 thoughts: 271 Thoughts: **My Initial Thoughts on XFS Geometry and Health Checks** Okay, first things first. Line 1513 of `fs/xfs/libxfs/xfs_sb.c` shows a `memset` on `geo`, which means the `geo` structure is being completely zeroed out. That's good to know – it sets a baseline. Now, I need to understand what potentially unhealthy inode issues are associated with that. I'll shift my focus to `fs/xfs/xfs_health.c`, particularly the `ino_map`. Looking at the `ino_map` structure, I see a few familiar constants associated with unhealthy inodes, and now the addition of `XFS_SICK_INO_FSVERITY`. That's interesting, I need to remember that that has been added and how it may affect the bulkstat operation (`xfs_bulkstat`). The `ino_map` is crucial because it gives the context of what the various `XFS_SICK_INO_*` constants mean in terms of underlying problems, like zapped directories and such. Now I need to understand how the `bs_sick` field in bulkstat gets filled in within the `xfs_health.c` code to see the actual link. |
| 34/3 | 2026/10/03 09:25 | tool | grepper |
0mArgs:{
"Expression": "ino_map",
"PathPrefix": "fs/xfs/"
}Results: {
"Output": "fs/xfs/xfs_health.c=600=xfs_healthmon_rtgroup_mask(\n--\nfs/xfs/xfs_health.c-613-\nfs/xfs/xfs_health.c:614:static const struct ioctl_sick_map ino_map[] = {\nfs/xfs/xfs_health.c-615-\t{ XFS_SICK_INO_CORE,\tXFS_BS_SICK_INODE },\n--\nfs/xfs/xfs_health.c=633=xfs_bulkstat_health(\n--\nfs/xfs/xfs_health.c-644-\txfs_inode_measure_sickness(ip, \u0026sick, \u0026checked);\nfs/xfs/xfs_health.c:645:\tfor_each_sick_map(ino_map, m) {\nfs/xfs/xfs_health.c-646-\t\tif (checked \u0026 m-\u003esick_mask)\n--\nfs/xfs/xfs_health.c=655=xfs_healthmon_inode_mask(\n--\nfs/xfs/xfs_health.c-660-\nfs/xfs/xfs_health.c:661:\tfor_each_sick_map(ino_map, m) {\nfs/xfs/xfs_health.c-662-\t\tif (sick_mask \u0026 m-\u003esick_mask)\n"
} |
| 35/3 | 2026/10/03 09:25 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 51075 output: 39 thoughts: 27 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, and written as if they were my own: **Diving into xfs_bulkstat_health** Alright, let's pull up `xfs_bulkstat_health` and take a look. I'm focusing on its implementation within `fs/xfs/xfs_health.c`. Clearly, this function is central to the XFS health monitoring system. I need to understand its role in providing bulk status information, and how it interacts with the underlying XFS data structures and potential error conditions. What parameters does it take? What information does it gather, and how is that information presented? My goal is to quickly assess its functionality and understand any potential bottlenecks or areas for improvement in terms of performance and reliability. I need to determine how robust this function is under heavy load and with various filesystem states. Does it handle race conditions gracefully? What about locking within its execution? I should also examine how this plays with other health-related diagnostics. Is this a critical piece of the health check puzzle? Where does it fit into the wider scheme of XFS integrity checks and proactive monitoring? |
| 36/3 | 2026/10/03 09:25 | tool | read-file |
0mArgs:{
"File": "fs/xfs/xfs_health.c",
"FirstLine": 633,
"LineCount": 25
}Results: {
"Contents": " 633:\txfs_bulkstat_health(\n 634:\t\tstruct xfs_inode\t\t*ip,\n 635:\t\tstruct xfs_bulkstat\t\t*bs)\n 636:\t{\n 637:\t\tconst struct ioctl_sick_map\t*m;\n 638:\t\tunsigned int\t\t\tsick;\n 639:\t\tunsigned int\t\t\tchecked;\n 640:\t\n 641:\t\tbs-\u003ebs_sick = 0;\n 642:\t\tbs-\u003ebs_checked = 0;\n 643:\t\n 644:\t\txfs_inode_measure_sickness(ip, \u0026sick, \u0026checked);\n 645:\t\tfor_each_sick_map(ino_map, m) {\n 646:\t\t\tif (checked \u0026 m-\u003esick_mask)\n 647:\t\t\t\tbs-\u003ebs_checked |= m-\u003eioctl_mask;\n 648:\t\t\tif (sick \u0026 m-\u003esick_mask)\n 649:\t\t\t\tbs-\u003ebs_sick |= m-\u003eioctl_mask;\n 650:\t\t}\n 651:\t}\n 652:\t\n 653:\t/* Translate XFS_SICK_INO_* into XFS_BS_SICK_*. */\n 654:\tunsigned int\n 655:\txfs_healthmon_inode_mask(\n 656:\t\tunsigned int\t\t\tsick_mask)\n 657:\t{\n"
} |
| 37/3 | 2026/10/03 09:25 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 51759 output: 32 thoughts: 667 Thoughts: **Analyzing Potential DIO Alignment Issues with `fsverity_active`** Okay, let's break this down. First, I see that both `bs->bs_sick` and `bs->bs_checked` are initialized and populated. That's a standard starting point. Now, I'm shifting focus to understand how the system reports Direct I/O (DIO) alignment, especially concerning `fsverity`. Looking at `xfs_ip2xflags`, I see that the `flags` bitmask is initialized to zero and then various flags like `FS_XFLAG_DAX`, `FS_XFLAG_COWEXTSIZE`, and now `FS_XFLAG_VERITY` are conditionally added. Seems straightforward. But here's where it gets interesting: I'm examining `xfs_report_dioalign`. Immediately, I see that `stat->result_mask` gets `STATX_DIOALIGN` and `STATX_DIO_READ_ALIGN` set. Then, there's a conditional check: `if (fsverity_active(VFS_I(ip))) return;`. The key point is: if `fsverity_active` is true, it just *returns*. This is where my expert instincts kick in. Hold on a second, what happens to the rest of the `stat` structure if we return early? I immediately recall that in `vfs_getattr_nosec`, we have `memset(stat, 0, sizeof(*stat));`. So, fields like `stat->dio_mem_align`, `stat->dio_offset_align`, etc., are *already* zeroed before `xfs_report_dioalign` is even called. So, `xfs_report_dioalign` sets `result_mask`, *then* checks `fsverity_active`, and *if* active, returns without touching any other fields of `stat`. The question is, why is it setting the `result_mask` at all if it's going to return early? And if `stat->dio_mem_align` remains zero (because we returned early), how does that translate in user space when using the statx system call? Is it interpreted correctly? I'm going to have to dive deeper and completely re-examine `xfs_report_dioalign` to get the full picture. |
| 38/3 | 2026/10/03 09:25 | tool | grepper |
0mArgs:{
"Expression": "xfs_report_dioalign",
"PathPrefix": "fs/xfs/"
}Results: {
"Output": "fs/xfs/xfs_iops.c=574=static void\nfs/xfs/xfs_iops.c:575:xfs_report_dioalign(\nfs/xfs/xfs_iops.c-576-\tstruct xfs_inode\t*ip,\n--\nfs/xfs/xfs_iops.c=686=xfs_vn_getattr(\n--\nfs/xfs/xfs_iops.c-746-\t\tif (request_mask \u0026 (STATX_DIOALIGN | STATX_DIO_READ_ALIGN))\nfs/xfs/xfs_iops.c:747:\t\t\txfs_report_dioalign(ip, stat);\nfs/xfs/xfs_iops.c-748-\t\tif (request_mask \u0026 STATX_WRITE_ATOMIC)\n"
} |
| 39/3 | 2026/10/03 09:25 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 53182 output: 40 thoughts: 13 Thoughts: Okay, here's my interpretation of "Let's read `xfs_report_dioalign`" as a thought process, tailored for an expert audience, and rendered in the first person: **Diving into `xfs_report_dioalign`** Alright, time to crack open `xfs_report_dioalign`. I need a quick refresher on this utility. It’s been a while since I've needed to explicitly check direct I/O alignment with XFS. I suspect there's a specific issue I'm chasing, likely a performance bottleneck or perhaps an unexpected discrepancy in I/O behavior that's prompting me to re-evaluate how direct I/O is interacting with the underlying block device. I remember it's useful for verifying alignment of direct I/O requests, making sure they're optimally sized and positioned relative to the file system's block size and the device's sector size, especially in scenarios involving high-throughput storage. I expect to be looking for misalignments that could lead to read-modify-write operations on the device, significantly hurting performance. I'll be focused on how it handles different block sizes, RAID configurations, and potential LVM or other logical volume layers. The output is likely going to show the reported alignment, sector size, and any detected offsets, and I'll need to interpret that within the context of the current storage setup. Let's dig in and see what `xfs_report_dioalign` has to tell me. I hope the output is straightforward enough to get me up to speed, and I am very familiar with all the nuance. |
| 40/3 | 2026/10/03 09:25 | tool | read-file |
0mArgs:{
"File": "fs/xfs/xfs_iops.c",
"FirstLine": 574,
"LineCount": 40
}Results: {
"Contents": " 574:\tstatic void\n 575:\txfs_report_dioalign(\n 576:\t\tstruct xfs_inode\t*ip,\n 577:\t\tstruct kstat\t\t*stat)\n 578:\t{\n 579:\t\tstruct xfs_buftarg\t*target = xfs_inode_buftarg(ip);\n 580:\t\tstruct block_device\t*bdev = target-\u003ebt_bdev;\n 581:\t\n 582:\t\tstat-\u003eresult_mask |= STATX_DIOALIGN | STATX_DIO_READ_ALIGN;\n 583:\t\n 584:\t\tif (fsverity_active(VFS_I(ip)))\n 585:\t\t\treturn;\n 586:\t\n 587:\t\tstat-\u003edio_mem_align = bdev_dma_alignment(bdev) + 1;\n 588:\t\n 589:\t\t/*\n 590:\t\t * For COW inodes, we can only perform out of place writes of entire\n 591:\t\t * allocation units (blocks or RT extents).\n 592:\t\t * For writes smaller than the allocation unit, we must fall back to\n 593:\t\t * buffered I/O to perform read-modify-write cycles. At best this is\n 594:\t\t * highly inefficient; at worst it leads to page cache invalidation\n 595:\t\t * races. Tell applications to avoid this by reporting the larger write\n 596:\t\t * alignment in dio_offset_align, and the smaller read alignment in\n 597:\t\t * dio_read_offset_align.\n 598:\t\t */\n 599:\t\tstat-\u003edio_read_offset_align = bdev_logical_block_size(bdev);\n 600:\t\tif (xfs_is_cow_inode(ip))\n 601:\t\t\tstat-\u003edio_offset_align = xfs_inode_alloc_unitsize(ip);\n 602:\t\telse\n 603:\t\t\tstat-\u003edio_offset_align = stat-\u003edio_read_offset_align;\n 604:\t}\n 605:\t\n 606:\tunsigned int\n 607:\txfs_get_atomic_write_min(\n 608:\t\tstruct xfs_inode\t*ip)\n 609:\t{\n 610:\t\tstruct xfs_mount\t*mp = ip-\u003ei_mount;\n 611:\t\n 612:\t\t/*\n 613:\t\t * If we can complete an atomic write via atomic out of place writes,\n"
} |
| 41/3 | 2026/10/03 09:25 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 54234 output: 25 thoughts: 467 Thoughts: **Analyzing Direct I/O and XFS Read Paths** Okay, so I'm digging into how direct I/O (DIO) behaves, especially with files that might not natively support it, like verity files. My initial thought is that if a file doesn't support DIO, `STATX_DIOALIGN` would set `dio_mem_align` to zero. It's essentially signaling that DIO is not supported, and since `vfs_getattr_nosec` initializes the stat structure to zero, I'm confident there's no risk of uninitialized memory being exposed here. We're safe in that regard. Now, I'm shifting my focus to `xfs_ioend_submit_read`. Specifically, I'm looking at the inclusion of a new `struct fsverity_info` pointer, `vi`. I can see that `ioend->io_vi` gets set to `vi`. I want to understand where `vi` originates. So, let's trace the callers of `xfs_ioend_submit_read`, beginning with `xfs_bio_submit_read`. Looking at the code snippet, it's clear that the `ctx->vi` variable is passed to `xfs_ioend_submit_read`. Now, I'm searching for the `iomap_read_folio_ctx` struct to see where `ctx->vi` is populated. That should lead me to the source of `vi`. I will keep digging... |
| 42/3 | 2026/10/03 09:25 | tool | grepper |
0mArgs:{
"Expression": "read_ctx",
"PathPrefix": "fs/"
}Results: {
"Output": "\nFull output is too long, showing 200 out of 239 lines.\nUse more precise expression if possible.\n\nfs/erofs/data.c=394=static int erofs_read_folio(struct file *file, struct folio *folio)\nfs/erofs/data.c-395-{\nfs/erofs/data.c:396:\tstruct iomap_read_folio_ctx read_ctx = {\nfs/erofs/data.c-397-\t\t.ops\t\t= \u0026iomap_bio_read_ops,\n--\nfs/erofs/data.c-405-\ttrace_erofs_read_folio(iter_ctx.realinode, folio, true);\nfs/erofs/data.c:406:\tiomap_read_folio(\u0026erofs_iomap_ops, \u0026read_ctx, \u0026iter_ctx);\nfs/erofs/data.c-407-\tif (need_iput)\n--\nfs/erofs/data.c=412=static void erofs_readahead(struct readahead_control *rac)\nfs/erofs/data.c-413-{\nfs/erofs/data.c:414:\tstruct iomap_read_folio_ctx read_ctx = {\nfs/erofs/data.c-415-\t\t.ops\t\t= \u0026iomap_bio_read_ops,\n--\nfs/erofs/data.c-424-\t\t\t readahead_count(rac), true);\nfs/erofs/data.c:425:\tiomap_readahead(\u0026erofs_iomap_ops, \u0026read_ctx, \u0026iter_ctx);\nfs/erofs/data.c-426-\tif (need_iput)\n--\nfs/f2fs/data.c-32-\nfs/f2fs/data.c:33:static struct kmem_cache *bio_post_read_ctx_cache;\nfs/f2fs/data.c-34-static struct kmem_cache *bio_entry_slab;\nfs/f2fs/data.c=35=static struct kmem_cache *ffs_entry_slab;\nfs/f2fs/data.c:36:static mempool_t *bio_post_read_ctx_pool;\nfs/f2fs/data.c-37-static struct bio_set f2fs_bioset;\n--\nfs/f2fs/data.c=100=enum bio_post_read_step {\n--\nfs/f2fs/data.c-112-\nfs/f2fs/data.c:113:struct bio_post_read_ctx {\nfs/f2fs/data.c-114-\tstruct bio *bio;\n--\nfs/f2fs/data.c=142=static void f2fs_finish_read_bio(struct bio *bio, bool in_task)\n--\nfs/f2fs/data.c-144-\tstruct folio_iter fi;\nfs/f2fs/data.c:145:\tstruct bio_post_read_ctx *ctx = bio-\u003ebi_private;\nfs/f2fs/data.c-146-\tunsigned long flags;\n--\nfs/f2fs/data.c-184-\tif (ctx)\nfs/f2fs/data.c:185:\t\tmempool_free(ctx, bio_post_read_ctx_pool);\nfs/f2fs/data.c-186-\tbio_put(bio);\n--\nfs/f2fs/data.c=189=static void f2fs_verify_bio(struct work_struct *work)\nfs/f2fs/data.c-190-{\nfs/f2fs/data.c:191:\tstruct bio_post_read_ctx *ctx =\nfs/f2fs/data.c:192:\t\tcontainer_of(work, struct bio_post_read_ctx, work);\nfs/f2fs/data.c-193-\tstruct bio *bio = ctx-\u003ebio;\n--\nfs/f2fs/data.c-199-\t * will be disabled for this, decryption and/or decompression may still\nfs/f2fs/data.c:200:\t * be needed, resulting in another bio_post_read_ctx being allocated.\nfs/f2fs/data.c-201-\t * So to prevent deadlocks we need to release the current ctx to the\n--\nfs/f2fs/data.c-203-\t */\nfs/f2fs/data.c:204:\tmempool_free(ctx, bio_post_read_ctx_pool);\nfs/f2fs/data.c-205-\tbio-\u003ebi_private = NULL;\n--\nfs/f2fs/data.c=239=static void f2fs_verify_and_finish_bio(struct bio *bio, bool in_task)\nfs/f2fs/data.c-240-{\nfs/f2fs/data.c:241:\tstruct bio_post_read_ctx *ctx = bio-\u003ebi_private;\nfs/f2fs/data.c-242-\n--\nfs/f2fs/data.c-259- */\nfs/f2fs/data.c:260:static void f2fs_handle_step_decompress(struct bio_post_read_ctx *ctx,\nfs/f2fs/data.c-261-\t\tbool in_task)\n--\nfs/f2fs/data.c=290=static void f2fs_post_read_work(struct work_struct *work)\nfs/f2fs/data.c-291-{\nfs/f2fs/data.c:292:\tstruct bio_post_read_ctx *ctx =\nfs/f2fs/data.c:293:\t\tcontainer_of(work, struct bio_post_read_ctx, work);\nfs/f2fs/data.c-294-\tstruct bio *bio = ctx-\u003ebio;\n--\nfs/f2fs/data.c=302=static void f2fs_read_end_io(struct bio *bio)\n--\nfs/f2fs/data.c-304-\tstruct f2fs_sb_info *sbi = F2FS_F_SB(bio_first_folio_all(bio));\nfs/f2fs/data.c:305:\tstruct bio_post_read_ctx *ctx;\nfs/f2fs/data.c-306-\tbool intask = in_task() \u0026\u0026 !irqs_disabled();\n--\nfs/f2fs/data.c=1132=static struct bio *f2fs_grab_read_bio(struct inode *inode,\n--\nfs/f2fs/data.c-1138-\tstruct bio *bio;\nfs/f2fs/data.c:1139:\tstruct bio_post_read_ctx *ctx = NULL;\nfs/f2fs/data.c-1140-\tunsigned int post_read_steps = 0;\n--\nfs/f2fs/data.c-1156-\t * contain both compressed and uncompressed clusters. We'll allocate a\nfs/f2fs/data.c:1157:\t * bio_post_read_ctx if the file is compressed, but the caller is\nfs/f2fs/data.c-1158-\t * responsible for enabling STEP_DECOMPRESS if it's actually needed.\n--\nfs/f2fs/data.c-1162-\t\t/* Due to the mempool, this never fails. */\nfs/f2fs/data.c:1163:\t\tctx = mempool_alloc(bio_post_read_ctx_pool, GFP_NOFS);\nfs/f2fs/data.c-1164-\t\tctx-\u003ebio = bio;\n--\nfs/f2fs/data.c=2317=int f2fs_read_multi_pages(struct compress_ctx *cc, struct bio **bio_ret,\n--\nfs/f2fs/data.c-2420-\t\tblock_t blkaddr;\nfs/f2fs/data.c:2421:\t\tstruct bio_post_read_ctx *ctx;\nfs/f2fs/data.c-2422-\n--\nfs/f2fs/data.c-2453-\nfs/f2fs/data.c:2454:\t\tctx = get_post_read_ctx(bio);\nfs/f2fs/data.c-2455-\t\tctx-\u003eenabled_steps |= STEP_DECOMPRESS;\n--\nfs/f2fs/data.c=4456=int __init f2fs_init_post_read_processing(void)\nfs/f2fs/data.c-4457-{\nfs/f2fs/data.c:4458:\tbio_post_read_ctx_cache =\nfs/f2fs/data.c:4459:\t\tkmem_cache_create(\"f2fs_bio_post_read_ctx\",\nfs/f2fs/data.c:4460:\t\t\t\t sizeof(struct bio_post_read_ctx), 0, 0, NULL);\nfs/f2fs/data.c:4461:\tif (!bio_post_read_ctx_cache)\nfs/f2fs/data.c-4462-\t\tgoto fail;\nfs/f2fs/data.c:4463:\tbio_post_read_ctx_pool =\nfs/f2fs/data.c-4464-\t\tmempool_create_slab_pool(NUM_PREALLOC_POST_READ_CTXS,\nfs/f2fs/data.c:4465:\t\t\t\t\t bio_post_read_ctx_cache);\nfs/f2fs/data.c:4466:\tif (!bio_post_read_ctx_pool)\nfs/f2fs/data.c-4467-\t\tgoto fail_free_cache;\n--\nfs/f2fs/data.c-4470-fail_free_cache:\nfs/f2fs/data.c:4471:\tkmem_cache_destroy(bio_post_read_ctx_cache);\nfs/f2fs/data.c-4472-fail:\n--\nfs/f2fs/data.c=4476=void f2fs_destroy_post_read_processing(void)\nfs/f2fs/data.c-4477-{\nfs/f2fs/data.c:4478:\tmempool_destroy(bio_post_read_ctx_pool);\nfs/f2fs/data.c:4479:\tkmem_cache_destroy(bio_post_read_ctx_cache);\nfs/f2fs/data.c-4480-}\n--\nfs/f2fs/file.c=5140=static void f2fs_dio_end_bio(struct bio *bio)\n--\nfs/f2fs/file.c-5142-\tstruct bio_iostat_ctx *iostat_ctx = bio-\u003ebi_private;\nfs/f2fs/file.c:5143:\tvoid *orig_bi_private = iostat_ctx-\u003epost_read_ctx;\nfs/f2fs/file.c-5144-\n--\nfs/f2fs/iostat.c=276=void iostat_update_and_unbind_ctx(struct bio *bio)\n--\nfs/f2fs/iostat.c-286-\t\tlat_type = READ_IO;\nfs/f2fs/iostat.c:287:\t\tbio-\u003ebi_private = iostat_ctx-\u003epost_read_ctx;\nfs/f2fs/iostat.c-288-\t}\n--\nfs/f2fs/iostat.c=294=void iostat_alloc_and_bind_ctx(struct f2fs_sb_info *sbi,\nfs/f2fs/iostat.c:295:\t\tstruct bio *bio, struct bio_post_read_ctx *ctx)\nfs/f2fs/iostat.c-296-{\n--\nfs/f2fs/iostat.c-302-\tiostat_ctx-\u003etype = 0;\nfs/f2fs/iostat.c:303:\tiostat_ctx-\u003epost_read_ctx = ctx;\nfs/f2fs/iostat.c-304-\tbio-\u003ebi_private = iostat_ctx;\n--\nfs/f2fs/iostat.h-8-\nfs/f2fs/iostat.h:9:struct bio_post_read_ctx;\nfs/f2fs/iostat.h-10-\n--\nfs/f2fs/iostat.h=40=struct bio_iostat_ctx {\n--\nfs/f2fs/iostat.h-43-\tenum page_type type;\nfs/f2fs/iostat.h:44:\tstruct bio_post_read_ctx *post_read_ctx;\nfs/f2fs/iostat.h-45-};\n--\nfs/f2fs/iostat.h=47=static inline void iostat_update_submit_ctx(struct bio *bio,\n--\nfs/f2fs/iostat.h-55-\nfs/f2fs/iostat.h:56:static inline struct bio_post_read_ctx *get_post_read_ctx(struct bio *bio)\nfs/f2fs/iostat.h-57-{\n--\nfs/f2fs/iostat.h-59-\nfs/f2fs/iostat.h:60:\treturn iostat_ctx-\u003epost_read_ctx;\nfs/f2fs/iostat.h-61-}\n--\nfs/f2fs/iostat.h=64=extern void iostat_alloc_and_bind_ctx(struct f2fs_sb_info *sbi,\nfs/f2fs/iostat.h:65:\t\tstruct bio *bio, struct bio_post_read_ctx *ctx);\nfs/f2fs/iostat.h-66-extern int f2fs_init_iostat_processing(void);\n--\nfs/f2fs/iostat.h=76=static inline void iostat_alloc_and_bind_ctx(struct f2fs_sb_info *sbi,\nfs/f2fs/iostat.h:77:\t\tstruct bio *bio, struct bio_post_read_ctx *ctx) {}\nfs/f2fs/iostat.h-78-static inline void iostat_update_submit_ctx(struct bio *bio,\nfs/f2fs/iostat.h-79-\t\tenum page_type type) {}\nfs/f2fs/iostat.h:80:static inline struct bio_post_read_ctx *get_post_read_ctx(struct bio *bio)\nfs/f2fs/iostat.h-81-{\n--\nfs/fuse/file.c=961=static int fuse_iomap_read_folio_range_async(const struct iomap_iter *iter,\n--\nfs/fuse/file.c-964-{\nfs/fuse/file.c:965:\tstruct fuse_fill_read_data *data = ctx-\u003eread_ctx;\nfs/fuse/file.c-966-\tstruct folio *folio = ctx-\u003ecur_folio;\n--\nfs/fuse/file.c=991=static int fuse_read_folio(struct file *file, struct folio *folio)\n--\nfs/fuse/file.c-999-\t\t.ops = \u0026fuse_iomap_read_ops,\nfs/fuse/file.c:1000:\t\t.read_ctx = \u0026data,\nfs/fuse/file.c-1001-\n--\nfs/fuse/file.c=1093=static void fuse_readahead(struct readahead_control *rac)\n--\nfs/fuse/file.c-1103-\t\t.rac = rac,\nfs/fuse/file.c:1104:\t\t.read_ctx = \u0026data\nfs/fuse/file.c-1105-\t};\n--\nfs/iomap/bio.c=81=void iomap_bio_submit_read_endio(const struct iomap_iter *iter,\n--\nfs/iomap/bio.c-83-{\nfs/iomap/bio.c:84:\tstruct bio *bio = ctx-\u003eread_ctx;\nfs/iomap/bio.c-85-\n--\nfs/iomap/bio.c-90-\nfs/iomap/bio.c:91:\tctx-\u003eread_ctx = NULL;\n\n"
} |
| 43/3 | 2026/10/03 09:25 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 59214 output: 32 thoughts: 29 Thoughts: Here's my take on the thought process, tailored for an expert audience and presented as a first-person summary: **Diving into the iomap_read_folio_ctx** Okay, so I'm looking at something involving I/O mapping and folio operations. Let's get right down to the nitty-gritty. My initial move is to zero in on the `struct iomap_read_folio_ctx` definition. I need to refresh my memory on the context structure used specifically for read operations. Where else would it be defined but in `include/linux/iomap.h`? Let's go check that header file and see what's actually in there. I have an inkling it contains the necessary read parameters, state, and perhaps some internal caching or buffering information to optimize the read workflow through I/O mapping. This is the starting point for me to understand the data structures for this type of operation. |
| 44/3 | 2026/10/03 09:25 | tool | grepper |
0mArgs:{
"Expression": "struct iomap_read_folio_ctx",
"PathPrefix": "include/"
}Results: {
"Output": "include/linux/iomap.h=20=struct iomap_writepage_ctx;\ninclude/linux/iomap.h:21:struct iomap_read_folio_ctx;\ninclude/linux/iomap.h-22-struct iov_iter;\n--\ninclude/linux/iomap.h=437=void iomap_read_folio(const struct iomap_ops *ops,\ninclude/linux/iomap.h:438:\t\tstruct iomap_read_folio_ctx *ctx, void *private);\ninclude/linux/iomap.h-439-void iomap_readahead(const struct iomap_ops *ops,\ninclude/linux/iomap.h:440:\t\tstruct iomap_read_folio_ctx *ctx, void *private);\ninclude/linux/iomap.h-441-bool iomap_is_partially_uptodate(struct folio *, size_t from, size_t count);\n--\ninclude/linux/iomap.h=613=void iomap_bounce_read_end_io(struct iomap_ioend *ioend, struct bio *orig_bio,\n--\ninclude/linux/iomap.h-615-\ninclude/linux/iomap.h:616:struct iomap_read_folio_ctx {\ninclude/linux/iomap.h-617-\tconst struct iomap_read_ops *ops;\n--\ninclude/linux/iomap.h=625=struct iomap_read_ops {\n--\ninclude/linux/iomap.h-634-\tint (*read_folio_range)(const struct iomap_iter *iter,\ninclude/linux/iomap.h:635:\t\t\tstruct iomap_read_folio_ctx *ctx, size_t len);\ninclude/linux/iomap.h-636-\n--\ninclude/linux/iomap.h-642-\tvoid (*submit_read)(const struct iomap_iter *iter,\ninclude/linux/iomap.h:643:\t\t\tstruct iomap_read_folio_ctx *ctx);\ninclude/linux/iomap.h-644-\n--\ninclude/linux/iomap.h=800=int iomap_bio_read_folio_range(const struct iomap_iter *iter,\ninclude/linux/iomap.h:801:\t\tstruct iomap_read_folio_ctx *ctx, size_t plen);\ninclude/linux/iomap.h-802-void iomap_bio_submit_read_endio(const struct iomap_iter *iter,\ninclude/linux/iomap.h:803:\t\tstruct iomap_read_folio_ctx *ctx, bio_end_io_t end_io);\ninclude/linux/iomap.h-804-\n--\ninclude/linux/iomap.h=807=static inline void iomap_bio_read_folio(struct folio *folio,\n--\ninclude/linux/iomap.h-809-{\ninclude/linux/iomap.h:810:\tstruct iomap_read_folio_ctx ctx = {\ninclude/linux/iomap.h-811-\t\t.ops\t\t= \u0026iomap_bio_read_ops,\n--\ninclude/linux/iomap.h=818=static inline void iomap_bio_readahead(struct readahead_control *rac,\n--\ninclude/linux/iomap.h-820-{\ninclude/linux/iomap.h:821:\tstruct iomap_read_folio_ctx ctx = {\ninclude/linux/iomap.h-822-\t\t.ops\t\t= \u0026iomap_bio_read_ops,\n"
} |
| 45/3 | 2026/10/03 09:25 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 60436 output: 37 thoughts: 25 Thoughts: Okay, here's my interpretation of reading those lines of `include/linux/iomap.h`, summarized in the first person, assuming the audience is another expert: **Delving into the iomap Implementation** Alright, let's dive into this specific section of `iomap.h`, lines 614-630. Right, I remember the context. We're talking about the `iov_iter_get_pages()` function, specifically the handling of the various flags. Looking at the code… hmm, we've got some interesting conditional logic in here. It seems like the core of this section is managing how pages are retrieved from the underlying storage, depending on how the I/O is configured. We are checking the `WRITE` flag, obviously. And then, there's a bunch of conditional branches dealing with different aspects of the I/O. I'm noting the checks for `IOMAP_F_DIRECT`, and `IOMAP_F_DIO`. This is where we account for direct I/O and whether or not we are using DIO, so the page handling must be different in that case. Interesting that it's using the same function for both read and write. This means we must be dealing with a unified page management strategy. Ah, I see. It's handling the `IOMAP_F_CONT` flag now. That's probably for contiguous regions, streamlining how the pages are handled within the loop. The logic for skipping unmapped regions is good to see as well, we want to skip as quickly as possible. This section appears to be the point where the actual page retrieval or allocation happens, based on the flags and the iterator's state. It needs to check the flags. Okay, I see. `iov_iter_get_pages` is doing what it's supposed to. |
| 46/3 | 2026/10/03 09:25 | tool | read-file |
0mArgs:{
"File": "include/linux/iomap.h",
"FirstLine": 614,
"LineCount": 20
}Results: {
"Contents": " 614:\t\t\tint error);\n 615:\t\n 616:\tstruct iomap_read_folio_ctx {\n 617:\t\tconst struct iomap_read_ops *ops;\n 618:\t\tstruct folio\t\t*cur_folio;\n 619:\t\tstruct readahead_control *rac;\n 620:\t\tvoid\t\t\t*read_ctx;\n 621:\t\tloff_t\t\t\tread_ctx_file_offset;\n 622:\t\tstruct fsverity_info\t*vi;\n 623:\t};\n 624:\t\n 625:\tstruct iomap_read_ops {\n 626:\t\t/*\n 627:\t\t * Read in a folio range.\n 628:\t\t *\n 629:\t\t * If this succeeds, iomap_finish_folio_read() must be called after the\n 630:\t\t * range is read in, regardless of whether the read succeeded or failed.\n 631:\t\t *\n 632:\t\t * Returns 0 on success or a negative error on failure.\n 633:\t\t */\n"
} |
| 47/3 | 2026/10/03 09:25 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 61189 output: 29 thoughts: 384 Thoughts: **Analyzing XFS I/O Operations with `iomap_read`** Okay, let's break this down. I'm looking at the XFS code, specifically `fs/xfs/xfs_aops.c`. I see `xfs_vm_read_folio` and `xfs_vm_readahead` using the `iomap_read` infrastructure. The important thing here is the context, `struct iomap_read_folio_ctx ctx`. It's initialized in both functions: once with `.cur_folio = folio` and the other with `.rac = rac`. Aha! C99 designated initializers. This means all the other fields of `ctx` that aren't explicitly initialized get zeroed. So, `ctx.read_ctx`, `ctx.read_ctx_file_offset`, and critically, `ctx.vi` are all effectively set to NULL right off the bat. Now the question is: how is `ctx.vi` (which, I assume, holds some vital information) getting set *later*? It can't be set later in the context initialization here. It's obviously used somewhere. I need to trace this variable. Time to look into the implementations of `iomap_read_folio` and `iomap_readahead`. I have a feeling the answer lies within those functions, and that's where the critical manipulation of the context occurs. Let's see how `xfs_read_iomap_ops` is using this `ctx`. |
| 48/3 | 2026/10/03 09:26 | tool | grepper |
0mArgs:{
"Expression": "-\u003evi =",
"PathPrefix": "fs/iomap/"
}Results: {
"Output": "fs/iomap/buffered-io.c=651=void iomap_read_folio(const struct iomap_ops *ops,\n--\nfs/iomap/buffered-io.c-669-\t */\nfs/iomap/buffered-io.c:670:\tctx-\u003evi = fsverity_get_info(iter.inode);\nfs/iomap/buffered-io.c-671-\tif (ctx-\u003evi \u0026\u0026 iter.pos \u003c i_size_read(iter.inode))\n--\nfs/iomap/buffered-io.c=728=void iomap_readahead(const struct iomap_ops *ops,\n--\nfs/iomap/buffered-io.c-745-\t */\nfs/iomap/buffered-io.c:746:\tctx-\u003evi = fsverity_get_info(iter.inode);\nfs/iomap/buffered-io.c-747-\tif (ctx-\u003evi \u0026\u0026 iter.pos \u003c i_size_read(iter.inode))\n"
} |
| 49/3 | 2026/10/03 09:26 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 62204 output: 31 thoughts: 1533 Thoughts: **Analyzing fs-verity Implementation in XFS** Okay, so I'm digging into the XFS implementation of fs-verity. I started by looking at how the `iomap_read_folio` and `iomap_readahead` functions interact with `fsverity_get_info`. Those functions set `ctx->vi`, which seems to be crucial for verity operations. Now, I checked out `xfs_dio_read_bounce_submit_io`. Interesting - the `vi` pointer, presumably carrying fs-verity metadata, is `NULL` in that context. That means direct I/O path is not using that data, or it is set elsewhere. Then, I looked into the read paths: `xfs_file_buffered_read`, `xfs_file_dio_read`, and the main read iterator, `xfs_file_read_iter`. I notice a key point in `xfs_file_dio_read`: There's a re-check of the verity status after acquiring a lock. This guards against a TOCTOU (Time-of-Check-to-Time-of-Use) vulnerability. If verity is active, the function falls back to buffered I/O, which I think is what is happening given the `NULL` `vi` in direct I/O. In `xfs_file_read_iter`, the code checks if verity is active. If so, it disables direct I/O, and the read continues as buffered. Next, I examined `xfs_fsverity_get_descriptor`. This is where the actual fs-verity descriptor retrieval happens. It first locks the inode exclusively, checks for the `S_VERITY` flag (important, indicating that verity is enabled), and then uses the bmap (block map) to locate the descriptor. Several corruption checks are present within this routine. The descriptor is retrieved from the end of the file, presumably as a metadata "tail". The code reads the size of the descriptor first and then reads the descriptor itself. Error handling and corruption checks are quite extensive, including marking the inode as "sick" if something is wrong. But I can't see where it is used. I'm stopping here to ask myself how the `xfs_fsverity_get_descriptor` function is actually *called*. It seems critical for retrieving the verity descriptor, and I need to figure out the calling context. Let me check for callers of the `->get_verity_descriptor()` function, which is, presumably, the interface used by the fs-verity code. |
| 50/3 | 2026/10/03 09:26 | tool | grepper |
0mArgs:{
"Expression": "get_verity_descriptor",
"PathPrefix": "fs/verity/"
}Results: {
"Output": "fs/verity/open.c=314=int fsverity_get_descriptor(struct inode *inode,\n--\nfs/verity/open.c-319-\nfs/verity/open.c:320:\tres = inode-\u003ei_sb-\u003es_vop-\u003eget_verity_descriptor(inode, NULL, 0);\nfs/verity/open.c-321-\tif (res \u003c 0) {\n--\nfs/verity/open.c-333-\t\treturn -ENOMEM;\nfs/verity/open.c:334:\tres = inode-\u003ei_sb-\u003es_vop-\u003eget_verity_descriptor(inode, desc, res);\nfs/verity/open.c-335-\tif (res \u003c 0) {\n"
} |
| 51/3 | 2026/10/03 09:26 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 64533 output: 34 thoughts: 1473 Thoughts: **Analyzing the fsverity Descriptor Retrieval** Okay, let's break this down. I'm looking at `fsverity_get_descriptor`. It starts by getting the descriptor size using `get_verity_descriptor`, then allocates memory for the descriptor and reads it into memory using `fsverity_pagecache_read`. The descriptor itself gets parsed by `fsverity_create_info`. The crucial part here is the positioning of the descriptor on disk. The position, `desc_pos`, is calculated based on `desc_size_pos` and `desc_size`: `desc_pos = round_down(desc_size_pos - desc_size, blocksize)`. Aha, now I'm seeing it more clearly, and this is where it gets interesting. Diving into `xfs_fsverity_write_descriptor`, I see the calculation for `desc_pos` and `desc_size_pos`. It writes the descriptor (`desc`) at `desc_pos` and the descriptor size, `desc_size_disk`, at `desc_size_pos`. The write happens via `iomap_fsverity_write`. Let's clarify what's being written. Let's assume the merkle tree is at block 65536. If `desc_size` is 200 bytes and the block size is 4096, `desc_pos` becomes 65536, and `desc_size_pos` is 69628. So, the descriptor itself goes from 65536 to 65736, and the descriptor size is written at 69628. The space in between is either unallocated or zeroed. Now, during the read, we're calculating `desc_pos` using `desc_size_pos`, which is read from the end of the extent or block. The key is in the calculation `desc_size_pos - desc_size = 69628 - 200 = 69428`, which gets rounded down to 65536. This is perfect. Then, `fsverity_pagecache_read` correctly reads 200 bytes from 65536. This pattern emerges. The really interesting question is what happens if `desc_size` is bigger than one block? Let's say `desc_size` is 5000 bytes. Following the same logic, `desc_size_pos` becomes 73724. Calculating `desc_size_pos - desc_size` gives us 68724, which rounds down to 65536. Again, it works! This shows the alignment is key to the correct calculation. So, `desc_size_pos` effectively is `desc_pos + desc_size + padding`. Because `desc_pos` is aligned to the block size, and the padding is smaller than the block size, rounding down gets us back to `desc_pos`. Clever! It's a neat trick. Ext4 is probably doing the same thing. I'll check `/fs/ext4/verity.c` line 285 to confirm. Let me just grep for `desc_size_pos`. Bingo! Let's check the code! |
| 52/3 | 2026/10/03 09:26 | tool | grepper |
0mArgs:{
"Expression": "desc_size_pos",
"PathPrefix": "fs/ext4/verity.c"
}Results: {
"Output": "fs/ext4/verity.c=145=static int ext4_write_verity_descriptor(struct inode *inode, const void *desc,\n--\nfs/ext4/verity.c-151-\tconst __le32 desc_size_disk = cpu_to_le32(desc_size);\nfs/ext4/verity.c:152:\tconst u64 desc_size_pos = round_up(desc_end + sizeof(desc_size_disk),\nfs/ext4/verity.c-153-\t\t\t\t\t i_blocksize(inode)) -\n--\nfs/ext4/verity.c-161-\treturn pagecache_write(inode, \u0026desc_size_disk, sizeof(desc_size_disk),\nfs/ext4/verity.c:162:\t\t\t desc_size_pos);\nfs/ext4/verity.c-163-}\n--\nfs/ext4/verity.c=245=static int ext4_get_verity_descriptor_location(struct inode *inode,\n--\nfs/ext4/verity.c-251-\tu32 end_lblk;\nfs/ext4/verity.c:252:\tu64 desc_size_pos;\nfs/ext4/verity.c-253-\t__le32 desc_size_disk;\n--\nfs/ext4/verity.c-280-\t\t ext4_ext_get_actual_len(last_extent);\nfs/ext4/verity.c:281:\tdesc_size_pos = EXT4_LBLK_TO_B(inode, end_lblk);\nfs/ext4/verity.c-282-\text4_free_ext_path(path);\nfs/ext4/verity.c-283-\nfs/ext4/verity.c:284:\tif (desc_size_pos \u003c sizeof(desc_size_disk))\nfs/ext4/verity.c-285-\t\tgoto bad;\nfs/ext4/verity.c:286:\tdesc_size_pos -= sizeof(desc_size_disk);\nfs/ext4/verity.c-287-\nfs/ext4/verity.c-288-\terr = fsverity_pagecache_read(inode, \u0026desc_size_disk,\nfs/ext4/verity.c:289:\t\t\t\t sizeof(desc_size_disk), desc_size_pos);\nfs/ext4/verity.c-290-\tif (err)\n--\nfs/ext4/verity.c-298-\nfs/ext4/verity.c:299:\tif (desc_size \u003e INT_MAX || desc_size \u003e desc_size_pos)\nfs/ext4/verity.c-300-\t\tgoto bad;\nfs/ext4/verity.c-301-\nfs/ext4/verity.c:302:\tdesc_pos = round_down(desc_size_pos - desc_size, i_blocksize(inode));\nfs/ext4/verity.c-303-\tif (desc_pos \u003c ext4_verity_metadata_pos(inode))\n"
} |
| 53/3 | 2026/10/03 09:26 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 67574 output: 640 thoughts: 2672 Thoughts: **KMSAN Assessment: FS Verity in XFS** Okay, let's break this down. It seems the core of this patch is implementing file system verity within XFS. My primary concern, as always, is data integrity and security, so I need to carefully scrutinize potential avenues for uninitialized memory usage or information leaks, especially those that KMSAN is uniquely equipped to catch. First, the pattern looks very similar to how verity was implemented in ext4 – that's a good starting point. I can leverage my understanding of the established verity implementation in other file systems. Next, I methodically went through the patch, cross-referencing against the criteria for `NeedsKMSAN=true`. I reviewed every instance of memory allocation and data transfer to userspace. I paid extra attention to potentially uninitialized fields in kernel structures and conditional logic based on potentially uninitialized variables. I looked at the structs that are touched or returned, making sure `stat`, `geo`, `bs`, `flags` are set. I confirmed all relevant structures like `vfs_getattr_nosec`, `xfs_report_dioalign`, `xfs_fs_geometry`, `xfs_bulkstat_health`, and several FS_IOC ioctls are appropriately initialized before being sent to userspace. I have confirmed that any complex data structures like `xfs_fsverity_ioend` and `iomap_read_folio_ctx` are fully initialized on allocation. I carefully examined potential risks in the `fsverity_pagecache_read`, specifically regarding read failures and data copies. I verified the logic is sound and covers all cases. I also looked at potential issues in truncation and extent operations, where I confirmed `xfs_can_free_eofblocks` and `xfs_free_eofblocks` don't leak information. I went through the entire logic of `xfs_fsverity_get_descriptor` to ensure that data is properly populated and there are no info leaks. I have also examined `xfs_zoned_buffered_write_iomap_begin` and `xfs_iomap_writepages`, and confirmed there are no issues. After the review, I determined that all newly introduced and touched data structures are fully zeroed or initialized. There are no exposed uninitialized variables or structs. In short: all the potential bugs I found in the code review are KASAN detectable bugs, or are bugs that fall into the category that standard debuggers cover, so there is no need for KMSAN. I will set `NeedsKMSAN=false` and move on. |
| 54/3 | 2026/10/03 09:26 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series implements fs-verity support for XFS, optimizes/refactors Merkle tree block zero-digest handling across filesystems (ext4, btrfs, f2fs, xfs), unifies pagecache metadata reading via `fsverity_pagecache_read()`, and exposes the STATX_ATTR_VERITY flag centrally in VFS stat.\n\nAnalysis of memory safety and sanitizer applicability:\n1. Kernel-to-userspace copies and info-leaks:\n - `vfs_getattr_nosec` sets `STATX_ATTR_VERITY` on `stat`, which is explicitly zeroed via `memset(stat, 0, sizeof(*stat))` at the start of the function.\n - `xfs_report_dioalign` conditionally skips populating `dio_mem_align` for fsverity files; the underlying `kstat` struct was already cleared with `memset`.\n - `xfs_fs_geometry` sets `XFS_FSOP_GEOM_FLAGS_VERITY` on `geo`, which is cleared via `memset(geo, 0, sizeof(*geo))` in `xfs_fs_geometry`.\n - Bulkstat health reporting (`xfs_bulkstat_health`) explicitly initializes `bs-\u003ebs_sick = 0` and `bs-\u003ebs_checked = 0` prior to bitmask assignment.\n - Verity ioctls (`FS_IOC_ENABLE_VERITY`, `FS_IOC_MEASURE_VERITY`, `FS_IOC_READ_VERITY_METADATA`) use existing fsverity core handlers.\n\n2. Structure allocation and initialization:\n - `xfs_fsverity_ioend` allocated from `xfs_fsverity_ioend_pool` has both of its member fields (`ioend` and `work`) explicitly initialized before use or queueing.\n - Designated initializers (`struct iomap_read_folio_ctx ctx = { .cur_folio = folio };`) guarantee all other fields (`vi`, `read_ctx`, etc.) are zero-initialized.\n - Merkle tree buffers in `build_merkle_tree` are zero-allocated with `kzalloc`.\n\n3. Primary risk profile:\n - Potential failure modes in this patch series involve extent mapping and truncation logic (`xfs_bmapi_convert_one_delalloc`, `__xfs_bunmapi`, `xfs_free_eofblocks`), iomap folio iterations, race conditions/locking around verity enablement, and bio completion workqueue handling.\n - These risks (out-of-bounds folio accesses, buffer overflows, use-after-free, and lock inversion) are effectively detected by KASAN and LOCKDEP. There are no uninitialized memory reads or information leak risks that would require dedicated KMSAN fuzzing.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series implements fs-verity support for XFS, optimizes/refactors Merkle tree block zero-digest handling across filesystems (ext4, btrfs, f2fs, xfs), unifies pagecache metadata reading via `fsverity_pagecache_read()`, and exposes the STATX_ATTR_VERITY flag centrally in VFS stat.\n\nAnalysis of memory safety and sanitizer applicability:\n1. Kernel-to-userspace copies and info-leaks:\n - `vfs_getattr_nosec` sets `STATX_ATTR_VERITY` on `stat`, which is explicitly zeroed via `memset(stat, 0, sizeof(*stat))` at the start of the function.\n - `xfs_report_dioalign` conditionally skips populating `dio_mem_align` for fsverity files; the underlying `kstat` struct was already cleared with `memset`.\n - `xfs_fs_geometry` sets `XFS_FSOP_GEOM_FLAGS_VERITY` on `geo`, which is cleared via `memset(geo, 0, sizeof(*geo))` in `xfs_fs_geometry`.\n - Bulkstat health reporting (`xfs_bulkstat_health`) explicitly initializes `bs-\u003ebs_sick = 0` and `bs-\u003ebs_checked = 0` prior to bitmask assignment.\n - Verity ioctls (`FS_IOC_ENABLE_VERITY`, `FS_IOC_MEASURE_VERITY`, `FS_IOC_READ_VERITY_METADATA`) use existing fsverity core handlers.\n\n2. Structure allocation and initialization:\n - `xfs_fsverity_ioend` allocated from `xfs_fsverity_ioend_pool` has both of its member fields (`ioend` and `work`) explicitly initialized before use or queueing.\n - Designated initializers (`struct iomap_read_folio_ctx ctx = { .cur_folio = folio };`) guarantee all other fields (`vi`, `read_ctx`, etc.) are zero-initialized.\n - Merkle tree buffers in `build_merkle_tree` are zero-allocated with `kzalloc`.\n\n3. Primary risk profile:\n - Potential failure modes in this patch series involve extent mapping and truncation logic (`xfs_bmapi_convert_one_delalloc`, `__xfs_bunmapi`, `xfs_free_eofblocks`), iomap folio iterations, race conditions/locking around verity enablement, and bio completion workqueue handling.\n - These risks (out-of-bounds folio accesses, buffer overflows, use-after-free, and lock inversion) are effectively detected by KASAN and LOCKDEP. There are no uninitialized memory reads or information leak risks that would require dedicated KMSAN fuzzing.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|