mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	fsverity: support verifying data from large folios
Try to make fs/verity/verify.c aware of large folios. This includes making fsverity_verify_bio() support the case where the bio contains large folios, and adding a function fsverity_verify_folio() which is the equivalent of fsverity_verify_page(). There's no way to actually test this with large folios yet, but I've tested that this doesn't cause any regressions. Signed-off-by: Eric Biggers <ebiggers@google.com> Link: https://lore.kernel.org/r/20230127221529.299560-1-ebiggers@kernel.org
This commit is contained in:
		
							parent
							
								
									245edf445c
								
							
						
					
					
						commit
						5d0f0e57ed
					
				
					 4 changed files with 44 additions and 37 deletions
				
			
		| 
						 | 
					@ -568,22 +568,22 @@ Pagecache
 | 
				
			||||||
~~~~~~~~~
 | 
					~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
For filesystems using Linux's pagecache, the ``->read_folio()`` and
 | 
					For filesystems using Linux's pagecache, the ``->read_folio()`` and
 | 
				
			||||||
``->readahead()`` methods must be modified to verify pages before they
 | 
					``->readahead()`` methods must be modified to verify folios before
 | 
				
			||||||
are marked Uptodate.  Merely hooking ``->read_iter()`` would be
 | 
					they are marked Uptodate.  Merely hooking ``->read_iter()`` would be
 | 
				
			||||||
insufficient, since ``->read_iter()`` is not used for memory maps.
 | 
					insufficient, since ``->read_iter()`` is not used for memory maps.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Therefore, fs/verity/ provides the function fsverity_verify_blocks()
 | 
					Therefore, fs/verity/ provides the function fsverity_verify_blocks()
 | 
				
			||||||
which verifies data that has been read into the pagecache of a verity
 | 
					which verifies data that has been read into the pagecache of a verity
 | 
				
			||||||
inode.  The containing page must still be locked and not Uptodate, so
 | 
					inode.  The containing folio must still be locked and not Uptodate, so
 | 
				
			||||||
it's not yet readable by userspace.  As needed to do the verification,
 | 
					it's not yet readable by userspace.  As needed to do the verification,
 | 
				
			||||||
fsverity_verify_blocks() will call back into the filesystem to read
 | 
					fsverity_verify_blocks() will call back into the filesystem to read
 | 
				
			||||||
hash blocks via fsverity_operations::read_merkle_tree_page().
 | 
					hash blocks via fsverity_operations::read_merkle_tree_page().
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fsverity_verify_blocks() returns false if verification failed; in this
 | 
					fsverity_verify_blocks() returns false if verification failed; in this
 | 
				
			||||||
case, the filesystem must not set the page Uptodate.  Following this,
 | 
					case, the filesystem must not set the folio Uptodate.  Following this,
 | 
				
			||||||
as per the usual Linux pagecache behavior, attempts by userspace to
 | 
					as per the usual Linux pagecache behavior, attempts by userspace to
 | 
				
			||||||
read() from the part of the file containing the page will fail with
 | 
					read() from the part of the file containing the folio will fail with
 | 
				
			||||||
EIO, and accesses to the page within a memory map will raise SIGBUS.
 | 
					EIO, and accesses to the folio within a memory map will raise SIGBUS.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
In principle, verifying a data block requires verifying the entire
 | 
					In principle, verifying a data block requires verifying the entire
 | 
				
			||||||
path in the Merkle tree from the data block to the root hash.
 | 
					path in the Merkle tree from the data block to the root hash.
 | 
				
			||||||
| 
						 | 
					@ -624,8 +624,8 @@ each bio and store it in ``->bi_private``::
 | 
				
			||||||
verity, or both is enabled.  After the bio completes, for each needed
 | 
					verity, or both is enabled.  After the bio completes, for each needed
 | 
				
			||||||
postprocessing step the filesystem enqueues the bio_post_read_ctx on a
 | 
					postprocessing step the filesystem enqueues the bio_post_read_ctx on a
 | 
				
			||||||
workqueue, and then the workqueue work does the decryption or
 | 
					workqueue, and then the workqueue work does the decryption or
 | 
				
			||||||
verification.  Finally, pages where no decryption or verity error
 | 
					verification.  Finally, folios where no decryption or verity error
 | 
				
			||||||
occurred are marked Uptodate, and the pages are unlocked.
 | 
					occurred are marked Uptodate, and the folios are unlocked.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
On many filesystems, files can contain holes.  Normally,
 | 
					On many filesystems, files can contain holes.  Normally,
 | 
				
			||||||
``->readahead()`` simply zeroes hole blocks and considers the
 | 
					``->readahead()`` simply zeroes hole blocks and considers the
 | 
				
			||||||
| 
						 | 
					@ -791,9 +791,9 @@ weren't already directly answered in other parts of this document.
 | 
				
			||||||
:A: There are many reasons why this is not possible or would be very
 | 
					:A: There are many reasons why this is not possible or would be very
 | 
				
			||||||
    difficult, including the following:
 | 
					    difficult, including the following:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - To prevent bypassing verification, pages must not be marked
 | 
					    - To prevent bypassing verification, folios must not be marked
 | 
				
			||||||
      Uptodate until they've been verified.  Currently, each
 | 
					      Uptodate until they've been verified.  Currently, each
 | 
				
			||||||
      filesystem is responsible for marking pages Uptodate via
 | 
					      filesystem is responsible for marking folios Uptodate via
 | 
				
			||||||
      ``->readahead()``.  Therefore, currently it's not possible for
 | 
					      ``->readahead()``.  Therefore, currently it's not possible for
 | 
				
			||||||
      the VFS to do the verification on its own.  Changing this would
 | 
					      the VFS to do the verification on its own.  Changing this would
 | 
				
			||||||
      require significant changes to the VFS and all filesystems.
 | 
					      require significant changes to the VFS and all filesystems.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -308,7 +308,8 @@ static void verify_bh(struct work_struct *work)
 | 
				
			||||||
	struct buffer_head *bh = ctx->bh;
 | 
						struct buffer_head *bh = ctx->bh;
 | 
				
			||||||
	bool valid;
 | 
						bool valid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	valid = fsverity_verify_blocks(bh->b_page, bh->b_size, bh_offset(bh));
 | 
						valid = fsverity_verify_blocks(page_folio(bh->b_page), bh->b_size,
 | 
				
			||||||
 | 
									       bh_offset(bh));
 | 
				
			||||||
	end_buffer_async_read(bh, valid);
 | 
						end_buffer_async_read(bh, valid);
 | 
				
			||||||
	kfree(ctx);
 | 
						kfree(ctx);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -266,20 +266,23 @@ verify_data_block(struct inode *inode, struct fsverity_info *vi,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool
 | 
					static bool
 | 
				
			||||||
verify_data_blocks(struct inode *inode, struct fsverity_info *vi,
 | 
					verify_data_blocks(struct inode *inode, struct fsverity_info *vi,
 | 
				
			||||||
		   struct ahash_request *req, struct page *data_page,
 | 
							   struct ahash_request *req, struct folio *data_folio,
 | 
				
			||||||
		   unsigned int len, unsigned int offset,
 | 
							   size_t len, size_t offset, unsigned long max_ra_pages)
 | 
				
			||||||
		   unsigned long max_ra_pages)
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	const unsigned int block_size = vi->tree_params.block_size;
 | 
						const unsigned int block_size = vi->tree_params.block_size;
 | 
				
			||||||
	u64 pos = (u64)data_page->index << PAGE_SHIFT;
 | 
						u64 pos = (u64)data_folio->index << PAGE_SHIFT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (WARN_ON_ONCE(len <= 0 || !IS_ALIGNED(len | offset, block_size)))
 | 
						if (WARN_ON_ONCE(len <= 0 || !IS_ALIGNED(len | offset, block_size)))
 | 
				
			||||||
		return false;
 | 
							return false;
 | 
				
			||||||
	if (WARN_ON_ONCE(!PageLocked(data_page) || PageUptodate(data_page)))
 | 
						if (WARN_ON_ONCE(!folio_test_locked(data_folio) ||
 | 
				
			||||||
 | 
								 folio_test_uptodate(data_folio)))
 | 
				
			||||||
		return false;
 | 
							return false;
 | 
				
			||||||
	do {
 | 
						do {
 | 
				
			||||||
		if (!verify_data_block(inode, vi, req, data_page,
 | 
							struct page *data_page =
 | 
				
			||||||
				       pos + offset, offset, max_ra_pages))
 | 
								folio_page(data_folio, offset >> PAGE_SHIFT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!verify_data_block(inode, vi, req, data_page, pos + offset,
 | 
				
			||||||
 | 
									       offset & ~PAGE_MASK, max_ra_pages))
 | 
				
			||||||
			return false;
 | 
								return false;
 | 
				
			||||||
		offset += block_size;
 | 
							offset += block_size;
 | 
				
			||||||
		len -= block_size;
 | 
							len -= block_size;
 | 
				
			||||||
| 
						 | 
					@ -288,21 +291,20 @@ verify_data_blocks(struct inode *inode, struct fsverity_info *vi,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * fsverity_verify_blocks() - verify data in a page
 | 
					 * fsverity_verify_blocks() - verify data in a folio
 | 
				
			||||||
 * @page: the page containing the data to verify
 | 
					 * @folio: the folio containing the data to verify
 | 
				
			||||||
 * @len: the length of the data to verify in the page
 | 
					 * @len: the length of the data to verify in the folio
 | 
				
			||||||
 * @offset: the offset of the data to verify in the page
 | 
					 * @offset: the offset of the data to verify in the folio
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * Verify data that has just been read from a verity file.  The data must be
 | 
					 * Verify data that has just been read from a verity file.  The data must be
 | 
				
			||||||
 * located in a pagecache page that is still locked and not yet uptodate.  The
 | 
					 * located in a pagecache folio that is still locked and not yet uptodate.  The
 | 
				
			||||||
 * length and offset of the data must be Merkle tree block size aligned.
 | 
					 * length and offset of the data must be Merkle tree block size aligned.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * Return: %true if the data is valid, else %false.
 | 
					 * Return: %true if the data is valid, else %false.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
bool fsverity_verify_blocks(struct page *page, unsigned int len,
 | 
					bool fsverity_verify_blocks(struct folio *folio, size_t len, size_t offset)
 | 
				
			||||||
			    unsigned int offset)
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct inode *inode = page->mapping->host;
 | 
						struct inode *inode = folio->mapping->host;
 | 
				
			||||||
	struct fsverity_info *vi = inode->i_verity_info;
 | 
						struct fsverity_info *vi = inode->i_verity_info;
 | 
				
			||||||
	struct ahash_request *req;
 | 
						struct ahash_request *req;
 | 
				
			||||||
	bool valid;
 | 
						bool valid;
 | 
				
			||||||
| 
						 | 
					@ -310,7 +312,7 @@ bool fsverity_verify_blocks(struct page *page, unsigned int len,
 | 
				
			||||||
	/* This allocation never fails, since it's mempool-backed. */
 | 
						/* This allocation never fails, since it's mempool-backed. */
 | 
				
			||||||
	req = fsverity_alloc_hash_request(vi->tree_params.hash_alg, GFP_NOFS);
 | 
						req = fsverity_alloc_hash_request(vi->tree_params.hash_alg, GFP_NOFS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	valid = verify_data_blocks(inode, vi, req, page, len, offset, 0);
 | 
						valid = verify_data_blocks(inode, vi, req, folio, len, offset, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fsverity_free_hash_request(vi->tree_params.hash_alg, req);
 | 
						fsverity_free_hash_request(vi->tree_params.hash_alg, req);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -338,8 +340,7 @@ void fsverity_verify_bio(struct bio *bio)
 | 
				
			||||||
	struct inode *inode = bio_first_page_all(bio)->mapping->host;
 | 
						struct inode *inode = bio_first_page_all(bio)->mapping->host;
 | 
				
			||||||
	struct fsverity_info *vi = inode->i_verity_info;
 | 
						struct fsverity_info *vi = inode->i_verity_info;
 | 
				
			||||||
	struct ahash_request *req;
 | 
						struct ahash_request *req;
 | 
				
			||||||
	struct bio_vec *bv;
 | 
						struct folio_iter fi;
 | 
				
			||||||
	struct bvec_iter_all iter_all;
 | 
					 | 
				
			||||||
	unsigned long max_ra_pages = 0;
 | 
						unsigned long max_ra_pages = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* This allocation never fails, since it's mempool-backed. */
 | 
						/* This allocation never fails, since it's mempool-backed. */
 | 
				
			||||||
| 
						 | 
					@ -358,9 +359,9 @@ void fsverity_verify_bio(struct bio *bio)
 | 
				
			||||||
		max_ra_pages = bio->bi_iter.bi_size >> (PAGE_SHIFT + 2);
 | 
							max_ra_pages = bio->bi_iter.bi_size >> (PAGE_SHIFT + 2);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bio_for_each_segment_all(bv, bio, iter_all) {
 | 
						bio_for_each_folio_all(fi, bio) {
 | 
				
			||||||
		if (!verify_data_blocks(inode, vi, req, bv->bv_page, bv->bv_len,
 | 
							if (!verify_data_blocks(inode, vi, req, fi.folio, fi.length,
 | 
				
			||||||
					bv->bv_offset, max_ra_pages)) {
 | 
										fi.offset, max_ra_pages)) {
 | 
				
			||||||
			bio->bi_status = BLK_STS_IOERR;
 | 
								bio->bi_status = BLK_STS_IOERR;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@
 | 
				
			||||||
#define _LINUX_FSVERITY_H
 | 
					#define _LINUX_FSVERITY_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <linux/fs.h>
 | 
					#include <linux/fs.h>
 | 
				
			||||||
 | 
					#include <linux/mm.h>
 | 
				
			||||||
#include <crypto/hash_info.h>
 | 
					#include <crypto/hash_info.h>
 | 
				
			||||||
#include <crypto/sha2.h>
 | 
					#include <crypto/sha2.h>
 | 
				
			||||||
#include <uapi/linux/fsverity.h>
 | 
					#include <uapi/linux/fsverity.h>
 | 
				
			||||||
| 
						 | 
					@ -169,8 +170,7 @@ int fsverity_ioctl_read_metadata(struct file *filp, const void __user *uarg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* verify.c */
 | 
					/* verify.c */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool fsverity_verify_blocks(struct page *page, unsigned int len,
 | 
					bool fsverity_verify_blocks(struct folio *folio, size_t len, size_t offset);
 | 
				
			||||||
			    unsigned int offset);
 | 
					 | 
				
			||||||
void fsverity_verify_bio(struct bio *bio);
 | 
					void fsverity_verify_bio(struct bio *bio);
 | 
				
			||||||
void fsverity_enqueue_verify_work(struct work_struct *work);
 | 
					void fsverity_enqueue_verify_work(struct work_struct *work);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -230,8 +230,8 @@ static inline int fsverity_ioctl_read_metadata(struct file *filp,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* verify.c */
 | 
					/* verify.c */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline bool fsverity_verify_blocks(struct page *page, unsigned int len,
 | 
					static inline bool fsverity_verify_blocks(struct folio *folio, size_t len,
 | 
				
			||||||
					  unsigned int offset)
 | 
										  size_t offset)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	WARN_ON(1);
 | 
						WARN_ON(1);
 | 
				
			||||||
	return false;
 | 
						return false;
 | 
				
			||||||
| 
						 | 
					@ -249,9 +249,14 @@ static inline void fsverity_enqueue_verify_work(struct work_struct *work)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif	/* !CONFIG_FS_VERITY */
 | 
					#endif	/* !CONFIG_FS_VERITY */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline bool fsverity_verify_folio(struct folio *folio)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return fsverity_verify_blocks(folio, folio_size(folio), 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline bool fsverity_verify_page(struct page *page)
 | 
					static inline bool fsverity_verify_page(struct page *page)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return fsverity_verify_blocks(page, PAGE_SIZE, 0);
 | 
						return fsverity_verify_blocks(page_folio(page), PAGE_SIZE, 0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue