Linux filesystem development
 help / color / mirror / Atom feed
* [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree
@ 2026-07-10  8:52 Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 01/20] fsverity: report validation errors through fserror to fsnotify Andrey Albershteyn
                   ` (20 more replies)
  0 siblings, 21 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong, linux-unionfs, david

Hi all,

This is v11 of fsverity for XFS. This revision includes fixes for issues
found by sashiko.dev + rebase to the latest master.

Below are my responses to the sashiko points.

Range-diff with v10 also below.

Patches 5, 6, and 10 are new.

Patches without review:
[PATCH v11 05/20] fsverity: improve flushing performance of
[PATCH v11 06/20] fsverity: don't allow setting DAX file attribute on
[PATCH v11 10/20] xfs: don't report dio_mem_align and
[PATCH v11 11/20] xfs: disable direct read path for fs-verity files
[PATCH v11 12/20] xfs: handle fsverity I/O in write/read path
[PATCH v11 14/20] xfs: add fs-verity support
[PATCH v11 15/20] xfs: remove unwritten extents after preallocations

This series based on v7.2-rc2.

kernel:
https://git.kernel.org/pub/scm/linux/kernel/git/aalbersh/xfs-linux.git/log/?h=fsverity

xfsprogs:
https://git.kernel.org/pub/scm/linux/kernel/git/aalbersh/xfsprogs-dev.git/log/?h=fsverity

xfstests:
https://git.kernel.org/pub/scm/linux/kernel/git/aalbersh/xfstests-dev.git/log/?h=fsverity

sashiko review:
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org

v10:
https://lore.kernel.org/fsverity/20260520123722.405752-1-aalbersh@kernel.org/#r

v9:
https://lore.kernel.org/fsverity/20260428083332.768693-1-aalbersh@kernel.org/#r

Cc: fsverity@lists.linux.dev
Cc: linux-fsdevel@vger.kernel.org
Cc: linux-xfs@vger.kernel.org
Cc: linux-unionfs@vger.kernel.org

Cc: david@fromorbit.com
Cc: djwong@kernel.org
Cc: ebiggers@kernel.org
Cc: hch@lst.de


---
Changes in v11:
- Drop wrong overlayfs patch
- Drop already merged iomap and fsverity patches
- Update to I_INO() use instead of ip->i_ino
- Sashiko.dev fixes. See list of issues below.
Changes in v10:
- Rebase to v7.1-rc3 with relevant adjustments
- Initialize ioend->io_vi to NULL to not get write work onto verity wq
- Range diff below
Changes in v9:
- Fix fsverity_fill_zerohash() parameter names
- A few fixes found by sashiko.dev:
	- Replace ip->i_mount->m_attr_geo->blksize with m_sb.sb_blocksize
	- Don't call xfs_trans_cancel() after xfs_trans_commit() in
	  xfs_fsverity_end_enable()
	- Call xfs_fsverity_delete_metadata() if verity enable failed
	- Change start/end type from xfs_fileoff_t to loff_t
	- Return xfs_trans_commit() error from
	  xfs_fsverity_cancel_unwritten()
Changes in v8:
- Return fsverity_ensure_verity_info() errors from
  ovl_ensure_verity_loaded()
Changes in v7:
- Move kerneldoc to fsverity_ensure_verity_info() definition
- Drop patch adding XFS traces
- Fix overly long line in the comment
- Make order of fserror and fsverity_error consistent
- Add overlay patch converting to fsverity_ensure_verity_info()
Changes in v6:
- Removed stub for fsverity_ensure_verity_info() as it's optimized out
- Rename fsverity_folio_zero_hash() to fsverify_fill_zerohash()
- Merge patches 8 to 10 into one
- Merge patch gerating zero_hash and fsverity_fill_zerohash() into one
- Add kerneldoc to fsverity_ensure_verity_info()
- Add comments to iomap_block_needs_zeroing()
Changes in v5:
- Add fserror_report_data_lost() for data blocks in page spanning EOF
- Issue fsverity metadata readahead in data readahead
- iomap_fsverity_write() return type fix
- Use of S_ISREG(mode)
- Make 65536 #define instead of open-coded
- Use transaction per unwritten extent removal
- Fetch fsverity_info for all fsverity metadata
- Revert fsverity_folio_zero_hash() stub as used in iomap
- Extend cancel_unwritten to whole file range to remove cow leftovers
- Drop delayed allocation on the COW fork on fsverity completion
Changes in v4:
- Use fserror interface in fsverity instead of fs callback
- Hoist pagecache_read from f2fs/ext4 to fsverity
- Refactor iomap code
- Fetch fsverity_info only for file data and merkle tree holes
- Do not disable preallocation, remove unwritten extents instead
- Offload fsverity hash I/O to fsverity workqueue in read path
- Store merkle tree at round_up(i_size, 64k)
- Add a spacing between merkle tree and fsverity descriptor as next 64k
  aligned block
- Squash helpers into first user commits
- Squash on-disk format changes into single commit
- Drop different offset for pagecache/on-disk
- Don't zero out pages in higher order folios in write path
- Link to v3: https://lore.kernel.org/fsverity/20260217231937.1183679-1-aalbersh@kernel.org/T/#t
Changes in v3:
- Different on-disk and pagecache offset
- Use read path ioends
- Switch to hashtable fsverity info
- Synthesize merkle tree blocks full of zeroes
- Other minor refactors
- Link to v2: https://lore.kernel.org/fsverity/20260114164210.GO15583@frogsfrogsfrogs/T/#t
Changes in v2:
- Move to VFS interface for merkle tree block reading
- Drop patchset for per filesystem workqueues
- Change how offsets of the descriptor and tree metadata is calculated
- Store fs-verity descriptor in data fork side by side with merkle tree
- Simplify iomap changes, remove interface for post eof read/write
- Get rid of extended attribute implementation
- Link to v1: https://lore.kernel.org/r/20250728-fsverity-v1-0-9e5443af0e34@kernel.org

-- >8 --
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=3

> Could bypassing kernel_file_open() break fs-verity initialization for encrypted
> lower files?
> Previously, kernel_file_open() triggered the underlying filesystem's open
> routine (such as ext4_file_open()), which in turn called fscrypt_file_open()
> to initialize the file's encryption key (inode->i_crypt_info).
> By calling fsverity_ensure_verity_info() directly, the fscrypt initialization
> is skipped. If a lower file uses both fs-verity and fscrypt, reading the
> verity descriptor via pagecache_read() will trigger
> fscrypt_decrypt_pagecache_blocks() upon I/O completion.
> Since the encryption key was never initialized, it appears this could lead to
> a NULL pointer dereference on inode->i_crypt_info when accessed by
> fscrypt_decrypt_pagecache_blocks().
> Does this allow an unprivileged user to trigger a kernel panic simply by
> stat'ing the file during path lookup?

Dropped

------------------------------------------------------------------------
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=4

> Does this loop cause excessive cache flushing overhead?
> Looking at memcpy_to_folio(), it maps the folio, copies the data, unmaps it,
> and then unconditionally calls flush_dcache_folio() on the entire folio:
> include/linux/highmem.h:memcpy_to_folio() {
> 	...
> 	do {
> 		char *to = kmap_local_folio(folio, offset);
> 		...
> 		memcpy(to, from, chunk);
> 		kunmap_local(to);
> 		...
> 	} while (len > 0);
> 	flush_dcache_folio(folio);
> }
> Because memcpy_to_folio() is called here once for every digest size
> (typically 32 bytes), a single 4KB folio will trigger 128 full folio cache
> flushes. For a 2MB large folio, this results in over 65,000 full folio cache
> flushes, creating O(N^2) cache flushing behavior.
> Could this function map the folio directly, perform all inner digest copies in
> a single pass, unmap it, and then flush the cache exactly once?

This is valid, open-coded the copying to modify folio once and then flush it.

------------------------------------------------------------------------
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=7

> Does this check wpc->iomap.flags before the current folio's mapping is
> initialized?

yes

> Looking at iomap_writeback_folio() below, iomap_writeback_handle_eof() is
> called early in the function to determine if the folio is past EOF. At this
> point, wpc->iomap has not yet been populated for the current folio; it is
> updated later when iomap_writeback_range() calls the map_blocks callback.
> This means iomap_writeback_handle_eof() appears to read stale state from the
> previously mapped folio, or zero if it is the first folio in the writepage
> context.
> If a regular file data block was processed previously, wpc->iomap.flags will
> lack IOMAP_F_FSVERITY, causing fs-verity metadata folios to be treated as
> invalid past-EOF data and incorrectly skipped.
> Conversely, if cyclic writeback wraps around to index 0 after processing an
> fs-verity folio, could the stale IOMAP_F_FSVERITY flag cause regular folios
> to incorrectly trigger the WARN_ON_ONCE() here?

The wpc->iomap is not initialized at this point for the first folio. The
XFS uses wpc->iomap.flags field to communicate to iomap that writeback is
happening for fsverity folios located beyond EOF. With XFS implementation data
folios are written back before merkle tree construction, so, we won't get any
data folios here.

That's true that flag is stale from a previous folio, however, all folios have
it set in the mapping at this point.

Or it could be done like this, without EOF check for fsverity writeback:
https://lore.kernel.org/fsverity/20250728-fsverity-v1-1-9e5443af0e34@kernel.org/

------------------------------------------------------------------------
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=8

> Does this leave the folio range uninitialized if ctx->vi is NULL?

yes, intentional

> Page cache folios are not zeroed by default. If we skip calling
> folio_zero_range() but still mark the range as uptodate, could this
> expose uninitialized memory to the fs-verity core during descriptor parsing?

yes, intentional

> Additionally, does this unconditionally fill the hole with zero-hashes if
> ctx->vi is present, even if this is the padding hole past the fsverity
> descriptor?

No, it doesn't as ctx->vi will not be set

> If the padding hole length is not aligned to the digest size, could this
> trigger a warning in fsverity_fill_zerohash()?

It's aligned as it always starts on at a block.

> It seems an explicit fallback to folio_zero_range() might be needed for both
> the NULL ctx->vi case and the descriptor padding hole case.

------------------------------------------------------------------------
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=9

> Will this unconditionally convert short writes into -EIO?
> iomap_file_buffered_write() implements standard VFS write semantics: if an
> error (like -ENOSPC or -EDQUOT) occurs after successfully writing part of the
> data across multiple extents, it returns the positive number of bytes written
> so far.
> Because this code does not loop while length > 0, a short write results in
> ret > 0 but ret != length. Masking the true cause of the error and returning
> a hardware error to userspace might cause confusion.
> Should this code implement a retry loop to properly extract the underlying
> error code?

Yes this is true, but we can not get these error codes from iomap_iter.

------------------------------------------------------------------------
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=10

> Since XFS_SB_FEAT_RO_COMPAT_VERITY is intentionally omitted from
> XFS_SB_FEAT_RO_COMPAT_ALL to force read-only mounts, won't the superblock
> verifier explicitly treat it as an unknown feature?
> This could trigger a high-priority alert (Superblock has unknown read-only
> compatible features (0x10) enabled) every time such a filesystem is mounted,
> even though the kernel explicitly parses this bit.
> Will this misleading warning persist until the end of the series where
> commit "xfs: enable ro-compat fs-verity flag" fully enables it?

Yes, this is intentional, so that kernel without full fsverity implementation
doesn't mount these filesytems.

> Does exposing XFS_DIFLAG2_VERITY to userspace as FS_XFLAG_VERITY require
> an update to xfs_flags2diflags2() to preserve it?
> When a user modifies file attributes (e.g., running chattr +S), VFS masks
> out the read-only FS_XFLAG_VERITY bit. The xfs_flags2diflags2 function then
> reconstructs i_diflags2 using a hardcoded preservation mask
> (XFS_DIFLAG2_REFLINK | XFS_DIFLAG2_BIGTIME | XFS_DIFLAG2_NREXT64).
> Because the verity bit is neither in this preservation mask nor present in
> the VFS-provided xflags, could it be permanently and erroneously cleared
> from the disk inode, allowing any user with attribute-setting privileges to
> silently disable fs-verity protection on disk?

This is valid, added a fix

> Does mapping XFS_DIFLAG2_VERITY to FS_XFLAG_VERITY and setting S_VERITY on
> the VFS inode here prematurely advertise to VFS and LSMs that the file is
> verified?
> Since the XFS read verification path is not yet implemented (it is added
> later in commit "xfs: handle fsverity I/O in write/read path"), if someone
> were to git bisect to this intermediate commit, could an attacker mount an
> fs-verity image read-only and serve malicious modifications to userspace
> without triggering validation errors?

This is fine, kernel don't know about fsverity feature yet. Alert about unknown
feature bit from above will pop-up.

> Does the mask that clears inode->i_flags later in this function need to be
> updated to include S_VERITY?
> Later in xfs_diflags_to_iflags(), flags are synchronized to the VFS inode:
>     inode->i_flags &= ~(S_IMMUTABLE | S_APPEND | S_SYNC | S_NOATIME);
> If S_VERITY is omitted from this clearing mask, and the XFS inode loses the
> verity flag (e.g., due to the xfs_flags2diflags2 issue), S_VERITY is never
> cleared from the VFS inode.
> Could this create a desynchronization where LSMs like IPE continue to trust
> the file's contents based on the stale VFS state, while XFS skips data
> verification on read because the underlying disk flag is missing?

This is valid, fixed

------------------------------------------------------------------------
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=12

> Does this change completely prevent DAX and verity from coexisting?
> While this prevents the in-memory DAX mapping from being enabled, could a
> user still set the persistent DAX flag (FS_XFLAG_DAX) via FS_IOC_FSSETXATTR
> on an inode that already has fs-verity enabled?
> It appears xfs_ioctl_setattr_xflags() might need a validation check to
> prevent creating a contradictory on-disk state where both XFS_DIFLAG2_DAX
> and XFS_DIFLAG2_VERITY are persistently set.

The file attribute fix applied. This check is not executed during setting file
attributes, only on inode creation. This will prevent fsverity inode to have DAX
with -o dax mount.

> This is a pre-existing issue, but does xfs_flags2diflags2() silently drop
> the XFS_DIFLAG2_VERITY flag?
> When modifying extended attributes via FS_IOC_FSSETXATTR, XFS recalculates
> the on-disk flags. Since xfs_flags2diflags2() only preserves a hardcoded
> whitelist of flags (REFLINK, BIGTIME, NREXT64) from ip->i_diflags2, it
> appears to ignore XFS_DIFLAG2_VERITY.
> Does this mean a valid xflags modification will permanently clear the
> verity flag from the on-disk inode?

This is true, addressed with a fix above.

------------------------------------------------------------------------
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=13

> Can a Time-of-Check to Time-of-Use (TOCTOU) race occur here?
> fsverity_active(inode) is checked without holding any locks. If a
> concurrent thread successfully enables fs-verity via FS_IOC_ENABLE_VERITY
> immediately after this lockless check evaluates to false, the IOCB_DIRECT
> flag will not be cleared.
> The reader thread then proceeds to xfs_file_dio_read() and blocks waiting
> for XFS_IOLOCK_SHARED. The thread enabling verity holds XFS_IOLOCK_EXCL
> while building the Merkle tree and setting S_VERITY. Once it completes
> and releases the lock, the file is fully verity-enabled.
> However, the blocked reader thread then acquires XFS_IOLOCK_SHARED and
> blindly passes the IOCB_DIRECT flag to iomap_dio_rw(), which does not
> re-check S_VERITY. Does this allow an attacker or a racing application to
> read unverified data from a file that has officially been sealed by
> fs-verity?

This is valid, addressed with a fix

> Does this change leave the statx STATX_DIOALIGN interface improperly
> advertising Direct I/O support for fs-verity files?
> This patch forces fs-verity files to fall back from Direct I/O to buffered
> I/O by clearing the IOCB_DIRECT flag. However, xfs_report_dioalign() appears
> to unconditionally populate stat->dio_mem_align and stat->dio_offset_align
> with non-zero values derived from the block device.
> Since Direct I/O is bypassed for these files, should these alignment fields
> be reported as 0 to avoid misleading applications into issuing DIO requests
> that silently degrade to buffered I/O performance?

This is valid, addressed with a fix to return statx->dio_*_align fields filled
with 0, meaning not supported

------------------------------------------------------------------------
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=14

> Does this code silently clear the IOMAP_F_FSVERITY flag if it races with
> another thread?
> Earlier in xfs_bmapi_convert_one_delalloc(), if we race with another thread
> converting the extent:
>     if (!isnullstartblock(bma.got.br_startblock)) {
>         xfs_bmbt_to_iomap(ip, iomap, &bma.got, 0, flags, ...);
>         ...
>         goto out_trans_cancel;
>     }
> The flags variable will not have IOMAP_F_FSVERITY set.
> Because xfs_bmbt_to_iomap() unconditionally assigns iomap->flags = iomap_flags,
> this will overwrite the iomap's flags and erase IOMAP_F_FSVERITY if it was
> previously set by xfs_map_blocks(). This could cause subsequent fs-verity
> metadata folios mapped by this iomap to lack the flag, leading to them being
> zeroed and skipped since they reside beyond EOF.

This probably can not happen in the current implementation as the only thread
converting these extents is writeback on post EOF region, called from the
fsverity_operations->end_enable_verity(). As these extesnts/folios reside past
EOF they should be skipped by normal writeback. But let's set the flag anyway so
there's no issue if anything changes.

> Could this drop fs-verity metadata for zoned inodes?
> In xfs_vm_writepages(), the zoned inode branch uses a separate
> xfs_zoned_writepage_ctx structure and calls iomap_writepages() without setting
> the IOMAP_F_FSVERITY flag on its iomap:
>     if (xfs_is_zoned_inode(ip)) {
>         struct xfs_zoned_writepage_ctx xc = { ... };
>         error = iomap_writepages(&xc.ctx);
>         ...
> Without the flag, folios past EOF might be zeroed or skipped by the core iomap
> writeback code, potentially losing the Merkle tree metadata.

This is valid, moved to the helper for both cases

> Can fs-verity metadata be dropped when using extent size hints or zoned inodes?
> Earlier in xfs_buffered_write_iomap_begin(), the code delegates to other
> functions and returns early:
>     if (xfs_is_zoned_inode(ip))
>         return xfs_zoned_buffered_write_iomap_begin(...);
>     if (xfs_get_extsz_hint(ip))
>         return xfs_direct_write_iomap_begin(...);
> Because these functions do not check for XFS_VERITY_CONSTRUCTION or set
> IOMAP_F_FSVERITY, the iomap will lack the flag. Does this cause writeback of
> folios past EOF to skip the Merkle tree metadata for these inodes?

This is valid, moved up with a few other fixes in write path.

------------------------------------------------------------------------
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=15

> Can merged ioends bypass cryptographic validation here?
> In xfs_end_io(), contiguous ioends are merged via iomap_ioend_try_merge(),
> which chains subsequent ioends into the head ioend's io_list:
> 	list_del_init(&ioend->io_list);
> 	iomap_ioend_try_merge(ioend, &tmp);
> 	if (bio_op(&ioend->io_bio) == REQ_OP_READ)
> 		xfs_end_ioend_read(ioend);
> xfs_end_ioend_read() is then called only on the head ioend and
> unconditionally verifies only that single bio (&ioend->io_bio). It doesn't
> appear to iterate over the merged ioends present in ioend->io_list.
> Afterward, iomap_finish_ioends() processes the entire ioend->io_list chain,
> marking all folios as Uptodate and unlocking them. If a read is split into
> multiple bios that complete in the same batch, does this allow the trailing
> merged bios to bypass fsverity verification completely?

I don't think this is the case as iomap_ioend_try_merge() doesn't merge READ
ioends.

> Does sharing a single work_struct (i_ioend_work) and list (i_ioend_list)
> for both fsverity data reads and other I/O completions cause workqueue
> multiplexing or deadlocks?
> If list_empty(&ip->i_ioend_list) is true, the work is queued to either the
> fsverity workqueue or m_unwritten_workqueue based on the first ioend that
> completes. Subsequent completions simply append to ip->i_ioend_list. Could
> this result in writes being processed by the fsverity workqueue, or data
> reads being processed by the unwritten workqueue?
> Additionally, if bdev_has_integrity_csum() is true, all reads (including
> fsverity metadata) use xfs_iomap_read_ops, routing their completions to
> xfs_end_bio() and this same list.
> Because iomap_sort_ioends() sorts data reads before metadata reads, could
> this create a single-thread deadlock? When xfs_end_io() processes the data
> read first, it calls fsverity_verify_bio(). If the metadata is not yet
> Uptodate, fsverity_verify_bio() blocks waiting for the metadata folio to
> become unlocked. However, the metadata ioend that unlocks the folio might
> be queued later in the same tmp list being processed by the same worker
> thread.

This is not correct. No writes happens on the file at this point. However, what
can happen is if block device has integrity checksum and file is fsverity
enabled, then, integrity checksum verification could get onto fsverity workqueue
instead of m_unwritten_workqueue, and vice versa. I don't think it's critical as
it doesn't create deadlocks.

------------------------------------------------------------------------
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=16

> The VFS fs-verity core calls this function without holding XFS-specific locks.
> Does xfs_bmap_last_extent() (which calls xfs_iread_extents()) require the
> XFS_ILOCK_SHARED lock to prevent lockdep assertions and data races when
> accessing the extent tree?

Added a lock/unlock

> Should this bounds check be <= instead of <?
> If a file has exactly 0 bytes of metadata, last_block_offset could be exactly
> equal to the metadata offset. In that case, this check passes, and the
> subsequent subtraction of 4 bytes for desc_size_pos will read into user data
> instead of metadata.

yes, could be the case for zero-length file, fixed

> Does this leave stale unevictable folios in the page cache?
> Since xfs_itruncate_extents() removes the blocks but truncate_inode_pages()
> or truncate_pagecache() is not called here, the dirty pages might remain in
> memory past EOF and could be exposed as data if the file is later extended.

Yes this leaves stale folios, added truncate_inode_pages() to i_size.

> Is it safe to drop the inode lock before canceling the transaction?
> xfs_trans_cancel() processes log item release callbacks, and
> xfs_inode_item_release() requires XFS_ILOCK_EXCL to be held if the item
> was dirtied. This ordering might trigger lockdep assertions or create a
> use-after-free race.

Fixed.

> If xfs_qm_dqattach() or xfs_fsverity_delete_metadata() fails, the function
> returns the error but does not clear the XFS_VERITY_CONSTRUCTION flag.
> Since the VFS won't call end_enable_verity on failure, does this leak the
> flag and permanently block future attempts to enable fs-verity on this inode?

Added clearing.

> Can xfs_bmap_punch_delalloc_range() dereference a NULL cowfp here?
> xfs_is_cow_inode() only checks the reflink inode flag or the global always_cow
> state, but it does not guarantee that the in-memory COW fork tracking structure
> (ip->i_cowfp) has been allocated. If there are no active COW operations, this
> might cause a NULL pointer dereference.

yes, fixed

------------------------------------------------------------------------
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=17

> Does this error path leak the XFS_ILOCK_EXCL inode lock?
> Since xfs_trans_ijoin() is called with a lock_flags of 0, the transaction
> framework will not automatically unlock the inode on commit. If
> xfs_trans_commit() returns an error, returning directly here will bypass
> the xfs_iunlock(ip, XFS_ILOCK_EXCL) call at the end of the loop.
> While a commit failure on a dirty transaction triggers a filesystem shutdown,
> the leaked lock can cause subsequent cleanup operations, such as inode
> eviction during unmount, to hang indefinitely.

Fixed, added XFS_ILOCK_EXCL to the ijoin.

------------------------------------------------------------------------
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=20

> Does this code risk marking healthy fs-verity files as incomplete?
> The setup function for the xattr scrubber, xchk_setup_xattr(), delegates
> to xchk_setup_inode_contents(), which does not seem to call
> xchk_inode_setup_verity().
> If fsverity_ensure_verity_info() is never called during the xattr scrub
> setup to instantiate the in-memory verity info, fsverity_get_info() might
> legitimately return NULL for valid verity files that haven't been accessed
> yet, causing xchk_inode_verity_broken() to incorrectly return true.

This is residue from the xattr implementation, removed

> This is a pre-existing issue, but could this call to
> fsverity_ensure_verity_info() lead to an assertion failure or data race?
> When fsverity_ensure_verity_info() invokes xfs_fsverity_get_descriptor(),
> it calls xfs_bmap_last_extent() without holding the XFS_ILOCK.
> Because xfs_bmap_last_extent() unconditionally calls xfs_iread_extents(),
> if extents are not yet loaded into memory, this can trigger the
> xfs_assert_ilocked(ip, XFS_ILOCK_EXCL) assertion or result in a data race.

Lock added.

> Will intercepting these validation errors cause xfs_scrub to permanently
> strip fs-verity protection from valid files?
> The fsverity core returns errors like -EINVAL for valid files if the kernel
> is missing specific crypto modules or if there is an unsupported
> configuration.
> If we treat these as corruption, xchk_inode_verity_broken() will return true,
> which then triggers xrep_inode_verity() to permanently strip the
> XFS_DIFLAG2_VERITY and S_VERITY flags from the inode, allowing the file to
> be modified without verification.

This is not true, fsverity select necessary hash algorithms and unsupported
configuration is valid reason to strip corrupted descriptor.

------------------------------------------------------------------------
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org?part=21

> This isn't a bug introduced by this patch, but does xfs_fsverity_get_descriptor()
> call xfs_bmap_last_extent() earlier without holding the XFS inode lock
> (XFS_ILOCK_SHARED)?
> If the file's extents have not been loaded into memory,
> xfs_bmap_last_extent() will call xfs_iread_extents(), which asserts that the
> caller holds at least XFS_ILOCK_SHARED.
> Without this lock, could this lead to an assertion failure on debug kernels,
> or a data race causing memory corruption when multiple threads concurrently
> attempt to load the extent tree?

Lock added.

> This is a pre-existing issue, but is there a missing bounds check to ensure
> desc_pos does not overlap with the file's data area?
> While there is a check above to ensure desc_size <= desc_size_pos, there
> doesn't appear to be validation that desc_pos >=
> xfs_fsverity_metadata_offset(ip).
> If a corrupted on-disk desc_size causes desc_pos to underflow the metadata
> boundary, will fsverity_pagecache_read() silently read from the regular file
> data area instead of returning a corruption error?

Looks real for desc_size being more than blocksize. Added a check.

------------------------------------------------------------------------
 1:  0e3e25b9860e =  1:  ecbf883c933d fsverity: report validation errors through fserror to fsnotify
 2:  9b43d2de7e80 =  2:  e4e5f5c4683e fsverity: expose ensure_fsverity_info()
 3:  6ea25205e221 <  -:  ------------ ovl: use core fsverity ensure info interface
 4:  c4aa84d61400 <  -:  ------------ fsverity: generate and store zero-block hash
 5:  dcfa45d327f7 =  3:  5670f4a05e4c fsverity: pass digest size and hash of the all-zeroes block to ->write
 6:  f6b1188a881b =  4:  b3a09eb38d51 fsverity: hoist pagecache_read from f2fs/ext4 to fsverity
 7:  ab412480398b <  -:  ------------ iomap: introduce IOMAP_F_FSVERITY and teach writeback to handle fsverity
 8:  72088ee192b0 <  -:  ------------ iomap: teach iomap to read files with fsverity
 9:  537d02c9d883 <  -:  ------------ iomap: introduce iomap_fsverity_write() for writing fsverity metadata
 -:  ------------ >  5:  091d197d8917 fsverity: improve flushing performance of fsverity_fill_zerohash
 -:  ------------ >  6:  3a46d6bd9824 fsverity: don't allow setting DAX file attribute on fsverity files
10:  e455ab8d9daa !  7:  5aae11e26d11 xfs: introduce fsverity on-disk changes
    @@ fs/xfs/libxfs/xfs_inode_buf.c: xfs_dinode_verify(
      		if (be32_to_cpu(dip->di_used_blocks) > mp->m_sb.sb_rgextents)

      ## fs/xfs/libxfs/xfs_inode_util.c ##
    +@@ fs/xfs/libxfs/xfs_inode_util.c: 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;
     @@ fs/xfs/libxfs/xfs_inode_util.c: xfs_ip2xflags(
      			flags |= FS_XFLAG_DAX;
      		if (ip->i_diflags2 & XFS_DIFLAG2_COWEXTSIZE)
    @@ fs/xfs/xfs_iops.c: xfs_diflags_to_iflags(

      	/*
      	 * 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;
    + }
    +

      ## fs/xfs/xfs_mount.h ##
     @@ fs/xfs/xfs_mount.h: typedef struct xfs_mount {
11:  03334565a9df =  8:  ff6d20a38acd xfs: initialize fs-verity on file open
12:  3e7c9e0021aa =  9:  2fbbe0b1239c xfs: don't allow to enable DAX on fs-verity sealed inode
13:  f689ba9f910b <  -:  ------------ xfs: disable direct read path for fs-verity files
 -:  ------------ > 10:  33f76d31f5db xfs: don't report dio_mem_align and dio_offset_align for fsverity files
 -:  ------------ > 11:  29b5121f6aad xfs: disable direct read path for fs-verity files
14:  ddab7d10f305 ! 12:  43198dc513b6 xfs: handle fsverity I/O in write/read path
    @@ Commit message
         Introduce a new inode flag meaning that merkle tree is being build on
         the inode.

    -    Reviewed-by: Darrick J. Wong <djwong@kernel.org>
    -    Reviewed-by: Christoph Hellwig <hch@lst.de>
         Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>

      ## fs/xfs/Makefile ##
    @@ fs/xfs/libxfs/xfs_bmap.c

      struct kmem_cache		*xfs_bmap_intent_cache;

    +@@ fs/xfs/libxfs/xfs_bmap.c: xfs_bmapi_convert_one_delalloc(
    + 	 * the extent.  Just return the real extent at this offset.
    + 	 */
    + 	if (!isnullstartblock(bma.got.br_startblock)) {
    ++		if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) &&
    ++		    XFS_FSB_TO_B(mp, bma.got.br_startoff) >=
    ++			    xfs_fsverity_metadata_offset(ip))
    ++			flags |= IOMAP_F_FSVERITY;
    + 		xfs_bmbt_to_iomap(ip, iomap, &bma.got, 0, flags,
    + 				xfs_iomap_inode_sequence(ip, flags));
    + 		if (seq)
     @@ fs/xfs/libxfs/xfs_bmap.c: 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);
    @@ fs/xfs/xfs_aops.c: xfs_map_blocks(
      	trace_xfs_map_blocks_found(ip, offset, count, whichfork, &imap);
      	return 0;
      allocate_blocks:
    +@@ fs/xfs/xfs_aops.c: 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.
    +@@ fs/xfs/xfs_aops.c: 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);
    +@@ fs/xfs/xfs_aops.c: xfs_zoned_map_blocks(
    + 	xfs_iunlock(ip, XFS_ILOCK_EXCL);
    +
    + 	wpc->iomap.type = IOMAP_MAPPED;
    +-	wpc->iomap.flags = IOMAP_F_DIRTY;
    ++	wpc->iomap.flags = iomap_flags | IOMAP_F_DIRTY;
    + 	wpc->iomap.bdev = mp->m_rtdev_targp->bt_bdev;
    + 	wpc->iomap.offset = offset;
    + 	wpc->iomap.length = XFS_FSB_TO_B(mp, count_fsb);
    +@@ fs/xfs/xfs_aops.c: 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,
    +@@ fs/xfs/xfs_aops.c: 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;
     @@ fs/xfs/xfs_aops.c: xfs_vm_writepages(
      			},
      		};

    -+		/*
    -+		 * 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))
    -+			wpc.ctx.iomap.flags |= IOMAP_F_FSVERITY;
    -+
    - 		return iomap_writepages(&wpc.ctx);
    +-		return iomap_writepages(&wpc.ctx);
    ++		return xfs_iomap_writepages(ip, &wpc.ctx);
      	}
      }
    +

      ## fs/xfs/xfs_fsverity.c (new) ##
     @@
    @@ fs/xfs/xfs_fsverity.c (new)
     +#include "xfs_trans_resv.h"
     +#include "xfs_mount.h"
     +#include "xfs_fsverity.h"
    -+#include "xfs_fsverity.h"
     +#include <linux/fsverity.h>
     +#include <linux/iomap.h>
     +
    @@ fs/xfs/xfs_iomap.c

      #define XFS_ALLOC_ALIGN(mp, off) \
      	(((off) >> mp->m_allocsize_log) << mp->m_allocsize_log)
    +@@ fs/xfs/xfs_iomap.c: 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);
    +@@ fs/xfs/xfs_iomap.c: 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;
    +@@ fs/xfs/xfs_iomap.c: xfs_zoned_buffered_write_iomap_begin(
    + 	struct xfs_iext_cursor	icur;
    + 	int			error = 0;
    +
    ++	iomap_flags = iomap_flags | IOMAP_F_SHARED;
    ++
    + 	ASSERT(!xfs_get_extsz_hint(ip));
    + 	ASSERT(!(flags & IOMAP_UNSHARE));
    + 	ASSERT(ac);
     @@ fs/xfs/xfs_iomap.c: xfs_buffered_write_iomap_begin(
    - 		return xfs_direct_write_iomap_begin(inode, offset, count,
    - 				flags, iomap, srcmap);
    + 	if (xfs_is_shutdown(mp))
    + 		return -EIO;

     +	if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
     +		iomap_flags |= IOMAP_F_FSVERITY;
     +
    - 	error = xfs_qm_dqattach(ip);
    - 	if (error)
    - 		return error;
    + 	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))
     @@ fs/xfs/xfs_iomap.c: xfs_read_iomap_begin(
      	bool			shared = false;
      	unsigned int		lockmode = XFS_ILOCK_SHARED;
15:  c37286a92d9c ! 13:  9c8fcebdb6c7 xfs: use read ioend for fsverity data verification
    @@ fs/xfs/xfs_aops.c: xfs_bio_submit_read(
     +	ioend = iomap_init_ioend(iter->inode, bio, ctx->read_ctx_file_offset,
     +			0);
     +	ioend->io_vi = ctx->vi;
    -+
    - 	bio->bi_end_io = xfs_end_bio;
    - 	submit_bio(bio);
    + 	iomap_bio_submit_read_endio(iter, ctx, xfs_end_bio);
      }
    +
     @@ fs/xfs/xfs_aops.c: static const struct iomap_read_ops xfs_iomap_read_ops = {

      static inline const struct iomap_read_ops *
16:  4b2a9d2f58ba ! 14:  cab97a909ad0 xfs: add fs-verity support
    @@ Commit message
         Merkle tree block. The size of the descriptor is stored at the end of
         the last descriptor block (descriptor can be multiple blocks).

    -    Reviewed-by: Christoph Hellwig <hch@lst.de>
    -    Reviewed-by: Darrick J. Wong <djwong@kernel.org>
         Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>

      ## fs/xfs/xfs_bmap_util.c ##
    @@ fs/xfs/xfs_fsverity.c
      #include "xfs_shared.h"
      #include "xfs_trans_resv.h"
      #include "xfs_mount.h"
    - #include "xfs_fsverity.h"
     +#include "xfs_da_format.h"
     +#include "xfs_da_btree.h"
     +#include "xfs_inode.h"
     +#include "xfs_log_format.h"
     +#include "xfs_bmap_util.h"
    -+#include "xfs_log_format.h"
     +#include "xfs_trans.h"
     +#include "xfs_trace.h"
     +#include "xfs_quota.h"
    @@ fs/xfs/xfs_fsverity.c: xfs_fsverity_is_file_data(
     +	xfs_fileoff_t		last_block_offset;
     +
     +	ASSERT(inode->i_flags & S_VERITY);
    ++	xfs_ilock(ip, XFS_ILOCK_SHARED);
     +	error = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, &rec, &is_empty);
    ++	xfs_iunlock(ip, XFS_ILOCK_SHARED);
     +	if (error)
     +		return error;
     +
    @@ fs/xfs/xfs_fsverity.c: xfs_fsverity_is_file_data(
     +
     +	last_block_offset =
     +		XFS_FSB_TO_B(mp, rec.br_startoff + rec.br_blockcount);
    -+	if (last_block_offset < xfs_fsverity_metadata_offset(ip))
    ++	if (last_block_offset <= xfs_fsverity_metadata_offset(ip))
     +		return -ENODATA;
     +
     +	desc_size_pos = last_block_offset - sizeof(__be32);
    @@ fs/xfs/xfs_fsverity.c: xfs_fsverity_is_file_data(
     +		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;
    @@ fs/xfs/xfs_fsverity.c: xfs_fsverity_is_file_data(
     +		goto err_cancel;
     +
     +	error = xfs_trans_commit(tp);
    ++	if (error)
    ++		goto out_unlock;
    ++out_unlock:
     +	xfs_iunlock(ip, XFS_ILOCK_EXCL);
    -+
     +	return error;
     +
     +err_cancel:
    -+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
     +	xfs_trans_cancel(tp);
    ++	xfs_iunlock(ip, XFS_ILOCK_EXCL);
     +	return error;
     +}
     +
    @@ fs/xfs/xfs_fsverity.c: xfs_fsverity_is_file_data(
     +
     +	xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);
     +
    -+	if (IS_DAX(inode))
    ++	if (IS_DAX(inode) || ip->i_diflags2 & XFS_DIFLAG2_DAX)
     +		return -EINVAL;
     +
     +	if (inode->i_size > XFS_FSVERITY_LARGEST_FILE)
    @@ fs/xfs/xfs_fsverity.c: xfs_fsverity_is_file_data(
     +
     +	error = xfs_qm_dqattach(ip);
     +	if (error)
    -+		return error;
    ++		goto out_clear;
     +
    -+	return xfs_fsverity_delete_metadata(ip);
    ++	error = xfs_fsverity_delete_metadata(ip);
    ++	if (error)
    ++		goto out_clear;
    ++	return error;
    ++out_clear:
    ++	xfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);
    ++	return error;
     +}
     +
     +/*
    @@ fs/xfs/xfs_fsverity.c: xfs_fsverity_is_file_data(
     +	 * Proactively drop any delayed allocations in COW fork, the fsverity
     +	 * files are read-only
     +	 */
    -+	if (xfs_is_cow_inode(ip))
    ++	if (xfs_is_cow_inode(ip)) {
    ++		if (!ip->i_cowfp)
    ++			xfs_ifork_init_cow(ip);
     +		xfs_bmap_punch_delalloc_range(ip, XFS_COW_FORK, 0, LLONG_MAX,
     +				NULL);
    ++	}
     +
     +	/*
     +	 * Set fsverity inode flag
    @@ fs/xfs/xfs_fsverity.c: xfs_fsverity_is_file_data(
     +		if (error2)
     +			xfs_alert(ip->i_mount,
     +"ino 0x%llx failed to clean up new fsverity metadata, err %d",
    -+					ip->i_ino, error2);
    ++					I_INO(ip), error2);
     +	}
     +
     +	xfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);
    @@ fs/xfs/xfs_fsverity.h

      ## fs/xfs/xfs_message.c ##
     @@ fs/xfs/xfs_message.c: xfs_warn_experimental(
    - 			.opstate	= XFS_OPSTATE_WARNED_ZONED,
    - 			.name		= "zoned RT device",
    + 			.opstate	= XFS_OPSTATE_WARNED_LARP,
    + 			.name		= "logged extended attributes",
      		},
     +		[XFS_EXPERIMENTAL_FSVERITY] = {
     +			.opstate	= XFS_OPSTATE_WARNED_FSVERITY,
    @@ fs/xfs/xfs_message.c: xfs_warn_experimental(
      	BUILD_BUG_ON(ARRAY_SIZE(features) != XFS_EXPERIMENTAL_MAX);

      ## fs/xfs/xfs_message.h ##
    -@@ fs/xfs/xfs_message.h: enum xfs_experimental_feat {
    +@@ fs/xfs/xfs_message.h: void xfs_buf_alert_ratelimited(struct xfs_buf *bp, const char *rlmsg,
    + enum xfs_experimental_feat {
      	XFS_EXPERIMENTAL_SHRINK,
      	XFS_EXPERIMENTAL_LARP,
    - 	XFS_EXPERIMENTAL_ZONED,
     +	XFS_EXPERIMENTAL_FSVERITY,

      	XFS_EXPERIMENTAL_MAX,
    @@ fs/xfs/xfs_message.h: enum xfs_experimental_feat {

      ## fs/xfs/xfs_mount.h ##
     @@ fs/xfs/xfs_mount.h: __XFS_HAS_FEAT(nouuid, NOUUID)
    - #define XFS_OPSTATE_WARNED_ZONED	19
    + #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 */
17:  5e3a2173dbdb ! 15:  b15dd18011a3 xfs: remove unwritten extents after preallocations in fsverity metadata
    @@ Commit message
         that region and remove any unwritten ones. These would be left overs in
         the holes in the merkle tree and past fsverity descriptor.

    -    Reviewed-by: Christoph Hellwig <hch@lst.de>
    -    Reviewed-by: Darrick J. Wong <djwong@kernel.org>
         Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>

      ## fs/xfs/xfs_fsverity.c ##
    @@ fs/xfs/xfs_fsverity.c: xfs_fsverity_delete_metadata(
     +			goto out_cancel;
     +
     +		if (imap.br_state == XFS_EXT_UNWRITTEN) {
    -+			xfs_trans_ijoin(tp, ip, 0);
    ++			xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
     +
     +			error = xfs_bunmapi(tp, ip, imap.br_startoff,
     +					imap.br_blockcount, 0, 1, &done);
    @@ fs/xfs/xfs_fsverity.c: xfs_fsverity_delete_metadata(
     +				return error;
     +		} else {
     +			xfs_trans_cancel(tp);
    ++			xfs_iunlock(ip, XFS_ILOCK_EXCL);
     +		}
    -+		xfs_iunlock(ip, XFS_ILOCK_EXCL);
     +
     +		offset_fsb = imap.br_startoff + imap.br_blockcount;
     +	}
18:  60f8807a7c21 = 16:  31e0b472282d xfs: add fs-verity ioctls
19:  2d0ec55b6936 = 17:  db2bdef8f620 xfs: advertise fs-verity being available on filesystem
20:  23a6c5e73f14 ! 18:  d80a609aace6 xfs: check and repair the verity inode flag state
    @@ Commit message
         Reviewed-by: Christoph Hellwig <hch@lst.de>
         Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>

    - ## fs/xfs/scrub/attr.c ##
    -@@ fs/xfs/scrub/attr.c: xchk_xattr(
    - 	if (!xfs_inode_hasattr(sc->ip))
    - 		return -ENOENT;
    -
    -+	/*
    -+	 * If this is a verity file that won't activate, we cannot check the
    -+	 * merkle tree geometry.
    -+	 */
    -+	if (xchk_inode_verity_broken(sc->ip))
    -+		xchk_set_incomplete(sc);
    -+
    - 	/* Allocate memory for xattr checking. */
    - 	error = xchk_setup_xattr_buf(sc, 0);
    - 	if (error == -ENOMEM)
    -
      ## fs/xfs/scrub/common.c ##
     @@
      #include "scrub/health.h"
    @@ fs/xfs/scrub/inode.c: xchk_prepare_iscrub(
      		return error;
     @@ fs/xfs/scrub/inode.c: xchk_inode(
      	if (S_ISREG(VFS_I(sc->ip)->i_mode))
    - 		xchk_inode_check_reflink_iflag(sc, sc->ip->i_ino);
    + 		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, sc->ip->i_ino, &di);
    + 	xchk_inode_xref(sc, I_INO(sc->ip), &di);

      ## fs/xfs/scrub/inode_repair.c ##
     @@ fs/xfs/scrub/inode_repair.c: xrep_dinode_flags(
21:  e69595560ea2 ! 19:  17f9793e4e68 xfs: introduce health state for corrupted fsverity metadata
    @@ fs/xfs/xfs_fsverity.c: xfs_fsverity_get_descriptor(
     +	}

      	desc_pos = round_down(desc_size_pos - desc_size, blocksize);
    - 	error = fsverity_pagecache_read(inode, buf, desc_size, desc_pos);
    + 	if (desc_pos < xfs_fsverity_metadata_offset(ip)) {

      ## fs/xfs/xfs_health.c ##
     @@ fs/xfs/xfs_health.c: static const struct ioctl_sick_map ino_map[] = {
22:  dae61fa276b0 = 20:  c5a3421e9c3b xfs: enable ro-compat fs-verity flag

Andrey Albershteyn (18):
  fsverity: report validation errors through fserror to fsnotify
  fsverity: expose ensure_fsverity_info()
  fsverity: pass digest size and hash of the all-zeroes block to ->write
  fsverity: hoist pagecache_read from f2fs/ext4 to fsverity
  fsverity: improve flushing performance of fsverity_fill_zerohash
  fsverity: don't allow setting DAX file attribute on fsverity files
  xfs: introduce fsverity on-disk changes
  xfs: initialize fs-verity on file open
  xfs: don't allow to enable DAX on fs-verity sealed inode
  xfs: don't report dio_mem_align and dio_offset_align for fsverity
    files
  xfs: disable direct read path for fs-verity files
  xfs: handle fsverity I/O in write/read path
  xfs: use read ioend for fsverity data verification
  xfs: add fs-verity support
  xfs: remove unwritten extents after preallocations in fsverity
    metadata
  xfs: add fs-verity ioctls
  xfs: introduce health state for corrupted fsverity metadata
  xfs: enable ro-compat fs-verity flag

Darrick J. Wong (2):
  xfs: advertise fs-verity being available on filesystem
  xfs: check and repair the verity inode flag state

 fs/btrfs/verity.c              |   6 +-
 fs/ext4/verity.c               |  36 +--
 fs/f2fs/verity.c               |  34 +--
 fs/file_attr.c                 |   5 +
 fs/verity/enable.c             |   4 +-
 fs/verity/open.c               |  22 +-
 fs/verity/pagecache.c          |  57 +++-
 fs/verity/verify.c             |   4 +
 fs/xfs/Makefile                |   1 +
 fs/xfs/libxfs/xfs_bmap.c       |  11 +
 fs/xfs/libxfs/xfs_format.h     |  35 ++-
 fs/xfs/libxfs/xfs_fs.h         |   2 +
 fs/xfs/libxfs/xfs_health.h     |   4 +-
 fs/xfs/libxfs/xfs_inode_buf.c  |   8 +
 fs/xfs/libxfs/xfs_inode_util.c |   5 +-
 fs/xfs/libxfs/xfs_sb.c         |   4 +
 fs/xfs/scrub/common.c          |  53 ++++
 fs/xfs/scrub/common.h          |   2 +
 fs/xfs/scrub/inode.c           |   7 +
 fs/xfs/scrub/inode_repair.c    |  36 +++
 fs/xfs/xfs_aops.c              |  81 +++++-
 fs/xfs/xfs_bmap_util.c         |   8 +
 fs/xfs/xfs_file.c              |  67 +++--
 fs/xfs/xfs_fsverity.c          | 473 +++++++++++++++++++++++++++++++++
 fs/xfs/xfs_fsverity.h          |  28 ++
 fs/xfs/xfs_health.c            |   1 +
 fs/xfs/xfs_inode.h             |   6 +
 fs/xfs/xfs_ioctl.c             |  14 +
 fs/xfs/xfs_iomap.c             |  23 +-
 fs/xfs/xfs_iops.c              |  12 +-
 fs/xfs/xfs_message.c           |   4 +
 fs/xfs/xfs_message.h           |   1 +
 fs/xfs/xfs_mount.h             |   4 +
 fs/xfs/xfs_super.c             |   7 +
 include/linux/fsverity.h       |  10 +-
 35 files changed, 962 insertions(+), 113 deletions(-)
 create mode 100644 fs/xfs/xfs_fsverity.c
 create mode 100644 fs/xfs/xfs_fsverity.h

-- 
2.54.0


^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v11 01/20] fsverity: report validation errors through fserror to fsnotify
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 02/20] fsverity: expose ensure_fsverity_info() Andrey Albershteyn
                   ` (19 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

Reported verification errors to fsnotify through recently added fserror
interface.

Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Acked-by: Eric Biggers <ebiggers@kernel.org>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/verity/verify.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/fs/verity/verify.c b/fs/verity/verify.c
index 4004a1d42875..db8c350234bb 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);
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 02/20] fsverity: expose ensure_fsverity_info()
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 01/20] fsverity: report validation errors through fserror to fsnotify Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 03/20] fsverity: pass digest size and hash of the all-zeroes block to ->write Andrey Albershteyn
                   ` (18 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

This function will be used by XFS's scrub to force fsverity activation,
therefore, to read fsverity context.

Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Acked-by: Eric Biggers <ebiggers@kernel.org>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
---
 fs/verity/open.c         | 22 ++++++++++++++++++++--
 include/linux/fsverity.h |  2 ++
 2 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/fs/verity/open.c b/fs/verity/open.c
index d0c56a7faa3b..875e8850ccba 100644
--- a/fs/verity/open.c
+++ b/fs/verity/open.c
@@ -347,7 +347,24 @@ int fsverity_get_descriptor(struct inode *inode,
 	return 0;
 }
 
-static int ensure_verity_info(struct inode *inode)
+/**
+ * fsverity_ensure_verity_info() - cache verity info if it's not already cached
+ * @inode: the inode for which verity info should be cached
+ *
+ * Ensure this inode has verity info attached to it, it's assumed the inode
+ * already has fsverity enabled. Read fsverity descriptor and creates verity
+ * based on that.
+ *
+ * This needs to be called at least once before any of the inode's data
+ * can be verified (and thus read at all) or the inode's fsverity digest
+ * retrieved.  fsverity_file_open() calls this already, which handles
+ * normal file accesses.  If a filesystem does any internal (i.e. not
+ * associated with a file descriptor) reads of the file's data or
+ * fsverity digest, it must call this explicitly before doing so.
+ *
+ * Return: 0 on success, -errno on failure
+ */
+int fsverity_ensure_verity_info(struct inode *inode)
 {
 	struct fsverity_info *vi = fsverity_get_info(inode), *found;
 	struct fsverity_descriptor *desc;
@@ -383,12 +400,13 @@ static int ensure_verity_info(struct inode *inode)
 	kfree(desc);
 	return err;
 }
+EXPORT_SYMBOL_GPL(fsverity_ensure_verity_info);
 
 int __fsverity_file_open(struct inode *inode, struct file *filp)
 {
 	if (filp->f_mode & FMODE_WRITE)
 		return -EPERM;
-	return ensure_verity_info(inode);
+	return fsverity_ensure_verity_info(inode);
 }
 EXPORT_SYMBOL_GPL(__fsverity_file_open);
 
diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h
index 6c467ded9751..3c3250f6f272 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -317,6 +317,8 @@ 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);
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 03/20] fsverity: pass digest size and hash of the all-zeroes block to ->write
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 01/20] fsverity: report validation errors through fserror to fsnotify Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 02/20] fsverity: expose ensure_fsverity_info() Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 04/20] fsverity: hoist pagecache_read from f2fs/ext4 to fsverity Andrey Albershteyn
                   ` (17 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

Let filesystem iterate over hashes in the block and check if these are
hashes of zeroed data blocks. XFS will use this to decide if it want to
store tree block full of these hashes.

Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
Acked-by: Eric Biggers <ebiggers@kernel.org>
---
 fs/btrfs/verity.c        | 6 +++++-
 fs/ext4/verity.c         | 4 +++-
 fs/f2fs/verity.c         | 4 +++-
 fs/verity/enable.c       | 4 +++-
 include/linux/fsverity.h | 6 +++++-
 5 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c
index 983365a73541..bd79e343ae23 100644
--- a/fs/btrfs/verity.c
+++ b/fs/btrfs/verity.c
@@ -773,11 +773,15 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode,
  * @buf:	Merkle tree block to write
  * @pos:	the position of the block in the Merkle tree (in bytes)
  * @size:	the Merkle tree block size (in bytes)
+ * @zero_digest:	the hash of the all-zeroes block
+ * @digest_size:	size of zero_digest, in bytes
  *
  * Returns 0 on success or negative error code on failure
  */
 static int btrfs_write_merkle_tree_block(struct file *file, const void *buf,
-					 u64 pos, unsigned int size)
+					 u64 pos, unsigned int size,
+					 const u8 *zero_digest,
+					 unsigned int digest_size)
 {
 	struct inode *inode = file_inode(file);
 	loff_t merkle_pos = merkle_file_pos(inode);
diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c
index ca61da53f313..347945ac23a4 100644
--- a/fs/ext4/verity.c
+++ b/fs/ext4/verity.c
@@ -374,7 +374,9 @@ static void ext4_readahead_merkle_tree(struct inode *inode, pgoff_t index,
 }
 
 static int ext4_write_merkle_tree_block(struct file *file, const void *buf,
-					u64 pos, unsigned int size)
+					u64 pos, unsigned int size,
+					const u8 *zero_digest,
+					unsigned int digest_size)
 {
 	pos += ext4_verity_metadata_pos(file_inode(file));
 
diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c
index 39f482515445..cc4158fc841d 100644
--- a/fs/f2fs/verity.c
+++ b/fs/f2fs/verity.c
@@ -272,7 +272,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/verity/enable.c b/fs/verity/enable.c
index 42dfed1ce0ce..ad4ff71d7dd9 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/include/linux/fsverity.h b/include/linux/fsverity.h
index 3c3250f6f272..9e7d946676b9 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
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 04/20] fsverity: hoist pagecache_read from f2fs/ext4 to fsverity
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (2 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 03/20] fsverity: pass digest size and hash of the all-zeroes block to ->write Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 05/20] fsverity: improve flushing performance of fsverity_fill_zerohash Andrey Albershteyn
                   ` (16 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

This is the same function to read from pageache. XFS will also need
this, so move this to core fsverity.

Note that f2fs and ext4 functions diverged a bit, as ext4 operated over
folios and f2fs operated over pages. The common one will operate over
folios.

Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Acked-by: Eric Biggers <ebiggers@kernel.org>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/ext4/verity.c         | 32 +++-----------------------------
 fs/f2fs/verity.c         | 30 +-----------------------------
 fs/verity/pagecache.c    | 33 +++++++++++++++++++++++++++++++++
 include/linux/fsverity.h |  2 ++
 4 files changed, 39 insertions(+), 58 deletions(-)

diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c
index 347945ac23a4..ac5c133f5529 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;
 	}
diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c
index cc4158fc841d..f3a4617656db 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;
 	}
diff --git a/fs/verity/pagecache.c b/fs/verity/pagecache.c
index 99f5f53eea98..9d82e6b74ba1 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/include/linux/fsverity.h b/include/linux/fsverity.h
index 9e7d946676b9..f9433332c274 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -328,5 +328,7 @@ 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 */
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 05/20] fsverity: improve flushing performance of fsverity_fill_zerohash
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (3 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 04/20] fsverity: hoist pagecache_read from f2fs/ext4 to fsverity Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 06/20] fsverity: don't allow setting DAX file attribute on fsverity files Andrey Albershteyn
                   ` (15 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

The current version calls flush_dcache_folio(), in memcpy_to_folio(), to
flush whole folio on every digest (which is 128 for 4k). Open code folio
mapping and flushing to copy all digests at once.

Reported-by: Eric Biggers <ebiggers@kernel.org>
Link: https://lore.kernel.org/linux-fsdevel/20260401222717.GH2466@quark/
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/verity/pagecache.c | 24 +++++++++++++++++++++---
 1 file changed, 21 insertions(+), 3 deletions(-)

diff --git a/fs/verity/pagecache.c b/fs/verity/pagecache.c
index 9d82e6b74ba1..270abd652e48 100644
--- a/fs/verity/pagecache.c
+++ b/fs/verity/pagecache.c
@@ -68,14 +68,32 @@ EXPORT_SYMBOL_GPL(generic_readahead_merkle_tree);
 void fsverity_fill_zerohash(struct folio *folio, size_t offset, size_t len,
 			      struct fsverity_info *vi)
 {
-	size_t off = offset;
+	void *vaddr;
+	void *to;
+	size_t chunk;
 
 	WARN_ON_ONCE(!IS_ALIGNED(offset, vi->tree_params.digest_size));
 	WARN_ON_ONCE(!IS_ALIGNED(len, vi->tree_params.digest_size));
+	WARN_ON_ONCE(offset + len > folio_size(folio));
 
-	for (; off < (offset + len); off += vi->tree_params.digest_size)
-		memcpy_to_folio(folio, off, vi->tree_params.zero_digest,
+	do {
+		vaddr = kmap_local_folio(folio, offset);
+		chunk = len;
+		to = vaddr;
+
+		if (folio_test_partial_kmap(folio) &&
+		    chunk > PAGE_SIZE - offset_in_page(offset))
+			chunk = PAGE_SIZE - offset_in_page(offset);
+		for (; to < (vaddr + chunk); to += vi->tree_params.digest_size)
+			memcpy(to, vi->tree_params.zero_digest,
 				vi->tree_params.digest_size);
+		kunmap_local(vaddr);
+
+		offset += chunk;
+		len -= chunk;
+	} while (len > 0);
+
+	flush_dcache_folio(folio);
 }
 EXPORT_SYMBOL_GPL(fsverity_fill_zerohash);
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 06/20] fsverity: don't allow setting DAX file attribute on fsverity files
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (4 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 05/20] fsverity: improve flushing performance of fsverity_fill_zerohash Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 07/20] xfs: introduce fsverity on-disk changes Andrey Albershteyn
                   ` (14 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

When fsverity is enabled on the file, with FS_IOC_ENABLE_VERITY ioctl(),
it checks if file has DAX enabled and fails if that's true. However, the
opposite case is not checked.

Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/file_attr.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/fs/file_attr.c b/fs/file_attr.c
index bfb00d256dd5..5424ec4e3949 100644
--- a/fs/file_attr.c
+++ b/fs/file_attr.c
@@ -246,6 +246,11 @@ static int fileattr_set_prepare(struct inode *inode,
 	if (fa->fsx_cowextsize == 0)
 		fa->fsx_xflags &= ~FS_XFLAG_COWEXTSIZE;
 
+	/* Can not enable DAX on fsverity file */
+	if ((old_ma->fsx_xflags & FS_XFLAG_VERITY) &&
+			fa->fsx_xflags & FS_XFLAG_DAX)
+		return -EINVAL;
+
 	return 0;
 }
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 07/20] xfs: introduce fsverity on-disk changes
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (5 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 06/20] fsverity: don't allow setting DAX file attribute on fsverity files Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 08/20] xfs: initialize fs-verity on file open Andrey Albershteyn
                   ` (13 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

Introduce XFS_DIFLAG2_VERITY for inodes with fsverity. This flag
indicates that inode has fs-verity enabled (i.e. descriptor exist,
tree is built and file is read-only).

Introduce XFS_SB_FEAT_RO_COMPAT_VERITY for filesystems having
fsverity inodes. As on-disk changes applies to fsverity inodes only, let
older kernels read-only access. This will be enabled in the further
patch after full fsverity support.

Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/libxfs/xfs_format.h     | 30 +++++++++++++++++++++++++++++-
 fs/xfs/libxfs/xfs_inode_buf.c  |  8 ++++++++
 fs/xfs/libxfs/xfs_inode_util.c |  5 ++++-
 fs/xfs/libxfs/xfs_sb.c         |  2 ++
 fs/xfs/xfs_iops.c              |  5 ++++-
 fs/xfs/xfs_mount.h             |  2 ++
 6 files changed, 49 insertions(+), 3 deletions(-)

diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h
index dd0ed046fbe9..7b0511eb03d0 100644
--- a/fs/xfs/libxfs/xfs_format.h
+++ b/fs/xfs/libxfs/xfs_format.h
@@ -374,6 +374,7 @@ 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 | \
@@ -1230,16 +1231,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 +2033,26 @@ struct xfs_acl {
 #define SGI_ACL_FILE_SIZE	(sizeof(SGI_ACL_FILE)-1)
 #define SGI_ACL_DEFAULT_SIZE	(sizeof(SGI_ACL_DEFAULT)-1)
 
+/*
+ * At maximum of 8 levels with 128 hashes per block (32 bytes SHA-256) maximum
+ * tree size is ((128^8 − 1)/(128 − 1)) = 567*10^12 blocks. This should fit in
+ * 53 bits address space.
+ *
+ * At this Merkle tree size we can cover 295EB large file. This is much larger
+ * than the currently supported file size.
+ *
+ * For sha512 the largest file we can cover ends at 1 << 50 offset, this is also
+ * good.
+ */
+#define XFS_FSVERITY_LARGEST_FILE	((loff_t)1ULL << 53)
+
+/*
+ * Alignment of the fsverity metadata placement. This is largest supported PAGE
+ * SIZE for fsverity. This is used to space out data and metadata in page cache.
+ * The spacing is necessary for non-exposure of metadata to userspace and
+ * correct merkle tree synethesis in the iomap.
+ */
+#define XFS_FSVERITY_START_ALIGN	(65536)
+
+
 #endif /* __XFS_FORMAT_H__ */
diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c
index 336ef843f2fe..bd80a19f2e09 100644
--- a/fs/xfs/libxfs/xfs_inode_buf.c
+++ b/fs/xfs/libxfs/xfs_inode_buf.c
@@ -760,6 +760,14 @@ xfs_dinode_verify(
 	    !xfs_has_rtreflink(mp))
 		return __this_address;
 
+	/* only regular files can have fsverity */
+	if (flags2 & XFS_DIFLAG2_VERITY) {
+		if (!xfs_has_verity(mp))
+			return __this_address;
+		if (!S_ISREG(mode))
+			return __this_address;
+	}
+
 	if (xfs_has_zoned(mp) &&
 	    dip->di_metatype == cpu_to_be16(XFS_METAFILE_RTRMAP)) {
 		if (be32_to_cpu(dip->di_used_blocks) > mp->m_sb.sb_rgextents)
diff --git a/fs/xfs/libxfs/xfs_inode_util.c b/fs/xfs/libxfs/xfs_inode_util.c
index 258ac3d0d486..5a516bd31c31 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 47322adb7690..a15510ebd2f1 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)
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 6339f4956ecb..11dc1a485ca7 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -1398,12 +1398,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_mount.h b/fs/xfs/xfs_mount.h
index 66a02d1b9ad7..9ed7d091093d 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -388,6 +388,7 @@ typedef struct xfs_mount {
 #define XFS_FEAT_EXCHANGE_RANGE	(1ULL << 27)	/* exchange range */
 #define XFS_FEAT_METADIR	(1ULL << 28)	/* metadata directory tree */
 #define XFS_FEAT_ZONED		(1ULL << 29)	/* zoned RT device */
+#define XFS_FEAT_VERITY		(1ULL << 30)	/* fs-verity */
 
 /* Mount features */
 #define XFS_FEAT_NOLIFETIME	(1ULL << 47)	/* disable lifetime hints */
@@ -445,6 +446,7 @@ __XFS_HAS_FEAT(exchange_range, EXCHANGE_RANGE)
 __XFS_HAS_FEAT(metadir, METADIR)
 __XFS_HAS_FEAT(zoned, ZONED)
 __XFS_HAS_FEAT(nolifetime, NOLIFETIME)
+__XFS_HAS_FEAT(verity, VERITY)
 
 static inline bool xfs_has_rtgroups(const struct xfs_mount *mp)
 {
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 08/20] xfs: initialize fs-verity on file open
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (6 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 07/20] xfs: introduce fsverity on-disk changes Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 09/20] xfs: don't allow to enable DAX on fs-verity sealed inode Andrey Albershteyn
                   ` (12 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

fs-verity will read and attach metadata (not the tree itself) from
a disk for those inodes which already have fs-verity enabled.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/xfs_file.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index 845a97c9b063..e3f4353b3b78 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -37,6 +37,7 @@
 #include <linux/fadvise.h>
 #include <linux/mount.h>
 #include <linux/filelock.h>
+#include <linux/fsverity.h>
 
 static const struct vm_operations_struct xfs_file_vm_ops;
 
@@ -1700,11 +1701,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);
 }
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 09/20] xfs: don't allow to enable DAX on fs-verity sealed inode
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (7 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 08/20] xfs: initialize fs-verity on file open Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 10/20] xfs: don't report dio_mem_align and dio_offset_align for fsverity files Andrey Albershteyn
                   ` (11 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

fs-verity doesn't support DAX. Forbid filesystem to enable DAX on
inodes which already have fs-verity enabled. The opposite is checked
when fs-verity is enabled, it won't be enabled if DAX is.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/xfs_iops.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 11dc1a485ca7..55667cc762f8 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -1370,6 +1370,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)
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 10/20] xfs: don't report dio_mem_align and dio_offset_align for fsverity files
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (8 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 09/20] xfs: don't allow to enable DAX on fs-verity sealed inode Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 11/20] xfs: disable direct read path for fs-verity files Andrey Albershteyn
                   ` (10 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

Sashiko.dev reported that while fsverity files falls back to the
buffered IO for Direct I/O, they should not report non-zero values in
dio_mem_align and dio_offset_align, meaning it's not supported.

Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/xfs_iops.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 55667cc762f8..70637cdcc299 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -35,6 +35,7 @@
 #include <linux/security.h>
 #include <linux/iversion.h>
 #include <linux/fiemap.h>
+#include <linux/fsverity.h>
 
 /*
  * Directories have different lock order w.r.t. mmap_lock compared to regular
@@ -580,6 +581,10 @@ xfs_report_dioalign(
 	struct block_device	*bdev = target->bt_bdev;
 
 	stat->result_mask |= STATX_DIOALIGN | STATX_DIO_READ_ALIGN;
+
+	if (fsverity_active(VFS_I(ip)))
+		return;
+
 	stat->dio_mem_align = bdev_dma_alignment(bdev) + 1;
 
 	/*
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 11/20] xfs: disable direct read path for fs-verity files
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (9 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 10/20] xfs: don't report dio_mem_align and dio_offset_align for fsverity files Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 12/20] xfs: handle fsverity I/O in write/read path Andrey Albershteyn
                   ` (9 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

The direct path is not supported on verity files. Attempts to use direct
I/O path on such files should fall back to buffered I/O path.

Add a fall back to buffered I/O at two place, in a common fast path and
latter when lock is acquired. The second check prevents TOCTOU issue
with reading fsverity_active() status and resetting IOCB_DIRECT flag.

If one thread saw fsverity_active() to be false, and then second thread
acquired XFS_IOLOCK_EXCL and enabled fsverity. The first thread will go
through the DIO path.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/xfs_file.c | 59 +++++++++++++++++++++++++++++++----------------
 1 file changed, 39 insertions(+), 20 deletions(-)

diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index e3f4353b3b78..cba194d678fc 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -246,6 +246,25 @@ static const struct iomap_dio_ops xfs_dio_read_bounce_ops = {
 	.bio_set	= &iomap_ioend_bioset,
 };
 
+STATIC ssize_t
+xfs_file_buffered_read(
+	struct kiocb		*iocb,
+	struct iov_iter		*to)
+{
+	struct xfs_inode	*ip = XFS_I(file_inode(iocb->ki_filp));
+	ssize_t			ret;
+
+	trace_xfs_file_buffered_read(iocb, to);
+
+	ret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED);
+	if (ret)
+		return ret;
+	ret = generic_file_read_iter(iocb, to);
+	xfs_iunlock(ip, XFS_IOLOCK_SHARED);
+
+	return ret;
+}
+
 STATIC ssize_t
 xfs_file_dio_read(
 	struct kiocb		*iocb,
@@ -266,6 +285,16 @@ xfs_file_dio_read(
 	ret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED);
 	if (ret)
 		return ret;
+	/*
+	 * Re-check verity status after acquiring lock. This prevents TOCTOU in
+	 * xfs_file_read_iter() while falling back from DIO to buffered I/O as
+	 * now we are holding a lock
+	 */
+	if (fsverity_active(VFS_I(ip))) {
+		xfs_iunlock(ip, XFS_IOLOCK_SHARED);
+		iocb->ki_flags &= ~IOCB_DIRECT;
+		return xfs_file_buffered_read(iocb, to);
+	}
 	if (mapping_stable_writes(iocb->ki_filp->f_mapping)) {
 		dio_ops = &xfs_dio_read_bounce_ops;
 		dio_flags |= IOMAP_DIO_BOUNCE;
@@ -282,7 +311,8 @@ xfs_file_dax_read(
 	struct kiocb		*iocb,
 	struct iov_iter		*to)
 {
-	struct xfs_inode	*ip = XFS_I(iocb->ki_filp->f_mapping->host);
+	struct inode		*inode = iocb->ki_filp->f_mapping->host;
+	struct xfs_inode	*ip = XFS_I(inode);
 	ssize_t			ret = 0;
 
 	trace_xfs_file_dax_read(iocb, to);
@@ -300,25 +330,6 @@ xfs_file_dax_read(
 	return ret;
 }
 
-STATIC ssize_t
-xfs_file_buffered_read(
-	struct kiocb		*iocb,
-	struct iov_iter		*to)
-{
-	struct xfs_inode	*ip = XFS_I(file_inode(iocb->ki_filp));
-	ssize_t			ret;
-
-	trace_xfs_file_buffered_read(iocb, to);
-
-	ret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED);
-	if (ret)
-		return ret;
-	ret = generic_file_read_iter(iocb, to);
-	xfs_iunlock(ip, XFS_IOLOCK_SHARED);
-
-	return ret;
-}
-
 STATIC ssize_t
 xfs_file_read_iter(
 	struct kiocb		*iocb,
@@ -333,6 +344,14 @@ xfs_file_read_iter(
 	if (xfs_is_shutdown(mp))
 		return -EIO;
 
+	/*
+	 * In case fs-verity is enabled, we also fallback to the buffered read
+	 * from the direct read path. Therefore, IOCB_DIRECT is set and need to
+	 * be cleared (see generic_file_read_iter())
+	 */
+	if (fsverity_active(inode))
+		iocb->ki_flags &= ~IOCB_DIRECT;
+
 	if (IS_DAX(inode))
 		ret = xfs_file_dax_read(iocb, to);
 	else if (iocb->ki_flags & IOCB_DIRECT)
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 12/20] xfs: handle fsverity I/O in write/read path
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (10 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 11/20] xfs: disable direct read path for fs-verity files Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 13/20] xfs: use read ioend for fsverity data verification Andrey Albershteyn
                   ` (8 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

For write/writeback set IOMAP_F_FSVERITY flag telling iomap to not
update inode size and to not skip folios beyond EOF.

Initiate fsverity writeback with IOMAP_F_FSVERITY set to tell iomap
should not skip folio that is dirty beyond EOF.

In read path let iomap know that we are reading fsverity metadata. So,
treat holes in the tree as request to synthesize tree blocks and hole
after descriptor as end of the fsverity region.

Introduce a new inode flag meaning that merkle tree is being build on
the inode.

Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/Makefile          |  1 +
 fs/xfs/libxfs/xfs_bmap.c | 11 +++++++++++
 fs/xfs/xfs_aops.c        | 36 +++++++++++++++++++++++++++++++-----
 fs/xfs/xfs_fsverity.c    | 20 ++++++++++++++++++++
 fs/xfs/xfs_fsverity.h    | 20 ++++++++++++++++++++
 fs/xfs/xfs_inode.h       |  6 ++++++
 fs/xfs/xfs_iomap.c       | 23 ++++++++++++++++++-----
 7 files changed, 107 insertions(+), 10 deletions(-)
 create mode 100644 fs/xfs/xfs_fsverity.c
 create mode 100644 fs/xfs/xfs_fsverity.h

diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
index 9f7133e02576..38b7f51e5d84 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -149,6 +149,7 @@ xfs-$(CONFIG_XFS_POSIX_ACL)	+= xfs_acl.o
 xfs-$(CONFIG_SYSCTL)		+= xfs_sysctl.o
 xfs-$(CONFIG_COMPAT)		+= xfs_ioctl32.o
 xfs-$(CONFIG_EXPORTFS_BLOCK_OPS)	+= xfs_pnfs.o
+xfs-$(CONFIG_FS_VERITY)		+= xfs_fsverity.o
 
 # notify failure
 ifeq ($(CONFIG_MEMORY_FAILURE),y)
diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c
index d64defeda645..cc48f6e20e80 100644
--- a/fs/xfs/libxfs/xfs_bmap.c
+++ b/fs/xfs/libxfs/xfs_bmap.c
@@ -41,6 +41,8 @@
 #include "xfs_inode_util.h"
 #include "xfs_rtgroup.h"
 #include "xfs_zone_alloc.h"
+#include "xfs_fsverity.h"
+#include <linux/fsverity.h>
 
 struct kmem_cache		*xfs_bmap_intent_cache;
 
@@ -4402,6 +4404,10 @@ xfs_bmapi_convert_one_delalloc(
 	 * the extent.  Just return the real extent at this offset.
 	 */
 	if (!isnullstartblock(bma.got.br_startblock)) {
+		if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) &&
+		    XFS_FSB_TO_B(mp, bma.got.br_startoff) >=
+			    xfs_fsverity_metadata_offset(ip))
+			flags |= IOMAP_F_FSVERITY;
 		xfs_bmbt_to_iomap(ip, iomap, &bma.got, 0, flags,
 				xfs_iomap_inode_sequence(ip, flags));
 		if (seq)
@@ -4449,6 +4455,11 @@ xfs_bmapi_convert_one_delalloc(
 	XFS_STATS_ADD(mp, xs_xstrat_bytes, XFS_FSB_TO_B(mp, bma.length));
 	XFS_STATS_INC(mp, xs_xstrat_quick);
 
+	if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) &&
+	    XFS_FSB_TO_B(mp, bma.got.br_startoff) >=
+		    xfs_fsverity_metadata_offset(ip))
+		flags |= IOMAP_F_FSVERITY;
+
 	ASSERT(!isnullstartblock(bma.got.br_startblock));
 	xfs_bmbt_to_iomap(ip, iomap, &bma.got, 0, flags,
 				xfs_iomap_inode_sequence(ip, flags));
diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
index 51293b6f331f..27ff2b240457 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -22,6 +22,7 @@
 #include "xfs_icache.h"
 #include "xfs_zone_alloc.h"
 #include "xfs_rtgroup.h"
+#include "xfs_fsverity.h"
 #include <linux/bio-integrity.h>
 
 struct xfs_writepage_ctx {
@@ -350,12 +351,16 @@ xfs_map_blocks(
 	int			retries = 0;
 	int			error = 0;
 	unsigned int		*seq;
+	unsigned int		iomap_flags = 0;
 
 	if (xfs_is_shutdown(mp))
 		return -EIO;
 
 	XFS_ERRORTAG_DELAY(mp, XFS_ERRTAG_WB_DELAY_MS);
 
+	if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+		iomap_flags |= IOMAP_F_FSVERITY;
+
 	/*
 	 * COW fork blocks can overlap data fork blocks even if the blocks
 	 * aren't shared.  COW I/O always takes precedent, so we must always
@@ -443,7 +448,8 @@ xfs_map_blocks(
 	    isnullstartblock(imap.br_startblock))
 		goto allocate_blocks;
 
-	xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, 0, XFS_WPC(wpc)->data_seq);
+	xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, iomap_flags,
+			  XFS_WPC(wpc)->data_seq);
 	trace_xfs_map_blocks_found(ip, offset, count, whichfork, &imap);
 	return 0;
 allocate_blocks:
@@ -590,12 +596,16 @@ xfs_zoned_map_blocks(
 	xfs_filblks_t		count_fsb;
 	struct xfs_bmbt_irec	imap, del;
 	struct xfs_iext_cursor	icur;
+	u16			iomap_flags = 0;
 
 	if (xfs_is_shutdown(mp))
 		return -EIO;
 
 	XFS_ERRORTAG_DELAY(mp, XFS_ERRTAG_WB_DELAY_MS);
 
+	if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+		iomap_flags |= IOMAP_F_FSVERITY;
+
 	/*
 	 * All dirty data must be covered by delalloc extents.  But truncate can
 	 * remove delalloc extents underneath us or reduce their size.
@@ -619,7 +629,7 @@ xfs_zoned_map_blocks(
 		imap.br_startblock = HOLESTARTBLOCK;
 		imap.br_state = XFS_EXT_NORM;
 		xfs_iunlock(ip, XFS_ILOCK_EXCL);
-		xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, 0, 0);
+		xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, iomap_flags, 0);
 		return 0;
 	}
 	end_fsb = min(end_fsb, imap.br_startoff + imap.br_blockcount);
@@ -632,7 +642,7 @@ xfs_zoned_map_blocks(
 	xfs_iunlock(ip, XFS_ILOCK_EXCL);
 
 	wpc->iomap.type = IOMAP_MAPPED;
-	wpc->iomap.flags = IOMAP_F_DIRTY;
+	wpc->iomap.flags = iomap_flags | IOMAP_F_DIRTY;
 	wpc->iomap.bdev = mp->m_rtdev_targp->bt_bdev;
 	wpc->iomap.offset = offset;
 	wpc->iomap.length = XFS_FSB_TO_B(mp, count_fsb);
@@ -684,6 +694,22 @@ static const struct iomap_writeback_ops xfs_zoned_writeback_ops = {
 	.writeback_submit	= xfs_zoned_writeback_submit,
 };
 
+static int
+xfs_iomap_writepages(
+	struct xfs_inode		*ip,
+	struct iomap_writepage_ctx	*ctx)
+{
+	/*
+	 * Writeback does not work for folios past EOF, let it know that
+	 * I/O happens for fsverity metadata and this restriction need
+	 * to be skipped
+	 */
+	if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+		ctx->iomap.flags |= IOMAP_F_FSVERITY;
+
+	return iomap_writepages(ctx);
+}
+
 STATIC int
 xfs_vm_writepages(
 	struct address_space	*mapping,
@@ -703,7 +729,7 @@ xfs_vm_writepages(
 		};
 		int				error;
 
-		error = iomap_writepages(&xc.ctx);
+		error = xfs_iomap_writepages(ip, &xc.ctx);
 		if (xc.open_zone)
 			xfs_open_zone_put(xc.open_zone);
 		return error;
@@ -716,7 +742,7 @@ xfs_vm_writepages(
 			},
 		};
 
-		return iomap_writepages(&wpc.ctx);
+		return xfs_iomap_writepages(ip, &wpc.ctx);
 	}
 }
 
diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c
new file mode 100644
index 000000000000..0971d2f983db
--- /dev/null
+++ b/fs/xfs/xfs_fsverity.c
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ */
+#include "xfs_platform.h"
+#include "xfs_format.h"
+#include "xfs_inode.h"
+#include "xfs_shared.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_fsverity.h"
+#include <linux/fsverity.h>
+#include <linux/iomap.h>
+
+loff_t
+xfs_fsverity_metadata_offset(
+	const struct xfs_inode	*ip)
+{
+	return round_up(i_size_read(VFS_IC(ip)), XFS_FSVERITY_START_ALIGN);
+}
diff --git a/fs/xfs/xfs_fsverity.h b/fs/xfs/xfs_fsverity.h
new file mode 100644
index 000000000000..5771db2cd797
--- /dev/null
+++ b/fs/xfs/xfs_fsverity.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ */
+#ifndef __XFS_FSVERITY_H__
+#define __XFS_FSVERITY_H__
+
+#include "xfs_platform.h"
+
+#ifdef CONFIG_FS_VERITY
+loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip);
+#else
+static inline loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip)
+{
+	WARN_ON_ONCE(1);
+	return ULLONG_MAX;
+}
+#endif	/* CONFIG_FS_VERITY */
+
+#endif	/* __XFS_FSVERITY_H__ */
diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h
index 34c1038ebfcd..17ce083591f4 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_iomap.c b/fs/xfs/xfs_iomap.c
index 225c3de88d03..fba51902416b 100644
--- a/fs/xfs/xfs_iomap.c
+++ b/fs/xfs/xfs_iomap.c
@@ -32,6 +32,8 @@
 #include "xfs_rtbitmap.h"
 #include "xfs_icache.h"
 #include "xfs_zone_alloc.h"
+#include "xfs_fsverity.h"
+#include <linux/fsverity.h>
 
 #define XFS_ALLOC_ALIGN(mp, off) \
 	(((off) >> mp->m_allocsize_log) << mp->m_allocsize_log)
@@ -1589,7 +1591,8 @@ xfs_zoned_buffered_write_iomap_begin(
 	loff_t			count,
 	unsigned		flags,
 	struct iomap		*iomap,
-	struct iomap		*srcmap)
+	struct iomap		*srcmap,
+	u16			iomap_flags)
 {
 	struct iomap_iter	*iter =
 		container_of(iomap, struct iomap_iter, iomap);
@@ -1599,7 +1602,6 @@ xfs_zoned_buffered_write_iomap_begin(
 	struct xfs_mount	*mp = ip->i_mount;
 	xfs_fileoff_t		offset_fsb = XFS_B_TO_FSBT(mp, offset);
 	xfs_fileoff_t		end_fsb = xfs_iomap_end_fsb(mp, offset, count);
-	u16			iomap_flags = IOMAP_F_SHARED;
 	unsigned int		lockmode = XFS_ILOCK_EXCL;
 	xfs_filblks_t		count_fsb;
 	xfs_extlen_t		indlen;
@@ -1607,6 +1609,8 @@ xfs_zoned_buffered_write_iomap_begin(
 	struct xfs_iext_cursor	icur;
 	int			error = 0;
 
+	iomap_flags = iomap_flags | IOMAP_F_SHARED;
+
 	ASSERT(!xfs_get_extsz_hint(ip));
 	ASSERT(!(flags & IOMAP_UNSHARE));
 	ASSERT(ac);
@@ -1803,9 +1807,12 @@ xfs_buffered_write_iomap_begin(
 	if (xfs_is_shutdown(mp))
 		return -EIO;
 
+	if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+		iomap_flags |= IOMAP_F_FSVERITY;
+
 	if (xfs_is_zoned_inode(ip))
 		return xfs_zoned_buffered_write_iomap_begin(inode, offset,
-				count, flags, iomap, srcmap);
+				count, flags, iomap, srcmap, iomap_flags);
 
 	/* we can't use delayed allocations when using extent size hints */
 	if (xfs_get_extsz_hint(ip))
@@ -2191,12 +2198,17 @@ xfs_read_iomap_begin(
 	bool			shared = false;
 	unsigned int		lockmode = XFS_ILOCK_SHARED;
 	u64			seq;
+	unsigned int		iomap_flags = 0;
 
 	ASSERT(!(flags & (IOMAP_WRITE | IOMAP_ZERO)));
 
 	if (xfs_is_shutdown(mp))
 		return -EIO;
 
+	if (fsverity_active(inode) &&
+	    (offset >= xfs_fsverity_metadata_offset(ip)))
+		iomap_flags |= IOMAP_F_FSVERITY;
+
 	error = xfs_ilock_for_iomap(ip, flags, &lockmode);
 	if (error)
 		return error;
@@ -2210,8 +2222,9 @@ xfs_read_iomap_begin(
 	if (error)
 		return error;
 	trace_xfs_iomap_found(ip, offset, length, XFS_DATA_FORK, &imap);
-	return xfs_bmbt_to_iomap(ip, iomap, &imap, flags,
-				 shared ? IOMAP_F_SHARED : 0, seq);
+	iomap_flags |= shared ? IOMAP_F_SHARED : 0;
+
+	return xfs_bmbt_to_iomap(ip, iomap, &imap, flags, iomap_flags, seq);
 }
 
 const struct iomap_ops xfs_read_iomap_ops = {
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 13/20] xfs: use read ioend for fsverity data verification
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (11 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 12/20] xfs: handle fsverity I/O in write/read path Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 14/20] xfs: add fs-verity support Andrey Albershteyn
                   ` (7 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

Use read ioends for fsverity verification. Do not issues fsverity
metadata I/O through the same workqueue due to risk of a deadlock by a
filled workqueue.

Pass fsverity_info from iomap context down to the ioend as hashtable
lookups are expensive.

Add a simple helper to check that this is not fsverity metadata but file
data that needs verification.

Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/xfs_aops.c     | 45 ++++++++++++++++++++++++++++++++++---------
 fs/xfs/xfs_fsverity.c |  9 +++++++++
 fs/xfs/xfs_fsverity.h |  6 ++++++
 3 files changed, 51 insertions(+), 9 deletions(-)

diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
index 27ff2b240457..b0537c67e960 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -24,6 +24,7 @@
 #include "xfs_rtgroup.h"
 #include "xfs_fsverity.h"
 #include <linux/bio-integrity.h>
+#include <linux/fsverity.h>
 
 struct xfs_writepage_ctx {
 	struct iomap_writepage_ctx ctx;
@@ -182,6 +183,23 @@ xfs_end_ioend_write(
 	memalloc_nofs_restore(nofs_flag);
 }
 
+/*
+ * IO read completion.
+ */
+static void
+xfs_end_ioend_read(
+	struct iomap_ioend	*ioend)
+{
+	struct xfs_inode	*ip = XFS_I(ioend->io_inode);
+
+	if (!ioend->io_bio.bi_status &&
+			xfs_fsverity_is_file_data(ip, ioend->io_offset))
+		fsverity_verify_bio(ioend->io_vi,
+				    &ioend->io_bio);
+	iomap_finish_ioends(ioend,
+		blk_status_to_errno(ioend->io_bio.bi_status));
+}
+
 /*
  * Finish all pending IO completions that require transactional modifications.
  *
@@ -216,8 +234,7 @@ xfs_end_io(
 		list_del_init(&ioend->io_list);
 		iomap_ioend_try_merge(ioend, &tmp);
 		if (bio_op(&ioend->io_bio) == REQ_OP_READ)
-			iomap_finish_ioends(ioend,
-				blk_status_to_errno(ioend->io_bio.bi_status));
+			xfs_end_ioend_read(ioend);
 		else
 			xfs_end_ioend_write(ioend);
 		cond_resched();
@@ -243,9 +260,14 @@ xfs_end_bio(
 	}
 
 	spin_lock_irqsave(&ip->i_ioend_lock, flags);
-	if (list_empty(&ip->i_ioend_list))
-		WARN_ON_ONCE(!queue_work(mp->m_unwritten_workqueue,
+	if (list_empty(&ip->i_ioend_list)) {
+		if (IS_ENABLED(CONFIG_FS_VERITY) && ioend->io_vi &&
+		    ioend->io_offset < xfs_fsverity_metadata_offset(ip))
+			fsverity_enqueue_verify_work(&ip->i_ioend_work);
+		else
+			WARN_ON_ONCE(!queue_work(mp->m_unwritten_workqueue,
 					 &ip->i_ioend_work));
+	}
 	list_add_tail(&ioend->io_list, &ip->i_ioend_list);
 	spin_unlock_irqrestore(&ip->i_ioend_lock, flags);
 }
@@ -787,9 +809,12 @@ xfs_bio_submit_read(
 	struct iomap_read_folio_ctx	*ctx)
 {
 	struct bio			*bio = ctx->read_ctx;
+	struct iomap_ioend		*ioend;
 
 	/* defer read completions to the ioend workqueue */
-	iomap_init_ioend(iter->inode, bio, ctx->read_ctx_file_offset, 0);
+	ioend = iomap_init_ioend(iter->inode, bio, ctx->read_ctx_file_offset,
+			0);
+	ioend->io_vi = ctx->vi;
 	iomap_bio_submit_read_endio(iter, ctx, xfs_end_bio);
 }
 
@@ -801,11 +826,13 @@ static const struct iomap_read_ops xfs_iomap_read_ops = {
 
 static inline const struct iomap_read_ops *
 xfs_get_iomap_read_ops(
-	const struct address_space	*mapping)
+	const struct address_space	*mapping,
+	loff_t				position)
 {
 	struct xfs_inode		*ip = XFS_I(mapping->host);
 
-	if (bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev))
+	if (bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev) ||
+			xfs_fsverity_is_file_data(ip, position))
 		return &xfs_iomap_read_ops;
 	return &iomap_bio_read_ops;
 }
@@ -817,7 +844,7 @@ xfs_vm_read_folio(
 {
 	struct iomap_read_folio_ctx	ctx = { .cur_folio = folio };
 
-	ctx.ops = xfs_get_iomap_read_ops(folio->mapping);
+	ctx.ops = xfs_get_iomap_read_ops(folio->mapping, folio_pos(folio));
 	iomap_read_folio(&xfs_read_iomap_ops, &ctx, NULL);
 	return 0;
 }
@@ -828,7 +855,7 @@ xfs_vm_readahead(
 {
 	struct iomap_read_folio_ctx	ctx = { .rac = rac };
 
-	ctx.ops = xfs_get_iomap_read_ops(rac->mapping),
+	ctx.ops = xfs_get_iomap_read_ops(rac->mapping, readahead_pos(rac));
 	iomap_readahead(&xfs_read_iomap_ops, &ctx, NULL);
 }
 
diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c
index 0971d2f983db..e3c8ac51c43f 100644
--- a/fs/xfs/xfs_fsverity.c
+++ b/fs/xfs/xfs_fsverity.c
@@ -18,3 +18,12 @@ xfs_fsverity_metadata_offset(
 {
 	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);
+}
diff --git a/fs/xfs/xfs_fsverity.h b/fs/xfs/xfs_fsverity.h
index 5771db2cd797..ec77ba571106 100644
--- a/fs/xfs/xfs_fsverity.h
+++ b/fs/xfs/xfs_fsverity.h
@@ -9,12 +9,18 @@
 
 #ifdef CONFIG_FS_VERITY
 loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip);
+bool xfs_fsverity_is_file_data(const struct xfs_inode *ip, loff_t offset);
 #else
 static inline loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip)
 {
 	WARN_ON_ONCE(1);
 	return ULLONG_MAX;
 }
+static inline bool xfs_fsverity_is_file_data(const struct xfs_inode *ip,
+					    loff_t offset)
+{
+	return false;
+}
 #endif	/* CONFIG_FS_VERITY */
 
 #endif	/* __XFS_FSVERITY_H__ */
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 14/20] xfs: add fs-verity support
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (12 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 13/20] xfs: use read ioend for fsverity data verification Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 15/20] xfs: remove unwritten extents after preallocations in fsverity metadata Andrey Albershteyn
                   ` (6 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

Add integration with fs-verity. XFS stores fs-verity descriptor and
Merkle tree in the inode data fork at first block aligned to 64k past
EOF.

The Merkle tree reading/writing is done through iomap interface. The
data itself is read to the inode's page cache. When XFS reads from this
region iomap doesn't call into fsverity to verify it against Merkle
tree. For data, verification is done at ioend completion in a workqueue.

When fs-verity is enabled on an inode, the XFS_IVERITY_CONSTRUCTION
flag is set meaning that the Merkle tree is being build. The
initialization ends with storing of verity descriptor and setting
inode on-disk flag (XFS_DIFLAG2_VERITY). Lastly, the
XFS_IVERITY_CONSTRUCTION is dropped and I_VERITY is set on inode.

The descriptor is stored in a new block aligned to 64k after the last
Merkle tree block. The size of the descriptor is stored at the end of
the last descriptor block (descriptor can be multiple blocks).

Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/xfs_bmap_util.c |   8 +
 fs/xfs/xfs_fsverity.c  | 370 ++++++++++++++++++++++++++++++++++++++++-
 fs/xfs/xfs_fsverity.h  |   2 +
 fs/xfs/xfs_message.c   |   4 +
 fs/xfs/xfs_message.h   |   1 +
 fs/xfs/xfs_mount.h     |   2 +
 fs/xfs/xfs_super.c     |   7 +
 7 files changed, 393 insertions(+), 1 deletion(-)

diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c
index 3b9f262f8e91..b34f2f27cc93 100644
--- a/fs/xfs/xfs_bmap_util.c
+++ b/fs/xfs/xfs_bmap_util.c
@@ -31,6 +31,7 @@
 #include "xfs_rtbitmap.h"
 #include "xfs_rtgroup.h"
 #include "xfs_zone_alloc.h"
+#include <linux/fsverity.h>
 
 /* Kernel only BMAP related definitions and functions */
 
@@ -553,6 +554,13 @@ xfs_can_free_eofblocks(
 	if (last_fsb <= end_fsb)
 		return false;
 
+	/*
+	 * Nothing to clean on fsverity inodes as they don't use prealloc and
+	 * there no delalloc as only written data is fsverity metadata
+	 */
+	if (IS_VERITY(VFS_I(ip)))
+		return false;
+
 	/*
 	 * Check if there is an post-EOF extent to free.  If there are any
 	 * delalloc blocks attached to the inode (data fork delalloc
diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c
index e3c8ac51c43f..0d66aec43383 100644
--- a/fs/xfs/xfs_fsverity.c
+++ b/fs/xfs/xfs_fsverity.c
@@ -4,13 +4,24 @@
  */
 #include "xfs_platform.h"
 #include "xfs_format.h"
-#include "xfs_inode.h"
 #include "xfs_shared.h"
 #include "xfs_trans_resv.h"
 #include "xfs_mount.h"
+#include "xfs_da_format.h"
+#include "xfs_da_btree.h"
+#include "xfs_inode.h"
+#include "xfs_log_format.h"
+#include "xfs_bmap_util.h"
+#include "xfs_trans.h"
+#include "xfs_trace.h"
+#include "xfs_quota.h"
 #include "xfs_fsverity.h"
+#include "xfs_iomap.h"
+#include "xfs_error.h"
+#include "xfs_health.h"
 #include <linux/fsverity.h>
 #include <linux/iomap.h>
+#include <linux/pagemap.h>
 
 loff_t
 xfs_fsverity_metadata_offset(
@@ -27,3 +38,360 @@ xfs_fsverity_is_file_data(
 	return fsverity_active(VFS_IC(ip)) &&
 			offset < xfs_fsverity_metadata_offset(ip);
 }
+
+/*
+ * Retrieve the verity descriptor.
+ */
+static int
+xfs_fsverity_get_descriptor(
+	struct inode		*inode,
+	void			*buf,
+	size_t			buf_size)
+{
+	struct xfs_inode	*ip = XFS_I(inode);
+	struct xfs_mount	*mp = ip->i_mount;
+	__be32			d_desc_size;
+	u32			desc_size;
+	u64			desc_size_pos;
+	int			error;
+	u64			desc_pos;
+	struct xfs_bmbt_irec	rec;
+	int			is_empty;
+	uint32_t		blocksize = i_blocksize(VFS_I(ip));
+	xfs_fileoff_t		last_block_offset;
+
+	ASSERT(inode->i_flags & S_VERITY);
+	xfs_ilock(ip, XFS_ILOCK_SHARED);
+	error = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, &rec, &is_empty);
+	xfs_iunlock(ip, XFS_ILOCK_SHARED);
+	if (error)
+		return error;
+
+	if (is_empty)
+		return -ENODATA;
+
+	last_block_offset =
+		XFS_FSB_TO_B(mp, rec.br_startoff + rec.br_blockcount);
+	if (last_block_offset <= xfs_fsverity_metadata_offset(ip))
+		return -ENODATA;
+
+	desc_size_pos = last_block_offset - sizeof(__be32);
+	error = fsverity_pagecache_read(inode, (char *)&d_desc_size,
+			sizeof(d_desc_size), desc_size_pos);
+	if (error)
+		return error;
+
+	desc_size = be32_to_cpu(d_desc_size);
+	if (XFS_IS_CORRUPT(mp, desc_size > FS_VERITY_MAX_DESCRIPTOR_SIZE))
+		return -ERANGE;
+	if (XFS_IS_CORRUPT(mp, desc_size > desc_size_pos))
+		return -ERANGE;
+
+	if (!buf_size)
+		return desc_size;
+
+	if (XFS_IS_CORRUPT(mp, desc_size > buf_size))
+		return -ERANGE;
+
+	desc_pos = round_down(desc_size_pos - desc_size, blocksize);
+	if (desc_pos < xfs_fsverity_metadata_offset(ip)) {
+		xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);
+		return -ERANGE;
+	}
+
+	error = fsverity_pagecache_read(inode, buf, desc_size, desc_pos);
+	if (error)
+		return error;
+
+	return desc_size;
+}
+
+static int
+xfs_fsverity_write_descriptor(
+	struct file		*file,
+	const void		*desc,
+	u32			desc_size,
+	u64			merkle_tree_size)
+{
+	int			error;
+	struct inode		*inode = file_inode(file);
+	struct xfs_inode	*ip = XFS_I(inode);
+	unsigned int		blksize = ip->i_mount->m_sb.sb_blocksize;
+	u64			tree_last_block =
+			xfs_fsverity_metadata_offset(ip) + merkle_tree_size;
+	u64			desc_pos =
+			round_up(tree_last_block, XFS_FSVERITY_START_ALIGN);
+	u64			desc_end = desc_pos + desc_size;
+	__be32			desc_size_disk = cpu_to_be32(desc_size);
+	u64			desc_size_pos =
+			round_up(desc_end + sizeof(desc_size_disk), blksize) -
+			sizeof(desc_size_disk);
+
+	error = iomap_fsverity_write(file, desc_size_pos, sizeof(__be32),
+			(const void *)&desc_size_disk,
+			&xfs_buffered_write_iomap_ops,
+			&xfs_iomap_write_ops);
+	if (error)
+		return error;
+
+	return iomap_fsverity_write(file, desc_pos, desc_size, desc,
+			&xfs_buffered_write_iomap_ops,
+			&xfs_iomap_write_ops);
+}
+
+/*
+ * Try to remove all the fsverity metadata after a failed enablement.
+ */
+static int
+xfs_fsverity_delete_metadata(
+	struct xfs_inode	*ip)
+{
+	struct xfs_trans	*tp;
+	struct xfs_mount	*mp = ip->i_mount;
+	int			error;
+
+	error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp);
+	if (error)
+		return error;
+
+	xfs_ilock(ip, XFS_ILOCK_EXCL);
+	xfs_trans_ijoin(tp, ip, 0);
+
+	/*
+	 * We removing post EOF data, no need to update i_size as fsverity
+	 * didn't move i_size in the first place
+	 */
+	error = xfs_itruncate_extents(&tp, ip, XFS_DATA_FORK, XFS_ISIZE(ip));
+	if (error)
+		goto err_cancel;
+
+	error = xfs_trans_commit(tp);
+	if (error)
+		goto out_unlock;
+out_unlock:
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	return error;
+
+err_cancel:
+	xfs_trans_cancel(tp);
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	return error;
+}
+
+
+/*
+ * Prepare to enable fsverity by clearing old metadata.
+ */
+static int
+xfs_fsverity_begin_enable(
+	struct file		*filp)
+{
+	struct inode		*inode = file_inode(filp);
+	struct xfs_inode	*ip = XFS_I(inode);
+	int			error;
+
+	xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);
+
+	if (IS_DAX(inode) || ip->i_diflags2 & XFS_DIFLAG2_DAX)
+		return -EINVAL;
+
+	if (inode->i_size > XFS_FSVERITY_LARGEST_FILE)
+		return -EFBIG;
+
+	/*
+	 * Flush pagecache before building Merkle tree. Inode is locked and no
+	 * further writes will happen to the file except fsverity metadata
+	 */
+	error = filemap_write_and_wait(inode->i_mapping);
+	if (error)
+		return error;
+
+	if (xfs_iflags_test_and_set(ip, XFS_VERITY_CONSTRUCTION))
+		return -EBUSY;
+
+	error = xfs_qm_dqattach(ip);
+	if (error)
+		goto out_clear;
+
+	error = xfs_fsverity_delete_metadata(ip);
+	if (error)
+		goto out_clear;
+	return error;
+out_clear:
+	xfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);
+	return error;
+}
+
+/*
+ * Complete (or fail) the process of enabling fsverity.
+ */
+static int
+xfs_fsverity_end_enable(
+	struct file		*file,
+	const void		*desc,
+	size_t			desc_size,
+	u64			merkle_tree_size)
+{
+	struct inode		*inode = file_inode(file);
+	struct xfs_inode	*ip = XFS_I(inode);
+	struct xfs_mount	*mp = ip->i_mount;
+	struct xfs_trans	*tp;
+	int			error = 0;
+	loff_t			range_start = xfs_fsverity_metadata_offset(ip);
+
+	xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);
+
+	/* fs-verity failed, just cleanup */
+	if (desc == NULL) {
+		error = xfs_fsverity_delete_metadata(ip);
+		goto out;
+	}
+
+	error = xfs_fsverity_write_descriptor(file, desc, desc_size,
+			merkle_tree_size);
+	if (error)
+		goto out;
+
+	/*
+	 * Wait for Merkle tree get written to disk before setting on-disk inode
+	 * flag and clearing XFS_VERITY_CONSTRUCTION
+	 */
+	error = filemap_write_and_wait_range(inode->i_mapping, range_start,
+			LLONG_MAX);
+	if (error)
+		goto out;
+
+	/*
+	 * Proactively drop any delayed allocations in COW fork, the fsverity
+	 * files are read-only
+	 */
+	if (xfs_is_cow_inode(ip)) {
+		if (!ip->i_cowfp)
+			xfs_ifork_init_cow(ip);
+		xfs_bmap_punch_delalloc_range(ip, XFS_COW_FORK, 0, LLONG_MAX,
+				NULL);
+	}
+
+	/*
+	 * Set fsverity inode flag
+	 */
+	error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_ichange,
+			0, 0, false, &tp);
+	if (error)
+		goto out;
+
+	/*
+	 * Ensure that we've persisted the verity information before we enable
+	 * it on the inode and tell the caller we have sealed the inode.
+	 */
+	ip->i_diflags2 |= XFS_DIFLAG2_VERITY;
+
+	xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
+	xfs_trans_set_sync(tp);
+
+	error = xfs_trans_commit(tp);
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+
+	if (!error)
+		inode->i_flags |= S_VERITY;
+
+out:
+	if (error) {
+		int	error2;
+
+		error2 = xfs_fsverity_delete_metadata(ip);
+		if (error2)
+			xfs_alert(ip->i_mount,
+"ino 0x%llx failed to clean up new fsverity metadata, err %d",
+					I_INO(ip), error2);
+	}
+
+	xfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);
+	return error;
+}
+
+/*
+ * Retrieve a merkle tree block.
+ */
+static struct page *
+xfs_fsverity_read_merkle(
+	struct inode		*inode,
+	pgoff_t			index)
+{
+	index += xfs_fsverity_metadata_offset(XFS_I(inode)) >> PAGE_SHIFT;
+
+	return generic_read_merkle_tree_page(inode, index);
+}
+
+/*
+ * Retrieve a merkle tree block.
+ */
+static void
+xfs_fsverity_readahead_merkle_tree(
+	struct inode		*inode,
+	pgoff_t			index,
+	unsigned long		nr_pages)
+{
+	index += xfs_fsverity_metadata_offset(XFS_I(inode)) >> PAGE_SHIFT;
+
+	generic_readahead_merkle_tree(inode, index, nr_pages);
+}
+
+/*
+ * Write a merkle tree block.
+ */
+static int
+xfs_fsverity_write_merkle(
+	struct file		*file,
+	const void		*buf,
+	u64			pos,
+	unsigned int		size,
+	const u8		*zero_digest,
+	unsigned int		digest_size)
+{
+	struct inode		*inode = file_inode(file);
+	struct xfs_inode	*ip = XFS_I(inode);
+	loff_t			position = pos +
+		xfs_fsverity_metadata_offset(ip);
+
+	if (position + size > inode->i_sb->s_maxbytes)
+		return -EFBIG;
+
+	/*
+	 * If this is a block full of hashes of zeroed blocks, don't bother
+	 * storing the block. We can synthesize them later.
+	 *
+	 * However, do this only in case Merkle tree block == fs block size.
+	 * Iomap synthesizes these blocks based on holes in the merkle tree. We
+	 * won't be able to tell if something need to be synthesizes for the
+	 * range in the fs block. For example, for 4k filesystem block
+	 *
+	 *	[ 1k | zero hashes | zero hashes | 1k ]
+	 *
+	 * Iomap won't know about these empty blocks.
+	 */
+	if (size == ip->i_mount->m_sb.sb_blocksize &&
+			/*
+			 * First digest is zero_digest
+			 */
+			memcmp(buf, zero_digest, digest_size) == 0 &&
+			/*
+			 * Every digest is same as previous, thus all are
+			 * zero_digest
+			 */
+			memcmp(buf + digest_size, buf, size - digest_size) == 0)
+		return 0;
+
+	return iomap_fsverity_write(file, position, size, buf,
+			&xfs_buffered_write_iomap_ops,
+			&xfs_iomap_write_ops);
+}
+
+const struct fsverity_operations xfs_fsverity_ops = {
+	.begin_enable_verity		= xfs_fsverity_begin_enable,
+	.end_enable_verity		= xfs_fsverity_end_enable,
+	.get_verity_descriptor		= xfs_fsverity_get_descriptor,
+	.read_merkle_tree_page		= xfs_fsverity_read_merkle,
+	.readahead_merkle_tree		= xfs_fsverity_readahead_merkle_tree,
+	.write_merkle_tree_block	= xfs_fsverity_write_merkle,
+};
diff --git a/fs/xfs/xfs_fsverity.h b/fs/xfs/xfs_fsverity.h
index ec77ba571106..6a981e20a75b 100644
--- a/fs/xfs/xfs_fsverity.h
+++ b/fs/xfs/xfs_fsverity.h
@@ -6,8 +6,10 @@
 #define __XFS_FSVERITY_H__
 
 #include "xfs_platform.h"
+#include <linux/fsverity.h>
 
 #ifdef CONFIG_FS_VERITY
+extern const struct fsverity_operations xfs_fsverity_ops;
 loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip);
 bool xfs_fsverity_is_file_data(const struct xfs_inode *ip, loff_t offset);
 #else
diff --git a/fs/xfs/xfs_message.c b/fs/xfs/xfs_message.c
index 0243e509a468..44b13d46049c 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 811b885f41c3..364fcc7e42b0 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 9ed7d091093d..875c9b0f8e99 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -584,6 +584,8 @@ __XFS_HAS_FEAT(nouuid, NOUUID)
 #define XFS_OPSTATE_RESUMING_QUOTAON	18
 /* (Zoned) GC is in progress */
 #define XFS_OPSTATE_ZONEGC_RUNNING	20
+/* Kernel has logged a warning about fsverity support */
+#define XFS_OPSTATE_WARNED_FSVERITY	21
 
 #define __XFS_IS_OPSTATE(name, NAME) \
 static inline bool xfs_is_ ## name (struct xfs_mount *mp) \
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index 8531d526fc44..a7774d2d6b23 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -30,6 +30,7 @@
 #include "xfs_filestream.h"
 #include "xfs_quota.h"
 #include "xfs_sysfs.h"
+#include "xfs_fsverity.h"
 #include "xfs_ondisk.h"
 #include "xfs_rmap_item.h"
 #include "xfs_refcount_item.h"
@@ -1689,6 +1690,9 @@ xfs_fs_fill_super(
 	sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP | QTYPE_MASK_PRJ;
 #endif
 	sb->s_op = &xfs_super_operations;
+#ifdef CONFIG_FS_VERITY
+	sb->s_vop = &xfs_fsverity_ops;
+#endif
 
 	/*
 	 * Delay mount work if the debug hook is set. This is debug
@@ -1941,6 +1945,9 @@ xfs_fs_fill_super(
 	if (error)
 		goto out_filestream_unmount;
 
+	if (xfs_has_verity(mp))
+		xfs_warn_experimental(mp, XFS_EXPERIMENTAL_FSVERITY);
+
 	root = igrab(VFS_I(mp->m_rootip));
 	if (!root) {
 		error = -ENOENT;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 15/20] xfs: remove unwritten extents after preallocations in fsverity metadata
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (13 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 14/20] xfs: add fs-verity support Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 16/20] xfs: add fs-verity ioctls Andrey Albershteyn
                   ` (5 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

XFS preallocates spaces during writes. In normal I/O this space, if
unused, is removed by truncate. For files with fsverity XFS does not use
truncate as fsverity metadata is stored past EOF.

After we're done with writing fsverity metadata iterate over extents in
that region and remove any unwritten ones. These would be left overs in
the holes in the merkle tree and past fsverity descriptor.

Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/xfs_fsverity.c | 69 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)

diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c
index 0d66aec43383..31faa95c396d 100644
--- a/fs/xfs/xfs_fsverity.c
+++ b/fs/xfs/xfs_fsverity.c
@@ -19,6 +19,8 @@
 #include "xfs_iomap.h"
 #include "xfs_error.h"
 #include "xfs_health.h"
+#include "xfs_bmap.h"
+#include "xfs_bmap_util.h"
 #include <linux/fsverity.h>
 #include <linux/iomap.h>
 #include <linux/pagemap.h>
@@ -178,6 +180,65 @@ xfs_fsverity_delete_metadata(
 	return error;
 }
 
+static int
+xfs_fsverity_cancel_unwritten(
+	struct xfs_inode	*ip,
+	loff_t			start,
+	loff_t			end)
+{
+	struct xfs_mount	*mp = ip->i_mount;
+	struct xfs_trans	*tp;
+	xfs_fileoff_t		offset_fsb = XFS_B_TO_FSB(mp, start);
+	xfs_fileoff_t		end_fsb = XFS_B_TO_FSB(mp, end);
+	struct xfs_bmbt_irec	imap;
+	int			nimaps;
+	int			error = 0;
+	int			done;
+
+
+	while (offset_fsb < end_fsb) {
+		nimaps = 1;
+
+		error = xfs_trans_alloc(mp, &M_RES(mp)->tr_write, 0, 0,
+				0, &tp);
+		if (error)
+			return error;
+
+		xfs_ilock(ip, XFS_ILOCK_EXCL);
+		error = xfs_bmapi_read(ip, offset_fsb, end_fsb - offset_fsb,
+				&imap, &nimaps, 0);
+		if (error)
+			goto out_cancel;
+
+		if (nimaps == 0)
+			goto out_cancel;
+
+		if (imap.br_state == XFS_EXT_UNWRITTEN) {
+			xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
+
+			error = xfs_bunmapi(tp, ip, imap.br_startoff,
+					imap.br_blockcount, 0, 1, &done);
+			if (error)
+				goto out_cancel;
+
+			error = xfs_trans_commit(tp);
+			if (error)
+				return error;
+		} else {
+			xfs_trans_cancel(tp);
+			xfs_iunlock(ip, XFS_ILOCK_EXCL);
+		}
+
+		offset_fsb = imap.br_startoff + imap.br_blockcount;
+	}
+
+	return error;
+out_cancel:
+	xfs_trans_cancel(tp);
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	return error;
+}
+
 
 /*
  * Prepare to enable fsverity by clearing old metadata.
@@ -261,6 +322,14 @@ xfs_fsverity_end_enable(
 	if (error)
 		goto out;
 
+	/*
+	 * Remove unwritten extents left by COW preallocations and write
+	 * preallocation in the merkle tree holes and past descriptor
+	 */
+	error = xfs_fsverity_cancel_unwritten(ip, range_start, LLONG_MAX);
+	if (error)
+		goto out;
+
 	/*
 	 * Proactively drop any delayed allocations in COW fork, the fsverity
 	 * files are read-only
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 16/20] xfs: add fs-verity ioctls
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (14 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 15/20] xfs: remove unwritten extents after preallocations in fsverity metadata Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 17/20] xfs: advertise fs-verity being available on filesystem Andrey Albershteyn
                   ` (4 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

Add fs-verity ioctls to enable, dump metadata (descriptor and Merkle
tree pages) and obtain file's digest.

[djwong: remove unnecessary casting]

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/xfs_ioctl.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index 1b53701bebea..fc7860a8b5ab 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;
 	}
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 17/20] xfs: advertise fs-verity being available on filesystem
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (15 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 16/20] xfs: add fs-verity ioctls Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 18/20] xfs: check and repair the verity inode flag state Andrey Albershteyn
                   ` (3 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Darrick J. Wong, hch, linux-ext4, linux-f2fs-devel, linux-btrfs,
	Andrey Albershteyn, Andrey Albershteyn

From: "Darrick J. Wong" <djwong@kernel.org>

Advertise that this filesystem supports fsverity.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Andrey Albershteyn <aalbersh@redhat.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/libxfs/xfs_fs.h | 1 +
 fs/xfs/libxfs/xfs_sb.c | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/fs/xfs/libxfs/xfs_fs.h b/fs/xfs/libxfs/xfs_fs.h
index 185f09f327c0..c80133784419 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.
diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c
index a15510ebd2f1..222bbe5559df 100644
--- a/fs/xfs/libxfs/xfs_sb.c
+++ b/fs/xfs/libxfs/xfs_sb.c
@@ -1590,6 +1590,8 @@ xfs_fs_geometry(
 		geo->flags |= XFS_FSOP_GEOM_FLAGS_METADIR;
 	if (xfs_has_zoned(mp))
 		geo->flags |= XFS_FSOP_GEOM_FLAGS_ZONED;
+	if (xfs_has_verity(mp))
+		geo->flags |= XFS_FSOP_GEOM_FLAGS_VERITY;
 	geo->rtsectsize = sbp->sb_blocksize;
 	geo->dirblocksize = xfs_dir2_dirblock_bytes(sbp);
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 18/20] xfs: check and repair the verity inode flag state
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (16 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 17/20] xfs: advertise fs-verity being available on filesystem Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 19/20] xfs: introduce health state for corrupted fsverity metadata Andrey Albershteyn
                   ` (2 subsequent siblings)
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Darrick J. Wong, hch, linux-ext4, linux-f2fs-devel, linux-btrfs,
	Andrey Albershteyn

From: "Darrick J. Wong" <djwong@kernel.org>

If an inode has the incore verity iflag set, make sure that we can
actually activate fsverity on that inode.  If activation fails due to
a fsverity metadata validation error, clear the flag.  The usage model
for fsverity requires that any program that cares about verity state is
required to call statx/getflags to check that the flag is set after
opening the file, so clearing the flag will not compromise that model.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/scrub/common.c       | 53 +++++++++++++++++++++++++++++++++++++
 fs/xfs/scrub/common.h       |  2 ++
 fs/xfs/scrub/inode.c        |  7 +++++
 fs/xfs/scrub/inode_repair.c | 36 +++++++++++++++++++++++++
 4 files changed, 98 insertions(+)

diff --git a/fs/xfs/scrub/common.c b/fs/xfs/scrub/common.c
index e5233e31abb7..1673b522244d 100644
--- a/fs/xfs/scrub/common.c
+++ b/fs/xfs/scrub/common.c
@@ -45,6 +45,8 @@
 #include "scrub/health.h"
 #include "scrub/tempfile.h"
 
+#include <linux/fsverity.h>
+
 /* Common code for the metadata scrubbers. */
 
 /*
@@ -1754,3 +1756,54 @@ xchk_inode_count_blocks(
 	return xfs_bmap_count_blocks(sc->tp, sc->ip, whichfork, nextents,
 			count);
 }
+
+/*
+ * If this inode has S_VERITY set on it, read the verity info. If the reading
+ * fails with anything other than ENOMEM, the file is corrupt, which we can
+ * detect later with fsverity_active.
+ *
+ * Callers must hold the IOLOCK and must not hold the ILOCK of sc->ip because
+ * activation reads inode data.
+ */
+int
+xchk_inode_setup_verity(
+	struct xfs_scrub	*sc)
+{
+	int			error;
+
+	if (!fsverity_active(VFS_I(sc->ip)))
+		return 0;
+
+	error = fsverity_ensure_verity_info(VFS_I(sc->ip));
+	switch (error) {
+	case 0:
+		/* fsverity is active */
+		break;
+	case -ENODATA:
+	case -EMSGSIZE:
+	case -EINVAL:
+	case -EFSCORRUPTED:
+	case -EFBIG:
+		/*
+		 * The nonzero errno codes above are the error codes that can
+		 * be returned from fsverity on metadata validation errors.
+		 */
+		return 0;
+	default:
+		/* runtime errors */
+		return error;
+	}
+
+	return 0;
+}
+
+/*
+ * Is this a verity file that failed to activate?  Callers must have tried to
+ * activate fsverity via xchk_inode_setup_verity.
+ */
+bool
+xchk_inode_verity_broken(
+	struct xfs_inode	*ip)
+{
+	return fsverity_active(VFS_I(ip)) && !fsverity_get_info(VFS_I(ip));
+}
diff --git a/fs/xfs/scrub/common.h b/fs/xfs/scrub/common.h
index 9d627fd50687..3de2b6009a99 100644
--- a/fs/xfs/scrub/common.h
+++ b/fs/xfs/scrub/common.h
@@ -268,6 +268,8 @@ int xchk_inode_is_allocated(struct xfs_scrub *sc, xfs_agino_t agino,
 		bool *inuse);
 int xchk_inode_count_blocks(struct xfs_scrub *sc, int whichfork,
 		xfs_extnum_t *nextents, xfs_filblks_t *count);
+int xchk_inode_setup_verity(struct xfs_scrub *sc);
+bool xchk_inode_verity_broken(struct xfs_inode *ip);
 
 bool xchk_inode_is_dirtree_root(const struct xfs_inode *ip);
 bool xchk_inode_is_sb_rooted(const struct xfs_inode *ip);
diff --git a/fs/xfs/scrub/inode.c b/fs/xfs/scrub/inode.c
index 65b13e311916..d1cdd6b445d0 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 493dcf5cc6c1..90f861bb4ef8 100644
--- a/fs/xfs/scrub/inode_repair.c
+++ b/fs/xfs/scrub/inode_repair.c
@@ -573,6 +573,8 @@ xrep_dinode_flags(
 		dip->di_nrext64_pad = 0;
 	else if (dip->di_version >= 3)
 		dip->di_v3_pad = 0;
+	if (!xfs_has_verity(mp) || !S_ISREG(mode))
+		flags2 &= ~XFS_DIFLAG2_VERITY;
 
 	if (flags2 & XFS_DIFLAG2_METADATA) {
 		xfs_failaddr_t	fa;
@@ -1617,6 +1619,10 @@ xrep_dinode_core(
 	if (iget_error)
 		return iget_error;
 
+	error = xchk_inode_setup_verity(sc);
+	if (error)
+		return error;
+
 	error = xchk_trans_alloc(sc, 0);
 	if (error)
 		return error;
@@ -2035,6 +2041,27 @@ xrep_inode_unlinked(
 	return 0;
 }
 
+/*
+ * If this file is a fsverity file, xchk_prepare_iscrub or xrep_dinode_core
+ * should have activated it.  If it's still not active, then there's something
+ * wrong with the verity descriptor and we should turn it off.
+ */
+STATIC int
+xrep_inode_verity(
+	struct xfs_scrub	*sc)
+{
+	struct inode		*inode = VFS_I(sc->ip);
+
+	if (xchk_inode_verity_broken(sc->ip)) {
+		sc->ip->i_diflags2 &= ~XFS_DIFLAG2_VERITY;
+		inode->i_flags &= ~S_VERITY;
+
+		xfs_trans_log_inode(sc->tp, sc->ip, XFS_ILOG_CORE);
+	}
+
+	return 0;
+}
+
 /* Repair an inode's fields. */
 int
 xrep_inode(
@@ -2084,6 +2111,15 @@ xrep_inode(
 			return error;
 	}
 
+	/*
+	 * Disable fsverity if it cannot be activated.  Activation failure
+	 * prohibits the file from being opened, so there cannot be another
+	 * program with an open fd to what it thinks is a verity file.
+	 */
+	error = xrep_inode_verity(sc);
+	if (error)
+		return error;
+
 	/* Reconnect incore unlinked list */
 	error = xrep_inode_unlinked(sc);
 	if (error)
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 19/20] xfs: introduce health state for corrupted fsverity metadata
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (17 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 18/20] xfs: check and repair the verity inode flag state Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10  8:52 ` [PATCH v11 20/20] xfs: enable ro-compat fs-verity flag Andrey Albershteyn
  2026-07-10 10:38 ` [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

Report corrupted fsverity descriptor through health system.

Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/libxfs/xfs_fs.h     |  1 +
 fs/xfs/libxfs/xfs_health.h |  4 +++-
 fs/xfs/xfs_fsverity.c      | 13 ++++++++++---
 fs/xfs/xfs_health.c        |  1 +
 4 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/fs/xfs/libxfs/xfs_fs.h b/fs/xfs/libxfs/xfs_fs.h
index c80133784419..84d62b7506a9 100644
--- a/fs/xfs/libxfs/xfs_fs.h
+++ b/fs/xfs/libxfs/xfs_fs.h
@@ -422,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 1d45cf5789e8..932b447190da 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/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c
index 31faa95c396d..9732811a1444 100644
--- a/fs/xfs/xfs_fsverity.c
+++ b/fs/xfs/xfs_fsverity.c
@@ -84,16 +84,23 @@ xfs_fsverity_get_descriptor(
 		return error;
 
 	desc_size = be32_to_cpu(d_desc_size);
-	if (XFS_IS_CORRUPT(mp, desc_size > FS_VERITY_MAX_DESCRIPTOR_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))
+	}
+
+	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))
+	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)) {
diff --git a/fs/xfs/xfs_health.c b/fs/xfs/xfs_health.c
index 239b843e83d4..be66760fb120 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. */
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v11 20/20] xfs: enable ro-compat fs-verity flag
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (18 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 19/20] xfs: introduce health state for corrupted fsverity metadata Andrey Albershteyn
@ 2026-07-10  8:52 ` Andrey Albershteyn
  2026-07-10 10:38 ` [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10  8:52 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: Andrey Albershteyn, hch, linux-ext4, linux-f2fs-devel,
	linux-btrfs, djwong

Finalize fs-verity integration in XFS by making kernel fs-verity
aware with ro-compat flag.

Reviewed-by: Darrick J. Wong <djwong@kernel.org>
[djwong: add spaces]
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
 fs/xfs/libxfs/xfs_format.h | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h
index 7b0511eb03d0..2878bc9cb656 100644
--- a/fs/xfs/libxfs/xfs_format.h
+++ b/fs/xfs/libxfs/xfs_format.h
@@ -378,8 +378,9 @@ xfs_sb_has_compat_feature(
 #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(
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree
  2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
                   ` (19 preceding siblings ...)
  2026-07-10  8:52 ` [PATCH v11 20/20] xfs: enable ro-compat fs-verity flag Andrey Albershteyn
@ 2026-07-10 10:38 ` Andrey Albershteyn
  20 siblings, 0 replies; 22+ messages in thread
From: Andrey Albershteyn @ 2026-07-10 10:38 UTC (permalink / raw)
  To: linux-xfs, fsverity, linux-fsdevel, ebiggers
  Cc: hch, linux-ext4, linux-f2fs-devel, linux-btrfs, djwong,
	linux-unionfs, david

I will go through the new round of sashiko.dev comments and check if
there's more fixes to be done (looks like double unlock is valid
one).

-- 
- Andrey

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2026-07-10 10:39 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10  8:52 [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 01/20] fsverity: report validation errors through fserror to fsnotify Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 02/20] fsverity: expose ensure_fsverity_info() Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 03/20] fsverity: pass digest size and hash of the all-zeroes block to ->write Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 04/20] fsverity: hoist pagecache_read from f2fs/ext4 to fsverity Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 05/20] fsverity: improve flushing performance of fsverity_fill_zerohash Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 06/20] fsverity: don't allow setting DAX file attribute on fsverity files Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 07/20] xfs: introduce fsverity on-disk changes Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 08/20] xfs: initialize fs-verity on file open Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 09/20] xfs: don't allow to enable DAX on fs-verity sealed inode Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 10/20] xfs: don't report dio_mem_align and dio_offset_align for fsverity files Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 11/20] xfs: disable direct read path for fs-verity files Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 12/20] xfs: handle fsverity I/O in write/read path Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 13/20] xfs: use read ioend for fsverity data verification Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 14/20] xfs: add fs-verity support Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 15/20] xfs: remove unwritten extents after preallocations in fsverity metadata Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 16/20] xfs: add fs-verity ioctls Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 17/20] xfs: advertise fs-verity being available on filesystem Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 18/20] xfs: check and repair the verity inode flag state Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 19/20] xfs: introduce health state for corrupted fsverity metadata Andrey Albershteyn
2026-07-10  8:52 ` [PATCH v11 20/20] xfs: enable ro-compat fs-verity flag Andrey Albershteyn
2026-07-10 10:38 ` [PATCH v11 00/20] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox