Merge patch series "vfs infrastructure for fs-verity support for XFS with post EOF merkle tree"

Christian Brauner <brauner@kernel.org> says:

This brings in the vfs infrastructure required to implement fs-verity
support for XFS.

* patches from https://patch.msgid.link/20260520123722.405752-1-aalbersh@kernel.org:
  iomap: introduce iomap_fsverity_write() for writing fsverity metadata
  iomap: teach iomap to read files with fsverity
  iomap: introduce IOMAP_F_FSVERITY and teach writeback to handle fsverity
  fsverity: generate and store zero-block hash

Link: https://patch.msgid.link/20260520123722.405752-1-aalbersh@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2026-06-04 13:47:27 +02:00
commit d4d80e0140
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
9 changed files with 152 additions and 14 deletions

View File

@ -9,6 +9,7 @@
#include <linux/swap.h>
#include <linux/migrate.h>
#include <linux/fserror.h>
#include <linux/fsverity.h>
#include "internal.h"
#include "trace.h"
@ -353,9 +354,26 @@ static inline bool iomap_block_needs_zeroing(const struct iomap_iter *iter,
{
const struct iomap *srcmap = iomap_iter_srcmap(iter);
return srcmap->type != IOMAP_MAPPED ||
(srcmap->flags & IOMAP_F_NEW) ||
pos >= i_size_read(iter->inode);
/*
* If this block has not been written, there's nothing to read
*/
if (srcmap->type != IOMAP_MAPPED)
return true;
/*
* Newly allocated blocks have not been written
*/
if (srcmap->flags & IOMAP_F_NEW)
return true;
/*
* fsverity metadata is stored past i_size, we need to read it instead
* of zeroing
*/
if (srcmap->flags & IOMAP_F_FSVERITY)
return false;
return pos >= i_size_read(iter->inode);
}
/**
@ -544,9 +562,27 @@ static int iomap_read_folio_iter(struct iomap_iter *iter,
if (plen == 0)
return 0;
/* zero post-eof blocks as the page may be mapped */
if (iomap_block_needs_zeroing(iter, pos)) {
/*
* Handling of fsverity "holes". We hit this for two case:
* 1. No need to go further, the hole after fsverity
* descriptor is the end of the fsverity metadata.
*
* 2. This folio contains merkle tree blocks which need to be
* synthesized. If we already have fsverity info (ctx->vi)
* synthesize these blocks.
*/
if ((iomap->flags & IOMAP_F_FSVERITY) &&
iomap->type == IOMAP_HOLE) {
if (ctx->vi)
fsverity_fill_zerohash(folio, poff, plen,
ctx->vi);
iomap_set_range_uptodate(folio, poff, plen);
} else if (iomap_block_needs_zeroing(iter, pos)) {
/* zero post-eof blocks as the page may be mapped */
folio_zero_range(folio, poff, plen);
if (ctx->vi &&
!fsverity_verify_blocks(ctx->vi, folio, plen, poff))
return -EIO;
iomap_set_range_uptodate(folio, poff, plen);
} else {
if (!*bytes_submitted)
@ -597,6 +633,15 @@ void iomap_read_folio(const struct iomap_ops *ops,
trace_iomap_readpage(iter.inode, 1);
/*
* Fetch fsverity_info for both data and fsverity metadata, as iomap
* needs zeroed hash for merkle tree block synthesis
*/
ctx->vi = fsverity_get_info(iter.inode);
if (ctx->vi && iter.pos < i_size_read(iter.inode))
fsverity_readahead(ctx->vi, folio->index,
folio_nr_pages(folio));
while ((ret = iomap_iter(&iter, ops)) > 0)
iter.status = iomap_read_folio_iter(&iter, ctx,
&bytes_submitted);
@ -664,6 +709,15 @@ void iomap_readahead(const struct iomap_ops *ops,
trace_iomap_readahead(rac->mapping->host, readahead_count(rac));
/*
* Fetch fsverity_info for both data and fsverity metadata, as iomap
* needs zeroed hash for merkle tree block synthesis
*/
ctx->vi = fsverity_get_info(iter.inode);
if (ctx->vi && iter.pos < i_size_read(iter.inode))
fsverity_readahead(ctx->vi, readahead_index(rac),
readahead_count(rac));
while (iomap_iter(&iter, ops) > 0)
iter.status = iomap_readahead_iter(&iter, ctx,
&cur_bytes_submitted);
@ -1170,13 +1224,14 @@ static int iomap_write_iter(struct iomap_iter *iter, struct iov_iter *i,
* unlock and release the folio.
*/
old_size = iter->inode->i_size;
if (pos + written > old_size) {
if (pos + written > old_size &&
!(iter->iomap.flags & IOMAP_F_FSVERITY)) {
i_size_write(iter->inode, pos + written);
iter->iomap.flags |= IOMAP_F_SIZE_CHANGED;
}
__iomap_put_folio(iter, write_ops, written, folio);
if (old_size < pos)
if (old_size < pos && !(iter->iomap.flags & IOMAP_F_FSVERITY))
pagecache_isize_extended(iter->inode, old_size, pos);
cond_resched();
@ -1235,6 +1290,31 @@ iomap_file_buffered_write(struct kiocb *iocb, struct iov_iter *i,
}
EXPORT_SYMBOL_GPL(iomap_file_buffered_write);
int iomap_fsverity_write(struct file *file, loff_t pos, size_t length,
const void *buf, const struct iomap_ops *ops,
const struct iomap_write_ops *write_ops)
{
int ret;
struct iov_iter iiter;
struct kvec kvec = {
.iov_base = (void *)buf,
.iov_len = length,
};
struct kiocb iocb = {
.ki_filp = file,
.ki_ioprio = get_current_ioprio(),
.ki_pos = pos,
};
iov_iter_kvec(&iiter, WRITE, &kvec, 1, length);
ret = iomap_file_buffered_write(&iocb, &iiter, ops, write_ops, NULL);
if (ret < 0)
return ret;
return ret == length ? 0 : -EIO;
}
EXPORT_SYMBOL_GPL(iomap_fsverity_write);
static void iomap_write_delalloc_ifs_punch(struct inode *inode,
struct folio *folio, loff_t start_byte, loff_t end_byte,
struct iomap *iomap, iomap_punch_t punch)
@ -1802,13 +1882,20 @@ static int iomap_writeback_range(struct iomap_writepage_ctx *wpc,
* Check interaction of the folio with the file end.
*
* If the folio is entirely beyond i_size, return false. If it straddles
* i_size, adjust end_pos and zero all data beyond i_size.
* i_size, adjust end_pos and zero all data beyond i_size. Don't skip fsverity
* folios as those are beyond i_size.
*/
static bool iomap_writeback_handle_eof(struct folio *folio, struct inode *inode,
u64 *end_pos)
static bool iomap_writeback_handle_eof(struct folio *folio,
struct iomap_writepage_ctx *wpc, u64 *end_pos)
{
struct inode *inode = wpc->inode;
u64 isize = i_size_read(inode);
if (wpc->iomap.flags & IOMAP_F_FSVERITY) {
WARN_ON_ONCE(folio_pos(folio) < isize);
return true;
}
if (*end_pos > isize) {
size_t poff = offset_in_folio(folio, isize);
pgoff_t end_index = isize >> PAGE_SHIFT;
@ -1874,7 +1961,7 @@ int iomap_writeback_folio(struct iomap_writepage_ctx *wpc, struct folio *folio)
trace_iomap_writeback_folio(inode, pos, folio_size(folio));
if (!iomap_writeback_handle_eof(folio, inode, &end_pos))
if (!iomap_writeback_handle_eof(folio, wpc, &end_pos))
return 0;
WARN_ON_ONCE(end_pos <= pos);

View File

@ -28,6 +28,7 @@ struct iomap_ioend *iomap_init_ioend(struct inode *inode,
ioend->io_offset = file_offset;
ioend->io_size = bio->bi_iter.bi_size;
ioend->io_sector = bio->bi_iter.bi_sector;
ioend->io_vi = NULL;
ioend->io_private = NULL;
return ioend;
}

View File

@ -118,7 +118,8 @@ DEFINE_RANGE_EVENT(iomap_zero_iter);
{ 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_FSVERITY, "FSVERITY" }
#define IOMAP_DIO_STRINGS \

View File

@ -53,6 +53,9 @@ struct merkle_tree_params {
u64 tree_size; /* Merkle tree size in bytes */
unsigned long tree_pages; /* Merkle tree size in pages */
/* the hash of an all-zeroes block */
u8 zero_digest[FS_VERITY_MAX_DIGEST_SIZE];
/*
* Starting block index for each tree level, ordered from leaf level (0)
* to root level ('num_levels - 1')

View File

@ -68,8 +68,8 @@ EXPORT_SYMBOL_GPL(fsverity_ioctl_measure);
* @alg: (out) the digest's algorithm, as a FS_VERITY_HASH_ALG_* value
* @halg: (out) the digest's algorithm, as a HASH_ALGO_* value
*
* Retrieves the fsverity digest of the given file. The file must have been
* opened at least once since the inode was last loaded into the inode cache;
* Retrieves the fsverity digest of the given file. The
* fsverity_ensure_verity_info() must be called on the inode beforehand;
* otherwise this function will not recognize when fsverity is enabled.
*
* The file's fsverity digest consists of @raw_digest in combination with either

View File

@ -153,6 +153,9 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
goto out_err;
}
fsverity_hash_block(params, page_address(ZERO_PAGE(0)),
params->zero_digest);
params->tree_size = offset << log_blocksize;
params->tree_pages = PAGE_ALIGN(params->tree_size) >> PAGE_SHIFT;
return 0;

View File

@ -2,6 +2,7 @@
/*
* Copyright 2019 Google LLC
*/
#include "fsverity_private.h"
#include <linux/export.h>
#include <linux/fsverity.h>
@ -56,3 +57,24 @@ void generic_readahead_merkle_tree(struct inode *inode, pgoff_t index,
folio_put(folio);
}
EXPORT_SYMBOL_GPL(generic_readahead_merkle_tree);
/**
* fsverity_fill_zerohash() - fill folio with hashes of zero data block
* @folio: folio to fill
* @offset: offset in the folio to start
* @len: length of the range to fill with hashes
* @vi: fsverity info
*/
void fsverity_fill_zerohash(struct folio *folio, size_t offset, size_t len,
struct fsverity_info *vi)
{
size_t off = offset;
WARN_ON_ONCE(!IS_ALIGNED(offset, vi->tree_params.digest_size));
WARN_ON_ONCE(!IS_ALIGNED(len, vi->tree_params.digest_size));
for (; off < (offset + len); off += vi->tree_params.digest_size)
memcpy_to_folio(folio, off, vi->tree_params.zero_digest,
vi->tree_params.digest_size);
}
EXPORT_SYMBOL_GPL(fsverity_fill_zerohash);

View File

@ -201,6 +201,8 @@ bool fsverity_verify_blocks(struct fsverity_info *vi, struct folio *folio,
size_t len, size_t offset);
void fsverity_verify_bio(struct fsverity_info *vi, struct bio *bio);
void fsverity_enqueue_verify_work(struct work_struct *work);
void fsverity_fill_zerohash(struct folio *folio, size_t offset, size_t len,
struct fsverity_info *vi);
#else /* !CONFIG_FS_VERITY */
@ -281,6 +283,12 @@ static inline void fsverity_enqueue_verify_work(struct work_struct *work)
WARN_ON_ONCE(1);
}
static inline void fsverity_fill_zerohash(struct folio *folio, size_t offset,
size_t len, struct fsverity_info *vi)
{
WARN_ON_ONCE(1);
}
#endif /* !CONFIG_FS_VERITY */
static inline bool fsverity_verify_folio(struct fsverity_info *vi,

View File

@ -91,6 +91,14 @@ struct vm_fault;
#endif /* CONFIG_BLK_DEV_INTEGRITY */
#define IOMAP_F_ZERO_TAIL (1U << 10)
/*
* Indicates reads and writes of fsverity metadata.
*
* Fsverity metadata is stored after the regular file data and thus beyond
* i_size.
*/
#define IOMAP_F_FSVERITY (1U << 11)
/*
* Flag reserved for file system specific usage
*/
@ -345,6 +353,9 @@ static inline bool iomap_want_unshare_iter(const struct iomap_iter *iter)
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);
int iomap_fsverity_write(struct file *file, loff_t pos, size_t length,
const void *buf, const struct iomap_ops *ops,
const struct iomap_write_ops *write_ops);
void iomap_read_folio(const struct iomap_ops *ops,
struct iomap_read_folio_ctx *ctx, void *private);
void iomap_readahead(const struct iomap_ops *ops,
@ -421,6 +432,7 @@ struct iomap_ioend {
loff_t io_offset; /* offset in the file */
sector_t io_sector; /* start sector of ioend */
void *io_private; /* file system private data */
struct fsverity_info *io_vi; /* fsverity info */
struct bio io_bio; /* MUST BE LAST! */
};
@ -495,6 +507,7 @@ struct iomap_read_folio_ctx {
struct readahead_control *rac;
void *read_ctx;
loff_t read_ctx_file_offset;
struct fsverity_info *vi;
};
struct iomap_read_ops {