Skip to contentSkip to conversation

fs-verity support for XFS with post EOF merkle tree

PATCH In review Andrey Albershteyn 5 participants 86 messages Jan 12 – Jan 21

Subsystems (5)
lore
Share & tools
lore
#0 lore raw

v2 cover fs-verity support for XFS with post EOF merkle tree

Hi all,

This patch series adds fs-verity support for XFS. This version stores merkle tree beyond end of the file, the same way as ext4 does it. The verity descriptor is stored at the tail of the merkle tree.

The patchset starts with a few fs-verity preparation patches. Then, a few patches to allow iomap to work in post EOF region. The XFS fs-verity implementation follows.

Preallocations. The preallocations are disabled for fs-verity files. If inode is fs-verity one the allocation size is set to zero. This is fine as the only writing happening is merkle tree data and descriptor. It would be nice to allocate tree size on first write, this could be improved in the future.

The tree is read by iomap into page cache at offset 1 << 53. This is far enough to handle any supported file size.

Testing. The -g verity is passing for 1k, 8k and 4k with/without quota, the tests include different merkle tree block size.

Feedback is welcomed :)

xfsprogs:
https://github.com/alberand/xfsprogs/tree/b4/fsverity

xfstests:
https://github.com/alberand/xfstests/tree/b4/fsverity

Andrey Albershteyn <[email protected]>:
fsverity: expose ensure_fsverity_info()
iomap: introduce IOMAP_F_BEYOND_EOF
iomap: allow iomap_file_buffered_write() take iocb without file
iomap: integrate fs-verity verification into iomap's read path
xfs: add fs-verity ro-compat flag
xfs: add inode on-disk VERITY flag
xfs: initialize fs-verity on file open and cleanup on inode destruction
xfs: don't allow to enable DAX on fs-verity sealed inode
xfs: disable direct read path for fs-verity files
xfs: add verity info pointer to xfs inode
xfs: introduce XFS_FSVERITY_CONSTRUCTION inode flag
xfs: introduce XFS_FSVERITY_REGION_START constant
xfs: disable preallocations for fsverity Merkle tree writes
xfs: add writeback and iomap reading of Merkle tree pages
xfs: add fs-verity support
xfs: add fs-verity ioctls
xfs: add fsverity traces
xfs: enable ro-compat fs-verity flag
Darrick J. Wong <[email protected]>:
fsverity: report validation errors back to the filesystem
xfs: advertise fs-verity being available on filesystem
xfs: check and repair the verity inode flag state
xfs: report verity failures through the health system

Diffstat:

  fs/iomap/bio.c                  |  66 +++++++++++++++++++++++++---
  fs/iomap/buffered-io.c          |  31 ++++++++++---
  fs/iomap/ioend.c                |  41 ++++++++++++++++-
  fs/iomap/trace.h                |   3 +-
  fs/verity/open.c                |   4 +-
  fs/verity/verify.c              |   4 +
  fs/xfs/Makefile                 |   1 +
  fs/xfs/libxfs/xfs_format.h      |  13 +++--
  fs/xfs/libxfs/xfs_fs.h          |  24 ++++++++++
  fs/xfs/libxfs/xfs_health.h      |   4 +-
  fs/xfs/libxfs/xfs_inode_buf.c   |   8 +++
  fs/xfs/libxfs/xfs_inode_util.c  |   2 +
  fs/xfs/libxfs/xfs_sb.c          |   4 +
  fs/xfs/scrub/attr.c             |   7 +++
  fs/xfs/scrub/common.c           |  53 +++++++++++++++++++++++
  fs/xfs/scrub/common.h           |   2 +
  fs/xfs/scrub/inode.c            |   7 +++
  fs/xfs/scrub/inode_repair.c     |  36 +++++++++++++++
  fs/xfs/xfs_aops.c               |  20 +++++++-
  fs/xfs/xfs_bmap_util.c          |   7 +++
  fs/xfs/xfs_file.c               |  23 ++++++++--
  fs/xfs/xfs_fsverity.c           | 395 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  fs/xfs/xfs_fsverity.h           |  12 +++++
  fs/xfs/xfs_health.c             |   1 +
  fs/xfs/xfs_icache.c             |   3 +
  fs/xfs/xfs_inode.h              |  11 ++++
  fs/xfs/xfs_ioctl.c              |  16 +++++++
  fs/xfs/xfs_iomap.c              |  28 ++++++++++--
  fs/xfs/xfs_iops.c               |   4 +
  fs/xfs/xfs_message.c            |   4 +
  fs/xfs/xfs_message.h            |   1 +
  fs/xfs/xfs_mount.h              |   2 +
  fs/xfs/xfs_super.c              |  16 +++++++
  fs/xfs/xfs_trace.h              |  46 ++++++++++++++++++++
  include/linux/fsverity.h        |  16 +++++++
  include/linux/iomap.h           |  16 +++++++
  include/trace/events/fsverity.h |  19 ++++++++
  37 files changed, 924 insertions(+), 26 deletions(-)
Signature · 1 line
- Andrey
#1 lore raw

PATCH v2 1/22 fsverity: report validation errors back to the filesystem

Provide a new function call so that validation errors can be reported back to the filesystem.

---
 fs/verity/verify.c              |  4 ++++
 include/linux/fsverity.h        | 14 ++++++++++++++
 include/trace/events/fsverity.h | 19 +++++++++++++++++++
 3 files changed, 37 insertions(+), 0 deletions(-)
fs/verity/verify.c +4 -0
index 47a66f088f..ef411cf5d8 100644
@@ -271,6 +271,10 @@
 		data_pos, level - 1, params->hash_alg->name, hsize, want_hash,
 		params->hash_alg->name, hsize,
 		level == 0 ? dblock->real_hash : real_hash);
+	trace_fsverity_file_corrupt(inode, data_pos, params->block_size);
+	if (inode->i_sb->s_vop->file_corrupt)
+		inode->i_sb->s_vop->file_corrupt(inode, data_pos,
+						 params->block_size);
 error:
 	for (; level > 0; level--) {
 		kunmap_local(hblocks[level - 1].addr);
include/linux/fsverity.h +14 -0
index 5bc7280425..b75e232890 100644
@@ -128,6 +128,20 @@
 	 */
 	int (*write_merkle_tree_block)(struct inode *inode, const void *buf,
 				       u64 pos, unsigned int size);
+
+	/**
+	 * Notify the filesystem that file data is corrupt.
+	 *
+	 * @inode: the inode being validated
+	 * @pos: the file position of the invalid data
+	 * @len: the length of the invalid data
+	 *
+	 * This function is called when fs-verity detects that a portion of a
+	 * file's data is inconsistent with the Merkle tree, or a Merkle tree
+	 * block needed to validate the data is inconsistent with the level
+	 * above it.
+	 */
+	void (*file_corrupt)(struct inode *inode, loff_t pos, size_t len);
 };
 
 #ifdef CONFIG_FS_VERITY
include/trace/events/fsverity.h +19 -0
index dab220884b..375fdddac6 100644
@@ -137,6 +137,25 @@
 		__entry->hidx)
 );
 
+TRACE_EVENT(fsverity_file_corrupt,
+	TP_PROTO(const struct inode *inode, loff_t pos, size_t len),
+	TP_ARGS(inode, pos, len),
+	TP_STRUCT__entry(
+		__field(ino_t, ino)
+		__field(loff_t, pos)
+		__field(size_t, len)
+	),
+	TP_fast_assign(
+		__entry->ino = inode->i_ino;
+		__entry->pos = pos;
+		__entry->len = len;
+	),
+	TP_printk("ino %lu pos %llu len %zu",
+		(unsigned long) __entry->ino,
+		__entry->pos,
+		__entry->len)
+);
+
 #endif /* _TRACE_FSVERITY_H */
 
 /* This part must be outside protection */
Signature · 1 line
- Andrey
#2 lore raw

Re: [PATCH v2 1/22] fsverity: report validation errors back to the filesystem

Quoted 1 line

To: "Darrick J. Wong" <[email protected]>

Say ^^^^^^^ what?

Quoted 16 lines · Darrick J. Wong

Provide a new function call so that validation errors can be reported back to the filesystem.

---
 fs/verity/verify.c              |  4 ++++
 include/linux/fsverity.h        | 14 ++++++++++++++
 include/trace/events/fsverity.h | 19 +++++++++++++++++++
 3 files changed, 37 insertions(+), 0 deletions(-)
fs/verity/verify.c +0 -0
index 47a66f088f..ef411cf5d8 100644

fs/verity/verify.c line 277

Quoted 7 lines
fs/verity/verify.c +4 -0
@@ -271,6 +271,10 @@
 		data_pos, level - 1, params->hash_alg->name, hsize, want_hash,
 		params->hash_alg->name, hsize,
 		level == 0 ? dblock->real_hash : real_hash);
+	trace_fsverity_file_corrupt(inode, data_pos, params->block_size);
+	if (inode->i_sb->s_vop->file_corrupt)
+		inode->i_sb->s_vop->file_corrupt(inode, data_pos,
+						 params->block_size);

If fserror_report[1] gets merged before this series, I think we should add a new FSERR_ type and call fserror_report instead.

https://lore.kernel.org/linux-fsdevel/176826402610.3490369.4378391061533403171.stgit@frogsfrogsfrogs/T/#u

--D

Quoted 63 lines
fs/verity/verify.c +0 -0
 error:
 	for (; level > 0; level--) {
 		kunmap_local(hblocks[level - 1].addr);
include/linux/fsverity.h +14 -0
index 5bc7280425..b75e232890 100644
@@ -128,6 +128,20 @@
 	 */
 	int (*write_merkle_tree_block)(struct inode *inode, const void *buf,
 				       u64 pos, unsigned int size);
+
+	/**
+	 * Notify the filesystem that file data is corrupt.
+	 *
+	 * @inode: the inode being validated
+	 * @pos: the file position of the invalid data
+	 * @len: the length of the invalid data
+	 *
+	 * This function is called when fs-verity detects that a portion of a
+	 * file's data is inconsistent with the Merkle tree, or a Merkle tree
+	 * block needed to validate the data is inconsistent with the level
+	 * above it.
+	 */
+	void (*file_corrupt)(struct inode *inode, loff_t pos, size_t len);
 };
 
 #ifdef CONFIG_FS_VERITY
include/trace/events/fsverity.h +19 -0
index dab220884b..375fdddac6 100644
@@ -137,6 +137,25 @@
 		__entry->hidx)
 );
 
+TRACE_EVENT(fsverity_file_corrupt,
+	TP_PROTO(const struct inode *inode, loff_t pos, size_t len),
+	TP_ARGS(inode, pos, len),
+	TP_STRUCT__entry(
+		__field(ino_t, ino)
+		__field(loff_t, pos)
+		__field(size_t, len)
+	),
+	TP_fast_assign(
+		__entry->ino = inode->i_ino;
+		__entry->pos = pos;
+		__entry->len = len;
+	),
+	TP_printk("ino %lu pos %llu len %zu",
+		(unsigned long) __entry->ino,
+		__entry->pos,
+		__entry->len)
+);
+
 #endif /* _TRACE_FSVERITY_H */
 
 /* This part must be outside protection */
Signature · 2 lines
-- 
- Andrey
#3 lore raw

Re: [PATCH v2 1/22] fsverity: report validation errors back to the filesystem

Quoted 3 lines · Darrick J. Wong
Quoted 1 line

To: "Darrick J. Wong" <[email protected]>

Say ^^^^^^^ what?

Heh. Looks like somehow your patches being resent all got messed up.

#4 lore raw

Re: [PATCH v2 1/22] fsverity: report validation errors back to the filesystem

In reply to Darrick J. Wong
Quoted 3 lines · Darrick J. Wong
Quoted 1 line

To: "Darrick J. Wong" <[email protected]>

Say ^^^^^^^ what?

oh damn, sorry, that's my broken script

Quoted 29 lines · Darrick J. Wong
Quoted 16 lines

Provide a new function call so that validation errors can be reported back to the filesystem.

---
 fs/verity/verify.c              |  4 ++++
 include/linux/fsverity.h        | 14 ++++++++++++++
 include/trace/events/fsverity.h | 19 +++++++++++++++++++
 3 files changed, 37 insertions(+), 0 deletions(-)
fs/verity/verify.c +0 -0
index 47a66f088f..ef411cf5d8 100644

fs/verity/verify.c line 277

Quoted 7 lines
fs/verity/verify.c +4 -0
@@ -271,6 +271,10 @@
 		data_pos, level - 1, params->hash_alg->name, hsize, want_hash,
 		params->hash_alg->name, hsize,
 		level == 0 ? dblock->real_hash : real_hash);
+	trace_fsverity_file_corrupt(inode, data_pos, params->block_size);
+	if (inode->i_sb->s_vop->file_corrupt)
+		inode->i_sb->s_vop->file_corrupt(inode, data_pos,
+						 params->block_size);

If fserror_report[1] gets merged before this series, I think we should add a new FSERR_ type and call fserror_report instead.

Quoted 1 line

https://lore.kernel.org/linux-fsdevel/176826402610.3490369.4378391061533403171.stgit@frogsfrogsfrogs/T/#u

I see, will health monitoring also use these events? I mean if XFS's fsverity need to report corrupting error then

Signature · 1 line
- Andrey
#5 lore raw

Re: [PATCH v2 1/22] fsverity: report validation errors back to the filesystem

Quoted 36 lines · Andrey Albershteyn
Quoted 3 lines · Darrick J. Wong
Quoted 1 line

To: "Darrick J. Wong" <[email protected]>

Say ^^^^^^^ what?

oh damn, sorry, that's my broken script

Quoted 29 lines · Darrick J. Wong
Quoted 16 lines

Provide a new function call so that validation errors can be reported back to the filesystem.

---
 fs/verity/verify.c              |  4 ++++
 include/linux/fsverity.h        | 14 ++++++++++++++
 include/trace/events/fsverity.h | 19 +++++++++++++++++++
 3 files changed, 37 insertions(+), 0 deletions(-)
fs/verity/verify.c +0 -0
index 47a66f088f..ef411cf5d8 100644

fs/verity/verify.c line 277

Quoted 7 lines
fs/verity/verify.c +4 -0
@@ -271,6 +271,10 @@
 		data_pos, level - 1, params->hash_alg->name, hsize, want_hash,
 		params->hash_alg->name, hsize,
 		level == 0 ? dblock->real_hash : real_hash);
+	trace_fsverity_file_corrupt(inode, data_pos, params->block_size);
+	if (inode->i_sb->s_vop->file_corrupt)
+		inode->i_sb->s_vop->file_corrupt(inode, data_pos,
+						 params->block_size);

If fserror_report[1] gets merged before this series, I think we should add a new FSERR_ type and call fserror_report instead.

Quoted 4 lines
Quoted 1 line

https://lore.kernel.org/linux-fsdevel/176826402610.3490369.4378391061533403171.stgit@frogsfrogsfrogs/T/#u

I see, will health monitoring also use these events? I mean if XFS's fsverity need to report corrupting error then

Yes, the stuff you put in xfs' ->file_corrupt function would get moved to xfs' ->report_error function. Something like:

static void
xfs_fs_report_error(
const struct fserror_event *event)

{
	if (event->inode && event->type == FSERR_VERITY &&
	    IS_VERITY(event->inode))
		xfs_inode_mark_sick(XFS_I(event->inode),
				XFS_SICK_INO_DATA);

	/* healthmon already knows about non-inode and metadata errors */
	if (event->inode && event->type != FSERR_METADATA)
		xfs_healthmon_report_file_ioerror(XFS_I(event->inode), event);
}
static const struct super_operations xfs_super_operations = {
	<snip>
	.report_error		= xfs_fs_report_error,
};

--D

Quoted 4 lines
Signature · 1 line
- Andrey
#6 lore raw

PATCH v2 2/22 fsverity: expose ensure_fsverity_info()

In reply to Andrey Albershteyn

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

---
 fs/verity/open.c         | 4 ++--
 include/linux/fsverity.h | 2 ++
 2 files changed, 4 insertions(+), 2 deletions(-)
fs/verity/open.c +2 -2
index 77b1c977af..956ddaffbf 100644
@@ -350,7 +350,7 @@
 	return 0;
 }
 
-static int ensure_verity_info(struct inode *inode)
+int fsverity_ensure_verity_info(struct inode *inode)
 {
 	struct fsverity_info *vi = fsverity_get_info(inode);
 	struct fsverity_descriptor *desc;
@@ -380,7 +380,7 @@
 {
 	if (filp->f_mode & FMODE_WRITE)
 		return -EPERM;
-	return ensure_verity_info(inode);
+	return fsverity_ensure_verity_info(inode);
 }
 EXPORT_SYMBOL_GPL(__fsverity_file_open);
 
include/linux/fsverity.h +2 -0
index b75e232890..16a6e51705 100644
@@ -196,6 +196,8 @@
 int __fsverity_prepare_setattr(struct dentry *dentry, struct iattr *attr);
 void __fsverity_cleanup_inode(struct inode *inode);
 
+int fsverity_ensure_verity_info(struct inode *inode);
+
 /**
  * fsverity_cleanup_inode() - free the inode's verity info, if present
  * @inode: an inode being evicted
Signature · 1 line
- Andrey
#7 lore raw

Re: [PATCH v2 2/22] fsverity: expose ensure_fsverity_info()

Quoted 14 lines · Andrey Albershteyn

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

---
 fs/verity/open.c         | 4 ++--
 include/linux/fsverity.h | 2 ++
 2 files changed, 4 insertions(+), 2 deletions(-)
fs/verity/open.c +0 -0
index 77b1c977af..956ddaffbf 100644

fs/verity/open.c line 353

Quoted 5 lines
fs/verity/open.c +1 -1
@@ -350,7 +350,7 @@
 	return 0;
 }
 
-static int ensure_verity_info(struct inode *inode)
+int fsverity_ensure_verity_info(struct inode *inode)

Looks mostly fine to me, but does fsverity_ensure_verity_info needs its own EXPORT_SYMBOL_GPL() for when xfs is built as an external module?

--D

Quoted 30 lines
fs/verity/open.c +1 -1
 {
 	struct fsverity_info *vi = fsverity_get_info(inode);
 	struct fsverity_descriptor *desc;
@@ -380,7 +380,7 @@
 {
 	if (filp->f_mode & FMODE_WRITE)
 		return -EPERM;
-	return ensure_verity_info(inode);
+	return fsverity_ensure_verity_info(inode);
 }
 EXPORT_SYMBOL_GPL(__fsverity_file_open);
 
include/linux/fsverity.h +2 -0
index b75e232890..16a6e51705 100644
@@ -196,6 +196,8 @@
 int __fsverity_prepare_setattr(struct dentry *dentry, struct iattr *attr);
 void __fsverity_cleanup_inode(struct inode *inode);
 
+int fsverity_ensure_verity_info(struct inode *inode);
+
 /**
  * fsverity_cleanup_inode() - free the inode's verity info, if present
  * @inode: an inode being evicted
Signature · 2 lines
-- 
- Andrey
#8 lore raw

PATCH v2 3/22 iomap: introduce IOMAP_F_BEYOND_EOF

In reply to Andrey Albershteyn

Flag to indicate to iomap that read/write is happening beyond EOF and no isize checks/update is needed.

---
 fs/iomap/buffered-io.c | 13 ++++++++-----
 fs/iomap/trace.h       |  3 ++-
 include/linux/iomap.h  |  5 +++++
 3 files changed, 15 insertions(+), 6 deletions(-)
fs/iomap/buffered-io.c +8 -5
index e5c1ca440d..cc1cbf2a4c 100644
@@ -533,7 +533,8 @@
 			return 0;
 
 		/* zero post-eof blocks as the page may be mapped */
-		if (iomap_block_needs_zeroing(iter, pos)) {
+		if (iomap_block_needs_zeroing(iter, pos) &&
+		    !(iomap->flags & IOMAP_F_BEYOND_EOF)) {
 			folio_zero_range(folio, poff, plen);
 			iomap_set_range_uptodate(folio, poff, plen);
 		} else {
@@ -1130,13 +1131,14 @@
 		 * unlock and release the folio.
 		 */
 		old_size = iter->inode->i_size;
-		if (pos + written > old_size) {
+		if (pos + written > old_size &&
+		    !(iter->flags & IOMAP_F_BEYOND_EOF)) {
 			i_size_write(iter->inode, pos + written);
 			iter->iomap.flags |= IOMAP_F_SIZE_CHANGED;
 		}
 		__iomap_put_folio(iter, write_ops, written, folio);
 
-		if (old_size < pos)
+		if (old_size < pos && !(iter->flags & IOMAP_F_BEYOND_EOF))
 			pagecache_isize_extended(iter->inode, old_size, pos);
 
 		cond_resched();
@@ -1815,8 +1817,9 @@
 
 	trace_iomap_writeback_folio(inode, pos, folio_size(folio));
 
-	if (!iomap_writeback_handle_eof(folio, inode, &end_pos))
-		return 0;
+	if (!(wpc->iomap.flags & IOMAP_F_BEYOND_EOF) &&
+	    !iomap_writeback_handle_eof(folio, inode, &end_pos))
+ 		return 0;
 	WARN_ON_ONCE(end_pos <= pos);
 
 	if (i_blocks_per_folio(inode, folio) > 1) {
fs/iomap/trace.h +2 -1
index 532787277b..f1895f7ae5 100644
@@ -118,7 +118,8 @@
 	{ IOMAP_F_ATOMIC_BIO,	"ATOMIC_BIO" }, \
 	{ IOMAP_F_PRIVATE,	"PRIVATE" }, \
 	{ IOMAP_F_SIZE_CHANGED,	"SIZE_CHANGED" }, \
-	{ IOMAP_F_STALE,	"STALE" }
+	{ IOMAP_F_STALE,	"STALE" }, \
+	{ IOMAP_F_BEYOND_EOF,	"BEYOND_EOF" }
 
 
 #define IOMAP_DIO_STRINGS \
include/linux/iomap.h +5 -0
index 520e967cb5..7a7e31c499 100644
@@ -86,6 +86,11 @@
 #define IOMAP_F_PRIVATE		(1U << 12)
 
 /*
+ * IO happens beyound inode EOF
+ */
+#define IOMAP_F_BEYOND_EOF	(1U << 13)
+
+/*
  * Flags set by the core iomap code during operations:
  *
  * IOMAP_F_SIZE_CHANGED indicates to the iomap_end method that the file size
Signature · 1 line
- Andrey
#9 lore raw

Re: [PATCH v2 3/22] iomap: introduce IOMAP_F_BEYOND_EOF

fs/iomap/buffered-io.c

Quoted 15 lines · Andrey Albershteyn

Flag to indicate to iomap that read/write is happening beyond EOF and no isize checks/update is needed.

---
 fs/iomap/buffered-io.c | 13 ++++++++-----
 fs/iomap/trace.h       |  3 ++-
 include/linux/iomap.h  |  5 +++++
 3 files changed, 15 insertions(+), 6 deletions(-)
fs/iomap/buffered-io.c +0 -0
index e5c1ca440d..cc1cbf2a4c 100644
@@ -533,7 +533,8 @@

(Does your diff program not set --show-c-function? That makes reviewing harder because I have to search on the comment text to figure out which function this is)

fs/iomap/buffered-io.c line 537

Quoted 6 lines
fs/iomap/buffered-io.c +2 -1
 			return 0;
 
 		/* zero post-eof blocks as the page may be mapped */
-		if (iomap_block_needs_zeroing(iter, pos)) {
+		if (iomap_block_needs_zeroing(iter, pos) &&
+		    !(iomap->flags & IOMAP_F_BEYOND_EOF)) {

Hrm. The last test in iomap_block_needs_zeroing is if pos is at or beyond EOF, and iomap_adjust_read_range takes great pains to reduce plen so that poff/plen never cross EOF. I think the intent of that code is to ensure that we always zero the post-EOF part of a folio when reading it in from disk.

For verity I can see why you don't want to zero the merkle tree blocks beyond EOF, but I think this code can expose unwritten junk in the post-EOF part of the EOF block on disk.

Would it be more correct to do:

static inline bool
iomap_block_needs_zeroing(
const struct iomap_iter *iter,
struct folio *folio,
loff_t pos)

{
	const struct iomap *srcmap = iomap_iter_srcmap(iter);

	if (srcmap->type != IOMAP_MAPPED)
		return true;
	if (srcmap->flags & IOMAP_F_NEW);
		return true;

	/*
	 * Merkle tree exists in a separate folio beyond EOF, so
	 * only zero if this is the EOF folio.
	 */
	if (iomap->flags & IOMAP_F_BEYOND_EOF)
		return folio_pos(folio) == i_size_read(iter->inode);

	return pos >= i_size_read(iter->inode);
}
Quoted 21 lines
fs/iomap/buffered-io.c +3 -2
 			folio_zero_range(folio, poff, plen);
 			iomap_set_range_uptodate(folio, poff, plen);
 		} else {
@@ -1130,13 +1131,14 @@
 		 * unlock and release the folio.
 		 */
 		old_size = iter->inode->i_size;
-		if (pos + written > old_size) {
+		if (pos + written > old_size &&
+		    !(iter->flags & IOMAP_F_BEYOND_EOF)) {
 			i_size_write(iter->inode, pos + written);
 			iter->iomap.flags |= IOMAP_F_SIZE_CHANGED;
 		}
 		__iomap_put_folio(iter, write_ops, written, folio);
 
-		if (old_size < pos)
+		if (old_size < pos && !(iter->flags & IOMAP_F_BEYOND_EOF))
 			pagecache_isize_extended(iter->inode, old_size, pos);
 
 		cond_resched();

fs/iomap/buffered-io.c line 1821

Quoted 7 lines
fs/iomap/buffered-io.c +2 -2
@@ -1815,8 +1817,9 @@
 
 	trace_iomap_writeback_folio(inode, pos, folio_size(folio));
 
-	if (!iomap_writeback_handle_eof(folio, inode, &end_pos))
-		return 0;
+	if (!(wpc->iomap.flags & IOMAP_F_BEYOND_EOF) &&
+	    !iomap_writeback_handle_eof(folio, inode, &end_pos))

Hrm. I /think/ this might break post-eof zeroing on writeback if BEYOND_EOF is set. For verity this isn't a problem because there's no writeback, but it's a bit of a logic bomb if someone ever tries to set BEYOND_EOF on a non-verity file.

--D

Quoted 23 lines
fs/iomap/buffered-io.c +1 -0
+ 		return 0;
 	WARN_ON_ONCE(end_pos <= pos);
 
 	if (i_blocks_per_folio(inode, folio) > 1) {
fs/iomap/trace.h +2 -1
index 532787277b..f1895f7ae5 100644
@@ -118,7 +118,8 @@
 	{ IOMAP_F_ATOMIC_BIO,	"ATOMIC_BIO" }, \
 	{ IOMAP_F_PRIVATE,	"PRIVATE" }, \
 	{ IOMAP_F_SIZE_CHANGED,	"SIZE_CHANGED" }, \
-	{ IOMAP_F_STALE,	"STALE" }
+	{ IOMAP_F_STALE,	"STALE" }, \
+	{ IOMAP_F_BEYOND_EOF,	"BEYOND_EOF" }
 
 
 #define IOMAP_DIO_STRINGS \
include/linux/iomap.h +0 -0
index 520e967cb5..7a7e31c499 100644

include/linux/iomap.h line 89

Quoted 4 lines
include/linux/iomap.h +1 -0
@@ -86,6 +86,11 @@
 #define IOMAP_F_PRIVATE		(1U << 12)
 
 /*
+ * IO happens beyound inode EOF

s/beyound/beyond/

Quoted 12 lines
include/linux/iomap.h +4 -0
+ */
+#define IOMAP_F_BEYOND_EOF	(1U << 13)
+
+/*
  * Flags set by the core iomap code during operations:
  *
  * IOMAP_F_SIZE_CHANGED indicates to the iomap_end method that the file size
Signature · 2 lines
-- 
- Andrey
#10 lore raw

Re: [PATCH v2 3/22] iomap: introduce IOMAP_F_BEYOND_EOF

Quoted 34 lines · Darrick J. Wong

fs/iomap/buffered-io.c

Quoted 15 lines · Andrey Albershteyn

Flag to indicate to iomap that read/write is happening beyond EOF and no isize checks/update is needed.

---
 fs/iomap/buffered-io.c | 13 ++++++++-----
 fs/iomap/trace.h       |  3 ++-
 include/linux/iomap.h  |  5 +++++
 3 files changed, 15 insertions(+), 6 deletions(-)
fs/iomap/buffered-io.c +0 -0
index e5c1ca440d..cc1cbf2a4c 100644
@@ -533,7 +533,8 @@

(Does your diff program not set --show-c-function? That makes reviewing harder because I have to search on the comment text to figure out which function this is)

fs/iomap/buffered-io.c line 537

Quoted 6 lines
fs/iomap/buffered-io.c +2 -1
 			return 0;
 
 		/* zero post-eof blocks as the page may be mapped */
-		if (iomap_block_needs_zeroing(iter, pos)) {
+		if (iomap_block_needs_zeroing(iter, pos) &&
+		    !(iomap->flags & IOMAP_F_BEYOND_EOF)) {

Hrm. The last test in iomap_block_needs_zeroing is if pos is at or beyond EOF, and iomap_adjust_read_range takes great pains to reduce plen so that poff/plen never cross EOF. I think the intent of that code is to ensure that we always zero the post-EOF part of a folio when reading it in from disk.

Quoted 3 lines

For verity I can see why you don't want to zero the merkle tree blocks beyond EOF, but I think this code can expose unwritten junk in the post-EOF part of the EOF block on disk.

Oh wait, is IOMAP_F_BEYOND_EOF only set on mappings that are entirely beyond EOF, aka the merkle tree extents?

--D

Quoted 104 lines

Would it be more correct to do:

static inline bool
iomap_block_needs_zeroing(
const struct iomap_iter *iter,
struct folio *folio,
loff_t pos)

{
	const struct iomap *srcmap = iomap_iter_srcmap(iter);

	if (srcmap->type != IOMAP_MAPPED)
		return true;
	if (srcmap->flags & IOMAP_F_NEW);
		return true;

	/*
	 * Merkle tree exists in a separate folio beyond EOF, so
	 * only zero if this is the EOF folio.
	 */
	if (iomap->flags & IOMAP_F_BEYOND_EOF)
		return folio_pos(folio) == i_size_read(iter->inode);

	return pos >= i_size_read(iter->inode);
}
Quoted 21 lines
fs/iomap/buffered-io.c +3 -2
 			folio_zero_range(folio, poff, plen);
 			iomap_set_range_uptodate(folio, poff, plen);
 		} else {
@@ -1130,13 +1131,14 @@
 		 * unlock and release the folio.
 		 */
 		old_size = iter->inode->i_size;
-		if (pos + written > old_size) {
+		if (pos + written > old_size &&
+		    !(iter->flags & IOMAP_F_BEYOND_EOF)) {
 			i_size_write(iter->inode, pos + written);
 			iter->iomap.flags |= IOMAP_F_SIZE_CHANGED;
 		}
 		__iomap_put_folio(iter, write_ops, written, folio);
 
-		if (old_size < pos)
+		if (old_size < pos && !(iter->flags & IOMAP_F_BEYOND_EOF))
 			pagecache_isize_extended(iter->inode, old_size, pos);
 
 		cond_resched();

fs/iomap/buffered-io.c line 1821

Quoted 7 lines
fs/iomap/buffered-io.c +2 -2
@@ -1815,8 +1817,9 @@
 
 	trace_iomap_writeback_folio(inode, pos, folio_size(folio));
 
-	if (!iomap_writeback_handle_eof(folio, inode, &end_pos))
-		return 0;
+	if (!(wpc->iomap.flags & IOMAP_F_BEYOND_EOF) &&
+	    !iomap_writeback_handle_eof(folio, inode, &end_pos))

Hrm. I /think/ this might break post-eof zeroing on writeback if BEYOND_EOF is set. For verity this isn't a problem because there's no writeback, but it's a bit of a logic bomb if someone ever tries to set BEYOND_EOF on a non-verity file.

--D

Quoted 23 lines
fs/iomap/buffered-io.c +1 -0
+ 		return 0;
 	WARN_ON_ONCE(end_pos <= pos);
 
 	if (i_blocks_per_folio(inode, folio) > 1) {
fs/iomap/trace.h +2 -1
index 532787277b..f1895f7ae5 100644
@@ -118,7 +118,8 @@
 	{ IOMAP_F_ATOMIC_BIO,	"ATOMIC_BIO" }, \
 	{ IOMAP_F_PRIVATE,	"PRIVATE" }, \
 	{ IOMAP_F_SIZE_CHANGED,	"SIZE_CHANGED" }, \
-	{ IOMAP_F_STALE,	"STALE" }
+	{ IOMAP_F_STALE,	"STALE" }, \
+	{ IOMAP_F_BEYOND_EOF,	"BEYOND_EOF" }
 
 
 #define IOMAP_DIO_STRINGS \
include/linux/iomap.h +0 -0
index 520e967cb5..7a7e31c499 100644

include/linux/iomap.h line 89

Quoted 4 lines
include/linux/iomap.h +1 -0
@@ -86,6 +86,11 @@
 #define IOMAP_F_PRIVATE		(1U << 12)
 
 /*
+ * IO happens beyound inode EOF

s/beyound/beyond/

Quoted 12 lines
include/linux/iomap.h +4 -0
+ */
+#define IOMAP_F_BEYOND_EOF	(1U << 13)
+
+/*
  * Flags set by the core iomap code during operations:
  *
  * IOMAP_F_SIZE_CHANGED indicates to the iomap_end method that the file size
Signature · 2 lines
-- 
- Andrey
#11 lore raw

Re: [PATCH v2 3/22] iomap: introduce IOMAP_F_BEYOND_EOF

Quoted 35 lines · Darrick J. Wong
Quoted 34 lines · Darrick J. Wong

fs/iomap/buffered-io.c

Quoted 15 lines · Andrey Albershteyn

Flag to indicate to iomap that read/write is happening beyond EOF and no isize checks/update is needed.

---
 fs/iomap/buffered-io.c | 13 ++++++++-----
 fs/iomap/trace.h       |  3 ++-
 include/linux/iomap.h  |  5 +++++
 3 files changed, 15 insertions(+), 6 deletions(-)
fs/iomap/buffered-io.c +0 -0
index e5c1ca440d..cc1cbf2a4c 100644
@@ -533,7 +533,8 @@

(Does your diff program not set --show-c-function? That makes reviewing harder because I have to search on the comment text to figure out which function this is)

fs/iomap/buffered-io.c line 537

Quoted 6 lines
fs/iomap/buffered-io.c +2 -1
 			return 0;
 
 		/* zero post-eof blocks as the page may be mapped */
-		if (iomap_block_needs_zeroing(iter, pos)) {
+		if (iomap_block_needs_zeroing(iter, pos) &&
+		    !(iomap->flags & IOMAP_F_BEYOND_EOF)) {

Hrm. The last test in iomap_block_needs_zeroing is if pos is at or beyond EOF, and iomap_adjust_read_range takes great pains to reduce plen so that poff/plen never cross EOF. I think the intent of that code is to ensure that we always zero the post-EOF part of a folio when reading it in from disk.

Quoted 6 lines
Quoted 3 lines

For verity I can see why you don't want to zero the merkle tree blocks beyond EOF, but I think this code can expose unwritten junk in the post-EOF part of the EOF block on disk.

Oh wait, is IOMAP_F_BEYOND_EOF only set on mappings that are entirely beyond EOF, aka the merkle tree extents?

yes, and only on fsverity files, so no normal read can get BEYOND_EOF

Quoted 61 lines

--D

Quoted 58 lines

Would it be more correct to do:

static inline bool
iomap_block_needs_zeroing(
const struct iomap_iter *iter,
struct folio *folio,
loff_t pos)

{
	const struct iomap *srcmap = iomap_iter_srcmap(iter);

	if (srcmap->type != IOMAP_MAPPED)
		return true;
	if (srcmap->flags & IOMAP_F_NEW);
		return true;

	/*
	 * Merkle tree exists in a separate folio beyond EOF, so
	 * only zero if this is the EOF folio.
	 */
	if (iomap->flags & IOMAP_F_BEYOND_EOF)
		return folio_pos(folio) == i_size_read(iter->inode);

	return pos >= i_size_read(iter->inode);
}
Quoted 21 lines
fs/iomap/buffered-io.c +3 -2
 			folio_zero_range(folio, poff, plen);
 			iomap_set_range_uptodate(folio, poff, plen);
 		} else {
@@ -1130,13 +1131,14 @@
 		 * unlock and release the folio.
 		 */
 		old_size = iter->inode->i_size;
-		if (pos + written > old_size) {
+		if (pos + written > old_size &&
+		    !(iter->flags & IOMAP_F_BEYOND_EOF)) {
 			i_size_write(iter->inode, pos + written);
 			iter->iomap.flags |= IOMAP_F_SIZE_CHANGED;
 		}
 		__iomap_put_folio(iter, write_ops, written, folio);
 
-		if (old_size < pos)
+		if (old_size < pos && !(iter->flags & IOMAP_F_BEYOND_EOF))
 			pagecache_isize_extended(iter->inode, old_size, pos);
 
 		cond_resched();

fs/iomap/buffered-io.c line 1821

Quoted 7 lines
fs/iomap/buffered-io.c +2 -2
@@ -1815,8 +1817,9 @@
 
 	trace_iomap_writeback_folio(inode, pos, folio_size(folio));
 
-	if (!iomap_writeback_handle_eof(folio, inode, &end_pos))
-		return 0;
+	if (!(wpc->iomap.flags & IOMAP_F_BEYOND_EOF) &&
+	    !iomap_writeback_handle_eof(folio, inode, &end_pos))

Hrm. I /think/ this might break post-eof zeroing on writeback if BEYOND_EOF is set. For verity this isn't a problem because there's no writeback, but it's a bit of a logic bomb if someone ever tries to set BEYOND_EOF on a non-verity file.

I will rename it to IOMAP_F_FSVERITY

Quoted 28 lines
Quoted 28 lines

--D

Quoted 25 lines
fs/iomap/buffered-io.c +1 -0
+ 		return 0;
 	WARN_ON_ONCE(end_pos <= pos);
 
 	if (i_blocks_per_folio(inode, folio) > 1) {
fs/iomap/trace.h +2 -1
index 532787277b..f1895f7ae5 100644
@@ -118,7 +118,8 @@
 	{ IOMAP_F_ATOMIC_BIO,	"ATOMIC_BIO" }, \
 	{ IOMAP_F_PRIVATE,	"PRIVATE" }, \
 	{ IOMAP_F_SIZE_CHANGED,	"SIZE_CHANGED" }, \
-	{ IOMAP_F_STALE,	"STALE" }
+	{ IOMAP_F_STALE,	"STALE" }, \
+	{ IOMAP_F_BEYOND_EOF,	"BEYOND_EOF" }
 
 
 #define IOMAP_DIO_STRINGS \
include/linux/iomap.h +0 -0
index 520e967cb5..7a7e31c499 100644
@@ -86,6 +86,11 @@
 #define IOMAP_F_PRIVATE		(1U << 12)
 
Quoted 4 lines
Quoted 4 lines

include/linux/iomap.h line 89

Quoted 2 lines
include/linux/iomap.h +1 -0
 /*
+ * IO happens beyound inode EOF

s/beyound/beyond/

will fix

Signature · 1 line
- Andrey
#12 lore raw

Re: [PATCH v2 3/22] iomap: introduce IOMAP_F_BEYOND_EOF

In reply to Darrick J. Wong
Quoted 11 lines · Darrick J. Wong
Quoted 10 lines · Andrey Albershteyn

Flag to indicate to iomap that read/write is happening beyond EOF and no isize checks/update is needed.

---
 fs/iomap/buffered-io.c | 13 ++++++++-----
 fs/iomap/trace.h       |  3 ++-
 include/linux/iomap.h  |  5 +++++
 3 files changed, 15 insertions(+), 6 deletions(-)
Quoted 9 lines

fs/iomap/buffered-io.c

Quoted 5 lines
fs/iomap/buffered-io.c +0 -0
index e5c1ca440d..cc1cbf2a4c 100644
@@ -533,7 +533,8 @@

(Does your diff program not set --show-c-function? That makes reviewing harder because I have to search on the comment text to figure out which function this is)

Or use git-send-email which sidesteps all these issues :)

Quoted 11 lines

Hrm. The last test in iomap_block_needs_zeroing is if pos is at or beyond EOF, and iomap_adjust_read_range takes great pains to reduce plen so that poff/plen never cross EOF. I think the intent of that code is to ensure that we always zero the post-EOF part of a folio when reading it in from disk.

For verity I can see why you don't want to zero the merkle tree blocks beyond EOF, but I think this code can expose unwritten junk in the post-EOF part of the EOF block on disk.

Would it be more correct to do:

Or replace the generic past EOF flag with a FSVERITY flag making the use case clear?

Quoted 4 lines
Quoted 4 lines
fs/iomap/buffered-io.c +0 -0
@@ -1815,8 +1817,9 @@
 
 	trace_iomap_writeback_folio(inode, pos, folio_size(folio));
 
Quoted 9 lines

fs/iomap/buffered-io.c line 1821

Quoted 4 lines
fs/iomap/buffered-io.c +2 -2
-	if (!iomap_writeback_handle_eof(folio, inode, &end_pos))
-		return 0;
+	if (!(wpc->iomap.flags & IOMAP_F_BEYOND_EOF) &&
+	    !iomap_writeback_handle_eof(folio, inode, &end_pos))

Hrm. I /think/ this might break post-eof zeroing on writeback if BEYOND_EOF is set. For verity this isn't a problem because there's no writeback, but it's a bit of a logic bomb if someone ever tries to set BEYOND_EOF on a non-verity file.

Maybe we should not even support the flag for writeback?

#13 lore raw

Re: [PATCH v2 3/22] iomap: introduce IOMAP_F_BEYOND_EOF

Quoted 30 lines · Christoph Hellwig
Quoted 11 lines · Darrick J. Wong
Quoted 10 lines · Andrey Albershteyn

Flag to indicate to iomap that read/write is happening beyond EOF and no isize checks/update is needed.

---
 fs/iomap/buffered-io.c | 13 ++++++++-----
 fs/iomap/trace.h       |  3 ++-
 include/linux/iomap.h  |  5 +++++
 3 files changed, 15 insertions(+), 6 deletions(-)
Quoted 9 lines

fs/iomap/buffered-io.c

Quoted 5 lines
fs/iomap/buffered-io.c +0 -0
index e5c1ca440d..cc1cbf2a4c 100644
@@ -533,7 +533,8 @@

(Does your diff program not set --show-c-function? That makes reviewing harder because I have to search on the comment text to figure out which function this is)

Or use git-send-email which sidesteps all these issues :)

Quoted 6 lines

Hrm. The last test in iomap_block_needs_zeroing is if pos is at or beyond EOF, and iomap_adjust_read_range takes great pains to reduce plen so that poff/plen never cross EOF. I think the intent of that code is to ensure that we always zero the post-EOF part of a folio when reading it in from disk.

Quoted 9 lines
Quoted 5 lines

For verity I can see why you don't want to zero the merkle tree blocks beyond EOF, but I think this code can expose unwritten junk in the post-EOF part of the EOF block on disk.

Would it be more correct to do:

Or replace the generic past EOF flag with a FSVERITY flag making the use case clear?

I will rename it to _FSVERITY

Quoted 14 lines
Quoted 4 lines
Quoted 4 lines
fs/iomap/buffered-io.c +0 -0
@@ -1815,8 +1817,9 @@
 
 	trace_iomap_writeback_folio(inode, pos, folio_size(folio));
 
Quoted 9 lines

fs/iomap/buffered-io.c line 1821

Quoted 4 lines
fs/iomap/buffered-io.c +2 -2
-	if (!iomap_writeback_handle_eof(folio, inode, &end_pos))
-		return 0;
+	if (!(wpc->iomap.flags & IOMAP_F_BEYOND_EOF) &&
+	    !iomap_writeback_handle_eof(folio, inode, &end_pos))

Hrm. I /think/ this might break post-eof zeroing on writeback if BEYOND_EOF is set. For verity this isn't a problem because there's no writeback, but it's a bit of a logic bomb if someone ever tries to set BEYOND_EOF on a non-verity file.

Quoted 2 lines

Maybe we should not even support the flag for writeback?

Hmm not sure how you see this, the verity pages need to get written somehow and as they are beyond EOF they will be zeroed out here.

Regarding your comment in the thread to use direct IO, fsverity wants to use page cache as cache for verified pages (already verified page will have "verified" flag set, freshly read won't).

So, I think writeback has to know how to handle them

Signature · 1 line
- Andrey
#14 lore raw

Re: [PATCH v2 3/22] iomap: introduce IOMAP_F_BEYOND_EOF

Quoted 7 lines · Andrey Albershteyn

Hmm not sure how you see this, the verity pages need to get written somehow and as they are beyond EOF they will be zeroed out here.

Regarding your comment in the thread to use direct IO, fsverity wants to use page cache as cache for verified pages (already verified page will have "verified" flag set, freshly read won't).

Ok, I guess we need to stick to the pagecache based path then. The upside is less differences from the existing file systems.

#15 lore raw

Re: [PATCH v2 3/22] iomap: introduce IOMAP_F_BEYOND_EOF

Quoted 11 lines · Christoph Hellwig
Quoted 7 lines · Andrey Albershteyn

Hmm not sure how you see this, the verity pages need to get written somehow and as they are beyond EOF they will be zeroed out here.

Regarding your comment in the thread to use direct IO, fsverity wants to use page cache as cache for verified pages (already verified page will have "verified" flag set, freshly read won't).

Ok, I guess we need to stick to the pagecache based path then. The upside is less differences from the existing file systems.

<nod> IIRC fsverity also doesn't allow directio, so it seems more consistent to me that newly created fsverity artifacts are written through the pagecache IO paths.

--D

#16 lore raw

Re: [PATCH v2 3/22] iomap: introduce IOMAP_F_BEYOND_EOF

In reply to Andrey Albershteyn
Quoted 2 lines · Andrey Albershteyn

Flag to indicate to iomap that read/write is happening beyond EOF and no isize checks/update is needed.

Darrick and I talked to Ted about this yesterday, and we think you don't need the writeback portions of this. (As Darrick said, it's hard to tell if this is all writeback or just some of it).

The flow for creating a verity file should be:

write data to pagecache
calculate merkle tree data, write it to pagecache
write_and_wait the entire file
do verity magic

If you follow this flow, there's never a need to do writeback past EOF. Or indeed writeback on a verity file at all, since it's now immutable.

#17 lore raw

Re: [PATCH v2 3/22] iomap: introduce IOMAP_F_BEYOND_EOF

Quoted 10 lines · Matthew Wilcox
Quoted 2 lines · Andrey Albershteyn

Flag to indicate to iomap that read/write is happening beyond EOF and no isize checks/update is needed.

Darrick and I talked to Ted about this yesterday, and we think you don't need the writeback portions of this. (As Darrick said, it's hard to tell if this is all writeback or just some of it).

The flow for creating a verity file should be:

Quoted 7 lines

write data to pagecache
calculate merkle tree data, write it to pagecache
write_and_wait the entire file
do verity magic

If you follow this flow, there's never a need to do writeback past EOF. Or indeed writeback on a verity file at all, since it's now immutable.

Hrmm, I came to rather opposite conclusions -- if you're going to write the fsverity metadata via the pagecache then you *do* have to fix iomap writeback to deal with post-EOF data correctly. Right now it just ignores everything beyond isize, and I think the xfs side of things will still do things like try to update the ondisk size in the ioend.

I think hch or someone said that maybe those writes could be directio, though using the pagecache for verity setup means it's all incore and ready to go when the ioctl returns.

OTOH it would be convenient to preallocate unwritten extents for the merkle tree, write it with directio, and now if isize ever gets corrupted such that userspace actually /can/ read the merkle tree data, they'll at least see zeroes. Though I think there's an ioctl that lets you do that anyway.

--D

#18 lore raw

PATCH v2 4/22 iomap: allow iomap_file_buffered_write() take iocb without file

In reply to Andrey Albershteyn

This will be necessary for XFS to use iomap_file_buffered_write() in context without file pointer.

As the only user of this is XFS fsverity let's set necessary IOMAP_F_BEYOND_EOF flag if no file provided instead of adding new flags to iocb->ki_flags.

---
 fs/iomap/buffered-io.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
fs/iomap/buffered-io.c +7 -1
index cc1cbf2a4c..79d1c97f02 100644
@@ -1173,7 +1173,6 @@
 		const struct iomap_write_ops *write_ops, void *private)
 {
 	struct iomap_iter iter = {
-		.inode		= iocb->ki_filp->f_mapping->host,
 		.pos		= iocb->ki_pos,
 		.len		= iov_iter_count(i),
 		.flags		= IOMAP_WRITE,
@@ -1181,6 +1180,13 @@
 	};
 	ssize_t ret;
 
+	if (iocb->ki_filp) {
+		iter.inode = iocb->ki_filp->f_mapping->host;
+	} else {
+		iter.inode = (struct inode *)private;
+		iter.flags |= IOMAP_F_BEYOND_EOF;
+	}
+
 	if (iocb->ki_flags & IOCB_NOWAIT)
 		iter.flags |= IOMAP_NOWAIT;
 	if (iocb->ki_flags & IOCB_DONTCACHE)
Signature · 1 line
- Andrey
#19 lore raw

Re: [PATCH v2 4/22] iomap: allow iomap_file_buffered_write() take iocb without file

Quoted 25 lines · Andrey Albershteyn

This will be necessary for XFS to use iomap_file_buffered_write() in context without file pointer.

As the only user of this is XFS fsverity let's set necessary IOMAP_F_BEYOND_EOF flag if no file provided instead of adding new flags to iocb->ki_flags.

---
 fs/iomap/buffered-io.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
fs/iomap/buffered-io.c +0 -1
index cc1cbf2a4c..79d1c97f02 100644
@@ -1173,7 +1173,6 @@
 		const struct iomap_write_ops *write_ops, void *private)
 {
 	struct iomap_iter iter = {
-		.inode		= iocb->ki_filp->f_mapping->host,
 		.pos		= iocb->ki_pos,
 		.len		= iov_iter_count(i),
 		.flags		= IOMAP_WRITE,

fs/iomap/buffered-io.c line 1186

Quoted 7 lines
fs/iomap/buffered-io.c +4 -0
@@ -1181,6 +1180,13 @@
 	};
 	ssize_t ret;
 
+	if (iocb->ki_filp) {
+		iter.inode = iocb->ki_filp->f_mapping->host;
+	} else {
+		iter.inode = (struct inode *)private;

@private is for the filesystem implementation to access, not the generic iomap code. If this is intended for fsverity, then shouldn't merkle tree construction be the only time that fsverity writes to the file? And shouldn't fsverity therefore have access to the struct file?

fs/iomap/buffered-io.c line 1187

Quoted 1 line
fs/iomap/buffered-io.c +1 -0
+		iter.flags |= IOMAP_F_BEYOND_EOF;

IOMAP_F_ flags are mapping state flags for struct iomap::flags, not the iomap_iter.

--D

Quoted 10 lines
fs/iomap/buffered-io.c +2 -0
+	}
+
 	if (iocb->ki_flags & IOCB_NOWAIT)
 		iter.flags |= IOMAP_NOWAIT;
 	if (iocb->ki_flags & IOCB_DONTCACHE)
Signature · 2 lines
-- 
- Andrey
#20 lore raw

Re: [PATCH v2 4/22] iomap: allow iomap_file_buffered_write() take iocb without file

Quoted 8 lines · Darrick J. Wong

fs/iomap/buffered-io.c line 1186

Quoted 3 lines
fs/iomap/buffered-io.c +3 -0
+		iter.inode = iocb->ki_filp->f_mapping->host;
+	} else {
+		iter.inode = (struct inode *)private;

@private is for the filesystem implementation to access, not the generic iomap code. If this is intended for fsverity, then shouldn't merkle tree construction be the only time that fsverity writes to the file? And shouldn't fsverity therefore have access to the struct file?

It's not passed down, but I think it could easily.

Quoted 5 lines

fs/iomap/buffered-io.c line 1187

Quoted 2 lines
fs/iomap/buffered-io.c +1 -0
+		iter.flags |= IOMAP_F_BEYOND_EOF;

IOMAP_F_ flags are mapping state flags for struct iomap::flags, not the iomap_iter.

But we could fix this part as well by having a specific helper for fsverity that is called instead of iomap_file_buffered_write. Neither the iocb, nor struct file are used inside of iomap_write_iter. So just add a new helper that takes an inode, sets the past-EOF/verify flag and open codes the iomap_iter/iomap_write_iter loop.

#21 lore raw

Re: [PATCH v2 4/22] iomap: allow iomap_file_buffered_write() take iocb without file

Quoted 18 lines · Christoph Hellwig
Quoted 8 lines · Darrick J. Wong

fs/iomap/buffered-io.c line 1186

Quoted 3 lines
fs/iomap/buffered-io.c +3 -0
+		iter.inode = iocb->ki_filp->f_mapping->host;
+	} else {
+		iter.inode = (struct inode *)private;

@private is for the filesystem implementation to access, not the generic iomap code. If this is intended for fsverity, then shouldn't merkle tree construction be the only time that fsverity writes to the file? And shouldn't fsverity therefore have access to the struct file?

It's not passed down, but I think it could easily.

Quoted 5 lines

fs/iomap/buffered-io.c line 1187

Quoted 2 lines
fs/iomap/buffered-io.c +1 -0
+		iter.flags |= IOMAP_F_BEYOND_EOF;

IOMAP_F_ flags are mapping state flags for struct iomap::flags, not the iomap_iter.

Quoted 6 lines

But we could fix this part as well by having a specific helper for fsverity that is called instead of iomap_file_buffered_write. Neither the iocb, nor struct file are used inside of iomap_write_iter. So just add a new helper that takes an inode, sets the past-EOF/verify flag and open codes the iomap_iter/iomap_write_iter loop.

sure, sounds good

Signature · 1 line
- Andrey
#22 lore raw

Re: [PATCH v2 4/22] iomap: allow iomap_file_buffered_write() take iocb without file

In reply to Christoph Hellwig
Quoted 11 lines · Christoph Hellwig
Quoted 8 lines · Darrick J. Wong

fs/iomap/buffered-io.c line 1186

Quoted 3 lines
fs/iomap/buffered-io.c +3 -0
+		iter.inode = iocb->ki_filp->f_mapping->host;
+	} else {
+		iter.inode = (struct inode *)private;

@private is for the filesystem implementation to access, not the generic iomap code. If this is intended for fsverity, then shouldn't merkle tree construction be the only time that fsverity writes to the file? And shouldn't fsverity therefore have access to the struct file?

It's not passed down, but I think it could easily.

willy@deadly:~/kernel/linux$ git grep ki_filp |grep file_inode | wc
    109     575    7766
willy@deadly:~/kernel/linux$ git grep ki_filp |wc
    367    1920   23371

I think there's a pretty strong argument for adding ki_inode to struct kiocb. What do you think?

#23 lore raw

Re: [PATCH v2 4/22] iomap: allow iomap_file_buffered_write() take iocb without file

Quoted 13 lines · Matthew Wilcox
Quoted 11 lines · Christoph Hellwig
Quoted 8 lines · Darrick J. Wong

fs/iomap/buffered-io.c line 1186

Quoted 3 lines
fs/iomap/buffered-io.c +3 -0
+		iter.inode = iocb->ki_filp->f_mapping->host;
+	} else {
+		iter.inode = (struct inode *)private;

@private is for the filesystem implementation to access, not the generic iomap code. If this is intended for fsverity, then shouldn't merkle tree construction be the only time that fsverity writes to the file? And shouldn't fsverity therefore have access to the struct file?

It's not passed down, but I think it could easily.

Quoted 7 lines
willy@deadly:~/kernel/linux$ git grep ki_filp |grep file_inode | wc
    109     575    7766
willy@deadly:~/kernel/linux$ git grep ki_filp |wc
    367    1920   23371

I think there's a pretty strong argument for adding ki_inode to struct kiocb. What do you think?

Regardless of this, I think it's an architectural mistake to not have struct file available for reading fsverity metadata. We do have it in fsverity_ioctl_read_metadata but we don't have it in verify_bh().

But reading the merkel tree block from the verify_bh() completion handler is bad for performance anyway. We should submit the metadata reads at the same time we submit the data reads. And there, we would have the struct file.

If we ever have ambitions to support merkel trees on network filesystems, we'll need to figure out some way to pass the struct file in, so maybe we should just bite the bullet and do this now?

#24 lore raw

Re: [PATCH v2 4/22] iomap: allow iomap_file_buffered_write() take iocb without file

In reply to Matthew Wilcox
Quoted 9 lines · Matthew Wilcox
Quoted 1 line

It's not passed down, but I think it could easily.

willy@deadly:~/kernel/linux$ git grep ki_filp |grep file_inode | wc
    109     575    7766
willy@deadly:~/kernel/linux$ git grep ki_filp |wc
    367    1920   23371

I think there's a pretty strong argument for adding ki_inode to struct kiocb. What do you think?

That assumes the reduced pointer dereference is worth bloating the structure. Feel free to give it a try.

Note that we'd still require the file to be set, otherwise we're going to have calling conventions from hell.

#25 lore raw

Re: [PATCH v2 4/22] iomap: allow iomap_file_buffered_write() take iocb without file

Quoted 8 lines · Christoph Hellwig
Quoted 7 lines · Matthew Wilcox
Quoted 1 line

It's not passed down, but I think it could easily.

willy@deadly:~/kernel/linux$ git grep ki_filp |grep file_inode | wc
    109     575    7766
willy@deadly:~/kernel/linux$ git grep ki_filp |wc
    367    1920   23371
Quoted 8 lines
Quoted 2 lines

I think there's a pretty strong argument for adding ki_inode to struct kiocb. What do you think?

That assumes the reduced pointer dereference is worth bloating the structure. Feel free to give it a try.

Note that we'd still require the file to be set, otherwise we're going to have calling conventions from hell.

I think it makes more sense to make fsverity take the file and pass it around to the filesystems. After all, fsverity metadata is just more file IO, right? :D

--D

#26 lore raw

PATCH v2 5/22 iomap: integrate fs-verity verification into iomap's read path

In reply to Andrey Albershteyn

This patch adds fs-verity verification into iomap's read path. After BIO's io operation is complete the data are verified against fs-verity's Merkle tree. Verification work is done in a separate workqueue.

The read path ioend iomap_read_ioend are stored side by side with BIOs if FS_VERITY is enabled.

---
 fs/iomap/bio.c         | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
 fs/iomap/buffered-io.c | 12 ++++++++-
 fs/iomap/ioend.c       | 41 +++++++++++++++++++++++++++++++-
 include/linux/iomap.h  | 11 ++++++++
 4 files changed, 123 insertions(+), 7 deletions(-)
fs/iomap/bio.c +61 -5
index fc045f2e4c..ac6c16b1f8 100644
@@ -5,6 +5,7 @@
  */
 #include <linux/iomap.h>
 #include <linux/pagemap.h>
+#include <linux/fsverity.h>
 #include "internal.h"
 #include "trace.h"
 
@@ -18,6 +19,60 @@
 	bio_put(bio);
 }
 
+#ifdef CONFIG_FS_VERITY
+static void
+iomap_read_fsverify_end_io_work(struct work_struct *work)
+{
+	struct iomap_fsverity_bio *fbio =
+		container_of(work, struct iomap_fsverity_bio, work);
+
+	fsverity_verify_bio(&fbio->bio);
+	iomap_read_end_io(&fbio->bio);
+}
+
+static void
+iomap_read_fsverity_end_io(struct bio *bio)
+{
+	struct iomap_fsverity_bio *fbio =
+		container_of(bio, struct iomap_fsverity_bio, bio);
+
+	INIT_WORK(&fbio->work, iomap_read_fsverify_end_io_work);
+	fsverity_enqueue_verify_work(&fbio->work);
+}
+
+static struct bio *
+iomap_fsverity_read_bio_alloc(struct inode *inode, struct block_device *bdev,
+			    int nr_vecs, gfp_t gfp)
+{
+	struct bio *bio;
+
+	bio = bio_alloc_bioset(bdev, nr_vecs, REQ_OP_READ, gfp,
+			iomap_fsverity_bioset);
+	if (bio)
+		bio->bi_end_io = iomap_read_fsverity_end_io;
+	return bio;
+}
+
+#else
+# define iomap_fsverity_read_bio_alloc(...)	(NULL)
+# define iomap_fsverity_tree_end_align(...)	(false)
+#endif /* CONFIG_FS_VERITY */
+
+static struct bio *iomap_read_bio_alloc(struct inode *inode,
+		const struct iomap *iomap, int nr_vecs, gfp_t gfp)
+{
+	struct bio *bio;
+	struct block_device *bdev = iomap->bdev;
+
+	if (!(iomap->flags & IOMAP_F_BEYOND_EOF) && fsverity_active(inode))
+		return iomap_fsverity_read_bio_alloc(inode, bdev, nr_vecs, gfp);
+
+	bio = bio_alloc(bdev, nr_vecs, REQ_OP_READ, gfp);
+	if (bio)
+		bio->bi_end_io = iomap_read_end_io;
+	return bio;
+}
+
 static void iomap_bio_submit_read(struct iomap_read_folio_ctx *ctx)
 {
 	struct bio *bio = ctx->read_ctx;
@@ -42,26 +97,27 @@
 	    !bio_add_folio(bio, folio, plen, poff)) {
 		gfp_t gfp = mapping_gfp_constraint(folio->mapping, GFP_KERNEL);
 		gfp_t orig_gfp = gfp;
-		unsigned int nr_vecs = DIV_ROUND_UP(length, PAGE_SIZE);
 
 		if (bio)
 			submit_bio(bio);
 
 		if (ctx->rac) /* same as readahead_gfp_mask */
 			gfp |= __GFP_NORETRY | __GFP_NOWARN;
-		bio = bio_alloc(iomap->bdev, bio_max_segs(nr_vecs), REQ_OP_READ,
-				     gfp);
+		bio = iomap_read_bio_alloc(iter->inode, iomap,
+				bio_max_segs(DIV_ROUND_UP(length, PAGE_SIZE)),
+				gfp);
+
 		/*
 		 * If the bio_alloc fails, try it again for a single page to
 		 * avoid having to deal with partial page reads.  This emulates
 		 * what do_mpage_read_folio does.
 		 */
 		if (!bio)
-			bio = bio_alloc(iomap->bdev, 1, REQ_OP_READ, orig_gfp);
+			bio = iomap_read_bio_alloc(iter->inode, iomap, 1,
+						   orig_gfp);
 		if (ctx->rac)
 			bio->bi_opf |= REQ_RAHEAD;
 		bio->bi_iter.bi_sector = sector;
-		bio->bi_end_io = iomap_read_end_io;
 		bio_add_folio_nofail(bio, folio, plen, poff);
 		ctx->read_ctx = bio;
 	}
fs/iomap/buffered-io.c +11 -1
index 79d1c97f02..481f7e1cff 100644
@@ -8,6 +8,7 @@
 #include <linux/writeback.h>
 #include <linux/swap.h>
 #include <linux/migrate.h>
+#include <linux/fsverity.h>
 #include "internal.h"
 #include "trace.h"
 
@@ -532,10 +533,19 @@
 		if (plen == 0)
 			return 0;
 
+		/* end of fs-verity region*/
+		if ((iomap->flags & IOMAP_F_BEYOND_EOF) && (iomap->type == IOMAP_HOLE)) {
+			folio_zero_range(folio, poff, plen);
+			iomap_set_range_uptodate(folio, poff, plen);
+		}
 		/* zero post-eof blocks as the page may be mapped */
-		if (iomap_block_needs_zeroing(iter, pos) &&
+		else if (iomap_block_needs_zeroing(iter, pos) &&
 		    !(iomap->flags & IOMAP_F_BEYOND_EOF)) {
 			folio_zero_range(folio, poff, plen);
+			if (fsverity_active(iter->inode) &&
+			    !fsverity_verify_blocks(folio, plen, poff)) {
+				return -EIO;
+			}
 			iomap_set_range_uptodate(folio, poff, plen);
 		} else {
 			if (!*bytes_submitted)
fs/iomap/ioend.c +40 -1

55 lines not on this page · raw message

include/linux/iomap.h +11 -0
index 7a7e31c499..b451ab3426 100644
@@ -342,6 +342,17 @@
 		iter->srcmap.type == IOMAP_MAPPED;
 }
 
+#ifdef CONFIG_FS_VERITY
+extern struct bio_set *iomap_fsverity_bioset;
+
+struct iomap_fsverity_bio {
+	struct work_struct	work;
+	struct bio		bio;
+};
+
+int iomap_fsverity_init_bioset(void);
+#endif
+
 ssize_t iomap_file_buffered_write(struct kiocb *iocb, struct iov_iter *from,
 		const struct iomap_ops *ops,
 		const struct iomap_write_ops *write_ops, void *private);
Signature · 1 line
- Andrey
#27 lore raw

Re: [PATCH v2 5/22] iomap: integrate fs-verity verification into iomap's read path

Quoted 29 lines · Andrey Albershteyn

This patch adds fs-verity verification into iomap's read path. After BIO's io operation is complete the data are verified against fs-verity's Merkle tree. Verification work is done in a separate workqueue.

The read path ioend iomap_read_ioend are stored side by side with BIOs if FS_VERITY is enabled.

---
 fs/iomap/bio.c         | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
 fs/iomap/buffered-io.c | 12 ++++++++-
 fs/iomap/ioend.c       | 41 +++++++++++++++++++++++++++++++-
 include/linux/iomap.h  | 11 ++++++++
 4 files changed, 123 insertions(+), 7 deletions(-)
fs/iomap/bio.c +1 -0
index fc045f2e4c..ac6c16b1f8 100644
@@ -5,6 +5,7 @@
  */
 #include <linux/iomap.h>
 #include <linux/pagemap.h>
+#include <linux/fsverity.h>
 #include "internal.h"
 #include "trace.h"
 

fs/iomap/bio.c line 22

Quoted 4 lines
fs/iomap/bio.c +1 -0
@@ -18,6 +19,60 @@
 	bio_put(bio);
 }
 
+#ifdef CONFIG_FS_VERITY

Should all this stuff go into fs/iomap/fsverity.c instead of ifdef'd around the iomap code?

<shrug>

Quoted 102 lines
fs/iomap/bio.c +59 -5
+static void
+iomap_read_fsverify_end_io_work(struct work_struct *work)
+{
+	struct iomap_fsverity_bio *fbio =
+		container_of(work, struct iomap_fsverity_bio, work);
+
+	fsverity_verify_bio(&fbio->bio);
+	iomap_read_end_io(&fbio->bio);
+}
+
+static void
+iomap_read_fsverity_end_io(struct bio *bio)
+{
+	struct iomap_fsverity_bio *fbio =
+		container_of(bio, struct iomap_fsverity_bio, bio);
+
+	INIT_WORK(&fbio->work, iomap_read_fsverify_end_io_work);
+	fsverity_enqueue_verify_work(&fbio->work);
+}
+
+static struct bio *
+iomap_fsverity_read_bio_alloc(struct inode *inode, struct block_device *bdev,
+			    int nr_vecs, gfp_t gfp)
+{
+	struct bio *bio;
+
+	bio = bio_alloc_bioset(bdev, nr_vecs, REQ_OP_READ, gfp,
+			iomap_fsverity_bioset);
+	if (bio)
+		bio->bi_end_io = iomap_read_fsverity_end_io;
+	return bio;
+}
+
+#else
+# define iomap_fsverity_read_bio_alloc(...)	(NULL)
+# define iomap_fsverity_tree_end_align(...)	(false)
+#endif /* CONFIG_FS_VERITY */
+
+static struct bio *iomap_read_bio_alloc(struct inode *inode,
+		const struct iomap *iomap, int nr_vecs, gfp_t gfp)
+{
+	struct bio *bio;
+	struct block_device *bdev = iomap->bdev;
+
+	if (!(iomap->flags & IOMAP_F_BEYOND_EOF) && fsverity_active(inode))
+		return iomap_fsverity_read_bio_alloc(inode, bdev, nr_vecs, gfp);
+
+	bio = bio_alloc(bdev, nr_vecs, REQ_OP_READ, gfp);
+	if (bio)
+		bio->bi_end_io = iomap_read_end_io;
+	return bio;
+}
+
 static void iomap_bio_submit_read(struct iomap_read_folio_ctx *ctx)
 {
 	struct bio *bio = ctx->read_ctx;
@@ -42,26 +97,27 @@
 	    !bio_add_folio(bio, folio, plen, poff)) {
 		gfp_t gfp = mapping_gfp_constraint(folio->mapping, GFP_KERNEL);
 		gfp_t orig_gfp = gfp;
-		unsigned int nr_vecs = DIV_ROUND_UP(length, PAGE_SIZE);
 
 		if (bio)
 			submit_bio(bio);
 
 		if (ctx->rac) /* same as readahead_gfp_mask */
 			gfp |= __GFP_NORETRY | __GFP_NOWARN;
-		bio = bio_alloc(iomap->bdev, bio_max_segs(nr_vecs), REQ_OP_READ,
-				     gfp);
+		bio = iomap_read_bio_alloc(iter->inode, iomap,
+				bio_max_segs(DIV_ROUND_UP(length, PAGE_SIZE)),
+				gfp);
+
 		/*
 		 * If the bio_alloc fails, try it again for a single page to
 		 * avoid having to deal with partial page reads.  This emulates
 		 * what do_mpage_read_folio does.
 		 */
 		if (!bio)
-			bio = bio_alloc(iomap->bdev, 1, REQ_OP_READ, orig_gfp);
+			bio = iomap_read_bio_alloc(iter->inode, iomap, 1,
+						   orig_gfp);
 		if (ctx->rac)
 			bio->bi_opf |= REQ_RAHEAD;
 		bio->bi_iter.bi_sector = sector;
-		bio->bi_end_io = iomap_read_end_io;
 		bio_add_folio_nofail(bio, folio, plen, poff);
 		ctx->read_ctx = bio;
 	}
fs/iomap/buffered-io.c +1 -0
index 79d1c97f02..481f7e1cff 100644
@@ -8,6 +8,7 @@
 #include <linux/writeback.h>
 #include <linux/swap.h>
 #include <linux/migrate.h>
+#include <linux/fsverity.h>
 #include "internal.h"
 #include "trace.h"
 

fs/iomap/buffered-io.c line 537

Quoted 5 lines
fs/iomap/buffered-io.c +2 -0
@@ -532,10 +533,19 @@
 		if (plen == 0)
 			return 0;
 
+		/* end of fs-verity region*/
+		if ((iomap->flags & IOMAP_F_BEYOND_EOF) && (iomap->type == IOMAP_HOLE)) {

Overly long line.

Also, when do we get the combination of BEYOND_EOF && HOLE? Is that for sparse regions in only the merkle tree? IIRC (and I could be wrong) fsverity still wants to checksum sparse holes in the regular file data, right?

fs/iomap/buffered-io.c line 542

Quoted 6 lines
fs/iomap/buffered-io.c +4 -1
+			folio_zero_range(folio, poff, plen);
+			iomap_set_range_uptodate(folio, poff, plen);
+		}
 		/* zero post-eof blocks as the page may be mapped */
-		if (iomap_block_needs_zeroing(iter, pos) &&
+		else if (iomap_block_needs_zeroing(iter, pos) &&
		} else if (...

(nitpicking indentation)

Quoted 14 lines
fs/iomap/buffered-io.c +4 -0
 		    !(iomap->flags & IOMAP_F_BEYOND_EOF)) {
 			folio_zero_range(folio, poff, plen);
+			if (fsverity_active(iter->inode) &&
+			    !fsverity_verify_blocks(folio, plen, poff)) {
+				return -EIO;
+			}
 			iomap_set_range_uptodate(folio, poff, plen);
 		} else {
 			if (!*bytes_submitted)
fs/iomap/ioend.c +0 -0
index 86f44922ed..30c0de3c75 100644

fs/iomap/ioend.c line 12

Quoted 4 lines
fs/iomap/ioend.c +1 -0
@@ -9,6 +9,8 @@
 #include "internal.h"
 #include "trace.h"
 
+#define IOMAP_POOL_SIZE		(4 * (PAGE_SIZE / SECTOR_SIZE))

How do we arrive at this pool size? How is it important to have a larger bio reserve pool for *larger* base page sizes?

--D

Quoted 79 lines
fs/iomap/ioend.c +39 -1
+
 struct bio_set iomap_ioend_bioset;
 EXPORT_SYMBOL_GPL(iomap_ioend_bioset);
 
@@ -423,9 +425,46 @@
 }
 EXPORT_SYMBOL_GPL(iomap_split_ioend);
 
+#ifdef CONFIG_FS_VERITY
+struct bio_set *iomap_fsverity_bioset;
+EXPORT_SYMBOL_GPL(iomap_fsverity_bioset);
+int iomap_fsverity_init_bioset(void)
+{
+	struct bio_set *bs, *old;
+	int error;
+
+	bs = kzalloc(sizeof(*bs), GFP_KERNEL);
+	if (!bs)
+		return -ENOMEM;
+
+	error = bioset_init(bs, IOMAP_POOL_SIZE,
+			    offsetof(struct iomap_fsverity_bio, bio),
+			    BIOSET_NEED_BVECS);
+	if (error) {
+		kfree(bs);
+		return error;
+	}
+
+	/*
+	 * This has to be atomic as readaheads can race to create the
+	 * bioset.  If someone set the pointer before us, we drop ours.
+	 */
+	old = cmpxchg(&iomap_fsverity_bioset, NULL, bs);
+	if (old) {
+		bioset_exit(bs);
+		kfree(bs);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iomap_fsverity_init_bioset);
+#else
+# define iomap_fsverity_init_bioset(...)	(-EOPNOTSUPP)
+#endif
+
 static int __init iomap_ioend_init(void)
 {
-	return bioset_init(&iomap_ioend_bioset, 4 * (PAGE_SIZE / SECTOR_SIZE),
+	return bioset_init(&iomap_ioend_bioset, IOMAP_POOL_SIZE,
 			   offsetof(struct iomap_ioend, io_bio),
 			   BIOSET_NEED_BVECS);
 }
include/linux/iomap.h +11 -0
index 7a7e31c499..b451ab3426 100644
@@ -342,6 +342,17 @@
 		iter->srcmap.type == IOMAP_MAPPED;
 }
 
+#ifdef CONFIG_FS_VERITY
+extern struct bio_set *iomap_fsverity_bioset;
+
+struct iomap_fsverity_bio {
+	struct work_struct	work;
+	struct bio		bio;
+};
+
+int iomap_fsverity_init_bioset(void);
+#endif
+
 ssize_t iomap_file_buffered_write(struct kiocb *iocb, struct iov_iter *from,
 		const struct iomap_ops *ops,
 		const struct iomap_write_ops *write_ops, void *private);
Signature · 2 lines
-- 
- Andrey
#28 lore raw

Re: [PATCH v2 5/22] iomap: integrate fs-verity verification into iomap's read path

Quoted 33 lines · Darrick J. Wong
Quoted 32 lines · Andrey Albershteyn

This patch adds fs-verity verification into iomap's read path. After BIO's io operation is complete the data are verified against fs-verity's Merkle tree. Verification work is done in a separate workqueue.

The read path ioend iomap_read_ioend are stored side by side with BIOs if FS_VERITY is enabled.

---
 fs/iomap/bio.c         | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
 fs/iomap/buffered-io.c | 12 ++++++++-
 fs/iomap/ioend.c       | 41 +++++++++++++++++++++++++++++++-
 include/linux/iomap.h  | 11 ++++++++
 4 files changed, 123 insertions(+), 7 deletions(-)
fs/iomap/bio.c +1 -0
index fc045f2e4c..ac6c16b1f8 100644
@@ -5,6 +5,7 @@
  */
 #include <linux/iomap.h>
 #include <linux/pagemap.h>
+#include <linux/fsverity.h>
 #include "internal.h"
 #include "trace.h"
 
@@ -18,6 +19,60 @@
 	bio_put(bio);
 }
 
Quoted 6 lines

fs/iomap/bio.c line 22

Quoted 1 line
fs/iomap/bio.c +1 -0
+#ifdef CONFIG_FS_VERITY

Should all this stuff go into fs/iomap/fsverity.c instead of ifdef'd around the iomap code?

<shrug>

oh, sure, this would be better

Quoted 13 lines
Quoted 13 lines
fs/iomap/buffered-io.c +1 -0
index 79d1c97f02..481f7e1cff 100644
@@ -8,6 +8,7 @@
 #include <linux/writeback.h>
 #include <linux/swap.h>
 #include <linux/migrate.h>
+#include <linux/fsverity.h>
 #include "internal.h"
 #include "trace.h"
 
Quoted 8 lines

fs/iomap/buffered-io.c line 537

Quoted 6 lines
fs/iomap/buffered-io.c +2 -0
@@ -532,10 +533,19 @@
 		if (plen == 0)
 			return 0;
 
+		/* end of fs-verity region*/
+		if ((iomap->flags & IOMAP_F_BEYOND_EOF) && (iomap->type == IOMAP_HOLE)) {

Overly long line.

will fix

Quoted 5 lines

Also, when do we get the combination of BEYOND_EOF && HOLE? Is that for sparse regions in only the merkle tree? IIRC (and I could be wrong) fsverity still wants to checksum sparse holes in the regular file data, right?

The _BEYOUND_EOF is only for fsverity metadata. This case handles the merkle tree tail case/end of tree. 1k fs bs 4k page 1k fsverity blocks, fsverity requests the page with a tree which is smaller than 4 fsverity blocks (e.g. 3072b). The last 1k block in the page will be hole. So, just zero out the rest and mark uptodate.

Quoted 29 lines

fs/iomap/buffered-io.c line 542

Quoted 7 lines
fs/iomap/buffered-io.c +4 -1
+			folio_zero_range(folio, poff, plen);
+			iomap_set_range_uptodate(folio, poff, plen);
+		}
 		/* zero post-eof blocks as the page may be mapped */
-		if (iomap_block_needs_zeroing(iter, pos) &&
+		else if (iomap_block_needs_zeroing(iter, pos) &&
		} else if (...

(nitpicking indentation)

Quoted 17 lines
fs/iomap/buffered-io.c +4 -0
 		    !(iomap->flags & IOMAP_F_BEYOND_EOF)) {
 			folio_zero_range(folio, poff, plen);
+			if (fsverity_active(iter->inode) &&
+			    !fsverity_verify_blocks(folio, plen, poff)) {
+				return -EIO;
+			}
 			iomap_set_range_uptodate(folio, poff, plen);
 		} else {
 			if (!*bytes_submitted)
fs/iomap/ioend.c +0 -0
index 86f44922ed..30c0de3c75 100644
@@ -9,6 +9,8 @@
 #include "internal.h"
 #include "trace.h"
 
Quoted 4 lines

fs/iomap/ioend.c line 12

Quoted 1 line
fs/iomap/ioend.c +1 -0
+#define IOMAP_POOL_SIZE		(4 * (PAGE_SIZE / SECTOR_SIZE))

How do we arrive at this pool size? How is it important to have a larger bio reserve pool for *larger* base page sizes?

Well, this is just a one which iomap uses by default for read pool. I'm not sure I know enough to optimize pool size here :)

Signature · 1 line
- Andrey
#29 lore raw

Re: [PATCH v2 5/22] iomap: integrate fs-verity verification into iomap's read path

Quoted 10 lines · Andrey Albershteyn
Quoted 4 lines

Also, when do we get the combination of BEYOND_EOF && HOLE? Is that for sparse regions in only the merkle tree? IIRC (and I could be wrong) fsverity still wants to checksum sparse holes in the regular file data, right?

The _BEYOUND_EOF is only for fsverity metadata. This case handles the merkle tree tail case/end of tree. 1k fs bs 4k page 1k fsverity blocks, fsverity requests the page with a tree which is smaller than 4 fsverity blocks (e.g. 3072b). The last 1k block in the page will be hole. So, just zero out the rest and mark uptodate.

Can you add a comment explaining this?

Quoted 7 lines
Quoted 4 lines
Quoted 1 line

+#define IOMAP_POOL_SIZE (4 * (PAGE_SIZE / SECTOR_SIZE))

How do we arrive at this pool size? How is it important to have a larger bio reserve pool for *larger* base page sizes?

Well, this is just a one which iomap uses by default for read pool. I'm not sure I know enough to optimize pool size here :)

Note that all this would go away when using ioends.

#30 lore raw

Re: [PATCH v2 5/22] iomap: integrate fs-verity verification into iomap's read path

In reply to Andrey Albershteyn

fs/iomap/bio.c line 31

Quoted 10 lines · Andrey Albershteyn
fs/iomap/bio.c +10 -0
+#ifdef CONFIG_FS_VERITY
+static void
+iomap_read_fsverify_end_io_work(struct work_struct *work)
+{
+	struct iomap_fsverity_bio *fbio =
+		container_of(work, struct iomap_fsverity_bio, work);
+
+	fsverity_verify_bio(&fbio->bio);
+	iomap_read_end_io(&fbio->bio);
+}

I'd much rather use the ioend processing infrastructure for this rather the reinventing another deferral method. This series:

https://git.infradead.org/?p=users/hch/misc.git;a=shortlog;h=refs/heads/iomap-pi

has the small patches enabling it for reads, and I will post a rebased and updated version soon.

#31 lore raw

PATCH v2 6/22 xfs: add fs-verity ro-compat flag

In reply to Andrey Albershteyn

To mark inodes with fs-verity enabled the new XFS_DIFLAG2_VERITY flag will be added in further patch. This requires ro-compat flag to let older kernels know that fs with fs-verity can not be modified.

---
 fs/xfs/libxfs/xfs_format.h | 1 +
 fs/xfs/libxfs/xfs_sb.c     | 2 ++
 fs/xfs/xfs_mount.h         | 2 ++
 3 files changed, 5 insertions(+), 0 deletions(-)
fs/xfs/libxfs/xfs_format.h +1 -0
index 779dac59b1..64c2acd1cf 100644
@@ -374,6 +374,7 @@
 #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 | \
fs/xfs/libxfs/xfs_sb.c +2 -0
index 94c272a2ae..744bd8480b 100644
@@ -165,6 +165,8 @@
 		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)
fs/xfs/xfs_mount.h +2 -0
index b871dfde37..8ef7fea8b3 100644
@@ -381,6 +381,7 @@
 #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 */
@@ -438,6 +439,7 @@
 __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)
 {
Signature · 1 line
- Andrey
#32 lore raw

PATCH v2 7/22 xfs: add inode on-disk VERITY flag

In reply to Andrey Albershteyn

Add flag to mark inodes which have fs-verity enabled on them (i.e. descriptor exist and tree is built).

---
 fs/xfs/libxfs/xfs_format.h     | 7 ++++++-
 fs/xfs/libxfs/xfs_inode_buf.c  | 8 ++++++++
 fs/xfs/libxfs/xfs_inode_util.c | 2 ++
 fs/xfs/xfs_iops.c              | 2 ++
 4 files changed, 18 insertions(+), 1 deletion(-)
fs/xfs/libxfs/xfs_format.h +6 -1
index 64c2acd1cf..d67b404964 100644
@@ -1231,16 +1231,21 @@
  */
 #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)
 {
fs/xfs/libxfs/xfs_inode_buf.c +8 -0
index b1812b2c3c..c4fff7a34c 100644
@@ -756,6 +756,14 @@
 	    !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 ((mode & S_IFMT) != S_IFREG)
+			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)
fs/xfs/libxfs/xfs_inode_util.c +2 -0
index 309ce6dd55..aaf51207b2 100644
@@ -126,6 +126,8 @@
 			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))
fs/xfs/xfs_iops.c +2 -0
index ad94fbf550..6b8e4e87ab 100644
@@ -1394,6 +1394,8 @@
 		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
Signature · 1 line
- Andrey
#33 lore raw

PATCH v2 8/22 xfs: initialize fs-verity on file open and cleanup on inode destruction

In reply to Andrey Albershteyn

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

---
 fs/xfs/xfs_file.c  | 8 ++++++++
 fs/xfs/xfs_super.c | 2 ++
 2 files changed, 10 insertions(+), 0 deletions(-)
fs/xfs/xfs_file.c +8 -0
index 7874cf745a..0c234ab449 100644
@@ -36,6 +36,7 @@
 #include <linux/mman.h>
 #include <linux/fadvise.h>
 #include <linux/mount.h>
+#include <linux/fsverity.h>
 
 static const struct vm_operations_struct xfs_file_vm_ops;
 
@@ -1604,11 +1605,18 @@
 	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);
 }
 
fs/xfs/xfs_super.c +2 -0
index bc71aa9dce..10c6fc8d20 100644
@@ -53,6 +53,7 @@
 #include <linux/magic.h>
 #include <linux/fs_context.h>
 #include <linux/fs_parser.h>
+#include <linux/fsverity.h>
 
 static const struct super_operations xfs_super_operations;
 
@@ -709,6 +710,7 @@
 	ASSERT(!rwsem_is_locked(&inode->i_rwsem));
 	XFS_STATS_INC(ip->i_mount, vn_rele);
 	XFS_STATS_INC(ip->i_mount, vn_remove);
+	fsverity_cleanup_inode(inode);
 	xfs_inode_mark_reclaimable(ip);
 }
 
Signature · 1 line
- Andrey
#34 lore raw

PATCH v2 9/22 xfs: don't allow to enable DAX on fs-verity sealed inode

In reply to Andrey Albershteyn

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.

---
 fs/xfs/xfs_iops.c | 2 ++
 1 file changed, 2 insertions(+), 0 deletions(-)
fs/xfs/xfs_iops.c +2 -0
index 6b8e4e87ab..44c6161137 100644
@@ -1366,6 +1366,8 @@
 		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)
Signature · 1 line
- Andrey
#35 lore raw

PATCH v2 10/22 xfs: disable direct read path for fs-verity files

In reply to Andrey Albershteyn

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.

---
 fs/xfs/xfs_file.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)
fs/xfs/xfs_file.c +12 -3
index 0c234ab449..87a96b81f0 100644
@@ -254,7 +254,8 @@
 	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);
@@ -307,10 +308,18 @@
 
 	if (IS_DAX(inode))
 		ret = xfs_file_dax_read(iocb, to);
-	else if (iocb->ki_flags & IOCB_DIRECT)
+	else if ((iocb->ki_flags & IOCB_DIRECT) && !fsverity_active(inode))
 		ret = xfs_file_dio_read(iocb, to);
-	else
+	else {
+		/*
+		 * 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())
+		 */
+		iocb->ki_flags &= ~IOCB_DIRECT;
 		ret = xfs_file_buffered_read(iocb, to);
+	}
 
 	if (ret > 0)
 		XFS_STATS_ADD(mp, xs_read_bytes, ret);
Signature · 1 line
- Andrey
#36 lore raw

Re: [PATCH v2 10/22] xfs: disable direct read path for fs-verity files

Quoted 6 lines · Andrey Albershteyn
fs/xfs/xfs_file.c +1 -2
 	if (IS_DAX(inode))
 		ret = xfs_file_dax_read(iocb, to);
-	else if (iocb->ki_flags & IOCB_DIRECT)
+	else if ((iocb->ki_flags & IOCB_DIRECT) && !fsverity_active(inode))
 		ret = xfs_file_dio_read(iocb, to);
-	else

fs/xfs/xfs_file.c line 322

Quoted 10 lines
fs/xfs/xfs_file.c +9 -0
+	else {
+		/*
+		 * 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())
+		 */
+		iocb->ki_flags &= ~IOCB_DIRECT;
 		ret = xfs_file_buffered_read(iocb, to);
+	}

I think this might actuall be easier as:

	if (fsverity_active(inode))
		iocb->ki_flags &= ~IOCB_DIRECT;

	...
	<existing if/else>
#37 lore raw

Re: [PATCH v2 10/22] xfs: disable direct read path for fs-verity files

Quoted 20 lines · Christoph Hellwig
Quoted 6 lines · Andrey Albershteyn
fs/xfs/xfs_file.c +1 -2
 	if (IS_DAX(inode))
 		ret = xfs_file_dax_read(iocb, to);
-	else if (iocb->ki_flags & IOCB_DIRECT)
+	else if ((iocb->ki_flags & IOCB_DIRECT) && !fsverity_active(inode))
 		ret = xfs_file_dio_read(iocb, to);
-	else

fs/xfs/xfs_file.c line 322

Quoted 10 lines
fs/xfs/xfs_file.c +9 -0
+	else {
+		/*
+		 * 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())
+		 */
+		iocb->ki_flags &= ~IOCB_DIRECT;
 		ret = xfs_file_buffered_read(iocb, to);
+	}

I think this might actuall be easier as:

Quoted 6 lines
	if (fsverity_active(inode))
		iocb->ki_flags &= ~IOCB_DIRECT;

	...
	<existing if/else>

sure, thanks!

Signature · 1 line
- Andrey
#38 lore raw

PATCH v2 11/22 xfs: add verity info pointer to xfs inode

In reply to Andrey Albershteyn

Add the fsverity_info pointer into the filesystem-specific part of the inode by adding the field xfs_inode->i_verity_info and configuring fsverity_operations::inode_info_offs accordingly.

---
 fs/xfs/xfs_icache.c | 3 +++
 fs/xfs/xfs_inode.h  | 5 +++++
 2 files changed, 8 insertions(+), 0 deletions(-)
fs/xfs/xfs_icache.c +3 -0
index 23a920437f..872785c68a 100644
@@ -130,6 +130,9 @@
 	spin_lock_init(&ip->i_ioend_lock);
 	ip->i_next_unlinked = NULLAGINO;
 	ip->i_prev_unlinked = 0;
+#ifdef CONFIG_FS_VERITY
+	ip->i_verity_info = NULL;
+#endif
 
 	return ip;
 }
fs/xfs/xfs_inode.h +5 -0
index bd6d335571..f149cb1eb5 100644
@@ -9,6 +9,7 @@
 #include "xfs_inode_buf.h"
 #include "xfs_inode_fork.h"
 #include "xfs_inode_util.h"
+#include <linux/fsverity.h>
 
 /*
  * Kernel only inode definitions
@@ -99,6 +100,10 @@
 	spinlock_t		i_ioend_lock;
 	struct work_struct	i_ioend_work;
 	struct list_head	i_ioend_list;
+
+#ifdef CONFIG_FS_VERITY
+	struct fsverity_info *i_verity_info;
+#endif
 } xfs_inode_t;
 
 static inline bool xfs_inode_on_unlinked_list(const struct xfs_inode *ip)
Signature · 1 line
- Andrey
Reviewed-by on v2 11/22
#39 lore raw

Re: [PATCH v2 11/22] xfs: add verity info pointer to xfs inode

Quoted 5 lines · Andrey Albershteyn

Add the fsverity_info pointer into the filesystem-specific part of the inode by adding the field xfs_inode->i_verity_info and configuring fsverity_operations::inode_info_offs accordingly.

I kinda don't like adding another pointer to struct xfs_inode but I can't see a better solution. Inodes can add or drop verity status dynamically so we can't do this trick:

struct xfs_verity_inode {
	struct xfs_inode	hork;
	struct fsverity_info	dork;
};

and only allocate the xfs_verity_inode when needed. Therefore,

--D

Quoted 47 lines
---
 fs/xfs/xfs_icache.c | 3 +++
 fs/xfs/xfs_inode.h  | 5 +++++
 2 files changed, 8 insertions(+), 0 deletions(-)
fs/xfs/xfs_icache.c +3 -0
index 23a920437f..872785c68a 100644
@@ -130,6 +130,9 @@
 	spin_lock_init(&ip->i_ioend_lock);
 	ip->i_next_unlinked = NULLAGINO;
 	ip->i_prev_unlinked = 0;
+#ifdef CONFIG_FS_VERITY
+	ip->i_verity_info = NULL;
+#endif
 
 	return ip;
 }
fs/xfs/xfs_inode.h +5 -0
index bd6d335571..f149cb1eb5 100644
@@ -9,6 +9,7 @@
 #include "xfs_inode_buf.h"
 #include "xfs_inode_fork.h"
 #include "xfs_inode_util.h"
+#include <linux/fsverity.h>
 
 /*
  * Kernel only inode definitions
@@ -99,6 +100,10 @@
 	spinlock_t		i_ioend_lock;
 	struct work_struct	i_ioend_work;
 	struct list_head	i_ioend_list;
+
+#ifdef CONFIG_FS_VERITY
+	struct fsverity_info *i_verity_info;
+#endif
 } xfs_inode_t;
 
 static inline bool xfs_inode_on_unlinked_list(const struct xfs_inode *ip)
Signature · 2 lines
-- 
- Andrey
#40 lore raw

Re: [PATCH v2 11/22] xfs: add verity info pointer to xfs inode

Quoted 8 lines · Darrick J. Wong
Quoted 5 lines · Andrey Albershteyn

Add the fsverity_info pointer into the filesystem-specific part of the inode by adding the field xfs_inode->i_verity_info and configuring fsverity_operations::inode_info_offs accordingly.

I kinda don't like adding another pointer to struct xfs_inode

Me neither.

Quoted 1 line

but I can't see a better solution.

Well, I suggested just making the fsverity_info a standalone object, and looking it up using a rhastable based on the ino. Eric didn't seem overly existed about that, but I think it would be really helpful to not bloat all the common inodes for fsverity.

#41 lore raw

Re: [PATCH v2 11/22] xfs: add verity info pointer to xfs inode

Quoted 10 lines · Christoph Hellwig
Quoted 8 lines · Darrick J. Wong
Quoted 5 lines · Andrey Albershteyn

Add the fsverity_info pointer into the filesystem-specific part of the inode by adding the field xfs_inode->i_verity_info and configuring fsverity_operations::inode_info_offs accordingly.

I kinda don't like adding another pointer to struct xfs_inode

Quoted 8 lines

Me neither.

Quoted 1 line

but I can't see a better solution.

Well, I suggested just making the fsverity_info a standalone object, and looking it up using a rhastable based on the ino. Eric didn't seem overly existed about that, but I think it would be really helpful to not bloat all the common inodes for fsverity.

Oh, right, now I remember you asking about using rhashtable to map an xfs_inode to a fsverity_info. I wonder how much of a performance overhead that lookup (vs. the pointer deref that's used now) would have for loading folios into the pagecache.

--D

#42 lore raw

Re: [PATCH v2 11/22] xfs: add verity info pointer to xfs inode

Quoted 9 lines · Darrick J. Wong
Quoted 4 lines

Well, I suggested just making the fsverity_info a standalone object, and looking it up using a rhastable based on the ino. Eric didn't seem overly existed about that, but I think it would be really helpful to not bloat all the common inodes for fsverity.

Oh, right, now I remember you asking about using rhashtable to map an xfs_inode to a fsverity_info. I wonder how much of a performance overhead that lookup (vs. the pointer deref that's used now) would have for loading folios into the pagecache.

If we have to do that on every folio it would suck. But if we rework the code to only do it once per operation it should be close to noise.

#43 lore raw

PATCH v2 12/22 xfs: introduce XFS_FSVERITY_CONSTRUCTION inode flag

In reply to Andrey Albershteyn

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

---
 fs/xfs/xfs_inode.h | 6 ++++++
 1 file changed, 6 insertions(+), 0 deletions(-)
fs/xfs/xfs_inode.h +6 -0
index f149cb1eb5..9373bf146a 100644
@@ -420,6 +420,12 @@
  */
 #define XFS_IREMAPPING		(1U << 15)
 
+/*
+ * fs-verity's Merkle tree is under construction. The file is read-only, the
+ * only writes happening is the ones with Merkle tree blocks.
+ */
+#define XFS_VERITY_CONSTRUCTION	(1U << 16)
+
 /* All inode state flags related to inode reclaim. */
 #define XFS_ALL_IRECLAIM_FLAGS	(XFS_IRECLAIMABLE | \
 				 XFS_IRECLAIM | \
Signature · 1 line
- Andrey
Reviewed-by on v2 12/22
#44 lore raw

Re: [PATCH v2 12/22] xfs: introduce XFS_FSVERITY_CONSTRUCTION inode flag

Quoted 3 lines · Andrey Albershteyn

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

Seems fine to me, with one nit below

--D

Quoted 9 lines
---
 fs/xfs/xfs_inode.h | 6 ++++++
 1 file changed, 6 insertions(+), 0 deletions(-)
fs/xfs/xfs_inode.h +0 -0
index f149cb1eb5..9373bf146a 100644

fs/xfs/xfs_inode.h line 425

Quoted 6 lines
fs/xfs/xfs_inode.h +3 -0
@@ -420,6 +420,12 @@
  */
 #define XFS_IREMAPPING		(1U << 15)
 
+/*
+ * fs-verity's Merkle tree is under construction. The file is read-only, the
+ * only writes happening is the ones with Merkle tree blocks.

"..the only writes happening are for the fsverity metadata."

since fsverity could in theory someday write more than just a merkle tree and a descriptor.

--D

Quoted 11 lines
fs/xfs/xfs_inode.h +3 -0
+ */
+#define XFS_VERITY_CONSTRUCTION	(1U << 16)
+
 /* All inode state flags related to inode reclaim. */
 #define XFS_ALL_IRECLAIM_FLAGS	(XFS_IRECLAIMABLE | \
 				 XFS_IRECLAIM | \
Signature · 2 lines
-- 
- Andrey
#45 lore raw

Re: [PATCH v2 12/22] xfs: introduce XFS_FSVERITY_CONSTRUCTION inode flag

Quoted 7 lines · Darrick J. Wong
Quoted 3 lines · Andrey Albershteyn

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

Seems fine to me, with one nit below

Thanks!

Quoted 15 lines

--D

Quoted 12 lines
---
 fs/xfs/xfs_inode.h | 6 ++++++
 1 file changed, 6 insertions(+), 0 deletions(-)
fs/xfs/xfs_inode.h +0 -0
index f149cb1eb5..9373bf146a 100644
@@ -420,6 +420,12 @@
  */
 #define XFS_IREMAPPING		(1U << 15)
 
Quoted 8 lines

fs/xfs/xfs_inode.h line 425

Quoted 3 lines
fs/xfs/xfs_inode.h +3 -0
+/*
+ * fs-verity's Merkle tree is under construction. The file is read-only, the
+ * only writes happening is the ones with Merkle tree blocks.

"..the only writes happening are for the fsverity metadata."

since fsverity could in theory someday write more than just a merkle tree and a descriptor.

changed

Signature · 1 line
- Andrey
#46 lore raw

PATCH v2 13/22 xfs: introduce XFS_FSVERITY_REGION_START constant

In reply to Andrey Albershteyn

This constant defines location of fsverity metadata in page cache of an inode.

---
 fs/xfs/libxfs/xfs_fs.h | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+), 0 deletions(-)
fs/xfs/libxfs/xfs_fs.h +22 -0
index 12463ba766..b73458a7c2 100644
@@ -1106,4 +1106,26 @@
 #define BBTOB(bbs)	((bbs) << BBSHIFT)
 #endif
 
+/* Merkle tree location in page cache. We take memory region from the inode's
+ * address space for Merkle tree.
+ *
+ * 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.
+ *
+ * The metadata is stored on disk as follows:
+ *
+ *	[merkle tree...][descriptor.............desc_size]
+ *	^ (1 << 53)     ^ (block border)                 ^ (end of the block)
+ *	                ^--------------------------------^
+ *	                Can be FS_VERITY_MAX_DESCRIPTOR_SIZE
+ */
+#define XFS_FSVERITY_REGION_START (1ULL << 53)
+
 #endif	/* __XFS_FS_H__ */
Signature · 1 line
- Andrey
#47 lore raw

Re: [PATCH v2 13/22] xfs: introduce XFS_FSVERITY_REGION_START constant

Quoted 13 lines · Andrey Albershteyn

This constant defines location of fsverity metadata in page cache of an inode.

---
 fs/xfs/libxfs/xfs_fs.h | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+), 0 deletions(-)
fs/xfs/libxfs/xfs_fs.h +0 -0
index 12463ba766..b73458a7c2 100644

fs/xfs/libxfs/xfs_fs.h line 1109

Quoted 4 lines
fs/xfs/libxfs/xfs_fs.h +1 -0
@@ -1106,4 +1106,26 @@
 #define BBTOB(bbs)	((bbs) << BBSHIFT)
 #endif
 
+/* Merkle tree location in page cache. We take memory region from the inode's

Dumb nit: new line after opening the multiline comment.

/*
* Merkle tree location in page cache...

also, isn't (1U<<53) the location of the Merkle tree ondisk in addition to its location in the page cache?

That occurs to me, what happens on 32-bit systems where the pagecache can only address up to 16T of data? Maybe we just don't allow fsverity on 32-bit xfs.

Quoted 10 lines
fs/xfs/libxfs/xfs_fs.h +10 -0
+ * address space for Merkle tree.
+ *
+ * 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

fs/xfs/libxfs/xfs_fs.h line 1129

Quoted 10 lines
fs/xfs/libxfs/xfs_fs.h +10 -0
+ * good.
+ *
+ * The metadata is stored on disk as follows:
+ *
+ *	[merkle tree...][descriptor.............desc_size]
+ *	^ (1 << 53)     ^ (block border)                 ^ (end of the block)
+ *	                ^--------------------------------^
+ *	                Can be FS_VERITY_MAX_DESCRIPTOR_SIZE
+ */
+#define XFS_FSVERITY_REGION_START (1ULL << 53)

Is this in fsblocks or in bytes? I think the comment should state that explicitly.

--D

Quoted 7 lines
fs/xfs/libxfs/xfs_fs.h +1 -0
+
 #endif	/* __XFS_FS_H__ */
Signature · 2 lines
-- 
- Andrey
#48 lore raw

Re: [PATCH v2 13/22] xfs: introduce XFS_FSVERITY_REGION_START constant

Quoted 21 lines · Darrick J. Wong
Quoted 13 lines · Andrey Albershteyn

This constant defines location of fsverity metadata in page cache of an inode.

---
 fs/xfs/libxfs/xfs_fs.h | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+), 0 deletions(-)
fs/xfs/libxfs/xfs_fs.h +0 -0
index 12463ba766..b73458a7c2 100644

fs/xfs/libxfs/xfs_fs.h line 1109

Quoted 4 lines
fs/xfs/libxfs/xfs_fs.h +1 -0
@@ -1106,4 +1106,26 @@
 #define BBTOB(bbs)	((bbs) << BBSHIFT)
 #endif
 
+/* Merkle tree location in page cache. We take memory region from the inode's

Dumb nit: new line after opening the multiline comment.

Quoted 5 lines

/*
* Merkle tree location in page cache...

also, isn't (1U<<53) the location of the Merkle tree ondisk in addition to its location in the page cache?

yes, it's file offset

Quoted 4 lines

That occurs to me, what happens on 32-bit systems where the pagecache can only address up to 16T of data? Maybe we just don't allow fsverity on 32-bit xfs.

hmm right, check in begin_enable() will be probably enough

Quoted 22 lines
Quoted 11 lines
fs/xfs/libxfs/xfs_fs.h +10 -0
+ * address space for Merkle tree.
+ *
+ * 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

fs/xfs/libxfs/xfs_fs.h line 1129

Quoted 10 lines
fs/xfs/libxfs/xfs_fs.h +10 -0
+ * good.
+ *
+ * The metadata is stored on disk as follows:
+ *
+ *	[merkle tree...][descriptor.............desc_size]
+ *	^ (1 << 53)     ^ (block border)                 ^ (end of the block)
+ *	                ^--------------------------------^
+ *	                Can be FS_VERITY_MAX_DESCRIPTOR_SIZE
+ */
+#define XFS_FSVERITY_REGION_START (1ULL << 53)
Quoted 2 lines

Is this in fsblocks or in bytes? I think the comment should state that explicitly.

sure, will add it

Signature · 1 line
- Andrey
#49 lore raw

Re: [PATCH v2 13/22] xfs: introduce XFS_FSVERITY_REGION_START constant

Quoted 28 lines · Andrey Albershteyn
Quoted 21 lines · Darrick J. Wong
Quoted 13 lines · Andrey Albershteyn

This constant defines location of fsverity metadata in page cache of an inode.

---
 fs/xfs/libxfs/xfs_fs.h | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+), 0 deletions(-)
fs/xfs/libxfs/xfs_fs.h +0 -0
index 12463ba766..b73458a7c2 100644

fs/xfs/libxfs/xfs_fs.h line 1109

Quoted 4 lines
fs/xfs/libxfs/xfs_fs.h +1 -0
@@ -1106,4 +1106,26 @@
 #define BBTOB(bbs)	((bbs) << BBSHIFT)
 #endif
 
+/* Merkle tree location in page cache. We take memory region from the inode's

Dumb nit: new line after opening the multiline comment.

Quoted 5 lines

/*
* Merkle tree location in page cache...

also, isn't (1U<<53) the location of the Merkle tree ondisk in addition to its location in the page cache?

Quoted 8 lines

yes, it's file offset

Quoted 4 lines

That occurs to me, what happens on 32-bit systems where the pagecache can only address up to 16T of data? Maybe we just don't allow fsverity on 32-bit xfs.

hmm right, check in begin_enable() will be probably enough

I think that would probably be more of a mount-time prohibition?

Which would be worse -- any fsverity filesystem refuses to mount on 32-bit; or it mounts but none of the fsverity files are readable?

Alternately I guess for 32-bit you could cheat in ->iomap_begin by loading the fsverity artifacts into the pagecache at 1<<39 instead of 1<<53, provided the file is smaller than 1<<39 bytes. Writing the fsverity metadata would perform the reverse translation.

(Or again we just don't allow mounting of fsverity on 32-bit kernels.)

--D

Quoted 30 lines
Quoted 21 lines
Quoted 10 lines
fs/xfs/libxfs/xfs_fs.h +10 -0
+ * address space for Merkle tree.
+ *
+ * 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

fs/xfs/libxfs/xfs_fs.h line 1129

Quoted 10 lines
fs/xfs/libxfs/xfs_fs.h +10 -0
+ * good.
+ *
+ * The metadata is stored on disk as follows:
+ *
+ *	[merkle tree...][descriptor.............desc_size]
+ *	^ (1 << 53)     ^ (block border)                 ^ (end of the block)
+ *	                ^--------------------------------^
+ *	                Can be FS_VERITY_MAX_DESCRIPTOR_SIZE
+ */
+#define XFS_FSVERITY_REGION_START (1ULL << 53)
Quoted 2 lines

Is this in fsblocks or in bytes? I think the comment should state that explicitly.

sure, will add it

Signature · 1 line
- Andrey
#50 lore raw

Re: [PATCH v2 13/22] xfs: introduce XFS_FSVERITY_REGION_START constant

Quoted 7 lines · Darrick J. Wong
Quoted 1 line

hmm right, check in begin_enable() will be probably enough

I think that would probably be more of a mount-time prohibition?

Which would be worse -- any fsverity filesystem refuses to mount on 32-bit; or it mounts but none of the fsverity files are readable?

Quoted 6 lines

Alternately I guess for 32-bit you could cheat in ->iomap_begin by loading the fsverity artifacts into the pagecache at 1<<39 instead of 1<<53, provided the file is smaller than 1<<39 bytes. Writing the fsverity metadata would perform the reverse translation.

(Or again we just don't allow mounting of fsverity on 32-bit kernels.)

What are the other file systems doing here? Unless we have a good reason to differ we should just follow the precedence.

#51 lore raw

Re: [PATCH v2 13/22] xfs: introduce XFS_FSVERITY_REGION_START constant

Quoted 13 lines · Christoph Hellwig
Quoted 12 lines · Darrick J. Wong
Quoted 1 line

hmm right, check in begin_enable() will be probably enough

I think that would probably be more of a mount-time prohibition?

Which would be worse -- any fsverity filesystem refuses to mount on 32-bit; or it mounts but none of the fsverity files are readable?

Alternately I guess for 32-bit you could cheat in ->iomap_begin by loading the fsverity artifacts into the pagecache at 1<<39 instead of 1<<53, provided the file is smaller than 1<<39 bytes. Writing the fsverity metadata would perform the reverse translation.

Quoted 5 lines
Quoted 1 line

(Or again we just don't allow mounting of fsverity on 32-bit kernels.)

What are the other file systems doing here? Unless we have a good reason to differ we should just follow the precedence.

The ext4/f2fs/btrfs does this

	round_up(inode->i_size, 65536);

It's not fixed offset but next block after EOF. As EOF won't move this is fine. So, this works for both 32/64bits

Signature · 1 line
- Andrey
#52 lore raw

Re: [PATCH v2 13/22] xfs: introduce XFS_FSVERITY_REGION_START constant

In reply to Christoph Hellwig
Quoted 13 lines · Christoph Hellwig
Quoted 12 lines · Darrick J. Wong
Quoted 1 line

hmm right, check in begin_enable() will be probably enough

I think that would probably be more of a mount-time prohibition?

Which would be worse -- any fsverity filesystem refuses to mount on 32-bit; or it mounts but none of the fsverity files are readable?

Alternately I guess for 32-bit you could cheat in ->iomap_begin by loading the fsverity artifacts into the pagecache at 1<<39 instead of 1<<53, provided the file is smaller than 1<<39 bytes. Writing the fsverity metadata would perform the reverse translation.

Quoted 4 lines
Quoted 1 line

(Or again we just don't allow mounting of fsverity on 32-bit kernels.)

What are the other file systems doing here? Unless we have a good reason to differ we should just follow the precedence.

I ext4/f2fs place the fsverity metadata at roundup(i_size, 64k) and who knows what happens if any of them ever enable large folios or have a gigantic base page size (looking at you, arch/hexagon/).

btrfs seems to persist the merkle tree elsewhere but it loads it into the pagecache at the same rounded-up address.

--D

#53 lore raw

PATCH v2 14/22 xfs: disable preallocations for fsverity Merkle tree writes

In reply to Andrey Albershteyn

While writing Merkle tree, file is read-only and there's no further writes except Merkle tree building. The file is truncated beforehand to remove any preallocated extents.

The Merkle tree is the only data XFS will write. As we don't want XFS to truncate file after we done writing, let's also skip truncation on fsverity files. Therefore, we also need to disable preallocations while writing merkle tree as we don't want any unused extents past the tree.

---
 fs/xfs/xfs_iomap.c | 28 +++++++++++++++++++++++++---
 1 file changed, 25 insertions(+), 3 deletions(-)
fs/xfs/xfs_iomap.c +25 -3
index 04f39ea158..61aab5617f 100644
@@ -1938,7 +1938,9 @@
 		 * Determine the initial size of the preallocation.
 		 * We clean up any extra preallocation when the file is closed.
 		 */
-		if (xfs_has_allocsize(mp))
+		if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+			prealloc_blocks = 0;
+		else if (xfs_has_allocsize(mp))
 			prealloc_blocks = mp->m_allocsize_blocks;
 		else if (allocfork == XFS_DATA_FORK)
 			prealloc_blocks = xfs_iomap_prealloc_size(ip, allocfork,
@@ -2065,6 +2067,13 @@
 	if (flags & IOMAP_FAULT)
 		return 0;
 
+	/*
+	 * While writing Merkle tree to disk we would not have any other
+	 * delayed allocations
+	 */
+	if (xfs_iflags_test(XFS_I(inode), XFS_VERITY_CONSTRUCTION))
+		return 0;
+
 	/* Nothing to do if we've written the entire delalloc extent */
 	start_byte = iomap_last_written_block(inode, offset, written);
 	end_byte = round_up(offset + length, i_blocksize(inode));
@@ -2109,6 +2118,7 @@
 	bool			shared = false;
 	unsigned int		lockmode = XFS_ILOCK_SHARED;
 	u64			seq;
+	int			iomap_flags;
 
 	ASSERT(!(flags & (IOMAP_WRITE | IOMAP_ZERO)));
 
@@ -2128,8 +2138,20 @@
 	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;
+
+	/*
+	 * We can not use fsverity_active() here. fsverity_active() checks for
+	 * verity info attached to inode. This info is based on data from a
+	 * verity descriptor. But to read verity descriptor we need to go
+	 * through read iomap path (this function). So, when descriptor is read
+	 * we will not set IOMAP_F_BEYOND_EOF and descriptor page will be empty
+	 * (post EOF hole).
+	 */
+	if ((offset >= XFS_FSVERITY_REGION_START) && IS_VERITY(inode))
+		iomap_flags |= IOMAP_F_BEYOND_EOF;
+
+	return xfs_bmbt_to_iomap(ip, iomap, &imap, flags, iomap_flags, seq);
 }
 
 const struct iomap_ops xfs_read_iomap_ops = {
Signature · 1 line
- Andrey
Reviewed-by on v2 14/22
#54 lore raw

Re: [PATCH v2 14/22] xfs: disable preallocations for fsverity Merkle tree writes

Quoted 58 lines · Andrey Albershteyn

While writing Merkle tree, file is read-only and there's no further writes except Merkle tree building. The file is truncated beforehand to remove any preallocated extents.

The Merkle tree is the only data XFS will write. As we don't want XFS to truncate file after we done writing, let's also skip truncation on fsverity files. Therefore, we also need to disable preallocations while writing merkle tree as we don't want any unused extents past the tree.

---
 fs/xfs/xfs_iomap.c | 28 +++++++++++++++++++++++++---
 1 file changed, 25 insertions(+), 3 deletions(-)
fs/xfs/xfs_iomap.c +12 -3
index 04f39ea158..61aab5617f 100644
@@ -1938,7 +1938,9 @@
 		 * Determine the initial size of the preallocation.
 		 * We clean up any extra preallocation when the file is closed.
 		 */
-		if (xfs_has_allocsize(mp))
+		if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+			prealloc_blocks = 0;
+		else if (xfs_has_allocsize(mp))
 			prealloc_blocks = mp->m_allocsize_blocks;
 		else if (allocfork == XFS_DATA_FORK)
 			prealloc_blocks = xfs_iomap_prealloc_size(ip, allocfork,
@@ -2065,6 +2067,13 @@
 	if (flags & IOMAP_FAULT)
 		return 0;
 
+	/*
+	 * While writing Merkle tree to disk we would not have any other
+	 * delayed allocations
+	 */
+	if (xfs_iflags_test(XFS_I(inode), XFS_VERITY_CONSTRUCTION))
+		return 0;
+
 	/* Nothing to do if we've written the entire delalloc extent */
 	start_byte = iomap_last_written_block(inode, offset, written);
 	end_byte = round_up(offset + length, i_blocksize(inode));
@@ -2109,6 +2118,7 @@
 	bool			shared = false;
 	unsigned int		lockmode = XFS_ILOCK_SHARED;
 	u64			seq;
+	int			iomap_flags;
 
 	ASSERT(!(flags & (IOMAP_WRITE | IOMAP_ZERO)));
 
@@ -2128,8 +2138,20 @@
 	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;

fs/xfs/xfs_iomap.c line 2151

Quoted 10 lines
fs/xfs/xfs_iomap.c +10 -0
+
+	/*
+	 * We can not use fsverity_active() here. fsverity_active() checks for
+	 * verity info attached to inode. This info is based on data from a
+	 * verity descriptor. But to read verity descriptor we need to go
+	 * through read iomap path (this function). So, when descriptor is read
+	 * we will not set IOMAP_F_BEYOND_EOF and descriptor page will be empty
+	 * (post EOF hole).
+	 */
+	if ((offset >= XFS_FSVERITY_REGION_START) && IS_VERITY(inode))

(Ok so XFS_FSVERITY_REGION_START is in units of bytes. Maybe cast it to loff_t to make that clearer?)

#define XFS_FSVERITY_REGION_START ((loff_t)1ULL << 53)

fs/xfs/xfs_iomap.c line 2152

Quoted 1 line
fs/xfs/xfs_iomap.c +1 -0
+		iomap_flags |= IOMAP_F_BEYOND_EOF;

Ok, this answers my question. IOMAP_F_BEYOND_EOF is indeed only intended to be set for mappings who are completely beyond EOF. IOWs, for the fsverity metadata but not the regular file data.

Looks fine to me,

--D

Quoted 10 lines
fs/xfs/xfs_iomap.c +2 -0
+
+	return xfs_bmbt_to_iomap(ip, iomap, &imap, flags, iomap_flags, seq);
 }
 
 const struct iomap_ops xfs_read_iomap_ops = {
Signature · 2 lines
-- 
- Andrey
#55 lore raw

PATCH v2 15/22 xfs: add writeback and iomap reading of Merkle tree pages

In reply to Andrey Albershteyn

In the writeback path use unbound write interface, meaning that inode size is not updated and none of the file size checks are applied.

In read path let iomap know that data is stored beyond EOF via flag. This leads to skipping of post EOF zeroing.

---
 fs/xfs/xfs_aops.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)
fs/xfs/xfs_aops.c +19 -1
index 56a5446384..30e38d5322 100644
@@ -22,6 +22,7 @@
 #include "xfs_icache.h"
 #include "xfs_zone_alloc.h"
 #include "xfs_rtgroup.h"
+#include "xfs_fsverity.h"
 
 struct xfs_writepage_ctx {
 	struct iomap_writepage_ctx ctx;
@@ -334,6 +335,7 @@
 	int			retries = 0;
 	int			error = 0;
 	unsigned int		*seq;
+	unsigned int		iomap_flags = 0;
 
 	if (xfs_is_shutdown(mp))
 		return -EIO;
@@ -427,7 +429,9 @@
 	    isnullstartblock(imap.br_startblock))
 		goto allocate_blocks;
 
-	xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, 0, XFS_WPC(wpc)->data_seq);
+	if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+		iomap_flags |= IOMAP_F_BEYOND_EOF;
+	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:
@@ -470,6 +474,9 @@
 			wpc->iomap.length = cow_offset - wpc->iomap.offset;
 	}
 
+	if (offset >= XFS_FSVERITY_REGION_START)
+		wpc->iomap.flags |= IOMAP_F_BEYOND_EOF;
+
 	ASSERT(wpc->iomap.offset <= offset);
 	ASSERT(wpc->iomap.offset + wpc->iomap.length > offset);
 	trace_xfs_map_blocks_alloc(ip, offset, count, whichfork, &imap);
@@ -698,6 +705,17 @@
 			},
 		};
 
+		if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION)) {
+			wbc->range_start = XFS_FSVERITY_REGION_START;
+			wbc->range_end = LLONG_MAX;
+			wbc->nr_to_write = LONG_MAX;
+			/*
+			 * Set IOMAP_F_BEYOND_EOF to skip initial EOF check
+			 * The following iomap->flags would be set in
+			 * xfs_map_blocks()
+			 */
+			wpc.ctx.iomap.flags |= IOMAP_F_BEYOND_EOF;
+		}
 		return iomap_writepages(&wpc.ctx);
 	}
 }
Signature · 1 line
- Andrey
#56 lore raw

Re: [PATCH v2 15/22] xfs: add writeback and iomap reading of Merkle tree pages

Quoted 56 lines · Andrey Albershteyn

In the writeback path use unbound write interface, meaning that inode size is not updated and none of the file size checks are applied.

In read path let iomap know that data is stored beyond EOF via flag. This leads to skipping of post EOF zeroing.

---
 fs/xfs/xfs_aops.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)
fs/xfs/xfs_aops.c +8 -1
index 56a5446384..30e38d5322 100644
@@ -22,6 +22,7 @@
 #include "xfs_icache.h"
 #include "xfs_zone_alloc.h"
 #include "xfs_rtgroup.h"
+#include "xfs_fsverity.h"
 
 struct xfs_writepage_ctx {
 	struct iomap_writepage_ctx ctx;
@@ -334,6 +335,7 @@
 	int			retries = 0;
 	int			error = 0;
 	unsigned int		*seq;
+	unsigned int		iomap_flags = 0;
 
 	if (xfs_is_shutdown(mp))
 		return -EIO;
@@ -427,7 +429,9 @@
 	    isnullstartblock(imap.br_startblock))
 		goto allocate_blocks;
 
-	xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, 0, XFS_WPC(wpc)->data_seq);
+	if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION))
+		iomap_flags |= IOMAP_F_BEYOND_EOF;
+	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:
@@ -470,6 +474,9 @@
 			wpc->iomap.length = cow_offset - wpc->iomap.offset;
 	}
 
+	if (offset >= XFS_FSVERITY_REGION_START)
+		wpc->iomap.flags |= IOMAP_F_BEYOND_EOF;
+
 	ASSERT(wpc->iomap.offset <= offset);
 	ASSERT(wpc->iomap.offset + wpc->iomap.length > offset);
 	trace_xfs_map_blocks_alloc(ip, offset, count, whichfork, &imap);
@@ -698,6 +705,17 @@
 			},
 		};
 

fs/xfs/xfs_aops.c line 717

Quoted 10 lines
fs/xfs/xfs_aops.c +10 -0
+		if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION)) {
+			wbc->range_start = XFS_FSVERITY_REGION_START;
+			wbc->range_end = LLONG_MAX;
+			wbc->nr_to_write = LONG_MAX;
+			/*
+			 * Set IOMAP_F_BEYOND_EOF to skip initial EOF check
+			 * The following iomap->flags would be set in
+			 * xfs_map_blocks()
+			 */
+			wpc.ctx.iomap.flags |= IOMAP_F_BEYOND_EOF;

But won't xfs_map_blocks reset wpc.ctx.iomap.flags?

/me realizes that you /are/ using writeback for writing the fsverity metadata now, so he'll go back and look at the iomap patches a little closer.

--D

Quoted 9 lines
fs/xfs/xfs_aops.c +1 -0
+		}
 		return iomap_writepages(&wpc.ctx);
 	}
 }
