* [PATCH v18 01/15] xfs: introduce fsverity on-disk changes
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 15:08 ` sashiko-bot
2026-11-02 14:41 ` [PATCH v18 02/15] xfs: don't allow to enable DAX on fs-verity sealed inode Andrey Albershteyn
` (13 subsequent siblings)
14 siblings, 1 reply; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david, Darrick J. Wong
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 469e879149ac..e1f50ad18f0b 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 | \
@@ -1234,16 +1235,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)
{
@@ -2031,4 +2037,26 @@ struct xfs_acl {
#define SGI_ACL_FILE_SIZE (sizeof(SGI_ACL_FILE)-1)
#define SGI_ACL_DEFAULT_SIZE (sizeof(SGI_ACL_DEFAULT)-1)
+/*
+ * At maximum of 8 levels with 128 hashes per block (32 bytes SHA-256) maximum
+ * tree size is ((128^8 − 1)/(128 − 1)) = 567*10^12 blocks. This should fit in
+ * 53 bits address space.
+ *
+ * At this Merkle tree size we can cover 295EB large file. This is much larger
+ * than the currently supported file size.
+ *
+ * For sha512 the largest file we can cover ends at 1 << 50 offset, this is also
+ * good.
+ */
+#define XFS_FSVERITY_LARGEST_FILE ((loff_t)1ULL << 53)
+
+/*
+ * Alignment of the fsverity metadata placement. This is largest supported PAGE
+ * SIZE for fsverity. This is used to space out data and metadata in page cache.
+ * The spacing is necessary for non-exposure of metadata to userspace and
+ * correct merkle tree synthesis in the iomap.
+ */
+#define XFS_FSVERITY_START_ALIGN (65536)
+
+
#endif /* __XFS_FORMAT_H__ */
diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c
index 8c9d296ebfe9..13042e1eaf35 100644
--- a/fs/xfs/libxfs/xfs_inode_buf.c
+++ b/fs/xfs/libxfs/xfs_inode_buf.c
@@ -758,6 +758,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 f486b23a9b52..a717b19558af 100644
--- a/fs/xfs/libxfs/xfs_inode_util.c
+++ b/fs/xfs/libxfs/xfs_inode_util.c
@@ -73,7 +73,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;
@@ -125,6 +126,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 ce44123c98ce..7629fc3f499f 100644
--- a/fs/xfs/libxfs/xfs_sb.c
+++ b/fs/xfs/libxfs/xfs_sb.c
@@ -164,6 +164,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 d307ec97fac8..3f8b8f79a4db 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -1415,12 +1415,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 cbcfb89c67a4..700964048587 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -403,6 +403,7 @@ typedef struct xfs_mount {
#define XFS_FEAT_EXCHANGE_RANGE (1ULL << 27) /* exchange range */
#define XFS_FEAT_METADIR (1ULL << 28) /* metadata directory tree */
#define XFS_FEAT_ZONED (1ULL << 29) /* zoned RT device */
+#define XFS_FEAT_VERITY (1ULL << 30) /* fs-verity */
/* Mount features */
#define XFS_FEAT_NOLIFETIME (1ULL << 47) /* disable lifetime hints */
@@ -460,6 +461,7 @@ __XFS_HAS_FEAT(exchange_range, EXCHANGE_RANGE)
__XFS_HAS_FEAT(metadir, METADIR)
__XFS_HAS_FEAT(zoned, ZONED)
__XFS_HAS_FEAT(nolifetime, NOLIFETIME)
+__XFS_HAS_FEAT(verity, VERITY)
static inline bool xfs_has_rtgroups(const struct xfs_mount *mp)
{
--
2.54.0
^ permalink raw reply related [flat|nested] 42+ messages in thread* Re: [PATCH v18 01/15] xfs: introduce fsverity on-disk changes
2026-11-02 14:41 ` [PATCH v18 01/15] xfs: introduce fsverity on-disk changes Andrey Albershteyn
@ 2026-11-02 15:08 ` sashiko-bot
0 siblings, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-11-02 15:08 UTC (permalink / raw)
To: Andrey Albershteyn, ebiggers, hch, Carlos Maiolino, fsverity
Cc: david, Darrick J. Wong
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] xfs: FS_IOC_SETFLAGS fails with EOPNOTSUPP on verity files in xfs_fileattr_set()
--
commit 28c38f60096ab4965845dc0881e604defd26203f
Author: Andrey Albershteyn <aalbersh@kernel.org>
xfs: introduce fsverity on-disk changes
This commit introduces the on-disk XFS_DIFLAG2_VERITY inode flag and the
XFS_SB_FEAT_RO_COMPAT_VERITY superblock feature bit for fs-verity support.
It also wires up inode verification and flag conversions between on-disk
diflags2, extended attributes, and VFS inode flags.
[ ... ]
> diff --git a/fs/xfs/libxfs/xfs_inode_util.c b/fs/xfs/libxfs/xfs_inode_util.c
> index f486b23a9b523..a717b19558afa 100644
> --- a/fs/xfs/libxfs/xfs_inode_util.c
> +++ b/fs/xfs/libxfs/xfs_inode_util.c
> @@ -73,7 +73,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;
> @@ -125,6 +126,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;
[Severity: Medium]
When FS_XFLAG_VERITY is returned by xfs_ip2xflags(), will a read-modify-write
cycle using FS_IOC_GETFLAGS and FS_IOC_SETFLAGS (such as chattr toggling
FS_IMMUTABLE_FL or FS_NOATIME_FL) on a verity-enabled inode fail with
-EOPNOTSUPP?
During FS_IOC_GETFLAGS on an inode with XFS_DIFLAG2_VERITY set,
vfs_fileattr_get() -> xfs_fileattr_get() -> xfs_fill_fsxattr() ->
fileattr_fill_xflags() translates FS_XFLAG_VERITY into FS_VERITY_FL in
fa->flags.
When userspace modifies another flag and passes the flags back via
FS_IOC_SETFLAGS, ioctl_setflags() calls fileattr_fill_flags(), which leaves
fa->fsx_valid as false while preserving FS_VERITY_FL in fa->flags. In
fs/xfs/xfs_ioctl.c:xfs_fileattr_set():
if (!fa->fsx_valid) {
unsigned int allowed = FS_IMMUTABLE_FL | FS_APPEND_FL |
FS_NOATIME_FL | FS_NODUMP_FL |
FS_SYNC_FL | FS_DAX_FL |
FS_PROJINHERIT_FL;
if (xfs_has_asciici(mp))
allowed |= FS_CASEFOLD_FL;
if (fa->flags & ~allowed)
return -EOPNOTSUPP;
}
Should xfs_fileattr_set() allow FS_VERITY_FL in allowed when
ip->i_diflags2 & XFS_DIFLAG2_VERITY is set, similar to how FS_CASEFOLD_FL is
handled right above it?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144147.2393369-1-aalbersh@kernel.org?part=1
^ permalink raw reply [flat|nested] 42+ messages in thread
* [PATCH v18 02/15] xfs: don't allow to enable DAX on fs-verity sealed inode
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
2026-11-02 14:41 ` [PATCH v18 01/15] xfs: introduce fsverity on-disk changes Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 14:41 ` [PATCH v18 03/15] xfs: disable direct read path for fs-verity files Andrey Albershteyn
` (12 subsequent siblings)
14 siblings, 0 replies; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david, Darrick J. Wong
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 3f8b8f79a4db..b25a0c2fbd2a 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -1387,6 +1387,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] 42+ messages in thread* [PATCH v18 03/15] xfs: disable direct read path for fs-verity files
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
2026-11-02 14:41 ` [PATCH v18 01/15] xfs: introduce fsverity on-disk changes Andrey Albershteyn
2026-11-02 14:41 ` [PATCH v18 02/15] xfs: don't allow to enable DAX on fs-verity sealed inode Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 14:41 ` [PATCH v18 04/15] xfs: don't report dio_mem_align and dio_offset_align for fsverity files Andrey Albershteyn
` (11 subsequent siblings)
14 siblings, 0 replies; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david, Darrick J. Wong
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>
Reviewed-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/xfs_file.c | 61 +++++++++++++++++++++++++++++++----------------
1 file changed, 41 insertions(+), 20 deletions(-)
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index 7d0c8a197575..f0d000272521 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -38,6 +38,7 @@
#include <linux/mount.h>
#include <linux/filelock.h>
#include <linux/bio-integrity.h>
+#include <linux/fsverity.h>
static const struct vm_operations_struct xfs_file_vm_ops;
@@ -231,6 +232,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,
@@ -249,6 +269,17 @@ xfs_file_dio_read(
ret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED);
if (ret)
return ret;
+
+ /*
+ * Re-check verity status after acquiring lock. This prevents TOCTOU in
+ * xfs_file_read_iter() while falling back from DIO to buffered I/O as
+ * now we are holding a lock
+ */
+ if (fsverity_active(VFS_I(ip))) {
+ xfs_iunlock(ip, XFS_IOLOCK_SHARED);
+ iocb->ki_flags &= ~IOCB_DIRECT;
+ return xfs_file_buffered_read(iocb, to);
+ }
if (mapping_stable_writes(iocb->ki_filp->f_mapping)) {
ret = iomap_dio_rw(iocb, to, &xfs_read_iomap_ops,
&xfs_dio_read_bounce_ops, 0, NULL, 0);
@@ -268,7 +299,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);
@@ -286,25 +318,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,
@@ -319,6 +332,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] 42+ messages in thread* [PATCH v18 04/15] xfs: don't report dio_mem_align and dio_offset_align for fsverity files
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
` (2 preceding siblings ...)
2026-11-02 14:41 ` [PATCH v18 03/15] xfs: disable direct read path for fs-verity files Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-03 9:23 ` Christoph Hellwig
2026-11-02 14:41 ` [PATCH v18 05/15] xfs: handle fsverity I/O in write/read path Andrey Albershteyn
` (10 subsequent siblings)
14 siblings, 1 reply; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david, Darrick J. Wong
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.
The STATX_DIOALIGN | STATX_DIO_READ_ALIGN flags are used to explicitly
report if DIO is supported or not. They can not indicate that fallback
to the buffered IO is used in this case. The zero alignment values also
mean that DIO is not supported on this file, see statx(2).
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
Acked-by: Eric Biggers <ebiggers@kernel.org>
Reviewed-by: "Darrick J. Wong" <djwong@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 b25a0c2fbd2a..150bbd705807 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -35,6 +35,7 @@
#include <linux/security.h>
#include <linux/iversion.h>
#include <linux/fiemap.h>
+#include <linux/fsverity.h>
/*
* Directories have different lock order w.r.t. mmap_lock compared to regular
@@ -579,6 +580,10 @@ xfs_report_dioalign(
struct block_device *bdev = target->bt_bdev;
stat->result_mask |= STATX_DIOALIGN | STATX_DIO_READ_ALIGN;
+
+ if (fsverity_active(VFS_I(ip)))
+ return;
+
stat->dio_mem_align = bdev_dma_alignment(bdev) + 1;
/*
--
2.54.0
^ permalink raw reply related [flat|nested] 42+ messages in thread* Re: [PATCH v18 04/15] xfs: don't report dio_mem_align and dio_offset_align for fsverity files
2026-11-02 14:41 ` [PATCH v18 04/15] xfs: don't report dio_mem_align and dio_offset_align for fsverity files Andrey Albershteyn
@ 2026-11-03 9:23 ` Christoph Hellwig
0 siblings, 0 replies; 42+ messages in thread
From: Christoph Hellwig @ 2026-11-03 9:23 UTC (permalink / raw)
To: Andrey Albershteyn
Cc: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs,
linux-fsdevel, david, Darrick J. Wong
On Mon, Nov 02, 2026 at 03:41:34PM +0100, Andrey Albershteyn wrote:
> struct block_device *bdev = target->bt_bdev;
>
> stat->result_mask |= STATX_DIOALIGN | STATX_DIO_READ_ALIGN;
> +
> + if (fsverity_active(VFS_I(ip)))
> + return;
> +
A comment based on the commit message on why filling out the comments
here is fine would probably be helpful.
Otherwise looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 42+ messages in thread
* [PATCH v18 05/15] xfs: handle fsverity I/O in write/read path
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
` (3 preceding siblings ...)
2026-11-02 14:41 ` [PATCH v18 04/15] xfs: don't report dio_mem_align and dio_offset_align for fsverity files Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 15:13 ` sashiko-bot
2026-11-03 9:37 ` Christoph Hellwig
2026-11-02 14:41 ` [PATCH v18 06/15] xfs: use read ioend for fsverity data verification Andrey Albershteyn
` (9 subsequent siblings)
14 siblings, 2 replies; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david, Darrick J. Wong
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>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/Makefile | 1 +
fs/xfs/libxfs/xfs_bmap.c | 8 ++++++++
fs/xfs/libxfs/xfs_verity.c | 20 +++++++++++++++++++
fs/xfs/libxfs/xfs_verity.h | 12 +++++++++++
fs/xfs/xfs_aops.c | 36 ++++++++++++++++++++++++++++-----
fs/xfs/xfs_inode.h | 6 ++++++
fs/xfs/xfs_iomap.c | 41 ++++++++++++++++++++++++++++----------
fs/xfs/xfs_iomap.h | 5 +++--
8 files changed, 111 insertions(+), 18 deletions(-)
create mode 100644 fs/xfs/libxfs/xfs_verity.c
create mode 100644 fs/xfs/libxfs/xfs_verity.h
diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
index 399a207f2d0e..cb7f71457ee9 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -59,6 +59,7 @@ xfs-y += $(addprefix libxfs/, \
xfs_trans_resv.o \
xfs_trans_space.o \
xfs_types.o \
+ xfs_verity.o \
)
# xfs_rtbitmap is shared with libxfs
xfs-$(CONFIG_XFS_RT) += $(addprefix libxfs/, \
diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c
index ca49bf4a50c5..a35a44cf8c63 100644
--- a/fs/xfs/libxfs/xfs_bmap.c
+++ b/fs/xfs/libxfs/xfs_bmap.c
@@ -41,6 +41,7 @@
#include "xfs_inode_util.h"
#include "xfs_rtgroup.h"
#include "xfs_zone_alloc.h"
+#include "xfs_verity.h"
struct kmem_cache *xfs_bmap_intent_cache;
@@ -4404,6 +4405,9 @@ xfs_bmapi_convert_one_delalloc(
* the extent. Just return the real extent at this offset.
*/
if (!isnullstartblock(bma.got.br_startblock)) {
+ if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) &&
+ offset >= xfs_fsverity_metadata_offset(ip))
+ flags |= IOMAP_F_FSVERITY;
xfs_bmbt_to_iomap(ip, iomap, &bma.got, 0, flags,
xfs_iomap_inode_sequence(ip, flags));
if (seq)
@@ -4451,6 +4455,10 @@ xfs_bmapi_convert_one_delalloc(
XFS_STATS_ADD(mp, xs_xstrat_bytes, XFS_FSB_TO_B(mp, bma.length));
XFS_STATS_INC(mp, xs_xstrat_quick);
+ if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) &&
+ offset >= xfs_fsverity_metadata_offset(ip))
+ flags |= IOMAP_F_FSVERITY;
+
ASSERT(!isnullstartblock(bma.got.br_startblock));
xfs_bmbt_to_iomap(ip, iomap, &bma.got, 0, flags,
xfs_iomap_inode_sequence(ip, flags));
diff --git a/fs/xfs/libxfs/xfs_verity.c b/fs/xfs/libxfs/xfs_verity.c
new file mode 100644
index 000000000000..c1731b89a5a1
--- /dev/null
+++ b/fs/xfs/libxfs/xfs_verity.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ */
+#include "xfs_platform.h"
+#include "xfs_fs.h"
+#include "xfs_shared.h"
+#include "xfs_format.h"
+#include "xfs_log_format.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_inode.h"
+#include "xfs_verity.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/libxfs/xfs_verity.h b/fs/xfs/libxfs/xfs_verity.h
new file mode 100644
index 000000000000..db89d557edcd
--- /dev/null
+++ b/fs/xfs/libxfs/xfs_verity.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ */
+#ifndef __XFS_VERITY_H__
+#define __XFS_VERITY_H__
+
+#include "xfs_platform.h"
+
+loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip);
+
+#endif /* __XFS_VERITY_H__ */
diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
index c30e688cfc9f..19640e4e3ed1 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -23,6 +23,7 @@
#include "xfs_ioend.h"
#include "xfs_zone_alloc.h"
#include "xfs_rtgroup.h"
+#include "xfs_fsverity.h"
struct xfs_writepage_ctx {
struct iomap_writepage_ctx ctx;
@@ -172,12 +173,16 @@ xfs_map_blocks(
int retries = 0;
int error = 0;
unsigned int *seq;
+ unsigned int iomap_flags = 0;
if (xfs_is_shutdown(mp))
return -EIO;
XFS_ERRORTAG_DELAY(mp, XFS_ERRTAG_WB_DELAY_MS);
+ if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+ iomap_flags |= IOMAP_F_FSVERITY;
+
/*
* COW fork blocks can overlap data fork blocks even if the blocks
* aren't shared. COW I/O always takes precedent, so we must always
@@ -265,7 +270,8 @@ xfs_map_blocks(
isnullstartblock(imap.br_startblock))
goto allocate_blocks;
- xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, 0, XFS_WPC(wpc)->data_seq);
+ xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, iomap_flags,
+ XFS_WPC(wpc)->data_seq);
trace_xfs_map_blocks_found(ip, offset, count, whichfork, &imap);
return 0;
allocate_blocks:
@@ -412,12 +418,16 @@ xfs_zoned_map_blocks(
xfs_filblks_t count_fsb;
struct xfs_bmbt_irec imap, del;
struct xfs_iext_cursor icur;
+ u16 iomap_flags = 0;
if (xfs_is_shutdown(mp))
return -EIO;
XFS_ERRORTAG_DELAY(mp, XFS_ERRTAG_WB_DELAY_MS);
+ if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+ iomap_flags |= IOMAP_F_FSVERITY;
+
/*
* All dirty data must be covered by delalloc extents. But truncate can
* remove delalloc extents underneath us or reduce their size.
@@ -441,7 +451,7 @@ xfs_zoned_map_blocks(
imap.br_startblock = HOLESTARTBLOCK;
imap.br_state = XFS_EXT_NORM;
xfs_iunlock(ip, XFS_ILOCK_EXCL);
- xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, 0, 0);
+ xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, iomap_flags, 0);
return 0;
}
end_fsb = min(end_fsb, imap.br_startoff + imap.br_blockcount);
@@ -454,7 +464,7 @@ xfs_zoned_map_blocks(
xfs_iunlock(ip, XFS_ILOCK_EXCL);
xfs_iomap_set_anon_write(ip, &wpc->iomap, offset,
- XFS_FSB_TO_B(mp, count_fsb));
+ XFS_FSB_TO_B(mp, count_fsb), iomap_flags);
trace_xfs_zoned_map_blocks(ip, offset, wpc->iomap.length);
return 0;
}
@@ -507,6 +517,22 @@ static const struct iomap_writeback_ops xfs_zoned_writeback_ops = {
.writeback_submit = xfs_zoned_writeback_submit,
};
+static int
+xfs_iomap_writepages(
+ struct xfs_inode *ip,
+ struct iomap_writepage_ctx *ctx)
+{
+ /*
+ * Writeback does not work for folios past EOF, let it know that
+ * I/O happens for fsverity metadata and this restriction need
+ * to be skipped
+ */
+ if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+ ctx->iomap.flags |= IOMAP_F_FSVERITY;
+
+ return iomap_writepages(ctx);
+}
+
STATIC int
xfs_vm_writepages(
struct address_space *mapping,
@@ -526,7 +552,7 @@ xfs_vm_writepages(
};
int error;
- error = iomap_writepages(&xc.ctx);
+ error = xfs_iomap_writepages(ip, &xc.ctx);
if (xc.open_zone)
xfs_open_zone_put(xc.open_zone);
return error;
@@ -539,7 +565,7 @@ xfs_vm_writepages(
},
};
- return iomap_writepages(&wpc.ctx);
+ return xfs_iomap_writepages(ip, &wpc.ctx);
}
}
diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h
index 75eff68c1e39..034d3f6072a9 100644
--- a/fs/xfs/xfs_inode.h
+++ b/fs/xfs/xfs_inode.h
@@ -428,6 +428,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 329c745e3120..68fef6576442 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_verity.h"
+#include <linux/fsverity.h>
#define XFS_ALLOC_ALIGN(mp, off) \
(((off) >> mp->m_allocsize_log) << mp->m_allocsize_log)
@@ -883,6 +885,9 @@ xfs_direct_write_iomap_begin(
if (flags & IOMAP_ATOMIC)
iomap_flags |= IOMAP_F_ATOMIC_BIO;
+ if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+ iomap_flags |= IOMAP_F_FSVERITY;
+
/*
* COW writes may allocate delalloc space or convert unwritten COW
* extents, so we need to make sure to take the lock exclusively here.
@@ -1023,7 +1028,8 @@ xfs_direct_write_iomap_begin(
trace_xfs_iomap_found(ip, offset, length - offset, XFS_COW_FORK, &cmap);
if (imap.br_startblock != HOLESTARTBLOCK) {
seq = xfs_iomap_inode_sequence(ip, 0);
- error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags, 0, seq);
+ error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags,
+ iomap_flags & IOMAP_F_FSVERITY, seq);
if (error)
goto out_unlock;
}
@@ -1083,7 +1089,7 @@ xfs_zoned_direct_write_iomap_begin(
return error;
}
- xfs_iomap_set_anon_write(ip, iomap, offset, length);
+ xfs_iomap_set_anon_write(ip, iomap, offset, length, 0);
return 0;
}
@@ -1594,7 +1600,8 @@ xfs_zoned_fill_srcmap(
xfs_fileoff_t offset_fsb,
xfs_fileoff_t *end_fsb,
unsigned flags,
- struct iomap *srcmap)
+ struct iomap *srcmap,
+ u16 iomap_flags)
{
struct xfs_bmbt_irec smap;
struct xfs_iext_cursor scur;
@@ -1615,7 +1622,7 @@ xfs_zoned_fill_srcmap(
*/
xfs_trim_extent(&smap, offset_fsb, *end_fsb - offset_fsb);
*end_fsb = min(*end_fsb, smap.br_startoff + smap.br_blockcount);
- return xfs_bmbt_to_iomap(ip, srcmap, &smap, flags, 0,
+ return xfs_bmbt_to_iomap(ip, srcmap, &smap, flags, iomap_flags,
xfs_iomap_inode_sequence(ip, 0));
}
@@ -1626,7 +1633,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);
@@ -1636,7 +1644,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;
@@ -1687,7 +1694,7 @@ xfs_zoned_buffered_write_iomap_begin(
!IS_ALIGNED(offset + count, mp->m_sb.sb_blocksize) ||
(flags & IOMAP_ZERO)) {
error = xfs_zoned_fill_srcmap(ip, offset_fsb, &end_fsb, flags,
- srcmap);
+ srcmap, iomap_flags);
if (error)
goto out_unlock;
}
@@ -1695,6 +1702,8 @@ xfs_zoned_buffered_write_iomap_begin(
if (!ip->i_cowfp)
xfs_ifork_init_cow(ip);
+ iomap_flags |= IOMAP_F_SHARED;
+
if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &got))
got.br_startoff = end_fsb;
if (got.br_startoff <= offset_fsb) {
@@ -1826,9 +1835,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))
@@ -2112,7 +2124,8 @@ xfs_buffered_write_iomap_begin(
found_cow:
if (imap.br_startoff <= offset_fsb) {
- error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags, 0,
+ error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags,
+ iomap_flags & IOMAP_F_FSVERITY,
xfs_iomap_inode_sequence(ip, 0));
if (error)
goto out_unlock;
@@ -2216,12 +2229,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;
@@ -2235,8 +2253,9 @@ xfs_read_iomap_begin(
if (error)
return error;
trace_xfs_iomap_found(ip, offset, length, XFS_DATA_FORK, &imap);
- return xfs_bmbt_to_iomap(ip, iomap, &imap, flags,
- shared ? IOMAP_F_SHARED : 0, seq);
+ iomap_flags |= shared ? IOMAP_F_SHARED : 0;
+
+ return xfs_bmbt_to_iomap(ip, iomap, &imap, flags, iomap_flags, seq);
}
static DEFINE_IOMAP_ITER_NEXT(xfs_read_iomap_next, xfs_read_iomap_begin);
diff --git a/fs/xfs/xfs_iomap.h b/fs/xfs/xfs_iomap.h
index f2520a9b3a13..d3ca0f2afc9f 100644
--- a/fs/xfs/xfs_iomap.h
+++ b/fs/xfs/xfs_iomap.h
@@ -34,13 +34,14 @@ xfs_iomap_set_anon_write(
struct xfs_inode *ip,
struct iomap *iomap,
loff_t offset,
- loff_t length)
+ loff_t length,
+ u16 iomap_flags)
{
iomap->type = IOMAP_MAPPED;
iomap->bdev = ip->i_mount->m_rtdev_targp->bt_bdev;
iomap->offset = offset;
iomap->length = length;
- iomap->flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY;
+ iomap->flags = iomap_flags | IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY;
if (bdev_has_integrity_csum(iomap->bdev))
iomap->flags |= IOMAP_F_INTEGRITY;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 42+ messages in thread* Re: [PATCH v18 05/15] xfs: handle fsverity I/O in write/read path
2026-11-02 14:41 ` [PATCH v18 05/15] xfs: handle fsverity I/O in write/read path Andrey Albershteyn
@ 2026-11-02 15:13 ` sashiko-bot
2026-11-03 9:37 ` Christoph Hellwig
1 sibling, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-11-02 15:13 UTC (permalink / raw)
To: Andrey Albershteyn, ebiggers, hch, Carlos Maiolino, fsverity
Cc: david, Darrick J. Wong
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] xfs: missing IOMAP_F_FSVERITY on CoW iomap in xfs_direct_write_iomap_begin()
- [High] xfs: race in xfs_iomap_writepages() drops dirty fsverity folios past EOF
- [Low] xfs: inclusion of missing header xfs_fsverity.h in xfs_aops.c breaks build
--
commit be21020c05928b16d2e0448f7473e28373912042
Author: Andrey Albershteyn <aalbersh@kernel.org>
xfs: handle fsverity I/O in write/read path
This commit updates the XFS write, writeback, and read paths to set
IOMAP_F_FSVERITY when handling fsverity metadata, and introduces the
XFS_VERITY_CONSTRUCTION inode flag for Merkle tree construction.
> diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
> index c30e688cfc9f6..19640e4e3ed12 100644
> --- a/fs/xfs/xfs_aops.c
> +++ b/fs/xfs/xfs_aops.c
> @@ -23,6 +23,7 @@
> #include "xfs_ioend.h"
> #include "xfs_zone_alloc.h"
> #include "xfs_rtgroup.h"
> +#include "xfs_fsverity.h"
[Severity: Low]
Does including xfs_fsverity.h here break the build at this commit?
This patch creates fs/xfs/libxfs/xfs_verity.h, whereas fs/xfs/xfs_fsverity.h
is only introduced in the subsequent commit ee4503160b45 ("xfs: use read
ioend for fsverity data verification"), causing compilation of
fs/xfs/xfs_aops.c to fail during bisection.
[ ... ]
> @@ -507,6 +517,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);
> +}
[Severity: High]
Can testing XFS_VERITY_CONSTRUCTION only once before iomap_writepages() race
with fsverity enablement and drop dirty Merkle tree folios past EOF?
If a background writeback thread enters xfs_vm_writepages() ->
xfs_iomap_writepages() just before xfs_fsverity_begin_enable() sets
XFS_VERITY_CONSTRUCTION, ctx->iomap.flags starts without IOMAP_F_FSVERITY.
While writeback_iter() -> writeback_get_folio() is running or reschedules on
data folios, xfs_fsverity_begin_enable() can finish filemap_write_and_wait()
(which only waits on PAGECACHE_TAG_WRITEBACK and does not serialize against an
in-flight do_writepages() call), set XFS_VERITY_CONSTRUCTION, and start
dirtying Merkle tree folios past i_size via build_merkle_tree() ->
iomap_fsverity_write().
When the writeback loop reaches those newly dirtied post-EOF folios,
folio_prepare_writeback() calls folio_clear_dirty_for_io(), clearing the
dirty state before iomap_writeback_folio() calls iomap_writeback_handle_eof():
fs/iomap/buffered-io.c:iomap_writeback_handle_eof() {
if (wpc->iomap.flags & IOMAP_F_FSVERITY) {
WARN_ON_ONCE(folio_pos(folio) < isize);
return true;
}
if (*end_pos > isize) {
...
if (folio->index > end_index ||
(folio->index == end_index && poff == 0))
return false;
}
Because iomap_writeback_handle_eof() runs before wpc->ops->writeback_range
(xfs_map_blocks), wpc->iomap.flags still lacks IOMAP_F_FSVERITY, so the clean
folio is skipped without writing it out or converting its delalloc extent.
Similarly, in xfs_bmapi_convert_one_delalloc(), IOMAP_F_FSVERITY is only set
when offset >= xfs_fsverity_metadata_offset(ip):
fs/xfs/libxfs/xfs_bmap.c:xfs_bmapi_convert_one_delalloc() {
if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) &&
offset >= xfs_fsverity_metadata_offset(ip))
flags |= IOMAP_F_FSVERITY;
ASSERT(!isnullstartblock(bma.got.br_startblock));
xfs_bmbt_to_iomap(ip, iomap, &bma.got, 0, flags,
xfs_iomap_inode_sequence(ip, flags));
}
If xfs_bmapi_convert_one_delalloc() converts a delalloc extent below
xfs_fsverity_metadata_offset(ip) while XFS_VERITY_CONSTRUCTION is set,
xfs_bmbt_to_iomap() overwrites wpc->iomap.flags without IOMAP_F_FSVERITY
before the writeback loop advances to post-EOF Merkle tree folios.
[ ... ]
> diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c
> index 329c745e31204..68fef65764420 100644
> --- a/fs/xfs/xfs_iomap.c
> +++ b/fs/xfs/xfs_iomap.c
[ ... ]
> @@ -883,6 +885,9 @@ xfs_direct_write_iomap_begin(
> if (flags & IOMAP_ATOMIC)
> iomap_flags |= IOMAP_F_ATOMIC_BIO;
>
> + if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
> + iomap_flags |= IOMAP_F_FSVERITY;
> +
> /*
> * COW writes may allocate delalloc space or convert unwritten COW
> * extents, so we need to make sure to take the lock exclusively here.
> @@ -1023,7 +1028,8 @@ xfs_direct_write_iomap_begin(
> trace_xfs_iomap_found(ip, offset, length - offset, XFS_COW_FORK, &cmap);
> if (imap.br_startblock != HOLESTARTBLOCK) {
> seq = xfs_iomap_inode_sequence(ip, 0);
> - error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags, 0, seq);
> + error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags,
> + iomap_flags & IOMAP_F_FSVERITY, seq);
> if (error)
> goto out_unlock;
> }
[Severity: High]
In xfs_direct_write_iomap_begin() at out_found_cow, should the final
xfs_bmbt_to_iomap() call for iomap also include iomap_flags (e.g.
iomap_flags | IOMAP_F_SHARED) instead of passing only IOMAP_F_SHARED?
Right after populating srcmap above, out_found_cow populates iomap with a
hardcoded IOMAP_F_SHARED:
fs/xfs/xfs_iomap.c:xfs_direct_write_iomap_begin() {
out_found_cow:
...
seq = xfs_iomap_inode_sequence(ip, IOMAP_F_SHARED);
xfs_iunlock(ip, lockmode);
return xfs_bmbt_to_iomap(ip, iomap, &cmap, flags, IOMAP_F_SHARED, seq);
}
When an inode has an extent size hint, xfs_buffered_write_iomap_begin()
delegates buffered fsverity metadata writes to xfs_direct_write_iomap_begin():
fs/xfs/xfs_iomap.c:xfs_buffered_write_iomap_begin() {
/* we can't use delayed allocations when using extent size hints */
if (xfs_get_extsz_hint(ip))
return xfs_direct_write_iomap_begin(inode, offset, count,
flags, iomap, srcmap);
}
If that write takes the out_found_cow path, xfs_bmbt_to_iomap() overwrites
iomap->flags with IOMAP_F_SHARED, dropping IOMAP_F_FSVERITY from
iter->iomap.flags.
In iomap_write_iter(), the EOF extension check tests iter->iomap.flags rather
than iter->srcmap.flags:
fs/iomap/buffered-io.c:iomap_write_iter() {
old_size = iter->inode->i_size;
if (pos + written > old_size &&
!(iter->iomap.flags & IOMAP_F_FSVERITY)) {
i_size_write(iter->inode, pos + written);
iter->iomap.flags |= IOMAP_F_SIZE_CHANGED;
}
}
Without IOMAP_F_FSVERITY on iter->iomap.flags, won't iomap_write_iter()
extend inode->i_size into the post-EOF fsverity metadata region?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144147.2393369-1-aalbersh@kernel.org?part=5
^ permalink raw reply [flat|nested] 42+ messages in thread* Re: [PATCH v18 05/15] xfs: handle fsverity I/O in write/read path
2026-11-02 14:41 ` [PATCH v18 05/15] xfs: handle fsverity I/O in write/read path Andrey Albershteyn
2026-11-02 15:13 ` sashiko-bot
@ 2026-11-03 9:37 ` Christoph Hellwig
2026-11-03 13:55 ` Andrey Albershteyn
1 sibling, 1 reply; 42+ messages in thread
From: Christoph Hellwig @ 2026-11-03 9:37 UTC (permalink / raw)
To: Andrey Albershteyn
Cc: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs,
linux-fsdevel, david, Darrick J. Wong
On Mon, Nov 02, 2026 at 03:41:35PM +0100, Andrey Albershteyn wrote:
> @@ -41,6 +41,7 @@
> #include "xfs_inode_util.h"
> #include "xfs_rtgroup.h"
> #include "xfs_zone_alloc.h"
> +#include "xfs_verity.h"
I think the Sashiko comment that this breaks the build until
xfs_fsverifty.h is added is spot on. Easiest is probably to just add
the header in this patch already.
> XFS_ERRORTAG_DELAY(mp, XFS_ERRTAG_WB_DELAY_MS);
>
> + if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
> + iomap_flags |= IOMAP_F_FSVERITY;
> +
> /*
> * COW fork blocks can overlap data fork blocks even if the blocks
> * aren't shared. COW I/O always takes precedent, so we must always
> @@ -265,7 +270,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);
Is there a reason to not just move the IOMAP_F_FSVERITY handling
into xfs_bmbt_to_iomap? It would break if single extent is shared
between data and verity data, but we already leave a large enough hole
that this could never happen, right?
If we need to protect agaist that explicitly passing the offset might
still be better, although it would be a bit ugly.
> xfs_iunlock(ip, XFS_ILOCK_EXCL);
>
> xfs_iomap_set_anon_write(ip, &wpc->iomap, offset,
> - XFS_FSB_TO_B(mp, count_fsb));
> + XFS_FSB_TO_B(mp, count_fsb), iomap_flags);
Same for xfs_iomap_set_anon_write, although we already do have the
offset here in either case.
> +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;
Sashiko complained about this, but I also really don't understand
how setting ctx->iomap.flags before we even get a mapping to start
with make much sense. This should be set together with filling the
iomap either way.
> +#include "xfs_verity.h"
> +#include <linux/fsverity.h>
>
> #define XFS_ALLOC_ALIGN(mp, off) \
> (((off) >> mp->m_allocsize_log) << mp->m_allocsize_log)
> @@ -883,6 +885,9 @@ xfs_direct_write_iomap_begin(
> if (flags & IOMAP_ATOMIC)
> iomap_flags |= IOMAP_F_ATOMIC_BIO;
>
> + if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
> + iomap_flags |= IOMAP_F_FSVERITY;
> +
> /*
> * COW writes may allocate delalloc space or convert unwritten COW
> * extents, so we need to make sure to take the lock exclusively here.
> @@ -1023,7 +1028,8 @@ xfs_direct_write_iomap_begin(
> trace_xfs_iomap_found(ip, offset, length - offset, XFS_COW_FORK, &cmap);
> if (imap.br_startblock != HOLESTARTBLOCK) {
> seq = xfs_iomap_inode_sequence(ip, 0);
> - error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags, 0, seq);
> + error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags,
> + iomap_flags & IOMAP_F_FSVERITY, seq);
Does passing on the other iomap_flags hurt here? Filtering out a
specific one looks pretty odd.
^ permalink raw reply [flat|nested] 42+ messages in thread* Re: [PATCH v18 05/15] xfs: handle fsverity I/O in write/read path
2026-11-03 9:37 ` Christoph Hellwig
@ 2026-11-03 13:55 ` Andrey Albershteyn
2026-11-03 14:53 ` Christoph Hellwig
0 siblings, 1 reply; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-03 13:55 UTC (permalink / raw)
To: Christoph Hellwig
Cc: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs,
linux-fsdevel, david, Darrick J. Wong
On 2026-11-03 01:37:46, Christoph Hellwig wrote:
> On Mon, Nov 02, 2026 at 03:41:35PM +0100, Andrey Albershteyn wrote:
> > @@ -41,6 +41,7 @@
> > #include "xfs_inode_util.h"
> > #include "xfs_rtgroup.h"
> > #include "xfs_zone_alloc.h"
> > +#include "xfs_verity.h"
>
> I think the Sashiko comment that this breaks the build until
> xfs_fsverifty.h is added is spot on. Easiest is probably to just add
> the header in this patch already.
>
> > XFS_ERRORTAG_DELAY(mp, XFS_ERRTAG_WB_DELAY_MS);
> >
> > + if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
> > + iomap_flags |= IOMAP_F_FSVERITY;
> > +
> > /*
> > * COW fork blocks can overlap data fork blocks even if the blocks
> > * aren't shared. COW I/O always takes precedent, so we must always
> > @@ -265,7 +270,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);
>
> Is there a reason to not just move the IOMAP_F_FSVERITY handling
> into xfs_bmbt_to_iomap? It would break if single extent is shared
> between data and verity data, but we already leave a large enough hole
> that this could never happen, right?
I think it can be moved to xfs_bmbt_to_iomap(), but we will need an
offset there. The one extent case could happen if file size is
exactly 64k and we got continuous phys space allocation with merkle
tree (then the extents will be merged).
>
> If we need to protect agaist that explicitly passing the offset might
> still be better, although it would be a bit ugly.
>
> > xfs_iunlock(ip, XFS_ILOCK_EXCL);
> >
> > xfs_iomap_set_anon_write(ip, &wpc->iomap, offset,
> > - XFS_FSB_TO_B(mp, count_fsb));
> > + XFS_FSB_TO_B(mp, count_fsb), iomap_flags);
>
> Same for xfs_iomap_set_anon_write, although we already do have the
> offset here in either case.
>
> > +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;
>
> Sashiko complained about this, but I also really don't understand
> how setting ctx->iomap.flags before we even get a mapping to start
> with make much sense. This should be set together with filling the
> iomap either way.
The problem here is that when we found a dirty folio to writeback,
and we know it's beyond EOF, how to find out that's it's fsverity
folio. We don't have any fsverity metadata attach to an inode yet as
we are in the process of creating it.
This is not a nice workaround here, but I use stale iomap.flags
here. So, every next folio in that iomap has the stale flags from a
previous one, which has fsverity set. This should be fine as the
only folios written, at this point, are fsverity metadata.
Here I set it for the first folio in the iomap, to get it started.
Otherwise, writeback will just will not continue on folios post EOF.
This could be probably done by elevating "verity in construction"
into inode_state_flags *shrug*
>
> > +#include "xfs_verity.h"
> > +#include <linux/fsverity.h>
> >
> > #define XFS_ALLOC_ALIGN(mp, off) \
> > (((off) >> mp->m_allocsize_log) << mp->m_allocsize_log)
> > @@ -883,6 +885,9 @@ xfs_direct_write_iomap_begin(
> > if (flags & IOMAP_ATOMIC)
> > iomap_flags |= IOMAP_F_ATOMIC_BIO;
> >
> > + if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
> > + iomap_flags |= IOMAP_F_FSVERITY;
> > +
> > /*
> > * COW writes may allocate delalloc space or convert unwritten COW
> > * extents, so we need to make sure to take the lock exclusively here.
> > @@ -1023,7 +1028,8 @@ xfs_direct_write_iomap_begin(
> > trace_xfs_iomap_found(ip, offset, length - offset, XFS_COW_FORK, &cmap);
> > if (imap.br_startblock != HOLESTARTBLOCK) {
> > seq = xfs_iomap_inode_sequence(ip, 0);
> > - error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags, 0, seq);
> > + error = xfs_bmbt_to_iomap(ip, srcmap, &imap, flags,
> > + iomap_flags & IOMAP_F_FSVERITY, seq);
>
> Does passing on the other iomap_flags hurt here? Filtering out a
> specific one looks pretty odd.
>
I suppose it's fine, the buffered-io path won't have anything except
fsverity, and direct-io can set IOMAP_F_DIRTY/IOMAP_F_ATOMIC_BIO
which is probably not desired for source cow extent. I will test it
--
- Andrey
^ permalink raw reply [flat|nested] 42+ messages in thread* Re: [PATCH v18 05/15] xfs: handle fsverity I/O in write/read path
2026-11-03 13:55 ` Andrey Albershteyn
@ 2026-11-03 14:53 ` Christoph Hellwig
2026-11-03 15:26 ` Andrey Albershteyn
2026-11-03 22:03 ` Eric Biggers
0 siblings, 2 replies; 42+ messages in thread
From: Christoph Hellwig @ 2026-11-03 14:53 UTC (permalink / raw)
To: Andrey Albershteyn
Cc: Christoph Hellwig, ebiggers, hch, Carlos Maiolino, fsverity,
linux-xfs, linux-fsdevel, david, Darrick J. Wong
[Note: a question for Eric about lifting a flag to the VFS below]
On Tue, Nov 03, 2026 at 02:55:48PM +0100, Andrey Albershteyn wrote:
> > Is there a reason to not just move the IOMAP_F_FSVERITY handling
> > into xfs_bmbt_to_iomap? It would break if single extent is shared
> > between data and verity data, but we already leave a large enough hole
> > that this could never happen, right?
>
> I think it can be moved to xfs_bmbt_to_iomap(), but we will need an
> offset there. The one extent case could happen if file size is
> exactly 64k and we got continuous phys space allocation with merkle
> tree (then the extents will be merged).
Or a multiple of 64k, yeah. I though we went for an "air gap",
but I have to admit I forgot all the details of this already.
I guess passing an offset it is then. And maybe also add a comment
about that while you're at it?
> > Sashiko complained about this, but I also really don't understand
> > how setting ctx->iomap.flags before we even get a mapping to start
> > with make much sense. This should be set together with filling the
> > iomap either way.
>
> The problem here is that when we found a dirty folio to writeback,
> and we know it's beyond EOF, how to find out that's it's fsverity
> folio. We don't have any fsverity metadata attach to an inode yet as
> we are in the process of creating it.
>
> This is not a nice workaround here, but I use stale iomap.flags
> here. So, every next folio in that iomap has the stale flags from a
> previous one, which has fsverity set. This should be fine as the
> only folios written, at this point, are fsverity metadata.
>
> Here I set it for the first folio in the iomap, to get it started.
> Otherwise, writeback will just will not continue on folios post EOF.
>
> This could be probably done by elevating "verity in construction"
> into inode_state_flags *shrug*
Lifting the flag to the VFS feels like a good and nicer than finding
backways communicaton channels through iomap. And once we have more
file systems supporting fsverity a common flag vs one in each
file system might be worth it even without that.
Eric?
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v18 05/15] xfs: handle fsverity I/O in write/read path
2026-11-03 14:53 ` Christoph Hellwig
@ 2026-11-03 15:26 ` Andrey Albershteyn
2026-11-03 22:03 ` Eric Biggers
1 sibling, 0 replies; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-03 15:26 UTC (permalink / raw)
To: Christoph Hellwig
Cc: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs,
linux-fsdevel, david, Darrick J. Wong
On 2026-11-03 06:53:47, Christoph Hellwig wrote:
> [Note: a question for Eric about lifting a flag to the VFS below]
>
> On Tue, Nov 03, 2026 at 02:55:48PM +0100, Andrey Albershteyn wrote:
> > > Is there a reason to not just move the IOMAP_F_FSVERITY handling
> > > into xfs_bmbt_to_iomap? It would break if single extent is shared
> > > between data and verity data, but we already leave a large enough hole
> > > that this could never happen, right?
> >
> > I think it can be moved to xfs_bmbt_to_iomap(), but we will need an
> > offset there. The one extent case could happen if file size is
> > exactly 64k and we got continuous phys space allocation with merkle
> > tree (then the extents will be merged).
>
> Or a multiple of 64k, yeah. I though we went for an "air gap",
> but I have to admit I forgot all the details of this already.
>
> I guess passing an offset it is then. And maybe also add a comment
> about that while you're at it?
air gap is zero on multiples of 64k, this is more of an alignment
with largest page size fsverity supports than the gap between
data/metadata (not to have mix of data/metadata in one page). The
xfs_fsverity_metadata_offset() is
loff_t
xfs_fsverity_metadata_offset(
const struct xfs_inode *ip)
{
return round_up(i_size_read(VFS_IC(ip)), XFS_FSVERITY_START_ALIGN);
}
--
- Andrey
^ permalink raw reply [flat|nested] 42+ messages in thread* Re: [PATCH v18 05/15] xfs: handle fsverity I/O in write/read path
2026-11-03 14:53 ` Christoph Hellwig
2026-11-03 15:26 ` Andrey Albershteyn
@ 2026-11-03 22:03 ` Eric Biggers
1 sibling, 0 replies; 42+ messages in thread
From: Eric Biggers @ 2026-11-03 22:03 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Andrey Albershteyn, hch, Carlos Maiolino, fsverity, linux-xfs,
linux-fsdevel, david, Darrick J. Wong
On Tue, Nov 03, 2026 at 06:53:47AM -0800, Christoph Hellwig wrote:
> > > Sashiko complained about this, but I also really don't understand
> > > how setting ctx->iomap.flags before we even get a mapping to start
> > > with make much sense. This should be set together with filling the
> > > iomap either way.
> >
> > The problem here is that when we found a dirty folio to writeback,
> > and we know it's beyond EOF, how to find out that's it's fsverity
> > folio. We don't have any fsverity metadata attach to an inode yet as
> > we are in the process of creating it.
> >
> > This is not a nice workaround here, but I use stale iomap.flags
> > here. So, every next folio in that iomap has the stale flags from a
> > previous one, which has fsverity set. This should be fine as the
> > only folios written, at this point, are fsverity metadata.
> >
> > Here I set it for the first folio in the iomap, to get it started.
> > Otherwise, writeback will just will not continue on folios post EOF.
> >
> > This could be probably done by elevating "verity in construction"
> > into inode_state_flags *shrug*
>
> Lifting the flag to the VFS feels like a good and nicer than finding
> backways communicaton channels through iomap. And once we have more
> file systems supporting fsverity a common flag vs one in each
> file system might be worth it even without that.
I don't feel great about reserving space in 'struct inode' just for
temporary state of the FS_IOC_ENABLE_VERITY ioctl. However, i_state is
currently using only 20 of its 32 available bits anyway, so it actually
would make no difference in the size and there are still plenty of bits
to go. So sure, if adding I_VERITY_IN_PROGRESS makes things easier for
now, it sounds okay to me.
- Eric
^ permalink raw reply [flat|nested] 42+ messages in thread
* [PATCH v18 06/15] xfs: use read ioend for fsverity data verification
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
` (4 preceding siblings ...)
2026-11-02 14:41 ` [PATCH v18 05/15] xfs: handle fsverity I/O in write/read path Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 14:57 ` sashiko-bot
2026-11-03 9:39 ` Christoph Hellwig
2026-11-02 14:41 ` [PATCH v18 07/15] xfs: add XFS_BMAPI_UNWRITTEN to unmap unwritten extents in __xfs_bunmapi() Andrey Albershteyn
` (8 subsequent siblings)
14 siblings, 2 replies; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david
Use read ioends for fsverity verification. Do not issue 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.
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
---
fs/xfs/Makefile | 1 +
fs/xfs/xfs_aops.c | 12 ++++++----
fs/xfs/xfs_file.c | 3 ++-
fs/xfs/xfs_fsverity.c | 46 +++++++++++++++++++++++++++++++++++++
fs/xfs/xfs_fsverity.h | 40 ++++++++++++++++++++++++++++++++
fs/xfs/xfs_ioend.c | 53 ++++++++++++++++++++++++++++++++++++++-----
fs/xfs/xfs_ioend.h | 4 +++-
fs/xfs/xfs_super.c | 24 +++++++++++++++++++-
8 files changed, 169 insertions(+), 14 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 cb7f71457ee9..9edb2850f094 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -151,6 +151,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/xfs_aops.c b/fs/xfs/xfs_aops.c
index 19640e4e3ed1..2f6e87991d83 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -611,7 +611,7 @@ xfs_bio_submit_read(
{
xfs_ioend_submit_read(iter->inode, ctx->read_ctx,
ctx->read_ctx_file_offset,
- iomap_ioend_flags(&iter->iomap));
+ iomap_ioend_flags(&iter->iomap), ctx->vi);
ctx->read_ctx = NULL;
}
@@ -623,11 +623,13 @@ static const struct iomap_read_ops xfs_iomap_read_ops = {
static inline const struct iomap_read_ops *
xfs_get_iomap_read_ops(
- const struct address_space *mapping)
+ const struct address_space *mapping,
+ loff_t pos)
{
struct xfs_inode *ip = XFS_I(mapping->host);
- if (bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev))
+ if (bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev) ||
+ xfs_fsverity_is_file_data(ip, pos))
return &xfs_iomap_read_ops;
return &iomap_bio_read_ops;
}
@@ -639,7 +641,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;
}
@@ -650,7 +652,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_file.c b/fs/xfs/xfs_file.c
index f0d000272521..dbb5f8f59678 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -224,7 +224,8 @@ xfs_dio_read_bounce_submit_io(
loff_t file_offset)
{
xfs_ioend_submit_read(iter->inode, bio, file_offset,
- iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT);
+ iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT,
+ NULL);
}
static const struct iomap_dio_ops xfs_dio_read_bounce_ops = {
diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c
new file mode 100644
index 000000000000..8fc3e763c017
--- /dev/null
+++ b/fs/xfs/xfs_fsverity.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ */
+#include "xfs_platform.h"
+#include "xfs_fs.h"
+#include "xfs_shared.h"
+#include "xfs_format.h"
+#include "xfs_log_format.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_inode.h"
+#include "xfs_fsverity.h"
+#include <linux/fsverity.h>
+#include <linux/iomap.h>
+
+struct kmem_cache *xfs_fsverity_ioend_cache;
+mempool_t xfs_fsverity_ioend_pool;
+
+#define XFS_FSVERITY_IOEND_POOL_MIN 128
+
+/*
+ * Back the fsverity ioend allocations with a mempool so that read I/O
+ * completion always makes forward progress and cannot deadlock
+ */
+int
+xfs_fsverity_init(void)
+{
+ return mempool_init_slab_pool(&xfs_fsverity_ioend_pool,
+ XFS_FSVERITY_IOEND_POOL_MIN, xfs_fsverity_ioend_cache);
+}
+
+void
+xfs_fsverity_exit(void)
+{
+ mempool_exit(&xfs_fsverity_ioend_pool);
+}
+
+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
new file mode 100644
index 000000000000..44f7bc7831f2
--- /dev/null
+++ b/fs/xfs/xfs_fsverity.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ */
+#ifndef __XFS_FSVERITY_H__
+#define __XFS_FSVERITY_H__
+
+#include "xfs_platform.h"
+#include "xfs_verity.h"
+#include <linux/iomap.h>
+#include <linux/mempool.h>
+
+#ifdef CONFIG_FS_VERITY
+int xfs_fsverity_init(void);
+void xfs_fsverity_exit(void);
+bool xfs_fsverity_is_file_data(const struct xfs_inode *ip, loff_t offset);
+#else
+static inline int xfs_fsverity_init(void)
+{
+ return 0;
+}
+static inline void xfs_fsverity_exit(void)
+{
+}
+static inline bool xfs_fsverity_is_file_data(const struct xfs_inode *ip,
+ loff_t offset)
+{
+ return false;
+}
+#endif /* CONFIG_FS_VERITY */
+
+struct xfs_fsverity_ioend {
+ struct iomap_ioend *ioend;
+ struct work_struct work;
+};
+
+extern struct kmem_cache *xfs_fsverity_ioend_cache;
+extern mempool_t xfs_fsverity_ioend_pool;
+
+#endif /* __XFS_FSVERITY_H__ */
diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c
index 48becc3bf608..d30aee973a87 100644
--- a/fs/xfs/xfs_ioend.c
+++ b/fs/xfs/xfs_ioend.c
@@ -18,7 +18,26 @@
#include "xfs_ioend.h"
#include "xfs_error.h"
#include "xfs_errortag.h"
+#include "xfs_fsverity.h"
#include <linux/bio-integrity.h>
+#include <linux/fsverity.h>
+
+static void
+xfs_end_fsverity_io_read(
+ struct work_struct *work)
+{
+ struct xfs_fsverity_ioend *fsv_ioend =
+ container_of(work, struct xfs_fsverity_ioend, work);
+ struct iomap_ioend *ioend = fsv_ioend->ioend;
+ struct bio *bio = &ioend->io_bio;
+
+ mempool_free(fsv_ioend, &xfs_fsverity_ioend_pool);
+
+ if (!bio->bi_status)
+ fsverity_verify_bio(ioend->io_vi, bio);
+
+ iomap_finish_ioends(ioend, blk_status_to_errno(bio->bi_status));
+}
static void
xfs_dio_bounce_end_io(
@@ -93,12 +112,14 @@ xfs_read_bounce_and_resubmit(
static void
xfs_end_io_read(
- struct bio *bio)
+ struct bio *bio)
{
- struct iomap_ioend *ioend = iomap_ioend_from_bio(bio);
- struct xfs_inode *ip = XFS_I(ioend->io_inode);
- struct xfs_mount *mp = ip->i_mount;
- int error = blk_status_to_errno(bio->bi_status);
+ struct iomap_ioend *ioend = iomap_ioend_from_bio(bio);
+ struct xfs_inode *ip = XFS_I(ioend->io_inode);
+ struct xfs_mount *mp = ip->i_mount;
+ int error =
+ blk_status_to_errno(bio->bi_status);
+ struct xfs_fsverity_ioend *fsv_ioend;
if (!error && (ioend->io_flags & IOMAP_IOEND_INTEGRITY)) {
error = iomap_ioend_integrity_verify(ioend);
@@ -117,6 +138,24 @@ xfs_end_io_read(
}
}
+ /*
+ * If we have fsverity on this bio, we need to run fsverity verification
+ * of data folios from a separate fsverity workqueue. This is necessary
+ * to avoid deadlocking due to fsverity issuing more reads of fsverity
+ * metadata which would be processed by the same worker in the BIO
+ * completion workqueue.
+ */
+ if (IS_ENABLED(CONFIG_FS_VERITY) && !error && ioend->io_vi &&
+ xfs_fsverity_is_file_data(ip, ioend->io_offset)) {
+ fsv_ioend = mempool_alloc(&xfs_fsverity_ioend_pool,
+ GFP_NOFS);
+ fsv_ioend->ioend = ioend;
+ INIT_WORK(&fsv_ioend->work, xfs_end_fsverity_io_read);
+
+ fsverity_enqueue_verify_work(&fsv_ioend->work);
+ return;
+ }
+
iomap_finish_ioends(ioend, error);
}
@@ -125,13 +164,15 @@ xfs_ioend_submit_read(
struct inode *inode,
struct bio *bio,
loff_t file_offset,
- u16 ioend_flags)
+ u16 ioend_flags,
+ struct fsverity_info *vi)
{
struct xfs_inode *ip = XFS_I(inode);
struct xfs_mount *mp = ip->i_mount;
struct iomap_ioend *ioend;
ioend = iomap_init_ioend(inode, bio, file_offset, ioend_flags);
+ ioend->io_vi = vi;
if ((ioend_flags & IOMAP_IOEND_DIRECT) &&
READ_ONCE(mp->m_read_bounce) == XFS_READ_BOUNCE_ALWAYS) {
iomap_bounce_read(ioend, bdev_logical_block_size(bio->bi_bdev),
diff --git a/fs/xfs/xfs_ioend.h b/fs/xfs/xfs_ioend.h
index 7c2a1ea3e6ed..992c248a693a 100644
--- a/fs/xfs/xfs_ioend.h
+++ b/fs/xfs/xfs_ioend.h
@@ -2,6 +2,8 @@
#ifndef __XFS_IOEND_H
#define __XFS_IOEND_H
+#include <linux/fsverity.h>
+
/*
* Fast and loose check if this write could update the on-disk inode size.
*/
@@ -13,6 +15,6 @@ static inline bool xfs_ioend_is_append(struct iomap_ioend *ioend)
void xfs_end_bio(struct bio *bio);
void xfs_ioend_submit_read(struct inode *inode, struct bio *bio,
- loff_t file_offset, u16 ioend_flags);
+ loff_t file_offset, u16 ioend_flags, struct fsverity_info *vi);
#endif /* __XFS_IOEND_H */
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index 5a06132aa384..3a7d7334e9a6 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -2516,8 +2516,20 @@ xfs_init_caches(void)
if (!xfs_parent_args_cache)
goto out_destroy_xmi_cache;
+#ifdef CONFIG_FS_VERITY
+ xfs_fsverity_ioend_cache = kmem_cache_create("xfs_fsverity_ioend",
+ sizeof(struct xfs_fsverity_ioend),
+ 0, 0, NULL);
+ if (!xfs_fsverity_ioend_cache)
+ goto out_destroy_parent_args_cache;
+#endif
+
return 0;
+#ifdef CONFIG_FS_VERITY
+ out_destroy_parent_args_cache:
+ kmem_cache_destroy(xfs_parent_args_cache);
+#endif
out_destroy_xmi_cache:
kmem_cache_destroy(xfs_xmi_cache);
out_destroy_xmd_cache:
@@ -2580,6 +2592,9 @@ xfs_destroy_caches(void)
* destroy caches.
*/
rcu_barrier();
+#ifdef CONFIG_FS_VERITY
+ kmem_cache_destroy(xfs_fsverity_ioend_cache);
+#endif
kmem_cache_destroy(xfs_parent_args_cache);
kmem_cache_destroy(xfs_xmd_cache);
kmem_cache_destroy(xfs_xmi_cache);
@@ -2660,10 +2675,14 @@ init_xfs_fs(void)
if (error)
goto out;
- error = xfs_init_workqueues();
+ error = xfs_fsverity_init();
if (error)
goto out_destroy_caches;
+ error = xfs_init_workqueues();
+ if (error)
+ goto out_fsverity_exit;
+
error = xfs_mru_cache_init();
if (error)
goto out_destroy_wq;
@@ -2740,6 +2759,8 @@ init_xfs_fs(void)
xfs_mru_cache_uninit();
out_destroy_wq:
xfs_destroy_workqueues();
+ out_fsverity_exit:
+ xfs_fsverity_exit();
out_destroy_caches:
xfs_destroy_caches();
out:
@@ -2763,6 +2784,7 @@ exit_xfs_fs(void)
xfs_cleanup_procfs();
xfs_mru_cache_uninit();
xfs_destroy_workqueues();
+ xfs_fsverity_exit();
xfs_destroy_caches();
xfs_uuid_table_free();
}
--
2.54.0
^ permalink raw reply related [flat|nested] 42+ messages in thread* Re: [PATCH v18 06/15] xfs: use read ioend for fsverity data verification
2026-11-02 14:41 ` [PATCH v18 06/15] xfs: use read ioend for fsverity data verification Andrey Albershteyn
@ 2026-11-02 14:57 ` sashiko-bot
2026-11-03 9:39 ` Christoph Hellwig
1 sibling, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-11-02 14:57 UTC (permalink / raw)
To: Andrey Albershteyn, ebiggers, hch, Carlos Maiolino, fsverity; +Cc: david
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] xfs: missing include of xfs_fsverity.h in fs/xfs/xfs_super.c breaks build
--
commit ee4503160b45b8debf89daa601b6a9be79307262
Author: Andrey Albershteyn <aalbersh@kernel.org>
xfs: use read ioend for fsverity data verification
This commit uses read ioends to offload fsverity data verification to a
dedicated fsverity workqueue, avoiding deadlocks with metadata read
completions. It also adds a slab cache and mempool for fsverity ioend
structures and passes fsverity_info from the iomap context to the ioend.
> diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
> index 5a06132aa3846..3a7d7334e9a65 100644
> --- a/fs/xfs/xfs_super.c
> +++ b/fs/xfs/xfs_super.c
> @@ -2516,8 +2516,20 @@ xfs_init_caches(void)
> if (!xfs_parent_args_cache)
> goto out_destroy_xmi_cache;
>
> +#ifdef CONFIG_FS_VERITY
> + xfs_fsverity_ioend_cache = kmem_cache_create("xfs_fsverity_ioend",
> + sizeof(struct xfs_fsverity_ioend),
> + 0, 0, NULL);
> + if (!xfs_fsverity_ioend_cache)
> + goto out_destroy_parent_args_cache;
> +#endif
> +
> return 0;
>
> +#ifdef CONFIG_FS_VERITY
> + out_destroy_parent_args_cache:
> + kmem_cache_destroy(xfs_parent_args_cache);
> +#endif
> out_destroy_xmi_cache:
> kmem_cache_destroy(xfs_xmi_cache);
> out_destroy_xmd_cache:
> @@ -2580,6 +2592,9 @@ xfs_destroy_caches(void)
> * destroy caches.
> */
> rcu_barrier();
> +#ifdef CONFIG_FS_VERITY
> + kmem_cache_destroy(xfs_fsverity_ioend_cache);
> +#endif
> kmem_cache_destroy(xfs_parent_args_cache);
> kmem_cache_destroy(xfs_xmd_cache);
> kmem_cache_destroy(xfs_xmi_cache);
> @@ -2660,10 +2675,14 @@ init_xfs_fs(void)
> if (error)
> goto out;
>
> - error = xfs_init_workqueues();
> + error = xfs_fsverity_init();
> if (error)
> goto out_destroy_caches;
>
> + error = xfs_init_workqueues();
> + if (error)
> + goto out_fsverity_exit;
> +
> error = xfs_mru_cache_init();
> if (error)
> goto out_destroy_wq;
> @@ -2740,6 +2759,8 @@ init_xfs_fs(void)
> xfs_mru_cache_uninit();
> out_destroy_wq:
> xfs_destroy_workqueues();
> + out_fsverity_exit:
> + xfs_fsverity_exit();
> out_destroy_caches:
> xfs_destroy_caches();
> out:
> @@ -2763,6 +2784,7 @@ exit_xfs_fs(void)
> xfs_cleanup_procfs();
> xfs_mru_cache_uninit();
> xfs_destroy_workqueues();
> + xfs_fsverity_exit();
> xfs_destroy_caches();
> xfs_uuid_table_free();
> }
[Severity: Low]
Does fs/xfs/xfs_super.c fail to build at this commit because xfs_fsverity.h
is not included yet?
In fs/xfs/xfs_super.c, xfs_init_caches() and xfs_destroy_caches() reference
xfs_fsverity_ioend_cache and struct xfs_fsverity_ioend when CONFIG_FS_VERITY
is enabled, while init_xfs_fs() and exit_xfs_fs() unconditionally call
xfs_fsverity_init() and xfs_fsverity_exit().
Those declarations and static inline stubs live in fs/xfs/xfs_fsverity.h,
which is not included in fs/xfs/xfs_super.c until a later commit in the
series, 996c2dec93ad ("xfs: add fs-verity support").
Should #include "xfs_fsverity.h" be moved into fs/xfs/xfs_super.c in this
commit so the series remains bisectable under both CONFIG_FS_VERITY=y and
CONFIG_FS_VERITY=n?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144147.2393369-1-aalbersh@kernel.org?part=6
^ permalink raw reply [flat|nested] 42+ messages in thread* Re: [PATCH v18 06/15] xfs: use read ioend for fsverity data verification
2026-11-02 14:41 ` [PATCH v18 06/15] xfs: use read ioend for fsverity data verification Andrey Albershteyn
2026-11-02 14:57 ` sashiko-bot
@ 2026-11-03 9:39 ` Christoph Hellwig
1 sibling, 0 replies; 42+ messages in thread
From: Christoph Hellwig @ 2026-11-03 9:39 UTC (permalink / raw)
To: Andrey Albershteyn
Cc: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs,
linux-fsdevel, david
Looks good modulo the xfs_fsverity.h issue:
Reviewed-by: Christoph Hellwig <hch@lst.de>
On Mon, Nov 02, 2026 at 03:41:36PM +0100, Andrey Albershteyn wrote:
> Use read ioends for fsverity verification. Do not issue 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.
>
> Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
> ---
> fs/xfs/Makefile | 1 +
> fs/xfs/xfs_aops.c | 12 ++++++----
> fs/xfs/xfs_file.c | 3 ++-
> fs/xfs/xfs_fsverity.c | 46 +++++++++++++++++++++++++++++++++++++
> fs/xfs/xfs_fsverity.h | 40 ++++++++++++++++++++++++++++++++
> fs/xfs/xfs_ioend.c | 53 ++++++++++++++++++++++++++++++++++++++-----
> fs/xfs/xfs_ioend.h | 4 +++-
> fs/xfs/xfs_super.c | 24 +++++++++++++++++++-
> 8 files changed, 169 insertions(+), 14 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 cb7f71457ee9..9edb2850f094 100644
> --- a/fs/xfs/Makefile
> +++ b/fs/xfs/Makefile
> @@ -151,6 +151,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/xfs_aops.c b/fs/xfs/xfs_aops.c
> index 19640e4e3ed1..2f6e87991d83 100644
> --- a/fs/xfs/xfs_aops.c
> +++ b/fs/xfs/xfs_aops.c
> @@ -611,7 +611,7 @@ xfs_bio_submit_read(
> {
> xfs_ioend_submit_read(iter->inode, ctx->read_ctx,
> ctx->read_ctx_file_offset,
> - iomap_ioend_flags(&iter->iomap));
> + iomap_ioend_flags(&iter->iomap), ctx->vi);
> ctx->read_ctx = NULL;
> }
>
> @@ -623,11 +623,13 @@ static const struct iomap_read_ops xfs_iomap_read_ops = {
>
> static inline const struct iomap_read_ops *
> xfs_get_iomap_read_ops(
> - const struct address_space *mapping)
> + const struct address_space *mapping,
> + loff_t pos)
> {
> struct xfs_inode *ip = XFS_I(mapping->host);
>
> - if (bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev))
> + if (bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev) ||
> + xfs_fsverity_is_file_data(ip, pos))
> return &xfs_iomap_read_ops;
> return &iomap_bio_read_ops;
> }
> @@ -639,7 +641,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;
> }
> @@ -650,7 +652,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_file.c b/fs/xfs/xfs_file.c
> index f0d000272521..dbb5f8f59678 100644
> --- a/fs/xfs/xfs_file.c
> +++ b/fs/xfs/xfs_file.c
> @@ -224,7 +224,8 @@ xfs_dio_read_bounce_submit_io(
> loff_t file_offset)
> {
> xfs_ioend_submit_read(iter->inode, bio, file_offset,
> - iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT);
> + iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT,
> + NULL);
> }
>
> static const struct iomap_dio_ops xfs_dio_read_bounce_ops = {
> diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c
> new file mode 100644
> index 000000000000..8fc3e763c017
> --- /dev/null
> +++ b/fs/xfs/xfs_fsverity.c
> @@ -0,0 +1,46 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2026 Red Hat, Inc.
> + */
> +#include "xfs_platform.h"
> +#include "xfs_fs.h"
> +#include "xfs_shared.h"
> +#include "xfs_format.h"
> +#include "xfs_log_format.h"
> +#include "xfs_trans_resv.h"
> +#include "xfs_mount.h"
> +#include "xfs_inode.h"
> +#include "xfs_fsverity.h"
> +#include <linux/fsverity.h>
> +#include <linux/iomap.h>
> +
> +struct kmem_cache *xfs_fsverity_ioend_cache;
> +mempool_t xfs_fsverity_ioend_pool;
> +
> +#define XFS_FSVERITY_IOEND_POOL_MIN 128
> +
> +/*
> + * Back the fsverity ioend allocations with a mempool so that read I/O
> + * completion always makes forward progress and cannot deadlock
> + */
> +int
> +xfs_fsverity_init(void)
> +{
> + return mempool_init_slab_pool(&xfs_fsverity_ioend_pool,
> + XFS_FSVERITY_IOEND_POOL_MIN, xfs_fsverity_ioend_cache);
> +}
> +
> +void
> +xfs_fsverity_exit(void)
> +{
> + mempool_exit(&xfs_fsverity_ioend_pool);
> +}
> +
> +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
> new file mode 100644
> index 000000000000..44f7bc7831f2
> --- /dev/null
> +++ b/fs/xfs/xfs_fsverity.h
> @@ -0,0 +1,40 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2026 Red Hat, Inc.
> + */
> +#ifndef __XFS_FSVERITY_H__
> +#define __XFS_FSVERITY_H__
> +
> +#include "xfs_platform.h"
> +#include "xfs_verity.h"
> +#include <linux/iomap.h>
> +#include <linux/mempool.h>
> +
> +#ifdef CONFIG_FS_VERITY
> +int xfs_fsverity_init(void);
> +void xfs_fsverity_exit(void);
> +bool xfs_fsverity_is_file_data(const struct xfs_inode *ip, loff_t offset);
> +#else
> +static inline int xfs_fsverity_init(void)
> +{
> + return 0;
> +}
> +static inline void xfs_fsverity_exit(void)
> +{
> +}
> +static inline bool xfs_fsverity_is_file_data(const struct xfs_inode *ip,
> + loff_t offset)
> +{
> + return false;
> +}
> +#endif /* CONFIG_FS_VERITY */
> +
> +struct xfs_fsverity_ioend {
> + struct iomap_ioend *ioend;
> + struct work_struct work;
> +};
> +
> +extern struct kmem_cache *xfs_fsverity_ioend_cache;
> +extern mempool_t xfs_fsverity_ioend_pool;
> +
> +#endif /* __XFS_FSVERITY_H__ */
> diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c
> index 48becc3bf608..d30aee973a87 100644
> --- a/fs/xfs/xfs_ioend.c
> +++ b/fs/xfs/xfs_ioend.c
> @@ -18,7 +18,26 @@
> #include "xfs_ioend.h"
> #include "xfs_error.h"
> #include "xfs_errortag.h"
> +#include "xfs_fsverity.h"
> #include <linux/bio-integrity.h>
> +#include <linux/fsverity.h>
> +
> +static void
> +xfs_end_fsverity_io_read(
> + struct work_struct *work)
> +{
> + struct xfs_fsverity_ioend *fsv_ioend =
> + container_of(work, struct xfs_fsverity_ioend, work);
> + struct iomap_ioend *ioend = fsv_ioend->ioend;
> + struct bio *bio = &ioend->io_bio;
> +
> + mempool_free(fsv_ioend, &xfs_fsverity_ioend_pool);
> +
> + if (!bio->bi_status)
> + fsverity_verify_bio(ioend->io_vi, bio);
> +
> + iomap_finish_ioends(ioend, blk_status_to_errno(bio->bi_status));
> +}
>
> static void
> xfs_dio_bounce_end_io(
> @@ -93,12 +112,14 @@ xfs_read_bounce_and_resubmit(
>
> static void
> xfs_end_io_read(
> - struct bio *bio)
> + struct bio *bio)
> {
> - struct iomap_ioend *ioend = iomap_ioend_from_bio(bio);
> - struct xfs_inode *ip = XFS_I(ioend->io_inode);
> - struct xfs_mount *mp = ip->i_mount;
> - int error = blk_status_to_errno(bio->bi_status);
> + struct iomap_ioend *ioend = iomap_ioend_from_bio(bio);
> + struct xfs_inode *ip = XFS_I(ioend->io_inode);
> + struct xfs_mount *mp = ip->i_mount;
> + int error =
> + blk_status_to_errno(bio->bi_status);
> + struct xfs_fsverity_ioend *fsv_ioend;
>
> if (!error && (ioend->io_flags & IOMAP_IOEND_INTEGRITY)) {
> error = iomap_ioend_integrity_verify(ioend);
> @@ -117,6 +138,24 @@ xfs_end_io_read(
> }
> }
>
> + /*
> + * If we have fsverity on this bio, we need to run fsverity verification
> + * of data folios from a separate fsverity workqueue. This is necessary
> + * to avoid deadlocking due to fsverity issuing more reads of fsverity
> + * metadata which would be processed by the same worker in the BIO
> + * completion workqueue.
> + */
> + if (IS_ENABLED(CONFIG_FS_VERITY) && !error && ioend->io_vi &&
> + xfs_fsverity_is_file_data(ip, ioend->io_offset)) {
> + fsv_ioend = mempool_alloc(&xfs_fsverity_ioend_pool,
> + GFP_NOFS);
> + fsv_ioend->ioend = ioend;
> + INIT_WORK(&fsv_ioend->work, xfs_end_fsverity_io_read);
> +
> + fsverity_enqueue_verify_work(&fsv_ioend->work);
> + return;
> + }
> +
> iomap_finish_ioends(ioend, error);
> }
>
> @@ -125,13 +164,15 @@ xfs_ioend_submit_read(
> struct inode *inode,
> struct bio *bio,
> loff_t file_offset,
> - u16 ioend_flags)
> + u16 ioend_flags,
> + struct fsverity_info *vi)
> {
> struct xfs_inode *ip = XFS_I(inode);
> struct xfs_mount *mp = ip->i_mount;
> struct iomap_ioend *ioend;
>
> ioend = iomap_init_ioend(inode, bio, file_offset, ioend_flags);
> + ioend->io_vi = vi;
> if ((ioend_flags & IOMAP_IOEND_DIRECT) &&
> READ_ONCE(mp->m_read_bounce) == XFS_READ_BOUNCE_ALWAYS) {
> iomap_bounce_read(ioend, bdev_logical_block_size(bio->bi_bdev),
> diff --git a/fs/xfs/xfs_ioend.h b/fs/xfs/xfs_ioend.h
> index 7c2a1ea3e6ed..992c248a693a 100644
> --- a/fs/xfs/xfs_ioend.h
> +++ b/fs/xfs/xfs_ioend.h
> @@ -2,6 +2,8 @@
> #ifndef __XFS_IOEND_H
> #define __XFS_IOEND_H
>
> +#include <linux/fsverity.h>
> +
> /*
> * Fast and loose check if this write could update the on-disk inode size.
> */
> @@ -13,6 +15,6 @@ static inline bool xfs_ioend_is_append(struct iomap_ioend *ioend)
>
> void xfs_end_bio(struct bio *bio);
> void xfs_ioend_submit_read(struct inode *inode, struct bio *bio,
> - loff_t file_offset, u16 ioend_flags);
> + loff_t file_offset, u16 ioend_flags, struct fsverity_info *vi);
>
> #endif /* __XFS_IOEND_H */
> diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
> index 5a06132aa384..3a7d7334e9a6 100644
> --- a/fs/xfs/xfs_super.c
> +++ b/fs/xfs/xfs_super.c
> @@ -2516,8 +2516,20 @@ xfs_init_caches(void)
> if (!xfs_parent_args_cache)
> goto out_destroy_xmi_cache;
>
> +#ifdef CONFIG_FS_VERITY
> + xfs_fsverity_ioend_cache = kmem_cache_create("xfs_fsverity_ioend",
> + sizeof(struct xfs_fsverity_ioend),
> + 0, 0, NULL);
> + if (!xfs_fsverity_ioend_cache)
> + goto out_destroy_parent_args_cache;
> +#endif
> +
> return 0;
>
> +#ifdef CONFIG_FS_VERITY
> + out_destroy_parent_args_cache:
> + kmem_cache_destroy(xfs_parent_args_cache);
> +#endif
> out_destroy_xmi_cache:
> kmem_cache_destroy(xfs_xmi_cache);
> out_destroy_xmd_cache:
> @@ -2580,6 +2592,9 @@ xfs_destroy_caches(void)
> * destroy caches.
> */
> rcu_barrier();
> +#ifdef CONFIG_FS_VERITY
> + kmem_cache_destroy(xfs_fsverity_ioend_cache);
> +#endif
> kmem_cache_destroy(xfs_parent_args_cache);
> kmem_cache_destroy(xfs_xmd_cache);
> kmem_cache_destroy(xfs_xmi_cache);
> @@ -2660,10 +2675,14 @@ init_xfs_fs(void)
> if (error)
> goto out;
>
> - error = xfs_init_workqueues();
> + error = xfs_fsverity_init();
> if (error)
> goto out_destroy_caches;
>
> + error = xfs_init_workqueues();
> + if (error)
> + goto out_fsverity_exit;
> +
> error = xfs_mru_cache_init();
> if (error)
> goto out_destroy_wq;
> @@ -2740,6 +2759,8 @@ init_xfs_fs(void)
> xfs_mru_cache_uninit();
> out_destroy_wq:
> xfs_destroy_workqueues();
> + out_fsverity_exit:
> + xfs_fsverity_exit();
> out_destroy_caches:
> xfs_destroy_caches();
> out:
> @@ -2763,6 +2784,7 @@ exit_xfs_fs(void)
> xfs_cleanup_procfs();
> xfs_mru_cache_uninit();
> xfs_destroy_workqueues();
> + xfs_fsverity_exit();
> xfs_destroy_caches();
> xfs_uuid_table_free();
> }
> --
> 2.54.0
>
>
---end quoted text---
^ permalink raw reply [flat|nested] 42+ messages in thread
* [PATCH v18 07/15] xfs: add XFS_BMAPI_UNWRITTEN to unmap unwritten extents in __xfs_bunmapi()
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
` (5 preceding siblings ...)
2026-11-02 14:41 ` [PATCH v18 06/15] xfs: use read ioend for fsverity data verification Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 15:02 ` sashiko-bot
2026-11-03 9:41 ` Christoph Hellwig
2026-11-02 14:41 ` [PATCH v18 08/15] xfs: don't remove written extents past EOF on fsverity inodes Andrey Albershteyn
` (7 subsequent siblings)
14 siblings, 2 replies; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david, Darrick J. Wong
The __xfs_bunmapi() is used to remove all extents past EOF. Add
XFS_BMAPI_UNWRITTEN to remove only unwritten extents, while leaving
written ones in place. This will be used in following patch to clean up
unwritten extents on fsverity inodes.
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/libxfs/xfs_bmap.c | 6 +++++-
fs/xfs/libxfs/xfs_bmap.h | 6 +++++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c
index a35a44cf8c63..706f2ead42f5 100644
--- a/fs/xfs/libxfs/xfs_bmap.c
+++ b/fs/xfs/libxfs/xfs_bmap.c
@@ -5279,6 +5279,10 @@ __xfs_bunmapi(
if (del.br_startoff + del.br_blockcount > end + 1)
del.br_blockcount = end + 1 - del.br_startoff;
+ if ((flags & XFS_BMAPI_UNWRITTEN) &&
+ del.br_state != XFS_EXT_UNWRITTEN)
+ goto skip;
+
if (!isrt || (flags & XFS_BMAPI_REMAP))
goto delete;
@@ -5413,7 +5417,7 @@ __xfs_bunmapi(
if (error)
goto error0;
}
-
+skip:
end = del.br_startoff - 1;
nodelete:
/*
diff --git a/fs/xfs/libxfs/xfs_bmap.h b/fs/xfs/libxfs/xfs_bmap.h
index e9f5416e4c05..25b24ff8e4de 100644
--- a/fs/xfs/libxfs/xfs_bmap.h
+++ b/fs/xfs/libxfs/xfs_bmap.h
@@ -90,6 +90,9 @@ struct xfs_bmalloca {
/* Try to align allocations to the extent size hint */
#define XFS_BMAPI_EXTSZALIGN (1u << 11)
+/* Process unwritten extents only. Used for unmapping */
+#define XFS_BMAPI_UNWRITTEN (1u << 12)
+
#define XFS_BMAPI_FLAGS \
{ XFS_BMAPI_ENTIRE, "ENTIRE" }, \
{ XFS_BMAPI_METADATA, "METADATA" }, \
@@ -102,7 +105,8 @@ struct xfs_bmalloca {
{ XFS_BMAPI_COWFORK, "COWFORK" }, \
{ XFS_BMAPI_NODISCARD, "NODISCARD" }, \
{ XFS_BMAPI_NORMAP, "NORMAP" },\
- { XFS_BMAPI_EXTSZALIGN, "EXTSZALIGN" }
+ { XFS_BMAPI_EXTSZALIGN, "EXTSZALIGN" }, \
+ { XFS_BMAPI_UNWRITTEN, "UNWRITTEN" }
static inline int xfs_bmapi_aflag(int w)
--
2.54.0
^ permalink raw reply related [flat|nested] 42+ messages in thread* Re: [PATCH v18 07/15] xfs: add XFS_BMAPI_UNWRITTEN to unmap unwritten extents in __xfs_bunmapi()
2026-11-02 14:41 ` [PATCH v18 07/15] xfs: add XFS_BMAPI_UNWRITTEN to unmap unwritten extents in __xfs_bunmapi() Andrey Albershteyn
@ 2026-11-02 15:02 ` sashiko-bot
2026-11-03 9:41 ` Christoph Hellwig
1 sibling, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-11-02 15:02 UTC (permalink / raw)
To: Andrey Albershteyn, ebiggers, hch, Carlos Maiolino, fsverity
Cc: david, Darrick J. Wong
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] xfs: __xfs_bunmapi() with XFS_BMAPI_UNWRITTEN unmaps written rt extents
--
commit 823f5c04cb0ac218e733c0788aa8567888bdc3de
Author: Andrey Albershteyn <aalbersh@kernel.org>
xfs: add XFS_BMAPI_UNWRITTEN to unmap unwritten extents in __xfs_bunmapi()
This commit adds the XFS_BMAPI_UNWRITTEN flag to __xfs_bunmapi() so callers
can unmap only unwritten extents while leaving written extents in place.
> diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c
> index a35a44cf8c635..706f2ead42f58 100644
> --- a/fs/xfs/libxfs/xfs_bmap.c
> +++ b/fs/xfs/libxfs/xfs_bmap.c
> @@ -5279,6 +5279,10 @@ __xfs_bunmapi(
> if (del.br_startoff + del.br_blockcount > end + 1)
> del.br_blockcount = end + 1 - del.br_startoff;
>
> + if ((flags & XFS_BMAPI_UNWRITTEN) &&
> + del.br_state != XFS_EXT_UNWRITTEN)
> + goto skip;
> +
> if (!isrt || (flags & XFS_BMAPI_REMAP))
> goto delete;
[Severity: High]
As reported by Darrick J. Wong on v16 [1], when __xfs_bunmapi() is called
with XFS_BMAPI_UNWRITTEN on a realtime inode with sb_rextsize > 1, can an
unwritten tail of a partially written realtime extent cause the written left
neighbor to be converted to unwritten and freed?
Specifically, if del is an unwritten extent (del.br_state ==
XFS_EXT_UNWRITTEN) at the end of a realtime allocation unit whose head
extent prev is written (prev.br_state == XFS_EXT_NORM), it passes this check
and enters the realtime alignment logic in __xfs_bunmapi():
mod = xfs_rtb_to_rtxoff(mp, del.br_startblock);
if (mod) {
...
} else if (del.br_startoff == start &&
(del.br_state == XFS_EXT_UNWRITTEN ||
tp->t_blk_res == 0)) {
...
continue;
} else if (del.br_state == XFS_EXT_UNWRITTEN) {
...
if (!xfs_iext_prev_extent(ifp, &icur, &prev))
ASSERT(0);
ASSERT(prev.br_state == XFS_EXT_NORM);
...
prev.br_state = XFS_EXT_UNWRITTEN;
error = xfs_bmap_add_extent_unwritten_real(tp,
ip, whichfork, &icur, &cur,
&prev, &logflags);
if (error)
goto error0;
goto nodelete;
}
When del.br_startoff > start and del.br_blockcount <= off, this branch
assumes the caller is also unmapping the preceding range. It converts the
written left neighbor prev to XFS_EXT_UNWRITTEN (merging it with del) and
jumps to nodelete. On the next loop iteration, the newly merged extent is
both unwritten and aligned to the realtime extent boundary, so
__xfs_bunmapi() jumps to delete and frees the written blocks as well.
Although xfs_fsverity_begin_enable() in the subsequent fs-verity patch
checks xfs_inode_has_bigrtalloc(ip), enable_verity() drops inode_lock()
after begin_enable_verity() while building the Merkle tree. On an empty or
sparse file where xfs_inode_has_filedata(ip) is false, a concurrent
FS_IOC_FSSETXATTR ioctl via xfs_ioctl_setattr_xflags() (which does not check
XFS_VERITY_CONSTRUCTION) can set XFS_DIFLAG_REALTIME before
xfs_fsverity_end_enable() writes the verity descriptor and calls
xfs_free_eofblocks(). Additionally, xfs_dinode_verify() does not reject
on-disk inodes that have both XFS_DIFLAG2_VERITY and XFS_DIFLAG_REALTIME
when sb_rextsize > 1.
Should __xfs_bunmapi() skip partial unwritten realtime extents when
XFS_BMAPI_UNWRITTEN is set instead of converting the written left neighbor?
[1] https://lore.kernel.org/r/20260922044911.GA6239@frogsfrogsfrogs
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144147.2393369-1-aalbersh@kernel.org?part=7
^ permalink raw reply [flat|nested] 42+ messages in thread* Re: [PATCH v18 07/15] xfs: add XFS_BMAPI_UNWRITTEN to unmap unwritten extents in __xfs_bunmapi()
2026-11-02 14:41 ` [PATCH v18 07/15] xfs: add XFS_BMAPI_UNWRITTEN to unmap unwritten extents in __xfs_bunmapi() Andrey Albershteyn
2026-11-02 15:02 ` sashiko-bot
@ 2026-11-03 9:41 ` Christoph Hellwig
2026-11-03 14:31 ` Andrey Albershteyn
1 sibling, 1 reply; 42+ messages in thread
From: Christoph Hellwig @ 2026-11-03 9:41 UTC (permalink / raw)
To: Andrey Albershteyn
Cc: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs,
linux-fsdevel, david, Darrick J. Wong
On Mon, Nov 02, 2026 at 03:41:37PM +0100, Andrey Albershteyn wrote:
> The __xfs_bunmapi() is used to remove all extents past EOF. Add
> XFS_BMAPI_UNWRITTEN to remove only unwritten extents, while leaving
> written ones in place. This will be used in following patch to clean up
> unwritten extents on fsverity inodes.
As discussed before, we either need to fix the Sashiko complains (which
could get really annoying), or disallow fsverity on RT files with
rt extent size > 1. And probably also add a soft assert about this
to __xfs_bunmapi itself to make the limitations clear.
Otherwise this looks good.
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v18 07/15] xfs: add XFS_BMAPI_UNWRITTEN to unmap unwritten extents in __xfs_bunmapi()
2026-11-03 9:41 ` Christoph Hellwig
@ 2026-11-03 14:31 ` Andrey Albershteyn
2026-11-03 14:39 ` Christoph Hellwig
0 siblings, 1 reply; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-03 14:31 UTC (permalink / raw)
To: Christoph Hellwig
Cc: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs,
linux-fsdevel, david, Darrick J. Wong
On 2026-11-03 01:41:19, Christoph Hellwig wrote:
> On Mon, Nov 02, 2026 at 03:41:37PM +0100, Andrey Albershteyn wrote:
> > The __xfs_bunmapi() is used to remove all extents past EOF. Add
> > XFS_BMAPI_UNWRITTEN to remove only unwritten extents, while leaving
> > written ones in place. This will be used in following patch to clean up
> > unwritten extents on fsverity inodes.
>
> As discussed before, we either need to fix the Sashiko complains (which
> could get really annoying), or disallow fsverity on RT files with
> rt extent size > 1. And probably also add a soft assert about this
> to __xfs_bunmapi itself to make the limitations clear.
>
> Otherwise this looks good.
>
Yes, I added the check in "[PATCH v18 09/15] xfs: add fs-verity
support" [1] in xfs_fsverity_begin_enable()
+ /*
+ * fs-verity stores the Merkle tree past EOF in units of the filesystem
+ * block size, so it cannot support realtime files whose allocation unit
+ * (extent size) is larger than the block size.
+ */
+ if (xfs_inode_has_bigrtalloc(ip))
+ return -EINVAL;
1: https://lore.kernel.org/linux-xfs/20261102144147.2393369-10-aalbersh@kernel.org/
--
- Andrey
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v18 07/15] xfs: add XFS_BMAPI_UNWRITTEN to unmap unwritten extents in __xfs_bunmapi()
2026-11-03 14:31 ` Andrey Albershteyn
@ 2026-11-03 14:39 ` Christoph Hellwig
2026-11-03 14:45 ` Andrey Albershteyn
0 siblings, 1 reply; 42+ messages in thread
From: Christoph Hellwig @ 2026-11-03 14:39 UTC (permalink / raw)
To: Andrey Albershteyn
Cc: Christoph Hellwig, ebiggers, hch, Carlos Maiolino, fsverity,
linux-xfs, linux-fsdevel, david, Darrick J. Wong
On Tue, Nov 03, 2026 at 03:31:15PM +0100, Andrey Albershteyn wrote:
> On 2026-11-03 01:41:19, Christoph Hellwig wrote:
> > On Mon, Nov 02, 2026 at 03:41:37PM +0100, Andrey Albershteyn wrote:
> > > The __xfs_bunmapi() is used to remove all extents past EOF. Add
> > > XFS_BMAPI_UNWRITTEN to remove only unwritten extents, while leaving
> > > written ones in place. This will be used in following patch to clean up
> > > unwritten extents on fsverity inodes.
> >
> > As discussed before, we either need to fix the Sashiko complains (which
> > could get really annoying), or disallow fsverity on RT files with
> > rt extent size > 1. And probably also add a soft assert about this
> > to __xfs_bunmapi itself to make the limitations clear.
> >
> > Otherwise this looks good.
> >
>
> Yes, I added the check in "[PATCH v18 09/15] xfs: add fs-verity
> support" [1] in xfs_fsverity_begin_enable()
>
> + /*
> + * fs-verity stores the Merkle tree past EOF in units of the filesystem
> + * block size, so it cannot support realtime files whose allocation unit
> + * (extent size) is larger than the block size.
> + */
> + if (xfs_inode_has_bigrtalloc(ip))
Please also add a similar check/comment in __xfs_bunampi for the
XFS_BMAPI_UNWRITTEN case as a safeguard and documentation of the
limitations.
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v18 07/15] xfs: add XFS_BMAPI_UNWRITTEN to unmap unwritten extents in __xfs_bunmapi()
2026-11-03 14:39 ` Christoph Hellwig
@ 2026-11-03 14:45 ` Andrey Albershteyn
2026-11-03 14:49 ` Christoph Hellwig
0 siblings, 1 reply; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-03 14:45 UTC (permalink / raw)
To: Christoph Hellwig
Cc: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs,
linux-fsdevel, david, Darrick J. Wong
On 2026-11-03 06:39:12, Christoph Hellwig wrote:
> On Tue, Nov 03, 2026 at 03:31:15PM +0100, Andrey Albershteyn wrote:
> > On 2026-11-03 01:41:19, Christoph Hellwig wrote:
> > > On Mon, Nov 02, 2026 at 03:41:37PM +0100, Andrey Albershteyn wrote:
> > > > The __xfs_bunmapi() is used to remove all extents past EOF. Add
> > > > XFS_BMAPI_UNWRITTEN to remove only unwritten extents, while leaving
> > > > written ones in place. This will be used in following patch to clean up
> > > > unwritten extents on fsverity inodes.
> > >
> > > As discussed before, we either need to fix the Sashiko complains (which
> > > could get really annoying), or disallow fsverity on RT files with
> > > rt extent size > 1. And probably also add a soft assert about this
> > > to __xfs_bunmapi itself to make the limitations clear.
> > >
> > > Otherwise this looks good.
> > >
> >
> > Yes, I added the check in "[PATCH v18 09/15] xfs: add fs-verity
> > support" [1] in xfs_fsverity_begin_enable()
> >
> > + /*
> > + * fs-verity stores the Merkle tree past EOF in units of the filesystem
> > + * block size, so it cannot support realtime files whose allocation unit
> > + * (extent size) is larger than the block size.
> > + */
> > + if (xfs_inode_has_bigrtalloc(ip))
>
> Please also add a similar check/comment in __xfs_bunampi for the
> XFS_BMAPI_UNWRITTEN case as a safeguard and documentation of the
> limitations.
>
Sure
I see that sashiko also noticed that realtime could be enabled
betwee _begin_enable() and _end_enable() as inode lock is not held
over that period. I will also add check there to forbid that, but
the xfs_dinode_verify() comment seems incorrect.
--
- Andrey
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v18 07/15] xfs: add XFS_BMAPI_UNWRITTEN to unmap unwritten extents in __xfs_bunmapi()
2026-11-03 14:45 ` Andrey Albershteyn
@ 2026-11-03 14:49 ` Christoph Hellwig
2026-11-03 15:17 ` Andrey Albershteyn
0 siblings, 1 reply; 42+ messages in thread
From: Christoph Hellwig @ 2026-11-03 14:49 UTC (permalink / raw)
To: Andrey Albershteyn
Cc: Christoph Hellwig, ebiggers, hch, Carlos Maiolino, fsverity,
linux-xfs, linux-fsdevel, david, Darrick J. Wong
On Tue, Nov 03, 2026 at 03:45:15PM +0100, Andrey Albershteyn wrote:
>
> I see that sashiko also noticed that realtime could be enabled
> betwee _begin_enable() and _end_enable() as inode lock is not held
> over that period. I will also add check there to forbid that, but
> the xfs_dinode_verify() comment seems incorrect.
I don't think it's quite incorrect, although maybe a bit overzealous
given that we should not be reading an inode from disk that's not
been completed. But adding a check there is cheap, so why not?
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH v18 07/15] xfs: add XFS_BMAPI_UNWRITTEN to unmap unwritten extents in __xfs_bunmapi()
2026-11-03 14:49 ` Christoph Hellwig
@ 2026-11-03 15:17 ` Andrey Albershteyn
0 siblings, 0 replies; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-03 15:17 UTC (permalink / raw)
To: Christoph Hellwig
Cc: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs,
linux-fsdevel, david, Darrick J. Wong
On 2026-11-03 06:49:30, Christoph Hellwig wrote:
> On Tue, Nov 03, 2026 at 03:45:15PM +0100, Andrey Albershteyn wrote:
> >
> > I see that sashiko also noticed that realtime could be enabled
> > betwee _begin_enable() and _end_enable() as inode lock is not held
> > over that period. I will also add check there to forbid that, but
> > the xfs_dinode_verify() comment seems incorrect.
>
> I don't think it's quite incorrect, although maybe a bit overzealous
> given that we should not be reading an inode from disk that's not
> been completed. But adding a check there is cheap, so why not?
>
>
yeah, not incorrect just seem safe to leave it out. Even if both
flags happens to be set, the fsverity inodes will skip
xfs_free_eofblocks().
but sure it's cheap, I will add it
--
- Andrey
^ permalink raw reply [flat|nested] 42+ messages in thread
* [PATCH v18 08/15] xfs: don't remove written extents past EOF on fsverity inodes
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
` (6 preceding siblings ...)
2026-11-02 14:41 ` [PATCH v18 07/15] xfs: add XFS_BMAPI_UNWRITTEN to unmap unwritten extents in __xfs_bunmapi() Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 15:11 ` sashiko-bot
2026-11-03 9:42 ` Christoph Hellwig
2026-11-02 14:41 ` [PATCH v18 09/15] xfs: add fs-verity support Andrey Albershteyn
` (6 subsequent siblings)
14 siblings, 2 replies; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david, Darrick J. Wong
xfs_free_eofblocks() removes all extents past EOF unless the
XFS_DIFLAG_PREALLOC or XFS_DIFLAG_APPEND flags are set. This is
undesired for fsverity as it stores metadata beyond EOF. However, while
merkle tree is being built, delayed preallocation and unwritten extents
are used. After metadata construction is done, fsverity inodes becomes
read-only and won't be changed anymore, none of these unwritten extents
or preallocations in post EOF region will be used.
Let xfs_free_eofblocks() be called on fsverity inode as usual to remove
anything which is not written extent. However, inodes which are
undergoing merkle tree construction need to be skipped in case reclaim
takes place.
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/xfs_bmap_util.c | 31 ++++++++++++++++++++++++++++---
1 file changed, 28 insertions(+), 3 deletions(-)
diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c
index 865aec3ae5ab..32cec523fd52 100644
--- a/fs/xfs/xfs_bmap_util.c
+++ b/fs/xfs/xfs_bmap_util.c
@@ -31,6 +31,7 @@
#include "xfs_rtbitmap.h"
#include "xfs_rtgroup.h"
#include "xfs_zone_alloc.h"
+#include <linux/fsverity.h>
/* Kernel only BMAP related definitions and functions */
@@ -553,6 +554,15 @@ xfs_can_free_eofblocks(
if (last_fsb <= end_fsb)
return false;
+ /*
+ * Don't clean fsverity inodes as they already have been cleaned up and
+ * are read-only. Skip inodes which have merkle tree being built, the
+ * merkle tree is written beyond EOF
+ */
+ if (fsverity_active(VFS_IC(ip)) ||
+ xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+ return false;
+
/*
* Check if there is an post-EOF extent to free. If there are any
* delalloc blocks attached to the inode (data fork delalloc
@@ -579,6 +589,9 @@ xfs_free_eofblocks(
struct xfs_trans *tp;
struct xfs_mount *mp = ip->i_mount;
int error;
+ int bmapi_flags = XFS_BMAPI_NODISCARD;
+ bool has_verity =
+ ip->i_diflags2 & XFS_DIFLAG2_VERITY;
/* Attach the dquots to the inode up front. */
error = xfs_qm_dqattach(ip);
@@ -593,15 +606,20 @@ xfs_free_eofblocks(
*
* Note that this means we also leave speculative preallocations in
* place for preallocated files.
+ *
+ * Clean up delalloc reservations for fsverity too as those won't be
+ * used
*/
- if (ip->i_diflags & (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND)) {
+ if (ip->i_diflags & (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND) ||
+ has_verity) {
if (ip->i_delayed_blks) {
xfs_bmap_punch_delalloc_range(ip, XFS_DATA_FORK,
round_up(XFS_ISIZE(ip), mp->m_sb.sb_blocksize),
LLONG_MAX, NULL);
}
xfs_inode_clear_eofblocks_tag(ip);
- return 0;
+ if (!has_verity)
+ return 0;
}
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp);
@@ -613,6 +631,13 @@ xfs_free_eofblocks(
xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_trans_ijoin(tp, ip, 0);
+ /*
+ * While fs-verity writes metadata after EOF, it can leave unwritten
+ * preallocations. Clear all that out.
+ */
+ if (has_verity)
+ bmapi_flags |= XFS_BMAPI_UNWRITTEN;
+
/*
* Do not update the on-disk file size. If we update the on-disk file
* size and then the system crashes before the contents of the file are
@@ -620,7 +645,7 @@ xfs_free_eofblocks(
* bug).
*/
error = xfs_itruncate_extents_flags(&tp, ip, XFS_DATA_FORK,
- XFS_ISIZE(ip), XFS_BMAPI_NODISCARD);
+ XFS_ISIZE(ip), bmapi_flags);
if (error)
goto err_cancel;
--
2.54.0
^ permalink raw reply related [flat|nested] 42+ messages in thread* Re: [PATCH v18 08/15] xfs: don't remove written extents past EOF on fsverity inodes
2026-11-02 14:41 ` [PATCH v18 08/15] xfs: don't remove written extents past EOF on fsverity inodes Andrey Albershteyn
@ 2026-11-02 15:11 ` sashiko-bot
2026-11-03 9:42 ` Christoph Hellwig
1 sibling, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-11-02 15:11 UTC (permalink / raw)
To: Andrey Albershteyn, ebiggers, hch, Carlos Maiolino, fsverity
Cc: david, Darrick J. Wong
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] xfs: written verity extents unmapped in xfs_free_eofblocks() on bigrtalloc
- [Medium] xfs: fsverity_active() check in xfs_can_free_eofblocks() fails if !VERITY
- [Medium] xfs: premature XFS_IEOFBLOCKS clear in xfs_can_free_eofblocks()
--
commit 2307cc3d64e773ab52b2cf76f7c9b88f364d2957
Author: Andrey Albershteyn <aalbersh@kernel.org>
xfs: don't remove written extents past EOF on fsverity inodes
This commit updates xfs_can_free_eofblocks() to skip active fsverity inodes
and inodes undergoing Merkle tree construction, and modifies
xfs_free_eofblocks() to punch only delalloc and unwritten extents past EOF on
fsverity inodes while preserving written metadata extents.
> diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c
> index 865aec3ae5ab3..32cec523fd52d 100644
> --- a/fs/xfs/xfs_bmap_util.c
> +++ b/fs/xfs/xfs_bmap_util.c
> @@ -31,6 +31,7 @@
> #include "xfs_rtbitmap.h"
> #include "xfs_rtgroup.h"
> #include "xfs_zone_alloc.h"
> +#include <linux/fsverity.h>
>
> /* Kernel only BMAP related definitions and functions */
>
> @@ -553,6 +554,15 @@ xfs_can_free_eofblocks(
> if (last_fsb <= end_fsb)
> return false;
>
> + /*
> + * Don't clean fsverity inodes as they already have been cleaned up and
> + * are read-only. Skip inodes which have merkle tree being built, the
> + * merkle tree is written beyond EOF
> + */
> + if (fsverity_active(VFS_IC(ip)) ||
> + xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
> + return false;
[Severity: Medium]
As reported by Sashiko on v16 [1], in xfs_can_free_eofblocks() in
fs/xfs/xfs_bmap_util.c, does checking fsverity_active(VFS_IC(ip)) instead of
ip->i_diflags2 & XFS_DIFLAG2_VERITY cause sealed fsverity inodes to go
through unnecessary inactivation and tr_itruncate transactions on every VFS
cache eviction when CONFIG_FS_VERITY=n?
The follow-up commit ("xfs: enable ro-compat fs-verity flag") unconditionally
includes XFS_SB_FEAT_RO_COMPAT_VERITY in XFS_SB_FEAT_RO_COMPAT_ALL, allowing
read-write mounts of fsverity filesystems even on kernels built with
CONFIG_FS_VERITY=n. When CONFIG_FS_VERITY=n, fsverity_active() in
include/linux/fsverity.h is a stub returning false:
static inline bool fsverity_active(const struct inode *inode)
{
return false;
}
Whereas xfs_free_eofblocks() checks:
bool has_verity = ip->i_diflags2 & XFS_DIFLAG2_VERITY;
When a sealed extent-format fsverity inode (with written Merkle tree and
descriptor extents past EOF) is looked up and later evicted on a
CONFIG_FS_VERITY=n kernel:
xfs_inode_mark_reclaimable()
-> xfs_inode_needs_inactive()
-> xfs_can_free_eofblocks()
xfs_can_free_eofblocks() sees fsverity_active() == false, finds the post-EOF
Merkle tree extents via xfs_iext_lookup_extent(), and returns true. This
queues the inode for background inactivation, where xfs_inactive() ->
xfs_free_eofblocks() sees has_verity == true, allocates a tr_itruncate
transaction, walks all post-EOF extents with XFS_BMAPI_UNWRITTEN without
freeing the written Merkle tree extents, logs XFS_ILOG_CORE in
xfs_itruncate_extents_flags(), and commits a dirty transaction on every
eviction.
Should xfs_can_free_eofblocks() check ip->i_diflags2 & XFS_DIFLAG2_VERITY (or
IS_VERITY(VFS_IC(ip))) to match xfs_free_eofblocks()?
[Severity: Medium]
Also in xfs_can_free_eofblocks() in fs/xfs/xfs_bmap_util.c, can returning
false when XFS_VERITY_CONSTRUCTION is set cause xfs_inode_free_eofblocks() to
prematurely clear the XFS_IEOFBLOCKS tag and leak post-EOF speculative
preallocations if Merkle tree construction later fails?
During FS_IOC_ENABLE_VERITY, enable_verity() in fs/verity/enable.c acquires
inode_lock(inode) (XFS_IOLOCK_EXCL), calls ->begin_enable_verity() which sets
XFS_VERITY_CONSTRUCTION, and then drops inode_lock(inode) while calling
build_merkle_tree(). While build_merkle_tree() writes past EOF without
holding XFS_IOLOCK_EXCL:
xfs_fsverity_write_merkle()
-> iomap_fsverity_write()
-> xfs_buffered_write_iomap_begin()
-> xfs_bmapi_reserve_delalloc()
-> xfs_inode_set_eofblocks_tag()
If background xfs_blockgc_worker() or a synchronous -ENOSPC/-EDQUOT scan runs
xfs_inode_free_eofblocks() in fs/xfs/xfs_icache.c while
XFS_VERITY_CONSTRUCTION is set, xfs_ilock_nowait(ip, XFS_IOLOCK_EXCL)
succeeds and xfs_can_free_eofblocks(ip) returns false:
if (xfs_can_free_eofblocks(ip))
return xfs_free_eofblocks(ip);
/* inode could be preallocated */
trace_xfs_inode_free_eofblocks_invalid(ip);
xfs_inode_clear_eofblocks_tag(ip);
return 0;
This removes the inode from the per-AG XFS_ICI_BLOCKGC_TAG radix tree while
post-EOF delalloc and speculative preallocation blocks are still attached. If
build_merkle_tree() subsequently fails and enable_verity() invokes the
rollback path ->end_enable_verity(filp, NULL, 0), xfs_fsverity_end_enable()
jumps to out with error == 0 and clears XFS_VERITY_CONSTRUCTION without
calling xfs_free_eofblocks() or xfs_fsverity_reset_inode(). Because the file
descriptor for FS_IOC_ENABLE_VERITY is opened O_RDONLY, xfs_release() does
not free post-EOF blocks on close either, leaving the post-EOF blocks
untracked by blockgc until inode eviction.
>
> /*
> * Check if there is an post-EOF extent to free. If there are any
> @@ -579,6 +589,9 @@ xfs_free_eofblocks(
> struct xfs_trans *tp;
> struct xfs_mount *mp = ip->i_mount;
> int error;
> + int bmapi_flags = XFS_BMAPI_NODISCARD;
> + bool has_verity =
> + ip->i_diflags2 & XFS_DIFLAG2_VERITY;
>
> /* Attach the dquots to the inode up front. */
> error = xfs_qm_dqattach(ip);
> @@ -593,15 +606,20 @@ xfs_free_eofblocks(
> *
> * Note that this means we also leave speculative preallocations in
> * place for preallocated files.
> + *
> + * Clean up delalloc reservations for fsverity too as those won't be
> + * used
> */
> - if (ip->i_diflags & (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND)) {
> + if (ip->i_diflags & (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND) ||
> + has_verity) {
> if (ip->i_delayed_blks) {
> xfs_bmap_punch_delalloc_range(ip, XFS_DATA_FORK,
> round_up(XFS_ISIZE(ip), mp->m_sb.sb_blocksize),
> LLONG_MAX, NULL);
> }
> xfs_inode_clear_eofblocks_tag(ip);
> - return 0;
> + if (!has_verity)
> + return 0;
> }
>
> error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp);
> @@ -613,6 +631,13 @@ xfs_free_eofblocks(
> xfs_ilock(ip, XFS_ILOCK_EXCL);
> xfs_trans_ijoin(tp, ip, 0);
>
> + /*
> + * While fs-verity writes metadata after EOF, it can leave unwritten
> + * preallocations. Clear all that out.
> + */
> + if (has_verity)
> + bmapi_flags |= XFS_BMAPI_UNWRITTEN;
> +
> /*
> * Do not update the on-disk file size. If we update the on-disk file
> * size and then the system crashes before the contents of the file are
> @@ -620,7 +645,7 @@ xfs_free_eofblocks(
> * bug).
> */
> error = xfs_itruncate_extents_flags(&tp, ip, XFS_DATA_FORK,
> - XFS_ISIZE(ip), XFS_BMAPI_NODISCARD);
> + XFS_ISIZE(ip), bmapi_flags);
[Severity: High]
In xfs_free_eofblocks() in fs/xfs/xfs_bmap_util.c, if an inode has both
XFS_DIFLAG2_VERITY and bigrtalloc (xfs_inode_has_bigrtalloc(ip)), can calling
xfs_itruncate_extents_flags() with XFS_BMAPI_UNWRITTEN and a 0-block
transaction reservation convert written post-EOF fsverity metadata blocks to
unwritten and unmap them (or trigger an -ENOSPC filesystem shutdown)?
Although xfs_fsverity_begin_enable() in the follow-up patch ("xfs: add
fs-verity support") checks xfs_inode_has_bigrtalloc(ip), enable_verity() in
fs/verity/enable.c drops inode_lock(inode) immediately after
->begin_enable_verity() returns. While inode_lock(inode) is dropped on a file
with no allocated data blocks yet (where xfs_inode_has_filedata(ip) is false,
such as an empty or hole-only file before Merkle tree blocks are allocated), a
concurrent FS_IOC_FSSETXATTR / file_setattr() call reaches:
xfs_fileattr_set()
-> xfs_ioctl_setattr_xflags()
which does not check XFS_VERITY_CONSTRUCTION or XFS_DIFLAG2_VERITY and allows
setting XFS_DIFLAG_REALTIME on the inode. In addition, xfs_dinode_verify() in
fs/xfs/libxfs/xfs_inode_buf.c does not reject on-disk inodes with both
XFS_DIFLAG2_VERITY and bigrtalloc.
When xfs_fsverity_end_enable() writes the fsverity descriptor on a bigrtalloc
inode (sb_rextsize > 1), xfs_buffered_write_iomap_begin() delegates via
xfs_get_extsz_hint() to xfs_direct_write_iomap_begin(), preallocating an
sb_rextsize-aligned unwritten realtime extent and converting only the
written descriptor block(s) to XFS_EXT_NORM, leaving an XFS_EXT_UNWRITTEN
tail in the same realtime extent.
When xfs_free_eofblocks() then calls xfs_itruncate_extents_flags() with
XFS_BMAPI_UNWRITTEN and tp->t_blk_res == 0, __xfs_bunmapi() in
fs/xfs/libxfs/xfs_bmap.c hits the unwritten tail of that realtime extent
(where del.br_state == XFS_EXT_UNWRITTEN, aligned at the end of the realtime
extent, but with mod = xfs_rtb_to_rtxoff(mp, del.br_startblock) != 0 and
del.br_startoff > start):
} else if (del.br_state == XFS_EXT_UNWRITTEN) {
struct xfs_bmbt_irec prev;
xfs_fileoff_t unwrite_start;
/*
* This one is already unwritten.
* It must have a written left neighbor.
* Unwrite the killed part of that one and
* try again.
*/
if (!xfs_iext_prev_extent(ifp, &icur, &prev))
ASSERT(0);
ASSERT(prev.br_state == XFS_EXT_NORM);
__xfs_bunmapi() converts the written left neighbor prev (containing the
written fsverity metadata) to XFS_EXT_UNWRITTEN via
xfs_bmap_add_extent_unwritten_real() without any block reservation for a bmap
btree split, and then unmaps and frees the entire realtime extent on the next
iteration.
[1] https://lore.kernel.org/r/20260918111539.1003439-15-aalbersh@kernel.org
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144147.2393369-1-aalbersh@kernel.org?part=8
^ permalink raw reply [flat|nested] 42+ messages in thread* Re: [PATCH v18 08/15] xfs: don't remove written extents past EOF on fsverity inodes
2026-11-02 14:41 ` [PATCH v18 08/15] xfs: don't remove written extents past EOF on fsverity inodes Andrey Albershteyn
2026-11-02 15:11 ` sashiko-bot
@ 2026-11-03 9:42 ` Christoph Hellwig
1 sibling, 0 replies; 42+ messages in thread
From: Christoph Hellwig @ 2026-11-03 9:42 UTC (permalink / raw)
To: Andrey Albershteyn
Cc: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs,
linux-fsdevel, david, Darrick J. Wong
On Mon, Nov 02, 2026 at 03:41:38PM +0100, Andrey Albershteyn wrote:
> xfs_free_eofblocks() removes all extents past EOF unless the
> XFS_DIFLAG_PREALLOC or XFS_DIFLAG_APPEND flags are set. This is
> undesired for fsverity as it stores metadata beyond EOF. However, while
> merkle tree is being built, delayed preallocation and unwritten extents
> are used. After metadata construction is done, fsverity inodes becomes
> read-only and won't be changed anymore, none of these unwritten extents
> or preallocations in post EOF region will be used.
>
> Let xfs_free_eofblocks() be called on fsverity inode as usual to remove
> anything which is not written extent. However, inodes which are
> undergoing merkle tree construction need to be skipped in case reclaim
> takes place.
The Sashiko comment about the enabled flag here looks correct to me,
or am I misisng something?
^ permalink raw reply [flat|nested] 42+ messages in thread
* [PATCH v18 09/15] xfs: add fs-verity support
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
` (7 preceding siblings ...)
2026-11-02 14:41 ` [PATCH v18 08/15] xfs: don't remove written extents past EOF on fsverity inodes Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 15:34 ` sashiko-bot
2026-11-02 14:41 ` [PATCH v18 10/15] xfs: initialize fs-verity on file open Andrey Albershteyn
` (5 subsequent siblings)
14 siblings, 1 reply; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david
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).
Pro-actively remove any unwritten extents as we use last extent to
locate descriptor.
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/xfs_fsverity.c | 425 +++++++++++++++++++++++++++++++++++++++++-
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 +
6 files changed, 440 insertions(+), 1 deletion(-)
diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c
index 8fc3e763c017..441251b36073 100644
--- a/fs/xfs/xfs_fsverity.c
+++ b/fs/xfs/xfs_fsverity.c
@@ -6,13 +6,22 @@
#include "xfs_fs.h"
#include "xfs_shared.h"
#include "xfs_format.h"
-#include "xfs_log_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_inode.h"
+#include "xfs_log_format.h"
+#include "xfs_trans.h"
+#include "xfs_trace.h"
+#include "xfs_quota.h"
#include "xfs_fsverity.h"
+#include "xfs_iomap.h"
+#include "xfs_error.h"
+#include "xfs_health.h"
+#include "xfs_bmap_util.h"
+#include "xfs_icache.h"
#include <linux/fsverity.h>
#include <linux/iomap.h>
+#include <linux/pagemap.h>
struct kmem_cache *xfs_fsverity_ioend_cache;
mempool_t xfs_fsverity_ioend_pool;
@@ -44,3 +53,417 @@ 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;
+
+ xfs_ilock(ip, XFS_ILOCK_EXCL);
+ /*
+ * Serialize reading of inode->i_flags with xfs_scrub clearing of this
+ * flag if inode is corrupted.
+ */
+ if (!(inode->i_flags & S_VERITY)) {
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ return -ENODATA;
+ }
+ error = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, &rec, &is_empty);
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ if (error)
+ return error;
+
+ if (is_empty)
+ 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))
+ return -ERANGE;
+
+ error = fsverity_pagecache_read(inode, buf, desc_size, desc_pos);
+ if (error)
+ return error;
+
+ return desc_size;
+}
+
+static int
+xfs_fsverity_write_descriptor(
+ struct file *file,
+ const void *desc,
+ u32 desc_size,
+ u64 merkle_tree_size)
+{
+ int error;
+ struct inode *inode = file_inode(file);
+ struct xfs_inode *ip = XFS_I(inode);
+ unsigned int blksize = ip->i_mount->m_sb.sb_blocksize;
+ u64 tree_last_block =
+ xfs_fsverity_metadata_offset(ip) + merkle_tree_size;
+ u64 desc_pos =
+ round_up(tree_last_block, XFS_FSVERITY_START_ALIGN);
+ u64 desc_end = desc_pos + desc_size;
+ __be32 desc_size_disk = cpu_to_be32(desc_size);
+ u64 desc_size_pos =
+ round_up(desc_end + sizeof(desc_size_disk), blksize) -
+ sizeof(desc_size_disk);
+
+ error = iomap_fsverity_write(file, desc_size_pos, sizeof(__be32),
+ (const void *)&desc_size_disk,
+ &xfs_buffered_write_iomap_ops,
+ &xfs_iomap_write_ops);
+ if (error)
+ return error;
+
+ return iomap_fsverity_write(file, desc_pos, desc_size, desc,
+ &xfs_buffered_write_iomap_ops,
+ &xfs_iomap_write_ops);
+}
+
+/*
+ * Try to remove all the fsverity metadata after a failed enablement.
+ */
+static int
+xfs_fsverity_delete_metadata(
+ struct xfs_inode *ip)
+{
+ struct xfs_trans *tp;
+ struct xfs_mount *mp = ip->i_mount;
+ int error;
+
+ xfs_ilock(ip, XFS_MMAPLOCK_EXCL);
+ truncate_inode_pages(VFS_I(ip)->i_mapping, XFS_ISIZE(ip));
+
+ error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp);
+ if (error) {
+ xfs_iunlock(ip, XFS_MMAPLOCK_EXCL);
+ return error;
+ }
+
+ xfs_ilock(ip, XFS_ILOCK_EXCL);
+ xfs_trans_ijoin(tp, ip, 0);
+
+ /*
+ * We remove post EOF data, no need to update i_size as fsverity
+ * didn't move i_size in the first place
+ */
+ error = xfs_itruncate_extents(&tp, ip, XFS_DATA_FORK, XFS_ISIZE(ip));
+ if (error)
+ goto err_cancel;
+
+ error = xfs_trans_commit(tp);
+ xfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_ILOCK_EXCL);
+ return error;
+err_cancel:
+ xfs_trans_cancel(tp);
+ xfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_ILOCK_EXCL);
+ return error;
+}
+
+static int
+xfs_fsverity_reset_inode(
+ struct xfs_inode *ip)
+{
+ int error;
+ struct xfs_mount *mp = ip->i_mount;
+ struct xfs_trans *tp;
+
+ error = xfs_fsverity_delete_metadata(ip);
+ if (error)
+ return error;
+
+ /*
+ * First, let's clean in-memory fsverity flag and then reset it on the
+ * disk
+ */
+ inode_set_flags(VFS_I(ip), 0, S_VERITY);
+
+ /*
+ * Set fsverity inode flag
+ */
+ error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_ichange,
+ 0, 0, false, &tp);
+ if (error)
+ return error;
+
+ ip->i_diflags2 &= ~XFS_DIFLAG2_VERITY;
+
+ xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
+ xfs_trans_set_sync(tp);
+
+ error = xfs_trans_commit(tp);
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ return error;
+}
+
+/*
+ * Prepare to enable fsverity by clearing old metadata.
+ */
+static int
+xfs_fsverity_begin_enable(
+ struct file *filp)
+{
+ struct inode *inode = file_inode(filp);
+ struct xfs_inode *ip = XFS_I(inode);
+ int error;
+
+ xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);
+
+ if (IS_DAX(inode) || ip->i_diflags2 & XFS_DIFLAG2_DAX)
+ return -EINVAL;
+
+ /*
+ * fs-verity stores the Merkle tree past EOF in units of the filesystem
+ * block size, so it cannot support realtime files whose allocation unit
+ * (extent size) is larger than the block size.
+ */
+ if (xfs_inode_has_bigrtalloc(ip))
+ return -EINVAL;
+
+ if (inode->i_size > XFS_FSVERITY_LARGEST_FILE)
+ return -EFBIG;
+
+ /*
+ * Flush pagecache before building Merkle tree. Inode is locked and no
+ * further writes will happen to the file except fsverity metadata
+ */
+ error = filemap_write_and_wait(inode->i_mapping);
+ if (error)
+ return error;
+
+ if (xfs_iflags_test_and_set(ip, XFS_VERITY_CONSTRUCTION))
+ return -EBUSY;
+
+ error = xfs_qm_dqattach(ip);
+ if (error)
+ goto out_clear;
+
+ error = xfs_fsverity_delete_metadata(ip);
+ if (error)
+ goto out_clear;
+ return error;
+out_clear:
+ xfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);
+ return error;
+}
+
+/*
+ * Complete (or fail) the process of enabling fsverity.
+ */
+static int
+xfs_fsverity_end_enable(
+ struct file *file,
+ const void *desc,
+ size_t desc_size,
+ u64 merkle_tree_size)
+{
+ struct inode *inode = file_inode(file);
+ struct xfs_inode *ip = XFS_I(inode);
+ struct xfs_mount *mp = ip->i_mount;
+ struct xfs_trans *tp;
+ int error = 0;
+ loff_t range_start = xfs_fsverity_metadata_offset(ip);
+
+ xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);
+
+ /* fs-verity failed, just cleanup */
+ if (desc == NULL)
+ goto out;
+
+ error = xfs_fsverity_write_descriptor(file, desc, desc_size,
+ merkle_tree_size);
+ if (error)
+ goto out;
+
+ /*
+ * Wait for Merkle tree get written to disk before setting on-disk inode
+ * flag and clearing XFS_VERITY_CONSTRUCTION
+ */
+ error = filemap_write_and_wait_range(inode->i_mapping, range_start,
+ LLONG_MAX);
+ if (error)
+ goto out;
+
+ /*
+ * Set fsverity inode flag
+ */
+ error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_ichange,
+ 0, 0, false, &tp);
+ if (error)
+ goto out;
+
+ /*
+ * Ensure that we've persisted the verity information before we enable
+ * it on the inode and tell the caller we have sealed the inode.
+ */
+ ip->i_diflags2 |= XFS_DIFLAG2_VERITY;
+
+ xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
+ xfs_trans_set_sync(tp);
+
+ error = xfs_trans_commit(tp);
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ if (error)
+ goto out;
+
+ inode_set_flags(inode, S_VERITY, S_VERITY);
+
+ /*
+ * Proactively remove unwritten extents left by COW preallocations and
+ * write preallocation in the merkle tree holes and past descriptor,
+ * and any delayed preallocations
+ */
+ error = xfs_free_eofblocks(ip);
+out:
+ if (error) {
+ int error2;
+
+ error2 = xfs_fsverity_reset_inode(ip);
+ if (error2)
+ xfs_alert(ip->i_mount,
+"ino 0x%llx failed to clean up new fsverity metadata, err %d",
+ I_INO(ip), error2);
+ }
+
+ xfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);
+ return error;
+}
+
+/*
+ * Retrieve a merkle tree block.
+ */
+static struct page *
+xfs_fsverity_read_merkle(
+ struct inode *inode,
+ pgoff_t index)
+{
+ index += xfs_fsverity_metadata_offset(XFS_I(inode)) >> PAGE_SHIFT;
+
+ return generic_read_merkle_tree_page(inode, index);
+}
+
+/*
+ * Retrieve a merkle tree block.
+ */
+static void
+xfs_fsverity_readahead_merkle_tree(
+ struct inode *inode,
+ pgoff_t index,
+ unsigned long nr_pages)
+{
+ index += xfs_fsverity_metadata_offset(XFS_I(inode)) >> PAGE_SHIFT;
+
+ generic_readahead_merkle_tree(inode, index, nr_pages);
+}
+
+static inline bool
+xfs_fsverity_is_hashes_of_zeroed_blocks(
+ struct xfs_inode *ip,
+ const void *buf,
+ unsigned int size,
+ const u8 *zero_digest,
+ unsigned int digest_size)
+{
+ /*
+ * If this is a block full of hashes of zeroed blocks, don't bother
+ * storing the block. We can synthesize them later.
+ *
+ * However, do this only in case Merkle tree block == fs block size.
+ * Iomap synthesizes these blocks based on holes in the merkle tree. We
+ * won't be able to tell if something need to be synthesizes for the
+ * range in the fs block. For example, for 4k filesystem block
+ *
+ * [ 1k | zero hashes | zero hashes | 1k ]
+ *
+ * Iomap won't know about these empty blocks.
+ */
+ if (size != ip->i_mount->m_sb.sb_blocksize)
+ return false;
+ /*
+ * First digest is zero_digest
+ */
+ if (memcmp(buf, zero_digest, digest_size))
+ return false;
+ /*
+ * Every digest is same as previous, thus all are
+ * zero_digest
+ */
+ return memcmp(buf + digest_size, buf, size - digest_size) == 0;
+}
+
+/*
+ * Write a merkle tree block.
+ */
+static int
+xfs_fsverity_write_merkle(
+ struct file *file,
+ const void *buf,
+ u64 pos,
+ unsigned int size,
+ const u8 *zero_digest,
+ unsigned int digest_size)
+{
+ struct inode *inode = file_inode(file);
+ struct xfs_inode *ip = XFS_I(inode);
+ loff_t position = pos +
+ xfs_fsverity_metadata_offset(ip);
+
+ if (position + size > inode->i_sb->s_maxbytes)
+ return -EFBIG;
+
+ if (xfs_fsverity_is_hashes_of_zeroed_blocks(ip, buf, size, zero_digest,
+ digest_size))
+ return 0;
+
+ return iomap_fsverity_write(file, position, size, buf,
+ &xfs_buffered_write_iomap_ops,
+ &xfs_iomap_write_ops);
+}
+
+const struct fsverity_operations xfs_fsverity_ops = {
+ .begin_enable_verity = xfs_fsverity_begin_enable,
+ .end_enable_verity = xfs_fsverity_end_enable,
+ .get_verity_descriptor = xfs_fsverity_get_descriptor,
+ .read_merkle_tree_page = xfs_fsverity_read_merkle,
+ .readahead_merkle_tree = xfs_fsverity_readahead_merkle_tree,
+ .write_merkle_tree_block = xfs_fsverity_write_merkle,
+};
diff --git a/fs/xfs/xfs_fsverity.h b/fs/xfs/xfs_fsverity.h
index 44f7bc7831f2..43f8ef62e12a 100644
--- a/fs/xfs/xfs_fsverity.h
+++ b/fs/xfs/xfs_fsverity.h
@@ -8,9 +8,11 @@
#include "xfs_platform.h"
#include "xfs_verity.h"
#include <linux/iomap.h>
+#include <linux/fsverity.h>
#include <linux/mempool.h>
#ifdef CONFIG_FS_VERITY
+extern const struct fsverity_operations xfs_fsverity_ops;
int xfs_fsverity_init(void);
void xfs_fsverity_exit(void);
bool xfs_fsverity_is_file_data(const struct xfs_inode *ip, loff_t offset);
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 700964048587..0b6463663599 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -599,6 +599,8 @@ __XFS_HAS_FEAT(nouuid, NOUUID)
#define XFS_OPSTATE_RESUMING_QUOTAON 18
/* (Zoned) GC is in progress */
#define XFS_OPSTATE_ZONEGC_RUNNING 20
+/* Kernel has logged a warning about fsverity support */
+#define XFS_OPSTATE_WARNED_FSVERITY 21
#define __XFS_IS_OPSTATE(name, NAME) \
static inline bool xfs_is_ ## name (struct xfs_mount *mp) \
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index 3a7d7334e9a6..af679b6ab52b 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -30,6 +30,7 @@
#include "xfs_filestream.h"
#include "xfs_quota.h"
#include "xfs_sysfs.h"
+#include "xfs_fsverity.h"
#include "xfs_ondisk.h"
#include "xfs_rmap_item.h"
#include "xfs_refcount_item.h"
@@ -1736,6 +1737,9 @@ xfs_fs_fill_super(
sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP | QTYPE_MASK_PRJ;
#endif
sb->s_op = &xfs_super_operations;
+#ifdef CONFIG_FS_VERITY
+ sb->s_vop = &xfs_fsverity_ops;
+#endif
/*
* Delay mount work if the debug hook is set. This is debug
@@ -1988,6 +1992,9 @@ xfs_fs_fill_super(
if (error)
goto out_filestream_unmount;
+ if (xfs_has_verity(mp))
+ xfs_warn_experimental(mp, XFS_EXPERIMENTAL_FSVERITY);
+
root = igrab(VFS_I(mp->m_rootip));
if (!root) {
error = -ENOENT;
--
2.54.0
^ permalink raw reply related [flat|nested] 42+ messages in thread* Re: [PATCH v18 09/15] xfs: add fs-verity support
2026-11-02 14:41 ` [PATCH v18 09/15] xfs: add fs-verity support Andrey Albershteyn
@ 2026-11-02 15:34 ` sashiko-bot
2026-11-03 18:23 ` Eric Biggers
0 siblings, 1 reply; 42+ messages in thread
From: sashiko-bot @ 2026-11-02 15:34 UTC (permalink / raw)
To: Andrey Albershteyn, ebiggers, hch, Carlos Maiolino, fsverity; +Cc: david
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] xfs: skipped metadata cleanup when desc is NULL in xfs_fsverity_end_enable()
- [High] xfs: premature S_VERITY publication and broken rollback in xfs_fsverity_end_enable()
- [High] xfs: NULL xfs_zone_alloc_ctx dereference when enabling verity on zoned inodes
- [High] xfs: missing s_maxbytes bounds checks in xfs_fsverity_write_descriptor() and xfs_fsverity_get_descriptor()
- [High] xfs: compound Merkle folios with bs > ps in xfs_fsverity_read_merkle()
- [High] xfs: missing extent bounds check in xfs_fsverity_get_descriptor()
--
commit 996c2dec93ad3f7a7f8b9c7b85e6cd7f58d990bd
Author: Andrey Albershteyn <aalbersh@kernel.org>
xfs: add fs-verity support
This commit integrates XFS with fs-verity by implementing
xfs_fsverity_ops to read and write Merkle tree blocks and the verity
descriptor past EOF in the inode data fork using iomap and pagecache
helpers. It also wires up superblock verity operations and logs an
experimental feature warning on mount.
> diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c
> index 8fc3e763c017b..441251b360731 100644
> --- a/fs/xfs/xfs_fsverity.c
> +++ b/fs/xfs/xfs_fsverity.c
[ ... ]
> @@ -44,3 +53,417 @@ 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)
> +{
[ ... ]
> + error = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, &rec, &is_empty);
> + xfs_iunlock(ip, XFS_ILOCK_EXCL);
> + if (error)
> + return error;
> +
> + if (is_empty)
> + 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))
> + return -ERANGE;
> +
> + error = fsverity_pagecache_read(inode, buf, desc_size, desc_pos);
[Severity: High]
In fs/xfs/xfs_fsverity.c, xfs_fsverity_get_descriptor() checks that
desc_pos >= xfs_fsverity_metadata_offset(ip), but it does not check that
desc_pos >= XFS_FSB_TO_B(mp, rec.br_startoff) or that rec is a written
extent covering the entire descriptor range [desc_pos, last_block_offset).
When an inode with S_VERITY is opened:
xfs_file_open()
-> fsverity_file_open()
-> fsverity_ensure_verity_info()
-> fsverity_get_descriptor()
-> xfs_fsverity_get_descriptor()
S_VERITY is already set on the inode while fsverity_get_info(inode) is
still NULL. If [desc_pos, last_block_offset) spans a hole before rec,
fsverity_pagecache_read() reads that hole through iomap_read_folio(), and
xfs_read_iomap_begin() sets IOMAP_F_FSVERITY on the IOMAP_HOLE mapping.
In fs/iomap/buffered-io.c:iomap_read_folio_iter():
if ((iomap->flags & IOMAP_F_FSVERITY) &&
iomap->type == IOMAP_HOLE) {
if (ctx->vi)
fsverity_fill_zerohash(folio, poff, plen,
ctx->vi);
iomap_set_range_uptodate(folio, poff, plen);
Because ctx->vi is NULL during descriptor loading,
fsverity_fill_zerohash() is skipped and the newly allocated folio range is
marked uptodate without being zeroed. Can this leave uninitialized kernel
page memory cached as uptodate in inode->i_mapping and allow it to be read
by userspace via FS_IOC_READ_VERITY_METADATA?
> + 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);
[Severity: High]
Unlike xfs_fsverity_write_merkle(), which checks whether
position + size > inode->i_sb->s_maxbytes, neither
xfs_fsverity_write_descriptor() nor xfs_fsverity_get_descriptor() validates
that the descriptor offset stays within inode->i_sb->s_maxbytes.
In xfs_fsverity_write_descriptor(), on 32-bit architectures where
s_maxbytes (MAX_LFS_FILESIZE) is (loff_t)ULONG_MAX << PAGE_SHIFT,
inode->i_size <= XFS_FSVERITY_LARGEST_FILE does not prevent tree_last_block
from ending within 64 KiB of s_maxbytes, so rounding up to
XFS_FSVERITY_START_ALIGN and adding desc_size pushes desc_pos and
desc_size_pos past s_maxbytes.
Similarly, in xfs_fsverity_get_descriptor(), rec.br_startoff +
rec.br_blockcount from an on-disk extent can exceed
XFS_B_TO_FSB(mp, inode->i_sb->s_maxbytes) or overflow xfs_fsize_t in
XFS_FSB_TO_B(mp, rec.br_startoff + rec.br_blockcount).
When an offset >= mp->m_super->s_maxbytes is passed to
iomap_fsverity_write() or fsverity_pagecache_read(), xfs_iomap_end_fsb()
hits ASSERT(offset <= mp->m_super->s_maxbytes) and clamps end_fsb <=
offset_fsb, which causes xfs_buffered_write_iomap_begin() and
xfs_read_iomap_begin() -> xfs_bmapi_read() to pass an uninitialized stack
struct xfs_bmbt_irec imap to xfs_bmbt_to_iomap(). Should both
xfs_fsverity_write_descriptor() and xfs_fsverity_get_descriptor() check
that the end of the descriptor does not exceed inode->i_sb->s_maxbytes?
[ ... ]
> +/*
> + * 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;
[Severity: High]
On an XFS filesystem formatted with sb_blocksize > PAGE_SIZE,
xfs_ialloc_setup_geometry() sets M_IGEO(mp)->min_folio_order > 0, so
inode->i_mapping only allocates compound folios.
When FS_IOC_ENABLE_VERITY is called with a Merkle tree block size of
PAGE_SIZE, fsverity_init_merkle_tree_params() accepts it because
log_blocksize <= PAGE_SHIFT and log_blocksize <= inode->i_blkbits, and
xfs_fsverity_begin_enable() does not reject blocksize > PAGE_SIZE.
When a subsequent read verifies data blocks:
xfs_end_fsverity_io_read()
-> fsverity_verify_bio()
-> verify_data_block()
-> xfs_fsverity_read_merkle()
-> generic_read_merkle_tree_page()
generic_read_merkle_tree_page() returns folio_file_page(folio, index) from
a compound folio in inode->i_mapping, and verify_data_block() /
is_hash_block_verified() in fs/verity/verify.c call SetPageChecked(hpage).
Because PageChecked uses the PF_NO_COMPOUND policy, doesn't this trigger
VM_BUG_ON_PGFLAGS(enforce && PageCompound(page), page) when
CONFIG_DEBUG_VM_PGFLAGS is enabled?
> +
> + /*
> + * fs-verity stores the Merkle tree past EOF in units of the filesystem
> + * block size, so it cannot support realtime files whose allocation unit
> + * (extent size) is larger than the block size.
> + */
> + if (xfs_inode_has_bigrtalloc(ip))
> + return -EINVAL;
[Severity: High]
In fs/xfs/xfs_fsverity.c, xfs_fsverity_begin_enable() checks
xfs_inode_has_bigrtalloc(ip), which only tests whether
XFS_IS_REALTIME_INODE(ip) && ip->i_mount->m_sb.sb_rextsize > 1. On zoned
XFS filesystems, sb_rextsize is 1, so zoned inodes (xfs_is_zoned_inode(ip))
are not rejected here.
When xfs_fsverity_write_merkle() or xfs_fsverity_write_descriptor() then
calls iomap_fsverity_write():
iomap_fsverity_write()
-> iomap_file_buffered_write(..., NULL)
-> xfs_buffered_write_iomap_begin()
-> xfs_zoned_buffered_write_iomap_begin()
iomap_file_buffered_write() is called with private == NULL instead of a
struct xfs_zone_alloc_ctx. In xfs_zoned_buffered_write_iomap_begin(),
struct xfs_zone_alloc_ctx *ac = iter->private is NULL, hitting ASSERT(ac)
and dereferencing ac->reserved_blocks. Should xfs_fsverity_begin_enable()
also reject xfs_is_zoned_inode(ip)?
[ ... ]
> +/*
> + * Complete (or fail) the process of enabling fsverity.
> + */
> +static int
> +xfs_fsverity_end_enable(
> + struct file *file,
> + const void *desc,
> + size_t desc_size,
> + u64 merkle_tree_size)
> +{
> + struct inode *inode = file_inode(file);
> + struct xfs_inode *ip = XFS_I(inode);
> + struct xfs_mount *mp = ip->i_mount;
> + struct xfs_trans *tp;
> + int error = 0;
> + loff_t range_start = xfs_fsverity_metadata_offset(ip);
> +
> + xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);
> +
> + /* fs-verity failed, just cleanup */
> + if (desc == NULL)
> + goto out;
[Severity: High]
In xfs_fsverity_end_enable(), error is initialized to 0. When
enable_verity() in fs/verity/enable.c fails during Merkle tree construction
or descriptor setup and calls vops->end_enable_verity(filp, NULL, 0,
params.tree_size), desc is NULL and execution jumps to out with error == 0:
out:
if (error) {
int error2;
error2 = xfs_fsverity_reset_inode(ip);
...
}
xfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);
return error;
Because error is 0, xfs_fsverity_reset_inode(ip) is skipped when
desc == NULL, leaving behind any post-EOF pagecache folios, delalloc
reservations, and extents written during build_merkle_tree(). Furthermore,
once XFS_VERITY_CONSTRUCTION is cleared, background writeback via
xfs_vm_writepages() no longer sets IOMAP_F_FSVERITY, so
iomap_writeback_handle_eof() skips the dirty post-EOF folios and strands
the unconverted delalloc blocks in ip->i_delayed_blks. Should the
desc == NULL path also run xfs_fsverity_delete_metadata(ip) (or
xfs_fsverity_reset_inode(ip)) before clearing XFS_VERITY_CONSTRUCTION?
[ ... ]
> + /*
> + * Ensure that we've persisted the verity information before we enable
> + * it on the inode and tell the caller we have sealed the inode.
> + */
> + ip->i_diflags2 |= XFS_DIFLAG2_VERITY;
> +
> + xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
> + xfs_trans_set_sync(tp);
> +
> + error = xfs_trans_commit(tp);
> + xfs_iunlock(ip, XFS_ILOCK_EXCL);
> + if (error)
> + goto out;
> +
> + inode_set_flags(inode, S_VERITY, S_VERITY);
> +
> + /*
> + * Proactively remove unwritten extents left by COW preallocations and
> + * write preallocation in the merkle tree holes and past descriptor,
> + * and any delayed preallocations
> + */
> + error = xfs_free_eofblocks(ip);
> +out:
> + if (error) {
> + int error2;
> +
> + error2 = xfs_fsverity_reset_inode(ip);
> + if (error2)
> + xfs_alert(ip->i_mount,
> +"ino 0x%llx failed to clean up new fsverity metadata, err %d",
> + I_INO(ip), error2);
> + }
[Severity: High]
As reported by Darrick J. Wong on v15 [1], xfs_fsverity_end_enable()
commits XFS_DIFLAG2_VERITY and sets S_VERITY on the VFS inode before
calling xfs_free_eofblocks(ip) to remove unwritten preallocations in Merkle
tree holes and past the descriptor.
This creates several problems:
1. Because S_VERITY is published before xfs_free_eofblocks(ip) runs,
concurrent readers can immediately observe S_VERITY while unwritten extents
still exist in Merkle tree holes (causing zero-hash blocks to map as
IOMAP_UNWRITTEN instead of IOMAP_HOLE) or after the descriptor (causing
xfs_fsverity_get_descriptor() to read from the trailing preallocation). If
xfs_free_eofblocks(ip) then fails and xfs_fsverity_reset_inode(ip) clears
S_VERITY, enable_verity() frees the struct fsverity_info via
fsverity_remove_info(vi) while concurrent readers that already saw
S_VERITY may still be verifying read bios against ioend->io_vi.
2. In xfs_fsverity_reset_inode(ip):
error = xfs_fsverity_delete_metadata(ip);
if (error)
return error;
inode_set_flags(VFS_I(ip), 0, S_VERITY);
error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_ichange,
0, 0, false, &tp);
if (error)
return error;
ip->i_diflags2 &= ~XFS_DIFLAG2_VERITY;
If xfs_fsverity_delete_metadata(ip) fails, xfs_fsverity_reset_inode(ip)
returns early with S_VERITY still set on the VFS inode, which triggers
WARN_ON_ONCE(IS_VERITY(inode)) in enable_verity() and leaks the
fsverity_info in fsverity_info_hash. And if xfs_fsverity_delete_metadata()
succeeds (or the system crashes after the synchronous XFS_DIFLAG2_VERITY
commit) before the second transaction clearing XFS_DIFLAG2_VERITY commits,
the inode is left marked with XFS_DIFLAG2_VERITY on disk after its Merkle
tree and descriptor extents have already been deleted.
Could xfs_free_eofblocks(ip) (or the post-EOF unwritten/delalloc cleanup)
be performed before setting XFS_DIFLAG2_VERITY and S_VERITY so that
enabling verity does not have to roll back committed on-disk state and
published VFS flags?
[1] https://lore.kernel.org/r/20260820164411.GI6072@frogsfrogsfrogs
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144147.2393369-1-aalbersh@kernel.org?part=9
^ permalink raw reply [flat|nested] 42+ messages in thread* Re: [PATCH v18 09/15] xfs: add fs-verity support
2026-11-02 15:34 ` sashiko-bot
@ 2026-11-03 18:23 ` Eric Biggers
0 siblings, 0 replies; 42+ messages in thread
From: Eric Biggers @ 2026-11-03 18:23 UTC (permalink / raw)
To: sashiko-reviews; +Cc: Andrey Albershteyn, hch, Carlos Maiolino, fsverity, david
On Mon, Nov 02, 2026 at 03:34:43PM +0000, sashiko-bot@kernel.org wrote:
> Could xfs_free_eofblocks(ip) (or the post-EOF unwritten/delalloc cleanup)
> be performed before setting XFS_DIFLAG2_VERITY and S_VERITY so that
> enabling verity does not have to roll back committed on-disk state and
> published VFS flags?
Yes, S_VERITY needs to be set at the very end of ->end_enable_verity(),
and only on success. I recently had to fix this for ext4 and btrfs.
- Eric
^ permalink raw reply [flat|nested] 42+ messages in thread
* [PATCH v18 10/15] xfs: initialize fs-verity on file open
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
` (8 preceding siblings ...)
2026-11-02 14:41 ` [PATCH v18 09/15] xfs: add fs-verity support Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 14:41 ` [PATCH v18 11/15] xfs: add fs-verity ioctls Andrey Albershteyn
` (4 subsequent siblings)
14 siblings, 0 replies; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david, Darrick J. Wong
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 | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index dbb5f8f59678..a610adc90c02 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -1793,11 +1793,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] 42+ messages in thread* [PATCH v18 11/15] xfs: add fs-verity ioctls
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
` (9 preceding siblings ...)
2026-11-02 14:41 ` [PATCH v18 10/15] xfs: initialize fs-verity on file open Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 15:24 ` sashiko-bot
2026-11-02 14:41 ` [PATCH v18 12/15] xfs: advertise fs-verity being available on filesystem Andrey Albershteyn
` (3 subsequent siblings)
14 siblings, 1 reply; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david, Darrick J. Wong
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 f81b6e52ac40..37c6a2fae58c 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
@@ -1575,6 +1576,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] 42+ messages in thread* Re: [PATCH v18 11/15] xfs: add fs-verity ioctls
2026-11-02 14:41 ` [PATCH v18 11/15] xfs: add fs-verity ioctls Andrey Albershteyn
@ 2026-11-02 15:24 ` sashiko-bot
0 siblings, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-11-02 15:24 UTC (permalink / raw)
To: Andrey Albershteyn, ebiggers, hch, Carlos Maiolino, fsverity
Cc: david, Darrick J. Wong
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] xfs: premature S_VERITY set before xfs_free_eofblocks() causes UAF
- [Critical] xfs: fs-verity triggers compound page BUG_ON on large block size fs
- [High] xfs: xfs_fsverity_end_enable() skips rollback cleanup when desc is NULL
- [Medium] xfs: xfs_fileattr_set() rejects FS_VERITY_FL during FS_IOC_SETFLAGS
- [Medium] xfs: xfs_fileattr_set() races with in-progress FS_IOC_ENABLE_VERITY
--
commit 2331d2406515413653c5db56870e5226577de902
Author: Andrey Albershteyn <aalbersh@kernel.org>
xfs: add fs-verity ioctls
This commit exposes the FS_IOC_ENABLE_VERITY, FS_IOC_MEASURE_VERITY, and
FS_IOC_READ_VERITY_METADATA ioctls in xfs_file_ioctl() when the filesystem
has the verity feature enabled.
> diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
> index f81b6e52ac403..37c6a2fae58cc 100644
> --- a/fs/xfs/xfs_ioctl.c
> +++ b/fs/xfs/xfs_ioctl.c
[ ... ]
> @@ -1575,6 +1576,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);
[Severity: High]
When FS_IOC_ENABLE_VERITY fails during Merkle tree construction or
descriptor validation, enable_verity() rolls back by calling
vops->end_enable_verity(filp, NULL, 0, params.tree_size).
In fs/xfs/xfs_fsverity.c:xfs_fsverity_end_enable(), local variable error is
initialized to 0 and the NULL descriptor check jumps directly to out:
int error = 0;
loff_t range_start = xfs_fsverity_metadata_offset(ip);
xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);
/* fs-verity failed, just cleanup */
if (desc == NULL)
goto out;
...
out:
if (error) {
int error2;
error2 = xfs_fsverity_reset_inode(ip);
if (error2)
xfs_alert(ip->i_mount,
"ino 0x%llx failed to clean up new fsverity metadata, err %d",
I_INO(ip), error2);
}
xfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);
return error;
Because error is 0 when desc is NULL, won't xfs_fsverity_reset_inode(ip) be
skipped on rollback, leaving dirty post-EOF Merkle tree folios in
inode->i_mapping and delayed allocation extents in ip->i_df after
XFS_VERITY_CONSTRUCTION is cleared?
[Severity: Medium]
While fsverity_ioctl_enable() -> enable_verity() is building the Merkle
tree, inode_lock(inode) is dropped between xfs_fsverity_begin_enable() and
xfs_fsverity_end_enable(), and XFS_DIFLAG2_VERITY is not set until
xfs_fsverity_end_enable().
During that window, can a concurrent FS_IOC_FSSETXATTR, FS_IOC_SETFLAGS, or
file_setattr call enter xfs_fileattr_set() -> xfs_ioctl_setattr_xflags()
and set XFS_DIFLAG2_DAX or XFS_DIFLAG_REALTIME (including bigrtalloc) on
the inode because neither function checks
xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION)?
[Severity: Medium]
Once FS_IOC_ENABLE_VERITY sets XFS_DIFLAG2_VERITY on an inode,
FS_IOC_GETFLAGS reports FS_VERITY_FL via:
ioctl_getflags()
-> xfs_fileattr_get()
-> xfs_fill_fsxattr()
-> fileattr_fill_xflags(fa, xfs_ip2xflags(ip))
When userspace tools such as chattr perform a read-modify-write via
FS_IOC_SETFLAGS (where fa->fsx_valid is false), fs/xfs/xfs_ioctl.c:
xfs_fileattr_set() checks:
if (!fa->fsx_valid) {
unsigned int allowed = FS_IMMUTABLE_FL | FS_APPEND_FL |
FS_NOATIME_FL | FS_NODUMP_FL |
FS_SYNC_FL | FS_DAX_FL |
FS_PROJINHERIT_FL;
...
if (xfs_has_asciici(mp))
allowed |= FS_CASEFOLD_FL;
if (fa->flags & ~allowed)
return -EOPNOTSUPP;
}
Since FS_VERITY_FL is not included in allowed even when XFS_DIFLAG2_VERITY
is already set on the inode, won't FS_IOC_SETFLAGS updates (such as
chattr +i or chattr +a) on verity-enabled files fail with -EOPNOTSUPP?
[Severity: Critical]
On XFS filesystems where sb_blocksize > PAGE_SIZE (or whenever compound
folios are instantiated in inode->i_mapping), xfs_fsverity_begin_enable()
allows enabling verity, and fs/xfs/xfs_fsverity.c:xfs_fsverity_read_merkle()
fetches Merkle tree pages via generic_read_merkle_tree_page():
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);
}
When verify_data_block() and is_hash_block_verified() subsequently call
PageChecked() and SetPageChecked() on the returned struct page belonging to
a compound folio, won't SetPageChecked() trigger
VM_BUG_ON_PGFLAGS(PageCompound(page), page) (defined via PF_NO_COMPOUND) and
corrupt tail page flags?
> + 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);
[Severity: Critical]
In fs/xfs/xfs_fsverity.c:xfs_fsverity_end_enable(), S_VERITY is set on the
VFS inode before calling xfs_free_eofblocks(ip):
inode_set_flags(inode, S_VERITY, S_VERITY);
/*
* Proactively remove unwritten extents left by COW preallocations and
* write preallocation in the merkle tree holes and past descriptor,
* and any delayed preallocations
*/
error = xfs_free_eofblocks(ip);
out:
if (error) {
int error2;
error2 = xfs_fsverity_reset_inode(ip);
Because enable_verity() has already hashed struct fsverity_info via
fsverity_set_info() before calling vops->end_enable_verity(), setting
S_VERITY makes fsverity_get_info(inode) return that unrefcounted pointer to
concurrent readers (buffered reads queueing bio verification work,
FS_IOC_MEASURE_VERITY, and FS_IOC_READ_VERITY_METADATA) while
xfs_free_eofblocks(ip) is still running.
If xfs_free_eofblocks(ip) fails, xfs_fsverity_reset_inode(ip) clears
S_VERITY (or leaves S_VERITY set if xfs_fsverity_delete_metadata(ip) also
fails) and enable_verity() removes and RCU-frees struct fsverity_info via
fsverity_remove_info(vi). Can concurrent readers or queued verification work
items then dereference the freed fsverity_info, or can concurrent
FS_IOC_READ_VERITY_METADATA calls invoke xfs_fsverity_get_descriptor() ->
xfs_bmap_last_extent() before xfs_free_eofblocks(ip) removes trailing
post-descriptor preallocations? Should inode_set_flags(inode, S_VERITY,
S_VERITY) only be called after xfs_free_eofblocks(ip) succeeds and rollback
is no longer possible?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144147.2393369-1-aalbersh@kernel.org?part=11
^ permalink raw reply [flat|nested] 42+ messages in thread
* [PATCH v18 12/15] xfs: advertise fs-verity being available on filesystem
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
` (10 preceding siblings ...)
2026-11-02 14:41 ` [PATCH v18 11/15] xfs: add fs-verity ioctls Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 14:41 ` [PATCH v18 13/15] xfs: check and repair the verity inode flag state Andrey Albershteyn
` (2 subsequent siblings)
14 siblings, 0 replies; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david, Darrick J. Wong,
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 7629fc3f499f..3323b8dcc793 100644
--- a/fs/xfs/libxfs/xfs_sb.c
+++ b/fs/xfs/libxfs/xfs_sb.c
@@ -1546,6 +1546,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] 42+ messages in thread* [PATCH v18 13/15] xfs: check and repair the verity inode flag state
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
` (11 preceding siblings ...)
2026-11-02 14:41 ` [PATCH v18 12/15] xfs: advertise fs-verity being available on filesystem Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 15:28 ` sashiko-bot
2026-11-02 14:41 ` [PATCH v18 14/15] xfs: introduce health state for corrupted fsverity metadata Andrey Albershteyn
2026-11-02 14:41 ` [PATCH v18 15/15] xfs: enable ro-compat fs-verity flag Andrey Albershteyn
14 siblings, 1 reply; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david, Darrick J. Wong
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>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
---
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 cbfe5e6f779d..fb0ae227df54 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. */
/*
@@ -1765,3 +1767,54 @@ xchk_inode_count_blocks(
return xfs_bmap_count_blocks(sc->tp, sc->ip, whichfork, nextents,
count);
}
+
+/*
+ * If this inode has S_VERITY set on it, read the verity info. If the reading
+ * fails with anything other than ENOMEM, the file is corrupt, which we can
+ * detect later with fsverity_active.
+ *
+ * Callers must hold the IOLOCK and must not hold the ILOCK of sc->ip because
+ * activation reads inode data.
+ */
+int
+xchk_inode_setup_verity(
+ struct xfs_scrub *sc)
+{
+ int error;
+
+ if (!fsverity_active(VFS_I(sc->ip)))
+ return 0;
+
+ error = fsverity_ensure_verity_info(VFS_I(sc->ip));
+ switch (error) {
+ case 0:
+ /* fsverity is active */
+ break;
+ case -ENODATA:
+ case -EMSGSIZE:
+ case -EFSCORRUPTED:
+ case -ERANGE:
+ case -EBADMSG:
+ /*
+ * The nonzero errno codes above are the error codes that can
+ * be returned from fsverity on metadata validation errors.
+ */
+ return 0;
+ default:
+ /* runtime errors */
+ return error;
+ }
+
+ return 0;
+}
+
+/*
+ * Is this a verity file that failed to activate? Callers must have tried to
+ * activate fsverity via xchk_inode_setup_verity.
+ */
+bool
+xchk_inode_verity_broken(
+ struct xfs_inode *ip)
+{
+ return fsverity_active(VFS_I(ip)) && !fsverity_get_info(VFS_I(ip));
+}
diff --git a/fs/xfs/scrub/common.h b/fs/xfs/scrub/common.h
index 181028f0c655..b59d4792a805 100644
--- a/fs/xfs/scrub/common.h
+++ b/fs/xfs/scrub/common.h
@@ -261,6 +261,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 bb8d2bc008e0..1d21758e0df0 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 a9ccf763c059..e84eaa8178dc 100644
--- a/fs/xfs/scrub/inode_repair.c
+++ b/fs/xfs/scrub/inode_repair.c
@@ -577,6 +577,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;
@@ -1659,6 +1661,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;
@@ -2198,6 +2204,27 @@ xrep_inode_reset_dquots(
# define xrep_inode_reset_dquots(sc) (0)
#endif
+/*
+ * If this file is a fsverity file, xchk_prepare_iscrub or xrep_dinode_core
+ * should have activated it. If it's still not active, then there's something
+ * wrong with the verity descriptor and we should turn it off.
+ */
+STATIC int
+xrep_inode_verity(
+ struct xfs_scrub *sc)
+{
+ struct inode *inode = VFS_I(sc->ip);
+
+ if (xchk_inode_verity_broken(sc->ip)) {
+ sc->ip->i_diflags2 &= ~XFS_DIFLAG2_VERITY;
+ inode_set_flags(inode, 0, S_VERITY);
+
+ xfs_trans_log_inode(sc->tp, sc->ip, XFS_ILOG_CORE);
+ }
+
+ return 0;
+}
+
/* Repair an inode's fields. */
int
xrep_inode(
@@ -2247,6 +2274,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] 42+ messages in thread* Re: [PATCH v18 13/15] xfs: check and repair the verity inode flag state
2026-11-02 14:41 ` [PATCH v18 13/15] xfs: check and repair the verity inode flag state Andrey Albershteyn
@ 2026-11-02 15:28 ` sashiko-bot
0 siblings, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-11-02 15:28 UTC (permalink / raw)
To: Andrey Albershteyn, ebiggers, hch, Carlos Maiolino, fsverity
Cc: david, Darrick J. Wong
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] xfs: unhandled verity validation errors in xchk_inode_setup_verity()
- [High] xfs: leftover post-EOF folios and extents in xrep_inode_verity()
- [Medium] xfs: verity scrub/repair conflicts with corrupt or zapped data fork
- [Low] xfs: wrong inode number passed to xchk_ino_set_corrupt() in xchk_inode()
--
commit 448cf76ee21f3e490894016a838e5f7f0e7783af
Author: Darrick J. Wong <djwong@kernel.org>
xfs: check and repair the verity inode flag state
This commit adds scrub and repair checks for inodes with the fsverity flag
set, attempting to activate fsverity during setup and clearing the verity
flags if activation fails due to a metadata validation error.
> diff --git a/fs/xfs/scrub/common.c b/fs/xfs/scrub/common.c
> index cbfe5e6f779df..fb0ae227df54a 100644
> --- a/fs/xfs/scrub/common.c
> +++ b/fs/xfs/scrub/common.c
[ ... ]
> @@ -1765,3 +1767,54 @@ xchk_inode_count_blocks(
> return xfs_bmap_count_blocks(sc->tp, sc->ip, whichfork, nextents,
> count);
> }
> +
> +/*
> + * If this inode has S_VERITY set on it, read the verity info. If the reading
> + * fails with anything other than ENOMEM, the file is corrupt, which we can
> + * detect later with fsverity_active.
> + *
> + * Callers must hold the IOLOCK and must not hold the ILOCK of sc->ip because
> + * activation reads inode data.
> + */
> +int
> +xchk_inode_setup_verity(
> + struct xfs_scrub *sc)
> +{
> + int error;
> +
> + if (!fsverity_active(VFS_I(sc->ip)))
> + return 0;
> +
> + error = fsverity_ensure_verity_info(VFS_I(sc->ip));
> + switch (error) {
> + case 0:
> + /* fsverity is active */
> + break;
> + case -ENODATA:
> + case -EMSGSIZE:
> + case -EFSCORRUPTED:
> + case -ERANGE:
> + case -EBADMSG:
> + /*
> + * The nonzero errno codes above are the error codes that can
> + * be returned from fsverity on metadata validation errors.
> + */
> + return 0;
> + default:
> + /* runtime errors */
> + return error;
> + }
[Severity: High]
Does this switch statement miss several error codes returned by
fsverity_ensure_verity_info() on metadata validation failures, causing
xchk_prepare_iscrub() and xrep_dinode_core() to abort with a runtime error
instead of flagging or repairing the inode?
In fs/verity/open.c, fsverity_get_descriptor() returns -EINVAL when
validate_fsverity_descriptor() fails (for example, unrecognized version,
non-zero reserved bytes, invalid salt_size or sig_size, or when
desc->data_size does not match inode->i_size, including after
xrep_dinode_size() modifies di_size):
if (!validate_fsverity_descriptor(inode, desc, size)) {
kfree(desc);
return -EINVAL;
}
Similarly, fsverity_create_info() -> fsverity_init_merkle_tree_params()
returns -EINVAL for an unsupported hash algorithm or log_blocksize, and
returns -EFBIG when the Merkle tree depth or hash block count exceeds the
maximum bounds. In addition, xfs_fsverity_get_descriptor() can return
-EFSBADCRC on a block checksum failure, and fsverity_verify_signature() can
return signature verification error codes such as -EKEYREJECTED, -ENOKEY,
-EPERM, or -ENOPKG.
Since those error codes hit the default branch here, won't scrub and repair
abort during setup before xchk_inode() or xrep_inode_verity() can run?
[ ... ]
> diff --git a/fs/xfs/scrub/inode.c b/fs/xfs/scrub/inode.c
> index bb8d2bc008e01..1d21758e0df06 100644
> --- a/fs/xfs/scrub/inode.c
> +++ b/fs/xfs/scrub/inode.c
[ ... ]
> @@ -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);
[Severity: Low]
Should this pass I_INO(sc->ip) instead of sc->sm->sm_ino to
xchk_ino_set_corrupt()?
When scrubbing an open file where the caller passed sc->sm->sm_ino == 0,
xchk_setup_inode() installs the live inode into sc->ip without updating
sc->sm->sm_ino, so passing sc->sm->sm_ino logs inode 0 in
trace_xchk_ino_error().
> diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c
> index a9ccf763c0596..e84eaa8178dc7 100644
> --- a/fs/xfs/scrub/inode_repair.c
> +++ b/fs/xfs/scrub/inode_repair.c
[ ... ]
> @@ -1659,6 +1661,10 @@ xrep_dinode_core(
> if (iget_error)
> return iget_error;
>
> + error = xchk_inode_setup_verity(sc);
> + if (error)
> + return error;
[Severity: Medium]
What happens if the inode's data fork BMBT is corrupt, or if
xrep_dinode_core() just zapped the data fork via xrep_dinode_zap_forks()?
First, if a verity inode has a valid dinode core but a corrupt out-of-line
data fork BMBT (XFS_DINODE_FMT_BTREE), xchk_prepare_iscrub() ->
xchk_inode_setup_verity() -> xfs_fsverity_get_descriptor() ->
xfs_bmap_last_extent() -> xfs_iread_extents() fails with -EFSCORRUPTED.
xchk_inode_setup_verity() swallows that error, and xchk_inode() then marks
XFS_SCRUB_TYPE_INODE corrupt via xchk_ino_set_corrupt(). When xrep_inode()
Subsequently runs, it sees XFS_SCRUB_OFLAG_CORRUPT and calls
xrep_inode_problems() -> xrep_inode_blockcounts() ->
xchk_inode_count_blocks() before reaching xrep_inode_verity(), which hits
-EFSCORRUPTED again in xfs_iread_extents() and fails the repair before
XFS_SCRUB_TYPE_BMAPBTD can repair the data fork.
Second, if xrep_dinode_core() had to zap the data fork in
xrep_dinode_zap_forks() (setting XFS_SICK_INO_BMBTD_ZAPPED so that
XFS_SCRUB_TYPE_BMAPBTD can later rebuild it from rmapbt), calling
xchk_inode_setup_verity() here on the newly emptied data fork will fail with
-ENODATA, causing xrep_inode_verity() to clear XFS_DIFLAG2_VERITY before
xrep_bmap_data() has a chance to rebuild the data fork.
> @@ -2198,6 +2204,27 @@ xrep_inode_reset_dquots(
> # define xrep_inode_reset_dquots(sc) (0)
> #endif
>
> +/*
> + * If this file is a fsverity file, xchk_prepare_iscrub or xrep_dinode_core
> + * should have activated it. If it's still not active, then there's something
> + * wrong with the verity descriptor and we should turn it off.
> + */
> +STATIC int
> +xrep_inode_verity(
> + struct xfs_scrub *sc)
> +{
> + struct inode *inode = VFS_I(sc->ip);
> +
> + if (xchk_inode_verity_broken(sc->ip)) {
> + sc->ip->i_diflags2 &= ~XFS_DIFLAG2_VERITY;
> + inode_set_flags(inode, 0, S_VERITY);
> +
> + xfs_trans_log_inode(sc->tp, sc->ip, XFS_ILOG_CORE);
> + }
> +
> + return 0;
> +}
[Severity: High]
When xrep_inode_verity() clears XFS_DIFLAG2_VERITY and S_VERITY, does it
also need to invalidate post-EOF pagecache folios and truncate the post-EOF
fsverity metadata extents, similar to xfs_fsverity_delete_metadata()?
During setup, xchk_inode_setup_verity() -> fsverity_ensure_verity_info() ->
xfs_fsverity_get_descriptor() -> fsverity_pagecache_read() reads the
post-EOF Merkle tree and descriptor blocks into inode->i_mapping while
S_VERITY is still set, leaving uptodate folios and written (XFS_EXT_NORM)
extents beyond i_size.
Once XFS_DIFLAG2_VERITY and S_VERITY are cleared without removing those
post-EOF folios and written extents:
1. If the file is later opened for writing and extended via truncate() or
write(), xfs_zero_range() -> iomap_zero_range() sees IOMAP_MAPPED for the
post-EOF written extent while inode->i_size is still at the old EOF,
triggering WARN_ON_ONCE(folio_pos(folio) > iter->inode->i_size) in
iomap_zero_iter().
2. If xfs_free_eofblocks() frees the post-EOF extents first (for example on
file close), the uptodate post-EOF folios populated by
fsverity_pagecache_read() remain in inode->i_mapping over the resulting
IOMAP_HOLE, so a subsequent extending truncate() or write() can expose the
stale fsverity metadata bytes as file data.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144147.2393369-1-aalbersh@kernel.org?part=13
^ permalink raw reply [flat|nested] 42+ messages in thread
* [PATCH v18 14/15] xfs: introduce health state for corrupted fsverity metadata
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
` (12 preceding siblings ...)
2026-11-02 14:41 ` [PATCH v18 13/15] xfs: check and repair the verity inode flag state Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 15:16 ` sashiko-bot
2026-11-02 14:41 ` [PATCH v18 15/15] xfs: enable ro-compat fs-verity flag Andrey Albershteyn
14 siblings, 1 reply; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david, Darrick J. Wong
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 | 25 +++++++++++++++++++------
fs/xfs/xfs_health.c | 1 +
4 files changed, 24 insertions(+), 7 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 441251b36073..9b83a0735dc0 100644
--- a/fs/xfs/xfs_fsverity.c
+++ b/fs/xfs/xfs_fsverity.c
@@ -89,13 +89,17 @@ xfs_fsverity_get_descriptor(
if (error)
return error;
- if (is_empty)
+ if (is_empty) {
+ xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);
return -ENODATA;
+ }
last_block_offset =
XFS_FSB_TO_B(mp, rec.br_startoff + rec.br_blockcount);
- if (last_block_offset <= xfs_fsverity_metadata_offset(ip))
+ if (last_block_offset <= xfs_fsverity_metadata_offset(ip)) {
+ xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);
return -ENODATA;
+ }
desc_size_pos = last_block_offset - sizeof(__be32);
error = fsverity_pagecache_read(inode, (char *)&d_desc_size,
@@ -104,20 +108,29 @@ 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))
+ 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)
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] 42+ messages in thread* Re: [PATCH v18 14/15] xfs: introduce health state for corrupted fsverity metadata
2026-11-02 14:41 ` [PATCH v18 14/15] xfs: introduce health state for corrupted fsverity metadata Andrey Albershteyn
@ 2026-11-02 15:16 ` sashiko-bot
0 siblings, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-11-02 15:16 UTC (permalink / raw)
To: Andrey Albershteyn, ebiggers, hch, Carlos Maiolino, fsverity
Cc: david, Darrick J. Wong
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] xfs: XFS_SICK_INO_FSVERITY is never cleared by online scrub or repair
--
commit 82a3ec6a24449be06212877f0d87b35ebb6ba704
Author: Andrey Albershteyn <aalbersh@kernel.org>
xfs: introduce health state for corrupted fsverity metadata
This commit adds the XFS_SICK_INO_FSVERITY inode health flag (and its bulkstat
counterpart XFS_BS_SICK_FSVERITY) and marks the inode sick in
xfs_fsverity_get_descriptor() when fsverity descriptor validation fails.
> diff --git a/fs/xfs/libxfs/xfs_health.h b/fs/xfs/libxfs/xfs_health.h
> index 1d45cf5789e86..932b447190da7 100644
> --- a/fs/xfs/libxfs/xfs_health.h
> +++ b/fs/xfs/libxfs/xfs_health.h
> @@ -104,6 +104,7 @@ struct xfs_rtgroup;
> /* Don't propagate sick status to ag health summary during inactivation */
> #define XFS_SICK_INO_FORGET (1 << 12)
> #define XFS_SICK_INO_DIRTREE (1 << 13) /* directory tree structure */
> +#define XFS_SICK_INO_FSVERITY (1 << 14) /* fsverity metadata */
>
> /* Primary evidence of health problems in a given group. */
> #define XFS_SICK_FS_PRIMARY (XFS_SICK_FS_COUNTERS | \
> @@ -140,7 +141,8 @@ struct xfs_rtgroup;
> XFS_SICK_INO_XATTR | \
> XFS_SICK_INO_SYMLINK | \
> XFS_SICK_INO_PARENT | \
> - XFS_SICK_INO_DIRTREE)
> + XFS_SICK_INO_DIRTREE | \
> + XFS_SICK_INO_FSVERITY)
>
> #define XFS_SICK_INO_ZAPPED (XFS_SICK_INO_BMBTD_ZAPPED | \
> XFS_SICK_INO_BMBTA_ZAPPED | \
> diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c
> index 441251b360731..9b83a0735dc04 100644
> --- a/fs/xfs/xfs_fsverity.c
> +++ b/fs/xfs/xfs_fsverity.c
> @@ -89,13 +89,17 @@ xfs_fsverity_get_descriptor(
> if (error)
> return error;
>
> - if (is_empty)
> + if (is_empty) {
> + xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY);
> return -ENODATA;
> + }
[Severity: Medium]
Once xfs_fsverity_get_descriptor() sets XFS_SICK_INO_FSVERITY on an inode, how
is this sickness flag cleared when online scrub or repair runs?
When an inode has corrupted fsverity metadata, opening the file or running
online scrub/repair (XFS_SCRUB_TYPE_INODE) invokes:
xchk_prepare_iscrub() / xrep_dinode_core()
xchk_inode_setup_verity()
fsverity_ensure_verity_info()
fsverity_get_descriptor()
xfs_fsverity_get_descriptor()
xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY)
In fs/xfs/scrub/health.c, type_to_health_flag maps XFS_SCRUB_TYPE_INODE only
to XFS_SICK_INO_CORE:
[XFS_SCRUB_TYPE_INODE] = { XHG_INO, XFS_SICK_INO_CORE },
During online repair, xchk_inode() sees xchk_inode_verity_broken(sc->ip) and
marks XFS_SICK_INO_CORE | XFS_SICK_INO_FORGET corrupt. Then
xrep_inode_verity() repairs the inode by clearing XFS_DIFLAG2_VERITY and
S_VERITY:
fs/xfs/scrub/inode_repair.c:xrep_inode_verity() {
if (xchk_inode_verity_broken(sc->ip)) {
sc->ip->i_diflags2 &= ~XFS_DIFLAG2_VERITY;
inode_set_flags(inode, 0, S_VERITY);
xfs_trans_log_inode(sc->tp, sc->ip, XFS_ILOG_CORE);
}
}
When xfs_scrub_metadata() re-runs xchk_inode() to verify the repair and calls
xchk_update_health(), xfs_inode_mark_healthy(sc->ip, mask) is called with
mask = XFS_SICK_INO_CORE | XFS_SICK_INO_FORGET, because neither xchk_inode()
nor xrep_inode_verity() adds XFS_SICK_INO_FSVERITY to sc->sick_mask or
sc->healthy_mask (and xfs_fsverity_delete_metadata(),
xfs_fsverity_reset_inode(), and xfs_fsverity_end_enable() do not clear it
either).
As a result, xfs_inode_mark_healthy() clears XFS_SICK_INO_CORE and
XFS_SICK_INO_FORGET while leaving XFS_SICK_INO_FSVERITY set in ip->i_sick.
Does this leave the repaired inode permanently sick in memory (preventing
xfs_reclaim_igrab() from reclaiming it and continuing to report
XFS_BS_SICK_FSVERITY in xfs_bulkstat_health()), and cause
xfs_inactive_health() to falsely mark the AG sick with XFS_SICK_AG_INODES when
the repaired inode is eventually inactivated because XFS_SICK_INO_FORGET was
cleared?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144147.2393369-1-aalbersh@kernel.org?part=14
^ permalink raw reply [flat|nested] 42+ messages in thread
* [PATCH v18 15/15] xfs: enable ro-compat fs-verity flag
2026-11-02 14:41 [PATCH v18 00/15] fs-verity support for XFS with post EOF merkle tree Andrey Albershteyn
` (13 preceding siblings ...)
2026-11-02 14:41 ` [PATCH v18 14/15] xfs: introduce health state for corrupted fsverity metadata Andrey Albershteyn
@ 2026-11-02 14:41 ` Andrey Albershteyn
2026-11-02 15:35 ` sashiko-bot
2026-11-03 9:44 ` Christoph Hellwig
14 siblings, 2 replies; 42+ messages in thread
From: Andrey Albershteyn @ 2026-11-02 14:41 UTC (permalink / raw)
To: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs
Cc: Andrey Albershteyn, linux-fsdevel, david, Darrick J. Wong
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 e1f50ad18f0b..6594fbe4f7f6 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] 42+ messages in thread* Re: [PATCH v18 15/15] xfs: enable ro-compat fs-verity flag
2026-11-02 14:41 ` [PATCH v18 15/15] xfs: enable ro-compat fs-verity flag Andrey Albershteyn
@ 2026-11-02 15:35 ` sashiko-bot
2026-11-03 9:44 ` Christoph Hellwig
1 sibling, 0 replies; 42+ messages in thread
From: sashiko-bot @ 2026-11-02 15:35 UTC (permalink / raw)
To: Andrey Albershteyn, ebiggers, hch, Carlos Maiolino, fsverity
Cc: david, Darrick J. Wong
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] xfs: skipped cleanup and early S_VERITY in xfs_fsverity_end_enable()
- [High] xfs: missing blocksize > PAGE_SIZE check in xfs_fsverity_begin_enable()
- [Medium] xfs: unhandled verity errors and uncleared sickness in xchk_inode()
- [Medium] xfs: unnecessary eofblocks transactions on verity inodes when CONFIG_FS_VERITY=n
--
commit 2c04fa631aa020ee4bcc0f60d29a09521669013e
Author: Andrey Albershteyn <aalbersh@kernel.org>
xfs: enable ro-compat fs-verity flag
This commit finalizes fs-verity integration in XFS by adding
XFS_SB_FEAT_RO_COMPAT_VERITY to XFS_SB_FEAT_RO_COMPAT_ALL so the kernel
allows read-write mounts of filesystems with the fs-verity ro-compat feature
enabled.
> diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h
> index e1f50ad18f0b5..6594fbe4f7f6a 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)
[Severity: High]
Now that XFS_SB_FEAT_RO_COMPAT_VERITY is enabled for read-write mounts,
making FS_IOC_ENABLE_VERITY reachable through xfs_has_verity(mp), does
xfs_fsverity_end_enable() skip cleaning up failed Merkle tree metadata when
desc is NULL, and set S_VERITY before rollback is no longer possible?
In fs/verity/enable.c:enable_verity(), if build_merkle_tree(),
fsverity_create_info(), or fsverity_set_info() fails after
vops->begin_enable_verity() succeeded, enable_verity() jumps to rollback and
calls vops->end_enable_verity(filp, NULL, 0, params.tree_size).
In fs/xfs/xfs_fsverity.c:xfs_fsverity_end_enable():
int error = 0;
...
/* fs-verity failed, just cleanup */
if (desc == NULL)
goto out;
...
inode_set_flags(inode, S_VERITY, S_VERITY);
/*
* Proactively remove unwritten extents left by COW preallocations and
* write preallocation in the merkle tree holes and past descriptor,
* and any delayed preallocations
*/
error = xfs_free_eofblocks(ip);
out:
if (error) {
int error2;
error2 = xfs_fsverity_reset_inode(ip);
...
}
xfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION);
return error;
When desc == NULL, error remains 0 at out, so xfs_fsverity_reset_inode(ip)
and xfs_fsverity_delete_metadata(ip) are skipped while
XFS_VERITY_CONSTRUCTION is cleared. Any post-EOF dirty folios and delalloc
extents written by xfs_fsverity_write_merkle() remain attached to the inode;
when writeback runs without XFS_VERITY_CONSTRUCTION set,
xfs_iomap_writepages() does not set IOMAP_F_FSVERITY and
iomap_writeback_handle_eof() skips the post-EOF folios after clearing their
dirty bit, stranding unwritten delalloc blocks.
In addition, on the normal enable path, inode_set_flags(inode, S_VERITY,
S_VERITY) is called before xfs_free_eofblocks(ip). Because enable_verity()
has already inserted vi into fsverity_info_hash, setting S_VERITY immediately
exposes vi to concurrent readers via fsverity_get_info(inode). If
xfs_free_eofblocks(ip) fails and triggers xfs_fsverity_reset_inode(ip),
either S_VERITY remains set if xfs_fsverity_delete_metadata(ip) also fails
(triggering WARN_ON_ONCE(IS_VERITY(inode)) in enable_verity()), or S_VERITY
is cleared and enable_verity() calls fsverity_remove_info(vi) while
concurrent readers or read ioends may already be using vi.
Should desc == NULL call xfs_fsverity_reset_inode(ip), and should
inode_set_flags(inode, S_VERITY, S_VERITY) only be called after
xfs_free_eofblocks(ip) succeeds?
[Severity: High]
Can enabling and reading an fs-verity file on an XFS filesystem with
sb_blocksize > PAGE_SIZE trigger VM_BUG_ON_PGFLAGS(PageCompound(page))?
When mp->m_sb.sb_blocksize > PAGE_SIZE, xfs_ialloc_setup_geometry() sets
igeo->min_folio_order = mp->m_sb.sb_blocklog - PAGE_SHIFT, and
xfs_inode_alloc() / xfs_reinit_inode() configures the mapping with
mapping_set_folio_min_order(inode->i_mapping, M_IGEO(mp)->min_folio_order).
In fs/xfs/xfs_fsverity.c:xfs_fsverity_begin_enable():
if (xfs_inode_has_bigrtalloc(ip))
return -EINVAL;
if (inode->i_size > XFS_FSVERITY_LARGEST_FILE)
return -EFBIG;
There is no check rejecting sb_blocksize > PAGE_SIZE (or
mapping_min_folio_order(inode->i_mapping) > 0), and
fsverity_init_merkle_tree_params() allows a 4K Merkle tree block size as long
as log_blocksize <= PAGE_SHIFT and log_blocksize <= inode->i_blkbits.
When reading the file, verify_data_block() calls:
xfs_fsverity_read_merkle()
-> generic_read_merkle_tree_page()
-> read_mapping_folio()
-> folio_file_page()
Because mapping_min_folio_order(inode->i_mapping) > 0, read_mapping_folio()
allocates a compound folio and folio_file_page() returns a page with
PageCompound(hpage) == true. verify_data_block() then calls
SetPageChecked(hpage), which is defined with PF_NO_COMPOUND and triggers
VM_BUG_ON_PGFLAGS(1 && PageCompound(page), page).
Should xfs_fsverity_begin_enable() (or xfs_has_verity() / mount-time checks)
reject filesystems with blocksize > PAGE_SIZE until fs-verity supports large
folios for Merkle tree caching?
[Severity: Medium]
In online scrub and repair, can corrupt fsverity descriptors cause
xchk_inode_setup_verity() to abort the scrub ioctl with -EINVAL or -EFBIG,
and does repairing or scrubbing a verity inode leave XFS_SICK_INO_FSVERITY
permanently set?
In fs/xfs/scrub/common.c:xchk_inode_setup_verity():
error = fsverity_ensure_verity_info(VFS_I(sc->ip));
switch (error) {
case 0:
/* fsverity is active */
break;
case -ENODATA:
case -EMSGSIZE:
case -EFSCORRUPTED:
case -ERANGE:
case -EBADMSG:
/*
* The nonzero errno codes above are the error codes that can
* be returned from fsverity on metadata validation errors.
*/
return 0;
default:
/* runtime errors */
return error;
}
In fs/verity/open.c, fsverity_ensure_verity_info() calls
fsverity_get_descriptor() -> validate_fsverity_descriptor(), which returns
-EINVAL when a descriptor field is invalid (bad version, non-zero
__reserved, invalid salt_size, data_size != inode->i_size, or sig_size
overflow), and fsverity_create_info() -> fsverity_init_merkle_tree_params(),
which returns -EINVAL for an invalid hash_algorithm or log_blocksize and
-EFBIG when the Merkle tree parameters exceed limits. Because -EINVAL and
-EFBIG hit the default branch in xchk_inode_setup_verity(),
xchk_prepare_iscrub() and xrep_dinode_core() abort the scrub or repair ioctl
instead of allowing xchk_inode() to flag corruption and xrep_inode_verity()
to clear XFS_DIFLAG2_VERITY.
Furthermore, xfs_fsverity_get_descriptor() marks corrupt inodes with
XFS_SICK_INO_FSVERITY (which is included in XFS_SICK_INO_PRIMARY), but
type_to_health_flag[XFS_SCRUB_TYPE_INODE] in fs/xfs/scrub/health.c only sets
XFS_SICK_INO_CORE, and neither xchk_inode() nor xrep_inode_verity() adds
XFS_SICK_INO_FSVERITY to sc->healthy_mask:
fs/xfs/scrub/inode_repair.c:xrep_inode_verity() {
if (xchk_inode_verity_broken(sc->ip)) {
sc->ip->i_diflags2 &= ~XFS_DIFLAG2_VERITY;
inode_set_flags(inode, 0, S_VERITY);
xfs_trans_log_inode(sc->tp, sc->ip, XFS_ILOG_CORE);
}
}
As a result, even after xrep_inode_verity() repairs the inode,
XFS_SICK_INO_FSVERITY remains set on ip->i_sick and causes
xfs_inactive_health() to taint the AG with XFS_SICK_AG_INODES when the inode
is evicted.
[Severity: Medium]
When a kernel built with CONFIG_FS_VERITY=n mounts an XFS filesystem with
XFS_SB_FEAT_RO_COMPAT_VERITY read-write (which adding
XFS_SB_FEAT_RO_COMPAT_VERITY to XFS_SB_FEAT_RO_COMPAT_ALL now permits), will
evicting a verity inode from the icache repeatedly run unnecessary
tr_itruncate transactions?
In fs/xfs/xfs_bmap_util.c:xfs_can_free_eofblocks():
if (fsverity_active(VFS_IC(ip)) ||
xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
return false;
And in fs/xfs/xfs_iops.c:xfs_report_dioalign():
if (fsverity_active(VFS_I(ip)))
return;
When CONFIG_FS_VERITY=n, fsverity_active() is a stub that always returns
false, even though xfs_diflags_to_iflags() still sets S_VERITY on the VFS
inode when XFS_DIFLAG2_VERITY is present.
When a verity inode is looked up (for example via stat(), where
xfs_iformat_data_fork() populates ip->i_df with if_needextents == 0 for
XFS_DINODE_FMT_EXTENTS inodes) and later evicted from the VFS icache:
xfs_inode_mark_reclaimable()
-> xfs_inode_needs_inactive()
-> xfs_can_free_eofblocks()
Because fsverity_active() returns false, xfs_can_free_eofblocks() looks up
extents past EOF, finds the post-EOF Merkle tree and descriptor extents, and
returns true. Inactivation then runs xfs_inactive() -> xfs_free_eofblocks()
-> xfs_itruncate_extents_flags(..., XFS_BMAPI_UNWRITTEN), which allocates a
tr_itruncate transaction, walks all post-EOF extents while skipping the
written verity metadata extents, and unconditionally dirties and commits the
inode core via xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE) on every eviction.
Similarly, xfs_report_dioalign() reports non-zero dio_mem_align and
dio_offset_align for verity files when CONFIG_FS_VERITY=n.
Should these checks test IS_VERITY(VFS_IC(ip)) (or ip->i_diflags2 &
XFS_DIFLAG2_VERITY) instead of fsverity_active()?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102144147.2393369-1-aalbersh@kernel.org?part=15
^ permalink raw reply [flat|nested] 42+ messages in thread* Re: [PATCH v18 15/15] xfs: enable ro-compat fs-verity flag
2026-11-02 14:41 ` [PATCH v18 15/15] xfs: enable ro-compat fs-verity flag Andrey Albershteyn
2026-11-02 15:35 ` sashiko-bot
@ 2026-11-03 9:44 ` Christoph Hellwig
1 sibling, 0 replies; 42+ messages in thread
From: Christoph Hellwig @ 2026-11-03 9:44 UTC (permalink / raw)
To: Andrey Albershteyn
Cc: ebiggers, hch, Carlos Maiolino, fsverity, linux-xfs,
linux-fsdevel, david, Darrick J. Wong
Kinda skipped over a lot more of the Sashiko comments that look
at least remotely sensible to the one here, which seems most
important: XFS_SB_FEAT_RO_COMPAT_VERITY for sure should not
be allowed if CONFIG_FS_VERITY is not set.
^ permalink raw reply [flat|nested] 42+ messages in thread