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 @@ out:
|
||||||
|
|
||||||
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…
Add table
Reference in a new issue