Signature · 2 lines
-- 
- Andrey
#57 lore raw

Re: [PATCH v2 15/22] xfs: add writeback and iomap reading of Merkle tree pages

Quoted 7 lines · Darrick J. Wong

fs/xfs/xfs_aops.c line 717

Quoted 1 line
fs/xfs/xfs_aops.c +1 -0
+			wpc.ctx.iomap.flags |= IOMAP_F_BEYOND_EOF;

But won't xfs_map_blocks reset wpc.ctx.iomap.flags?

/me realizes that you /are/ using writeback for writing the fsverity metadata now, so he'll go back and look at the iomap patches a little closer.

The real question to me is why we're doing that, instead of doing a simple direct I/O-style I/O (or even doing actual dio using a bvec buffer)?

#58 lore raw

Re: [PATCH v2 15/22] xfs: add writeback and iomap reading of Merkle tree pages

Quoted 4 lines · Christoph Hellwig
Quoted 3 lines · Darrick J. Wong

fs/xfs/xfs_aops.c line 717

Quoted 1 line
fs/xfs/xfs_aops.c +1 -0
+			wpc.ctx.iomap.flags |= IOMAP_F_BEYOND_EOF;

But won't xfs_map_blocks reset wpc.ctx.iomap.flags?

It will, this one is only for the initial run. This is what allows to pass that check in iomap_writeback_folio(), the iomap_writeback_handle_eof() call. Further folios would have this flag set in xfs_map_blocks().

