mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-16 17:23:55 +08:00
fsverity updates for 6.3
Fix the longstanding implementation limitation that fsverity was only supported when the Merkle tree block size, filesystem block size, and PAGE_SIZE were all equal. Specifically, add support for Merkle tree block sizes less than PAGE_SIZE, and make ext4 support fsverity on filesystems where the filesystem block size is less than PAGE_SIZE. Effectively, this means that fsverity can now be used on systems with non-4K pages, at least on ext4. These changes have been tested using the verity group of xfstests, newly updated to cover the new code paths. Also update fs/verity/ to support verifying data from large folios. There's also a similar patch for fs/crypto/, to support decrypting data from large folios, which I'm including in this pull request to avoid a merge conflict between the fscrypt and fsverity branches. There will be a merge conflict in fs/buffer.c with some of the foliation work in the mm tree. Please use the merge resolution from linux-next. -----BEGIN PGP SIGNATURE----- iIoEABYIADIWIQSacvsUNc7UX4ntmEPzXCl4vpKOKwUCY/KJtRQcZWJpZ2dlcnNA Z29vZ2xlLmNvbQAKCRDzXCl4vpKOK/A/AP0RUlCClBRuHwXPRG0we8R1L153ga4s Vl+xRpCr+SswXwEAiOEpYN5cXoVKzNgxbEXo2pQzxi5lrpjZgUI6CL3DuQs= =ZRFX -----END PGP SIGNATURE----- Merge tag 'fsverity-for-linus' of git://git.kernel.org/pub/scm/fs/fsverity/linux Pull fsverity updates from Eric Biggers: "Fix the longstanding implementation limitation that fsverity was only supported when the Merkle tree block size, filesystem block size, and PAGE_SIZE were all equal. Specifically, add support for Merkle tree block sizes less than PAGE_SIZE, and make ext4 support fsverity on filesystems where the filesystem block size is less than PAGE_SIZE. Effectively, this means that fsverity can now be used on systems with non-4K pages, at least on ext4. These changes have been tested using the verity group of xfstests, newly updated to cover the new code paths. Also update fs/verity/ to support verifying data from large folios. There's also a similar patch for fs/crypto/, to support decrypting data from large folios, which I'm including in here to avoid a merge conflict between the fscrypt and fsverity branches" * tag 'fsverity-for-linus' of git://git.kernel.org/pub/scm/fs/fsverity/linux: fscrypt: support decrypting data from large folios fsverity: support verifying data from large folios fsverity.rst: update git repo URL for fsverity-utils ext4: allow verity with fs block size < PAGE_SIZE fs/buffer.c: support fsverity in block_read_full_folio() f2fs: simplify f2fs_readpage_limit() ext4: simplify ext4_readpage_limit() fsverity: support enabling with tree block size < PAGE_SIZE fsverity: support verification with tree block size < PAGE_SIZE fsverity: replace fsverity_hash_page() with fsverity_hash_block() fsverity: use EFBIG for file too large to enable verity fsverity: store log2(digest_size) precomputed fsverity: simplify Merkle tree readahead size calculation fsverity: use unsigned long for level_start fsverity: remove debug messages and CONFIG_FS_VERITY_DEBUG fsverity: pass pos and size to ->write_merkle_tree_block fsverity: optimize fsverity_cleanup_inode() on non-verity files fsverity: optimize fsverity_prepare_setattr() on non-verity files fsverity: optimize fsverity_file_open() on non-verity files
This commit is contained in:
commit
6639c3ce7f
@ -1277,8 +1277,8 @@ the file contents themselves, as described below:
|
|||||||
|
|
||||||
For the read path (->read_folio()) of regular files, filesystems can
|
For the read path (->read_folio()) of regular files, filesystems can
|
||||||
read the ciphertext into the page cache and decrypt it in-place. The
|
read the ciphertext into the page cache and decrypt it in-place. The
|
||||||
page lock must be held until decryption has finished, to prevent the
|
folio lock must be held until decryption has finished, to prevent the
|
||||||
page from becoming visible to userspace prematurely.
|
folio from becoming visible to userspace prematurely.
|
||||||
|
|
||||||
For the write path (->writepage()) of regular files, filesystems
|
For the write path (->writepage()) of regular files, filesystems
|
||||||
cannot encrypt data in-place in the page cache, since the cached
|
cannot encrypt data in-place in the page cache, since the cached
|
||||||
|
@ -118,10 +118,11 @@ as follows:
|
|||||||
- ``hash_algorithm`` must be the identifier for the hash algorithm to
|
- ``hash_algorithm`` must be the identifier for the hash algorithm to
|
||||||
use for the Merkle tree, such as FS_VERITY_HASH_ALG_SHA256. See
|
use for the Merkle tree, such as FS_VERITY_HASH_ALG_SHA256. See
|
||||||
``include/uapi/linux/fsverity.h`` for the list of possible values.
|
``include/uapi/linux/fsverity.h`` for the list of possible values.
|
||||||
- ``block_size`` must be the Merkle tree block size. Currently, this
|
- ``block_size`` is the Merkle tree block size, in bytes. In Linux
|
||||||
must be equal to the system page size, which is usually 4096 bytes.
|
v6.3 and later, this can be any power of 2 between (inclusively)
|
||||||
Other sizes may be supported in the future. This value is not
|
1024 and the minimum of the system page size and the filesystem
|
||||||
necessarily the same as the filesystem block size.
|
block size. In earlier versions, the page size was the only allowed
|
||||||
|
value.
|
||||||
- ``salt_size`` is the size of the salt in bytes, or 0 if no salt is
|
- ``salt_size`` is the size of the salt in bytes, or 0 if no salt is
|
||||||
provided. The salt is a value that is prepended to every hashed
|
provided. The salt is a value that is prepended to every hashed
|
||||||
block; it can be used to personalize the hashing for a particular
|
block; it can be used to personalize the hashing for a particular
|
||||||
@ -161,6 +162,7 @@ FS_IOC_ENABLE_VERITY can fail with the following errors:
|
|||||||
- ``EBUSY``: this ioctl is already running on the file
|
- ``EBUSY``: this ioctl is already running on the file
|
||||||
- ``EEXIST``: the file already has verity enabled
|
- ``EEXIST``: the file already has verity enabled
|
||||||
- ``EFAULT``: the caller provided inaccessible memory
|
- ``EFAULT``: the caller provided inaccessible memory
|
||||||
|
- ``EFBIG``: the file is too large to enable verity on
|
||||||
- ``EINTR``: the operation was interrupted by a fatal signal
|
- ``EINTR``: the operation was interrupted by a fatal signal
|
||||||
- ``EINVAL``: unsupported version, hash algorithm, or block size; or
|
- ``EINVAL``: unsupported version, hash algorithm, or block size; or
|
||||||
reserved bits are set; or the file descriptor refers to neither a
|
reserved bits are set; or the file descriptor refers to neither a
|
||||||
@ -495,9 +497,11 @@ To create verity files on an ext4 filesystem, the filesystem must have
|
|||||||
been formatted with ``-O verity`` or had ``tune2fs -O verity`` run on
|
been formatted with ``-O verity`` or had ``tune2fs -O verity`` run on
|
||||||
it. "verity" is an RO_COMPAT filesystem feature, so once set, old
|
it. "verity" is an RO_COMPAT filesystem feature, so once set, old
|
||||||
kernels will only be able to mount the filesystem readonly, and old
|
kernels will only be able to mount the filesystem readonly, and old
|
||||||
versions of e2fsck will be unable to check the filesystem. Moreover,
|
versions of e2fsck will be unable to check the filesystem.
|
||||||
currently ext4 only supports mounting a filesystem with the "verity"
|
|
||||||
feature when its block size is equal to PAGE_SIZE (often 4096 bytes).
|
Originally, an ext4 filesystem with the "verity" feature could only be
|
||||||
|
mounted when its block size was equal to the system page size
|
||||||
|
(typically 4096 bytes). In Linux v6.3, this limitation was removed.
|
||||||
|
|
||||||
ext4 sets the EXT4_VERITY_FL on-disk inode flag on verity files. It
|
ext4 sets the EXT4_VERITY_FL on-disk inode flag on verity files. It
|
||||||
can only be set by `FS_IOC_ENABLE_VERITY`_, and it cannot be cleared.
|
can only be set by `FS_IOC_ENABLE_VERITY`_, and it cannot be cleared.
|
||||||
@ -518,9 +522,7 @@ support paging multi-gigabyte xattrs into memory, and to support
|
|||||||
encrypting xattrs. Note that the verity metadata *must* be encrypted
|
encrypting xattrs. Note that the verity metadata *must* be encrypted
|
||||||
when the file is, since it contains hashes of the plaintext data.
|
when the file is, since it contains hashes of the plaintext data.
|
||||||
|
|
||||||
Currently, ext4 verity only supports the case where the Merkle tree
|
ext4 only allows verity on extent-based files.
|
||||||
block size, filesystem block size, and page size are all the same. It
|
|
||||||
also only supports extent-based files.
|
|
||||||
|
|
||||||
f2fs
|
f2fs
|
||||||
----
|
----
|
||||||
@ -538,11 +540,10 @@ Like ext4, f2fs stores the verity metadata (Merkle tree and
|
|||||||
fsverity_descriptor) past the end of the file, starting at the first
|
fsverity_descriptor) past the end of the file, starting at the first
|
||||||
64K boundary beyond i_size. See explanation for ext4 above.
|
64K boundary beyond i_size. See explanation for ext4 above.
|
||||||
Moreover, f2fs supports at most 4096 bytes of xattr entries per inode
|
Moreover, f2fs supports at most 4096 bytes of xattr entries per inode
|
||||||
which wouldn't be enough for even a single Merkle tree block.
|
which usually wouldn't be enough for even a single Merkle tree block.
|
||||||
|
|
||||||
Currently, f2fs verity only supports a Merkle tree block size of 4096.
|
f2fs doesn't support enabling verity on files that currently have
|
||||||
Also, f2fs doesn't support enabling verity on files that currently
|
atomic or volatile writes pending.
|
||||||
have atomic or volatile writes pending.
|
|
||||||
|
|
||||||
btrfs
|
btrfs
|
||||||
-----
|
-----
|
||||||
@ -567,51 +568,48 @@ 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 a function fsverity_verify_page() which
|
Therefore, fs/verity/ provides the function fsverity_verify_blocks()
|
||||||
verifies a page that has been read into the pagecache of a verity
|
which verifies data that has been read into the pagecache of a verity
|
||||||
inode, but is still locked and not Uptodate, so it's not yet readable
|
inode. The containing folio must still be locked and not Uptodate, so
|
||||||
by userspace. As needed to do the verification,
|
it's not yet readable by userspace. As needed to do the verification,
|
||||||
fsverity_verify_page() will call back into the filesystem to read
|
fsverity_verify_blocks() will call back into the filesystem to read
|
||||||
Merkle tree pages via fsverity_operations::read_merkle_tree_page().
|
hash blocks via fsverity_operations::read_merkle_tree_page().
|
||||||
|
|
||||||
fsverity_verify_page() 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.
|
||||||
|
|
||||||
fsverity_verify_page() currently only supports the case where the
|
In principle, verifying a data block requires verifying the entire
|
||||||
Merkle tree block size is equal to PAGE_SIZE (often 4096 bytes).
|
path in the Merkle tree from the data block to the root hash.
|
||||||
|
However, for efficiency the filesystem may cache the hash blocks.
|
||||||
In principle, fsverity_verify_page() verifies the entire path in the
|
Therefore, fsverity_verify_blocks() only ascends the tree reading hash
|
||||||
Merkle tree from the data page to the root hash. However, for
|
blocks until an already-verified hash block is seen. It then verifies
|
||||||
efficiency the filesystem may cache the hash pages. Therefore,
|
the path to that block.
|
||||||
fsverity_verify_page() only ascends the tree reading hash pages until
|
|
||||||
an already-verified hash page is seen, as indicated by the PageChecked
|
|
||||||
bit being set. It then verifies the path to that page.
|
|
||||||
|
|
||||||
This optimization, which is also used by dm-verity, results in
|
This optimization, which is also used by dm-verity, results in
|
||||||
excellent sequential read performance. This is because usually (e.g.
|
excellent sequential read performance. This is because usually (e.g.
|
||||||
127 in 128 times for 4K blocks and SHA-256) the hash page from the
|
127 in 128 times for 4K blocks and SHA-256) the hash block from the
|
||||||
bottom level of the tree will already be cached and checked from
|
bottom level of the tree will already be cached and checked from
|
||||||
reading a previous data page. However, random reads perform worse.
|
reading a previous data block. However, random reads perform worse.
|
||||||
|
|
||||||
Block device based filesystems
|
Block device based filesystems
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Block device based filesystems (e.g. ext4 and f2fs) in Linux also use
|
Block device based filesystems (e.g. ext4 and f2fs) in Linux also use
|
||||||
the pagecache, so the above subsection applies too. However, they
|
the pagecache, so the above subsection applies too. However, they
|
||||||
also usually read many pages from a file at once, grouped into a
|
also usually read many data blocks from a file at once, grouped into a
|
||||||
structure called a "bio". To make it easier for these types of
|
structure called a "bio". To make it easier for these types of
|
||||||
filesystems to support fs-verity, fs/verity/ also provides a function
|
filesystems to support fs-verity, fs/verity/ also provides a function
|
||||||
fsverity_verify_bio() which verifies all pages in a bio.
|
fsverity_verify_bio() which verifies all data blocks in a bio.
|
||||||
|
|
||||||
ext4 and f2fs also support encryption. If a verity file is also
|
ext4 and f2fs also support encryption. If a verity file is also
|
||||||
encrypted, the pages must be decrypted before being verified. To
|
encrypted, the data must be decrypted before being verified. To
|
||||||
support this, these filesystems allocate a "post-read context" for
|
support this, these filesystems allocate a "post-read context" for
|
||||||
each bio and store it in ``->bi_private``::
|
each bio and store it in ``->bi_private``::
|
||||||
|
|
||||||
@ -626,14 +624,14 @@ 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 holes and sets the corresponding pages
|
``->readahead()`` simply zeroes hole blocks and considers the
|
||||||
Uptodate; no bios are issued. To prevent this case from bypassing
|
corresponding data to be up-to-date; no bios are issued. To prevent
|
||||||
fs-verity, these filesystems use fsverity_verify_page() to verify hole
|
this case from bypassing fs-verity, filesystems use
|
||||||
pages.
|
fsverity_verify_blocks() to verify hole blocks.
|
||||||
|
|
||||||
Filesystems also disable direct I/O on verity files, since otherwise
|
Filesystems also disable direct I/O on verity files, since otherwise
|
||||||
direct I/O would bypass fs-verity.
|
direct I/O would bypass fs-verity.
|
||||||
@ -644,7 +642,7 @@ Userspace utility
|
|||||||
This document focuses on the kernel, but a userspace utility for
|
This document focuses on the kernel, but a userspace utility for
|
||||||
fs-verity can be found at:
|
fs-verity can be found at:
|
||||||
|
|
||||||
https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/fsverity-utils.git
|
https://git.kernel.org/pub/scm/fs/fsverity/fsverity-utils.git
|
||||||
|
|
||||||
See the README.md file in the fsverity-utils source tree for details,
|
See the README.md file in the fsverity-utils source tree for details,
|
||||||
including examples of setting up fs-verity protected files.
|
including examples of setting up fs-verity protected files.
|
||||||
@ -793,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.
|
||||||
|
@ -784,29 +784,24 @@ again:
|
|||||||
* fsverity op that writes a Merkle tree block into the btree.
|
* fsverity op that writes a Merkle tree block into the btree.
|
||||||
*
|
*
|
||||||
* @inode: inode to write a Merkle tree block for
|
* @inode: inode to write a Merkle tree block for
|
||||||
* @buf: Merkle tree data block to write
|
* @buf: Merkle tree block to write
|
||||||
* @index: index of the block in the Merkle tree
|
* @pos: the position of the block in the Merkle tree (in bytes)
|
||||||
* @log_blocksize: log base 2 of the Merkle tree block size
|
* @size: the Merkle tree block size (in bytes)
|
||||||
*
|
|
||||||
* Note that the block size could be different from the page size, so it is not
|
|
||||||
* safe to assume that index is a page index.
|
|
||||||
*
|
*
|
||||||
* Returns 0 on success or negative error code on failure
|
* Returns 0 on success or negative error code on failure
|
||||||
*/
|
*/
|
||||||
static int btrfs_write_merkle_tree_block(struct inode *inode, const void *buf,
|
static int btrfs_write_merkle_tree_block(struct inode *inode, const void *buf,
|
||||||
u64 index, int log_blocksize)
|
u64 pos, unsigned int size)
|
||||||
{
|
{
|
||||||
u64 off = index << log_blocksize;
|
|
||||||
u64 len = 1ULL << log_blocksize;
|
|
||||||
loff_t merkle_pos = merkle_file_pos(inode);
|
loff_t merkle_pos = merkle_file_pos(inode);
|
||||||
|
|
||||||
if (merkle_pos < 0)
|
if (merkle_pos < 0)
|
||||||
return merkle_pos;
|
return merkle_pos;
|
||||||
if (merkle_pos > inode->i_sb->s_maxbytes - off - len)
|
if (merkle_pos > inode->i_sb->s_maxbytes - pos - size)
|
||||||
return -EFBIG;
|
return -EFBIG;
|
||||||
|
|
||||||
return write_key_bytes(BTRFS_I(inode), BTRFS_VERITY_MERKLE_ITEM_KEY,
|
return write_key_bytes(BTRFS_I(inode), BTRFS_VERITY_MERKLE_ITEM_KEY,
|
||||||
off, buf, len);
|
pos, buf, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct fsverity_operations btrfs_verityops = {
|
const struct fsverity_operations btrfs_verityops = {
|
||||||
|
70
fs/buffer.c
70
fs/buffer.c
@ -48,6 +48,7 @@
|
|||||||
#include <linux/sched/mm.h>
|
#include <linux/sched/mm.h>
|
||||||
#include <trace/events/block.h>
|
#include <trace/events/block.h>
|
||||||
#include <linux/fscrypt.h>
|
#include <linux/fscrypt.h>
|
||||||
|
#include <linux/fsverity.h>
|
||||||
|
|
||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
|
|
||||||
@ -295,20 +296,53 @@ still_busy:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct decrypt_bh_ctx {
|
struct postprocess_bh_ctx {
|
||||||
struct work_struct work;
|
struct work_struct work;
|
||||||
struct buffer_head *bh;
|
struct buffer_head *bh;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void verify_bh(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct postprocess_bh_ctx *ctx =
|
||||||
|
container_of(work, struct postprocess_bh_ctx, work);
|
||||||
|
struct buffer_head *bh = ctx->bh;
|
||||||
|
bool valid;
|
||||||
|
|
||||||
|
valid = fsverity_verify_blocks(page_folio(bh->b_page), bh->b_size,
|
||||||
|
bh_offset(bh));
|
||||||
|
end_buffer_async_read(bh, valid);
|
||||||
|
kfree(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool need_fsverity(struct buffer_head *bh)
|
||||||
|
{
|
||||||
|
struct page *page = bh->b_page;
|
||||||
|
struct inode *inode = page->mapping->host;
|
||||||
|
|
||||||
|
return fsverity_active(inode) &&
|
||||||
|
/* needed by ext4 */
|
||||||
|
page->index < DIV_ROUND_UP(inode->i_size, PAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
static void decrypt_bh(struct work_struct *work)
|
static void decrypt_bh(struct work_struct *work)
|
||||||
{
|
{
|
||||||
struct decrypt_bh_ctx *ctx =
|
struct postprocess_bh_ctx *ctx =
|
||||||
container_of(work, struct decrypt_bh_ctx, work);
|
container_of(work, struct postprocess_bh_ctx, work);
|
||||||
struct buffer_head *bh = ctx->bh;
|
struct buffer_head *bh = ctx->bh;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = fscrypt_decrypt_pagecache_blocks(bh->b_page, bh->b_size,
|
err = fscrypt_decrypt_pagecache_blocks(page_folio(bh->b_page),
|
||||||
bh_offset(bh));
|
bh->b_size, bh_offset(bh));
|
||||||
|
if (err == 0 && need_fsverity(bh)) {
|
||||||
|
/*
|
||||||
|
* We use different work queues for decryption and for verity
|
||||||
|
* because verity may require reading metadata pages that need
|
||||||
|
* decryption, and we shouldn't recurse to the same workqueue.
|
||||||
|
*/
|
||||||
|
INIT_WORK(&ctx->work, verify_bh);
|
||||||
|
fsverity_enqueue_verify_work(&ctx->work);
|
||||||
|
return;
|
||||||
|
}
|
||||||
end_buffer_async_read(bh, err == 0);
|
end_buffer_async_read(bh, err == 0);
|
||||||
kfree(ctx);
|
kfree(ctx);
|
||||||
}
|
}
|
||||||
@ -319,15 +353,24 @@ static void decrypt_bh(struct work_struct *work)
|
|||||||
*/
|
*/
|
||||||
static void end_buffer_async_read_io(struct buffer_head *bh, int uptodate)
|
static void end_buffer_async_read_io(struct buffer_head *bh, int uptodate)
|
||||||
{
|
{
|
||||||
/* Decrypt if needed */
|
struct inode *inode = bh->b_page->mapping->host;
|
||||||
if (uptodate &&
|
bool decrypt = fscrypt_inode_uses_fs_layer_crypto(inode);
|
||||||
fscrypt_inode_uses_fs_layer_crypto(bh->b_page->mapping->host)) {
|
bool verify = need_fsverity(bh);
|
||||||
struct decrypt_bh_ctx *ctx = kmalloc(sizeof(*ctx), GFP_ATOMIC);
|
|
||||||
|
/* Decrypt (with fscrypt) and/or verify (with fsverity) if needed. */
|
||||||
|
if (uptodate && (decrypt || verify)) {
|
||||||
|
struct postprocess_bh_ctx *ctx =
|
||||||
|
kmalloc(sizeof(*ctx), GFP_ATOMIC);
|
||||||
|
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
INIT_WORK(&ctx->work, decrypt_bh);
|
|
||||||
ctx->bh = bh;
|
ctx->bh = bh;
|
||||||
|
if (decrypt) {
|
||||||
|
INIT_WORK(&ctx->work, decrypt_bh);
|
||||||
fscrypt_enqueue_decrypt_work(&ctx->work);
|
fscrypt_enqueue_decrypt_work(&ctx->work);
|
||||||
|
} else {
|
||||||
|
INIT_WORK(&ctx->work, verify_bh);
|
||||||
|
fsverity_enqueue_verify_work(&ctx->work);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uptodate = 0;
|
uptodate = 0;
|
||||||
@ -2245,6 +2288,11 @@ int block_read_full_folio(struct folio *folio, get_block_t *get_block)
|
|||||||
int nr, i;
|
int nr, i;
|
||||||
int fully_mapped = 1;
|
int fully_mapped = 1;
|
||||||
bool page_error = false;
|
bool page_error = false;
|
||||||
|
loff_t limit = i_size_read(inode);
|
||||||
|
|
||||||
|
/* This is needed for ext4. */
|
||||||
|
if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode))
|
||||||
|
limit = inode->i_sb->s_maxbytes;
|
||||||
|
|
||||||
VM_BUG_ON_FOLIO(folio_test_large(folio), folio);
|
VM_BUG_ON_FOLIO(folio_test_large(folio), folio);
|
||||||
|
|
||||||
@ -2253,7 +2301,7 @@ int block_read_full_folio(struct folio *folio, get_block_t *get_block)
|
|||||||
bbits = block_size_bits(blocksize);
|
bbits = block_size_bits(blocksize);
|
||||||
|
|
||||||
iblock = (sector_t)folio->index << (PAGE_SHIFT - bbits);
|
iblock = (sector_t)folio->index << (PAGE_SHIFT - bbits);
|
||||||
lblock = (i_size_read(inode)+blocksize-1) >> bbits;
|
lblock = (limit+blocksize-1) >> bbits;
|
||||||
bh = head;
|
bh = head;
|
||||||
nr = 0;
|
nr = 0;
|
||||||
i = 0;
|
i = 0;
|
||||||
|
@ -30,13 +30,11 @@
|
|||||||
*/
|
*/
|
||||||
bool fscrypt_decrypt_bio(struct bio *bio)
|
bool fscrypt_decrypt_bio(struct bio *bio)
|
||||||
{
|
{
|
||||||
struct bio_vec *bv;
|
struct folio_iter fi;
|
||||||
struct bvec_iter_all iter_all;
|
|
||||||
|
|
||||||
bio_for_each_segment_all(bv, bio, iter_all) {
|
bio_for_each_folio_all(fi, bio) {
|
||||||
struct page *page = bv->bv_page;
|
int err = fscrypt_decrypt_pagecache_blocks(fi.folio, fi.length,
|
||||||
int err = fscrypt_decrypt_pagecache_blocks(page, bv->bv_len,
|
fi.offset);
|
||||||
bv->bv_offset);
|
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
bio->bi_status = errno_to_blk_status(err);
|
bio->bi_status = errno_to_blk_status(err);
|
||||||
|
@ -237,41 +237,43 @@ EXPORT_SYMBOL(fscrypt_encrypt_block_inplace);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* fscrypt_decrypt_pagecache_blocks() - Decrypt filesystem blocks in a
|
* fscrypt_decrypt_pagecache_blocks() - Decrypt filesystem blocks in a
|
||||||
* pagecache page
|
* pagecache folio
|
||||||
* @page: The locked pagecache page containing the block(s) to decrypt
|
* @folio: The locked pagecache folio containing the block(s) to decrypt
|
||||||
* @len: Total size of the block(s) to decrypt. Must be a nonzero
|
* @len: Total size of the block(s) to decrypt. Must be a nonzero
|
||||||
* multiple of the filesystem's block size.
|
* multiple of the filesystem's block size.
|
||||||
* @offs: Byte offset within @page of the first block to decrypt. Must be
|
* @offs: Byte offset within @folio of the first block to decrypt. Must be
|
||||||
* a multiple of the filesystem's block size.
|
* a multiple of the filesystem's block size.
|
||||||
*
|
*
|
||||||
* The specified block(s) are decrypted in-place within the pagecache page,
|
* The specified block(s) are decrypted in-place within the pagecache folio,
|
||||||
* which must still be locked and not uptodate. Normally, blocksize ==
|
* which must still be locked and not uptodate.
|
||||||
* PAGE_SIZE and the whole page is decrypted at once.
|
|
||||||
*
|
*
|
||||||
* This is for use by the filesystem's ->readahead() method.
|
* This is for use by the filesystem's ->readahead() method.
|
||||||
*
|
*
|
||||||
* Return: 0 on success; -errno on failure
|
* Return: 0 on success; -errno on failure
|
||||||
*/
|
*/
|
||||||
int fscrypt_decrypt_pagecache_blocks(struct page *page, unsigned int len,
|
int fscrypt_decrypt_pagecache_blocks(struct folio *folio, size_t len,
|
||||||
unsigned int offs)
|
size_t offs)
|
||||||
{
|
{
|
||||||
const struct inode *inode = page->mapping->host;
|
const struct inode *inode = folio->mapping->host;
|
||||||
const unsigned int blockbits = inode->i_blkbits;
|
const unsigned int blockbits = inode->i_blkbits;
|
||||||
const unsigned int blocksize = 1 << blockbits;
|
const unsigned int blocksize = 1 << blockbits;
|
||||||
u64 lblk_num = ((u64)page->index << (PAGE_SHIFT - blockbits)) +
|
u64 lblk_num = ((u64)folio->index << (PAGE_SHIFT - blockbits)) +
|
||||||
(offs >> blockbits);
|
(offs >> blockbits);
|
||||||
unsigned int i;
|
size_t i;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (WARN_ON_ONCE(!PageLocked(page)))
|
if (WARN_ON_ONCE(!folio_test_locked(folio)))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
if (WARN_ON_ONCE(len <= 0 || !IS_ALIGNED(len | offs, blocksize)))
|
if (WARN_ON_ONCE(len <= 0 || !IS_ALIGNED(len | offs, blocksize)))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
for (i = offs; i < offs + len; i += blocksize, lblk_num++) {
|
for (i = offs; i < offs + len; i += blocksize, lblk_num++) {
|
||||||
|
struct page *page = folio_page(folio, i >> PAGE_SHIFT);
|
||||||
|
|
||||||
err = fscrypt_crypt_block(inode, FS_DECRYPT, lblk_num, page,
|
err = fscrypt_crypt_block(inode, FS_DECRYPT, lblk_num, page,
|
||||||
page, blocksize, i, GFP_NOFS);
|
page, blocksize, i & ~PAGE_MASK,
|
||||||
|
GFP_NOFS);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@ -1136,7 +1136,8 @@ static int ext4_block_write_begin(struct page *page, loff_t pos, unsigned len,
|
|||||||
for (i = 0; i < nr_wait; i++) {
|
for (i = 0; i < nr_wait; i++) {
|
||||||
int err2;
|
int err2;
|
||||||
|
|
||||||
err2 = fscrypt_decrypt_pagecache_blocks(page, blocksize,
|
err2 = fscrypt_decrypt_pagecache_blocks(page_folio(page),
|
||||||
|
blocksize,
|
||||||
bh_offset(wait[i]));
|
bh_offset(wait[i]));
|
||||||
if (err2) {
|
if (err2) {
|
||||||
clear_buffer_uptodate(wait[i]);
|
clear_buffer_uptodate(wait[i]);
|
||||||
@ -3858,7 +3859,8 @@ static int __ext4_block_zero_page_range(handle_t *handle,
|
|||||||
if (fscrypt_inode_uses_fs_layer_crypto(inode)) {
|
if (fscrypt_inode_uses_fs_layer_crypto(inode)) {
|
||||||
/* We expect the key to be set. */
|
/* We expect the key to be set. */
|
||||||
BUG_ON(!fscrypt_has_encryption_key(inode));
|
BUG_ON(!fscrypt_has_encryption_key(inode));
|
||||||
err = fscrypt_decrypt_pagecache_blocks(page, blocksize,
|
err = fscrypt_decrypt_pagecache_blocks(page_folio(page),
|
||||||
|
blocksize,
|
||||||
bh_offset(bh));
|
bh_offset(bh));
|
||||||
if (err) {
|
if (err) {
|
||||||
clear_buffer_uptodate(bh);
|
clear_buffer_uptodate(bh);
|
||||||
|
@ -211,8 +211,7 @@ static void ext4_set_bio_post_read_ctx(struct bio *bio,
|
|||||||
|
|
||||||
static inline loff_t ext4_readpage_limit(struct inode *inode)
|
static inline loff_t ext4_readpage_limit(struct inode *inode)
|
||||||
{
|
{
|
||||||
if (IS_ENABLED(CONFIG_FS_VERITY) &&
|
if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode))
|
||||||
(IS_VERITY(inode) || ext4_verity_in_progress(inode)))
|
|
||||||
return inode->i_sb->s_maxbytes;
|
return inode->i_sb->s_maxbytes;
|
||||||
|
|
||||||
return i_size_read(inode);
|
return i_size_read(inode);
|
||||||
|
@ -5325,11 +5325,6 @@ static int __ext4_fill_super(struct fs_context *fc, struct super_block *sb)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ext4_has_feature_verity(sb) && sb->s_blocksize != PAGE_SIZE) {
|
|
||||||
ext4_msg(sb, KERN_ERR, "Unsupported blocksize for fs-verity");
|
|
||||||
goto failed_mount_wq;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the # of file system overhead blocks from the
|
* Get the # of file system overhead blocks from the
|
||||||
* superblock if present.
|
* superblock if present.
|
||||||
|
@ -381,11 +381,11 @@ static struct page *ext4_read_merkle_tree_page(struct inode *inode,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int ext4_write_merkle_tree_block(struct inode *inode, const void *buf,
|
static int ext4_write_merkle_tree_block(struct inode *inode, const void *buf,
|
||||||
u64 index, int log_blocksize)
|
u64 pos, unsigned int size)
|
||||||
{
|
{
|
||||||
loff_t pos = ext4_verity_metadata_pos(inode) + (index << log_blocksize);
|
pos += ext4_verity_metadata_pos(inode);
|
||||||
|
|
||||||
return pagecache_write(inode, buf, 1 << log_blocksize, pos);
|
return pagecache_write(inode, buf, size, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct fsverity_operations ext4_verityops = {
|
const struct fsverity_operations ext4_verityops = {
|
||||||
|
@ -2053,8 +2053,7 @@ out:
|
|||||||
|
|
||||||
static inline loff_t f2fs_readpage_limit(struct inode *inode)
|
static inline loff_t f2fs_readpage_limit(struct inode *inode)
|
||||||
{
|
{
|
||||||
if (IS_ENABLED(CONFIG_FS_VERITY) &&
|
if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode))
|
||||||
(IS_VERITY(inode) || f2fs_verity_in_progress(inode)))
|
|
||||||
return inode->i_sb->s_maxbytes;
|
return inode->i_sb->s_maxbytes;
|
||||||
|
|
||||||
return i_size_read(inode);
|
return i_size_read(inode);
|
||||||
|
@ -276,11 +276,11 @@ static struct page *f2fs_read_merkle_tree_page(struct inode *inode,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int f2fs_write_merkle_tree_block(struct inode *inode, const void *buf,
|
static int f2fs_write_merkle_tree_block(struct inode *inode, const void *buf,
|
||||||
u64 index, int log_blocksize)
|
u64 pos, unsigned int size)
|
||||||
{
|
{
|
||||||
loff_t pos = f2fs_verity_metadata_pos(inode) + (index << log_blocksize);
|
pos += f2fs_verity_metadata_pos(inode);
|
||||||
|
|
||||||
return pagecache_write(inode, buf, 1 << log_blocksize, pos);
|
return pagecache_write(inode, buf, size, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct fsverity_operations f2fs_verityops = {
|
const struct fsverity_operations f2fs_verityops = {
|
||||||
|
@ -34,14 +34,6 @@ config FS_VERITY
|
|||||||
|
|
||||||
If unsure, say N.
|
If unsure, say N.
|
||||||
|
|
||||||
config FS_VERITY_DEBUG
|
|
||||||
bool "FS Verity debugging"
|
|
||||||
depends on FS_VERITY
|
|
||||||
help
|
|
||||||
Enable debugging messages related to fs-verity by default.
|
|
||||||
|
|
||||||
Say N unless you are an fs-verity developer.
|
|
||||||
|
|
||||||
config FS_VERITY_BUILTIN_SIGNATURES
|
config FS_VERITY_BUILTIN_SIGNATURES
|
||||||
bool "FS Verity builtin signature support"
|
bool "FS Verity builtin signature support"
|
||||||
depends on FS_VERITY
|
depends on FS_VERITY
|
||||||
|
@ -7,137 +7,51 @@
|
|||||||
|
|
||||||
#include "fsverity_private.h"
|
#include "fsverity_private.h"
|
||||||
|
|
||||||
#include <crypto/hash.h>
|
|
||||||
#include <linux/backing-dev.h>
|
|
||||||
#include <linux/mount.h>
|
#include <linux/mount.h>
|
||||||
#include <linux/pagemap.h>
|
#include <linux/pagemap.h>
|
||||||
#include <linux/sched/signal.h>
|
#include <linux/sched/signal.h>
|
||||||
#include <linux/uaccess.h>
|
#include <linux/uaccess.h>
|
||||||
|
|
||||||
/*
|
struct block_buffer {
|
||||||
* Read a file data page for Merkle tree construction. Do aggressive readahead,
|
u32 filled;
|
||||||
* since we're sequentially reading the entire file.
|
u8 *data;
|
||||||
*/
|
};
|
||||||
static struct page *read_file_data_page(struct file *file, pgoff_t index,
|
|
||||||
struct file_ra_state *ra,
|
|
||||||
unsigned long remaining_pages)
|
|
||||||
{
|
|
||||||
DEFINE_READAHEAD(ractl, file, ra, file->f_mapping, index);
|
|
||||||
struct folio *folio;
|
|
||||||
|
|
||||||
folio = __filemap_get_folio(ractl.mapping, index, FGP_ACCESSED, 0);
|
/* Hash a block, writing the result to the next level's pending block buffer. */
|
||||||
if (!folio || !folio_test_uptodate(folio)) {
|
static int hash_one_block(struct inode *inode,
|
||||||
if (folio)
|
|
||||||
folio_put(folio);
|
|
||||||
else
|
|
||||||
page_cache_sync_ra(&ractl, remaining_pages);
|
|
||||||
folio = read_cache_folio(ractl.mapping, index, NULL, file);
|
|
||||||
if (IS_ERR(folio))
|
|
||||||
return &folio->page;
|
|
||||||
}
|
|
||||||
if (folio_test_readahead(folio))
|
|
||||||
page_cache_async_ra(&ractl, folio, remaining_pages);
|
|
||||||
return folio_file_page(folio, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int build_merkle_tree_level(struct file *filp, unsigned int level,
|
|
||||||
u64 num_blocks_to_hash,
|
|
||||||
const struct merkle_tree_params *params,
|
const struct merkle_tree_params *params,
|
||||||
u8 *pending_hashes,
|
struct ahash_request *req, struct block_buffer *cur)
|
||||||
struct ahash_request *req)
|
|
||||||
{
|
{
|
||||||
struct inode *inode = file_inode(filp);
|
struct block_buffer *next = cur + 1;
|
||||||
const struct fsverity_operations *vops = inode->i_sb->s_vop;
|
|
||||||
struct file_ra_state ra = { 0 };
|
|
||||||
unsigned int pending_size = 0;
|
|
||||||
u64 dst_block_num;
|
|
||||||
u64 i;
|
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (WARN_ON(params->block_size != PAGE_SIZE)) /* checked earlier too */
|
/* Zero-pad the block if it's shorter than the block size. */
|
||||||
return -EINVAL;
|
memset(&cur->data[cur->filled], 0, params->block_size - cur->filled);
|
||||||
|
|
||||||
if (level < params->num_levels) {
|
err = fsverity_hash_block(params, inode, req, virt_to_page(cur->data),
|
||||||
dst_block_num = params->level_start[level];
|
offset_in_page(cur->data),
|
||||||
} else {
|
&next->data[next->filled]);
|
||||||
if (WARN_ON(num_blocks_to_hash != 1))
|
|
||||||
return -EINVAL;
|
|
||||||
dst_block_num = 0; /* unused */
|
|
||||||
}
|
|
||||||
|
|
||||||
file_ra_state_init(&ra, filp->f_mapping);
|
|
||||||
|
|
||||||
for (i = 0; i < num_blocks_to_hash; i++) {
|
|
||||||
struct page *src_page;
|
|
||||||
|
|
||||||
if ((pgoff_t)i % 10000 == 0 || i + 1 == num_blocks_to_hash)
|
|
||||||
pr_debug("Hashing block %llu of %llu for level %u\n",
|
|
||||||
i + 1, num_blocks_to_hash, level);
|
|
||||||
|
|
||||||
if (level == 0) {
|
|
||||||
/* Leaf: hashing a data block */
|
|
||||||
src_page = read_file_data_page(filp, i, &ra,
|
|
||||||
num_blocks_to_hash - i);
|
|
||||||
if (IS_ERR(src_page)) {
|
|
||||||
err = PTR_ERR(src_page);
|
|
||||||
fsverity_err(inode,
|
|
||||||
"Error %d reading data page %llu",
|
|
||||||
err, i);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unsigned long num_ra_pages =
|
|
||||||
min_t(unsigned long, num_blocks_to_hash - i,
|
|
||||||
inode->i_sb->s_bdi->io_pages);
|
|
||||||
|
|
||||||
/* Non-leaf: hashing hash block from level below */
|
|
||||||
src_page = vops->read_merkle_tree_page(inode,
|
|
||||||
params->level_start[level - 1] + i,
|
|
||||||
num_ra_pages);
|
|
||||||
if (IS_ERR(src_page)) {
|
|
||||||
err = PTR_ERR(src_page);
|
|
||||||
fsverity_err(inode,
|
|
||||||
"Error %d reading Merkle tree page %llu",
|
|
||||||
err, params->level_start[level - 1] + i);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fsverity_hash_page(params, inode, req, src_page,
|
|
||||||
&pending_hashes[pending_size]);
|
|
||||||
put_page(src_page);
|
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
pending_size += params->digest_size;
|
next->filled += params->digest_size;
|
||||||
|
cur->filled = 0;
|
||||||
if (level == params->num_levels) /* Root hash? */
|
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (pending_size + params->digest_size > params->block_size ||
|
static int write_merkle_tree_block(struct inode *inode, const u8 *buf,
|
||||||
i + 1 == num_blocks_to_hash) {
|
unsigned long index,
|
||||||
/* Flush the pending hash block */
|
const struct merkle_tree_params *params)
|
||||||
memset(&pending_hashes[pending_size], 0,
|
{
|
||||||
params->block_size - pending_size);
|
u64 pos = (u64)index << params->log_blocksize;
|
||||||
err = vops->write_merkle_tree_block(inode,
|
int err;
|
||||||
pending_hashes,
|
|
||||||
dst_block_num,
|
err = inode->i_sb->s_vop->write_merkle_tree_block(inode, buf, pos,
|
||||||
params->log_blocksize);
|
params->block_size);
|
||||||
if (err) {
|
if (err)
|
||||||
fsverity_err(inode,
|
fsverity_err(inode, "Error %d writing Merkle tree block %lu",
|
||||||
"Error %d writing Merkle tree block %llu",
|
err, index);
|
||||||
err, dst_block_num);
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
dst_block_num++;
|
|
||||||
pending_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fatal_signal_pending(current))
|
|
||||||
return -EINTR;
|
|
||||||
cond_resched();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Build the Merkle tree for the given file using the given parameters, and
|
* Build the Merkle tree for the given file using the given parameters, and
|
||||||
@ -152,13 +66,17 @@ static int build_merkle_tree(struct file *filp,
|
|||||||
u8 *root_hash)
|
u8 *root_hash)
|
||||||
{
|
{
|
||||||
struct inode *inode = file_inode(filp);
|
struct inode *inode = file_inode(filp);
|
||||||
u8 *pending_hashes;
|
const u64 data_size = inode->i_size;
|
||||||
|
const int num_levels = params->num_levels;
|
||||||
struct ahash_request *req;
|
struct ahash_request *req;
|
||||||
u64 blocks;
|
struct block_buffer _buffers[1 + FS_VERITY_MAX_LEVELS + 1] = {};
|
||||||
unsigned int level;
|
struct block_buffer *buffers = &_buffers[1];
|
||||||
int err = -ENOMEM;
|
unsigned long level_offset[FS_VERITY_MAX_LEVELS];
|
||||||
|
int level;
|
||||||
|
u64 offset;
|
||||||
|
int err;
|
||||||
|
|
||||||
if (inode->i_size == 0) {
|
if (data_size == 0) {
|
||||||
/* Empty file is a special case; root hash is all 0's */
|
/* Empty file is a special case; root hash is all 0's */
|
||||||
memset(root_hash, 0, params->digest_size);
|
memset(root_hash, 0, params->digest_size);
|
||||||
return 0;
|
return 0;
|
||||||
@ -167,29 +85,95 @@ static int build_merkle_tree(struct file *filp,
|
|||||||
/* This allocation never fails, since it's mempool-backed. */
|
/* This allocation never fails, since it's mempool-backed. */
|
||||||
req = fsverity_alloc_hash_request(params->hash_alg, GFP_KERNEL);
|
req = fsverity_alloc_hash_request(params->hash_alg, GFP_KERNEL);
|
||||||
|
|
||||||
pending_hashes = kmalloc(params->block_size, GFP_KERNEL);
|
|
||||||
if (!pending_hashes)
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Build each level of the Merkle tree, starting at the leaf level
|
* Allocate the block buffers. Buffer "-1" is for data blocks.
|
||||||
* (level 0) and ascending to the root node (level 'num_levels - 1').
|
* Buffers 0 <= level < num_levels are for the actual tree levels.
|
||||||
* Then at the end (level 'num_levels'), calculate the root hash.
|
* Buffer 'num_levels' is for the root hash.
|
||||||
*/
|
*/
|
||||||
blocks = ((u64)inode->i_size + params->block_size - 1) >>
|
for (level = -1; level < num_levels; level++) {
|
||||||
params->log_blocksize;
|
buffers[level].data = kzalloc(params->block_size, GFP_KERNEL);
|
||||||
for (level = 0; level <= params->num_levels; level++) {
|
if (!buffers[level].data) {
|
||||||
err = build_merkle_tree_level(filp, level, blocks, params,
|
err = -ENOMEM;
|
||||||
pending_hashes, req);
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffers[num_levels].data = root_hash;
|
||||||
|
|
||||||
|
BUILD_BUG_ON(sizeof(level_offset) != sizeof(params->level_start));
|
||||||
|
memcpy(level_offset, params->level_start, sizeof(level_offset));
|
||||||
|
|
||||||
|
/* Hash each data block, also hashing the tree blocks as they fill up */
|
||||||
|
for (offset = 0; offset < data_size; offset += params->block_size) {
|
||||||
|
ssize_t bytes_read;
|
||||||
|
loff_t pos = offset;
|
||||||
|
|
||||||
|
buffers[-1].filled = min_t(u64, params->block_size,
|
||||||
|
data_size - offset);
|
||||||
|
bytes_read = __kernel_read(filp, buffers[-1].data,
|
||||||
|
buffers[-1].filled, &pos);
|
||||||
|
if (bytes_read < 0) {
|
||||||
|
err = bytes_read;
|
||||||
|
fsverity_err(inode, "Error %d reading file data", err);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (bytes_read != buffers[-1].filled) {
|
||||||
|
err = -EINVAL;
|
||||||
|
fsverity_err(inode, "Short read of file data");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
err = hash_one_block(inode, params, req, &buffers[-1]);
|
||||||
if (err)
|
if (err)
|
||||||
goto out;
|
goto out;
|
||||||
blocks = (blocks + params->hashes_per_block - 1) >>
|
for (level = 0; level < num_levels; level++) {
|
||||||
params->log_arity;
|
if (buffers[level].filled + params->digest_size <=
|
||||||
|
params->block_size) {
|
||||||
|
/* Next block at @level isn't full yet */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* Next block at @level is full */
|
||||||
|
|
||||||
|
err = hash_one_block(inode, params, req,
|
||||||
|
&buffers[level]);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
err = write_merkle_tree_block(inode,
|
||||||
|
buffers[level].data,
|
||||||
|
level_offset[level],
|
||||||
|
params);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
level_offset[level]++;
|
||||||
|
}
|
||||||
|
if (fatal_signal_pending(current)) {
|
||||||
|
err = -EINTR;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
cond_resched();
|
||||||
|
}
|
||||||
|
/* Finish all nonempty pending tree blocks. */
|
||||||
|
for (level = 0; level < num_levels; level++) {
|
||||||
|
if (buffers[level].filled != 0) {
|
||||||
|
err = hash_one_block(inode, params, req,
|
||||||
|
&buffers[level]);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
err = write_merkle_tree_block(inode,
|
||||||
|
buffers[level].data,
|
||||||
|
level_offset[level],
|
||||||
|
params);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* The root hash was filled by the last call to hash_one_block(). */
|
||||||
|
if (WARN_ON(buffers[num_levels].filled != params->digest_size)) {
|
||||||
|
err = -EINVAL;
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
memcpy(root_hash, pending_hashes, params->digest_size);
|
|
||||||
err = 0;
|
err = 0;
|
||||||
out:
|
out:
|
||||||
kfree(pending_hashes);
|
for (level = -1; level < num_levels; level++)
|
||||||
|
kfree(buffers[level].data);
|
||||||
fsverity_free_hash_request(params->hash_alg, req);
|
fsverity_free_hash_request(params->hash_alg, req);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@ -263,15 +247,12 @@ static int enable_verity(struct file *filp,
|
|||||||
* ->begin_enable_verity() and ->end_enable_verity() using the inode
|
* ->begin_enable_verity() and ->end_enable_verity() using the inode
|
||||||
* lock and only allow one process to be here at a time on a given file.
|
* lock and only allow one process to be here at a time on a given file.
|
||||||
*/
|
*/
|
||||||
pr_debug("Building Merkle tree...\n");
|
|
||||||
BUILD_BUG_ON(sizeof(desc->root_hash) < FS_VERITY_MAX_DIGEST_SIZE);
|
BUILD_BUG_ON(sizeof(desc->root_hash) < FS_VERITY_MAX_DIGEST_SIZE);
|
||||||
err = build_merkle_tree(filp, ¶ms, desc->root_hash);
|
err = build_merkle_tree(filp, ¶ms, desc->root_hash);
|
||||||
if (err) {
|
if (err) {
|
||||||
fsverity_err(inode, "Error %d building Merkle tree", err);
|
fsverity_err(inode, "Error %d building Merkle tree", err);
|
||||||
goto rollback;
|
goto rollback;
|
||||||
}
|
}
|
||||||
pr_debug("Done building Merkle tree. Root hash is %s:%*phN\n",
|
|
||||||
params.hash_alg->name, params.digest_size, desc->root_hash);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create the fsverity_info. Don't bother trying to save work by
|
* Create the fsverity_info. Don't bother trying to save work by
|
||||||
@ -286,10 +267,6 @@ static int enable_verity(struct file *filp,
|
|||||||
goto rollback;
|
goto rollback;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg->sig_size)
|
|
||||||
pr_debug("Storing a %u-byte PKCS#7 signature alongside the file\n",
|
|
||||||
arg->sig_size);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Tell the filesystem to finish enabling verity on the file.
|
* Tell the filesystem to finish enabling verity on the file.
|
||||||
* Serialized with ->begin_enable_verity() by the inode lock.
|
* Serialized with ->begin_enable_verity() by the inode lock.
|
||||||
@ -352,7 +329,7 @@ int fsverity_ioctl_enable(struct file *filp, const void __user *uarg)
|
|||||||
memchr_inv(arg.__reserved2, 0, sizeof(arg.__reserved2)))
|
memchr_inv(arg.__reserved2, 0, sizeof(arg.__reserved2)))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
if (arg.block_size != PAGE_SIZE)
|
if (!is_power_of_2(arg.block_size))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
if (arg.salt_size > sizeof_field(struct fsverity_descriptor, salt))
|
if (arg.salt_size > sizeof_field(struct fsverity_descriptor, salt))
|
||||||
|
@ -8,10 +8,6 @@
|
|||||||
#ifndef _FSVERITY_PRIVATE_H
|
#ifndef _FSVERITY_PRIVATE_H
|
||||||
#define _FSVERITY_PRIVATE_H
|
#define _FSVERITY_PRIVATE_H
|
||||||
|
|
||||||
#ifdef CONFIG_FS_VERITY_DEBUG
|
|
||||||
#define DEBUG
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define pr_fmt(fmt) "fs-verity: " fmt
|
#define pr_fmt(fmt) "fs-verity: " fmt
|
||||||
|
|
||||||
#include <linux/fsverity.h>
|
#include <linux/fsverity.h>
|
||||||
@ -46,17 +42,20 @@ struct merkle_tree_params {
|
|||||||
unsigned int digest_size; /* same as hash_alg->digest_size */
|
unsigned int digest_size; /* same as hash_alg->digest_size */
|
||||||
unsigned int block_size; /* size of data and tree blocks */
|
unsigned int block_size; /* size of data and tree blocks */
|
||||||
unsigned int hashes_per_block; /* number of hashes per tree block */
|
unsigned int hashes_per_block; /* number of hashes per tree block */
|
||||||
unsigned int log_blocksize; /* log2(block_size) */
|
unsigned int blocks_per_page; /* PAGE_SIZE / block_size */
|
||||||
unsigned int log_arity; /* log2(hashes_per_block) */
|
u8 log_digestsize; /* log2(digest_size) */
|
||||||
|
u8 log_blocksize; /* log2(block_size) */
|
||||||
|
u8 log_arity; /* log2(hashes_per_block) */
|
||||||
|
u8 log_blocks_per_page; /* log2(blocks_per_page) */
|
||||||
unsigned int num_levels; /* number of levels in Merkle tree */
|
unsigned int num_levels; /* number of levels in Merkle tree */
|
||||||
u64 tree_size; /* Merkle tree size in bytes */
|
u64 tree_size; /* Merkle tree size in bytes */
|
||||||
unsigned long level0_blocks; /* number of blocks in tree level 0 */
|
unsigned long tree_pages; /* Merkle tree size in pages */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Starting block index for each tree level, ordered from leaf level (0)
|
* Starting block index for each tree level, ordered from leaf level (0)
|
||||||
* to root level ('num_levels - 1')
|
* to root level ('num_levels - 1')
|
||||||
*/
|
*/
|
||||||
u64 level_start[FS_VERITY_MAX_LEVELS];
|
unsigned long level_start[FS_VERITY_MAX_LEVELS];
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -73,9 +72,10 @@ struct fsverity_info {
|
|||||||
u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE];
|
u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE];
|
||||||
u8 file_digest[FS_VERITY_MAX_DIGEST_SIZE];
|
u8 file_digest[FS_VERITY_MAX_DIGEST_SIZE];
|
||||||
const struct inode *inode;
|
const struct inode *inode;
|
||||||
|
unsigned long *hash_block_verified;
|
||||||
|
spinlock_t hash_page_init_lock;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#define FS_VERITY_MAX_SIGNATURE_SIZE (FS_VERITY_MAX_DESCRIPTOR_SIZE - \
|
#define FS_VERITY_MAX_SIGNATURE_SIZE (FS_VERITY_MAX_DESCRIPTOR_SIZE - \
|
||||||
sizeof(struct fsverity_descriptor))
|
sizeof(struct fsverity_descriptor))
|
||||||
|
|
||||||
@ -91,9 +91,9 @@ void fsverity_free_hash_request(struct fsverity_hash_alg *alg,
|
|||||||
struct ahash_request *req);
|
struct ahash_request *req);
|
||||||
const u8 *fsverity_prepare_hash_state(struct fsverity_hash_alg *alg,
|
const u8 *fsverity_prepare_hash_state(struct fsverity_hash_alg *alg,
|
||||||
const u8 *salt, size_t salt_size);
|
const u8 *salt, size_t salt_size);
|
||||||
int fsverity_hash_page(const struct merkle_tree_params *params,
|
int fsverity_hash_block(const struct merkle_tree_params *params,
|
||||||
const struct inode *inode,
|
const struct inode *inode, struct ahash_request *req,
|
||||||
struct ahash_request *req, struct page *page, u8 *out);
|
struct page *page, unsigned int offset, u8 *out);
|
||||||
int fsverity_hash_buffer(struct fsverity_hash_alg *alg,
|
int fsverity_hash_buffer(struct fsverity_hash_alg *alg,
|
||||||
const void *data, size_t size, u8 *out);
|
const void *data, size_t size, u8 *out);
|
||||||
void __init fsverity_check_hash_algs(void);
|
void __init fsverity_check_hash_algs(void);
|
||||||
|
@ -220,35 +220,33 @@ err_free:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fsverity_hash_page() - hash a single data or hash page
|
* fsverity_hash_block() - hash a single data or hash block
|
||||||
* @params: the Merkle tree's parameters
|
* @params: the Merkle tree's parameters
|
||||||
* @inode: inode for which the hashing is being done
|
* @inode: inode for which the hashing is being done
|
||||||
* @req: preallocated hash request
|
* @req: preallocated hash request
|
||||||
* @page: the page to hash
|
* @page: the page containing the block to hash
|
||||||
|
* @offset: the offset of the block within @page
|
||||||
* @out: output digest, size 'params->digest_size' bytes
|
* @out: output digest, size 'params->digest_size' bytes
|
||||||
*
|
*
|
||||||
* Hash a single data or hash block, assuming block_size == PAGE_SIZE.
|
* Hash a single data or hash block. The hash is salted if a salt is specified
|
||||||
* The hash is salted if a salt is specified in the Merkle tree parameters.
|
* in the Merkle tree parameters.
|
||||||
*
|
*
|
||||||
* Return: 0 on success, -errno on failure
|
* Return: 0 on success, -errno on failure
|
||||||
*/
|
*/
|
||||||
int fsverity_hash_page(const struct merkle_tree_params *params,
|
int fsverity_hash_block(const struct merkle_tree_params *params,
|
||||||
const struct inode *inode,
|
const struct inode *inode, struct ahash_request *req,
|
||||||
struct ahash_request *req, struct page *page, u8 *out)
|
struct page *page, unsigned int offset, u8 *out)
|
||||||
{
|
{
|
||||||
struct scatterlist sg;
|
struct scatterlist sg;
|
||||||
DECLARE_CRYPTO_WAIT(wait);
|
DECLARE_CRYPTO_WAIT(wait);
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (WARN_ON(params->block_size != PAGE_SIZE))
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
sg_init_table(&sg, 1);
|
sg_init_table(&sg, 1);
|
||||||
sg_set_page(&sg, page, PAGE_SIZE, 0);
|
sg_set_page(&sg, page, params->block_size, offset);
|
||||||
ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP |
|
ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP |
|
||||||
CRYPTO_TFM_REQ_MAY_BACKLOG,
|
CRYPTO_TFM_REQ_MAY_BACKLOG,
|
||||||
crypto_req_done, &wait);
|
crypto_req_done, &wait);
|
||||||
ahash_request_set_crypt(req, &sg, out, PAGE_SIZE);
|
ahash_request_set_crypt(req, &sg, out, params->block_size);
|
||||||
|
|
||||||
if (params->hashstate) {
|
if (params->hashstate) {
|
||||||
err = crypto_ahash_import(req, params->hashstate);
|
err = crypto_ahash_import(req, params->hashstate);
|
||||||
@ -264,7 +262,7 @@ int fsverity_hash_page(const struct merkle_tree_params *params,
|
|||||||
|
|
||||||
err = crypto_wait_req(err, &wait);
|
err = crypto_wait_req(err, &wait);
|
||||||
if (err)
|
if (err)
|
||||||
fsverity_err(inode, "Error %d computing page hash", err);
|
fsverity_err(inode, "Error %d computing block hash", err);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,6 @@ static int __init fsverity_init(void)
|
|||||||
if (err)
|
if (err)
|
||||||
goto err_exit_workqueue;
|
goto err_exit_workqueue;
|
||||||
|
|
||||||
pr_debug("Initialized fs-verity\n");
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_exit_workqueue:
|
err_exit_workqueue:
|
||||||
|
163
fs/verity/open.c
163
fs/verity/open.c
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "fsverity_private.h"
|
#include "fsverity_private.h"
|
||||||
|
|
||||||
|
#include <linux/mm.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
|
||||||
static struct kmem_cache *fsverity_info_cachep;
|
static struct kmem_cache *fsverity_info_cachep;
|
||||||
@ -34,6 +35,7 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
|
|||||||
struct fsverity_hash_alg *hash_alg;
|
struct fsverity_hash_alg *hash_alg;
|
||||||
int err;
|
int err;
|
||||||
u64 blocks;
|
u64 blocks;
|
||||||
|
u64 blocks_in_level[FS_VERITY_MAX_LEVELS];
|
||||||
u64 offset;
|
u64 offset;
|
||||||
int level;
|
int level;
|
||||||
|
|
||||||
@ -54,7 +56,23 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
|
|||||||
goto out_err;
|
goto out_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log_blocksize != PAGE_SHIFT) {
|
/*
|
||||||
|
* fs/verity/ directly assumes that the Merkle tree block size is a
|
||||||
|
* power of 2 less than or equal to PAGE_SIZE. Another restriction
|
||||||
|
* arises from the interaction between fs/verity/ and the filesystems
|
||||||
|
* themselves: filesystems expect to be able to verify a single
|
||||||
|
* filesystem block of data at a time. Therefore, the Merkle tree block
|
||||||
|
* size must also be less than or equal to the filesystem block size.
|
||||||
|
*
|
||||||
|
* The above are the only hard limitations, so in theory the Merkle tree
|
||||||
|
* block size could be as small as twice the digest size. However,
|
||||||
|
* that's not useful, and it would result in some unusually deep and
|
||||||
|
* large Merkle trees. So we currently require that the Merkle tree
|
||||||
|
* block size be at least 1024 bytes. That's small enough to test the
|
||||||
|
* sub-page block case on systems with 4K pages, but not too small.
|
||||||
|
*/
|
||||||
|
if (log_blocksize < 10 || log_blocksize > PAGE_SHIFT ||
|
||||||
|
log_blocksize > inode->i_blkbits) {
|
||||||
fsverity_warn(inode, "Unsupported log_blocksize: %u",
|
fsverity_warn(inode, "Unsupported log_blocksize: %u",
|
||||||
log_blocksize);
|
log_blocksize);
|
||||||
err = -EINVAL;
|
err = -EINVAL;
|
||||||
@ -62,6 +80,8 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
|
|||||||
}
|
}
|
||||||
params->log_blocksize = log_blocksize;
|
params->log_blocksize = log_blocksize;
|
||||||
params->block_size = 1 << log_blocksize;
|
params->block_size = 1 << log_blocksize;
|
||||||
|
params->log_blocks_per_page = PAGE_SHIFT - log_blocksize;
|
||||||
|
params->blocks_per_page = 1 << params->log_blocks_per_page;
|
||||||
|
|
||||||
if (WARN_ON(!is_power_of_2(params->digest_size))) {
|
if (WARN_ON(!is_power_of_2(params->digest_size))) {
|
||||||
err = -EINVAL;
|
err = -EINVAL;
|
||||||
@ -74,13 +94,10 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
|
|||||||
err = -EINVAL;
|
err = -EINVAL;
|
||||||
goto out_err;
|
goto out_err;
|
||||||
}
|
}
|
||||||
params->log_arity = params->log_blocksize - ilog2(params->digest_size);
|
params->log_digestsize = ilog2(params->digest_size);
|
||||||
|
params->log_arity = log_blocksize - params->log_digestsize;
|
||||||
params->hashes_per_block = 1 << params->log_arity;
|
params->hashes_per_block = 1 << params->log_arity;
|
||||||
|
|
||||||
pr_debug("Merkle tree uses %s with %u-byte blocks (%u hashes/block), salt=%*phN\n",
|
|
||||||
hash_alg->name, params->block_size, params->hashes_per_block,
|
|
||||||
(int)salt_size, salt);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Compute the number of levels in the Merkle tree and create a map from
|
* Compute the number of levels in the Merkle tree and create a map from
|
||||||
* level to the starting block of that level. Level 'num_levels - 1' is
|
* level to the starting block of that level. Level 'num_levels - 1' is
|
||||||
@ -90,31 +107,45 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
|
|||||||
|
|
||||||
/* Compute number of levels and the number of blocks in each level */
|
/* Compute number of levels and the number of blocks in each level */
|
||||||
blocks = ((u64)inode->i_size + params->block_size - 1) >> log_blocksize;
|
blocks = ((u64)inode->i_size + params->block_size - 1) >> log_blocksize;
|
||||||
pr_debug("Data is %lld bytes (%llu blocks)\n", inode->i_size, blocks);
|
|
||||||
while (blocks > 1) {
|
while (blocks > 1) {
|
||||||
if (params->num_levels >= FS_VERITY_MAX_LEVELS) {
|
if (params->num_levels >= FS_VERITY_MAX_LEVELS) {
|
||||||
fsverity_err(inode, "Too many levels in Merkle tree");
|
fsverity_err(inode, "Too many levels in Merkle tree");
|
||||||
err = -EINVAL;
|
err = -EFBIG;
|
||||||
goto out_err;
|
goto out_err;
|
||||||
}
|
}
|
||||||
blocks = (blocks + params->hashes_per_block - 1) >>
|
blocks = (blocks + params->hashes_per_block - 1) >>
|
||||||
params->log_arity;
|
params->log_arity;
|
||||||
/* temporarily using level_start[] to store blocks in level */
|
blocks_in_level[params->num_levels++] = blocks;
|
||||||
params->level_start[params->num_levels++] = blocks;
|
|
||||||
}
|
}
|
||||||
params->level0_blocks = params->level_start[0];
|
|
||||||
|
|
||||||
/* Compute the starting block of each level */
|
/* Compute the starting block of each level */
|
||||||
offset = 0;
|
offset = 0;
|
||||||
for (level = (int)params->num_levels - 1; level >= 0; level--) {
|
for (level = (int)params->num_levels - 1; level >= 0; level--) {
|
||||||
blocks = params->level_start[level];
|
|
||||||
params->level_start[level] = offset;
|
params->level_start[level] = offset;
|
||||||
pr_debug("Level %d is %llu blocks starting at index %llu\n",
|
offset += blocks_in_level[level];
|
||||||
level, blocks, offset);
|
}
|
||||||
offset += blocks;
|
|
||||||
|
/*
|
||||||
|
* With block_size != PAGE_SIZE, an in-memory bitmap will need to be
|
||||||
|
* allocated to track the "verified" status of hash blocks. Don't allow
|
||||||
|
* this bitmap to get too large. For now, limit it to 1 MiB, which
|
||||||
|
* limits the file size to about 4.4 TB with SHA-256 and 4K blocks.
|
||||||
|
*
|
||||||
|
* Together with the fact that the data, and thus also the Merkle tree,
|
||||||
|
* cannot have more than ULONG_MAX pages, this implies that hash block
|
||||||
|
* indices can always fit in an 'unsigned long'. But to be safe, we
|
||||||
|
* explicitly check for that too. Note, this is only for hash block
|
||||||
|
* indices; data block indices might not fit in an 'unsigned long'.
|
||||||
|
*/
|
||||||
|
if ((params->block_size != PAGE_SIZE && offset > 1 << 23) ||
|
||||||
|
offset > ULONG_MAX) {
|
||||||
|
fsverity_err(inode, "Too many blocks in Merkle tree");
|
||||||
|
err = -EFBIG;
|
||||||
|
goto out_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
params->tree_size = offset << log_blocksize;
|
params->tree_size = offset << log_blocksize;
|
||||||
|
params->tree_pages = PAGE_ALIGN(params->tree_size) >> PAGE_SHIFT;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
out_err:
|
out_err:
|
||||||
@ -165,7 +196,7 @@ struct fsverity_info *fsverity_create_info(const struct inode *inode,
|
|||||||
fsverity_err(inode,
|
fsverity_err(inode,
|
||||||
"Error %d initializing Merkle tree parameters",
|
"Error %d initializing Merkle tree parameters",
|
||||||
err);
|
err);
|
||||||
goto out;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(vi->root_hash, desc->root_hash, vi->tree_params.digest_size);
|
memcpy(vi->root_hash, desc->root_hash, vi->tree_params.digest_size);
|
||||||
@ -174,20 +205,48 @@ struct fsverity_info *fsverity_create_info(const struct inode *inode,
|
|||||||
vi->file_digest);
|
vi->file_digest);
|
||||||
if (err) {
|
if (err) {
|
||||||
fsverity_err(inode, "Error %d computing file digest", err);
|
fsverity_err(inode, "Error %d computing file digest", err);
|
||||||
goto out;
|
goto fail;
|
||||||
}
|
}
|
||||||
pr_debug("Computed file digest: %s:%*phN\n",
|
|
||||||
vi->tree_params.hash_alg->name,
|
|
||||||
vi->tree_params.digest_size, vi->file_digest);
|
|
||||||
|
|
||||||
err = fsverity_verify_signature(vi, desc->signature,
|
err = fsverity_verify_signature(vi, desc->signature,
|
||||||
le32_to_cpu(desc->sig_size));
|
le32_to_cpu(desc->sig_size));
|
||||||
out:
|
if (err)
|
||||||
if (err) {
|
goto fail;
|
||||||
fsverity_free_info(vi);
|
|
||||||
vi = ERR_PTR(err);
|
if (vi->tree_params.block_size != PAGE_SIZE) {
|
||||||
|
/*
|
||||||
|
* When the Merkle tree block size and page size differ, we use
|
||||||
|
* a bitmap to keep track of which hash blocks have been
|
||||||
|
* verified. This bitmap must contain one bit per hash block,
|
||||||
|
* including alignment to a page boundary at the end.
|
||||||
|
*
|
||||||
|
* Eventually, to support extremely large files in an efficient
|
||||||
|
* way, it might be necessary to make pages of this bitmap
|
||||||
|
* reclaimable. But for now, simply allocating the whole bitmap
|
||||||
|
* is a simple solution that works well on the files on which
|
||||||
|
* fsverity is realistically used. E.g., with SHA-256 and 4K
|
||||||
|
* blocks, a 100MB file only needs a 24-byte bitmap, and the
|
||||||
|
* bitmap for any file under 17GB fits in a 4K page.
|
||||||
|
*/
|
||||||
|
unsigned long num_bits =
|
||||||
|
vi->tree_params.tree_pages <<
|
||||||
|
vi->tree_params.log_blocks_per_page;
|
||||||
|
|
||||||
|
vi->hash_block_verified = kvcalloc(BITS_TO_LONGS(num_bits),
|
||||||
|
sizeof(unsigned long),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!vi->hash_block_verified) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto fail;
|
||||||
}
|
}
|
||||||
|
spin_lock_init(&vi->hash_page_init_lock);
|
||||||
|
}
|
||||||
|
|
||||||
return vi;
|
return vi;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
fsverity_free_info(vi);
|
||||||
|
return ERR_PTR(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fsverity_set_info(struct inode *inode, struct fsverity_info *vi)
|
void fsverity_set_info(struct inode *inode, struct fsverity_info *vi)
|
||||||
@ -214,6 +273,7 @@ void fsverity_free_info(struct fsverity_info *vi)
|
|||||||
if (!vi)
|
if (!vi)
|
||||||
return;
|
return;
|
||||||
kfree(vi->tree_params.hashstate);
|
kfree(vi->tree_params.hashstate);
|
||||||
|
kvfree(vi->hash_block_verified);
|
||||||
kmem_cache_free(fsverity_info_cachep, vi);
|
kmem_cache_free(fsverity_info_cachep, vi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,67 +385,28 @@ out_free_desc:
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
int __fsverity_file_open(struct inode *inode, struct file *filp)
|
||||||
* fsverity_file_open() - prepare to open a verity file
|
|
||||||
* @inode: the inode being opened
|
|
||||||
* @filp: the struct file being set up
|
|
||||||
*
|
|
||||||
* When opening a verity file, deny the open if it is for writing. Otherwise,
|
|
||||||
* set up the inode's ->i_verity_info if not already done.
|
|
||||||
*
|
|
||||||
* When combined with fscrypt, this must be called after fscrypt_file_open().
|
|
||||||
* Otherwise, we won't have the key set up to decrypt the verity metadata.
|
|
||||||
*
|
|
||||||
* Return: 0 on success, -errno on failure
|
|
||||||
*/
|
|
||||||
int fsverity_file_open(struct inode *inode, struct file *filp)
|
|
||||||
{
|
{
|
||||||
if (!IS_VERITY(inode))
|
if (filp->f_mode & FMODE_WRITE)
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (filp->f_mode & FMODE_WRITE) {
|
|
||||||
pr_debug("Denying opening verity file (ino %lu) for write\n",
|
|
||||||
inode->i_ino);
|
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
}
|
|
||||||
|
|
||||||
return ensure_verity_info(inode);
|
return ensure_verity_info(inode);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(fsverity_file_open);
|
EXPORT_SYMBOL_GPL(__fsverity_file_open);
|
||||||
|
|
||||||
/**
|
int __fsverity_prepare_setattr(struct dentry *dentry, struct iattr *attr)
|
||||||
* fsverity_prepare_setattr() - prepare to change a verity inode's attributes
|
|
||||||
* @dentry: dentry through which the inode is being changed
|
|
||||||
* @attr: attributes to change
|
|
||||||
*
|
|
||||||
* Verity files are immutable, so deny truncates. This isn't covered by the
|
|
||||||
* open-time check because sys_truncate() takes a path, not a file descriptor.
|
|
||||||
*
|
|
||||||
* Return: 0 on success, -errno on failure
|
|
||||||
*/
|
|
||||||
int fsverity_prepare_setattr(struct dentry *dentry, struct iattr *attr)
|
|
||||||
{
|
{
|
||||||
if (IS_VERITY(d_inode(dentry)) && (attr->ia_valid & ATTR_SIZE)) {
|
if (attr->ia_valid & ATTR_SIZE)
|
||||||
pr_debug("Denying truncate of verity file (ino %lu)\n",
|
|
||||||
d_inode(dentry)->i_ino);
|
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(fsverity_prepare_setattr);
|
EXPORT_SYMBOL_GPL(__fsverity_prepare_setattr);
|
||||||
|
|
||||||
/**
|
void __fsverity_cleanup_inode(struct inode *inode)
|
||||||
* fsverity_cleanup_inode() - free the inode's verity info, if present
|
|
||||||
* @inode: an inode being evicted
|
|
||||||
*
|
|
||||||
* Filesystems must call this on inode eviction to free ->i_verity_info.
|
|
||||||
*/
|
|
||||||
void fsverity_cleanup_inode(struct inode *inode)
|
|
||||||
{
|
{
|
||||||
fsverity_free_info(inode->i_verity_info);
|
fsverity_free_info(inode->i_verity_info);
|
||||||
inode->i_verity_info = NULL;
|
inode->i_verity_info = NULL;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(fsverity_cleanup_inode);
|
EXPORT_SYMBOL_GPL(__fsverity_cleanup_inode);
|
||||||
|
|
||||||
int __init fsverity_init_info_cache(void)
|
int __init fsverity_init_info_cache(void)
|
||||||
{
|
{
|
||||||
|
@ -82,8 +82,6 @@ int fsverity_verify_signature(const struct fsverity_info *vi,
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
pr_debug("Valid signature for file digest %s:%*phN\n",
|
|
||||||
hash_alg->name, hash_alg->digest_size, vi->file_digest);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,39 +9,12 @@
|
|||||||
|
|
||||||
#include <crypto/hash.h>
|
#include <crypto/hash.h>
|
||||||
#include <linux/bio.h>
|
#include <linux/bio.h>
|
||||||
#include <linux/ratelimit.h>
|
|
||||||
|
|
||||||
static struct workqueue_struct *fsverity_read_workqueue;
|
static struct workqueue_struct *fsverity_read_workqueue;
|
||||||
|
|
||||||
/**
|
|
||||||
* hash_at_level() - compute the location of the block's hash at the given level
|
|
||||||
*
|
|
||||||
* @params: (in) the Merkle tree parameters
|
|
||||||
* @dindex: (in) the index of the data block being verified
|
|
||||||
* @level: (in) the level of hash we want (0 is leaf level)
|
|
||||||
* @hindex: (out) the index of the hash block containing the wanted hash
|
|
||||||
* @hoffset: (out) the byte offset to the wanted hash within the hash block
|
|
||||||
*/
|
|
||||||
static void hash_at_level(const struct merkle_tree_params *params,
|
|
||||||
pgoff_t dindex, unsigned int level, pgoff_t *hindex,
|
|
||||||
unsigned int *hoffset)
|
|
||||||
{
|
|
||||||
pgoff_t position;
|
|
||||||
|
|
||||||
/* Offset of the hash within the level's region, in hashes */
|
|
||||||
position = dindex >> (level * params->log_arity);
|
|
||||||
|
|
||||||
/* Index of the hash block in the tree overall */
|
|
||||||
*hindex = params->level_start[level] + (position >> params->log_arity);
|
|
||||||
|
|
||||||
/* Offset of the wanted hash (in bytes) within the hash block */
|
|
||||||
*hoffset = (position & ((1 << params->log_arity) - 1)) <<
|
|
||||||
(params->log_blocksize - params->log_arity);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cmp_hashes(const struct fsverity_info *vi,
|
static inline int cmp_hashes(const struct fsverity_info *vi,
|
||||||
const u8 *want_hash, const u8 *real_hash,
|
const u8 *want_hash, const u8 *real_hash,
|
||||||
pgoff_t index, int level)
|
u64 data_pos, int level)
|
||||||
{
|
{
|
||||||
const unsigned int hsize = vi->tree_params.digest_size;
|
const unsigned int hsize = vi->tree_params.digest_size;
|
||||||
|
|
||||||
@ -49,159 +22,312 @@ static inline int cmp_hashes(const struct fsverity_info *vi,
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
fsverity_err(vi->inode,
|
fsverity_err(vi->inode,
|
||||||
"FILE CORRUPTED! index=%lu, level=%d, want_hash=%s:%*phN, real_hash=%s:%*phN",
|
"FILE CORRUPTED! pos=%llu, level=%d, want_hash=%s:%*phN, real_hash=%s:%*phN",
|
||||||
index, level,
|
data_pos, level,
|
||||||
vi->tree_params.hash_alg->name, hsize, want_hash,
|
vi->tree_params.hash_alg->name, hsize, want_hash,
|
||||||
vi->tree_params.hash_alg->name, hsize, real_hash);
|
vi->tree_params.hash_alg->name, hsize, real_hash);
|
||||||
return -EBADMSG;
|
return -EBADMSG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool data_is_zeroed(struct inode *inode, struct page *page,
|
||||||
|
unsigned int len, unsigned int offset)
|
||||||
|
{
|
||||||
|
void *virt = kmap_local_page(page);
|
||||||
|
|
||||||
|
if (memchr_inv(virt + offset, 0, len)) {
|
||||||
|
kunmap_local(virt);
|
||||||
|
fsverity_err(inode,
|
||||||
|
"FILE CORRUPTED! Data past EOF is not zeroed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
kunmap_local(virt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Verify a single data page against the file's Merkle tree.
|
* Returns true if the hash block with index @hblock_idx in the tree, located in
|
||||||
|
* @hpage, has already been verified.
|
||||||
|
*/
|
||||||
|
static bool is_hash_block_verified(struct fsverity_info *vi, struct page *hpage,
|
||||||
|
unsigned long hblock_idx)
|
||||||
|
{
|
||||||
|
bool verified;
|
||||||
|
unsigned int blocks_per_page;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When the Merkle tree block size and page size are the same, then the
|
||||||
|
* ->hash_block_verified bitmap isn't allocated, and we use PG_checked
|
||||||
|
* to directly indicate whether the page's block has been verified.
|
||||||
|
*
|
||||||
|
* Using PG_checked also guarantees that we re-verify hash pages that
|
||||||
|
* get evicted and re-instantiated from the backing storage, as new
|
||||||
|
* pages always start out with PG_checked cleared.
|
||||||
|
*/
|
||||||
|
if (!vi->hash_block_verified)
|
||||||
|
return PageChecked(hpage);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When the Merkle tree block size and page size differ, we use a bitmap
|
||||||
|
* to indicate whether each hash block has been verified.
|
||||||
|
*
|
||||||
|
* However, we still need to ensure that hash pages that get evicted and
|
||||||
|
* re-instantiated from the backing storage are re-verified. To do
|
||||||
|
* this, we use PG_checked again, but now it doesn't really mean
|
||||||
|
* "checked". Instead, now it just serves as an indicator for whether
|
||||||
|
* the hash page is newly instantiated or not.
|
||||||
|
*
|
||||||
|
* The first thread that sees PG_checked=0 must clear the corresponding
|
||||||
|
* bitmap bits, then set PG_checked=1. This requires a spinlock. To
|
||||||
|
* avoid having to take this spinlock in the common case of
|
||||||
|
* PG_checked=1, we start with an opportunistic lockless read.
|
||||||
|
*/
|
||||||
|
if (PageChecked(hpage)) {
|
||||||
|
/*
|
||||||
|
* A read memory barrier is needed here to give ACQUIRE
|
||||||
|
* semantics to the above PageChecked() test.
|
||||||
|
*/
|
||||||
|
smp_rmb();
|
||||||
|
return test_bit(hblock_idx, vi->hash_block_verified);
|
||||||
|
}
|
||||||
|
spin_lock(&vi->hash_page_init_lock);
|
||||||
|
if (PageChecked(hpage)) {
|
||||||
|
verified = test_bit(hblock_idx, vi->hash_block_verified);
|
||||||
|
} else {
|
||||||
|
blocks_per_page = vi->tree_params.blocks_per_page;
|
||||||
|
hblock_idx = round_down(hblock_idx, blocks_per_page);
|
||||||
|
for (i = 0; i < blocks_per_page; i++)
|
||||||
|
clear_bit(hblock_idx + i, vi->hash_block_verified);
|
||||||
|
/*
|
||||||
|
* A write memory barrier is needed here to give RELEASE
|
||||||
|
* semantics to the below SetPageChecked() operation.
|
||||||
|
*/
|
||||||
|
smp_wmb();
|
||||||
|
SetPageChecked(hpage);
|
||||||
|
verified = false;
|
||||||
|
}
|
||||||
|
spin_unlock(&vi->hash_page_init_lock);
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verify a single data block against the file's Merkle tree.
|
||||||
*
|
*
|
||||||
* In principle, we need to verify the entire path to the root node. However,
|
* In principle, we need to verify the entire path to the root node. However,
|
||||||
* for efficiency the filesystem may cache the hash pages. Therefore we need
|
* for efficiency the filesystem may cache the hash blocks. Therefore we need
|
||||||
* only ascend the tree until an already-verified page is seen, as indicated by
|
* only ascend the tree until an already-verified hash block is seen, and then
|
||||||
* the PageChecked bit being set; then verify the path to that page.
|
* verify the path to that block.
|
||||||
*
|
*
|
||||||
* This code currently only supports the case where the verity block size is
|
* Return: %true if the data block is valid, else %false.
|
||||||
* equal to PAGE_SIZE. Doing otherwise would be possible but tricky, since we
|
|
||||||
* wouldn't be able to use the PageChecked bit.
|
|
||||||
*
|
|
||||||
* Note that multiple processes may race to verify a hash page and mark it
|
|
||||||
* Checked, but it doesn't matter; the result will be the same either way.
|
|
||||||
*
|
|
||||||
* Return: true if the page is valid, else false.
|
|
||||||
*/
|
*/
|
||||||
static bool verify_page(struct inode *inode, const struct fsverity_info *vi,
|
static bool
|
||||||
|
verify_data_block(struct inode *inode, struct fsverity_info *vi,
|
||||||
struct ahash_request *req, struct page *data_page,
|
struct ahash_request *req, struct page *data_page,
|
||||||
unsigned long level0_ra_pages)
|
u64 data_pos, unsigned int dblock_offset_in_page,
|
||||||
|
unsigned long max_ra_pages)
|
||||||
{
|
{
|
||||||
const struct merkle_tree_params *params = &vi->tree_params;
|
const struct merkle_tree_params *params = &vi->tree_params;
|
||||||
const unsigned int hsize = params->digest_size;
|
const unsigned int hsize = params->digest_size;
|
||||||
const pgoff_t index = data_page->index;
|
|
||||||
int level;
|
int level;
|
||||||
u8 _want_hash[FS_VERITY_MAX_DIGEST_SIZE];
|
u8 _want_hash[FS_VERITY_MAX_DIGEST_SIZE];
|
||||||
const u8 *want_hash;
|
const u8 *want_hash;
|
||||||
u8 real_hash[FS_VERITY_MAX_DIGEST_SIZE];
|
u8 real_hash[FS_VERITY_MAX_DIGEST_SIZE];
|
||||||
struct page *hpages[FS_VERITY_MAX_LEVELS];
|
/* The hash blocks that are traversed, indexed by level */
|
||||||
unsigned int hoffsets[FS_VERITY_MAX_LEVELS];
|
struct {
|
||||||
|
/* Page containing the hash block */
|
||||||
|
struct page *page;
|
||||||
|
/* Index of the hash block in the tree overall */
|
||||||
|
unsigned long index;
|
||||||
|
/* Byte offset of the hash block within @page */
|
||||||
|
unsigned int offset_in_page;
|
||||||
|
/* Byte offset of the wanted hash within @page */
|
||||||
|
unsigned int hoffset;
|
||||||
|
} hblocks[FS_VERITY_MAX_LEVELS];
|
||||||
|
/*
|
||||||
|
* The index of the previous level's block within that level; also the
|
||||||
|
* index of that block's hash within the current level.
|
||||||
|
*/
|
||||||
|
u64 hidx = data_pos >> params->log_blocksize;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (WARN_ON_ONCE(!PageLocked(data_page) || PageUptodate(data_page)))
|
if (unlikely(data_pos >= inode->i_size)) {
|
||||||
return false;
|
/*
|
||||||
|
* This can happen in the data page spanning EOF when the Merkle
|
||||||
pr_debug_ratelimited("Verifying data page %lu...\n", index);
|
* tree block size is less than the page size. The Merkle tree
|
||||||
|
* doesn't cover data blocks fully past EOF. But the entire
|
||||||
|
* page spanning EOF can be visible to userspace via a mmap, and
|
||||||
|
* any part past EOF should be all zeroes. Therefore, we need
|
||||||
|
* to verify that any data blocks fully past EOF are all zeroes.
|
||||||
|
*/
|
||||||
|
return data_is_zeroed(inode, data_page, params->block_size,
|
||||||
|
dblock_offset_in_page);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Starting at the leaf level, ascend the tree saving hash pages along
|
* Starting at the leaf level, ascend the tree saving hash blocks along
|
||||||
* the way until we find a verified hash page, indicated by PageChecked;
|
* the way until we find a hash block that has already been verified, or
|
||||||
* or until we reach the root.
|
* until we reach the root.
|
||||||
*/
|
*/
|
||||||
for (level = 0; level < params->num_levels; level++) {
|
for (level = 0; level < params->num_levels; level++) {
|
||||||
pgoff_t hindex;
|
unsigned long next_hidx;
|
||||||
|
unsigned long hblock_idx;
|
||||||
|
pgoff_t hpage_idx;
|
||||||
|
unsigned int hblock_offset_in_page;
|
||||||
unsigned int hoffset;
|
unsigned int hoffset;
|
||||||
struct page *hpage;
|
struct page *hpage;
|
||||||
|
|
||||||
hash_at_level(params, index, level, &hindex, &hoffset);
|
/*
|
||||||
|
* The index of the block in the current level; also the index
|
||||||
|
* of that block's hash within the next level.
|
||||||
|
*/
|
||||||
|
next_hidx = hidx >> params->log_arity;
|
||||||
|
|
||||||
pr_debug_ratelimited("Level %d: hindex=%lu, hoffset=%u\n",
|
/* Index of the hash block in the tree overall */
|
||||||
level, hindex, hoffset);
|
hblock_idx = params->level_start[level] + next_hidx;
|
||||||
|
|
||||||
hpage = inode->i_sb->s_vop->read_merkle_tree_page(inode, hindex,
|
/* Index of the hash page in the tree overall */
|
||||||
level == 0 ? level0_ra_pages : 0);
|
hpage_idx = hblock_idx >> params->log_blocks_per_page;
|
||||||
|
|
||||||
|
/* Byte offset of the hash block within the page */
|
||||||
|
hblock_offset_in_page =
|
||||||
|
(hblock_idx << params->log_blocksize) & ~PAGE_MASK;
|
||||||
|
|
||||||
|
/* Byte offset of the hash within the page */
|
||||||
|
hoffset = hblock_offset_in_page +
|
||||||
|
((hidx << params->log_digestsize) &
|
||||||
|
(params->block_size - 1));
|
||||||
|
|
||||||
|
hpage = inode->i_sb->s_vop->read_merkle_tree_page(inode,
|
||||||
|
hpage_idx, level == 0 ? min(max_ra_pages,
|
||||||
|
params->tree_pages - hpage_idx) : 0);
|
||||||
if (IS_ERR(hpage)) {
|
if (IS_ERR(hpage)) {
|
||||||
err = PTR_ERR(hpage);
|
err = PTR_ERR(hpage);
|
||||||
fsverity_err(inode,
|
fsverity_err(inode,
|
||||||
"Error %d reading Merkle tree page %lu",
|
"Error %d reading Merkle tree page %lu",
|
||||||
err, hindex);
|
err, hpage_idx);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
if (is_hash_block_verified(vi, hpage, hblock_idx)) {
|
||||||
if (PageChecked(hpage)) {
|
|
||||||
memcpy_from_page(_want_hash, hpage, hoffset, hsize);
|
memcpy_from_page(_want_hash, hpage, hoffset, hsize);
|
||||||
want_hash = _want_hash;
|
want_hash = _want_hash;
|
||||||
put_page(hpage);
|
put_page(hpage);
|
||||||
pr_debug_ratelimited("Hash page already checked, want %s:%*phN\n",
|
|
||||||
params->hash_alg->name,
|
|
||||||
hsize, want_hash);
|
|
||||||
goto descend;
|
goto descend;
|
||||||
}
|
}
|
||||||
pr_debug_ratelimited("Hash page not yet checked\n");
|
hblocks[level].page = hpage;
|
||||||
hpages[level] = hpage;
|
hblocks[level].index = hblock_idx;
|
||||||
hoffsets[level] = hoffset;
|
hblocks[level].offset_in_page = hblock_offset_in_page;
|
||||||
|
hblocks[level].hoffset = hoffset;
|
||||||
|
hidx = next_hidx;
|
||||||
}
|
}
|
||||||
|
|
||||||
want_hash = vi->root_hash;
|
want_hash = vi->root_hash;
|
||||||
pr_debug("Want root hash: %s:%*phN\n",
|
|
||||||
params->hash_alg->name, hsize, want_hash);
|
|
||||||
descend:
|
descend:
|
||||||
/* Descend the tree verifying hash pages */
|
/* Descend the tree verifying hash blocks. */
|
||||||
for (; level > 0; level--) {
|
for (; level > 0; level--) {
|
||||||
struct page *hpage = hpages[level - 1];
|
struct page *hpage = hblocks[level - 1].page;
|
||||||
unsigned int hoffset = hoffsets[level - 1];
|
unsigned long hblock_idx = hblocks[level - 1].index;
|
||||||
|
unsigned int hblock_offset_in_page =
|
||||||
|
hblocks[level - 1].offset_in_page;
|
||||||
|
unsigned int hoffset = hblocks[level - 1].hoffset;
|
||||||
|
|
||||||
err = fsverity_hash_page(params, inode, req, hpage, real_hash);
|
err = fsverity_hash_block(params, inode, req, hpage,
|
||||||
|
hblock_offset_in_page, real_hash);
|
||||||
if (err)
|
if (err)
|
||||||
goto out;
|
goto out;
|
||||||
err = cmp_hashes(vi, want_hash, real_hash, index, level - 1);
|
err = cmp_hashes(vi, want_hash, real_hash, data_pos, level - 1);
|
||||||
if (err)
|
if (err)
|
||||||
goto out;
|
goto out;
|
||||||
|
/*
|
||||||
|
* Mark the hash block as verified. This must be atomic and
|
||||||
|
* idempotent, as the same hash block might be verified by
|
||||||
|
* multiple threads concurrently.
|
||||||
|
*/
|
||||||
|
if (vi->hash_block_verified)
|
||||||
|
set_bit(hblock_idx, vi->hash_block_verified);
|
||||||
|
else
|
||||||
SetPageChecked(hpage);
|
SetPageChecked(hpage);
|
||||||
memcpy_from_page(_want_hash, hpage, hoffset, hsize);
|
memcpy_from_page(_want_hash, hpage, hoffset, hsize);
|
||||||
want_hash = _want_hash;
|
want_hash = _want_hash;
|
||||||
put_page(hpage);
|
put_page(hpage);
|
||||||
pr_debug("Verified hash page at level %d, now want %s:%*phN\n",
|
|
||||||
level - 1, params->hash_alg->name, hsize, want_hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Finally, verify the data page */
|
/* Finally, verify the data block. */
|
||||||
err = fsverity_hash_page(params, inode, req, data_page, real_hash);
|
err = fsverity_hash_block(params, inode, req, data_page,
|
||||||
|
dblock_offset_in_page, real_hash);
|
||||||
if (err)
|
if (err)
|
||||||
goto out;
|
goto out;
|
||||||
err = cmp_hashes(vi, want_hash, real_hash, index, -1);
|
err = cmp_hashes(vi, want_hash, real_hash, data_pos, -1);
|
||||||
out:
|
out:
|
||||||
for (; level > 0; level--)
|
for (; level > 0; level--)
|
||||||
put_page(hpages[level - 1]);
|
put_page(hblocks[level - 1].page);
|
||||||
|
|
||||||
return err == 0;
|
return err == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static bool
|
||||||
* fsverity_verify_page() - verify a data page
|
verify_data_blocks(struct inode *inode, struct fsverity_info *vi,
|
||||||
* @page: the page to verity
|
struct ahash_request *req, struct folio *data_folio,
|
||||||
*
|
size_t len, size_t offset, unsigned long max_ra_pages)
|
||||||
* Verify a page that has just been read from a verity file. The page must be a
|
|
||||||
* pagecache page that is still locked and not yet uptodate.
|
|
||||||
*
|
|
||||||
* Return: true if the page is valid, else false.
|
|
||||||
*/
|
|
||||||
bool fsverity_verify_page(struct page *page)
|
|
||||||
{
|
{
|
||||||
struct inode *inode = page->mapping->host;
|
const unsigned int block_size = vi->tree_params.block_size;
|
||||||
const struct fsverity_info *vi = inode->i_verity_info;
|
u64 pos = (u64)data_folio->index << PAGE_SHIFT;
|
||||||
|
|
||||||
|
if (WARN_ON_ONCE(len <= 0 || !IS_ALIGNED(len | offset, block_size)))
|
||||||
|
return false;
|
||||||
|
if (WARN_ON_ONCE(!folio_test_locked(data_folio) ||
|
||||||
|
folio_test_uptodate(data_folio)))
|
||||||
|
return false;
|
||||||
|
do {
|
||||||
|
struct page *data_page =
|
||||||
|
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;
|
||||||
|
offset += block_size;
|
||||||
|
len -= block_size;
|
||||||
|
} while (len);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_verify_blocks() - verify data in a folio
|
||||||
|
* @folio: the folio containing the data to verify
|
||||||
|
* @len: the length of the data to verify in the folio
|
||||||
|
* @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
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Return: %true if the data is valid, else %false.
|
||||||
|
*/
|
||||||
|
bool fsverity_verify_blocks(struct folio *folio, size_t len, size_t offset)
|
||||||
|
{
|
||||||
|
struct inode *inode = folio->mapping->host;
|
||||||
|
struct fsverity_info *vi = inode->i_verity_info;
|
||||||
struct ahash_request *req;
|
struct ahash_request *req;
|
||||||
bool valid;
|
bool valid;
|
||||||
|
|
||||||
/* 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_page(inode, vi, req, page, 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);
|
||||||
|
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(fsverity_verify_page);
|
EXPORT_SYMBOL_GPL(fsverity_verify_blocks);
|
||||||
|
|
||||||
#ifdef CONFIG_BLOCK
|
#ifdef CONFIG_BLOCK
|
||||||
/**
|
/**
|
||||||
* fsverity_verify_bio() - verify a 'read' bio that has just completed
|
* fsverity_verify_bio() - verify a 'read' bio that has just completed
|
||||||
* @bio: the bio to verify
|
* @bio: the bio to verify
|
||||||
*
|
*
|
||||||
* Verify a set of pages that have just been read from a verity file. The pages
|
* Verify the bio's data against the file's Merkle tree. All bio data segments
|
||||||
* must be pagecache pages that are still locked and not yet uptodate. If a
|
* must be aligned to the file's Merkle tree block size. If any data fails
|
||||||
* page fails verification, then bio->bi_status is set to an error status.
|
* verification, then bio->bi_status is set to an error status.
|
||||||
*
|
*
|
||||||
* This is a helper function for use by the ->readahead() method of filesystems
|
* This is a helper function for use by the ->readahead() method of filesystems
|
||||||
* that issue bios to read data directly into the page cache. Filesystems that
|
* that issue bios to read data directly into the page cache. Filesystems that
|
||||||
@ -212,15 +338,13 @@ EXPORT_SYMBOL_GPL(fsverity_verify_page);
|
|||||||
void fsverity_verify_bio(struct bio *bio)
|
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;
|
||||||
const struct fsverity_info *vi = inode->i_verity_info;
|
struct fsverity_info *vi = inode->i_verity_info;
|
||||||
const struct merkle_tree_params *params = &vi->tree_params;
|
|
||||||
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. */
|
||||||
req = fsverity_alloc_hash_request(params->hash_alg, GFP_NOFS);
|
req = fsverity_alloc_hash_request(vi->tree_params.hash_alg, GFP_NOFS);
|
||||||
|
|
||||||
if (bio->bi_opf & REQ_RAHEAD) {
|
if (bio->bi_opf & REQ_RAHEAD) {
|
||||||
/*
|
/*
|
||||||
@ -232,24 +356,18 @@ void fsverity_verify_bio(struct bio *bio)
|
|||||||
* This improves sequential read performance, as it greatly
|
* This improves sequential read performance, as it greatly
|
||||||
* reduces the number of I/O requests made to the Merkle tree.
|
* reduces the number of I/O requests made to the Merkle tree.
|
||||||
*/
|
*/
|
||||||
bio_for_each_segment_all(bv, bio, iter_all)
|
max_ra_pages = bio->bi_iter.bi_size >> (PAGE_SHIFT + 2);
|
||||||
max_ra_pages++;
|
|
||||||
max_ra_pages /= 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bio_for_each_segment_all(bv, bio, iter_all) {
|
bio_for_each_folio_all(fi, bio) {
|
||||||
struct page *page = bv->bv_page;
|
if (!verify_data_blocks(inode, vi, req, fi.folio, fi.length,
|
||||||
unsigned long level0_index = page->index >> params->log_arity;
|
fi.offset, max_ra_pages)) {
|
||||||
unsigned long level0_ra_pages =
|
|
||||||
min(max_ra_pages, params->level0_blocks - level0_index);
|
|
||||||
|
|
||||||
if (!verify_page(inode, vi, req, page, level0_ra_pages)) {
|
|
||||||
bio->bi_status = BLK_STS_IOERR;
|
bio->bi_status = BLK_STS_IOERR;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fsverity_free_hash_request(params->hash_alg, req);
|
fsverity_free_hash_request(vi->tree_params.hash_alg, req);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(fsverity_verify_bio);
|
EXPORT_SYMBOL_GPL(fsverity_verify_bio);
|
||||||
#endif /* CONFIG_BLOCK */
|
#endif /* CONFIG_BLOCK */
|
||||||
|
@ -257,8 +257,8 @@ int fscrypt_encrypt_block_inplace(const struct inode *inode, struct page *page,
|
|||||||
unsigned int len, unsigned int offs,
|
unsigned int len, unsigned int offs,
|
||||||
u64 lblk_num, gfp_t gfp_flags);
|
u64 lblk_num, gfp_t gfp_flags);
|
||||||
|
|
||||||
int fscrypt_decrypt_pagecache_blocks(struct page *page, unsigned int len,
|
int fscrypt_decrypt_pagecache_blocks(struct folio *folio, size_t len,
|
||||||
unsigned int offs);
|
size_t offs);
|
||||||
int fscrypt_decrypt_block_inplace(const struct inode *inode, struct page *page,
|
int fscrypt_decrypt_block_inplace(const struct inode *inode, struct page *page,
|
||||||
unsigned int len, unsigned int offs,
|
unsigned int len, unsigned int offs,
|
||||||
u64 lblk_num);
|
u64 lblk_num);
|
||||||
@ -420,9 +420,8 @@ static inline int fscrypt_encrypt_block_inplace(const struct inode *inode,
|
|||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int fscrypt_decrypt_pagecache_blocks(struct page *page,
|
static inline int fscrypt_decrypt_pagecache_blocks(struct folio *folio,
|
||||||
unsigned int len,
|
size_t len, size_t offs)
|
||||||
unsigned int offs)
|
|
||||||
{
|
{
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
@ -93,8 +94,7 @@ struct fsverity_operations {
|
|||||||
* isn't already cached. Implementations may ignore this
|
* isn't already cached. Implementations may ignore this
|
||||||
* argument; it's only a performance optimization.
|
* argument; it's only a performance optimization.
|
||||||
*
|
*
|
||||||
* This can be called at any time on an open verity file, as well as
|
* This can be called at any time on an open verity file. It may be
|
||||||
* between ->begin_enable_verity() and ->end_enable_verity(). It may be
|
|
||||||
* called by multiple processes concurrently, even with the same page.
|
* called by multiple processes concurrently, even with the same page.
|
||||||
*
|
*
|
||||||
* Note that this must retrieve a *page*, not necessarily a *block*.
|
* Note that this must retrieve a *page*, not necessarily a *block*.
|
||||||
@ -109,9 +109,9 @@ struct fsverity_operations {
|
|||||||
* Write a Merkle tree block to the given inode.
|
* Write a Merkle tree block to the given inode.
|
||||||
*
|
*
|
||||||
* @inode: the inode for which the Merkle tree is being built
|
* @inode: the inode for which the Merkle tree is being built
|
||||||
* @buf: block to write
|
* @buf: the Merkle tree block to write
|
||||||
* @index: 0-based index of the block within the Merkle tree
|
* @pos: the position of the block in the Merkle tree (in bytes)
|
||||||
* @log_blocksize: log base 2 of the Merkle tree block size
|
* @size: the Merkle tree block size (in bytes)
|
||||||
*
|
*
|
||||||
* This is only called between ->begin_enable_verity() and
|
* This is only called between ->begin_enable_verity() and
|
||||||
* ->end_enable_verity().
|
* ->end_enable_verity().
|
||||||
@ -119,7 +119,7 @@ struct fsverity_operations {
|
|||||||
* Return: 0 on success, -errno on failure
|
* Return: 0 on success, -errno on failure
|
||||||
*/
|
*/
|
||||||
int (*write_merkle_tree_block)(struct inode *inode, const void *buf,
|
int (*write_merkle_tree_block)(struct inode *inode, const void *buf,
|
||||||
u64 index, int log_blocksize);
|
u64 pos, unsigned int size);
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef CONFIG_FS_VERITY
|
#ifdef CONFIG_FS_VERITY
|
||||||
@ -148,9 +148,21 @@ int fsverity_get_digest(struct inode *inode,
|
|||||||
|
|
||||||
/* open.c */
|
/* open.c */
|
||||||
|
|
||||||
int fsverity_file_open(struct inode *inode, struct file *filp);
|
int __fsverity_file_open(struct inode *inode, struct file *filp);
|
||||||
int fsverity_prepare_setattr(struct dentry *dentry, struct iattr *attr);
|
int __fsverity_prepare_setattr(struct dentry *dentry, struct iattr *attr);
|
||||||
void fsverity_cleanup_inode(struct inode *inode);
|
void __fsverity_cleanup_inode(struct inode *inode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_cleanup_inode() - free the inode's verity info, if present
|
||||||
|
* @inode: an inode being evicted
|
||||||
|
*
|
||||||
|
* Filesystems must call this on inode eviction to free ->i_verity_info.
|
||||||
|
*/
|
||||||
|
static inline void fsverity_cleanup_inode(struct inode *inode)
|
||||||
|
{
|
||||||
|
if (inode->i_verity_info)
|
||||||
|
__fsverity_cleanup_inode(inode);
|
||||||
|
}
|
||||||
|
|
||||||
/* read_metadata.c */
|
/* read_metadata.c */
|
||||||
|
|
||||||
@ -158,7 +170,7 @@ int fsverity_ioctl_read_metadata(struct file *filp, const void __user *uarg);
|
|||||||
|
|
||||||
/* verify.c */
|
/* verify.c */
|
||||||
|
|
||||||
bool fsverity_verify_page(struct page *page);
|
bool fsverity_verify_blocks(struct folio *folio, size_t len, size_t 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);
|
||||||
|
|
||||||
@ -193,15 +205,15 @@ static inline int fsverity_get_digest(struct inode *inode,
|
|||||||
|
|
||||||
/* open.c */
|
/* open.c */
|
||||||
|
|
||||||
static inline int fsverity_file_open(struct inode *inode, struct file *filp)
|
static inline int __fsverity_file_open(struct inode *inode, struct file *filp)
|
||||||
{
|
{
|
||||||
return IS_VERITY(inode) ? -EOPNOTSUPP : 0;
|
return -EOPNOTSUPP;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int fsverity_prepare_setattr(struct dentry *dentry,
|
static inline int __fsverity_prepare_setattr(struct dentry *dentry,
|
||||||
struct iattr *attr)
|
struct iattr *attr)
|
||||||
{
|
{
|
||||||
return IS_VERITY(d_inode(dentry)) ? -EOPNOTSUPP : 0;
|
return -EOPNOTSUPP;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void fsverity_cleanup_inode(struct inode *inode)
|
static inline void fsverity_cleanup_inode(struct inode *inode)
|
||||||
@ -218,7 +230,8 @@ static inline int fsverity_ioctl_read_metadata(struct file *filp,
|
|||||||
|
|
||||||
/* verify.c */
|
/* verify.c */
|
||||||
|
|
||||||
static inline bool fsverity_verify_page(struct page *page)
|
static inline bool fsverity_verify_blocks(struct folio *folio, size_t len,
|
||||||
|
size_t offset)
|
||||||
{
|
{
|
||||||
WARN_ON(1);
|
WARN_ON(1);
|
||||||
return false;
|
return false;
|
||||||
@ -236,6 +249,16 @@ 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)
|
||||||
|
{
|
||||||
|
return fsverity_verify_blocks(page_folio(page), PAGE_SIZE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fsverity_active() - do reads from the inode need to go through fs-verity?
|
* fsverity_active() - do reads from the inode need to go through fs-verity?
|
||||||
* @inode: inode to check
|
* @inode: inode to check
|
||||||
@ -254,4 +277,42 @@ static inline bool fsverity_active(const struct inode *inode)
|
|||||||
return fsverity_get_info(inode) != NULL;
|
return fsverity_get_info(inode) != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_file_open() - prepare to open a verity file
|
||||||
|
* @inode: the inode being opened
|
||||||
|
* @filp: the struct file being set up
|
||||||
|
*
|
||||||
|
* When opening a verity file, deny the open if it is for writing. Otherwise,
|
||||||
|
* set up the inode's ->i_verity_info if not already done.
|
||||||
|
*
|
||||||
|
* When combined with fscrypt, this must be called after fscrypt_file_open().
|
||||||
|
* Otherwise, we won't have the key set up to decrypt the verity metadata.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, -errno on failure
|
||||||
|
*/
|
||||||
|
static inline int fsverity_file_open(struct inode *inode, struct file *filp)
|
||||||
|
{
|
||||||
|
if (IS_VERITY(inode))
|
||||||
|
return __fsverity_file_open(inode, filp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_prepare_setattr() - prepare to change a verity inode's attributes
|
||||||
|
* @dentry: dentry through which the inode is being changed
|
||||||
|
* @attr: attributes to change
|
||||||
|
*
|
||||||
|
* Verity files are immutable, so deny truncates. This isn't covered by the
|
||||||
|
* open-time check because sys_truncate() takes a path, not a file descriptor.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, -errno on failure
|
||||||
|
*/
|
||||||
|
static inline int fsverity_prepare_setattr(struct dentry *dentry,
|
||||||
|
struct iattr *attr)
|
||||||
|
{
|
||||||
|
if (IS_VERITY(d_inode(dentry)))
|
||||||
|
return __fsverity_prepare_setattr(dentry, attr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* _LINUX_FSVERITY_H */
|
#endif /* _LINUX_FSVERITY_H */
|
||||||
|
Loading…
Reference in New Issue
Block a user