Reported verification errors to fsnotify through recently added fserror interface. Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Acked-by: Eric Biggers Signed-off-by: Andrey Albershteyn --- fs/verity/verify.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/verity/verify.c b/fs/verity/verify.c index 4004a1d42875..db8c350234bb 100644 --- a/fs/verity/verify.c +++ b/fs/verity/verify.c @@ -9,6 +9,7 @@ #include #include +#include #define FS_VERITY_MAX_PENDING_BLOCKS 2 @@ -205,6 +206,8 @@ static bool verify_data_block(struct fsverity_info *vi, if (memchr_inv(dblock->data, 0, params->block_size)) { fsverity_err(inode, "FILE CORRUPTED! Data past EOF is not zeroed"); + fserror_report_data_lost(inode, data_pos, + params->block_size, GFP_NOFS); return false; } return true; @@ -312,6 +315,7 @@ static bool verify_data_block(struct fsverity_info *vi, data_pos, level - 1, params->hash_alg->name, hsize, want_hash, params->hash_alg->name, hsize, level == 0 ? dblock->real_hash : real_hash); + fserror_report_data_lost(inode, data_pos, params->block_size, GFP_NOFS); error: for (; level > 0; level--) { kunmap_local(hblocks[level - 1].addr); -- 2.54.0 This function will be used by XFS's scrub to force fsverity activation, therefore, to read fsverity context. Reviewed-by: Darrick J. Wong Acked-by: Eric Biggers Signed-off-by: Andrey Albershteyn Reviewed-by: Christoph Hellwig --- fs/verity/open.c | 26 ++++++++++++++++++++++++-- include/linux/fsverity.h | 2 ++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/fs/verity/open.c b/fs/verity/open.c index d0c56a7faa3b..4f06697f5bf4 100644 --- a/fs/verity/open.c +++ b/fs/verity/open.c @@ -347,7 +347,28 @@ int fsverity_get_descriptor(struct inode *inode, return 0; } -static int ensure_verity_info(struct inode *inode) +/** + * fsverity_ensure_verity_info() - cache verity info if it's not already cached + * @inode: the inode for which verity info should be cached + * + * Ensure this inode has verity info attached to it, it's assumed the inode + * already has fsverity enabled. Read fsverity descriptor and creates verity + * based on that. + * + * This needs to be called at least once before any of the inode's data + * can be verified (and thus read at all) or the inode's fsverity digest + * retrieved. fsverity_file_open() calls this already, which handles + * normal file accesses. If a filesystem does any internal (i.e. not + * associated with a file descriptor) reads of the file's data or + * fsverity digest, it must call this explicitly before doing so. + * + * In case filesystem supports both fscrypt and fsverity, this should be called + * after fscrypt's encryption key is set up. Otherwise, the fsverity metadata is + * still encrypted. See fscrypt_file_open(). + * + * Return: 0 on success, -errno on failure + */ +int fsverity_ensure_verity_info(struct inode *inode) { struct fsverity_info *vi = fsverity_get_info(inode), *found; struct fsverity_descriptor *desc; @@ -383,12 +404,13 @@ static int ensure_verity_info(struct inode *inode) kfree(desc); return err; } +EXPORT_SYMBOL_GPL(fsverity_ensure_verity_info); int __fsverity_file_open(struct inode *inode, struct file *filp) { if (filp->f_mode & FMODE_WRITE) return -EPERM; - return ensure_verity_info(inode); + return fsverity_ensure_verity_info(inode); } EXPORT_SYMBOL_GPL(__fsverity_file_open); diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h index 6c467ded9751..3c3250f6f272 100644 --- a/include/linux/fsverity.h +++ b/include/linux/fsverity.h @@ -317,6 +317,8 @@ static inline int fsverity_file_open(struct inode *inode, struct file *filp) return 0; } +int fsverity_ensure_verity_info(struct inode *inode); + void fsverity_cleanup_inode(struct inode *inode); struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index); -- 2.54.0 Let filesystem iterate over hashes in the block and check if these are hashes of zeroed data blocks. XFS will use this to decide if it want to store tree block full of these hashes. Signed-off-by: Andrey Albershteyn Reviewed-by: "Darrick J. Wong" Acked-by: Eric Biggers Acked-by: David Sterba --- fs/btrfs/verity.c | 6 +++++- fs/ext4/verity.c | 4 +++- fs/f2fs/verity.c | 4 +++- fs/verity/enable.c | 4 +++- include/linux/fsverity.h | 6 +++++- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c index 4e0ab5842274..c572acacb4d4 100644 --- a/fs/btrfs/verity.c +++ b/fs/btrfs/verity.c @@ -779,11 +779,15 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, * @buf: Merkle tree block to write * @pos: the position of the block in the Merkle tree (in bytes) * @size: the Merkle tree block size (in bytes) + * @zero_digest: the hash of the all-zeroes block + * @digest_size: size of zero_digest, in bytes * * Returns 0 on success or negative error code on failure */ static int btrfs_write_merkle_tree_block(struct file *file, const void *buf, - u64 pos, unsigned int size) + u64 pos, unsigned int size, + const u8 *zero_digest, + unsigned int digest_size) { struct inode *inode = file_inode(file); loff_t merkle_pos = merkle_file_pos(inode); diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c index ca61da53f313..347945ac23a4 100644 --- a/fs/ext4/verity.c +++ b/fs/ext4/verity.c @@ -374,7 +374,9 @@ static void ext4_readahead_merkle_tree(struct inode *inode, pgoff_t index, } static int ext4_write_merkle_tree_block(struct file *file, const void *buf, - u64 pos, unsigned int size) + u64 pos, unsigned int size, + const u8 *zero_digest, + unsigned int digest_size) { pos += ext4_verity_metadata_pos(file_inode(file)); diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c index 39f482515445..cc4158fc841d 100644 --- a/fs/f2fs/verity.c +++ b/fs/f2fs/verity.c @@ -272,7 +272,9 @@ static void f2fs_readahead_merkle_tree(struct inode *inode, pgoff_t index, } static int f2fs_write_merkle_tree_block(struct file *file, const void *buf, - u64 pos, unsigned int size) + u64 pos, unsigned int size, + const u8 *zero_digest, + unsigned int digest_size) { pos += f2fs_verity_metadata_pos(file_inode(file)); diff --git a/fs/verity/enable.c b/fs/verity/enable.c index 42dfed1ce0ce..ad4ff71d7dd9 100644 --- a/fs/verity/enable.c +++ b/fs/verity/enable.c @@ -50,7 +50,9 @@ static int write_merkle_tree_block(struct file *file, const u8 *buf, int err; err = inode->i_sb->s_vop->write_merkle_tree_block(file, buf, pos, - params->block_size); + params->block_size, + params->zero_digest, + params->digest_size); if (err) fsverity_err(inode, "Error %d writing Merkle tree block %lu", err, index); diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h index 3c3250f6f272..9e7d946676b9 100644 --- a/include/linux/fsverity.h +++ b/include/linux/fsverity.h @@ -124,6 +124,8 @@ struct fsverity_operations { * @buf: the Merkle tree block to write * @pos: the position of the block in the Merkle tree (in bytes) * @size: the Merkle tree block size (in bytes) + * @zero_digest: the hash of the all-zeroes block + * @digest_size: size of zero_digest, in bytes * * This is only called between ->begin_enable_verity() and * ->end_enable_verity(). @@ -131,7 +133,9 @@ struct fsverity_operations { * Return: 0 on success, -errno on failure */ int (*write_merkle_tree_block)(struct file *file, const void *buf, - u64 pos, unsigned int size); + u64 pos, unsigned int size, + const u8 *zero_digest, + unsigned int digest_size); }; #ifdef CONFIG_FS_VERITY -- 2.54.0 This is the same function to read from pageache. XFS will also need this, so move this to core fsverity. Note that f2fs and ext4 functions diverged a bit, as ext4 operated over folios and f2fs operated over pages. The common one will operate over folios. Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Acked-by: Eric Biggers Signed-off-by: Andrey Albershteyn --- fs/ext4/verity.c | 32 +++----------------------------- fs/f2fs/verity.c | 30 +----------------------------- fs/verity/pagecache.c | 33 +++++++++++++++++++++++++++++++++ include/linux/fsverity.h | 2 ++ 4 files changed, 39 insertions(+), 58 deletions(-) diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c index 347945ac23a4..ac5c133f5529 100644 --- a/fs/ext4/verity.c +++ b/fs/ext4/verity.c @@ -34,32 +34,6 @@ static inline loff_t ext4_verity_metadata_pos(const struct inode *inode) return round_up(inode->i_size, 65536); } -/* - * Read some verity metadata from the inode. __vfs_read() can't be used because - * we need to read beyond i_size. - */ -static int pagecache_read(struct inode *inode, void *buf, size_t count, - loff_t pos) -{ - while (count) { - struct folio *folio; - size_t n; - - folio = read_mapping_folio(inode->i_mapping, pos >> PAGE_SHIFT, - NULL); - if (IS_ERR(folio)) - return PTR_ERR(folio); - - n = memcpy_from_file_folio(buf, folio, pos, count); - folio_put(folio); - - buf += n; - pos += n; - count -= n; - } - return 0; -} - /* * Write some verity metadata to the inode for FS_IOC_ENABLE_VERITY. * kernel_write() can't be used because the file descriptor is readonly. @@ -311,8 +285,8 @@ static int ext4_get_verity_descriptor_location(struct inode *inode, goto bad; desc_size_pos -= sizeof(desc_size_disk); - err = pagecache_read(inode, &desc_size_disk, sizeof(desc_size_disk), - desc_size_pos); + err = fsverity_pagecache_read(inode, &desc_size_disk, + sizeof(desc_size_disk), desc_size_pos); if (err) return err; desc_size = le32_to_cpu(desc_size_disk); @@ -352,7 +326,7 @@ static int ext4_get_verity_descriptor(struct inode *inode, void *buf, if (buf_size) { if (desc_size > buf_size) return -ERANGE; - err = pagecache_read(inode, buf, desc_size, desc_pos); + err = fsverity_pagecache_read(inode, buf, desc_size, desc_pos); if (err) return err; } diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c index cc4158fc841d..f3a4617656db 100644 --- a/fs/f2fs/verity.c +++ b/fs/f2fs/verity.c @@ -37,34 +37,6 @@ static inline loff_t f2fs_verity_metadata_pos(const struct inode *inode) return round_up(inode->i_size, 65536); } -/* - * Read some verity metadata from the inode. __vfs_read() can't be used because - * we need to read beyond i_size. - */ -static int pagecache_read(struct inode *inode, void *buf, size_t count, - loff_t pos) -{ - while (count) { - size_t n = min_t(size_t, count, - PAGE_SIZE - offset_in_page(pos)); - struct page *page; - - page = read_mapping_page(inode->i_mapping, pos >> PAGE_SHIFT, - NULL); - if (IS_ERR(page)) - return PTR_ERR(page); - - memcpy_from_page(buf, page, offset_in_page(pos), n); - - put_page(page); - - buf += n; - pos += n; - count -= n; - } - return 0; -} - /* * Write some verity metadata to the inode for FS_IOC_ENABLE_VERITY. * kernel_write() can't be used because the file descriptor is readonly. @@ -250,7 +222,7 @@ static int f2fs_get_verity_descriptor(struct inode *inode, void *buf, if (buf_size) { if (size > buf_size) return -ERANGE; - res = pagecache_read(inode, buf, size, pos); + res = fsverity_pagecache_read(inode, buf, size, pos); if (res) return res; } diff --git a/fs/verity/pagecache.c b/fs/verity/pagecache.c index 99f5f53eea98..9d82e6b74ba1 100644 --- a/fs/verity/pagecache.c +++ b/fs/verity/pagecache.c @@ -78,3 +78,36 @@ void fsverity_fill_zerohash(struct folio *folio, size_t offset, size_t len, vi->tree_params.digest_size); } EXPORT_SYMBOL_GPL(fsverity_fill_zerohash); + +/** + * fsverity_pagecache_read() - read page and copy data to buffer + * @inode: copy from this inode's address space + * @buf: buffer to copy to + * @count: number of bytes to copy + * @pos: position of the folio to copy from + * + * Read some verity metadata from the inode. __vfs_read() can't be used because + * we need to read beyond i_size. + */ +int fsverity_pagecache_read(struct inode *inode, void *buf, size_t count, + loff_t pos) +{ + while (count) { + struct folio *folio; + size_t n; + + folio = read_mapping_folio(inode->i_mapping, pos >> PAGE_SHIFT, + NULL); + if (IS_ERR(folio)) + return PTR_ERR(folio); + + n = memcpy_from_file_folio(buf, folio, pos, count); + folio_put(folio); + + buf += n; + pos += n; + count -= n; + } + return 0; +} +EXPORT_SYMBOL_GPL(fsverity_pagecache_read); diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h index 9e7d946676b9..f9433332c274 100644 --- a/include/linux/fsverity.h +++ b/include/linux/fsverity.h @@ -328,5 +328,7 @@ void fsverity_cleanup_inode(struct inode *inode); struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index); void generic_readahead_merkle_tree(struct inode *inode, pgoff_t index, unsigned long nr_pages); +int fsverity_pagecache_read(struct inode *inode, void *buf, size_t count, + loff_t pos); #endif /* _LINUX_FSVERITY_H */ -- 2.54.0 When fsverity is enabled on the file, with FS_IOC_ENABLE_VERITY ioctl(), it checks if file has DAX enabled and fails if that's true. However, the opposite case is not checked. Note, that the only other filesystem supporting DAX and fsverity is ext4, and ext4 does check for this case. Signed-off-by: Andrey Albershteyn Reviewed-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" --- fs/file_attr.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/file_attr.c b/fs/file_attr.c index bfb00d256dd5..391edd1d0ce3 100644 --- a/fs/file_attr.c +++ b/fs/file_attr.c @@ -235,10 +235,15 @@ static int fileattr_set_prepare(struct inode *inode, /* * It is only valid to set the DAX flag on regular files and * directories on filesystems. + * + * DAX and fsverity are incompatible. */ - if ((fa->fsx_xflags & FS_XFLAG_DAX) && - !(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) - return -EINVAL; + if (fa->fsx_xflags & FS_XFLAG_DAX) { + if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) + return -EINVAL; + if (old_ma->fsx_xflags & FS_XFLAG_VERITY) + return -EINVAL; + } /* Extent size hints of zero turn off the flags. */ if (fa->fsx_extsize == 0) -- 2.54.0 All filesystems, supporting fsverity, report this status by checking inode flag. Also, BTRFS was missing stat->attributes_mask, which is fixed now. Fixes: 146054090b08 ("btrfs: initial fsverity support") Cc: stable@vger.kernel.org Signed-off-by: Andrey Albershteyn Acked-by: Eric Biggers Reviewed-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" --- fs/btrfs/inode.c | 3 --- fs/ext4/inode.c | 5 +---- fs/f2fs/file.c | 5 +---- fs/stat.c | 6 +++++- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 93ef3cec191e..c34d0198b988 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -8236,7 +8236,6 @@ static int btrfs_getattr(struct mnt_idmap *idmap, struct inode *inode = d_inode(path->dentry); u32 blocksize = btrfs_sb(inode->i_sb)->sectorsize; u32 bi_flags = BTRFS_I(inode)->flags; - u32 bi_ro_flags = BTRFS_I(inode)->ro_flags; stat->result_mask |= STATX_BTIME; stat->btime.tv_sec = BTRFS_I(inode)->i_otime_sec; @@ -8249,8 +8248,6 @@ static int btrfs_getattr(struct mnt_idmap *idmap, stat->attributes |= STATX_ATTR_IMMUTABLE; if (bi_flags & BTRFS_INODE_NODUMP) stat->attributes |= STATX_ATTR_NODUMP; - if (bi_ro_flags & BTRFS_INODE_RO_VERITY) - stat->attributes |= STATX_ATTR_VERITY; stat->attributes_mask |= (STATX_ATTR_APPEND | STATX_ATTR_COMPRESSED | diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 26f0f9714f03..206c6245c619 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -6318,15 +6318,12 @@ int ext4_getattr(struct mnt_idmap *idmap, const struct path *path, stat->attributes |= STATX_ATTR_IMMUTABLE; if (flags & EXT4_NODUMP_FL) stat->attributes |= STATX_ATTR_NODUMP; - if (flags & EXT4_VERITY_FL) - stat->attributes |= STATX_ATTR_VERITY; stat->attributes_mask |= (STATX_ATTR_APPEND | STATX_ATTR_COMPRESSED | STATX_ATTR_ENCRYPTED | STATX_ATTR_IMMUTABLE | - STATX_ATTR_NODUMP | - STATX_ATTR_VERITY); + STATX_ATTR_NODUMP); generic_fillattr(idmap, request_mask, inode, stat); return 0; diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index edc352569e87..dc1cc0965532 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -1075,15 +1075,12 @@ int f2fs_getattr(struct mnt_idmap *idmap, const struct path *path, stat->attributes |= STATX_ATTR_IMMUTABLE; if (flags & F2FS_NODUMP_FL) stat->attributes |= STATX_ATTR_NODUMP; - if (IS_VERITY(inode)) - stat->attributes |= STATX_ATTR_VERITY; stat->attributes_mask |= (STATX_ATTR_COMPRESSED | STATX_ATTR_APPEND | STATX_ATTR_ENCRYPTED | STATX_ATTR_IMMUTABLE | - STATX_ATTR_NODUMP | - STATX_ATTR_VERITY); + STATX_ATTR_NODUMP); generic_fillattr(idmap, request_mask, inode, stat); diff --git a/fs/stat.c b/fs/stat.c index c461c3054234..e03954c36b33 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -203,8 +203,12 @@ int vfs_getattr_nosec(const struct path *path, struct kstat *stat, if (IS_DAX(inode)) stat->attributes |= STATX_ATTR_DAX; + if (IS_VERITY(inode)) + stat->attributes |= STATX_ATTR_VERITY; + stat->attributes_mask |= (STATX_ATTR_AUTOMOUNT | - STATX_ATTR_DAX); + STATX_ATTR_DAX | + STATX_ATTR_VERITY); idmap = mnt_idmap(path->mnt); if (inode->i_op->getattr) { -- 2.54.0 Introduce XFS_DIFLAG2_VERITY for inodes with fsverity. This flag indicates that inode has fs-verity enabled (i.e. descriptor exist, tree is built and file is read-only). Introduce XFS_SB_FEAT_RO_COMPAT_VERITY for filesystems having fsverity inodes. As on-disk changes applies to fsverity inodes only, let older kernels read-only access. This will be enabled in the further patch after full fsverity support. Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Andrey Albershteyn --- fs/xfs/libxfs/xfs_format.h | 30 +++++++++++++++++++++++++++++- fs/xfs/libxfs/xfs_inode_buf.c | 8 ++++++++ fs/xfs/libxfs/xfs_inode_util.c | 5 ++++- fs/xfs/libxfs/xfs_sb.c | 2 ++ fs/xfs/xfs_iops.c | 5 ++++- fs/xfs/xfs_mount.h | 2 ++ 6 files changed, 49 insertions(+), 3 deletions(-) diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h index dd0ed046fbe9..fc196d9e12dd 100644 --- a/fs/xfs/libxfs/xfs_format.h +++ b/fs/xfs/libxfs/xfs_format.h @@ -374,6 +374,7 @@ xfs_sb_has_compat_feature( #define XFS_SB_FEAT_RO_COMPAT_RMAPBT (1 << 1) /* reverse map btree */ #define XFS_SB_FEAT_RO_COMPAT_REFLINK (1 << 2) /* reflinked files */ #define XFS_SB_FEAT_RO_COMPAT_INOBTCNT (1 << 3) /* inobt block counts */ +#define XFS_SB_FEAT_RO_COMPAT_VERITY (1 << 4) /* fs-verity */ #define XFS_SB_FEAT_RO_COMPAT_ALL \ (XFS_SB_FEAT_RO_COMPAT_FINOBT | \ XFS_SB_FEAT_RO_COMPAT_RMAPBT | \ @@ -1230,16 +1231,21 @@ static inline void xfs_dinode_put_rdev(struct xfs_dinode *dip, xfs_dev_t rdev) */ #define XFS_DIFLAG2_METADATA_BIT 5 +/* inodes sealed with fs-verity */ +#define XFS_DIFLAG2_VERITY_BIT 6 + #define XFS_DIFLAG2_DAX (1ULL << XFS_DIFLAG2_DAX_BIT) #define XFS_DIFLAG2_REFLINK (1ULL << XFS_DIFLAG2_REFLINK_BIT) #define XFS_DIFLAG2_COWEXTSIZE (1ULL << XFS_DIFLAG2_COWEXTSIZE_BIT) #define XFS_DIFLAG2_BIGTIME (1ULL << XFS_DIFLAG2_BIGTIME_BIT) #define XFS_DIFLAG2_NREXT64 (1ULL << XFS_DIFLAG2_NREXT64_BIT) #define XFS_DIFLAG2_METADATA (1ULL << XFS_DIFLAG2_METADATA_BIT) +#define XFS_DIFLAG2_VERITY (1ULL << XFS_DIFLAG2_VERITY_BIT) #define XFS_DIFLAG2_ANY \ (XFS_DIFLAG2_DAX | XFS_DIFLAG2_REFLINK | XFS_DIFLAG2_COWEXTSIZE | \ - XFS_DIFLAG2_BIGTIME | XFS_DIFLAG2_NREXT64 | XFS_DIFLAG2_METADATA) + XFS_DIFLAG2_BIGTIME | XFS_DIFLAG2_NREXT64 | XFS_DIFLAG2_METADATA | \ + XFS_DIFLAG2_VERITY) static inline bool xfs_dinode_has_bigtime(const struct xfs_dinode *dip) { @@ -2027,4 +2033,26 @@ struct xfs_acl { #define SGI_ACL_FILE_SIZE (sizeof(SGI_ACL_FILE)-1) #define SGI_ACL_DEFAULT_SIZE (sizeof(SGI_ACL_DEFAULT)-1) +/* + * At maximum of 8 levels with 128 hashes per block (32 bytes SHA-256) maximum + * tree size is ((128^8 − 1)/(128 − 1)) = 567*10^12 blocks. This should fit in + * 53 bits address space. + * + * At this Merkle tree size we can cover 295EB large file. This is much larger + * than the currently supported file size. + * + * For sha512 the largest file we can cover ends at 1 << 50 offset, this is also + * good. + */ +#define XFS_FSVERITY_LARGEST_FILE ((loff_t)1ULL << 53) + +/* + * Alignment of the fsverity metadata placement. This is largest supported PAGE + * SIZE for fsverity. This is used to space out data and metadata in page cache. + * The spacing is necessary for non-exposure of metadata to userspace and + * correct merkle tree synthesis in the iomap. + */ +#define XFS_FSVERITY_START_ALIGN (65536) + + #endif /* __XFS_FORMAT_H__ */ diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c index e4c3f7b24e95..b4cc673adc8a 100644 --- a/fs/xfs/libxfs/xfs_inode_buf.c +++ b/fs/xfs/libxfs/xfs_inode_buf.c @@ -761,6 +761,14 @@ xfs_dinode_verify( !xfs_has_rtreflink(mp)) return __this_address; + /* only regular files can have fsverity */ + if (flags2 & XFS_DIFLAG2_VERITY) { + if (!xfs_has_verity(mp)) + return __this_address; + if (!S_ISREG(mode)) + return __this_address; + } + if (xfs_has_zoned(mp) && dip->di_metatype == cpu_to_be16(XFS_METAFILE_RTRMAP)) { if (be32_to_cpu(dip->di_used_blocks) > mp->m_sb.sb_rgextents) diff --git a/fs/xfs/libxfs/xfs_inode_util.c b/fs/xfs/libxfs/xfs_inode_util.c index 258ac3d0d486..5a516bd31c31 100644 --- a/fs/xfs/libxfs/xfs_inode_util.c +++ b/fs/xfs/libxfs/xfs_inode_util.c @@ -74,7 +74,8 @@ xfs_flags2diflags2( uint64_t di_flags2 = (ip->i_diflags2 & (XFS_DIFLAG2_REFLINK | XFS_DIFLAG2_BIGTIME | - XFS_DIFLAG2_NREXT64)); + XFS_DIFLAG2_NREXT64 | + XFS_DIFLAG2_VERITY)); if (xflags & FS_XFLAG_DAX) di_flags2 |= XFS_DIFLAG2_DAX; @@ -126,6 +127,8 @@ xfs_ip2xflags( flags |= FS_XFLAG_DAX; if (ip->i_diflags2 & XFS_DIFLAG2_COWEXTSIZE) flags |= FS_XFLAG_COWEXTSIZE; + if (ip->i_diflags2 & XFS_DIFLAG2_VERITY) + flags |= FS_XFLAG_VERITY; } if (xfs_inode_has_attr_fork(ip)) diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c index f0341adbb879..985c7c6a09f2 100644 --- a/fs/xfs/libxfs/xfs_sb.c +++ b/fs/xfs/libxfs/xfs_sb.c @@ -165,6 +165,8 @@ xfs_sb_version_to_features( features |= XFS_FEAT_REFLINK; if (sbp->sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_INOBTCNT) features |= XFS_FEAT_INOBTCNT; + if (sbp->sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_VERITY) + features |= XFS_FEAT_VERITY; if (sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_FTYPE) features |= XFS_FEAT_FTYPE; if (sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_SPINODES) diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index d1306e723899..c7ecbb3d258a 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -1397,12 +1397,15 @@ xfs_diflags_to_iflags( flags |= S_NOATIME; if (init && xfs_inode_should_enable_dax(ip)) flags |= S_DAX; + if (xflags & FS_XFLAG_VERITY) + flags |= S_VERITY; /* * S_DAX can only be set during inode initialization and is never set by * the VFS, so we cannot mask off S_DAX in i_flags. */ - inode->i_flags &= ~(S_IMMUTABLE | S_APPEND | S_SYNC | S_NOATIME); + inode->i_flags &= + ~(S_IMMUTABLE | S_APPEND | S_SYNC | S_NOATIME | S_VERITY); inode->i_flags |= flags; } diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h index 894ff2f4ecbd..60cb54c1a5ec 100644 --- a/fs/xfs/xfs_mount.h +++ b/fs/xfs/xfs_mount.h @@ -403,6 +403,7 @@ typedef struct xfs_mount { #define XFS_FEAT_EXCHANGE_RANGE (1ULL << 27) /* exchange range */ #define XFS_FEAT_METADIR (1ULL << 28) /* metadata directory tree */ #define XFS_FEAT_ZONED (1ULL << 29) /* zoned RT device */ +#define XFS_FEAT_VERITY (1ULL << 30) /* fs-verity */ /* Mount features */ #define XFS_FEAT_NOLIFETIME (1ULL << 47) /* disable lifetime hints */ @@ -460,6 +461,7 @@ __XFS_HAS_FEAT(exchange_range, EXCHANGE_RANGE) __XFS_HAS_FEAT(metadir, METADIR) __XFS_HAS_FEAT(zoned, ZONED) __XFS_HAS_FEAT(nolifetime, NOLIFETIME) +__XFS_HAS_FEAT(verity, VERITY) static inline bool xfs_has_rtgroups(const struct xfs_mount *mp) { -- 2.54.0 fs-verity doesn't support DAX. Forbid filesystem to enable DAX on inodes which already have fs-verity enabled. The opposite is checked when fs-verity is enabled, it won't be enabled if DAX is. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Andrey Albershteyn --- fs/xfs/xfs_iops.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index c7ecbb3d258a..b07ca2a33c64 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -1369,6 +1369,8 @@ xfs_inode_should_enable_dax( return false; if (!xfs_inode_supports_dax(ip)) return false; + if (ip->i_diflags2 & XFS_DIFLAG2_VERITY) + return false; if (xfs_has_dax_always(ip->i_mount)) return true; if (ip->i_diflags2 & XFS_DIFLAG2_DAX) -- 2.54.0 The direct path is not supported on verity files. Attempts to use direct I/O path on such files should fall back to buffered I/O path. Add a fall back to buffered I/O at two place, in a common fast path and latter when lock is acquired. The second check prevents TOCTOU issue with reading fsverity_active() status and resetting IOCB_DIRECT flag. If one thread saw fsverity_active() to be false, and then second thread acquired XFS_IOLOCK_EXCL and enabled fsverity. The first thread will go through the DIO path. Signed-off-by: Darrick J. Wong Signed-off-by: Andrey Albershteyn Reviewed-by: Christoph Hellwig --- fs/xfs/xfs_file.c | 61 +++++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index dd3782e9af93..07abf6e8783f 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -38,6 +38,7 @@ #include #include #include +#include static const struct vm_operations_struct xfs_file_vm_ops; @@ -232,6 +233,25 @@ static const struct iomap_dio_ops xfs_dio_read_bounce_ops = { .bio_set = &iomap_ioend_bioset, }; +STATIC ssize_t +xfs_file_buffered_read( + struct kiocb *iocb, + struct iov_iter *to) +{ + struct xfs_inode *ip = XFS_I(file_inode(iocb->ki_filp)); + ssize_t ret; + + trace_xfs_file_buffered_read(iocb, to); + + ret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED); + if (ret) + return ret; + ret = generic_file_read_iter(iocb, to); + xfs_iunlock(ip, XFS_IOLOCK_SHARED); + + return ret; +} + STATIC ssize_t xfs_file_dio_read( struct kiocb *iocb, @@ -250,6 +270,17 @@ xfs_file_dio_read( ret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED); if (ret) return ret; + + /* + * Re-check verity status after acquiring lock. This prevents TOCTOU in + * xfs_file_read_iter() while falling back from DIO to buffered I/O as + * now we are holding a lock + */ + if (fsverity_active(VFS_I(ip))) { + xfs_iunlock(ip, XFS_IOLOCK_SHARED); + iocb->ki_flags &= ~IOCB_DIRECT; + return xfs_file_buffered_read(iocb, to); + } if (mapping_stable_writes(iocb->ki_filp->f_mapping)) { ret = iomap_dio_rw(iocb, to, &xfs_read_iomap_ops, &xfs_dio_read_bounce_ops, 0, NULL, 0); @@ -269,7 +300,8 @@ xfs_file_dax_read( struct kiocb *iocb, struct iov_iter *to) { - struct xfs_inode *ip = XFS_I(iocb->ki_filp->f_mapping->host); + struct inode *inode = iocb->ki_filp->f_mapping->host; + struct xfs_inode *ip = XFS_I(inode); ssize_t ret = 0; trace_xfs_file_dax_read(iocb, to); @@ -287,25 +319,6 @@ xfs_file_dax_read( return ret; } -STATIC ssize_t -xfs_file_buffered_read( - struct kiocb *iocb, - struct iov_iter *to) -{ - struct xfs_inode *ip = XFS_I(file_inode(iocb->ki_filp)); - ssize_t ret; - - trace_xfs_file_buffered_read(iocb, to); - - ret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED); - if (ret) - return ret; - ret = generic_file_read_iter(iocb, to); - xfs_iunlock(ip, XFS_IOLOCK_SHARED); - - return ret; -} - STATIC ssize_t xfs_file_read_iter( struct kiocb *iocb, @@ -320,6 +333,14 @@ xfs_file_read_iter( if (xfs_is_shutdown(mp)) return -EIO; + /* + * In case fs-verity is enabled, we also fallback to the buffered read + * from the direct read path. Therefore, IOCB_DIRECT is set and need to + * be cleared (see generic_file_read_iter()) + */ + if (fsverity_active(inode)) + iocb->ki_flags &= ~IOCB_DIRECT; + if (IS_DAX(inode)) ret = xfs_file_dax_read(iocb, to); else if (iocb->ki_flags & IOCB_DIRECT) -- 2.54.0 Sashiko.dev reported that while fsverity files falls back to the buffered IO for Direct I/O, they should not report non-zero values in dio_mem_align and dio_offset_align, meaning it's not supported. The STATX_DIOALIGN | STATX_DIO_READ_ALIGN flags are used to explicitly report if DIO is supported or not. They can not indicate that fallback to the buffered IO is used in this case. The zero alignment values also mean that DIO is not supported on this file, see statx(2). Signed-off-by: Andrey Albershteyn Acked-by: Eric Biggers Reviewed-by: "Darrick J. Wong" --- fs/xfs/xfs_iops.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index b07ca2a33c64..d9c25c06cc60 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -35,6 +35,7 @@ #include #include #include +#include /* * Directories have different lock order w.r.t. mmap_lock compared to regular @@ -579,6 +580,10 @@ xfs_report_dioalign( struct block_device *bdev = target->bt_bdev; stat->result_mask |= STATX_DIOALIGN | STATX_DIO_READ_ALIGN; + + if (fsverity_active(VFS_I(ip))) + return; + stat->dio_mem_align = bdev_dma_alignment(bdev) + 1; /* -- 2.54.0 For write/writeback set IOMAP_F_FSVERITY flag telling iomap to not update inode size and to not skip folios beyond EOF. Initiate fsverity writeback with IOMAP_F_FSVERITY set to tell iomap should not skip folio that is dirty beyond EOF. In read path let iomap know that we are reading fsverity metadata. So, treat holes in the tree as request to synthesize tree blocks and hole after descriptor as end of the fsverity region. Introduce a new inode flag meaning that merkle tree is being build on the inode. Signed-off-by: Andrey Albershteyn Reviewed-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig --- fs/xfs/Makefile | 1 + fs/xfs/libxfs/xfs_bmap.c | 11 +++++++++++ fs/xfs/xfs_aops.c | 36 +++++++++++++++++++++++++++++++----- fs/xfs/xfs_fsverity.c | 22 ++++++++++++++++++++++ fs/xfs/xfs_fsverity.h | 20 ++++++++++++++++++++ fs/xfs/xfs_inode.h | 6 ++++++ fs/xfs/xfs_iomap.c | 31 ++++++++++++++++++++++++------- fs/xfs/xfs_iomap.h | 5 +++-- 8 files changed, 118 insertions(+), 14 deletions(-) create mode 100644 fs/xfs/xfs_fsverity.c create mode 100644 fs/xfs/xfs_fsverity.h diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile index 399a207f2d0e..dd712c521862 100644 --- a/fs/xfs/Makefile +++ b/fs/xfs/Makefile @@ -150,6 +150,7 @@ xfs-$(CONFIG_XFS_POSIX_ACL) += xfs_acl.o xfs-$(CONFIG_SYSCTL) += xfs_sysctl.o xfs-$(CONFIG_COMPAT) += xfs_ioctl32.o xfs-$(CONFIG_EXPORTFS_BLOCK_OPS) += xfs_pnfs.o +xfs-$(CONFIG_FS_VERITY) += xfs_fsverity.o # notify failure ifeq ($(CONFIG_MEMORY_FAILURE),y) diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index d64defeda645..cc48f6e20e80 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -41,6 +41,8 @@ #include "xfs_inode_util.h" #include "xfs_rtgroup.h" #include "xfs_zone_alloc.h" +#include "xfs_fsverity.h" +#include struct kmem_cache *xfs_bmap_intent_cache; @@ -4402,6 +4404,10 @@ xfs_bmapi_convert_one_delalloc( * the extent. Just return the real extent at this offset. */ if (!isnullstartblock(bma.got.br_startblock)) { + if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) && + XFS_FSB_TO_B(mp, bma.got.br_startoff) >= + xfs_fsverity_metadata_offset(ip)) + flags |= IOMAP_F_FSVERITY; xfs_bmbt_to_iomap(ip, iomap, &bma.got, 0, flags, xfs_iomap_inode_sequence(ip, flags)); if (seq) @@ -4449,6 +4455,11 @@ xfs_bmapi_convert_one_delalloc( XFS_STATS_ADD(mp, xs_xstrat_bytes, XFS_FSB_TO_B(mp, bma.length)); XFS_STATS_INC(mp, xs_xstrat_quick); + if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION) && + XFS_FSB_TO_B(mp, bma.got.br_startoff) >= + xfs_fsverity_metadata_offset(ip)) + flags |= IOMAP_F_FSVERITY; + ASSERT(!isnullstartblock(bma.got.br_startblock)); xfs_bmbt_to_iomap(ip, iomap, &bma.got, 0, flags, xfs_iomap_inode_sequence(ip, flags)); diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index c30e688cfc9f..19640e4e3ed1 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -23,6 +23,7 @@ #include "xfs_ioend.h" #include "xfs_zone_alloc.h" #include "xfs_rtgroup.h" +#include "xfs_fsverity.h" struct xfs_writepage_ctx { struct iomap_writepage_ctx ctx; @@ -172,12 +173,16 @@ xfs_map_blocks( int retries = 0; int error = 0; unsigned int *seq; + unsigned int iomap_flags = 0; if (xfs_is_shutdown(mp)) return -EIO; XFS_ERRORTAG_DELAY(mp, XFS_ERRTAG_WB_DELAY_MS); + if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION)) + iomap_flags |= IOMAP_F_FSVERITY; + /* * COW fork blocks can overlap data fork blocks even if the blocks * aren't shared. COW I/O always takes precedent, so we must always @@ -265,7 +270,8 @@ xfs_map_blocks( isnullstartblock(imap.br_startblock)) goto allocate_blocks; - xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, 0, XFS_WPC(wpc)->data_seq); + xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, iomap_flags, + XFS_WPC(wpc)->data_seq); trace_xfs_map_blocks_found(ip, offset, count, whichfork, &imap); return 0; allocate_blocks: @@ -412,12 +418,16 @@ xfs_zoned_map_blocks( xfs_filblks_t count_fsb; struct xfs_bmbt_irec imap, del; struct xfs_iext_cursor icur; + u16 iomap_flags = 0; if (xfs_is_shutdown(mp)) return -EIO; XFS_ERRORTAG_DELAY(mp, XFS_ERRTAG_WB_DELAY_MS); + if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION)) + iomap_flags |= IOMAP_F_FSVERITY; + /* * All dirty data must be covered by delalloc extents. But truncate can * remove delalloc extents underneath us or reduce their size. @@ -441,7 +451,7 @@ xfs_zoned_map_blocks( imap.br_startblock = HOLESTARTBLOCK; imap.br_state = XFS_EXT_NORM; xfs_iunlock(ip, XFS_ILOCK_EXCL); - xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, 0, 0); + xfs_bmbt_to_iomap(ip, &wpc->iomap, &imap, 0, iomap_flags, 0); return 0; } end_fsb = min(end_fsb, imap.br_startoff + imap.br_blockcount); @@ -454,7 +464,7 @@ xfs_zoned_map_blocks( xfs_iunlock(ip, XFS_ILOCK_EXCL); xfs_iomap_set_anon_write(ip, &wpc->iomap, offset, - XFS_FSB_TO_B(mp, count_fsb)); + XFS_FSB_TO_B(mp, count_fsb), iomap_flags); trace_xfs_zoned_map_blocks(ip, offset, wpc->iomap.length); return 0; } @@ -507,6 +517,22 @@ static const struct iomap_writeback_ops xfs_zoned_writeback_ops = { .writeback_submit = xfs_zoned_writeback_submit, }; +static int +xfs_iomap_writepages( + struct xfs_inode *ip, + struct iomap_writepage_ctx *ctx) +{ + /* + * Writeback does not work for folios past EOF, let it know that + * I/O happens for fsverity metadata and this restriction need + * to be skipped + */ + if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION)) + ctx->iomap.flags |= IOMAP_F_FSVERITY; + + return iomap_writepages(ctx); +} + STATIC int xfs_vm_writepages( struct address_space *mapping, @@ -526,7 +552,7 @@ xfs_vm_writepages( }; int error; - error = iomap_writepages(&xc.ctx); + error = xfs_iomap_writepages(ip, &xc.ctx); if (xc.open_zone) xfs_open_zone_put(xc.open_zone); return error; @@ -539,7 +565,7 @@ xfs_vm_writepages( }, }; - return iomap_writepages(&wpc.ctx); + return xfs_iomap_writepages(ip, &wpc.ctx); } } diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c new file mode 100644 index 000000000000..e30021c22683 --- /dev/null +++ b/fs/xfs/xfs_fsverity.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Red Hat, Inc. + */ +#include "xfs_platform.h" +#include "xfs_fs.h" +#include "xfs_shared.h" +#include "xfs_format.h" +#include "xfs_log_format.h" +#include "xfs_trans_resv.h" +#include "xfs_mount.h" +#include "xfs_inode.h" +#include "xfs_fsverity.h" +#include +#include + +loff_t +xfs_fsverity_metadata_offset( + const struct xfs_inode *ip) +{ + return round_up(i_size_read(VFS_IC(ip)), XFS_FSVERITY_START_ALIGN); +} diff --git a/fs/xfs/xfs_fsverity.h b/fs/xfs/xfs_fsverity.h new file mode 100644 index 000000000000..c2ab5af89370 --- /dev/null +++ b/fs/xfs/xfs_fsverity.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Red Hat, Inc. + */ +#ifndef __XFS_FSVERITY_H__ +#define __XFS_FSVERITY_H__ + +#include "xfs_platform.h" + +#ifdef CONFIG_FS_VERITY +loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip); +#else +static inline loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip) +{ + WARN_ON_ONCE(1); + return ULLONG_MAX; +} +#endif /* CONFIG_FS_VERITY */ + +#endif /* __XFS_FSVERITY_H__ */ diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h index 1602027cd0aa..af5ced6997e8 100644 --- a/fs/xfs/xfs_inode.h +++ b/fs/xfs/xfs_inode.h @@ -419,6 +419,12 @@ static inline bool xfs_inode_can_sw_atomic_write(const struct xfs_inode *ip) */ #define XFS_IREMAPPING (1U << 15) +/* + * fs-verity's Merkle tree is under construction. The file is read-only, the + * only writes happening are for the fsverity metadata. + */ +#define XFS_VERITY_CONSTRUCTION (1U << 16) + /* All inode state flags related to inode reclaim. */ #define XFS_ALL_IRECLAIM_FLAGS (XFS_IRECLAIMABLE | \ XFS_IRECLAIM | \ diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index 7c6238fed61e..fa11d830dfc0 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -32,6 +32,8 @@ #include "xfs_rtbitmap.h" #include "xfs_icache.h" #include "xfs_zone_alloc.h" +#include "xfs_fsverity.h" +#include #define XFS_ALLOC_ALIGN(mp, off) \ (((off) >> mp->m_allocsize_log) << mp->m_allocsize_log) @@ -883,6 +885,9 @@ xfs_direct_write_iomap_begin( if (flags & IOMAP_ATOMIC) iomap_flags |= IOMAP_F_ATOMIC_BIO; + if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION)) + iomap_flags |= IOMAP_F_FSVERITY; + /* * COW writes may allocate delalloc space or convert unwritten COW * extents, so we need to make sure to take the lock exclusively here. @@ -1083,7 +1088,7 @@ xfs_zoned_direct_write_iomap_begin( return error; } - xfs_iomap_set_anon_write(ip, iomap, offset, length); + xfs_iomap_set_anon_write(ip, iomap, offset, length, 0); return 0; } @@ -1595,7 +1600,8 @@ xfs_zoned_buffered_write_iomap_begin( loff_t count, unsigned flags, struct iomap *iomap, - struct iomap *srcmap) + struct iomap *srcmap, + u16 iomap_flags) { struct iomap_iter *iter = container_of(iomap, struct iomap_iter, iomap); @@ -1605,7 +1611,6 @@ xfs_zoned_buffered_write_iomap_begin( struct xfs_mount *mp = ip->i_mount; xfs_fileoff_t offset_fsb = XFS_B_TO_FSBT(mp, offset); xfs_fileoff_t end_fsb = xfs_iomap_end_fsb(mp, offset, count); - u16 iomap_flags = IOMAP_F_SHARED; unsigned int lockmode = XFS_ILOCK_EXCL; xfs_filblks_t count_fsb; xfs_extlen_t indlen; @@ -1668,7 +1673,8 @@ xfs_zoned_buffered_write_iomap_begin( smap.br_startoff + smap.br_blockcount); xfs_trim_extent(&smap, offset_fsb, end_fsb - offset_fsb); - error = xfs_bmbt_to_iomap(ip, srcmap, &smap, flags, 0, + error = xfs_bmbt_to_iomap(ip, srcmap, &smap, flags, + iomap_flags, xfs_iomap_inode_sequence(ip, 0)); if (error) goto out_unlock; @@ -1678,6 +1684,8 @@ xfs_zoned_buffered_write_iomap_begin( if (!ip->i_cowfp) xfs_ifork_init_cow(ip); + iomap_flags |= IOMAP_F_SHARED; + if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &got)) got.br_startoff = end_fsb; if (got.br_startoff <= offset_fsb) { @@ -1809,9 +1817,12 @@ xfs_buffered_write_iomap_begin( if (xfs_is_shutdown(mp)) return -EIO; + if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION)) + iomap_flags |= IOMAP_F_FSVERITY; + if (xfs_is_zoned_inode(ip)) return xfs_zoned_buffered_write_iomap_begin(inode, offset, - count, flags, iomap, srcmap); + count, flags, iomap, srcmap, iomap_flags); /* we can't use delayed allocations when using extent size hints */ if (xfs_get_extsz_hint(ip)) @@ -2199,12 +2210,17 @@ xfs_read_iomap_begin( bool shared = false; unsigned int lockmode = XFS_ILOCK_SHARED; u64 seq; + unsigned int iomap_flags = 0; ASSERT(!(flags & (IOMAP_WRITE | IOMAP_ZERO))); if (xfs_is_shutdown(mp)) return -EIO; + if (fsverity_active(inode) && + (offset >= xfs_fsverity_metadata_offset(ip))) + iomap_flags |= IOMAP_F_FSVERITY; + error = xfs_ilock_for_iomap(ip, flags, &lockmode); if (error) return error; @@ -2218,8 +2234,9 @@ xfs_read_iomap_begin( if (error) return error; trace_xfs_iomap_found(ip, offset, length, XFS_DATA_FORK, &imap); - return xfs_bmbt_to_iomap(ip, iomap, &imap, flags, - shared ? IOMAP_F_SHARED : 0, seq); + iomap_flags |= shared ? IOMAP_F_SHARED : 0; + + return xfs_bmbt_to_iomap(ip, iomap, &imap, flags, iomap_flags, seq); } static DEFINE_IOMAP_ITER_NEXT(xfs_read_iomap_next, xfs_read_iomap_begin); diff --git a/fs/xfs/xfs_iomap.h b/fs/xfs/xfs_iomap.h index f2520a9b3a13..d3ca0f2afc9f 100644 --- a/fs/xfs/xfs_iomap.h +++ b/fs/xfs/xfs_iomap.h @@ -34,13 +34,14 @@ xfs_iomap_set_anon_write( struct xfs_inode *ip, struct iomap *iomap, loff_t offset, - loff_t length) + loff_t length, + u16 iomap_flags) { iomap->type = IOMAP_MAPPED; iomap->bdev = ip->i_mount->m_rtdev_targp->bt_bdev; iomap->offset = offset; iomap->length = length; - iomap->flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; + iomap->flags = iomap_flags | IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; if (bdev_has_integrity_csum(iomap->bdev)) iomap->flags |= IOMAP_F_INTEGRITY; } -- 2.54.0 Use read ioends for fsverity verification. Do not issue fsverity metadata I/O through the same workqueue due to risk of a deadlock by a filled workqueue. Pass fsverity_info from iomap context down to the ioend as hashtable lookups are expensive. Add a simple helper to check that this is not fsverity metadata but file data that needs verification. Signed-off-by: Andrey Albershteyn --- fs/xfs/xfs_aops.c | 13 +++++---- fs/xfs/xfs_file.c | 3 +- fs/xfs/xfs_fsverity.c | 11 ++++++++ fs/xfs/xfs_fsverity.h | 14 ++++++++++ fs/xfs/xfs_ioend.c | 65 +++++++++++++++++++++++++++++++++++++++---- fs/xfs/xfs_ioend.h | 4 ++- fs/xfs/xfs_super.c | 15 ++++++++++ 7 files changed, 112 insertions(+), 13 deletions(-) diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index 19640e4e3ed1..a36f840884b4 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -24,6 +24,7 @@ #include "xfs_zone_alloc.h" #include "xfs_rtgroup.h" #include "xfs_fsverity.h" +#include struct xfs_writepage_ctx { struct iomap_writepage_ctx ctx; @@ -611,7 +612,7 @@ xfs_bio_submit_read( { xfs_ioend_submit_read(iter->inode, ctx->read_ctx, ctx->read_ctx_file_offset, - iomap_ioend_flags(&iter->iomap)); + iomap_ioend_flags(&iter->iomap), ctx->vi); ctx->read_ctx = NULL; } @@ -623,11 +624,13 @@ static const struct iomap_read_ops xfs_iomap_read_ops = { static inline const struct iomap_read_ops * xfs_get_iomap_read_ops( - const struct address_space *mapping) + const struct address_space *mapping, + loff_t pos) { struct xfs_inode *ip = XFS_I(mapping->host); - if (bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev)) + if (bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev) || + xfs_fsverity_is_file_data(ip, pos)) return &xfs_iomap_read_ops; return &iomap_bio_read_ops; } @@ -639,7 +642,7 @@ xfs_vm_read_folio( { struct iomap_read_folio_ctx ctx = { .cur_folio = folio }; - ctx.ops = xfs_get_iomap_read_ops(folio->mapping); + ctx.ops = xfs_get_iomap_read_ops(folio->mapping, folio_pos(folio)); iomap_read_folio(&xfs_read_iomap_ops, &ctx, NULL); return 0; } @@ -650,7 +653,7 @@ xfs_vm_readahead( { struct iomap_read_folio_ctx ctx = { .rac = rac }; - ctx.ops = xfs_get_iomap_read_ops(rac->mapping), + ctx.ops = xfs_get_iomap_read_ops(rac->mapping, readahead_pos(rac)); iomap_readahead(&xfs_read_iomap_ops, &ctx, NULL); } diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 07abf6e8783f..578ca6fb8292 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -225,7 +225,8 @@ xfs_dio_read_bounce_submit_io( loff_t file_offset) { xfs_ioend_submit_read(iter->inode, bio, file_offset, - iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT); + iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT, + NULL); } static const struct iomap_dio_ops xfs_dio_read_bounce_ops = { diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c index e30021c22683..525855fdd30d 100644 --- a/fs/xfs/xfs_fsverity.c +++ b/fs/xfs/xfs_fsverity.c @@ -14,9 +14,20 @@ #include #include +struct kmem_cache *xfs_fsverity_ioend_cache; + loff_t xfs_fsverity_metadata_offset( const struct xfs_inode *ip) { return round_up(i_size_read(VFS_IC(ip)), XFS_FSVERITY_START_ALIGN); } + +bool +xfs_fsverity_is_file_data( + const struct xfs_inode *ip, + loff_t offset) +{ + return fsverity_active(VFS_IC(ip)) && + offset < xfs_fsverity_metadata_offset(ip); +} diff --git a/fs/xfs/xfs_fsverity.h b/fs/xfs/xfs_fsverity.h index c2ab5af89370..836e3a4eb194 100644 --- a/fs/xfs/xfs_fsverity.h +++ b/fs/xfs/xfs_fsverity.h @@ -6,15 +6,29 @@ #define __XFS_FSVERITY_H__ #include "xfs_platform.h" +#include #ifdef CONFIG_FS_VERITY loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip); +bool xfs_fsverity_is_file_data(const struct xfs_inode *ip, loff_t offset); #else static inline loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip) { WARN_ON_ONCE(1); return ULLONG_MAX; } +static inline bool xfs_fsverity_is_file_data(const struct xfs_inode *ip, + loff_t offset) +{ + return false; +} #endif /* CONFIG_FS_VERITY */ +struct xfs_fsverity_ioend { + struct iomap_ioend *ioend; + struct work_struct work; +}; + +extern struct kmem_cache *xfs_fsverity_ioend_cache; + #endif /* __XFS_FSVERITY_H__ */ diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c index e70be5b86f0b..2f46855b052f 100644 --- a/fs/xfs/xfs_ioend.c +++ b/fs/xfs/xfs_ioend.c @@ -18,7 +18,26 @@ #include "xfs_ioend.h" #include "xfs_error.h" #include "xfs_errortag.h" +#include "xfs_fsverity.h" #include +#include + +static void +xfs_end_fsverity_io_read( + struct work_struct *work) +{ + struct xfs_fsverity_ioend *fsv_ioend = + container_of(work, struct xfs_fsverity_ioend, work); + struct iomap_ioend *ioend = fsv_ioend->ioend; + struct bio *bio = &ioend->io_bio; + + kmem_cache_free(xfs_fsverity_ioend_cache, fsv_ioend); + + if (!bio->bi_status) + fsverity_verify_bio(ioend->io_vi, bio); + + iomap_finish_ioends(ioend, blk_status_to_errno(bio->bi_status)); +} static void xfs_dio_bounce_end_io( @@ -93,12 +112,14 @@ xfs_read_bounce_and_resubmit( static void xfs_end_io_read( - struct bio *bio) + struct bio *bio) { - struct iomap_ioend *ioend = iomap_ioend_from_bio(bio); - struct xfs_inode *ip = XFS_I(ioend->io_inode); - struct xfs_mount *mp = ip->i_mount; - int error = blk_status_to_errno(bio->bi_status); + struct iomap_ioend *ioend = iomap_ioend_from_bio(bio); + struct xfs_inode *ip = XFS_I(ioend->io_inode); + struct xfs_mount *mp = ip->i_mount; + int error = + blk_status_to_errno(bio->bi_status); + struct xfs_fsverity_ioend *fsv_ioend; if (!error && (ioend->io_flags & IOMAP_IOEND_INTEGRITY)) { error = iomap_ioend_integrity_verify(ioend); @@ -117,6 +138,36 @@ xfs_end_io_read( } } + /* + * If we have fsverity and block device integrity attached to this bio, + * we need to run fsverity verification of data folios from a separate + * fsverity workqueue. This is necessary to avoid deadlocking due to + * fsverity issuing more reads of fsverity metadata which would be + * processed by the same worker in the BIO completion workqueue. + * + * Without block device integrity, fsverity metadata IO will not use + * ioends for completion. + */ + if (IS_ENABLED(CONFIG_FS_VERITY) && !error && ioend->io_vi && + xfs_fsverity_is_file_data(ip, ioend->io_offset)) { + if (ioend->io_flags & IOMAP_IOEND_INTEGRITY) { + fsv_ioend = kmem_cache_zalloc(xfs_fsverity_ioend_cache, + GFP_KERNEL); + if (!fsv_ioend) { + iomap_finish_ioends(ioend, -ENOMEM); + return; + } + fsv_ioend->ioend = ioend; + INIT_WORK(&fsv_ioend->work, xfs_end_fsverity_io_read); + + fsverity_enqueue_verify_work(&fsv_ioend->work); + return; + } + + fsverity_verify_bio(ioend->io_vi, &ioend->io_bio); + error = blk_status_to_errno(ioend->io_bio.bi_status); + } + iomap_finish_ioends(ioend, error); } @@ -125,13 +176,15 @@ xfs_ioend_submit_read( struct inode *inode, struct bio *bio, loff_t file_offset, - u16 ioend_flags) + u16 ioend_flags, + struct fsverity_info *vi) { struct xfs_inode *ip = XFS_I(inode); struct xfs_mount *mp = ip->i_mount; struct iomap_ioend *ioend; ioend = iomap_init_ioend(inode, bio, file_offset, ioend_flags); + ioend->io_vi = vi; if ((ioend_flags & IOMAP_IOEND_DIRECT) && READ_ONCE(mp->m_read_bounce) == XFS_READ_BOUNCE_ALWAYS) { iomap_bounce_read(ioend, bdev_logical_block_size(bio->bi_bdev), diff --git a/fs/xfs/xfs_ioend.h b/fs/xfs/xfs_ioend.h index 7c2a1ea3e6ed..992c248a693a 100644 --- a/fs/xfs/xfs_ioend.h +++ b/fs/xfs/xfs_ioend.h @@ -2,6 +2,8 @@ #ifndef __XFS_IOEND_H #define __XFS_IOEND_H +#include + /* * Fast and loose check if this write could update the on-disk inode size. */ @@ -13,6 +15,6 @@ static inline bool xfs_ioend_is_append(struct iomap_ioend *ioend) void xfs_end_bio(struct bio *bio); void xfs_ioend_submit_read(struct inode *inode, struct bio *bio, - loff_t file_offset, u16 ioend_flags); + loff_t file_offset, u16 ioend_flags, struct fsverity_info *vi); #endif /* __XFS_IOEND_H */ diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index fce1d2905c94..aca50130625e 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -2516,8 +2516,20 @@ xfs_init_caches(void) if (!xfs_parent_args_cache) goto out_destroy_xmi_cache; +#ifdef CONFIG_FS_VERITY + xfs_fsverity_ioend_cache = kmem_cache_create("xfs_fsverity_ioend", + sizeof(struct xfs_fsverity_ioend), + 0, 0, NULL); + if (!xfs_fsverity_ioend_cache) + goto out_destroy_fsverity_ioend_cache; +#endif + return 0; +#ifdef CONFIG_FS_VERITY + out_destroy_fsverity_ioend_cache: + kmem_cache_destroy(xfs_fsverity_ioend_cache); +#endif out_destroy_xmi_cache: kmem_cache_destroy(xfs_xmi_cache); out_destroy_xmd_cache: @@ -2580,6 +2592,9 @@ xfs_destroy_caches(void) * destroy caches. */ rcu_barrier(); +#ifdef CONFIG_FS_VERITY + kmem_cache_destroy(xfs_fsverity_ioend_cache); +#endif kmem_cache_destroy(xfs_parent_args_cache); kmem_cache_destroy(xfs_xmd_cache); kmem_cache_destroy(xfs_xmi_cache); -- 2.54.0 The __xfs_bunmapi() is used to remove all extents past EOF. Add XFS_BMAPI_UNWRITTEN to remove only unwritten extents, while leaving written ones in place. This will be used in following patch to clean up unwritten extents on fsverity inodes. Signed-off-by: Andrey Albershteyn --- fs/xfs/libxfs/xfs_bmap.c | 6 +++++- fs/xfs/libxfs/xfs_bmap.h | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index cc48f6e20e80..7ba76017ae56 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -5280,6 +5280,10 @@ __xfs_bunmapi( if (del.br_startoff + del.br_blockcount > end + 1) del.br_blockcount = end + 1 - del.br_startoff; + if ((flags & XFS_BMAPI_UNWRITTEN) && + del.br_state != XFS_EXT_UNWRITTEN) + goto skip; + if (!isrt || (flags & XFS_BMAPI_REMAP)) goto delete; @@ -5414,7 +5418,7 @@ __xfs_bunmapi( if (error) goto error0; } - +skip: end = del.br_startoff - 1; nodelete: /* diff --git a/fs/xfs/libxfs/xfs_bmap.h b/fs/xfs/libxfs/xfs_bmap.h index d5f2729305fa..0f36431d9936 100644 --- a/fs/xfs/libxfs/xfs_bmap.h +++ b/fs/xfs/libxfs/xfs_bmap.h @@ -90,6 +90,9 @@ struct xfs_bmalloca { /* Try to align allocations to the extent size hint */ #define XFS_BMAPI_EXTSZALIGN (1u << 11) +/* Process unwritten extents only. Used for unmapping */ +#define XFS_BMAPI_UNWRITTEN (1u << 12) + #define XFS_BMAPI_FLAGS \ { XFS_BMAPI_ENTIRE, "ENTIRE" }, \ { XFS_BMAPI_METADATA, "METADATA" }, \ @@ -102,7 +105,8 @@ struct xfs_bmalloca { { XFS_BMAPI_COWFORK, "COWFORK" }, \ { XFS_BMAPI_NODISCARD, "NODISCARD" }, \ { XFS_BMAPI_NORMAP, "NORMAP" },\ - { XFS_BMAPI_EXTSZALIGN, "EXTSZALIGN" } + { XFS_BMAPI_EXTSZALIGN, "EXTSZALIGN" }, \ + { XFS_BMAPI_UNWRITTEN, "UNWRITTEN" } static inline int xfs_bmapi_aflag(int w) -- 2.54.0 xfs_free_eofblocks() removes all extents past EOF unless the XFS_DIFLAG_PREALLOC or XFS_DIFLAG_APPEND flags are set. This is undesired for fsverity as it stores metadata beyond EOF. However, while merkle tree is being built, delayed preallocation and unwritten extents are used. After metadata construction is done, fsverity inodes becomes read-only and won't be changed anymore, none of these unwritten extents or preallocations in post EOF region will be used. Let xfs_free_eofblocks() be called on fsverity inode as usual to remove anything which is not written extent. However, inodes which are undergoing merkle tree construction need to be skipped in case reclaim takes place. Signed-off-by: Andrey Albershteyn --- fs/xfs/xfs_bmap_util.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index 268d159339d0..7fd951992557 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -31,6 +31,7 @@ #include "xfs_rtbitmap.h" #include "xfs_rtgroup.h" #include "xfs_zone_alloc.h" +#include /* Kernel only BMAP related definitions and functions */ @@ -553,6 +554,13 @@ xfs_can_free_eofblocks( if (last_fsb <= end_fsb) return false; + /* + * Don't clean fsverity inodes which have merkle tree being built, the + * merkle tree is written beyond EOF + */ + if (xfs_iflags_test(ip, XFS_VERITY_CONSTRUCTION)) + return false; + /* * Check if there is an post-EOF extent to free. If there are any * delalloc blocks attached to the inode (data fork delalloc @@ -579,6 +587,9 @@ xfs_free_eofblocks( struct xfs_trans *tp; struct xfs_mount *mp = ip->i_mount; int error; + int bmapi_flags = XFS_BMAPI_NODISCARD; + bool has_verity = + ip->i_diflags2 & XFS_DIFLAG2_VERITY; /* Attach the dquots to the inode up front. */ error = xfs_qm_dqattach(ip); @@ -593,15 +604,20 @@ xfs_free_eofblocks( * * Note that this means we also leave speculative preallocations in * place for preallocated files. + * + * Clean up delalloc reservations for fsverity too as those won't be + * used */ - if (ip->i_diflags & (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND)) { + if (ip->i_diflags & (XFS_DIFLAG_PREALLOC | XFS_DIFLAG_APPEND) || + has_verity) { if (ip->i_delayed_blks) { xfs_bmap_punch_delalloc_range(ip, XFS_DATA_FORK, round_up(XFS_ISIZE(ip), mp->m_sb.sb_blocksize), LLONG_MAX, NULL); } xfs_inode_clear_eofblocks_tag(ip); - return 0; + if (!has_verity) + return 0; } error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp); @@ -613,6 +629,9 @@ xfs_free_eofblocks( xfs_ilock(ip, XFS_ILOCK_EXCL); xfs_trans_ijoin(tp, ip, 0); + if (has_verity) + bmapi_flags |= XFS_BMAPI_UNWRITTEN; + /* * Do not update the on-disk file size. If we update the on-disk file * size and then the system crashes before the contents of the file are @@ -620,7 +639,7 @@ xfs_free_eofblocks( * bug). */ error = xfs_itruncate_extents_flags(&tp, ip, XFS_DATA_FORK, - XFS_ISIZE(ip), XFS_BMAPI_NODISCARD); + XFS_ISIZE(ip), bmapi_flags); if (error) goto err_cancel; -- 2.54.0 Add integration with fs-verity. XFS stores fs-verity descriptor and Merkle tree in the inode data fork at first block aligned to 64k past EOF. The Merkle tree reading/writing is done through iomap interface. The data itself is read to the inode's page cache. When XFS reads from this region iomap doesn't call into fsverity to verify it against Merkle tree. For data, verification is done at ioend completion in a workqueue. When fs-verity is enabled on an inode, the XFS_IVERITY_CONSTRUCTION flag is set meaning that the Merkle tree is being build. The initialization ends with storing of verity descriptor and setting inode on-disk flag (XFS_DIFLAG2_VERITY). Lastly, the XFS_IVERITY_CONSTRUCTION is dropped and I_VERITY is set on inode. The descriptor is stored in a new block aligned to 64k after the last Merkle tree block. The size of the descriptor is stored at the end of the last descriptor block (descriptor can be multiple blocks). Pro-actively remove any unwritten extents as we use last extent to locate descriptor. Signed-off-by: Andrey Albershteyn Reviewed-by: Christoph Hellwig --- fs/xfs/xfs_fsverity.c | 363 +++++++++++++++++++++++++++++++++++++++++- fs/xfs/xfs_fsverity.h | 2 + fs/xfs/xfs_message.c | 4 + fs/xfs/xfs_message.h | 1 + fs/xfs/xfs_mount.h | 2 + fs/xfs/xfs_super.c | 7 + 6 files changed, 378 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c index 525855fdd30d..e62c9a5d0e1d 100644 --- a/fs/xfs/xfs_fsverity.c +++ b/fs/xfs/xfs_fsverity.c @@ -6,13 +6,22 @@ #include "xfs_fs.h" #include "xfs_shared.h" #include "xfs_format.h" -#include "xfs_log_format.h" #include "xfs_trans_resv.h" #include "xfs_mount.h" #include "xfs_inode.h" +#include "xfs_log_format.h" +#include "xfs_trans.h" +#include "xfs_trace.h" +#include "xfs_quota.h" #include "xfs_fsverity.h" +#include "xfs_iomap.h" +#include "xfs_error.h" +#include "xfs_health.h" +#include "xfs_bmap_util.h" +#include "xfs_icache.h" #include #include +#include struct kmem_cache *xfs_fsverity_ioend_cache; @@ -31,3 +40,355 @@ xfs_fsverity_is_file_data( return fsverity_active(VFS_IC(ip)) && offset < xfs_fsverity_metadata_offset(ip); } + +/* + * Retrieve the verity descriptor. + */ +static int +xfs_fsverity_get_descriptor( + struct inode *inode, + void *buf, + size_t buf_size) +{ + struct xfs_inode *ip = XFS_I(inode); + struct xfs_mount *mp = ip->i_mount; + __be32 d_desc_size; + u32 desc_size; + u64 desc_size_pos; + int error; + u64 desc_pos; + struct xfs_bmbt_irec rec; + int is_empty; + uint32_t blocksize = i_blocksize(VFS_I(ip)); + xfs_fileoff_t last_block_offset; + + ASSERT(inode->i_flags & S_VERITY); + xfs_ilock(ip, XFS_ILOCK_SHARED); + error = xfs_bmap_last_extent(NULL, ip, XFS_DATA_FORK, &rec, &is_empty); + xfs_iunlock(ip, XFS_ILOCK_SHARED); + if (error) + return error; + + if (is_empty) + return -ENODATA; + + last_block_offset = + XFS_FSB_TO_B(mp, rec.br_startoff + rec.br_blockcount); + if (last_block_offset <= xfs_fsverity_metadata_offset(ip)) + return -ENODATA; + + desc_size_pos = last_block_offset - sizeof(__be32); + error = fsverity_pagecache_read(inode, (char *)&d_desc_size, + sizeof(d_desc_size), desc_size_pos); + if (error) + return error; + + desc_size = be32_to_cpu(d_desc_size); + if (XFS_IS_CORRUPT(mp, desc_size > FS_VERITY_MAX_DESCRIPTOR_SIZE)) + return -ERANGE; + if (XFS_IS_CORRUPT(mp, desc_size > desc_size_pos)) + return -ERANGE; + + if (!buf_size) + return desc_size; + + if (XFS_IS_CORRUPT(mp, desc_size > buf_size)) + return -ERANGE; + + desc_pos = round_down(desc_size_pos - desc_size, blocksize); + if (desc_pos < xfs_fsverity_metadata_offset(ip)) + return -ERANGE; + + error = fsverity_pagecache_read(inode, buf, desc_size, desc_pos); + if (error) + return error; + + return desc_size; +} + +static int +xfs_fsverity_write_descriptor( + struct file *file, + const void *desc, + u32 desc_size, + u64 merkle_tree_size) +{ + int error; + struct inode *inode = file_inode(file); + struct xfs_inode *ip = XFS_I(inode); + unsigned int blksize = ip->i_mount->m_sb.sb_blocksize; + u64 tree_last_block = + xfs_fsverity_metadata_offset(ip) + merkle_tree_size; + u64 desc_pos = + round_up(tree_last_block, XFS_FSVERITY_START_ALIGN); + u64 desc_end = desc_pos + desc_size; + __be32 desc_size_disk = cpu_to_be32(desc_size); + u64 desc_size_pos = + round_up(desc_end + sizeof(desc_size_disk), blksize) - + sizeof(desc_size_disk); + + error = iomap_fsverity_write(file, desc_size_pos, sizeof(__be32), + (const void *)&desc_size_disk, + &xfs_buffered_write_iomap_ops, + &xfs_iomap_write_ops); + if (error) + return error; + + return iomap_fsverity_write(file, desc_pos, desc_size, desc, + &xfs_buffered_write_iomap_ops, + &xfs_iomap_write_ops); +} + +/* + * Try to remove all the fsverity metadata after a failed enablement. + */ +static int +xfs_fsverity_delete_metadata( + struct xfs_inode *ip) +{ + struct xfs_trans *tp; + struct xfs_mount *mp = ip->i_mount; + int error; + + xfs_ilock(ip, XFS_MMAPLOCK_EXCL); + error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp); + if (error) { + xfs_iunlock(ip, XFS_MMAPLOCK_EXCL); + return error; + } + + xfs_ilock(ip, XFS_ILOCK_EXCL); + xfs_trans_ijoin(tp, ip, 0); + + truncate_inode_pages(VFS_I(ip)->i_mapping, XFS_ISIZE(ip)); + + /* + * We remove post EOF data, no need to update i_size as fsverity + * didn't move i_size in the first place + */ + error = xfs_itruncate_extents(&tp, ip, XFS_DATA_FORK, XFS_ISIZE(ip)); + if (error) + goto err_cancel; + + error = xfs_trans_commit(tp); + xfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_ILOCK_EXCL); + return error; +err_cancel: + xfs_trans_cancel(tp); + xfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_ILOCK_EXCL); + return error; +} + + +/* + * Prepare to enable fsverity by clearing old metadata. + */ +static int +xfs_fsverity_begin_enable( + struct file *filp) +{ + struct inode *inode = file_inode(filp); + struct xfs_inode *ip = XFS_I(inode); + int error; + + xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL); + + if (IS_DAX(inode) || ip->i_diflags2 & XFS_DIFLAG2_DAX) + return -EINVAL; + + if (inode->i_size > XFS_FSVERITY_LARGEST_FILE) + return -EFBIG; + + /* + * Flush pagecache before building Merkle tree. Inode is locked and no + * further writes will happen to the file except fsverity metadata + */ + error = filemap_write_and_wait(inode->i_mapping); + if (error) + return error; + + if (xfs_iflags_test_and_set(ip, XFS_VERITY_CONSTRUCTION)) + return -EBUSY; + + error = xfs_qm_dqattach(ip); + if (error) + goto out_clear; + + error = xfs_fsverity_delete_metadata(ip); + if (error) + goto out_clear; + return error; +out_clear: + xfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION); + return error; +} + +/* + * Complete (or fail) the process of enabling fsverity. + */ +static int +xfs_fsverity_end_enable( + struct file *file, + const void *desc, + size_t desc_size, + u64 merkle_tree_size) +{ + struct inode *inode = file_inode(file); + struct xfs_inode *ip = XFS_I(inode); + struct xfs_mount *mp = ip->i_mount; + struct xfs_trans *tp; + int error = 0; + loff_t range_start = xfs_fsverity_metadata_offset(ip); + + xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL); + + /* fs-verity failed, just cleanup */ + if (desc == NULL) { + error = xfs_fsverity_delete_metadata(ip); + goto out; + } + + error = xfs_fsverity_write_descriptor(file, desc, desc_size, + merkle_tree_size); + if (error) + goto out; + + /* + * Wait for Merkle tree get written to disk before setting on-disk inode + * flag and clearing XFS_VERITY_CONSTRUCTION + */ + error = filemap_write_and_wait_range(inode->i_mapping, range_start, + LLONG_MAX); + if (error) + goto out; + + /* + * Set fsverity inode flag + */ + error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_ichange, + 0, 0, false, &tp); + if (error) + goto out; + + /* + * Ensure that we've persisted the verity information before we enable + * it on the inode and tell the caller we have sealed the inode. + */ + ip->i_diflags2 |= XFS_DIFLAG2_VERITY; + + xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); + xfs_trans_set_sync(tp); + + error = xfs_trans_commit(tp); + xfs_iunlock(ip, XFS_ILOCK_EXCL); + if (error) + goto out; + + inode_set_flags(inode, S_VERITY, S_VERITY); + + /* + * Proactively remove unwritten extents left by COW preallocations and + * write preallocation in the merkle tree holes and past descriptor, + * and any delayed preallocations + */ + error = xfs_free_eofblocks(ip); +out: + if (error) { + int error2; + + error2 = xfs_fsverity_delete_metadata(ip); + if (error2) + xfs_alert(ip->i_mount, +"ino 0x%llx failed to clean up new fsverity metadata, err %d", + I_INO(ip), error2); + } + + xfs_iflags_clear(ip, XFS_VERITY_CONSTRUCTION); + return error; +} + +/* + * Retrieve a merkle tree block. + */ +static struct page * +xfs_fsverity_read_merkle( + struct inode *inode, + pgoff_t index) +{ + index += xfs_fsverity_metadata_offset(XFS_I(inode)) >> PAGE_SHIFT; + + return generic_read_merkle_tree_page(inode, index); +} + +/* + * Retrieve a merkle tree block. + */ +static void +xfs_fsverity_readahead_merkle_tree( + struct inode *inode, + pgoff_t index, + unsigned long nr_pages) +{ + index += xfs_fsverity_metadata_offset(XFS_I(inode)) >> PAGE_SHIFT; + + generic_readahead_merkle_tree(inode, index, nr_pages); +} + +/* + * Write a merkle tree block. + */ +static int +xfs_fsverity_write_merkle( + struct file *file, + const void *buf, + u64 pos, + unsigned int size, + const u8 *zero_digest, + unsigned int digest_size) +{ + struct inode *inode = file_inode(file); + struct xfs_inode *ip = XFS_I(inode); + loff_t position = pos + + xfs_fsverity_metadata_offset(ip); + + if (position + size > inode->i_sb->s_maxbytes) + return -EFBIG; + + /* + * If this is a block full of hashes of zeroed blocks, don't bother + * storing the block. We can synthesize them later. + * + * However, do this only in case Merkle tree block == fs block size. + * Iomap synthesizes these blocks based on holes in the merkle tree. We + * won't be able to tell if something need to be synthesizes for the + * range in the fs block. For example, for 4k filesystem block + * + * [ 1k | zero hashes | zero hashes | 1k ] + * + * Iomap won't know about these empty blocks. + */ + if (size == ip->i_mount->m_sb.sb_blocksize && + /* + * First digest is zero_digest + */ + memcmp(buf, zero_digest, digest_size) == 0 && + /* + * Every digest is same as previous, thus all are + * zero_digest + */ + memcmp(buf + digest_size, buf, size - digest_size) == 0) + return 0; + + return iomap_fsverity_write(file, position, size, buf, + &xfs_buffered_write_iomap_ops, + &xfs_iomap_write_ops); +} + +const struct fsverity_operations xfs_fsverity_ops = { + .begin_enable_verity = xfs_fsverity_begin_enable, + .end_enable_verity = xfs_fsverity_end_enable, + .get_verity_descriptor = xfs_fsverity_get_descriptor, + .read_merkle_tree_page = xfs_fsverity_read_merkle, + .readahead_merkle_tree = xfs_fsverity_readahead_merkle_tree, + .write_merkle_tree_block = xfs_fsverity_write_merkle, +}; diff --git a/fs/xfs/xfs_fsverity.h b/fs/xfs/xfs_fsverity.h index 836e3a4eb194..9290dbf576f8 100644 --- a/fs/xfs/xfs_fsverity.h +++ b/fs/xfs/xfs_fsverity.h @@ -7,8 +7,10 @@ #include "xfs_platform.h" #include +#include #ifdef CONFIG_FS_VERITY +extern const struct fsverity_operations xfs_fsverity_ops; loff_t xfs_fsverity_metadata_offset(const struct xfs_inode *ip); bool xfs_fsverity_is_file_data(const struct xfs_inode *ip, loff_t offset); #else diff --git a/fs/xfs/xfs_message.c b/fs/xfs/xfs_message.c index 0243e509a468..44b13d46049c 100644 --- a/fs/xfs/xfs_message.c +++ b/fs/xfs/xfs_message.c @@ -149,6 +149,10 @@ xfs_warn_experimental( .opstate = XFS_OPSTATE_WARNED_LARP, .name = "logged extended attributes", }, + [XFS_EXPERIMENTAL_FSVERITY] = { + .opstate = XFS_OPSTATE_WARNED_FSVERITY, + .name = "fsverity", + }, }; ASSERT(feat >= 0 && feat < XFS_EXPERIMENTAL_MAX); BUILD_BUG_ON(ARRAY_SIZE(features) != XFS_EXPERIMENTAL_MAX); diff --git a/fs/xfs/xfs_message.h b/fs/xfs/xfs_message.h index 811b885f41c3..364fcc7e42b0 100644 --- a/fs/xfs/xfs_message.h +++ b/fs/xfs/xfs_message.h @@ -93,6 +93,7 @@ void xfs_buf_alert_ratelimited(struct xfs_buf *bp, const char *rlmsg, enum xfs_experimental_feat { XFS_EXPERIMENTAL_SHRINK, XFS_EXPERIMENTAL_LARP, + XFS_EXPERIMENTAL_FSVERITY, XFS_EXPERIMENTAL_MAX, }; diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h index 60cb54c1a5ec..a3074f060928 100644 --- a/fs/xfs/xfs_mount.h +++ b/fs/xfs/xfs_mount.h @@ -599,6 +599,8 @@ __XFS_HAS_FEAT(nouuid, NOUUID) #define XFS_OPSTATE_RESUMING_QUOTAON 18 /* (Zoned) GC is in progress */ #define XFS_OPSTATE_ZONEGC_RUNNING 20 +/* Kernel has logged a warning about fsverity support */ +#define XFS_OPSTATE_WARNED_FSVERITY 21 #define __XFS_IS_OPSTATE(name, NAME) \ static inline bool xfs_is_ ## name (struct xfs_mount *mp) \ diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index aca50130625e..c5075474cd8c 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -30,6 +30,7 @@ #include "xfs_filestream.h" #include "xfs_quota.h" #include "xfs_sysfs.h" +#include "xfs_fsverity.h" #include "xfs_ondisk.h" #include "xfs_rmap_item.h" #include "xfs_refcount_item.h" @@ -1736,6 +1737,9 @@ xfs_fs_fill_super( sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP | QTYPE_MASK_PRJ; #endif sb->s_op = &xfs_super_operations; +#ifdef CONFIG_FS_VERITY + sb->s_vop = &xfs_fsverity_ops; +#endif /* * Delay mount work if the debug hook is set. This is debug @@ -1988,6 +1992,9 @@ xfs_fs_fill_super( if (error) goto out_filestream_unmount; + if (xfs_has_verity(mp)) + xfs_warn_experimental(mp, XFS_EXPERIMENTAL_FSVERITY); + root = igrab(VFS_I(mp->m_rootip)); if (!root) { error = -ENOENT; -- 2.54.0 fs-verity will read and attach metadata (not the tree itself) from a disk for those inodes which already have fs-verity enabled. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Andrey Albershteyn --- fs/xfs/xfs_file.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 578ca6fb8292..e13390539f04 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -1794,11 +1794,18 @@ xfs_file_open( struct inode *inode, struct file *file) { + int error; + if (xfs_is_shutdown(XFS_M(inode->i_sb))) return -EIO; file->f_mode |= FMODE_NOWAIT | FMODE_CAN_ODIRECT; if (xfs_get_atomic_write_min(XFS_I(inode)) > 0) file->f_mode |= FMODE_CAN_ATOMIC_WRITE; + + error = fsverity_file_open(inode, file); + if (error) + return error; + return generic_file_open(inode, file); } -- 2.54.0 Add fs-verity ioctls to enable, dump metadata (descriptor and Merkle tree pages) and obtain file's digest. [djwong: remove unnecessary casting] Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Andrey Albershteyn --- fs/xfs/xfs_ioctl.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 96ca3e480cb9..ac4c36f381b8 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -49,6 +49,7 @@ #include #include +#include /* Return 0 on success or positive error */ int @@ -1466,6 +1467,19 @@ xfs_file_ioctl( case XFS_IOC_VERIFY_MEDIA: return xfs_ioc_verify_media(filp, arg); + case FS_IOC_ENABLE_VERITY: + if (!xfs_has_verity(mp)) + return -EOPNOTSUPP; + return fsverity_ioctl_enable(filp, arg); + case FS_IOC_MEASURE_VERITY: + if (!xfs_has_verity(mp)) + return -EOPNOTSUPP; + return fsverity_ioctl_measure(filp, arg); + case FS_IOC_READ_VERITY_METADATA: + if (!xfs_has_verity(mp)) + return -EOPNOTSUPP; + return fsverity_ioctl_read_metadata(filp, arg); + default: return -ENOTTY; } -- 2.54.0 From: "Darrick J. Wong" Advertise that this filesystem supports fsverity. Signed-off-by: Darrick J. Wong Reviewed-by: Andrey Albershteyn Reviewed-by: Christoph Hellwig Signed-off-by: Andrey Albershteyn --- fs/xfs/libxfs/xfs_fs.h | 1 + fs/xfs/libxfs/xfs_sb.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/fs/xfs/libxfs/xfs_fs.h b/fs/xfs/libxfs/xfs_fs.h index 185f09f327c0..c80133784419 100644 --- a/fs/xfs/libxfs/xfs_fs.h +++ b/fs/xfs/libxfs/xfs_fs.h @@ -250,6 +250,7 @@ typedef struct xfs_fsop_resblks { #define XFS_FSOP_GEOM_FLAGS_PARENT (1 << 25) /* linux parent pointers */ #define XFS_FSOP_GEOM_FLAGS_METADIR (1 << 26) /* metadata directories */ #define XFS_FSOP_GEOM_FLAGS_ZONED (1 << 27) /* zoned rt device */ +#define XFS_FSOP_GEOM_FLAGS_VERITY (1 << 28) /* fs-verity */ /* * Minimum and maximum sizes need for growth checks. diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c index 985c7c6a09f2..fd06c3d6cc11 100644 --- a/fs/xfs/libxfs/xfs_sb.c +++ b/fs/xfs/libxfs/xfs_sb.c @@ -1587,6 +1587,8 @@ xfs_fs_geometry( geo->flags |= XFS_FSOP_GEOM_FLAGS_METADIR; if (xfs_has_zoned(mp)) geo->flags |= XFS_FSOP_GEOM_FLAGS_ZONED; + if (xfs_has_verity(mp)) + geo->flags |= XFS_FSOP_GEOM_FLAGS_VERITY; geo->rtsectsize = sbp->sb_blocksize; geo->dirblocksize = xfs_dir2_dirblock_bytes(sbp); -- 2.54.0 From: "Darrick J. Wong" If an inode has the incore verity iflag set, make sure that we can actually activate fsverity on that inode. If activation fails due to a fsverity metadata validation error, clear the flag. The usage model for fsverity requires that any program that cares about verity state is required to call statx/getflags to check that the flag is set after opening the file, so clearing the flag will not compromise that model. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Andrey Albershteyn --- fs/xfs/scrub/common.c | 55 +++++++++++++++++++++++++++++++++++++ fs/xfs/scrub/common.h | 2 ++ fs/xfs/scrub/inode.c | 7 +++++ fs/xfs/scrub/inode_repair.c | 36 ++++++++++++++++++++++++ 4 files changed, 100 insertions(+) diff --git a/fs/xfs/scrub/common.c b/fs/xfs/scrub/common.c index e5233e31abb7..02b68bf4c512 100644 --- a/fs/xfs/scrub/common.c +++ b/fs/xfs/scrub/common.c @@ -45,6 +45,8 @@ #include "scrub/health.h" #include "scrub/tempfile.h" +#include + /* Common code for the metadata scrubbers. */ /* @@ -1754,3 +1756,56 @@ xchk_inode_count_blocks( return xfs_bmap_count_blocks(sc->tp, sc->ip, whichfork, nextents, count); } + +/* + * If this inode has S_VERITY set on it, read the verity info. If the reading + * fails with anything other than ENOMEM, the file is corrupt, which we can + * detect later with fsverity_active. + * + * Callers must hold the IOLOCK and must not hold the ILOCK of sc->ip because + * activation reads inode data. + */ +int +xchk_inode_setup_verity( + struct xfs_scrub *sc) +{ + int error; + + if (!fsverity_active(VFS_I(sc->ip))) + return 0; + + error = fsverity_ensure_verity_info(VFS_I(sc->ip)); + switch (error) { + case 0: + /* fsverity is active */ + break; + case -ENODATA: + case -EMSGSIZE: + case -EINVAL: + case -EFSCORRUPTED: + case -EFBIG: + case -ERANGE: + case -EBADMSG: + /* + * The nonzero errno codes above are the error codes that can + * be returned from fsverity on metadata validation errors. + */ + return 0; + default: + /* runtime errors */ + return error; + } + + return 0; +} + +/* + * Is this a verity file that failed to activate? Callers must have tried to + * activate fsverity via xchk_inode_setup_verity. + */ +bool +xchk_inode_verity_broken( + struct xfs_inode *ip) +{ + return fsverity_active(VFS_I(ip)) && !fsverity_get_info(VFS_I(ip)); +} diff --git a/fs/xfs/scrub/common.h b/fs/xfs/scrub/common.h index f0f073a93413..676b4afd0be4 100644 --- a/fs/xfs/scrub/common.h +++ b/fs/xfs/scrub/common.h @@ -267,6 +267,8 @@ int xchk_inode_is_allocated(struct xfs_scrub *sc, xfs_agino_t agino, bool *inuse); int xchk_inode_count_blocks(struct xfs_scrub *sc, int whichfork, xfs_extnum_t *nextents, xfs_filblks_t *count); +int xchk_inode_setup_verity(struct xfs_scrub *sc); +bool xchk_inode_verity_broken(struct xfs_inode *ip); bool xchk_inode_is_dirtree_root(const struct xfs_inode *ip); bool xchk_inode_is_sb_rooted(const struct xfs_inode *ip); diff --git a/fs/xfs/scrub/inode.c b/fs/xfs/scrub/inode.c index 65b13e311916..d1cdd6b445d0 100644 --- a/fs/xfs/scrub/inode.c +++ b/fs/xfs/scrub/inode.c @@ -36,6 +36,10 @@ xchk_prepare_iscrub( xchk_ilock(sc, XFS_IOLOCK_EXCL); + error = xchk_inode_setup_verity(sc); + if (error) + return error; + error = xchk_trans_alloc(sc, 0); if (error) return error; @@ -833,6 +837,9 @@ xchk_inode( if (S_ISREG(VFS_I(sc->ip)->i_mode)) xchk_inode_check_reflink_iflag(sc, I_INO(sc->ip)); + if (xchk_inode_verity_broken(sc->ip)) + xchk_ino_set_corrupt(sc, sc->sm->sm_ino); + xchk_inode_check_unlinked(sc); xchk_inode_xref(sc, I_INO(sc->ip), &di); diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index 8bc508336aa5..6c0b7130e227 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -573,6 +573,8 @@ xrep_dinode_flags( dip->di_nrext64_pad = 0; else if (dip->di_version >= 3) dip->di_v3_pad = 0; + if (!xfs_has_verity(mp) || !S_ISREG(mode)) + flags2 &= ~XFS_DIFLAG2_VERITY; if (flags2 & XFS_DIFLAG2_METADATA) { xfs_failaddr_t fa; @@ -1617,6 +1619,10 @@ xrep_dinode_core( if (iget_error) return iget_error; + error = xchk_inode_setup_verity(sc); + if (error) + return error; + error = xchk_trans_alloc(sc, 0); if (error) return error; @@ -2035,6 +2041,27 @@ xrep_inode_unlinked( return 0; } +/* + * If this file is a fsverity file, xchk_prepare_iscrub or xrep_dinode_core + * should have activated it. If it's still not active, then there's something + * wrong with the verity descriptor and we should turn it off. + */ +STATIC int +xrep_inode_verity( + struct xfs_scrub *sc) +{ + struct inode *inode = VFS_I(sc->ip); + + if (xchk_inode_verity_broken(sc->ip)) { + sc->ip->i_diflags2 &= ~XFS_DIFLAG2_VERITY; + inode_set_flags(inode, 0, S_VERITY); + + xfs_trans_log_inode(sc->tp, sc->ip, XFS_ILOG_CORE); + } + + return 0; +} + /* Repair an inode's fields. */ int xrep_inode( @@ -2084,6 +2111,15 @@ xrep_inode( return error; } + /* + * Disable fsverity if it cannot be activated. Activation failure + * prohibits the file from being opened, so there cannot be another + * program with an open fd to what it thinks is a verity file. + */ + error = xrep_inode_verity(sc); + if (error) + return error; + /* Reconnect incore unlinked list */ error = xrep_inode_unlinked(sc); if (error) -- 2.54.0 Report corrupted fsverity descriptor through health system. Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Andrey Albershteyn --- fs/xfs/libxfs/xfs_fs.h | 1 + fs/xfs/libxfs/xfs_health.h | 4 +++- fs/xfs/xfs_fsverity.c | 25 +++++++++++++++++++------ fs/xfs/xfs_health.c | 1 + 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/fs/xfs/libxfs/xfs_fs.h b/fs/xfs/libxfs/xfs_fs.h index c80133784419..84d62b7506a9 100644 --- a/fs/xfs/libxfs/xfs_fs.h +++ b/fs/xfs/libxfs/xfs_fs.h @@ -422,6 +422,7 @@ struct xfs_bulkstat { #define XFS_BS_SICK_SYMLINK (1 << 6) /* symbolic link remote target */ #define XFS_BS_SICK_PARENT (1 << 7) /* parent pointers */ #define XFS_BS_SICK_DIRTREE (1 << 8) /* directory tree structure */ +#define XFS_BS_SICK_FSVERITY (1 << 9) /* fsverity metadata */ /* * Project quota id helpers (previously projid was 16bit only diff --git a/fs/xfs/libxfs/xfs_health.h b/fs/xfs/libxfs/xfs_health.h index 1d45cf5789e8..932b447190da 100644 --- a/fs/xfs/libxfs/xfs_health.h +++ b/fs/xfs/libxfs/xfs_health.h @@ -104,6 +104,7 @@ struct xfs_rtgroup; /* Don't propagate sick status to ag health summary during inactivation */ #define XFS_SICK_INO_FORGET (1 << 12) #define XFS_SICK_INO_DIRTREE (1 << 13) /* directory tree structure */ +#define XFS_SICK_INO_FSVERITY (1 << 14) /* fsverity metadata */ /* Primary evidence of health problems in a given group. */ #define XFS_SICK_FS_PRIMARY (XFS_SICK_FS_COUNTERS | \ @@ -140,7 +141,8 @@ struct xfs_rtgroup; XFS_SICK_INO_XATTR | \ XFS_SICK_INO_SYMLINK | \ XFS_SICK_INO_PARENT | \ - XFS_SICK_INO_DIRTREE) + XFS_SICK_INO_DIRTREE | \ + XFS_SICK_INO_FSVERITY) #define XFS_SICK_INO_ZAPPED (XFS_SICK_INO_BMBTD_ZAPPED | \ XFS_SICK_INO_BMBTA_ZAPPED | \ diff --git a/fs/xfs/xfs_fsverity.c b/fs/xfs/xfs_fsverity.c index e62c9a5d0e1d..18a2cdf95427 100644 --- a/fs/xfs/xfs_fsverity.c +++ b/fs/xfs/xfs_fsverity.c @@ -69,13 +69,17 @@ xfs_fsverity_get_descriptor( if (error) return error; - if (is_empty) + if (is_empty) { + xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY); return -ENODATA; + } last_block_offset = XFS_FSB_TO_B(mp, rec.br_startoff + rec.br_blockcount); - if (last_block_offset <= xfs_fsverity_metadata_offset(ip)) + if (last_block_offset <= xfs_fsverity_metadata_offset(ip)) { + xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY); return -ENODATA; + } desc_size_pos = last_block_offset - sizeof(__be32); error = fsverity_pagecache_read(inode, (char *)&d_desc_size, @@ -84,20 +88,29 @@ xfs_fsverity_get_descriptor( return error; desc_size = be32_to_cpu(d_desc_size); - if (XFS_IS_CORRUPT(mp, desc_size > FS_VERITY_MAX_DESCRIPTOR_SIZE)) + if (XFS_IS_CORRUPT(mp, desc_size > FS_VERITY_MAX_DESCRIPTOR_SIZE)) { + xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY); return -ERANGE; - if (XFS_IS_CORRUPT(mp, desc_size > desc_size_pos)) + } + + if (XFS_IS_CORRUPT(mp, desc_size > desc_size_pos)) { + xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY); return -ERANGE; + } if (!buf_size) return desc_size; - if (XFS_IS_CORRUPT(mp, desc_size > buf_size)) + if (XFS_IS_CORRUPT(mp, desc_size > buf_size)) { + xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY); return -ERANGE; + } desc_pos = round_down(desc_size_pos - desc_size, blocksize); - if (desc_pos < xfs_fsverity_metadata_offset(ip)) + if (desc_pos < xfs_fsverity_metadata_offset(ip)) { + xfs_inode_mark_sick(XFS_I(inode), XFS_SICK_INO_FSVERITY); return -ERANGE; + } error = fsverity_pagecache_read(inode, buf, desc_size, desc_pos); if (error) diff --git a/fs/xfs/xfs_health.c b/fs/xfs/xfs_health.c index 239b843e83d4..be66760fb120 100644 --- a/fs/xfs/xfs_health.c +++ b/fs/xfs/xfs_health.c @@ -625,6 +625,7 @@ static const struct ioctl_sick_map ino_map[] = { { XFS_SICK_INO_DIR_ZAPPED, XFS_BS_SICK_DIR }, { XFS_SICK_INO_SYMLINK_ZAPPED, XFS_BS_SICK_SYMLINK }, { XFS_SICK_INO_DIRTREE, XFS_BS_SICK_DIRTREE }, + { XFS_SICK_INO_FSVERITY, XFS_BS_SICK_FSVERITY }, }; /* Fill out bulkstat health info. */ -- 2.54.0 Finalize fs-verity integration in XFS by making kernel fs-verity aware with ro-compat flag. Reviewed-by: Darrick J. Wong [djwong: add spaces] Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Andrey Albershteyn --- fs/xfs/libxfs/xfs_format.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h index fc196d9e12dd..8be1b101b196 100644 --- a/fs/xfs/libxfs/xfs_format.h +++ b/fs/xfs/libxfs/xfs_format.h @@ -378,8 +378,9 @@ xfs_sb_has_compat_feature( #define XFS_SB_FEAT_RO_COMPAT_ALL \ (XFS_SB_FEAT_RO_COMPAT_FINOBT | \ XFS_SB_FEAT_RO_COMPAT_RMAPBT | \ - XFS_SB_FEAT_RO_COMPAT_REFLINK| \ - XFS_SB_FEAT_RO_COMPAT_INOBTCNT) + XFS_SB_FEAT_RO_COMPAT_REFLINK | \ + XFS_SB_FEAT_RO_COMPAT_INOBTCNT | \ + XFS_SB_FEAT_RO_COMPAT_VERITY) #define XFS_SB_FEAT_RO_COMPAT_UNKNOWN ~XFS_SB_FEAT_RO_COMPAT_ALL static inline bool xfs_sb_has_ro_compat_feature( -- 2.54.0