Quoted 9 lines
Quoted 4 lines

/me realizes that you /are/ using writeback for writing the fsverity metadata now, so he'll go back and look at the iomap patches a little closer.

The real question to me is why we're doing that, instead of doing a simple direct I/O-style I/O (or even doing actual dio using a bvec buffer)?

fs-verity uses page cache for caching verified status via setting flag on the verified pages.

Signature · 1 line
- Andrey
#59 lore raw

PATCH v2 16/22 xfs: add fs-verity support

In reply to Andrey Albershteyn

Add integration with fs-verity. XFS stores fs-verity descriptor and Merkle tree in the inode data fork at offset file offset (1 << 53).

The Merkle tree reading/writing is done through iomap interface. The data itself are 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 on BIO 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).

The descriptor is stored in a new block 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).

---
 fs/xfs/Makefile        |   1 +
 fs/xfs/xfs_bmap_util.c |   7 +
 fs/xfs/xfs_fsverity.c  | 376 ++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/xfs/xfs_fsverity.h  |  12 +
 fs/xfs/xfs_message.c   |   4 +
 fs/xfs/xfs_message.h   |   1 +
 fs/xfs/xfs_super.c     |  14 +
 7 files changed, 415 insertions(+), 0 deletions(-)
fs/xfs/Makefile +1 -0
index 5bf501cf82..ad66439db7 100644
@@ -147,6 +147,7 @@
 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)
fs/xfs/xfs_bmap_util.c +7 -0
index 2208a720ec..79a255a3ac 100644
@@ -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 */
 
@@ -554,6 +555,12 @@
 		return false;
 
 	/*
+	 * Nothing to clean on fsverity inodes as they are read-only
+	 */
+	if (IS_VERITY(VFS_I(ip)))
+		return false;
+
+	/*
 	 * Check if there is an post-EOF extent to free.  If there are any
 	 * delalloc blocks attached to the inode (data fork delalloc
 	 * reservations or CoW extents of any kind), we need to free them so
fs/xfs/xfs_fsverity.c new file +376 -0

376 lines not on this page · raw message

fs/xfs/xfs_fsverity.h new file +12 -0
new file mode 100644
index 0000000000..8b0d7ef456
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 Red Hat, Inc.
+ */
+#ifndef __XFS_FSVERITY_H__
+#define __XFS_FSVERITY_H__
+
+#ifdef CONFIG_FS_VERITY
+extern const struct fsverity_operations xfs_fsverity_ops;
+#endif	/* CONFIG_FS_VERITY */
+
+#endif	/* __XFS_FSVERITY_H__ */
fs/xfs/xfs_message.c +4 -0
index 19aba2c3d5..17f0f0ca7b 100644
@@ -161,6 +161,10 @@
 			.opstate	= XFS_OPSTATE_WARNED_ZONED,
 			.name		= "zoned RT device",
 		},
+		[XFS_EXPERIMENTAL_FSVERITY] = {
+			.opstate	= XFS_OPSTATE_WARNED_ZONED,
+			.name		= "fsverity",
+		},
 	};
 	ASSERT(feat >= 0 && feat < XFS_EXPERIMENTAL_MAX);
 	BUILD_BUG_ON(ARRAY_SIZE(features) != XFS_EXPERIMENTAL_MAX);
fs/xfs/xfs_message.h +1 -0
index d68e72379f..1647d32ea4 100644
@@ -96,6 +96,7 @@
 	XFS_EXPERIMENTAL_LBS,
 	XFS_EXPERIMENTAL_METADIR,
 	XFS_EXPERIMENTAL_ZONED,
+	XFS_EXPERIMENTAL_FSVERITY,
 
 	XFS_EXPERIMENTAL_MAX,
 };
fs/xfs/xfs_super.c +14 -0
index 10c6fc8d20..42a16b15a6 100644
@@ -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"
@@ -54,6 +55,7 @@
 #include <linux/fs_context.h>
 #include <linux/fs_parser.h>
 #include <linux/fsverity.h>
+#include <linux/iomap.h>
 
 static const struct super_operations xfs_super_operations;
 
@@ -1706,6 +1708,9 @@
 	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
@@ -1959,10 +1964,19 @@
 		xfs_set_resuming_quotaon(mp);
 	mp->m_qflags &= ~XFS_QFLAGS_MNTOPTS;
 
+	if (xfs_has_verity(mp))
+		xfs_warn_experimental(mp, XFS_EXPERIMENTAL_FSVERITY);
+
 	error = xfs_mountfs(mp);
 	if (error)
 		goto out_filestream_unmount;
 
+#ifdef CONFIG_FS_VERITY
+	error = iomap_fsverity_init_bioset();
+	if (error)
+		goto out_unmount;
+#endif
+
 	root = igrab(VFS_I(mp->m_rootip));
 	if (!root) {
 		error = -ENOENT;
Signature · 1 line
- Andrey
#60 lore raw

Re: [PATCH v2 16/22] xfs: add fs-verity support

Quoted 8 lines · Andrey Albershteyn

Add integration with fs-verity. XFS stores fs-verity descriptor and Merkle tree in the inode data fork at offset file offset (1 << 53).

The Merkle tree reading/writing is done through iomap interface. The data itself are 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 on BIO completion in a workqueue.

Quoted 4 lines

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).

Might want to mention that XFS_DIFLAG2_VERITY sets S_VERITY, and that XFS_IVERITY_CONSTRUCTION gets dropped after construction ends.

Quoted 3 lines

The descriptor is stored in a new block 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).

Huh, I would have thought the descriptor would go at 1<<53 and the merkle tree goes immediately afterwards but eh, whatever. :)

Quoted 112 lines
---
 fs/xfs/Makefile        |   1 +
 fs/xfs/xfs_bmap_util.c |   7 +
 fs/xfs/xfs_fsverity.c  | 376 ++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/xfs/xfs_fsverity.h  |  12 +
 fs/xfs/xfs_message.c   |   4 +
 fs/xfs/xfs_message.h   |   1 +
 fs/xfs/xfs_super.c     |  14 +
 7 files changed, 415 insertions(+), 0 deletions(-)
fs/xfs/Makefile +1 -0
index 5bf501cf82..ad66439db7 100644
@@ -147,6 +147,7 @@
 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)
fs/xfs/xfs_bmap_util.c +7 -0
index 2208a720ec..79a255a3ac 100644
@@ -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 */
 
@@ -554,6 +555,12 @@
 		return false;
 
 	/*
+	 * Nothing to clean on fsverity inodes as they are read-only
+	 */
+	if (IS_VERITY(VFS_I(ip)))
+		return false;
+
+	/*
 	 * Check if there is an post-EOF extent to free.  If there are any
 	 * delalloc blocks attached to the inode (data fork delalloc
 	 * reservations or CoW extents of any kind), we need to free them so
fs/xfs/xfs_fsverity.c new file +58 -0
new file mode 100644
index 0000000000..691dc60778
@@ -0,0 +1,376 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025 Red Hat, Inc.
+ */
+#include "xfs.h"
+#include "xfs_shared.h"
+#include "xfs_format.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_da_format.h"
+#include "xfs_da_btree.h"
+#include "xfs_inode.h"
+#include "xfs_log_format.h"
+#include "xfs_bmap_util.h"
+#include "xfs_log_format.h"
+#include "xfs_trans.h"
+#include "xfs_trace.h"
+#include "xfs_quota.h"
+#include "xfs_fsverity.h"
+#include "xfs_iomap.h"
+#include <linux/fsverity.h>
+#include <linux/pagemap.h>
+
+static int
+xfs_fsverity_read(
+	struct inode	*inode,
+	void		*buf,
+	size_t		count,
+	loff_t		pos)
+{
+	struct folio	*folio;
+	size_t		n;
+
+	while (count) {
+		folio = read_mapping_folio(inode->i_mapping, pos >> PAGE_SHIFT,
+					 NULL);
+		if (IS_ERR(folio))
+			return PTR_ERR(folio);
+
+		n = memcpy_from_file_folio(buf, folio, pos, count);
+		folio_put(folio);
+
+		buf += n;
+		pos += n;
+		count -= n;
+	}
+	return 0;
+}
+
+static int
+xfs_fsverity_write(
+	struct xfs_inode	*ip,
+	loff_t			pos,
+	size_t			length,
+	const void		*buf)
+{
+	int			ret;
+	struct iov_iter		iter;

fs/xfs/xfs_fsverity.c line 68

Quoted 10 lines
fs/xfs/xfs_fsverity.c new file +10 -0
+	struct kvec		kvec = {
+		.iov_base	= (void *)buf,
+		.iov_len	= length,
+	};
+	struct kiocb		iocb = {
+		/* 
+		 * We don't have file here, but iomap_file_buffered_write uses
+		 * it only to obtain inode, so, pass inode as private arg
+		 * directly
+		 */

Oh, it occurs to me that fsverity doesn't take the struct file and pass it through to the per-fs implementation. And that's why you had to resort to the trick of passing the struct inode in as "private" earlier.

I think that needs to get fixed, unfortunately it's a treewide change. What if you wrote a iomap_write_iter wrapper that skips all the iocb junk and just takes the inode/pos/len directly?

Quoted 58 lines
fs/xfs/xfs_fsverity.c new file +58 -0
+		.ki_filp = NULL,
+		.ki_ioprio = get_current_ioprio(),
+		.ki_pos = pos,
+	};
+
+	iov_iter_kvec(&iter, WRITE, &kvec, 1, length);
+	ret = iomap_file_buffered_write(&iocb, &iter,
+			&xfs_buffered_write_iomap_ops, &xfs_iomap_write_ops,
+			VFS_I(ip));
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/*
+ * 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);
+	__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;
+
+	ASSERT(inode->i_flags & S_VERITY);
+	error = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, &rec, &is_empty);
+	if (error)
+		return error;
+
+	if (is_empty)
+		return -ENODATA;
+
+	last_block = (rec.br_startoff + rec.br_blockcount);
+	desc_size_pos = (last_block << ip->i_mount->m_sb.sb_blocklog) -
+			sizeof(__be32);
+	error = xfs_fsverity_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 (desc_size > FS_VERITY_MAX_DESCRIPTOR_SIZE || desc_size > desc_size_pos)
+		return -ERANGE;
+
+	if (!buf_size)
+		return desc_size;
+

fs/xfs/xfs_fsverity.c line 136

Quoted 10 lines
fs/xfs/xfs_fsverity.c new file +10 -0
+	if (desc_size > buf_size)
+		return -ERANGE;
+
+	desc_pos = round_down(desc_size_pos - desc_size, blocksize);
+	error = xfs_fsverity_read(inode, buf, desc_size, desc_pos);
+	if (error)
+		return error;
+
+	return desc_size;
+}

You might want to wrap the integrity checks through XFS_IS_CORRUPT so that we get some logging on corrupt fsverity data. Also, if descriptor corruption doesn't prevent iget from completing, then we ought to define a new health state for the xfs_inode so that it can report those kinds of failures via bulkstat.

Quoted 35 lines
fs/xfs/xfs_fsverity.c new file +35 -0
+
+static int
+xfs_fsverity_write_descriptor(
+	struct xfs_inode	*ip,
+	const void		*desc,
+	u32			desc_size,
+	u64			merkle_tree_size)
+{
+	int			error;
+	unsigned int		blksize = ip->i_mount->m_attr_geo->blksize;
+	u64			desc_pos = round_up(
+			XFS_FSVERITY_REGION_START | merkle_tree_size, blksize);
+	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 = xfs_fsverity_write(ip, desc_size_pos,
+				   sizeof(__be32),
+				   (const void *)&desc_size_disk);
+	if (error)
+		return error;
+
+	error = xfs_fsverity_write(ip, desc_pos, desc_size, desc);
+
+	return error;
+}
+
+/*
+ * Try to remove all the fsverity metadata after a failed enablement.
+ */
+static int
+xfs_fsverity_delete_metadata(
+	struct xfs_inode	*ip)

fs/xfs/xfs_fsverity.c line 181

Quoted 10 lines
fs/xfs/xfs_fsverity.c new file +10 -0
+{
+	struct xfs_trans	*tp;
+	struct xfs_mount	*mp = ip->i_mount;
+	int			error;
+
+	error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp);
+	if (error)
+		return error;
+
+	xfs_ilock(ip, XFS_MMAPLOCK_EXCL);

The MMAPLOCK should be taken before the transaction allocation like everything else in xfs.

Quoted 46 lines
fs/xfs/xfs_fsverity.c new file +46 -0
+	error = xfs_truncate_page(ip, XFS_ISIZE(ip), NULL, NULL);
+	xfs_iunlock(ip, XFS_MMAPLOCK_EXCL);
+
+	xfs_ilock(ip, XFS_ILOCK_EXCL);
+	xfs_trans_ijoin(tp, ip, 0);
+
+	/*
+	 * We removing post EOF data, no need to update i_size
+	 */
+	error = xfs_itruncate_extents(&tp, ip, XFS_DATA_FORK, XFS_ISIZE(ip));
+	if (error)
+		goto err_cancel;
+
+	error = xfs_trans_commit(tp);
+	if (error)
+		goto err_cancel;
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+
+	return error;
+
+err_cancel:
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	xfs_trans_cancel(tp);
+	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))
+		return -EINVAL;
+
+	if (inode->i_size > XFS_FSVERITY_REGION_START)
+		return -EFBIG;
+

fs/xfs/xfs_fsverity.c line 237

Quoted 10 lines
fs/xfs/xfs_fsverity.c new file +10 -0
+	if (xfs_iflags_test_and_set(ip, XFS_VERITY_CONSTRUCTION))
+		return -EBUSY;
+
+	error = xfs_qm_dqattach(ip);
+	if (error)
+		return error;
+
+	/*
+	 * Flush pagecache before building Merkle tree. Inode is locked and no
+	 * further writes will happen to the file except fsverity metadata

Don't we need to take the MMAPLOCK to prevent concurrent write faults?

Quoted 97 lines
fs/xfs/xfs_fsverity.c new file +97 -0
+	 */
+	error = filemap_write_and_wait(inode->i_mapping);
+	if (error)
+		return error;
+
+	return xfs_fsverity_delete_metadata(ip);
+}
+
+/*
+ * Complete (or fail) the process of enabling fsverity.
+ */
+static int
+xfs_fsverity_end_enable(
+	struct file		*filp,
+	const void		*desc,
+	size_t			desc_size,
+	u64			merkle_tree_size)
+{
+	struct inode		*inode = file_inode(filp);
+	struct xfs_inode	*ip = XFS_I(inode);
+	struct xfs_mount	*mp = ip->i_mount;
+	struct xfs_trans	*tp;
+	int			error = 0;
+
+	xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);
+
+	/* fs-verity failed, just cleanup */
+	if (desc == NULL)
+		goto out;
+
+	error = xfs_fsverity_write_descriptor(ip, 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(inode->i_mapping);
+	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)
+		inode->i_flags |= S_VERITY;
+
+out:
+	if (error) {
+		int	error2;
+
+		error2 = xfs_fsverity_delete_metadata(ip);
+		if (error2)
+			xfs_alert(ip->i_mount,
+"ino 0x%llx failed to clean up new fsverity metadata, err %d",
+					ip->i_ino, 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,
+	unsigned long		num_ra_pages)
+{
+	struct folio            *folio;
+	pgoff_t			offset =
+			index | (XFS_FSVERITY_REGION_START >> PAGE_SHIFT);
+
+	folio = __filemap_get_folio(inode->i_mapping, offset, FGP_ACCESSED, 0);
+	if (IS_ERR(folio) || !folio_test_uptodate(folio)) {
+		DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, offset);

fs/xfs/xfs_fsverity.c line 344

Quoted 10 lines
fs/xfs/xfs_fsverity.c new file +10 -0
+
+		if (!IS_ERR(folio))
+			folio_put(folio);
+		else if (num_ra_pages > 1)
+			page_cache_ra_unbounded(&ractl, num_ra_pages, 0);
+		folio = read_mapping_folio(inode->i_mapping, offset, NULL);
+		if (IS_ERR(folio))
+			return ERR_CAST(folio);
+	}
+	return folio_file_page(folio, offset);

Shouldn't this be some _BEYOND_EOF variant of generic_file_read_iter?

Quoted 13 lines
fs/xfs/xfs_fsverity.c new file +13 -0
+}
+
+/*
+ * Write a merkle tree block.
+ */
+static int
+xfs_fsverity_write_merkle(
+	struct inode		*inode,
+	const void		*buf,
+	u64			pos,
+	unsigned int		size)
+{
+	struct xfs_inode	*ip = XFS_I(inode);

fs/xfs/xfs_fsverity.c line 367

Quoted 10 lines
fs/xfs/xfs_fsverity.c new file +10 -0
+	loff_t			position = pos | XFS_FSVERITY_REGION_START;
+
+	if (position + size > inode->i_sb->s_maxbytes)
+		return -EFBIG;
+
+	return xfs_fsverity_write(ip, position, size, buf);
+}
+
+const ptrdiff_t info_offs = (int)offsetof(struct xfs_inode, i_verity_info) -
+			    (int)offsetof(struct xfs_inode, i_vnode);

I ... wow.

Not blaming you for writing this, just surprised that the common code makes you do that.

Quoted 86 lines
fs/xfs/xfs_fsverity.c new file +8 -0
+const struct fsverity_operations xfs_fsverity_ops = {
+	.inode_info_offs		= info_offs,
+	.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,
+	.write_merkle_tree_block	= xfs_fsverity_write_merkle,
+};
fs/xfs/xfs_fsverity.h new file +12 -0
new file mode 100644
index 0000000000..8b0d7ef456
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 Red Hat, Inc.
+ */
+#ifndef __XFS_FSVERITY_H__
+#define __XFS_FSVERITY_H__
+
+#ifdef CONFIG_FS_VERITY
+extern const struct fsverity_operations xfs_fsverity_ops;
+#endif	/* CONFIG_FS_VERITY */
+
+#endif	/* __XFS_FSVERITY_H__ */
fs/xfs/xfs_message.c +4 -0
index 19aba2c3d5..17f0f0ca7b 100644
@@ -161,6 +161,10 @@
 			.opstate	= XFS_OPSTATE_WARNED_ZONED,
 			.name		= "zoned RT device",
 		},
+		[XFS_EXPERIMENTAL_FSVERITY] = {
+			.opstate	= XFS_OPSTATE_WARNED_ZONED,
+			.name		= "fsverity",
+		},
 	};
 	ASSERT(feat >= 0 && feat < XFS_EXPERIMENTAL_MAX);
 	BUILD_BUG_ON(ARRAY_SIZE(features) != XFS_EXPERIMENTAL_MAX);
fs/xfs/xfs_message.h +1 -0
index d68e72379f..1647d32ea4 100644
@@ -96,6 +96,7 @@
 	XFS_EXPERIMENTAL_LBS,
 	XFS_EXPERIMENTAL_METADIR,
 	XFS_EXPERIMENTAL_ZONED,
+	XFS_EXPERIMENTAL_FSVERITY,
 
 	XFS_EXPERIMENTAL_MAX,
 };
fs/xfs/xfs_super.c +5 -0
index 10c6fc8d20..42a16b15a6 100644
@@ -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"
@@ -54,6 +55,7 @@
 #include <linux/fs_context.h>
 #include <linux/fs_parser.h>
 #include <linux/fsverity.h>
+#include <linux/iomap.h>
 
 static const struct super_operations xfs_super_operations;
 
@@ -1706,6 +1708,9 @@
 	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
@@ -1959,10 +1964,19 @@
 		xfs_set_resuming_quotaon(mp);
 	mp->m_qflags &= ~XFS_QFLAGS_MNTOPTS;

fs/xfs/xfs_super.c line 1975

Quoted 10 lines
fs/xfs/xfs_super.c +5 -0
 
+	if (xfs_has_verity(mp))
+		xfs_warn_experimental(mp, XFS_EXPERIMENTAL_FSVERITY);
+
 	error = xfs_mountfs(mp);
 	if (error)
 		goto out_filestream_unmount;
 
+#ifdef CONFIG_FS_VERITY
+	error = iomap_fsverity_init_bioset();

if (xfs_has_verity()) ?

--D

Quoted 12 lines
fs/xfs/xfs_super.c +4 -0
+	if (error)
+		goto out_unmount;
+#endif
+
 	root = igrab(VFS_I(mp->m_rootip));
 	if (!root) {
 		error = -ENOENT;
Signature · 2 lines
-- 
- Andrey
#61 lore raw

Re: [PATCH v2 16/22] xfs: add fs-verity support

Quoted 9 lines · Darrick J. Wong
Quoted 8 lines · Andrey Albershteyn

Add integration with fs-verity. XFS stores fs-verity descriptor and Merkle tree in the inode data fork at offset file offset (1 << 53).

The Merkle tree reading/writing is done through iomap interface. The data itself are 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 on BIO completion in a workqueue.

Quoted 7 lines
Quoted 4 lines

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).

Might want to mention that XFS_DIFLAG2_VERITY sets S_VERITY, and that XFS_IVERITY_CONSTRUCTION gets dropped after construction ends.

added

Quoted 7 lines
Quoted 4 lines

The descriptor is stored in a new block 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).

Huh, I would have thought the descriptor would go at 1<<53 and the merkle tree goes immediately afterwards but eh, whatever. :)

Yeah, I did it initially as it's easier to find both, descriptor and tree. But it will cause problems if descriptor will ever grow in size (current max is 16k).

Quoted 128 lines
Quoted 113 lines
---
 fs/xfs/Makefile        |   1 +
 fs/xfs/xfs_bmap_util.c |   7 +
 fs/xfs/xfs_fsverity.c  | 376 ++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/xfs/xfs_fsverity.h  |  12 +
 fs/xfs/xfs_message.c   |   4 +
 fs/xfs/xfs_message.h   |   1 +
 fs/xfs/xfs_super.c     |  14 +
 7 files changed, 415 insertions(+), 0 deletions(-)
fs/xfs/Makefile +1 -0
index 5bf501cf82..ad66439db7 100644
@@ -147,6 +147,7 @@
 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)
fs/xfs/xfs_bmap_util.c +7 -0
index 2208a720ec..79a255a3ac 100644
@@ -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 */
 
@@ -554,6 +555,12 @@
 		return false;
 
 	/*
+	 * Nothing to clean on fsverity inodes as they are read-only
+	 */
+	if (IS_VERITY(VFS_I(ip)))
+		return false;
+
+	/*
 	 * Check if there is an post-EOF extent to free.  If there are any
 	 * delalloc blocks attached to the inode (data fork delalloc
 	 * reservations or CoW extents of any kind), we need to free them so
fs/xfs/xfs_fsverity.c new file +58 -0
new file mode 100644
index 0000000000..691dc60778
@@ -0,0 +1,376 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025 Red Hat, Inc.
+ */
+#include "xfs.h"
+#include "xfs_shared.h"
+#include "xfs_format.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_da_format.h"
+#include "xfs_da_btree.h"
+#include "xfs_inode.h"
+#include "xfs_log_format.h"
+#include "xfs_bmap_util.h"
+#include "xfs_log_format.h"
+#include "xfs_trans.h"
+#include "xfs_trace.h"
+#include "xfs_quota.h"
+#include "xfs_fsverity.h"
+#include "xfs_iomap.h"
+#include <linux/fsverity.h>
+#include <linux/pagemap.h>
+
+static int
+xfs_fsverity_read(
+	struct inode	*inode,
+	void		*buf,
+	size_t		count,
+	loff_t		pos)
+{
+	struct folio	*folio;
+	size_t		n;
+
+	while (count) {
+		folio = read_mapping_folio(inode->i_mapping, pos >> PAGE_SHIFT,
+					 NULL);
+		if (IS_ERR(folio))
+			return PTR_ERR(folio);
+
+		n = memcpy_from_file_folio(buf, folio, pos, count);
+		folio_put(folio);
+
+		buf += n;
+		pos += n;
+		count -= n;
+	}
+	return 0;
+}
+
+static int
+xfs_fsverity_write(
+	struct xfs_inode	*ip,
+	loff_t			pos,
+	size_t			length,
+	const void		*buf)
+{
+	int			ret;
+	struct iov_iter		iter;

fs/xfs/xfs_fsverity.c line 68

Quoted 10 lines
fs/xfs/xfs_fsverity.c new file +10 -0
+	struct kvec		kvec = {
+		.iov_base	= (void *)buf,
+		.iov_len	= length,
+	};
+	struct kiocb		iocb = {
+		/* 
+		 * We don't have file here, but iomap_file_buffered_write uses
+		 * it only to obtain inode, so, pass inode as private arg
+		 * directly
+		 */

Oh, it occurs to me that fsverity doesn't take the struct file and pass it through to the per-fs implementation. And that's why you had to resort to the trick of passing the struct inode in as "private" earlier.

Quoted 3 lines

I think that needs to get fixed, unfortunately it's a treewide change. What if you wrote a iomap_write_iter wrapper that skips all the iocb junk and just takes the inode/pos/len directly?

I will add a wrapper for now

Quoted 67 lines
Quoted 67 lines
fs/xfs/xfs_fsverity.c new file +66 -0
+		.ki_filp = NULL,
+		.ki_ioprio = get_current_ioprio(),
+		.ki_pos = pos,
+	};
+
+	iov_iter_kvec(&iter, WRITE, &kvec, 1, length);
+	ret = iomap_file_buffered_write(&iocb, &iter,
+			&xfs_buffered_write_iomap_ops, &xfs_iomap_write_ops,
+			VFS_I(ip));
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/*
+ * 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);
+	__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;
+
+	ASSERT(inode->i_flags & S_VERITY);
+	error = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, &rec, &is_empty);
+	if (error)
+		return error;
+
+	if (is_empty)
+		return -ENODATA;
+
+	last_block = (rec.br_startoff + rec.br_blockcount);
+	desc_size_pos = (last_block << ip->i_mount->m_sb.sb_blocklog) -
+			sizeof(__be32);
+	error = xfs_fsverity_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 (desc_size > FS_VERITY_MAX_DESCRIPTOR_SIZE || desc_size > desc_size_pos)
+		return -ERANGE;
+
+	if (!buf_size)
+		return desc_size;
+
+	if (desc_size > buf_size)
+		return -ERANGE;
+
+	desc_pos = round_down(desc_size_pos - desc_size, blocksize);
+	error = xfs_fsverity_read(inode, buf, desc_size, desc_pos);
+	if (error)
+		return error;
+
Quoted 8 lines

fs/xfs/xfs_fsverity.c line 136

Quoted 2 lines
fs/xfs/xfs_fsverity.c new file +2 -0
+	return desc_size;
+}

You might want to wrap the integrity checks through XFS_IS_CORRUPT so that we get some logging on corrupt fsverity data. Also, if descriptor corruption doesn't prevent iget from completing, then we ought to define a new health state for the xfs_inode so that it can report those kinds of failures via bulkstat.

sure, thanks! I will add XFS_IS_CORRUPT

Quoted 41 lines
Quoted 41 lines
fs/xfs/xfs_fsverity.c new file +40 -0
+
+static int
+xfs_fsverity_write_descriptor(
+	struct xfs_inode	*ip,
+	const void		*desc,
+	u32			desc_size,
+	u64			merkle_tree_size)
+{
+	int			error;
+	unsigned int		blksize = ip->i_mount->m_attr_geo->blksize;
+	u64			desc_pos = round_up(
+			XFS_FSVERITY_REGION_START | merkle_tree_size, blksize);
+	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 = xfs_fsverity_write(ip, desc_size_pos,
+				   sizeof(__be32),
+				   (const void *)&desc_size_disk);
+	if (error)
+		return error;
+
+	error = xfs_fsverity_write(ip, desc_pos, desc_size, desc);
+
+	return error;
+}
+
+/*
+ * 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;
+
Quoted 8 lines

fs/xfs/xfs_fsverity.c line 181

Quoted 5 lines
fs/xfs/xfs_fsverity.c new file +5 -0
+	error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp);
+	if (error)
+		return error;
+
+	xfs_ilock(ip, XFS_MMAPLOCK_EXCL);

The MMAPLOCK should be taken before the transaction allocation like everything else in xfs.

hmm, but is it needed for transaction? I meant to only wrap xfs_truncate_page() with mmaplock

Quoted 51 lines
Quoted 51 lines
fs/xfs/xfs_fsverity.c new file +50 -0
+	error = xfs_truncate_page(ip, XFS_ISIZE(ip), NULL, NULL);
+	xfs_iunlock(ip, XFS_MMAPLOCK_EXCL);
+
+	xfs_ilock(ip, XFS_ILOCK_EXCL);
+	xfs_trans_ijoin(tp, ip, 0);
+
+	/*
+	 * We removing post EOF data, no need to update i_size
+	 */
+	error = xfs_itruncate_extents(&tp, ip, XFS_DATA_FORK, XFS_ISIZE(ip));
+	if (error)
+		goto err_cancel;
+
+	error = xfs_trans_commit(tp);
+	if (error)
+		goto err_cancel;
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+
+	return error;
+
+err_cancel:
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	xfs_trans_cancel(tp);
+	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))
+		return -EINVAL;
+
+	if (inode->i_size > XFS_FSVERITY_REGION_START)
+		return -EFBIG;
+
+	if (xfs_iflags_test_and_set(ip, XFS_VERITY_CONSTRUCTION))
+		return -EBUSY;
+
+	error = xfs_qm_dqattach(ip);
Quoted 8 lines

fs/xfs/xfs_fsverity.c line 237

Quoted 6 lines
fs/xfs/xfs_fsverity.c new file +6 -0
+	if (error)
+		return error;
+
+	/*
+	 * Flush pagecache before building Merkle tree. Inode is locked and no
+	 * further writes will happen to the file except fsverity metadata

Don't we need to take the MMAPLOCK to prevent concurrent write faults?

will add it

Quoted 102 lines
Quoted 102 lines
fs/xfs/xfs_fsverity.c new file +101 -0
+	 */
+	error = filemap_write_and_wait(inode->i_mapping);
+	if (error)
+		return error;
+
+	return xfs_fsverity_delete_metadata(ip);
+}
+
+/*
+ * Complete (or fail) the process of enabling fsverity.
+ */
+static int
+xfs_fsverity_end_enable(
+	struct file		*filp,
+	const void		*desc,
+	size_t			desc_size,
+	u64			merkle_tree_size)
+{
+	struct inode		*inode = file_inode(filp);
+	struct xfs_inode	*ip = XFS_I(inode);
+	struct xfs_mount	*mp = ip->i_mount;
+	struct xfs_trans	*tp;
+	int			error = 0;
+
+	xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);
+
+	/* fs-verity failed, just cleanup */
+	if (desc == NULL)
+		goto out;
+
+	error = xfs_fsverity_write_descriptor(ip, 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(inode->i_mapping);
+	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)
+		inode->i_flags |= S_VERITY;
+
+out:
+	if (error) {
+		int	error2;
+
+		error2 = xfs_fsverity_delete_metadata(ip);
+		if (error2)
+			xfs_alert(ip->i_mount,
+"ino 0x%llx failed to clean up new fsverity metadata, err %d",
+					ip->i_ino, 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,
+	unsigned long		num_ra_pages)
+{
+	struct folio            *folio;
+	pgoff_t			offset =
+			index | (XFS_FSVERITY_REGION_START >> PAGE_SHIFT);
+
+	folio = __filemap_get_folio(inode->i_mapping, offset, FGP_ACCESSED, 0);
+	if (IS_ERR(folio) || !folio_test_uptodate(folio)) {
+		DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, offset);
+
+		if (!IS_ERR(folio))
+			folio_put(folio);
+		else if (num_ra_pages > 1)
Quoted 8 lines

fs/xfs/xfs_fsverity.c line 344

Quoted 6 lines
fs/xfs/xfs_fsverity.c new file +6 -0
+			page_cache_ra_unbounded(&ractl, num_ra_pages, 0);
+		folio = read_mapping_folio(inode->i_mapping, offset, NULL);
+		if (IS_ERR(folio))
+			return ERR_CAST(folio);
+	}
+	return folio_file_page(folio, offset);

Shouldn't this be some _BEYOND_EOF variant of generic_file_read_iter?

_BEYOND_EOF is set by xfs_iomap_read_begin() while mapping the blocks, and also this uses _unbounded version of readhead to skip EOF checks.

Quoted 124 lines
Quoted 14 lines
fs/xfs/xfs_fsverity.c new file +13 -0
+}
+
+/*
+ * Write a merkle tree block.
+ */
+static int
+xfs_fsverity_write_merkle(
+	struct inode		*inode,
+	const void		*buf,
+	u64			pos,
+	unsigned int		size)
+{
+	struct xfs_inode	*ip = XFS_I(inode);

fs/xfs/xfs_fsverity.c line 367

Quoted 10 lines
fs/xfs/xfs_fsverity.c new file +10 -0
+	loff_t			position = pos | XFS_FSVERITY_REGION_START;
+
+	if (position + size > inode->i_sb->s_maxbytes)
+		return -EFBIG;
+
+	return xfs_fsverity_write(ip, position, size, buf);
+}
+
+const ptrdiff_t info_offs = (int)offsetof(struct xfs_inode, i_verity_info) -
+			    (int)offsetof(struct xfs_inode, i_vnode);

I ... wow.

Not blaming you for writing this, just surprised that the common code makes you do that.

Quoted 94 lines
fs/xfs/xfs_fsverity.c new file +8 -0
+const struct fsverity_operations xfs_fsverity_ops = {
+	.inode_info_offs		= info_offs,
+	.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,
+	.write_merkle_tree_block	= xfs_fsverity_write_merkle,
+};
fs/xfs/xfs_fsverity.h new file +12 -0
new file mode 100644
index 0000000000..8b0d7ef456
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 Red Hat, Inc.
+ */
+#ifndef __XFS_FSVERITY_H__
+#define __XFS_FSVERITY_H__
+
+#ifdef CONFIG_FS_VERITY
+extern const struct fsverity_operations xfs_fsverity_ops;
+#endif	/* CONFIG_FS_VERITY */
+
+#endif	/* __XFS_FSVERITY_H__ */
fs/xfs/xfs_message.c +4 -0
index 19aba2c3d5..17f0f0ca7b 100644
@@ -161,6 +161,10 @@
 			.opstate	= XFS_OPSTATE_WARNED_ZONED,
 			.name		= "zoned RT device",
 		},
+		[XFS_EXPERIMENTAL_FSVERITY] = {
+			.opstate	= XFS_OPSTATE_WARNED_ZONED,
+			.name		= "fsverity",
+		},
 	};
 	ASSERT(feat >= 0 && feat < XFS_EXPERIMENTAL_MAX);
 	BUILD_BUG_ON(ARRAY_SIZE(features) != XFS_EXPERIMENTAL_MAX);
fs/xfs/xfs_message.h +1 -0
index d68e72379f..1647d32ea4 100644
@@ -96,6 +96,7 @@
 	XFS_EXPERIMENTAL_LBS,
 	XFS_EXPERIMENTAL_METADIR,
 	XFS_EXPERIMENTAL_ZONED,
+	XFS_EXPERIMENTAL_FSVERITY,
 
 	XFS_EXPERIMENTAL_MAX,
 };
fs/xfs/xfs_super.c +8 -0
index 10c6fc8d20..42a16b15a6 100644
@@ -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"
@@ -54,6 +55,7 @@
 #include <linux/fs_context.h>
 #include <linux/fs_parser.h>
 #include <linux/fsverity.h>
+#include <linux/iomap.h>
 
 static const struct super_operations xfs_super_operations;
 
@@ -1706,6 +1708,9 @@
 	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
@@ -1959,10 +1964,19 @@
 		xfs_set_resuming_quotaon(mp);
 	mp->m_qflags &= ~XFS_QFLAGS_MNTOPTS;
 
+	if (xfs_has_verity(mp))
+		xfs_warn_experimental(mp, XFS_EXPERIMENTAL_FSVERITY);
+
 	error = xfs_mountfs(mp);
 	if (error)
 		goto out_filestream_unmount;
 
Quoted 4 lines

fs/xfs/xfs_super.c line 1975

Quoted 2 lines
fs/xfs/xfs_super.c +2 -0
+#ifdef CONFIG_FS_VERITY
+	error = iomap_fsverity_init_bioset();

if (xfs_has_verity()) ?

sure, will change it

Signature · 1 line
- Andrey
#62 lore raw

Re: [PATCH v2 16/22] xfs: add fs-verity support

Quoted 10 lines · Andrey Albershteyn
Quoted 9 lines · Darrick J. Wong
Quoted 8 lines · Andrey Albershteyn

Add integration with fs-verity. XFS stores fs-verity descriptor and Merkle tree in the inode data fork at offset file offset (1 << 53).

The Merkle tree reading/writing is done through iomap interface. The data itself are 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 on BIO completion in a workqueue.

Quoted 4 lines
Quoted 4 lines
Quoted 4 lines

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).

<snip some of this stuff>

Quoted 20 lines
Quoted 11 lines
Quoted 11 lines
fs/xfs/xfs_fsverity.c new file +11 -0
+/*
+ * 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;
+
Quoted 8 lines

fs/xfs/xfs_fsverity.c line 181

Quoted 5 lines
fs/xfs/xfs_fsverity.c new file +5 -0
+	error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp);
+	if (error)
+		return error;
+
+	xfs_ilock(ip, XFS_MMAPLOCK_EXCL);

The MMAPLOCK should be taken before the transaction allocation like everything else in xfs.

Quoted 2 lines

hmm, but is it needed for transaction? I meant to only wrap xfs_truncate_page() with mmaplock

The MMAPLOCK isn't needed to run the transaction, but the normal resource acquisition order is:

i_rwsem (IOLOCK) -> mmap_invalidatelock (MMAPLOCK) -> log grant space (aka xfs_trans_reserve) -> ILOCK.

By taking the MMAPLOCK after log space has been granted, you're now risking an ABBA deadlock because another thread could hold the same MMAPLOCK and is waiting for a log grant while the current thread took the last of the log grant space and is now waiting for the MMAPLOCK.

Why are we calling xfs_truncate_page on the EOF page?

Should we hold the MMAPLOCK during the entire merkle tree construction process so that page_mkwrite is blocked?

Also, should page_mkwrite return an error if either S_VERITY or XFS_VERITY_CONSTRUCTION are set on the inode?

--D

Quoted 310 lines
Quoted 50 lines
Quoted 50 lines
fs/xfs/xfs_fsverity.c new file +50 -0
+	error = xfs_truncate_page(ip, XFS_ISIZE(ip), NULL, NULL);
+	xfs_iunlock(ip, XFS_MMAPLOCK_EXCL);
+
+	xfs_ilock(ip, XFS_ILOCK_EXCL);
+	xfs_trans_ijoin(tp, ip, 0);
+
+	/*
+	 * We removing post EOF data, no need to update i_size
+	 */
+	error = xfs_itruncate_extents(&tp, ip, XFS_DATA_FORK, XFS_ISIZE(ip));
+	if (error)
+		goto err_cancel;
+
+	error = xfs_trans_commit(tp);
+	if (error)
+		goto err_cancel;
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+
+	return error;
+
+err_cancel:
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	xfs_trans_cancel(tp);
+	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))
+		return -EINVAL;
+
+	if (inode->i_size > XFS_FSVERITY_REGION_START)
+		return -EFBIG;
+
+	if (xfs_iflags_test_and_set(ip, XFS_VERITY_CONSTRUCTION))
+		return -EBUSY;
+
+	error = xfs_qm_dqattach(ip);
Quoted 8 lines

fs/xfs/xfs_fsverity.c line 237

Quoted 6 lines
fs/xfs/xfs_fsverity.c new file +6 -0
+	if (error)
+		return error;
+
+	/*
+	 * Flush pagecache before building Merkle tree. Inode is locked and no
+	 * further writes will happen to the file except fsverity metadata

Don't we need to take the MMAPLOCK to prevent concurrent write faults?

will add it

Quoted 102 lines
Quoted 102 lines
fs/xfs/xfs_fsverity.c new file +101 -0
+	 */
+	error = filemap_write_and_wait(inode->i_mapping);
+	if (error)
+		return error;
+
+	return xfs_fsverity_delete_metadata(ip);
+}
+
+/*
+ * Complete (or fail) the process of enabling fsverity.
+ */
+static int
+xfs_fsverity_end_enable(
+	struct file		*filp,
+	const void		*desc,
+	size_t			desc_size,
+	u64			merkle_tree_size)
+{
+	struct inode		*inode = file_inode(filp);
+	struct xfs_inode	*ip = XFS_I(inode);
+	struct xfs_mount	*mp = ip->i_mount;
+	struct xfs_trans	*tp;
+	int			error = 0;
+
+	xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL);
+
+	/* fs-verity failed, just cleanup */
+	if (desc == NULL)
+		goto out;
+
+	error = xfs_fsverity_write_descriptor(ip, 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(inode->i_mapping);
+	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)
+		inode->i_flags |= S_VERITY;
+
+out:
+	if (error) {
+		int	error2;
+
+		error2 = xfs_fsverity_delete_metadata(ip);
+		if (error2)
+			xfs_alert(ip->i_mount,
+"ino 0x%llx failed to clean up new fsverity metadata, err %d",
+					ip->i_ino, 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,
+	unsigned long		num_ra_pages)
+{
+	struct folio            *folio;
+	pgoff_t			offset =
+			index | (XFS_FSVERITY_REGION_START >> PAGE_SHIFT);
+
+	folio = __filemap_get_folio(inode->i_mapping, offset, FGP_ACCESSED, 0);
+	if (IS_ERR(folio) || !folio_test_uptodate(folio)) {
+		DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, offset);
+
+		if (!IS_ERR(folio))
+			folio_put(folio);
+		else if (num_ra_pages > 1)
Quoted 8 lines

fs/xfs/xfs_fsverity.c line 344

Quoted 6 lines
fs/xfs/xfs_fsverity.c new file +6 -0
+			page_cache_ra_unbounded(&ractl, num_ra_pages, 0);
+		folio = read_mapping_folio(inode->i_mapping, offset, NULL);
+		if (IS_ERR(folio))
+			return ERR_CAST(folio);
+	}
+	return folio_file_page(folio, offset);

Shouldn't this be some _BEYOND_EOF variant of generic_file_read_iter?

_BEYOND_EOF is set by xfs_iomap_read_begin() while mapping the blocks, and also this uses _unbounded version of readhead to skip EOF checks.

Quoted 124 lines
Quoted 14 lines
fs/xfs/xfs_fsverity.c new file +13 -0
+}
+
+/*
+ * Write a merkle tree block.
+ */
+static int
+xfs_fsverity_write_merkle(
+	struct inode		*inode,
+	const void		*buf,
+	u64			pos,
+	unsigned int		size)
+{
+	struct xfs_inode	*ip = XFS_I(inode);

fs/xfs/xfs_fsverity.c line 367

Quoted 10 lines
fs/xfs/xfs_fsverity.c new file +10 -0
+	loff_t			position = pos | XFS_FSVERITY_REGION_START;
+
+	if (position + size > inode->i_sb->s_maxbytes)
+		return -EFBIG;
+
+	return xfs_fsverity_write(ip, position, size, buf);
+}
+
+const ptrdiff_t info_offs = (int)offsetof(struct xfs_inode, i_verity_info) -
+			    (int)offsetof(struct xfs_inode, i_vnode);

I ... wow.

Not blaming you for writing this, just surprised that the common code makes you do that.

Quoted 94 lines
fs/xfs/xfs_fsverity.c new file +8 -0
+const struct fsverity_operations xfs_fsverity_ops = {
+	.inode_info_offs		= info_offs,
+	.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,
+	.write_merkle_tree_block	= xfs_fsverity_write_merkle,
+};
fs/xfs/xfs_fsverity.h new file +12 -0
new file mode 100644
index 0000000000..8b0d7ef456
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 Red Hat, Inc.
+ */
+#ifndef __XFS_FSVERITY_H__
+#define __XFS_FSVERITY_H__
+
+#ifdef CONFIG_FS_VERITY
+extern const struct fsverity_operations xfs_fsverity_ops;
+#endif	/* CONFIG_FS_VERITY */
+
+#endif	/* __XFS_FSVERITY_H__ */
fs/xfs/xfs_message.c +4 -0
index 19aba2c3d5..17f0f0ca7b 100644
@@ -161,6 +161,10 @@
 			.opstate	= XFS_OPSTATE_WARNED_ZONED,
 			.name		= "zoned RT device",
 		},
+		[XFS_EXPERIMENTAL_FSVERITY] = {
+			.opstate	= XFS_OPSTATE_WARNED_ZONED,
+			.name		= "fsverity",
+		},
 	};
 	ASSERT(feat >= 0 && feat < XFS_EXPERIMENTAL_MAX);
 	BUILD_BUG_ON(ARRAY_SIZE(features) != XFS_EXPERIMENTAL_MAX);
fs/xfs/xfs_message.h +1 -0
index d68e72379f..1647d32ea4 100644
@@ -96,6 +96,7 @@
 	XFS_EXPERIMENTAL_LBS,
 	XFS_EXPERIMENTAL_METADIR,
 	XFS_EXPERIMENTAL_ZONED,
+	XFS_EXPERIMENTAL_FSVERITY,
 
 	XFS_EXPERIMENTAL_MAX,
 };
fs/xfs/xfs_super.c +8 -0
index 10c6fc8d20..42a16b15a6 100644
@@ -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"
@@ -54,6 +55,7 @@
 #include <linux/fs_context.h>
 #include <linux/fs_parser.h>
 #include <linux/fsverity.h>
+#include <linux/iomap.h>
 
 static const struct super_operations xfs_super_operations;
 
@@ -1706,6 +1708,9 @@
 	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
@@ -1959,10 +1964,19 @@
 		xfs_set_resuming_quotaon(mp);
 	mp->m_qflags &= ~XFS_QFLAGS_MNTOPTS;
 
+	if (xfs_has_verity(mp))
+		xfs_warn_experimental(mp, XFS_EXPERIMENTAL_FSVERITY);
+
 	error = xfs_mountfs(mp);
 	if (error)
 		goto out_filestream_unmount;
 
Quoted 4 lines

fs/xfs/xfs_super.c line 1975

Quoted 2 lines
fs/xfs/xfs_super.c +2 -0
+#ifdef CONFIG_FS_VERITY
+	error = iomap_fsverity_init_bioset();

if (xfs_has_verity()) ?

sure, will change it

Signature · 1 line
- Andrey
#63 lore raw

Re: [PATCH v2 16/22] xfs: add fs-verity support

In reply to Darrick J. Wong
Quoted 5 lines
Quoted 5 lines
fs/xfs/xfs_fsverity.c new file +5 -0
+	desc_pos = round_down(desc_size_pos - desc_size, blocksize);
+	error = xfs_fsverity_read(inode, buf, desc_size, desc_pos);
+	if (error)
+		return error;
+
Quoted 8 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+	return desc_size;
+}

You might want to wrap the integrity checks through XFS_IS_CORRUPT so that we get some logging on corrupt fsverity data. Also, if descriptor corruption doesn't prevent iget from completing, then we ought to define a new health state for the xfs_inode so that it can report those kinds of failures via bulkstat.

yeah, iget will complete as it doesn't trigger fsverity to read descriptor. I will add a new health state. Thanks!

Signature · 1 line
- Andrey
#64 lore raw

PATCH v2 17/22 xfs: add fs-verity ioctls

In reply to Andrey Albershteyn

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

[djwong: remove unnecessary casting]
---
 fs/xfs/xfs_ioctl.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+), 0 deletions(-)
fs/xfs/xfs_ioctl.c +16 -0
index 59eaad7743..d343af6aa0 100644
@@ -44,6 +44,7 @@
 
 #include <linux/mount.h>
 #include <linux/fileattr.h>
+#include <linux/fsverity.h>
 
 /* Return 0 on success or positive error */
 int
@@ -1419,6 +1420,21 @@
 	case XFS_IOC_COMMIT_RANGE:
 		return xfs_ioc_commit_range(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;
 	}
Signature · 1 line
- Andrey
#65 lore raw

PATCH v2 18/22 xfs: advertise fs-verity being available on filesystem

In reply to Andrey Albershteyn

Advertise that this filesystem supports fsverity.

---
 fs/xfs/libxfs/xfs_fs.h | 1 +
 fs/xfs/libxfs/xfs_sb.c | 2 ++
 2 files changed, 3 insertions(+), 0 deletions(-)
fs/xfs/libxfs/xfs_fs.h +1 -0
index b73458a7c2..3db9beb579 100644
@@ -250,6 +250,7 @@
 #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.
fs/xfs/libxfs/xfs_sb.c +2 -0
index 744bd8480b..fe400bffa5 100644
@@ -1587,6 +1587,8 @@
 		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);
 
Signature · 1 line
- Andrey
#66 lore raw

PATCH v2 19/22 xfs: check and repair the verity inode flag state

In reply to Andrey Albershteyn

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.

---
 fs/xfs/scrub/attr.c         |  7 ++++++
 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 +++++++++++++++++++++++++++++++
 5 files changed, 105 insertions(+), 0 deletions(-)
fs/xfs/scrub/attr.c +7 -0
index 708334f9b2..b1448832ae 100644
@@ -646,6 +646,13 @@
 	if (!xfs_inode_hasattr(sc->ip))
 		return -ENOENT;
 
+	/*
+	 * If this is a verity file that won't activate, we cannot check the
+	 * merkle tree geometry.
+	 */
+	if (xchk_inode_verity_broken(sc->ip))
+		xchk_set_incomplete(sc);
+
 	/* Allocate memory for xattr checking. */
 	error = xchk_setup_xattr_buf(sc, 0);
 	if (error == -ENOMEM)
fs/xfs/scrub/common.c +53 -0

62 lines not on this page · raw message

fs/xfs/scrub/common.h +2 -0
index ddbc065c79..36d6a33337 100644
@@ -289,6 +289,8 @@
 		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);
fs/xfs/scrub/inode.c +7 -0
index bb3f475b63..1e7cfef00a 100644
@@ -36,6 +36,10 @@
 
 	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 @@
 	if (S_ISREG(VFS_I(sc->ip)->i_mode))
 		xchk_inode_check_reflink_iflag(sc, sc->ip->i_ino);
 
+	if (xchk_inode_verity_broken(sc->ip))
+		xchk_ino_set_corrupt(sc, sc->sm->sm_ino);
+
 	xchk_inode_check_unlinked(sc);
 
 	xchk_inode_xref(sc, sc->ip->i_ino, &di);
fs/xfs/scrub/inode_repair.c +36 -0
index 4f7040c9dd..846a47286e 100644
@@ -573,6 +573,8 @@
 		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;
@@ -1613,6 +1615,10 @@
 	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;
@@ -2032,6 +2038,27 @@
 	return 0;
 }
 
+/*
+ * If this file is a fsverity file, xchk_prepare_iscrub or xrep_dinode_core
+ * should have activated it.  If it's still not active, then there's something
+ * wrong with the verity descriptor and we should turn it off.
+ */
+STATIC int
+xrep_inode_verity(
+	struct xfs_scrub	*sc)
+{
+	struct inode		*inode = VFS_I(sc->ip);
+
+	if (xchk_inode_verity_broken(sc->ip)) {
+		sc->ip->i_diflags2 &= ~XFS_DIFLAG2_VERITY;
+		inode->i_flags &= ~S_VERITY;
+
+		xfs_trans_log_inode(sc->tp, sc->ip, XFS_ILOG_CORE);
+	}
+
+	return 0;
+}
+
 /* Repair an inode's fields. */
 int
 xrep_inode(
@@ -2081,6 +2108,15 @@
 			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)
Signature · 1 line
- Andrey
#67 lore raw

PATCH v2 20/22 xfs: report verity failures through the health system

In reply to Andrey Albershteyn

Record verity failures and report them through the health system.

---
 fs/xfs/libxfs/xfs_fs.h     |  1 +
 fs/xfs/libxfs/xfs_health.h |  4 +++-
 fs/xfs/xfs_fsverity.c      | 11 +++++++++++
 fs/xfs/xfs_health.c        |  1 +
 4 files changed, 16 insertions(+), 1 deletion(-)
fs/xfs/libxfs/xfs_fs.h +1 -0
index 3db9beb579..b82fef9a1f 100644
@@ -422,6 +422,7 @@
 #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_DATA	(1 << 9)  /* file data */
 
 /*
  * Project quota id helpers (previously projid was 16bit only
fs/xfs/libxfs/xfs_health.h +3 -1
index b31000f719..fa91916ad0 100644
@@ -104,6 +104,7 @@
 /* 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_DATA	(1 << 14)  /* file data */
 
 /* Primary evidence of health problems in a given group. */
 #define XFS_SICK_FS_PRIMARY	(XFS_SICK_FS_COUNTERS | \
@@ -140,7 +141,8 @@
 				 XFS_SICK_INO_XATTR | \
 				 XFS_SICK_INO_SYMLINK | \
 				 XFS_SICK_INO_PARENT | \
-				 XFS_SICK_INO_DIRTREE)
+				 XFS_SICK_INO_DIRTREE | \
+				 XFS_SICK_INO_DATA)
 
 #define XFS_SICK_INO_ZAPPED	(XFS_SICK_INO_BMBTD_ZAPPED | \
 				 XFS_SICK_INO_BMBTA_ZAPPED | \
fs/xfs/xfs_fsverity.c +11 -0
index 691dc60778..f53a404578 100644
@@ -18,6 +18,7 @@
 #include "xfs_quota.h"
 #include "xfs_fsverity.h"
 #include "xfs_iomap.h"
+#include "xfs_health.h"
 #include <linux/fsverity.h>
 #include <linux/pagemap.h>
 
@@ -363,6 +364,15 @@
 	return xfs_fsverity_write(ip, position, size, buf);
 }
 
+static void
+xfs_fsverity_file_corrupt(
+	struct inode		*inode,
+	loff_t			pos,
+	size_t			len)
+{
+	xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_DATA);
+}
+
 const ptrdiff_t info_offs = (int)offsetof(struct xfs_inode, i_verity_info) -
 			    (int)offsetof(struct xfs_inode, i_vnode);
 
@@ -373,4 +383,5 @@
 	.get_verity_descriptor		= xfs_fsverity_get_descriptor,
 	.read_merkle_tree_page		= xfs_fsverity_read_merkle,
 	.write_merkle_tree_block	= xfs_fsverity_write_merkle,
+	.file_corrupt			= xfs_fsverity_file_corrupt,
 };
fs/xfs/xfs_health.c +1 -0
index 3c1557fb1c..b851651c02 100644
@@ -487,6 +487,7 @@
 	{ 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_DATA,	XFS_BS_SICK_DATA },
 };
 
 /* Fill out bulkstat health info. */
Signature · 1 line
- Andrey
#68 lore raw

PATCH v2 21/22 xfs: add fsverity traces

In reply to Andrey Albershteyn

Even though fsverity has traces, debugging issues with varying block sizes could be a bit less transparent without read/write traces.

---
 fs/xfs/xfs_fsverity.c |  8 ++++++++
 fs/xfs/xfs_trace.h    | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 54 insertions(+), 0 deletions(-)
fs/xfs/xfs_fsverity.c +8 -0
index f53a404578..06eac2561b 100644
@@ -102,6 +102,8 @@
 	uint32_t		blocksize = i_blocksize(VFS_I(ip));
 	xfs_fileoff_t		last_block;
 
+	trace_xfs_fsverity_get_descriptor(ip);
+
 	ASSERT(inode->i_flags & S_VERITY);
 	error = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, &rec, &is_empty);
 	if (error)
@@ -330,6 +332,8 @@
 	pgoff_t			offset =
 			index | (XFS_FSVERITY_REGION_START >> PAGE_SHIFT);
 
+	trace_xfs_fsverity_read_merkle(XFS_I(inode), offset, PAGE_SIZE);
+
 	folio = __filemap_get_folio(inode->i_mapping, offset, FGP_ACCESSED, 0);
 	if (IS_ERR(folio) || !folio_test_uptodate(folio)) {
 		DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, offset);
@@ -358,6 +362,8 @@
 	struct xfs_inode	*ip = XFS_I(inode);
 	loff_t			position = pos | XFS_FSVERITY_REGION_START;
 
+	trace_xfs_fsverity_write_merkle(XFS_I(inode), pos, size);
+
 	if (position + size > inode->i_sb->s_maxbytes)
 		return -EFBIG;
 
@@ -370,6 +376,8 @@
 	loff_t			pos,
 	size_t			len)
 {
+	trace_xfs_fsverity_file_corrupt(XFS_I(inode), pos, len);
+
 	xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_DATA);
 }
 
fs/xfs/xfs_trace.h +46 -0

52 lines not on this page · raw message

Signature · 1 line
- Andrey
Reviewed-by on v2 21/22
#69 lore raw

Re: [PATCH v2 21/22] xfs: add fsverity traces

Quoted 79 lines · Andrey Albershteyn

Even though fsverity has traces, debugging issues with varying block sizes could be a bit less transparent without read/write traces.

---
 fs/xfs/xfs_fsverity.c |  8 ++++++++
 fs/xfs/xfs_trace.h    | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 54 insertions(+), 0 deletions(-)
fs/xfs/xfs_fsverity.c +8 -0
index f53a404578..06eac2561b 100644
@@ -102,6 +102,8 @@
 	uint32_t		blocksize = i_blocksize(VFS_I(ip));
 	xfs_fileoff_t		last_block;
 
+	trace_xfs_fsverity_get_descriptor(ip);
+
 	ASSERT(inode->i_flags & S_VERITY);
 	error = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, &rec, &is_empty);
 	if (error)
@@ -330,6 +332,8 @@
 	pgoff_t			offset =
 			index | (XFS_FSVERITY_REGION_START >> PAGE_SHIFT);
 
+	trace_xfs_fsverity_read_merkle(XFS_I(inode), offset, PAGE_SIZE);
+
 	folio = __filemap_get_folio(inode->i_mapping, offset, FGP_ACCESSED, 0);
 	if (IS_ERR(folio) || !folio_test_uptodate(folio)) {
 		DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, offset);
@@ -358,6 +362,8 @@
 	struct xfs_inode	*ip = XFS_I(inode);
 	loff_t			position = pos | XFS_FSVERITY_REGION_START;
 
+	trace_xfs_fsverity_write_merkle(XFS_I(inode), pos, size);
+
 	if (position + size > inode->i_sb->s_maxbytes)
 		return -EFBIG;
 
@@ -370,6 +376,8 @@
 	loff_t			pos,
 	size_t			len)
 {
+	trace_xfs_fsverity_file_corrupt(XFS_I(inode), pos, len);
+
 	xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_DATA);
 }
 
fs/xfs/xfs_trace.h +22 -0
index f70afbf3cb..1ce4e10b6b 100644
@@ -5906,6 +5906,52 @@
 DEFINE_FREEBLOCKS_RESV_EVENT(xfs_freecounter_reserved);
 DEFINE_FREEBLOCKS_RESV_EVENT(xfs_freecounter_enospc);
 
+TRACE_EVENT(xfs_fsverity_get_descriptor,
+	TP_PROTO(struct xfs_inode *ip),
+	TP_ARGS(ip),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(xfs_ino_t, ino)
+	),
+	TP_fast_assign(
+		__entry->dev = VFS_I(ip)->i_sb->s_dev;
+		__entry->ino = ip->i_ino;
+	),
+	TP_printk("dev %d:%d ino 0x%llx",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino)
+);
+
+DECLARE_EVENT_CLASS(xfs_fsverity_class,
+	TP_PROTO(struct xfs_inode *ip, u64 pos, unsigned int length),
+	TP_ARGS(ip, pos, length),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(xfs_ino_t, ino)

fs/xfs/xfs_trace.h line 5940

Quoted 10 lines
fs/xfs/xfs_trace.h +10 -0
+		__field(u64, pos)
+		__field(unsigned int, length)
+	),
+	TP_fast_assign(
+		__entry->dev = VFS_I(ip)->i_sb->s_dev;
+		__entry->ino = ip->i_ino;
+		__entry->pos = pos;
+		__entry->length = length;
+	),
+	TP_printk("dev %d:%d ino 0x%llx pos %llx length %x",

pos and length should have '0x' before them since they're presented in hexadecimal.

Otherwise this looks ok so

--D

Quoted 22 lines
fs/xfs/xfs_trace.h +14 -0
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino,
+		  __entry->pos,
+		  __entry->length)
+)
+
+#define DEFINE_FSVERITY_EVENT(name) \
+DEFINE_EVENT(xfs_fsverity_class, name, \
+	TP_PROTO(struct xfs_inode *ip, u64 pos, unsigned int length), \
+	TP_ARGS(ip, pos, length))
+DEFINE_FSVERITY_EVENT(xfs_fsverity_read_merkle);
+DEFINE_FSVERITY_EVENT(xfs_fsverity_write_merkle);
+DEFINE_FSVERITY_EVENT(xfs_fsverity_file_corrupt);
+
 #endif /* _TRACE_XFS_H */
 
 #undef TRACE_INCLUDE_PATH
Signature · 2 lines
-- 
- Andrey
#70 lore raw

PATCH v2 22/22 xfs: enable ro-compat fs-verity flag

In reply to Andrey Albershteyn

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

---
 fs/xfs/libxfs/xfs_format.h | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
fs/xfs/libxfs/xfs_format.h +3 -2
index d67b404964..f5e43909f0 100644
@@ -378,8 +378,9 @@
 #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(
Signature · 1 line
- Andrey
#71 lore raw
In reply to Andrey Albershteyn
Quoted 2 lines · Andrey Albershteyn

The tree is read by iomap into page cache at offset 1 << 53. This is far enough to handle any supported file size.

What happens on 32-bit systems? (I presume you mean "offset" as "index", so this is 1 << 65 bytes on machines with a 4KiB page size)

#72 lore raw
Quoted 7 lines · Matthew Wilcox
Quoted 2 lines · Andrey Albershteyn

The tree is read by iomap into page cache at offset 1 << 53. This is far enough to handle any supported file size.

What happens on 32-bit systems? (I presume you mean "offset" as "index", so this is 1 << 65 bytes on machines with a 4KiB page size)

it's in bytes, yeah I missed 32-bit systems, I think I will try to convert this offset to something lower on 32-bit in iomap, as Darrick suggested.

Signature · 1 line
- Andrey
#73 lore raw
Quoted 11 lines · Andrey Albershteyn
Quoted 7 lines · Matthew Wilcox
Quoted 2 lines · Andrey Albershteyn

The tree is read by iomap into page cache at offset 1 << 53. This is far enough to handle any supported file size.

What happens on 32-bit systems? (I presume you mean "offset" as "index", so this is 1 << 65 bytes on machines with a 4KiB page size)

it's in bytes, yeah I missed 32-bit systems, I think I will try to convert this offset to something lower on 32-bit in iomap, as Darrick suggested.

Hm, we use all 32 bits of folio->index on 32-bit plaftorms. That's MAX_LFS_FILESIZE. Are you proposing reducing that?

There are some other (performance) penalties to using 1<<53 as the lowest index for metadata on 64-bit. The radix tree is going to go quite high; we use 6 bits at each level, so if you have a folio at 0 and a folio at 1<<53, you'll have a tree of height 9 and use 17 nodes.

That's going to be a lot of extra cache misses when walking the XArray to find any given folio. Allowing the filesystem to decide where the metadata starts for any given file really is an important optimisation. Even if it starts at index 1<<29, you'll almost halve the number of nodes needed.

Adding this ability to support RW merkel trees is certainly coming at a cost. Is it worth it? I haven't seen a user need for that articulated, but I haven't been paying close attention.

#74 lore raw
Quoted 21 lines · Matthew Wilcox
Quoted 11 lines · Andrey Albershteyn
Quoted 7 lines · Matthew Wilcox
Quoted 2 lines · Andrey Albershteyn

The tree is read by iomap into page cache at offset 1 << 53. This is far enough to handle any supported file size.

What happens on 32-bit systems? (I presume you mean "offset" as "index", so this is 1 << 65 bytes on machines with a 4KiB page size)

it's in bytes, yeah I missed 32-bit systems, I think I will try to convert this offset to something lower on 32-bit in iomap, as Darrick suggested.

Hm, we use all 32 bits of folio->index on 32-bit plaftorms. That's MAX_LFS_FILESIZE. Are you proposing reducing that?

There are some other (performance) penalties to using 1<<53 as the lowest index for metadata on 64-bit. The radix tree is going to go quite high; we use 6 bits at each level, so if you have a folio at 0 and a folio at 1<<53, you'll have a tree of height 9 and use 17 nodes.

Quoted 5 lines

That's going to be a lot of extra cache misses when walking the XArray to find any given folio. Allowing the filesystem to decide where the metadata starts for any given file really is an important optimisation. Even if it starts at index 1<<29, you'll almost halve the number of nodes needed.

1<<53 is only the location of the fsverity metadata in the ondisk mapping. For the incore mapping, in theory we could load the fsverity anywhere in the post-EOF part of the pagecache to save some bits.

roundup(i_size_read(), 1<<folio_max_order)) would work, right?

Quoted 3 lines

Adding this ability to support RW merkel trees is certainly coming at a cost. Is it worth it? I haven't seen a user need for that articulated, but I haven't been paying close attention.

I think the pagecache writes of fsverity metadata are only performed once, while enabling fsverity for a file.

--D

#75 lore raw
Quoted 22 lines · Darrick J. Wong
Quoted 21 lines · Matthew Wilcox
Quoted 11 lines · Andrey Albershteyn
Quoted 7 lines · Matthew Wilcox
Quoted 2 lines · Andrey Albershteyn

The tree is read by iomap into page cache at offset 1 << 53. This is far enough to handle any supported file size.

What happens on 32-bit systems? (I presume you mean "offset" as "index", so this is 1 << 65 bytes on machines with a 4KiB page size)

it's in bytes, yeah I missed 32-bit systems, I think I will try to convert this offset to something lower on 32-bit in iomap, as Darrick suggested.

Hm, we use all 32 bits of folio->index on 32-bit plaftorms. That's MAX_LFS_FILESIZE. Are you proposing reducing that?

There are some other (performance) penalties to using 1<<53 as the lowest index for metadata on 64-bit. The radix tree is going to go quite high; we use 6 bits at each level, so if you have a folio at 0 and a folio at 1<<53, you'll have a tree of height 9 and use 17 nodes.

Quoted 5 lines
Quoted 5 lines

That's going to be a lot of extra cache misses when walking the XArray to find any given folio. Allowing the filesystem to decide where the metadata starts for any given file really is an important optimisation. Even if it starts at index 1<<29, you'll almost halve the number of nodes needed.

Thanks for this overview!

Quoted 6 lines

1<<53 is only the location of the fsverity metadata in the ondisk mapping. For the incore mapping, in theory we could load the fsverity anywhere in the post-EOF part of the pagecache to save some bits.

roundup(i_size_read(), 1<<folio_max_order)) would work, right?

Then, there's probably no benefits to have ondisk mapping differ, no?

Quoted 7 lines
Quoted 4 lines

Adding this ability to support RW merkel trees is certainly coming at a cost. Is it worth it? I haven't seen a user need for that articulated, but I haven't been paying close attention.

I think the pagecache writes of fsverity metadata are only performed once, while enabling fsverity for a file.

yes

Signature · 1 line
- Andrey
#76 lore raw
Quoted 32 lines · Andrey Albershteyn
Quoted 22 lines · Darrick J. Wong
Quoted 21 lines · Matthew Wilcox
Quoted 11 lines · Andrey Albershteyn
Quoted 7 lines · Matthew Wilcox
Quoted 2 lines · Andrey Albershteyn

The tree is read by iomap into page cache at offset 1 << 53. This is far enough to handle any supported file size.

What happens on 32-bit systems? (I presume you mean "offset" as "index", so this is 1 << 65 bytes on machines with a 4KiB page size)

it's in bytes, yeah I missed 32-bit systems, I think I will try to convert this offset to something lower on 32-bit in iomap, as Darrick suggested.

Hm, we use all 32 bits of folio->index on 32-bit plaftorms. That's MAX_LFS_FILESIZE. Are you proposing reducing that?

There are some other (performance) penalties to using 1<<53 as the lowest index for metadata on 64-bit. The radix tree is going to go quite high; we use 6 bits at each level, so if you have a folio at 0 and a folio at 1<<53, you'll have a tree of height 9 and use 17 nodes.

Quoted 5 lines
Quoted 5 lines

That's going to be a lot of extra cache misses when walking the XArray to find any given folio. Allowing the filesystem to decide where the metadata starts for any given file really is an important optimisation. Even if it starts at index 1<<29, you'll almost halve the number of nodes needed.

Thanks for this overview!

Quoted 1 line
Quoted 8 lines
Quoted 5 lines

1<<53 is only the location of the fsverity metadata in the ondisk mapping. For the incore mapping, in theory we could load the fsverity anywhere in the post-EOF part of the pagecache to save some bits.

roundup(i_size_read(), 1<<folio_max_order)) would work, right?

Then, there's probably no benefits to have ondisk mapping differ, no?

oh, the fixed ondisk offset will help to not break if filesystem would be mounted by machine with different page size.

Signature · 1 line
- Andrey
#77 lore raw
Quoted 37 lines · Andrey Albershteyn
Quoted 36 lines · Andrey Albershteyn
Quoted 22 lines · Darrick J. Wong
Quoted 21 lines · Matthew Wilcox
Quoted 11 lines · Andrey Albershteyn
Quoted 7 lines · Matthew Wilcox
Quoted 2 lines · Andrey Albershteyn

The tree is read by iomap into page cache at offset 1 << 53. This is far enough to handle any supported file size.

What happens on 32-bit systems? (I presume you mean "offset" as "index", so this is 1 << 65 bytes on machines with a 4KiB page size)

it's in bytes, yeah I missed 32-bit systems, I think I will try to convert this offset to something lower on 32-bit in iomap, as Darrick suggested.

Hm, we use all 32 bits of folio->index on 32-bit plaftorms. That's MAX_LFS_FILESIZE. Are you proposing reducing that?

There are some other (performance) penalties to using 1<<53 as the lowest index for metadata on 64-bit. The radix tree is going to go quite high; we use 6 bits at each level, so if you have a folio at 0 and a folio at 1<<53, you'll have a tree of height 9 and use 17 nodes.

Quoted 5 lines
Quoted 5 lines

That's going to be a lot of extra cache misses when walking the XArray to find any given folio. Allowing the filesystem to decide where the metadata starts for any given file really is an important optimisation. Even if it starts at index 1<<29, you'll almost halve the number of nodes needed.

Thanks for this overview!

Quoted 5 lines

1<<53 is only the location of the fsverity metadata in the ondisk mapping. For the incore mapping, in theory we could load the fsverity anywhere in the post-EOF part of the pagecache to save some bits.

Quoted 7 lines
Quoted 4 lines
Quoted 1 line

roundup(i_size_read(), 1<<folio_max_order)) would work, right?

Then, there's probably no benefits to have ondisk mapping differ, no?

oh, the fixed ondisk offset will help to not break if filesystem would be mounted by machine with different page size.

Yeah. The i(ncore)ext(ent) tree won't have excessive nodes if you pick a high file offset, so 1<<53 is ok for the ondisk file offset.

--D

Quoted 3 lines
Signature · 1 line
- Andrey
#78 lore raw

fsverity metadata offset, was: Re: [PATCH v2 0/23] fs-verity support for XFS with post EOF merkle tree

In reply to Andrey Albershteyn

While looking at fsverity I'd like to understand the choise of offset in ext4 and f2fs, and wonder about an issue.

Both ext4 and f2fs round up the inode size to the next 64k boundary and place the metadata there. Both use the 65536 magic number for that instead of a well documented constant unfortunately.

I assume this was picked to align up to the largest reasonable page size? Unfortunately for that:

a) not all architectures are reasonable. As Darrick pointed out hexagon seems to support page size up to 1MiB. While I don't know if they exist in real life, powerpc supports up to 256kiB pages, and I know they are used for real in various embedded settings
b) with large folio support in the page cache, the folios used to map files can be much larger than the base page size, with all the same issues as a larger page size

So assuming that fsverity is trying to avoid the issue of a page/folio that covers both data and fsverity metadata, how does it copy with that? Do we need to disable fsverity on > 64k page size and disable large folios on fsverity files? The latter would mean writing back all cached data first as well.

And going forward, should we have a v2 format that fixes this? For that
we'd still need a maximum folio size of course. And of course I'd like
to get all these things right from the start in XFS, while still being as similar as possible to ext4/f2fs.

Quoted 37 lines · Andrey Albershteyn
Quoted 36 lines · Andrey Albershteyn
Quoted 22 lines · Darrick J. Wong
Quoted 21 lines · Matthew Wilcox
Quoted 11 lines · Andrey Albershteyn
Quoted 7 lines · Matthew Wilcox
Quoted 2 lines · Andrey Albershteyn

The tree is read by iomap into page cache at offset 1 << 53. This is far enough to handle any supported file size.

What happens on 32-bit systems? (I presume you mean "offset" as "index", so this is 1 << 65 bytes on machines with a 4KiB page size)

it's in bytes, yeah I missed 32-bit systems, I think I will try to convert this offset to something lower on 32-bit in iomap, as Darrick suggested.

Hm, we use all 32 bits of folio->index on 32-bit plaftorms. That's MAX_LFS_FILESIZE. Are you proposing reducing that?

There are some other (performance) penalties to using 1<<53 as the lowest index for metadata on 64-bit. The radix tree is going to go quite high; we use 6 bits at each level, so if you have a folio at 0 and a folio at 1<<53, you'll have a tree of height 9 and use 17 nodes.

Quoted 5 lines
Quoted 5 lines

That's going to be a lot of extra cache misses when walking the XArray to find any given folio. Allowing the filesystem to decide where the metadata starts for any given file really is an important optimisation. Even if it starts at index 1<<29, you'll almost halve the number of nodes needed.

Thanks for this overview!

Quoted 5 lines

1<<53 is only the location of the fsverity metadata in the ondisk mapping. For the incore mapping, in theory we could load the fsverity anywhere in the post-EOF part of the pagecache to save some bits.

Quoted 10 lines
Quoted 4 lines
Quoted 1 line

roundup(i_size_read(), 1<<folio_max_order)) would work, right?

Then, there's probably no benefits to have ondisk mapping differ, no?

oh, the fixed ondisk offset will help to not break if filesystem would be mounted by machine with different page size.

Signature · 1 line
- Andrey

---end quoted text---

maintainer
#79 lore raw

Re: fsverity metadata offset, was: Re: [PATCH v2 0/23] fs-verity support for XFS with post EOF merkle tree

Quoted 24 lines · Christoph Hellwig

While looking at fsverity I'd like to understand the choise of offset in ext4 and f2fs, and wonder about an issue.

Both ext4 and f2fs round up the inode size to the next 64k boundary and place the metadata there. Both use the 65536 magic number for that instead of a well documented constant unfortunately.

I assume this was picked to align up to the largest reasonable page size? Unfortunately for that:

a) not all architectures are reasonable. As Darrick pointed out hexagon seems to support page size up to 1MiB. While I don't know if they exist in real life, powerpc supports up to 256kiB pages, and I know they are used for real in various embedded settings
b) with large folio support in the page cache, the folios used to map files can be much larger than the base page size, with all the same issues as a larger page size

So assuming that fsverity is trying to avoid the issue of a page/folio that covers both data and fsverity metadata, how does it copy with that? Do we need to disable fsverity on > 64k page size and disable large folios on fsverity files? The latter would mean writing back all cached data first as well.

Quoted 4 lines

And going forward, should we have a v2 format that fixes this? For that
we'd still need a maximum folio size of course. And of course I'd like
to get all these things right from the start in XFS, while still being as similar as possible to ext4/f2fs.

Yes, if I recall correctly it was intended to be the "largest reasonable page size". It looks like PAGE_SIZE > 65536 can't work as-is, so indeed we should disable fsverity support in that configuration.

I don't think large folios are quite as problematic. ext4_read_merkle_tree_page() and f2fs_read_merkle_tree_page() read a folio and return the appropriate page in it, and fs/verity/verify.c operates on the page. If it's a page in the folio that spans EOF, I think everything will actually still work, except userspace will be able to see Merkle tree data after a 64K boundary past EOF if the file is mmapped using huge pages.

The mmap issue isn't great, but I'm not sure how much it matters, especially when the zeroes do still go up to a 64K boundary.

If we do need to fix this, there are a couple things we could consider doing without changing the on-disk format in ext4 or f2fs: putting the data in the page cache at a different offset than it exists on-disk, or using "small" pages for EOF specifically.

But yes, XFS should choose a larger alignment than 64K.

- Eric

#80 lore raw

Re: fsverity metadata offset, was: Re: [PATCH v2 0/23] fs-verity support for XFS with post EOF merkle tree

Quoted 8 lines · Eric Biggers
Quoted 7 lines · Christoph Hellwig

While looking at fsverity I'd like to understand the choise of offset in ext4 and f2fs, and wonder about an issue.

Both ext4 and f2fs round up the inode size to the next 64k boundary and place the metadata there. Both use the 65536 magic number for that instead of a well documented constant unfortunately.

Quoted 7 lines
Quoted 7 lines

I assume this was picked to align up to the largest reasonable page size? Unfortunately for that:

a) not all architectures are reasonable. As Darrick pointed out hexagon seems to support page size up to 1MiB. While I don't know if they exist in real life, powerpc supports up to 256kiB pages, and I know they are used for real in various embedded settings

They *did* way back in the day, I worked with some seekrit PPC440s early in my career. I don't know that any of them still exist, but the code is still there...

Quoted 19 lines
Quoted 10 lines

b) with large folio support in the page cache, the folios used to map files can be much larger than the base page size, with all the same issues as a larger page size

So assuming that fsverity is trying to avoid the issue of a page/folio that covers both data and fsverity metadata, how does it copy with that? Do we need to disable fsverity on > 64k page size and disable large folios on fsverity files? The latter would mean writing back all cached data first as well.

Quoted 4 lines

And going forward, should we have a v2 format that fixes this? For that
we'd still need a maximum folio size of course. And of course I'd like
to get all these things right from the start in XFS, while still being as similar as possible to ext4/f2fs.

Yes, if I recall correctly it was intended to be the "largest reasonable page size". It looks like PAGE_SIZE > 65536 can't work as-is, so indeed we should disable fsverity support in that configuration.

Quoted 7 lines

I don't think large folios are quite as problematic. ext4_read_merkle_tree_page() and f2fs_read_merkle_tree_page() read a folio and return the appropriate page in it, and fs/verity/verify.c operates on the page. If it's a page in the folio that spans EOF, I think everything will actually still work, except userspace will be able to see Merkle tree data after a 64K boundary past EOF if the file is mmapped using huge pages.

We don't allow mmapping file data beyond the EOF basepage, even if the underlying folio is a large folio. See generic/749, though recently Kiryl Shutsemau tried to remove that restriction[1], until dchinner and willy told him no.

Quoted 2 lines

The mmap issue isn't great, but I'm not sure how much it matters, especially when the zeroes do still go up to a 64K boundary.

I'm concerned that post-eof zeroing of a 256k folio could accidentally obliterate merkle tree content that was somehow previously loaded. Though afaict from the existing codebases, none of them actually make that mistake.

Quoted 4 lines

If we do need to fix this, there are a couple things we could consider doing without changing the on-disk format in ext4 or f2fs: putting the data in the page cache at a different offset than it exists on-disk, or using "small" pages for EOF specifically.

I'd leave the ondisk offset as-is, but change the pagecache offset to roundup(i_size_read(), mapping_max_folio_size_supported()) just to keep file data and fsverity metadata completely separate.

Quoted 1 line

But yes, XFS should choose a larger alignment than 64K.

The roundup() formula above is what I'd choose for the pagecache offset for xfs. The ondisk offset of 1<<53 is ok with me.

--D

[1] https://lore.kernel.org/linux-fsdevel/20251014175214.GW6188@frogsfrogsfrogs/

#81 lore raw

Re: fsverity metadata offset, was: Re: [PATCH v2 0/23] fs-verity support for XFS with post EOF merkle tree

Quoted 8 lines · Darrick J. Wong
Quoted 4 lines
Quoted 4 lines

a) not all architectures are reasonable. As Darrick pointed out hexagon seems to support page size up to 1MiB. While I don't know if they exist in real life, powerpc supports up to 256kiB pages, and I know they are used for real in various embedded settings

They *did* way back in the day, I worked with some seekrit PPC440s early in my career. I don't know that any of them still exist, but the code is still there...

Sorry, I meant I don't really know how real the hexagon large page sizes are. I know about the ppcs one personally, too.

Quoted 8 lines
Quoted 4 lines

If we do need to fix this, there are a couple things we could consider doing without changing the on-disk format in ext4 or f2fs: putting the data in the page cache at a different offset than it exists on-disk, or using "small" pages for EOF specifically.

I'd leave the ondisk offset as-is, but change the pagecache offset to roundup(i_size_read(), mapping_max_folio_size_supported()) just to keep file data and fsverity metadata completely separate.

Can we find a way to do that in common code and make ext4 and f2fs do the same?

#82 lore raw

Re: fsverity metadata offset, was: Re: [PATCH v2 0/23] fs-verity support for XFS with post EOF merkle tree

Quoted 18 lines · Christoph Hellwig
Quoted 8 lines · Darrick J. Wong
Quoted 4 lines
Quoted 4 lines

a) not all architectures are reasonable. As Darrick pointed out hexagon seems to support page size up to 1MiB. While I don't know if they exist in real life, powerpc supports up to 256kiB pages, and I know they are used for real in various embedded settings

They *did* way back in the day, I worked with some seekrit PPC440s early in my career. I don't know that any of them still exist, but the code is still there...

Sorry, I meant I don't really know how real the hexagon large page sizes are. I know about the ppcs one personally, too.

Quoted 5 lines
Quoted 4 lines

If we do need to fix this, there are a couple things we could consider doing without changing the on-disk format in ext4 or f2fs: putting the data in the page cache at a different offset than it exists on-disk, or using "small" pages for EOF specifically.

Quoted 6 lines
Quoted 3 lines

I'd leave the ondisk offset as-is, but change the pagecache offset to roundup(i_size_read(), mapping_max_folio_size_supported()) just to keep file data and fsverity metadata completely separate.

Can we find a way to do that in common code and make ext4 and f2fs do the same?

hmm I don't see what else we could do except providing common offset and then use it to map blocks

loff_t fsverity_metadata_offset(struct inode *inode)

{
	return roundup(i_size_read(), mapping_max_folio_size_supported());
}
Signature · 1 line
- Andrey
#83 lore raw

Re: fsverity metadata offset, was: Re: [PATCH v2 0/23] fs-verity support for XFS with post EOF merkle tree

Quoted 26 lines · Andrey Albershteyn
Quoted 18 lines · Christoph Hellwig
Quoted 8 lines · Darrick J. Wong
Quoted 4 lines
Quoted 4 lines

a) not all architectures are reasonable. As Darrick pointed out hexagon seems to support page size up to 1MiB. While I don't know if they exist in real life, powerpc supports up to 256kiB pages, and I know they are used for real in various embedded settings

They *did* way back in the day, I worked with some seekrit PPC440s early in my career. I don't know that any of them still exist, but the code is still there...

Sorry, I meant I don't really know how real the hexagon large page sizes are. I know about the ppcs one personally, too.

Quoted 5 lines
Quoted 4 lines

If we do need to fix this, there are a couple things we could consider doing without changing the on-disk format in ext4 or f2fs: putting the data in the page cache at a different offset than it exists on-disk, or using "small" pages for EOF specifically.

Quoted 6 lines
Quoted 3 lines

I'd leave the ondisk offset as-is, but change the pagecache offset to roundup(i_size_read(), mapping_max_folio_size_supported()) just to keep file data and fsverity metadata completely separate.

Can we find a way to do that in common code and make ext4 and f2fs do the same?

Quoted 7 lines

hmm I don't see what else we could do except providing common offset and then use it to map blocks

loff_t fsverity_metadata_offset(struct inode *inode)

{
	return roundup(i_size_read(), mapping_max_folio_size_supported());
}

Yeah, that's probably the best we can do. Please add a comment to that helper to state explicitly that this is the *incore* file offset of the merkle tree if the filesystem decides to cache it in the pagecache.

--D

Quoted 4 lines
Signature · 1 line
- Andrey
#84 lore raw

Re: fsverity metadata offset, was: Re: [PATCH v2 0/23] fs-verity support for XFS with post EOF merkle tree

In reply to Andrey Albershteyn
Quoted 7 lines · Andrey Albershteyn
Quoted 6 lines
Quoted 3 lines

I'd leave the ondisk offset as-is, but change the pagecache offset to roundup(i_size_read(), mapping_max_folio_size_supported()) just to keep file data and fsverity metadata completely separate.

Can we find a way to do that in common code and make ext4 and f2fs do the same?

Quoted 7 lines

hmm I don't see what else we could do except providing common offset and then use it to map blocks

loff_t fsverity_metadata_offset(struct inode *inode)

{
	return roundup(i_size_read(), mapping_max_folio_size_supported());
}

Something like that, yes.

#85 lore raw

Re: fsverity metadata offset, was: Re: [PATCH v2 0/23] fs-verity support for XFS with post EOF merkle tree

In reply to Eric Biggers
Quoted 25 lines · Eric Biggers
Quoted 24 lines · Christoph Hellwig

While looking at fsverity I'd like to understand the choise of offset in ext4 and f2fs, and wonder about an issue.

Both ext4 and f2fs round up the inode size to the next 64k boundary and place the metadata there. Both use the 65536 magic number for that instead of a well documented constant unfortunately.

I assume this was picked to align up to the largest reasonable page size? Unfortunately for that:

a) not all architectures are reasonable. As Darrick pointed out hexagon seems to support page size up to 1MiB. While I don't know if they exist in real life, powerpc supports up to 256kiB pages, and I know they are used for real in various embedded settings
b) with large folio support in the page cache, the folios used to map files can be much larger than the base page size, with all the same issues as a larger page size

So assuming that fsverity is trying to avoid the issue of a page/folio that covers both data and fsverity metadata, how does it copy with that? Do we need to disable fsverity on > 64k page size and disable large folios on fsverity files? The latter would mean writing back all cached data first as well.

Quoted 8 lines
Quoted 4 lines

And going forward, should we have a v2 format that fixes this? For that
we'd still need a maximum folio size of course. And of course I'd like
to get all these things right from the start in XFS, while still being as similar as possible to ext4/f2fs.

Yes, if I recall correctly it was intended to be the "largest reasonable page size". It looks like PAGE_SIZE > 65536 can't work as-is, so indeed we should disable fsverity support in that configuration.

I don't think anybody will weep for lack of fsverity support in these weirdo large PAGE_SIZE configurations.

Quoted 10 lines

I don't think large folios are quite as problematic. ext4_read_merkle_tree_page() and f2fs_read_merkle_tree_page() read a folio and return the appropriate page in it, and fs/verity/verify.c operates on the page. If it's a page in the folio that spans EOF, I think everything will actually still work, except userspace will be able to see Merkle tree data after a 64K boundary past EOF if the file is mmapped using huge pages.

The mmap issue isn't great, but I'm not sure how much it matters, especially when the zeroes do still go up to a 64K boundary.

We actually refuse to map pages after EOF. See filemap_map_pages()

        if ((file_end >= folio_next_index(folio) || shmem_mapping(mapping)) &&
            filemap_map_pmd(vmf, folio, start_pgoff)) {
                ret = VM_FAULT_NOPAGE;
                goto out;
        }

along with the other treatment of end_pgoff.