mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 14:11:52 +00:00
fs-verity for 5.4
Please consider pulling fs-verity for 5.4. fs-verity is a filesystem feature that provides Merkle tree based hashing (similar to dm-verity) for individual readonly files, mainly for the purpose of efficient authenticity verification. This pull request includes: (a) The fs/verity/ support layer and documentation. (b) fs-verity support for ext4 and f2fs. Compared to the original fs-verity patchset from last year, the UAPI to enable fs-verity on a file has been greatly simplified. Lots of other things were cleaned up too. fs-verity is planned to be used by two different projects on Android; most of the userspace code is in place already. Another userspace tool ("fsverity-utils"), and xfstests, are also available. e2fsprogs and f2fs-tools already have fs-verity support. Other people have shown interest in using fs-verity too. I've tested this on ext4 and f2fs with xfstests, both the existing tests and the new fs-verity tests. This has also been in linux-next since July 30 with no reported issues except a couple minor ones I found myself and folded in fixes for. Ted and I will be co-maintaining fs-verity. -----BEGIN PGP SIGNATURE----- iIoEABYIADIWIQSacvsUNc7UX4ntmEPzXCl4vpKOKwUCXX8ZUBQcZWJpZ2dlcnNA Z29vZ2xlLmNvbQAKCRDzXCl4vpKOK2YOAQCbnBAKWDxXS3alLARRwjQLjmEtQIGl gsek+WurFIg/zAEAlpSzHwu13LvYzTqv3rhO2yhSlvhnDu4GQEJPXPm0wgM= =ID0n -----END PGP SIGNATURE----- Merge tag 'fsverity-for-linus' of git://git.kernel.org/pub/scm/fs/fscrypt/fscrypt Pull fs-verity support from Eric Biggers: "fs-verity is a filesystem feature that provides Merkle tree based hashing (similar to dm-verity) for individual readonly files, mainly for the purpose of efficient authenticity verification. This pull request includes: (a) The fs/verity/ support layer and documentation. (b) fs-verity support for ext4 and f2fs. Compared to the original fs-verity patchset from last year, the UAPI to enable fs-verity on a file has been greatly simplified. Lots of other things were cleaned up too. fs-verity is planned to be used by two different projects on Android; most of the userspace code is in place already. Another userspace tool ("fsverity-utils"), and xfstests, are also available. e2fsprogs and f2fs-tools already have fs-verity support. Other people have shown interest in using fs-verity too. I've tested this on ext4 and f2fs with xfstests, both the existing tests and the new fs-verity tests. This has also been in linux-next since July 30 with no reported issues except a couple minor ones I found myself and folded in fixes for. Ted and I will be co-maintaining fs-verity" * tag 'fsverity-for-linus' of git://git.kernel.org/pub/scm/fs/fscrypt/fscrypt: f2fs: add fs-verity support ext4: update on-disk format documentation for fs-verity ext4: add fs-verity read support ext4: add basic fs-verity support fs-verity: support builtin file signatures fs-verity: add SHA-512 support fs-verity: implement FS_IOC_MEASURE_VERITY ioctl fs-verity: implement FS_IOC_ENABLE_VERITY ioctl fs-verity: add data verification hooks for ->readpages() fs-verity: add the hook for file ->setattr() fs-verity: add the hook for file ->open() fs-verity: add inode and superblock fields fs-verity: add Kconfig and the helper functions for hashing fs: uapi: define verity bit for FS_IOC_GETFLAGS fs-verity: add UAPI header fs-verity: add MAINTAINERS file entry fs-verity: add a documentation file
This commit is contained in:
commit
f60c55a94e
@ -277,6 +277,8 @@ The ``i_flags`` field is a combination of these values:
|
|||||||
- This is a huge file (EXT4\_HUGE\_FILE\_FL).
|
- This is a huge file (EXT4\_HUGE\_FILE\_FL).
|
||||||
* - 0x80000
|
* - 0x80000
|
||||||
- Inode uses extents (EXT4\_EXTENTS\_FL).
|
- Inode uses extents (EXT4\_EXTENTS\_FL).
|
||||||
|
* - 0x100000
|
||||||
|
- Verity protected file (EXT4\_VERITY\_FL).
|
||||||
* - 0x200000
|
* - 0x200000
|
||||||
- Inode stores a large extended attribute value in its data blocks
|
- Inode stores a large extended attribute value in its data blocks
|
||||||
(EXT4\_EA\_INODE\_FL).
|
(EXT4\_EA\_INODE\_FL).
|
||||||
@ -299,9 +301,9 @@ The ``i_flags`` field is a combination of these values:
|
|||||||
- Reserved for ext4 library (EXT4\_RESERVED\_FL).
|
- Reserved for ext4 library (EXT4\_RESERVED\_FL).
|
||||||
* -
|
* -
|
||||||
- Aggregate flags:
|
- Aggregate flags:
|
||||||
* - 0x4BDFFF
|
* - 0x705BDFFF
|
||||||
- User-visible flags.
|
- User-visible flags.
|
||||||
* - 0x4B80FF
|
* - 0x604BC0FF
|
||||||
- User-modifiable flags. Note that while EXT4\_JOURNAL\_DATA\_FL and
|
- User-modifiable flags. Note that while EXT4\_JOURNAL\_DATA\_FL and
|
||||||
EXT4\_EXTENTS\_FL can be set with setattr, they are not in the kernel's
|
EXT4\_EXTENTS\_FL can be set with setattr, they are not in the kernel's
|
||||||
EXT4\_FL\_USER\_MODIFIABLE mask, since it needs to handle the setting of
|
EXT4\_FL\_USER\_MODIFIABLE mask, since it needs to handle the setting of
|
||||||
|
@ -24,3 +24,4 @@ order.
|
|||||||
.. include:: bigalloc.rst
|
.. include:: bigalloc.rst
|
||||||
.. include:: inlinedata.rst
|
.. include:: inlinedata.rst
|
||||||
.. include:: eainode.rst
|
.. include:: eainode.rst
|
||||||
|
.. include:: verity.rst
|
||||||
|
@ -696,6 +696,8 @@ the following:
|
|||||||
(RO\_COMPAT\_READONLY)
|
(RO\_COMPAT\_READONLY)
|
||||||
* - 0x2000
|
* - 0x2000
|
||||||
- Filesystem tracks project quotas. (RO\_COMPAT\_PROJECT)
|
- Filesystem tracks project quotas. (RO\_COMPAT\_PROJECT)
|
||||||
|
* - 0x8000
|
||||||
|
- Verity inodes may be present on the filesystem. (RO\_COMPAT\_VERITY)
|
||||||
|
|
||||||
.. _super_def_hash:
|
.. _super_def_hash:
|
||||||
|
|
||||||
|
41
Documentation/filesystems/ext4/verity.rst
Normal file
41
Documentation/filesystems/ext4/verity.rst
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
.. SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
Verity files
|
||||||
|
------------
|
||||||
|
|
||||||
|
ext4 supports fs-verity, which is a filesystem feature that provides
|
||||||
|
Merkle tree based hashing for individual readonly files. Most of
|
||||||
|
fs-verity is common to all filesystems that support it; see
|
||||||
|
:ref:`Documentation/filesystems/fsverity.rst <fsverity>` for the
|
||||||
|
fs-verity documentation. However, the on-disk layout of the verity
|
||||||
|
metadata is filesystem-specific. On ext4, the verity metadata is
|
||||||
|
stored after the end of the file data itself, in the following format:
|
||||||
|
|
||||||
|
- Zero-padding to the next 65536-byte boundary. This padding need not
|
||||||
|
actually be allocated on-disk, i.e. it may be a hole.
|
||||||
|
|
||||||
|
- The Merkle tree, as documented in
|
||||||
|
:ref:`Documentation/filesystems/fsverity.rst
|
||||||
|
<fsverity_merkle_tree>`, with the tree levels stored in order from
|
||||||
|
root to leaf, and the tree blocks within each level stored in their
|
||||||
|
natural order.
|
||||||
|
|
||||||
|
- Zero-padding to the next filesystem block boundary.
|
||||||
|
|
||||||
|
- The verity descriptor, as documented in
|
||||||
|
:ref:`Documentation/filesystems/fsverity.rst <fsverity_descriptor>`,
|
||||||
|
with optionally appended signature blob.
|
||||||
|
|
||||||
|
- Zero-padding to the next offset that is 4 bytes before a filesystem
|
||||||
|
block boundary.
|
||||||
|
|
||||||
|
- The size of the verity descriptor in bytes, as a 4-byte little
|
||||||
|
endian integer.
|
||||||
|
|
||||||
|
Verity inodes have EXT4_VERITY_FL set, and they must use extents, i.e.
|
||||||
|
EXT4_EXTENTS_FL must be set and EXT4_INLINE_DATA_FL must be clear.
|
||||||
|
They can have EXT4_ENCRYPT_FL set, in which case the verity metadata
|
||||||
|
is encrypted as well as the data itself.
|
||||||
|
|
||||||
|
Verity files cannot have blocks allocated past the end of the verity
|
||||||
|
metadata.
|
726
Documentation/filesystems/fsverity.rst
Normal file
726
Documentation/filesystems/fsverity.rst
Normal file
@ -0,0 +1,726 @@
|
|||||||
|
.. SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
.. _fsverity:
|
||||||
|
|
||||||
|
=======================================================
|
||||||
|
fs-verity: read-only file-based authenticity protection
|
||||||
|
=======================================================
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
============
|
||||||
|
|
||||||
|
fs-verity (``fs/verity/``) is a support layer that filesystems can
|
||||||
|
hook into to support transparent integrity and authenticity protection
|
||||||
|
of read-only files. Currently, it is supported by the ext4 and f2fs
|
||||||
|
filesystems. Like fscrypt, not too much filesystem-specific code is
|
||||||
|
needed to support fs-verity.
|
||||||
|
|
||||||
|
fs-verity is similar to `dm-verity
|
||||||
|
<https://www.kernel.org/doc/Documentation/device-mapper/verity.txt>`_
|
||||||
|
but works on files rather than block devices. On regular files on
|
||||||
|
filesystems supporting fs-verity, userspace can execute an ioctl that
|
||||||
|
causes the filesystem to build a Merkle tree for the file and persist
|
||||||
|
it to a filesystem-specific location associated with the file.
|
||||||
|
|
||||||
|
After this, the file is made readonly, and all reads from the file are
|
||||||
|
automatically verified against the file's Merkle tree. Reads of any
|
||||||
|
corrupted data, including mmap reads, will fail.
|
||||||
|
|
||||||
|
Userspace can use another ioctl to retrieve the root hash (actually
|
||||||
|
the "file measurement", which is a hash that includes the root hash)
|
||||||
|
that fs-verity is enforcing for the file. This ioctl executes in
|
||||||
|
constant time, regardless of the file size.
|
||||||
|
|
||||||
|
fs-verity is essentially a way to hash a file in constant time,
|
||||||
|
subject to the caveat that reads which would violate the hash will
|
||||||
|
fail at runtime.
|
||||||
|
|
||||||
|
Use cases
|
||||||
|
=========
|
||||||
|
|
||||||
|
By itself, the base fs-verity feature only provides integrity
|
||||||
|
protection, i.e. detection of accidental (non-malicious) corruption.
|
||||||
|
|
||||||
|
However, because fs-verity makes retrieving the file hash extremely
|
||||||
|
efficient, it's primarily meant to be used as a tool to support
|
||||||
|
authentication (detection of malicious modifications) or auditing
|
||||||
|
(logging file hashes before use).
|
||||||
|
|
||||||
|
Trusted userspace code (e.g. operating system code running on a
|
||||||
|
read-only partition that is itself authenticated by dm-verity) can
|
||||||
|
authenticate the contents of an fs-verity file by using the
|
||||||
|
`FS_IOC_MEASURE_VERITY`_ ioctl to retrieve its hash, then verifying a
|
||||||
|
digital signature of it.
|
||||||
|
|
||||||
|
A standard file hash could be used instead of fs-verity. However,
|
||||||
|
this is inefficient if the file is large and only a small portion may
|
||||||
|
be accessed. This is often the case for Android application package
|
||||||
|
(APK) files, for example. These typically contain many translations,
|
||||||
|
classes, and other resources that are infrequently or even never
|
||||||
|
accessed on a particular device. It would be slow and wasteful to
|
||||||
|
read and hash the entire file before starting the application.
|
||||||
|
|
||||||
|
Unlike an ahead-of-time hash, fs-verity also re-verifies data each
|
||||||
|
time it's paged in. This ensures that malicious disk firmware can't
|
||||||
|
undetectably change the contents of the file at runtime.
|
||||||
|
|
||||||
|
fs-verity does not replace or obsolete dm-verity. dm-verity should
|
||||||
|
still be used on read-only filesystems. fs-verity is for files that
|
||||||
|
must live on a read-write filesystem because they are independently
|
||||||
|
updated and potentially user-installed, so dm-verity cannot be used.
|
||||||
|
|
||||||
|
The base fs-verity feature is a hashing mechanism only; actually
|
||||||
|
authenticating the files is up to userspace. However, to meet some
|
||||||
|
users' needs, fs-verity optionally supports a simple signature
|
||||||
|
verification mechanism where users can configure the kernel to require
|
||||||
|
that all fs-verity files be signed by a key loaded into a keyring; see
|
||||||
|
`Built-in signature verification`_. Support for fs-verity file hashes
|
||||||
|
in IMA (Integrity Measurement Architecture) policies is also planned.
|
||||||
|
|
||||||
|
User API
|
||||||
|
========
|
||||||
|
|
||||||
|
FS_IOC_ENABLE_VERITY
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
The FS_IOC_ENABLE_VERITY ioctl enables fs-verity on a file. It takes
|
||||||
|
in a pointer to a :c:type:`struct fsverity_enable_arg`, defined as
|
||||||
|
follows::
|
||||||
|
|
||||||
|
struct fsverity_enable_arg {
|
||||||
|
__u32 version;
|
||||||
|
__u32 hash_algorithm;
|
||||||
|
__u32 block_size;
|
||||||
|
__u32 salt_size;
|
||||||
|
__u64 salt_ptr;
|
||||||
|
__u32 sig_size;
|
||||||
|
__u32 __reserved1;
|
||||||
|
__u64 sig_ptr;
|
||||||
|
__u64 __reserved2[11];
|
||||||
|
};
|
||||||
|
|
||||||
|
This structure contains the parameters of the Merkle tree to build for
|
||||||
|
the file, and optionally contains a signature. It must be initialized
|
||||||
|
as follows:
|
||||||
|
|
||||||
|
- ``version`` must be 1.
|
||||||
|
- ``hash_algorithm`` must be the identifier for the hash algorithm to
|
||||||
|
use for the Merkle tree, such as FS_VERITY_HASH_ALG_SHA256. See
|
||||||
|
``include/uapi/linux/fsverity.h`` for the list of possible values.
|
||||||
|
- ``block_size`` must be the Merkle tree block size. Currently, this
|
||||||
|
must be equal to the system page size, which is usually 4096 bytes.
|
||||||
|
Other sizes may be supported in the future. This value is not
|
||||||
|
necessarily the same as the filesystem block size.
|
||||||
|
- ``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
|
||||||
|
block; it can be used to personalize the hashing for a particular
|
||||||
|
file or device. Currently the maximum salt size is 32 bytes.
|
||||||
|
- ``salt_ptr`` is the pointer to the salt, or NULL if no salt is
|
||||||
|
provided.
|
||||||
|
- ``sig_size`` is the size of the signature in bytes, or 0 if no
|
||||||
|
signature is provided. Currently the signature is (somewhat
|
||||||
|
arbitrarily) limited to 16128 bytes. See `Built-in signature
|
||||||
|
verification`_ for more information.
|
||||||
|
- ``sig_ptr`` is the pointer to the signature, or NULL if no
|
||||||
|
signature is provided.
|
||||||
|
- All reserved fields must be zeroed.
|
||||||
|
|
||||||
|
FS_IOC_ENABLE_VERITY causes the filesystem to build a Merkle tree for
|
||||||
|
the file and persist it to a filesystem-specific location associated
|
||||||
|
with the file, then mark the file as a verity file. This ioctl may
|
||||||
|
take a long time to execute on large files, and it is interruptible by
|
||||||
|
fatal signals.
|
||||||
|
|
||||||
|
FS_IOC_ENABLE_VERITY checks for write access to the inode. However,
|
||||||
|
it must be executed on an O_RDONLY file descriptor and no processes
|
||||||
|
can have the file open for writing. Attempts to open the file for
|
||||||
|
writing while this ioctl is executing will fail with ETXTBSY. (This
|
||||||
|
is necessary to guarantee that no writable file descriptors will exist
|
||||||
|
after verity is enabled, and to guarantee that the file's contents are
|
||||||
|
stable while the Merkle tree is being built over it.)
|
||||||
|
|
||||||
|
On success, FS_IOC_ENABLE_VERITY returns 0, and the file becomes a
|
||||||
|
verity file. On failure (including the case of interruption by a
|
||||||
|
fatal signal), no changes are made to the file.
|
||||||
|
|
||||||
|
FS_IOC_ENABLE_VERITY can fail with the following errors:
|
||||||
|
|
||||||
|
- ``EACCES``: the process does not have write access to the file
|
||||||
|
- ``EBADMSG``: the signature is malformed
|
||||||
|
- ``EBUSY``: this ioctl is already running on the file
|
||||||
|
- ``EEXIST``: the file already has verity enabled
|
||||||
|
- ``EFAULT``: the caller provided inaccessible memory
|
||||||
|
- ``EINTR``: the operation was interrupted by a fatal signal
|
||||||
|
- ``EINVAL``: unsupported version, hash algorithm, or block size; or
|
||||||
|
reserved bits are set; or the file descriptor refers to neither a
|
||||||
|
regular file nor a directory.
|
||||||
|
- ``EISDIR``: the file descriptor refers to a directory
|
||||||
|
- ``EKEYREJECTED``: the signature doesn't match the file
|
||||||
|
- ``EMSGSIZE``: the salt or signature is too long
|
||||||
|
- ``ENOKEY``: the fs-verity keyring doesn't contain the certificate
|
||||||
|
needed to verify the signature
|
||||||
|
- ``ENOPKG``: fs-verity recognizes the hash algorithm, but it's not
|
||||||
|
available in the kernel's crypto API as currently configured (e.g.
|
||||||
|
for SHA-512, missing CONFIG_CRYPTO_SHA512).
|
||||||
|
- ``ENOTTY``: this type of filesystem does not implement fs-verity
|
||||||
|
- ``EOPNOTSUPP``: the kernel was not configured with fs-verity
|
||||||
|
support; or the filesystem superblock has not had the 'verity'
|
||||||
|
feature enabled on it; or the filesystem does not support fs-verity
|
||||||
|
on this file. (See `Filesystem support`_.)
|
||||||
|
- ``EPERM``: the file is append-only; or, a signature is required and
|
||||||
|
one was not provided.
|
||||||
|
- ``EROFS``: the filesystem is read-only
|
||||||
|
- ``ETXTBSY``: someone has the file open for writing. This can be the
|
||||||
|
caller's file descriptor, another open file descriptor, or the file
|
||||||
|
reference held by a writable memory map.
|
||||||
|
|
||||||
|
FS_IOC_MEASURE_VERITY
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
The FS_IOC_MEASURE_VERITY ioctl retrieves the measurement of a verity
|
||||||
|
file. The file measurement is a digest that cryptographically
|
||||||
|
identifies the file contents that are being enforced on reads.
|
||||||
|
|
||||||
|
This ioctl takes in a pointer to a variable-length structure::
|
||||||
|
|
||||||
|
struct fsverity_digest {
|
||||||
|
__u16 digest_algorithm;
|
||||||
|
__u16 digest_size; /* input/output */
|
||||||
|
__u8 digest[];
|
||||||
|
};
|
||||||
|
|
||||||
|
``digest_size`` is an input/output field. On input, it must be
|
||||||
|
initialized to the number of bytes allocated for the variable-length
|
||||||
|
``digest`` field.
|
||||||
|
|
||||||
|
On success, 0 is returned and the kernel fills in the structure as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
- ``digest_algorithm`` will be the hash algorithm used for the file
|
||||||
|
measurement. It will match ``fsverity_enable_arg::hash_algorithm``.
|
||||||
|
- ``digest_size`` will be the size of the digest in bytes, e.g. 32
|
||||||
|
for SHA-256. (This can be redundant with ``digest_algorithm``.)
|
||||||
|
- ``digest`` will be the actual bytes of the digest.
|
||||||
|
|
||||||
|
FS_IOC_MEASURE_VERITY is guaranteed to execute in constant time,
|
||||||
|
regardless of the size of the file.
|
||||||
|
|
||||||
|
FS_IOC_MEASURE_VERITY can fail with the following errors:
|
||||||
|
|
||||||
|
- ``EFAULT``: the caller provided inaccessible memory
|
||||||
|
- ``ENODATA``: the file is not a verity file
|
||||||
|
- ``ENOTTY``: this type of filesystem does not implement fs-verity
|
||||||
|
- ``EOPNOTSUPP``: the kernel was not configured with fs-verity
|
||||||
|
support, or the filesystem superblock has not had the 'verity'
|
||||||
|
feature enabled on it. (See `Filesystem support`_.)
|
||||||
|
- ``EOVERFLOW``: the digest is longer than the specified
|
||||||
|
``digest_size`` bytes. Try providing a larger buffer.
|
||||||
|
|
||||||
|
FS_IOC_GETFLAGS
|
||||||
|
---------------
|
||||||
|
|
||||||
|
The existing ioctl FS_IOC_GETFLAGS (which isn't specific to fs-verity)
|
||||||
|
can also be used to check whether a file has fs-verity enabled or not.
|
||||||
|
To do so, check for FS_VERITY_FL (0x00100000) in the returned flags.
|
||||||
|
|
||||||
|
The verity flag is not settable via FS_IOC_SETFLAGS. You must use
|
||||||
|
FS_IOC_ENABLE_VERITY instead, since parameters must be provided.
|
||||||
|
|
||||||
|
Accessing verity files
|
||||||
|
======================
|
||||||
|
|
||||||
|
Applications can transparently access a verity file just like a
|
||||||
|
non-verity one, with the following exceptions:
|
||||||
|
|
||||||
|
- Verity files are readonly. They cannot be opened for writing or
|
||||||
|
truncate()d, even if the file mode bits allow it. Attempts to do
|
||||||
|
one of these things will fail with EPERM. However, changes to
|
||||||
|
metadata such as owner, mode, timestamps, and xattrs are still
|
||||||
|
allowed, since these are not measured by fs-verity. Verity files
|
||||||
|
can also still be renamed, deleted, and linked to.
|
||||||
|
|
||||||
|
- Direct I/O is not supported on verity files. Attempts to use direct
|
||||||
|
I/O on such files will fall back to buffered I/O.
|
||||||
|
|
||||||
|
- DAX (Direct Access) is not supported on verity files, because this
|
||||||
|
would circumvent the data verification.
|
||||||
|
|
||||||
|
- Reads of data that doesn't match the verity Merkle tree will fail
|
||||||
|
with EIO (for read()) or SIGBUS (for mmap() reads).
|
||||||
|
|
||||||
|
- If the sysctl "fs.verity.require_signatures" is set to 1 and the
|
||||||
|
file's verity measurement is not signed by a key in the fs-verity
|
||||||
|
keyring, then opening the file will fail. See `Built-in signature
|
||||||
|
verification`_.
|
||||||
|
|
||||||
|
Direct access to the Merkle tree is not supported. Therefore, if a
|
||||||
|
verity file is copied, or is backed up and restored, then it will lose
|
||||||
|
its "verity"-ness. fs-verity is primarily meant for files like
|
||||||
|
executables that are managed by a package manager.
|
||||||
|
|
||||||
|
File measurement computation
|
||||||
|
============================
|
||||||
|
|
||||||
|
This section describes how fs-verity hashes the file contents using a
|
||||||
|
Merkle tree to produce the "file measurement" which cryptographically
|
||||||
|
identifies the file contents. This algorithm is the same for all
|
||||||
|
filesystems that support fs-verity.
|
||||||
|
|
||||||
|
Userspace only needs to be aware of this algorithm if it needs to
|
||||||
|
compute the file measurement itself, e.g. in order to sign the file.
|
||||||
|
|
||||||
|
.. _fsverity_merkle_tree:
|
||||||
|
|
||||||
|
Merkle tree
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The file contents is divided into blocks, where the block size is
|
||||||
|
configurable but is usually 4096 bytes. The end of the last block is
|
||||||
|
zero-padded if needed. Each block is then hashed, producing the first
|
||||||
|
level of hashes. Then, the hashes in this first level are grouped
|
||||||
|
into 'blocksize'-byte blocks (zero-padding the ends as needed) and
|
||||||
|
these blocks are hashed, producing the second level of hashes. This
|
||||||
|
proceeds up the tree until only a single block remains. The hash of
|
||||||
|
this block is the "Merkle tree root hash".
|
||||||
|
|
||||||
|
If the file fits in one block and is nonempty, then the "Merkle tree
|
||||||
|
root hash" is simply the hash of the single data block. If the file
|
||||||
|
is empty, then the "Merkle tree root hash" is all zeroes.
|
||||||
|
|
||||||
|
The "blocks" here are not necessarily the same as "filesystem blocks".
|
||||||
|
|
||||||
|
If a salt was specified, then it's zero-padded to the closest multiple
|
||||||
|
of the input size of the hash algorithm's compression function, e.g.
|
||||||
|
64 bytes for SHA-256 or 128 bytes for SHA-512. The padded salt is
|
||||||
|
prepended to every data or Merkle tree block that is hashed.
|
||||||
|
|
||||||
|
The purpose of the block padding is to cause every hash to be taken
|
||||||
|
over the same amount of data, which simplifies the implementation and
|
||||||
|
keeps open more possibilities for hardware acceleration. The purpose
|
||||||
|
of the salt padding is to make the salting "free" when the salted hash
|
||||||
|
state is precomputed, then imported for each hash.
|
||||||
|
|
||||||
|
Example: in the recommended configuration of SHA-256 and 4K blocks,
|
||||||
|
128 hash values fit in each block. Thus, each level of the Merkle
|
||||||
|
tree is approximately 128 times smaller than the previous, and for
|
||||||
|
large files the Merkle tree's size converges to approximately 1/127 of
|
||||||
|
the original file size. However, for small files, the padding is
|
||||||
|
significant, making the space overhead proportionally more.
|
||||||
|
|
||||||
|
.. _fsverity_descriptor:
|
||||||
|
|
||||||
|
fs-verity descriptor
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
By itself, the Merkle tree root hash is ambiguous. For example, it
|
||||||
|
can't a distinguish a large file from a small second file whose data
|
||||||
|
is exactly the top-level hash block of the first file. Ambiguities
|
||||||
|
also arise from the convention of padding to the next block boundary.
|
||||||
|
|
||||||
|
To solve this problem, the verity file measurement is actually
|
||||||
|
computed as a hash of the following structure, which contains the
|
||||||
|
Merkle tree root hash as well as other fields such as the file size::
|
||||||
|
|
||||||
|
struct fsverity_descriptor {
|
||||||
|
__u8 version; /* must be 1 */
|
||||||
|
__u8 hash_algorithm; /* Merkle tree hash algorithm */
|
||||||
|
__u8 log_blocksize; /* log2 of size of data and tree blocks */
|
||||||
|
__u8 salt_size; /* size of salt in bytes; 0 if none */
|
||||||
|
__le32 sig_size; /* must be 0 */
|
||||||
|
__le64 data_size; /* size of file the Merkle tree is built over */
|
||||||
|
__u8 root_hash[64]; /* Merkle tree root hash */
|
||||||
|
__u8 salt[32]; /* salt prepended to each hashed block */
|
||||||
|
__u8 __reserved[144]; /* must be 0's */
|
||||||
|
};
|
||||||
|
|
||||||
|
Note that the ``sig_size`` field must be set to 0 for the purpose of
|
||||||
|
computing the file measurement, even if a signature was provided (or
|
||||||
|
will be provided) to `FS_IOC_ENABLE_VERITY`_.
|
||||||
|
|
||||||
|
Built-in signature verification
|
||||||
|
===============================
|
||||||
|
|
||||||
|
With CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y, fs-verity supports putting
|
||||||
|
a portion of an authentication policy (see `Use cases`_) in the
|
||||||
|
kernel. Specifically, it adds support for:
|
||||||
|
|
||||||
|
1. At fs-verity module initialization time, a keyring ".fs-verity" is
|
||||||
|
created. The root user can add trusted X.509 certificates to this
|
||||||
|
keyring using the add_key() system call, then (when done)
|
||||||
|
optionally use keyctl_restrict_keyring() to prevent additional
|
||||||
|
certificates from being added.
|
||||||
|
|
||||||
|
2. `FS_IOC_ENABLE_VERITY`_ accepts a pointer to a PKCS#7 formatted
|
||||||
|
detached signature in DER format of the file measurement. On
|
||||||
|
success, this signature is persisted alongside the Merkle tree.
|
||||||
|
Then, any time the file is opened, the kernel will verify the
|
||||||
|
file's actual measurement against this signature, using the
|
||||||
|
certificates in the ".fs-verity" keyring.
|
||||||
|
|
||||||
|
3. A new sysctl "fs.verity.require_signatures" is made available.
|
||||||
|
When set to 1, the kernel requires that all verity files have a
|
||||||
|
correctly signed file measurement as described in (2).
|
||||||
|
|
||||||
|
File measurements must be signed in the following format, which is
|
||||||
|
similar to the structure used by `FS_IOC_MEASURE_VERITY`_::
|
||||||
|
|
||||||
|
struct fsverity_signed_digest {
|
||||||
|
char magic[8]; /* must be "FSVerity" */
|
||||||
|
__le16 digest_algorithm;
|
||||||
|
__le16 digest_size;
|
||||||
|
__u8 digest[];
|
||||||
|
};
|
||||||
|
|
||||||
|
fs-verity's built-in signature verification support is meant as a
|
||||||
|
relatively simple mechanism that can be used to provide some level of
|
||||||
|
authenticity protection for verity files, as an alternative to doing
|
||||||
|
the signature verification in userspace or using IMA-appraisal.
|
||||||
|
However, with this mechanism, userspace programs still need to check
|
||||||
|
that the verity bit is set, and there is no protection against verity
|
||||||
|
files being swapped around.
|
||||||
|
|
||||||
|
Filesystem support
|
||||||
|
==================
|
||||||
|
|
||||||
|
fs-verity is currently supported by the ext4 and f2fs filesystems.
|
||||||
|
The CONFIG_FS_VERITY kconfig option must be enabled to use fs-verity
|
||||||
|
on either filesystem.
|
||||||
|
|
||||||
|
``include/linux/fsverity.h`` declares the interface between the
|
||||||
|
``fs/verity/`` support layer and filesystems. Briefly, filesystems
|
||||||
|
must provide an ``fsverity_operations`` structure that provides
|
||||||
|
methods to read and write the verity metadata to a filesystem-specific
|
||||||
|
location, including the Merkle tree blocks and
|
||||||
|
``fsverity_descriptor``. Filesystems must also call functions in
|
||||||
|
``fs/verity/`` at certain times, such as when a file is opened or when
|
||||||
|
pages have been read into the pagecache. (See `Verifying data`_.)
|
||||||
|
|
||||||
|
ext4
|
||||||
|
----
|
||||||
|
|
||||||
|
ext4 supports fs-verity since Linux TODO and e2fsprogs v1.45.2.
|
||||||
|
|
||||||
|
To create verity files on an ext4 filesystem, the filesystem must have
|
||||||
|
been formatted with ``-O verity`` or had ``tune2fs -O verity`` run on
|
||||||
|
it. "verity" is an RO_COMPAT filesystem feature, so once set, old
|
||||||
|
kernels will only be able to mount the filesystem readonly, and old
|
||||||
|
versions of e2fsck will be unable to check the filesystem. Moreover,
|
||||||
|
currently ext4 only supports mounting a filesystem with the "verity"
|
||||||
|
feature when its block size is equal to PAGE_SIZE (often 4096 bytes).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
ext4 also supports encryption, which can be used simultaneously with
|
||||||
|
fs-verity. In this case, the plaintext data is verified rather than
|
||||||
|
the ciphertext. This is necessary in order to make the file
|
||||||
|
measurement meaningful, since every file is encrypted differently.
|
||||||
|
|
||||||
|
ext4 stores the verity metadata (Merkle tree and fsverity_descriptor)
|
||||||
|
past the end of the file, starting at the first 64K boundary beyond
|
||||||
|
i_size. This approach works because (a) verity files are readonly,
|
||||||
|
and (b) pages fully beyond i_size aren't visible to userspace but can
|
||||||
|
be read/written internally by ext4 with only some relatively small
|
||||||
|
changes to ext4. This approach avoids having to depend on the
|
||||||
|
EA_INODE feature and on rearchitecturing ext4's xattr support to
|
||||||
|
support paging multi-gigabyte xattrs into memory, and to support
|
||||||
|
encrypting xattrs. Note that the verity metadata *must* be encrypted
|
||||||
|
when the file is, since it contains hashes of the plaintext data.
|
||||||
|
|
||||||
|
Currently, ext4 verity only supports the case where the Merkle tree
|
||||||
|
block size, filesystem block size, and page size are all the same. It
|
||||||
|
also only supports extent-based files.
|
||||||
|
|
||||||
|
f2fs
|
||||||
|
----
|
||||||
|
|
||||||
|
f2fs supports fs-verity since Linux TODO and f2fs-tools v1.11.0.
|
||||||
|
|
||||||
|
To create verity files on an f2fs filesystem, the filesystem must have
|
||||||
|
been formatted with ``-O verity``.
|
||||||
|
|
||||||
|
f2fs sets the FADVISE_VERITY_BIT on-disk inode flag on verity files.
|
||||||
|
It can only be set by `FS_IOC_ENABLE_VERITY`_, and it cannot be
|
||||||
|
cleared.
|
||||||
|
|
||||||
|
Like ext4, f2fs stores the verity metadata (Merkle tree and
|
||||||
|
fsverity_descriptor) past the end of the file, starting at the first
|
||||||
|
64K boundary beyond i_size. See explanation for ext4 above.
|
||||||
|
Moreover, f2fs supports at most 4096 bytes of xattr entries per inode
|
||||||
|
which wouldn't be enough for even a single Merkle tree block.
|
||||||
|
|
||||||
|
Currently, f2fs verity only supports a Merkle tree block size of 4096.
|
||||||
|
Also, f2fs doesn't support enabling verity on files that currently
|
||||||
|
have atomic or volatile writes pending.
|
||||||
|
|
||||||
|
Implementation details
|
||||||
|
======================
|
||||||
|
|
||||||
|
Verifying data
|
||||||
|
--------------
|
||||||
|
|
||||||
|
fs-verity ensures that all reads of a verity file's data are verified,
|
||||||
|
regardless of which syscall is used to do the read (e.g. mmap(),
|
||||||
|
read(), pread()) and regardless of whether it's the first read or a
|
||||||
|
later read (unless the later read can return cached data that was
|
||||||
|
already verified). Below, we describe how filesystems implement this.
|
||||||
|
|
||||||
|
Pagecache
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
For filesystems using Linux's pagecache, the ``->readpage()`` and
|
||||||
|
``->readpages()`` methods must be modified to verify pages before they
|
||||||
|
are marked Uptodate. Merely hooking ``->read_iter()`` would be
|
||||||
|
insufficient, since ``->read_iter()`` is not used for memory maps.
|
||||||
|
|
||||||
|
Therefore, fs/verity/ provides a function fsverity_verify_page() which
|
||||||
|
verifies a page that has been read into the pagecache of a verity
|
||||||
|
inode, but is still locked and not Uptodate, so it's not yet readable
|
||||||
|
by userspace. As needed to do the verification,
|
||||||
|
fsverity_verify_page() will call back into the filesystem to read
|
||||||
|
Merkle tree pages via fsverity_operations::read_merkle_tree_page().
|
||||||
|
|
||||||
|
fsverity_verify_page() returns false if verification failed; in this
|
||||||
|
case, the filesystem must not set the page Uptodate. Following this,
|
||||||
|
as per the usual Linux pagecache behavior, attempts by userspace to
|
||||||
|
read() from the part of the file containing the page will fail with
|
||||||
|
EIO, and accesses to the page within a memory map will raise SIGBUS.
|
||||||
|
|
||||||
|
fsverity_verify_page() currently only supports the case where the
|
||||||
|
Merkle tree block size is equal to PAGE_SIZE (often 4096 bytes).
|
||||||
|
|
||||||
|
In principle, fsverity_verify_page() verifies the entire path in the
|
||||||
|
Merkle tree from the data page to the root hash. However, for
|
||||||
|
efficiency the filesystem may cache the hash pages. Therefore,
|
||||||
|
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
|
||||||
|
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
|
||||||
|
bottom level of the tree will already be cached and checked from
|
||||||
|
reading a previous data page. However, random reads perform worse.
|
||||||
|
|
||||||
|
Block device based filesystems
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Block device based filesystems (e.g. ext4 and f2fs) in Linux also use
|
||||||
|
the pagecache, so the above subsection applies too. However, they
|
||||||
|
also usually read many pages from a file at once, grouped into a
|
||||||
|
structure called a "bio". To make it easier for these types of
|
||||||
|
filesystems to support fs-verity, fs/verity/ also provides a function
|
||||||
|
fsverity_verify_bio() which verifies all pages in a bio.
|
||||||
|
|
||||||
|
ext4 and f2fs also support encryption. If a verity file is also
|
||||||
|
encrypted, the pages must be decrypted before being verified. To
|
||||||
|
support this, these filesystems allocate a "post-read context" for
|
||||||
|
each bio and store it in ``->bi_private``::
|
||||||
|
|
||||||
|
struct bio_post_read_ctx {
|
||||||
|
struct bio *bio;
|
||||||
|
struct work_struct work;
|
||||||
|
unsigned int cur_step;
|
||||||
|
unsigned int enabled_steps;
|
||||||
|
};
|
||||||
|
|
||||||
|
``enabled_steps`` is a bitmask that specifies whether decryption,
|
||||||
|
verity, or both is enabled. After the bio completes, for each needed
|
||||||
|
postprocessing step the filesystem enqueues the bio_post_read_ctx on a
|
||||||
|
workqueue, and then the workqueue work does the decryption or
|
||||||
|
verification. Finally, pages where no decryption or verity error
|
||||||
|
occurred are marked Uptodate, and the pages are unlocked.
|
||||||
|
|
||||||
|
Files on ext4 and f2fs may contain holes. Normally, ``->readpages()``
|
||||||
|
simply zeroes holes and sets the corresponding pages Uptodate; no bios
|
||||||
|
are issued. To prevent this case from bypassing fs-verity, these
|
||||||
|
filesystems use fsverity_verify_page() to verify hole pages.
|
||||||
|
|
||||||
|
ext4 and f2fs disable direct I/O on verity files, since otherwise
|
||||||
|
direct I/O would bypass fs-verity. (They also do the same for
|
||||||
|
encrypted files.)
|
||||||
|
|
||||||
|
Userspace utility
|
||||||
|
=================
|
||||||
|
|
||||||
|
This document focuses on the kernel, but a userspace utility for
|
||||||
|
fs-verity can be found at:
|
||||||
|
|
||||||
|
https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/fsverity-utils.git
|
||||||
|
|
||||||
|
See the README.md file in the fsverity-utils source tree for details,
|
||||||
|
including examples of setting up fs-verity protected files.
|
||||||
|
|
||||||
|
Tests
|
||||||
|
=====
|
||||||
|
|
||||||
|
To test fs-verity, use xfstests. For example, using `kvm-xfstests
|
||||||
|
<https://github.com/tytso/xfstests-bld/blob/master/Documentation/kvm-quickstart.md>`_::
|
||||||
|
|
||||||
|
kvm-xfstests -c ext4,f2fs -g verity
|
||||||
|
|
||||||
|
FAQ
|
||||||
|
===
|
||||||
|
|
||||||
|
This section answers frequently asked questions about fs-verity that
|
||||||
|
weren't already directly answered in other parts of this document.
|
||||||
|
|
||||||
|
:Q: Why isn't fs-verity part of IMA?
|
||||||
|
:A: fs-verity and IMA (Integrity Measurement Architecture) have
|
||||||
|
different focuses. fs-verity is a filesystem-level mechanism for
|
||||||
|
hashing individual files using a Merkle tree. In contrast, IMA
|
||||||
|
specifies a system-wide policy that specifies which files are
|
||||||
|
hashed and what to do with those hashes, such as log them,
|
||||||
|
authenticate them, or add them to a measurement list.
|
||||||
|
|
||||||
|
IMA is planned to support the fs-verity hashing mechanism as an
|
||||||
|
alternative to doing full file hashes, for people who want the
|
||||||
|
performance and security benefits of the Merkle tree based hash.
|
||||||
|
But it doesn't make sense to force all uses of fs-verity to be
|
||||||
|
through IMA. As a standalone filesystem feature, fs-verity
|
||||||
|
already meets many users' needs, and it's testable like other
|
||||||
|
filesystem features e.g. with xfstests.
|
||||||
|
|
||||||
|
:Q: Isn't fs-verity useless because the attacker can just modify the
|
||||||
|
hashes in the Merkle tree, which is stored on-disk?
|
||||||
|
:A: To verify the authenticity of an fs-verity file you must verify
|
||||||
|
the authenticity of the "file measurement", which is basically the
|
||||||
|
root hash of the Merkle tree. See `Use cases`_.
|
||||||
|
|
||||||
|
:Q: Isn't fs-verity useless because the attacker can just replace a
|
||||||
|
verity file with a non-verity one?
|
||||||
|
:A: See `Use cases`_. In the initial use case, it's really trusted
|
||||||
|
userspace code that authenticates the files; fs-verity is just a
|
||||||
|
tool to do this job efficiently and securely. The trusted
|
||||||
|
userspace code will consider non-verity files to be inauthentic.
|
||||||
|
|
||||||
|
:Q: Why does the Merkle tree need to be stored on-disk? Couldn't you
|
||||||
|
store just the root hash?
|
||||||
|
:A: If the Merkle tree wasn't stored on-disk, then you'd have to
|
||||||
|
compute the entire tree when the file is first accessed, even if
|
||||||
|
just one byte is being read. This is a fundamental consequence of
|
||||||
|
how Merkle tree hashing works. To verify a leaf node, you need to
|
||||||
|
verify the whole path to the root hash, including the root node
|
||||||
|
(the thing which the root hash is a hash of). But if the root
|
||||||
|
node isn't stored on-disk, you have to compute it by hashing its
|
||||||
|
children, and so on until you've actually hashed the entire file.
|
||||||
|
|
||||||
|
That defeats most of the point of doing a Merkle tree-based hash,
|
||||||
|
since if you have to hash the whole file ahead of time anyway,
|
||||||
|
then you could simply do sha256(file) instead. That would be much
|
||||||
|
simpler, and a bit faster too.
|
||||||
|
|
||||||
|
It's true that an in-memory Merkle tree could still provide the
|
||||||
|
advantage of verification on every read rather than just on the
|
||||||
|
first read. However, it would be inefficient because every time a
|
||||||
|
hash page gets evicted (you can't pin the entire Merkle tree into
|
||||||
|
memory, since it may be very large), in order to restore it you
|
||||||
|
again need to hash everything below it in the tree. This again
|
||||||
|
defeats most of the point of doing a Merkle tree-based hash, since
|
||||||
|
a single block read could trigger re-hashing gigabytes of data.
|
||||||
|
|
||||||
|
:Q: But couldn't you store just the leaf nodes and compute the rest?
|
||||||
|
:A: See previous answer; this really just moves up one level, since
|
||||||
|
one could alternatively interpret the data blocks as being the
|
||||||
|
leaf nodes of the Merkle tree. It's true that the tree can be
|
||||||
|
computed much faster if the leaf level is stored rather than just
|
||||||
|
the data, but that's only because each level is less than 1% the
|
||||||
|
size of the level below (assuming the recommended settings of
|
||||||
|
SHA-256 and 4K blocks). For the exact same reason, by storing
|
||||||
|
"just the leaf nodes" you'd already be storing over 99% of the
|
||||||
|
tree, so you might as well simply store the whole tree.
|
||||||
|
|
||||||
|
:Q: Can the Merkle tree be built ahead of time, e.g. distributed as
|
||||||
|
part of a package that is installed to many computers?
|
||||||
|
:A: This isn't currently supported. It was part of the original
|
||||||
|
design, but was removed to simplify the kernel UAPI and because it
|
||||||
|
wasn't a critical use case. Files are usually installed once and
|
||||||
|
used many times, and cryptographic hashing is somewhat fast on
|
||||||
|
most modern processors.
|
||||||
|
|
||||||
|
:Q: Why doesn't fs-verity support writes?
|
||||||
|
:A: Write support would be very difficult and would require a
|
||||||
|
completely different design, so it's well outside the scope of
|
||||||
|
fs-verity. Write support would require:
|
||||||
|
|
||||||
|
- A way to maintain consistency between the data and hashes,
|
||||||
|
including all levels of hashes, since corruption after a crash
|
||||||
|
(especially of potentially the entire file!) is unacceptable.
|
||||||
|
The main options for solving this are data journalling,
|
||||||
|
copy-on-write, and log-structured volume. But it's very hard to
|
||||||
|
retrofit existing filesystems with new consistency mechanisms.
|
||||||
|
Data journalling is available on ext4, but is very slow.
|
||||||
|
|
||||||
|
- Rebuilding the the Merkle tree after every write, which would be
|
||||||
|
extremely inefficient. Alternatively, a different authenticated
|
||||||
|
dictionary structure such as an "authenticated skiplist" could
|
||||||
|
be used. However, this would be far more complex.
|
||||||
|
|
||||||
|
Compare it to dm-verity vs. dm-integrity. dm-verity is very
|
||||||
|
simple: the kernel just verifies read-only data against a
|
||||||
|
read-only Merkle tree. In contrast, dm-integrity supports writes
|
||||||
|
but is slow, is much more complex, and doesn't actually support
|
||||||
|
full-device authentication since it authenticates each sector
|
||||||
|
independently, i.e. there is no "root hash". It doesn't really
|
||||||
|
make sense for the same device-mapper target to support these two
|
||||||
|
very different cases; the same applies to fs-verity.
|
||||||
|
|
||||||
|
:Q: Since verity files are immutable, why isn't the immutable bit set?
|
||||||
|
:A: The existing "immutable" bit (FS_IMMUTABLE_FL) already has a
|
||||||
|
specific set of semantics which not only make the file contents
|
||||||
|
read-only, but also prevent the file from being deleted, renamed,
|
||||||
|
linked to, or having its owner or mode changed. These extra
|
||||||
|
properties are unwanted for fs-verity, so reusing the immutable
|
||||||
|
bit isn't appropriate.
|
||||||
|
|
||||||
|
:Q: Why does the API use ioctls instead of setxattr() and getxattr()?
|
||||||
|
:A: Abusing the xattr interface for basically arbitrary syscalls is
|
||||||
|
heavily frowned upon by most of the Linux filesystem developers.
|
||||||
|
An xattr should really just be an xattr on-disk, not an API to
|
||||||
|
e.g. magically trigger construction of a Merkle tree.
|
||||||
|
|
||||||
|
:Q: Does fs-verity support remote filesystems?
|
||||||
|
:A: Only ext4 and f2fs support is implemented currently, but in
|
||||||
|
principle any filesystem that can store per-file verity metadata
|
||||||
|
can support fs-verity, regardless of whether it's local or remote.
|
||||||
|
Some filesystems may have fewer options of where to store the
|
||||||
|
verity metadata; one possibility is to store it past the end of
|
||||||
|
the file and "hide" it from userspace by manipulating i_size. The
|
||||||
|
data verification functions provided by ``fs/verity/`` also assume
|
||||||
|
that the filesystem uses the Linux pagecache, but both local and
|
||||||
|
remote filesystems normally do so.
|
||||||
|
|
||||||
|
:Q: Why is anything filesystem-specific at all? Shouldn't fs-verity
|
||||||
|
be implemented entirely at the VFS level?
|
||||||
|
:A: There are many reasons why this is not possible or would be very
|
||||||
|
difficult, including the following:
|
||||||
|
|
||||||
|
- To prevent bypassing verification, pages must not be marked
|
||||||
|
Uptodate until they've been verified. Currently, each
|
||||||
|
filesystem is responsible for marking pages Uptodate via
|
||||||
|
``->readpages()``. Therefore, currently it's not possible for
|
||||||
|
the VFS to do the verification on its own. Changing this would
|
||||||
|
require significant changes to the VFS and all filesystems.
|
||||||
|
|
||||||
|
- It would require defining a filesystem-independent way to store
|
||||||
|
the verity metadata. Extended attributes don't work for this
|
||||||
|
because (a) the Merkle tree may be gigabytes, but many
|
||||||
|
filesystems assume that all xattrs fit into a single 4K
|
||||||
|
filesystem block, and (b) ext4 and f2fs encryption doesn't
|
||||||
|
encrypt xattrs, yet the Merkle tree *must* be encrypted when the
|
||||||
|
file contents are, because it stores hashes of the plaintext
|
||||||
|
file contents.
|
||||||
|
|
||||||
|
So the verity metadata would have to be stored in an actual
|
||||||
|
file. Using a separate file would be very ugly, since the
|
||||||
|
metadata is fundamentally part of the file to be protected, and
|
||||||
|
it could cause problems where users could delete the real file
|
||||||
|
but not the metadata file or vice versa. On the other hand,
|
||||||
|
having it be in the same file would break applications unless
|
||||||
|
filesystems' notion of i_size were divorced from the VFS's,
|
||||||
|
which would be complex and require changes to all filesystems.
|
||||||
|
|
||||||
|
- It's desirable that FS_IOC_ENABLE_VERITY uses the filesystem's
|
||||||
|
transaction mechanism so that either the file ends up with
|
||||||
|
verity enabled, or no changes were made. Allowing intermediate
|
||||||
|
states to occur after a crash may cause problems.
|
@ -36,3 +36,4 @@ filesystem implementations.
|
|||||||
|
|
||||||
journalling
|
journalling
|
||||||
fscrypt
|
fscrypt
|
||||||
|
fsverity
|
||||||
|
@ -233,6 +233,7 @@ Code Seq# Include File Comments
|
|||||||
'f' 00-0F fs/ext4/ext4.h conflict!
|
'f' 00-0F fs/ext4/ext4.h conflict!
|
||||||
'f' 00-0F linux/fs.h conflict!
|
'f' 00-0F linux/fs.h conflict!
|
||||||
'f' 00-0F fs/ocfs2/ocfs2_fs.h conflict!
|
'f' 00-0F fs/ocfs2/ocfs2_fs.h conflict!
|
||||||
|
'f' 81-8F linux/fsverity.h
|
||||||
'g' 00-0F linux/usb/gadgetfs.h
|
'g' 00-0F linux/usb/gadgetfs.h
|
||||||
'g' 20-2F linux/usb/g_printer.h
|
'g' 20-2F linux/usb/g_printer.h
|
||||||
'h' 00-7F conflict! Charon filesystem
|
'h' 00-7F conflict! Charon filesystem
|
||||||
|
12
MAINTAINERS
12
MAINTAINERS
@ -6694,6 +6694,18 @@ S: Maintained
|
|||||||
F: fs/notify/
|
F: fs/notify/
|
||||||
F: include/linux/fsnotify*.h
|
F: include/linux/fsnotify*.h
|
||||||
|
|
||||||
|
FSVERITY: READ-ONLY FILE-BASED AUTHENTICITY PROTECTION
|
||||||
|
M: Eric Biggers <ebiggers@kernel.org>
|
||||||
|
M: Theodore Y. Ts'o <tytso@mit.edu>
|
||||||
|
L: linux-fscrypt@vger.kernel.org
|
||||||
|
Q: https://patchwork.kernel.org/project/linux-fscrypt/list/
|
||||||
|
T: git git://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git fsverity
|
||||||
|
S: Supported
|
||||||
|
F: fs/verity/
|
||||||
|
F: include/linux/fsverity.h
|
||||||
|
F: include/uapi/linux/fsverity.h
|
||||||
|
F: Documentation/filesystems/fsverity.rst
|
||||||
|
|
||||||
FUJITSU LAPTOP EXTRAS
|
FUJITSU LAPTOP EXTRAS
|
||||||
M: Jonathan Woithe <jwoithe@just42.net>
|
M: Jonathan Woithe <jwoithe@just42.net>
|
||||||
L: platform-driver-x86@vger.kernel.org
|
L: platform-driver-x86@vger.kernel.org
|
||||||
|
@ -112,6 +112,8 @@ config MANDATORY_FILE_LOCKING
|
|||||||
|
|
||||||
source "fs/crypto/Kconfig"
|
source "fs/crypto/Kconfig"
|
||||||
|
|
||||||
|
source "fs/verity/Kconfig"
|
||||||
|
|
||||||
source "fs/notify/Kconfig"
|
source "fs/notify/Kconfig"
|
||||||
|
|
||||||
source "fs/quota/Kconfig"
|
source "fs/quota/Kconfig"
|
||||||
|
@ -34,6 +34,7 @@ obj-$(CONFIG_AIO) += aio.o
|
|||||||
obj-$(CONFIG_IO_URING) += io_uring.o
|
obj-$(CONFIG_IO_URING) += io_uring.o
|
||||||
obj-$(CONFIG_FS_DAX) += dax.o
|
obj-$(CONFIG_FS_DAX) += dax.o
|
||||||
obj-$(CONFIG_FS_ENCRYPTION) += crypto/
|
obj-$(CONFIG_FS_ENCRYPTION) += crypto/
|
||||||
|
obj-$(CONFIG_FS_VERITY) += verity/
|
||||||
obj-$(CONFIG_FILE_LOCKING) += locks.o
|
obj-$(CONFIG_FILE_LOCKING) += locks.o
|
||||||
obj-$(CONFIG_COMPAT) += compat.o compat_ioctl.o
|
obj-$(CONFIG_COMPAT) += compat.o compat_ioctl.o
|
||||||
obj-$(CONFIG_BINFMT_AOUT) += binfmt_aout.o
|
obj-$(CONFIG_BINFMT_AOUT) += binfmt_aout.o
|
||||||
|
@ -13,3 +13,4 @@ ext4-y := balloc.o bitmap.o block_validity.o dir.o ext4_jbd2.o extents.o \
|
|||||||
|
|
||||||
ext4-$(CONFIG_EXT4_FS_POSIX_ACL) += acl.o
|
ext4-$(CONFIG_EXT4_FS_POSIX_ACL) += acl.o
|
||||||
ext4-$(CONFIG_EXT4_FS_SECURITY) += xattr_security.o
|
ext4-$(CONFIG_EXT4_FS_SECURITY) += xattr_security.o
|
||||||
|
ext4-$(CONFIG_FS_VERITY) += verity.o
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <linux/fscrypt.h>
|
#include <linux/fscrypt.h>
|
||||||
|
#include <linux/fsverity.h>
|
||||||
|
|
||||||
#include <linux/compiler.h>
|
#include <linux/compiler.h>
|
||||||
|
|
||||||
@ -395,6 +396,7 @@ struct flex_groups {
|
|||||||
#define EXT4_TOPDIR_FL 0x00020000 /* Top of directory hierarchies*/
|
#define EXT4_TOPDIR_FL 0x00020000 /* Top of directory hierarchies*/
|
||||||
#define EXT4_HUGE_FILE_FL 0x00040000 /* Set to each huge file */
|
#define EXT4_HUGE_FILE_FL 0x00040000 /* Set to each huge file */
|
||||||
#define EXT4_EXTENTS_FL 0x00080000 /* Inode uses extents */
|
#define EXT4_EXTENTS_FL 0x00080000 /* Inode uses extents */
|
||||||
|
#define EXT4_VERITY_FL 0x00100000 /* Verity protected inode */
|
||||||
#define EXT4_EA_INODE_FL 0x00200000 /* Inode used for large EA */
|
#define EXT4_EA_INODE_FL 0x00200000 /* Inode used for large EA */
|
||||||
#define EXT4_EOFBLOCKS_FL 0x00400000 /* Blocks allocated beyond EOF */
|
#define EXT4_EOFBLOCKS_FL 0x00400000 /* Blocks allocated beyond EOF */
|
||||||
#define EXT4_INLINE_DATA_FL 0x10000000 /* Inode has inline data. */
|
#define EXT4_INLINE_DATA_FL 0x10000000 /* Inode has inline data. */
|
||||||
@ -402,7 +404,7 @@ struct flex_groups {
|
|||||||
#define EXT4_CASEFOLD_FL 0x40000000 /* Casefolded file */
|
#define EXT4_CASEFOLD_FL 0x40000000 /* Casefolded file */
|
||||||
#define EXT4_RESERVED_FL 0x80000000 /* reserved for ext4 lib */
|
#define EXT4_RESERVED_FL 0x80000000 /* reserved for ext4 lib */
|
||||||
|
|
||||||
#define EXT4_FL_USER_VISIBLE 0x704BDFFF /* User visible flags */
|
#define EXT4_FL_USER_VISIBLE 0x705BDFFF /* User visible flags */
|
||||||
#define EXT4_FL_USER_MODIFIABLE 0x604BC0FF /* User modifiable flags */
|
#define EXT4_FL_USER_MODIFIABLE 0x604BC0FF /* User modifiable flags */
|
||||||
|
|
||||||
/* Flags we can manipulate with through EXT4_IOC_FSSETXATTR */
|
/* Flags we can manipulate with through EXT4_IOC_FSSETXATTR */
|
||||||
@ -467,6 +469,7 @@ enum {
|
|||||||
EXT4_INODE_TOPDIR = 17, /* Top of directory hierarchies*/
|
EXT4_INODE_TOPDIR = 17, /* Top of directory hierarchies*/
|
||||||
EXT4_INODE_HUGE_FILE = 18, /* Set to each huge file */
|
EXT4_INODE_HUGE_FILE = 18, /* Set to each huge file */
|
||||||
EXT4_INODE_EXTENTS = 19, /* Inode uses extents */
|
EXT4_INODE_EXTENTS = 19, /* Inode uses extents */
|
||||||
|
EXT4_INODE_VERITY = 20, /* Verity protected inode */
|
||||||
EXT4_INODE_EA_INODE = 21, /* Inode used for large EA */
|
EXT4_INODE_EA_INODE = 21, /* Inode used for large EA */
|
||||||
EXT4_INODE_EOFBLOCKS = 22, /* Blocks allocated beyond EOF */
|
EXT4_INODE_EOFBLOCKS = 22, /* Blocks allocated beyond EOF */
|
||||||
EXT4_INODE_INLINE_DATA = 28, /* Data in inode. */
|
EXT4_INODE_INLINE_DATA = 28, /* Data in inode. */
|
||||||
@ -512,6 +515,7 @@ static inline void ext4_check_flag_values(void)
|
|||||||
CHECK_FLAG_VALUE(TOPDIR);
|
CHECK_FLAG_VALUE(TOPDIR);
|
||||||
CHECK_FLAG_VALUE(HUGE_FILE);
|
CHECK_FLAG_VALUE(HUGE_FILE);
|
||||||
CHECK_FLAG_VALUE(EXTENTS);
|
CHECK_FLAG_VALUE(EXTENTS);
|
||||||
|
CHECK_FLAG_VALUE(VERITY);
|
||||||
CHECK_FLAG_VALUE(EA_INODE);
|
CHECK_FLAG_VALUE(EA_INODE);
|
||||||
CHECK_FLAG_VALUE(EOFBLOCKS);
|
CHECK_FLAG_VALUE(EOFBLOCKS);
|
||||||
CHECK_FLAG_VALUE(INLINE_DATA);
|
CHECK_FLAG_VALUE(INLINE_DATA);
|
||||||
@ -1560,6 +1564,7 @@ enum {
|
|||||||
EXT4_STATE_MAY_INLINE_DATA, /* may have in-inode data */
|
EXT4_STATE_MAY_INLINE_DATA, /* may have in-inode data */
|
||||||
EXT4_STATE_EXT_PRECACHED, /* extents have been precached */
|
EXT4_STATE_EXT_PRECACHED, /* extents have been precached */
|
||||||
EXT4_STATE_LUSTRE_EA_INODE, /* Lustre-style ea_inode */
|
EXT4_STATE_LUSTRE_EA_INODE, /* Lustre-style ea_inode */
|
||||||
|
EXT4_STATE_VERITY_IN_PROGRESS, /* building fs-verity Merkle tree */
|
||||||
};
|
};
|
||||||
|
|
||||||
#define EXT4_INODE_BIT_FNS(name, field, offset) \
|
#define EXT4_INODE_BIT_FNS(name, field, offset) \
|
||||||
@ -1610,6 +1615,12 @@ static inline void ext4_clear_state_flags(struct ext4_inode_info *ei)
|
|||||||
#define EXT4_SB(sb) (sb)
|
#define EXT4_SB(sb) (sb)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static inline bool ext4_verity_in_progress(struct inode *inode)
|
||||||
|
{
|
||||||
|
return IS_ENABLED(CONFIG_FS_VERITY) &&
|
||||||
|
ext4_test_inode_state(inode, EXT4_STATE_VERITY_IN_PROGRESS);
|
||||||
|
}
|
||||||
|
|
||||||
#define NEXT_ORPHAN(inode) EXT4_I(inode)->i_dtime
|
#define NEXT_ORPHAN(inode) EXT4_I(inode)->i_dtime
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1662,6 +1673,7 @@ static inline void ext4_clear_state_flags(struct ext4_inode_info *ei)
|
|||||||
#define EXT4_FEATURE_RO_COMPAT_METADATA_CSUM 0x0400
|
#define EXT4_FEATURE_RO_COMPAT_METADATA_CSUM 0x0400
|
||||||
#define EXT4_FEATURE_RO_COMPAT_READONLY 0x1000
|
#define EXT4_FEATURE_RO_COMPAT_READONLY 0x1000
|
||||||
#define EXT4_FEATURE_RO_COMPAT_PROJECT 0x2000
|
#define EXT4_FEATURE_RO_COMPAT_PROJECT 0x2000
|
||||||
|
#define EXT4_FEATURE_RO_COMPAT_VERITY 0x8000
|
||||||
|
|
||||||
#define EXT4_FEATURE_INCOMPAT_COMPRESSION 0x0001
|
#define EXT4_FEATURE_INCOMPAT_COMPRESSION 0x0001
|
||||||
#define EXT4_FEATURE_INCOMPAT_FILETYPE 0x0002
|
#define EXT4_FEATURE_INCOMPAT_FILETYPE 0x0002
|
||||||
@ -1756,6 +1768,7 @@ EXT4_FEATURE_RO_COMPAT_FUNCS(bigalloc, BIGALLOC)
|
|||||||
EXT4_FEATURE_RO_COMPAT_FUNCS(metadata_csum, METADATA_CSUM)
|
EXT4_FEATURE_RO_COMPAT_FUNCS(metadata_csum, METADATA_CSUM)
|
||||||
EXT4_FEATURE_RO_COMPAT_FUNCS(readonly, READONLY)
|
EXT4_FEATURE_RO_COMPAT_FUNCS(readonly, READONLY)
|
||||||
EXT4_FEATURE_RO_COMPAT_FUNCS(project, PROJECT)
|
EXT4_FEATURE_RO_COMPAT_FUNCS(project, PROJECT)
|
||||||
|
EXT4_FEATURE_RO_COMPAT_FUNCS(verity, VERITY)
|
||||||
|
|
||||||
EXT4_FEATURE_INCOMPAT_FUNCS(compression, COMPRESSION)
|
EXT4_FEATURE_INCOMPAT_FUNCS(compression, COMPRESSION)
|
||||||
EXT4_FEATURE_INCOMPAT_FUNCS(filetype, FILETYPE)
|
EXT4_FEATURE_INCOMPAT_FUNCS(filetype, FILETYPE)
|
||||||
@ -1813,7 +1826,8 @@ EXT4_FEATURE_INCOMPAT_FUNCS(casefold, CASEFOLD)
|
|||||||
EXT4_FEATURE_RO_COMPAT_BIGALLOC |\
|
EXT4_FEATURE_RO_COMPAT_BIGALLOC |\
|
||||||
EXT4_FEATURE_RO_COMPAT_METADATA_CSUM|\
|
EXT4_FEATURE_RO_COMPAT_METADATA_CSUM|\
|
||||||
EXT4_FEATURE_RO_COMPAT_QUOTA |\
|
EXT4_FEATURE_RO_COMPAT_QUOTA |\
|
||||||
EXT4_FEATURE_RO_COMPAT_PROJECT)
|
EXT4_FEATURE_RO_COMPAT_PROJECT |\
|
||||||
|
EXT4_FEATURE_RO_COMPAT_VERITY)
|
||||||
|
|
||||||
#define EXTN_FEATURE_FUNCS(ver) \
|
#define EXTN_FEATURE_FUNCS(ver) \
|
||||||
static inline bool ext4_has_unknown_ext##ver##_compat_features(struct super_block *sb) \
|
static inline bool ext4_has_unknown_ext##ver##_compat_features(struct super_block *sb) \
|
||||||
@ -3177,6 +3191,8 @@ static inline void ext4_set_de_type(struct super_block *sb,
|
|||||||
extern int ext4_mpage_readpages(struct address_space *mapping,
|
extern int ext4_mpage_readpages(struct address_space *mapping,
|
||||||
struct list_head *pages, struct page *page,
|
struct list_head *pages, struct page *page,
|
||||||
unsigned nr_pages, bool is_readahead);
|
unsigned nr_pages, bool is_readahead);
|
||||||
|
extern int __init ext4_init_post_read_processing(void);
|
||||||
|
extern void ext4_exit_post_read_processing(void);
|
||||||
|
|
||||||
/* symlink.c */
|
/* symlink.c */
|
||||||
extern const struct inode_operations ext4_encrypted_symlink_inode_operations;
|
extern const struct inode_operations ext4_encrypted_symlink_inode_operations;
|
||||||
@ -3283,6 +3299,9 @@ extern int ext4_bio_write_page(struct ext4_io_submit *io,
|
|||||||
/* mmp.c */
|
/* mmp.c */
|
||||||
extern int ext4_multi_mount_protect(struct super_block *, ext4_fsblk_t);
|
extern int ext4_multi_mount_protect(struct super_block *, ext4_fsblk_t);
|
||||||
|
|
||||||
|
/* verity.c */
|
||||||
|
extern const struct fsverity_operations ext4_verityops;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add new method to test whether block and inode bitmaps are properly
|
* Add new method to test whether block and inode bitmaps are properly
|
||||||
* initialized. With uninit_bg reading the block from disk is not enough
|
* initialized. With uninit_bg reading the block from disk is not enough
|
||||||
|
@ -457,6 +457,10 @@ static int ext4_file_open(struct inode * inode, struct file * filp)
|
|||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
ret = fsverity_file_open(inode, filp);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set up the jbd2_inode if we are opening the inode for
|
* Set up the jbd2_inode if we are opening the inode for
|
||||||
* writing and the journal is present
|
* writing and the journal is present
|
||||||
|
@ -1340,6 +1340,9 @@ retry_journal:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
bool extended = (pos + len > inode->i_size) &&
|
||||||
|
!ext4_verity_in_progress(inode);
|
||||||
|
|
||||||
unlock_page(page);
|
unlock_page(page);
|
||||||
/*
|
/*
|
||||||
* __block_write_begin may have instantiated a few blocks
|
* __block_write_begin may have instantiated a few blocks
|
||||||
@ -1349,11 +1352,11 @@ retry_journal:
|
|||||||
* Add inode to orphan list in case we crash before
|
* Add inode to orphan list in case we crash before
|
||||||
* truncate finishes
|
* truncate finishes
|
||||||
*/
|
*/
|
||||||
if (pos + len > inode->i_size && ext4_can_truncate(inode))
|
if (extended && ext4_can_truncate(inode))
|
||||||
ext4_orphan_add(handle, inode);
|
ext4_orphan_add(handle, inode);
|
||||||
|
|
||||||
ext4_journal_stop(handle);
|
ext4_journal_stop(handle);
|
||||||
if (pos + len > inode->i_size) {
|
if (extended) {
|
||||||
ext4_truncate_failed_write(inode);
|
ext4_truncate_failed_write(inode);
|
||||||
/*
|
/*
|
||||||
* If truncate failed early the inode might
|
* If truncate failed early the inode might
|
||||||
@ -1406,6 +1409,7 @@ static int ext4_write_end(struct file *file,
|
|||||||
int ret = 0, ret2;
|
int ret = 0, ret2;
|
||||||
int i_size_changed = 0;
|
int i_size_changed = 0;
|
||||||
int inline_data = ext4_has_inline_data(inode);
|
int inline_data = ext4_has_inline_data(inode);
|
||||||
|
bool verity = ext4_verity_in_progress(inode);
|
||||||
|
|
||||||
trace_ext4_write_end(inode, pos, len, copied);
|
trace_ext4_write_end(inode, pos, len, copied);
|
||||||
if (inline_data) {
|
if (inline_data) {
|
||||||
@ -1423,12 +1427,16 @@ static int ext4_write_end(struct file *file,
|
|||||||
/*
|
/*
|
||||||
* it's important to update i_size while still holding page lock:
|
* it's important to update i_size while still holding page lock:
|
||||||
* page writeout could otherwise come in and zero beyond i_size.
|
* page writeout could otherwise come in and zero beyond i_size.
|
||||||
|
*
|
||||||
|
* If FS_IOC_ENABLE_VERITY is running on this inode, then Merkle tree
|
||||||
|
* blocks are being written past EOF, so skip the i_size update.
|
||||||
*/
|
*/
|
||||||
i_size_changed = ext4_update_inode_size(inode, pos + copied);
|
if (!verity)
|
||||||
|
i_size_changed = ext4_update_inode_size(inode, pos + copied);
|
||||||
unlock_page(page);
|
unlock_page(page);
|
||||||
put_page(page);
|
put_page(page);
|
||||||
|
|
||||||
if (old_size < pos)
|
if (old_size < pos && !verity)
|
||||||
pagecache_isize_extended(inode, old_size, pos);
|
pagecache_isize_extended(inode, old_size, pos);
|
||||||
/*
|
/*
|
||||||
* Don't mark the inode dirty under page lock. First, it unnecessarily
|
* Don't mark the inode dirty under page lock. First, it unnecessarily
|
||||||
@ -1439,7 +1447,7 @@ static int ext4_write_end(struct file *file,
|
|||||||
if (i_size_changed || inline_data)
|
if (i_size_changed || inline_data)
|
||||||
ext4_mark_inode_dirty(handle, inode);
|
ext4_mark_inode_dirty(handle, inode);
|
||||||
|
|
||||||
if (pos + len > inode->i_size && ext4_can_truncate(inode))
|
if (pos + len > inode->i_size && !verity && ext4_can_truncate(inode))
|
||||||
/* if we have allocated more blocks and copied
|
/* if we have allocated more blocks and copied
|
||||||
* less. We will have blocks allocated outside
|
* less. We will have blocks allocated outside
|
||||||
* inode->i_size. So truncate them
|
* inode->i_size. So truncate them
|
||||||
@ -1450,7 +1458,7 @@ errout:
|
|||||||
if (!ret)
|
if (!ret)
|
||||||
ret = ret2;
|
ret = ret2;
|
||||||
|
|
||||||
if (pos + len > inode->i_size) {
|
if (pos + len > inode->i_size && !verity) {
|
||||||
ext4_truncate_failed_write(inode);
|
ext4_truncate_failed_write(inode);
|
||||||
/*
|
/*
|
||||||
* If truncate failed early the inode might still be
|
* If truncate failed early the inode might still be
|
||||||
@ -1511,6 +1519,7 @@ static int ext4_journalled_write_end(struct file *file,
|
|||||||
unsigned from, to;
|
unsigned from, to;
|
||||||
int size_changed = 0;
|
int size_changed = 0;
|
||||||
int inline_data = ext4_has_inline_data(inode);
|
int inline_data = ext4_has_inline_data(inode);
|
||||||
|
bool verity = ext4_verity_in_progress(inode);
|
||||||
|
|
||||||
trace_ext4_journalled_write_end(inode, pos, len, copied);
|
trace_ext4_journalled_write_end(inode, pos, len, copied);
|
||||||
from = pos & (PAGE_SIZE - 1);
|
from = pos & (PAGE_SIZE - 1);
|
||||||
@ -1540,13 +1549,14 @@ static int ext4_journalled_write_end(struct file *file,
|
|||||||
if (!partial)
|
if (!partial)
|
||||||
SetPageUptodate(page);
|
SetPageUptodate(page);
|
||||||
}
|
}
|
||||||
size_changed = ext4_update_inode_size(inode, pos + copied);
|
if (!verity)
|
||||||
|
size_changed = ext4_update_inode_size(inode, pos + copied);
|
||||||
ext4_set_inode_state(inode, EXT4_STATE_JDATA);
|
ext4_set_inode_state(inode, EXT4_STATE_JDATA);
|
||||||
EXT4_I(inode)->i_datasync_tid = handle->h_transaction->t_tid;
|
EXT4_I(inode)->i_datasync_tid = handle->h_transaction->t_tid;
|
||||||
unlock_page(page);
|
unlock_page(page);
|
||||||
put_page(page);
|
put_page(page);
|
||||||
|
|
||||||
if (old_size < pos)
|
if (old_size < pos && !verity)
|
||||||
pagecache_isize_extended(inode, old_size, pos);
|
pagecache_isize_extended(inode, old_size, pos);
|
||||||
|
|
||||||
if (size_changed || inline_data) {
|
if (size_changed || inline_data) {
|
||||||
@ -1555,7 +1565,7 @@ static int ext4_journalled_write_end(struct file *file,
|
|||||||
ret = ret2;
|
ret = ret2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pos + len > inode->i_size && ext4_can_truncate(inode))
|
if (pos + len > inode->i_size && !verity && ext4_can_truncate(inode))
|
||||||
/* if we have allocated more blocks and copied
|
/* if we have allocated more blocks and copied
|
||||||
* less. We will have blocks allocated outside
|
* less. We will have blocks allocated outside
|
||||||
* inode->i_size. So truncate them
|
* inode->i_size. So truncate them
|
||||||
@ -1566,7 +1576,7 @@ errout:
|
|||||||
ret2 = ext4_journal_stop(handle);
|
ret2 = ext4_journal_stop(handle);
|
||||||
if (!ret)
|
if (!ret)
|
||||||
ret = ret2;
|
ret = ret2;
|
||||||
if (pos + len > inode->i_size) {
|
if (pos + len > inode->i_size && !verity) {
|
||||||
ext4_truncate_failed_write(inode);
|
ext4_truncate_failed_write(inode);
|
||||||
/*
|
/*
|
||||||
* If truncate failed early the inode might still be
|
* If truncate failed early the inode might still be
|
||||||
@ -2162,7 +2172,8 @@ static int ext4_writepage(struct page *page,
|
|||||||
|
|
||||||
trace_ext4_writepage(page);
|
trace_ext4_writepage(page);
|
||||||
size = i_size_read(inode);
|
size = i_size_read(inode);
|
||||||
if (page->index == size >> PAGE_SHIFT)
|
if (page->index == size >> PAGE_SHIFT &&
|
||||||
|
!ext4_verity_in_progress(inode))
|
||||||
len = size & ~PAGE_MASK;
|
len = size & ~PAGE_MASK;
|
||||||
else
|
else
|
||||||
len = PAGE_SIZE;
|
len = PAGE_SIZE;
|
||||||
@ -2246,7 +2257,8 @@ static int mpage_submit_page(struct mpage_da_data *mpd, struct page *page)
|
|||||||
* after page tables are updated.
|
* after page tables are updated.
|
||||||
*/
|
*/
|
||||||
size = i_size_read(mpd->inode);
|
size = i_size_read(mpd->inode);
|
||||||
if (page->index == size >> PAGE_SHIFT)
|
if (page->index == size >> PAGE_SHIFT &&
|
||||||
|
!ext4_verity_in_progress(mpd->inode))
|
||||||
len = size & ~PAGE_MASK;
|
len = size & ~PAGE_MASK;
|
||||||
else
|
else
|
||||||
len = PAGE_SIZE;
|
len = PAGE_SIZE;
|
||||||
@ -2345,6 +2357,9 @@ static int mpage_process_page_bufs(struct mpage_da_data *mpd,
|
|||||||
ext4_lblk_t blocks = (i_size_read(inode) + i_blocksize(inode) - 1)
|
ext4_lblk_t blocks = (i_size_read(inode) + i_blocksize(inode) - 1)
|
||||||
>> inode->i_blkbits;
|
>> inode->i_blkbits;
|
||||||
|
|
||||||
|
if (ext4_verity_in_progress(inode))
|
||||||
|
blocks = EXT_MAX_BLOCKS;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
BUG_ON(buffer_locked(bh));
|
BUG_ON(buffer_locked(bh));
|
||||||
|
|
||||||
@ -3061,8 +3076,8 @@ static int ext4_da_write_begin(struct file *file, struct address_space *mapping,
|
|||||||
|
|
||||||
index = pos >> PAGE_SHIFT;
|
index = pos >> PAGE_SHIFT;
|
||||||
|
|
||||||
if (ext4_nonda_switch(inode->i_sb) ||
|
if (ext4_nonda_switch(inode->i_sb) || S_ISLNK(inode->i_mode) ||
|
||||||
S_ISLNK(inode->i_mode)) {
|
ext4_verity_in_progress(inode)) {
|
||||||
*fsdata = (void *)FALL_BACK_TO_NONDELALLOC;
|
*fsdata = (void *)FALL_BACK_TO_NONDELALLOC;
|
||||||
return ext4_write_begin(file, mapping, pos,
|
return ext4_write_begin(file, mapping, pos,
|
||||||
len, flags, pagep, fsdata);
|
len, flags, pagep, fsdata);
|
||||||
@ -3897,6 +3912,8 @@ static ssize_t ext4_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
|
|||||||
if (IS_ENCRYPTED(inode) && S_ISREG(inode->i_mode))
|
if (IS_ENCRYPTED(inode) && S_ISREG(inode->i_mode))
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
|
if (fsverity_active(inode))
|
||||||
|
return 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If we are doing data journalling we don't support O_DIRECT
|
* If we are doing data journalling we don't support O_DIRECT
|
||||||
@ -4736,6 +4753,8 @@ static bool ext4_should_use_dax(struct inode *inode)
|
|||||||
return false;
|
return false;
|
||||||
if (ext4_test_inode_flag(inode, EXT4_INODE_ENCRYPT))
|
if (ext4_test_inode_flag(inode, EXT4_INODE_ENCRYPT))
|
||||||
return false;
|
return false;
|
||||||
|
if (ext4_test_inode_flag(inode, EXT4_INODE_VERITY))
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4760,9 +4779,11 @@ void ext4_set_inode_flags(struct inode *inode)
|
|||||||
new_fl |= S_ENCRYPTED;
|
new_fl |= S_ENCRYPTED;
|
||||||
if (flags & EXT4_CASEFOLD_FL)
|
if (flags & EXT4_CASEFOLD_FL)
|
||||||
new_fl |= S_CASEFOLD;
|
new_fl |= S_CASEFOLD;
|
||||||
|
if (flags & EXT4_VERITY_FL)
|
||||||
|
new_fl |= S_VERITY;
|
||||||
inode_set_flags(inode, new_fl,
|
inode_set_flags(inode, new_fl,
|
||||||
S_SYNC|S_APPEND|S_IMMUTABLE|S_NOATIME|S_DIRSYNC|S_DAX|
|
S_SYNC|S_APPEND|S_IMMUTABLE|S_NOATIME|S_DIRSYNC|S_DAX|
|
||||||
S_ENCRYPTED|S_CASEFOLD);
|
S_ENCRYPTED|S_CASEFOLD|S_VERITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
static blkcnt_t ext4_inode_blocks(struct ext4_inode *raw_inode,
|
static blkcnt_t ext4_inode_blocks(struct ext4_inode *raw_inode,
|
||||||
@ -5552,6 +5573,10 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr)
|
|||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
|
error = fsverity_prepare_setattr(dentry, attr);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
if (is_quota_modification(inode, attr)) {
|
if (is_quota_modification(inode, attr)) {
|
||||||
error = dquot_initialize(inode);
|
error = dquot_initialize(inode);
|
||||||
if (error)
|
if (error)
|
||||||
|
@ -1198,6 +1198,17 @@ out:
|
|||||||
}
|
}
|
||||||
case EXT4_IOC_SHUTDOWN:
|
case EXT4_IOC_SHUTDOWN:
|
||||||
return ext4_shutdown(sb, arg);
|
return ext4_shutdown(sb, arg);
|
||||||
|
|
||||||
|
case FS_IOC_ENABLE_VERITY:
|
||||||
|
if (!ext4_has_feature_verity(sb))
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
return fsverity_ioctl_enable(filp, (const void __user *)arg);
|
||||||
|
|
||||||
|
case FS_IOC_MEASURE_VERITY:
|
||||||
|
if (!ext4_has_feature_verity(sb))
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
return fsverity_ioctl_measure(filp, (void __user *)arg);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return -ENOTTY;
|
return -ENOTTY;
|
||||||
}
|
}
|
||||||
@ -1265,6 +1276,8 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||||||
case FS_IOC_GET_ENCRYPTION_KEY_STATUS:
|
case FS_IOC_GET_ENCRYPTION_KEY_STATUS:
|
||||||
case EXT4_IOC_SHUTDOWN:
|
case EXT4_IOC_SHUTDOWN:
|
||||||
case FS_IOC_GETFSMAP:
|
case FS_IOC_GETFSMAP:
|
||||||
|
case FS_IOC_ENABLE_VERITY:
|
||||||
|
case FS_IOC_MEASURE_VERITY:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return -ENOIOCTLCMD;
|
return -ENOIOCTLCMD;
|
||||||
|
@ -47,13 +47,103 @@
|
|||||||
|
|
||||||
#include "ext4.h"
|
#include "ext4.h"
|
||||||
|
|
||||||
static inline bool ext4_bio_encrypted(struct bio *bio)
|
#define NUM_PREALLOC_POST_READ_CTXS 128
|
||||||
|
|
||||||
|
static struct kmem_cache *bio_post_read_ctx_cache;
|
||||||
|
static mempool_t *bio_post_read_ctx_pool;
|
||||||
|
|
||||||
|
/* postprocessing steps for read bios */
|
||||||
|
enum bio_post_read_step {
|
||||||
|
STEP_INITIAL = 0,
|
||||||
|
STEP_DECRYPT,
|
||||||
|
STEP_VERITY,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct bio_post_read_ctx {
|
||||||
|
struct bio *bio;
|
||||||
|
struct work_struct work;
|
||||||
|
unsigned int cur_step;
|
||||||
|
unsigned int enabled_steps;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void __read_end_io(struct bio *bio)
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_FS_ENCRYPTION
|
struct page *page;
|
||||||
return unlikely(bio->bi_private != NULL);
|
struct bio_vec *bv;
|
||||||
#else
|
struct bvec_iter_all iter_all;
|
||||||
return false;
|
|
||||||
#endif
|
bio_for_each_segment_all(bv, bio, iter_all) {
|
||||||
|
page = bv->bv_page;
|
||||||
|
|
||||||
|
/* PG_error was set if any post_read step failed */
|
||||||
|
if (bio->bi_status || PageError(page)) {
|
||||||
|
ClearPageUptodate(page);
|
||||||
|
/* will re-read again later */
|
||||||
|
ClearPageError(page);
|
||||||
|
} else {
|
||||||
|
SetPageUptodate(page);
|
||||||
|
}
|
||||||
|
unlock_page(page);
|
||||||
|
}
|
||||||
|
if (bio->bi_private)
|
||||||
|
mempool_free(bio->bi_private, bio_post_read_ctx_pool);
|
||||||
|
bio_put(bio);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bio_post_read_processing(struct bio_post_read_ctx *ctx);
|
||||||
|
|
||||||
|
static void decrypt_work(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct bio_post_read_ctx *ctx =
|
||||||
|
container_of(work, struct bio_post_read_ctx, work);
|
||||||
|
|
||||||
|
fscrypt_decrypt_bio(ctx->bio);
|
||||||
|
|
||||||
|
bio_post_read_processing(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void verity_work(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct bio_post_read_ctx *ctx =
|
||||||
|
container_of(work, struct bio_post_read_ctx, work);
|
||||||
|
|
||||||
|
fsverity_verify_bio(ctx->bio);
|
||||||
|
|
||||||
|
bio_post_read_processing(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bio_post_read_processing(struct bio_post_read_ctx *ctx)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
switch (++ctx->cur_step) {
|
||||||
|
case STEP_DECRYPT:
|
||||||
|
if (ctx->enabled_steps & (1 << STEP_DECRYPT)) {
|
||||||
|
INIT_WORK(&ctx->work, decrypt_work);
|
||||||
|
fscrypt_enqueue_decrypt_work(&ctx->work);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx->cur_step++;
|
||||||
|
/* fall-through */
|
||||||
|
case STEP_VERITY:
|
||||||
|
if (ctx->enabled_steps & (1 << STEP_VERITY)) {
|
||||||
|
INIT_WORK(&ctx->work, verity_work);
|
||||||
|
fsverity_enqueue_verify_work(&ctx->work);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx->cur_step++;
|
||||||
|
/* fall-through */
|
||||||
|
default:
|
||||||
|
__read_end_io(ctx->bio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bio_post_read_required(struct bio *bio)
|
||||||
|
{
|
||||||
|
return bio->bi_private && !bio->bi_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -70,30 +160,53 @@ static inline bool ext4_bio_encrypted(struct bio *bio)
|
|||||||
*/
|
*/
|
||||||
static void mpage_end_io(struct bio *bio)
|
static void mpage_end_io(struct bio *bio)
|
||||||
{
|
{
|
||||||
struct bio_vec *bv;
|
if (bio_post_read_required(bio)) {
|
||||||
struct bvec_iter_all iter_all;
|
struct bio_post_read_ctx *ctx = bio->bi_private;
|
||||||
|
|
||||||
if (ext4_bio_encrypted(bio)) {
|
ctx->cur_step = STEP_INITIAL;
|
||||||
if (bio->bi_status) {
|
bio_post_read_processing(ctx);
|
||||||
fscrypt_release_ctx(bio->bi_private);
|
return;
|
||||||
} else {
|
|
||||||
fscrypt_enqueue_decrypt_bio(bio->bi_private, bio);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bio_for_each_segment_all(bv, bio, iter_all) {
|
__read_end_io(bio);
|
||||||
struct page *page = bv->bv_page;
|
}
|
||||||
|
|
||||||
if (!bio->bi_status) {
|
static inline bool ext4_need_verity(const struct inode *inode, pgoff_t idx)
|
||||||
SetPageUptodate(page);
|
{
|
||||||
} else {
|
return fsverity_active(inode) &&
|
||||||
ClearPageUptodate(page);
|
idx < DIV_ROUND_UP(inode->i_size, PAGE_SIZE);
|
||||||
SetPageError(page);
|
}
|
||||||
}
|
|
||||||
unlock_page(page);
|
static struct bio_post_read_ctx *get_bio_post_read_ctx(struct inode *inode,
|
||||||
|
struct bio *bio,
|
||||||
|
pgoff_t first_idx)
|
||||||
|
{
|
||||||
|
unsigned int post_read_steps = 0;
|
||||||
|
struct bio_post_read_ctx *ctx = NULL;
|
||||||
|
|
||||||
|
if (IS_ENCRYPTED(inode) && S_ISREG(inode->i_mode))
|
||||||
|
post_read_steps |= 1 << STEP_DECRYPT;
|
||||||
|
|
||||||
|
if (ext4_need_verity(inode, first_idx))
|
||||||
|
post_read_steps |= 1 << STEP_VERITY;
|
||||||
|
|
||||||
|
if (post_read_steps) {
|
||||||
|
ctx = mempool_alloc(bio_post_read_ctx_pool, GFP_NOFS);
|
||||||
|
if (!ctx)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
ctx->bio = bio;
|
||||||
|
ctx->enabled_steps = post_read_steps;
|
||||||
|
bio->bi_private = ctx;
|
||||||
}
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
bio_put(bio);
|
static inline loff_t ext4_readpage_limit(struct inode *inode)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_FS_VERITY) &&
|
||||||
|
(IS_VERITY(inode) || ext4_verity_in_progress(inode)))
|
||||||
|
return inode->i_sb->s_maxbytes;
|
||||||
|
|
||||||
|
return i_size_read(inode);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ext4_mpage_readpages(struct address_space *mapping,
|
int ext4_mpage_readpages(struct address_space *mapping,
|
||||||
@ -141,7 +254,8 @@ int ext4_mpage_readpages(struct address_space *mapping,
|
|||||||
|
|
||||||
block_in_file = (sector_t)page->index << (PAGE_SHIFT - blkbits);
|
block_in_file = (sector_t)page->index << (PAGE_SHIFT - blkbits);
|
||||||
last_block = block_in_file + nr_pages * blocks_per_page;
|
last_block = block_in_file + nr_pages * blocks_per_page;
|
||||||
last_block_in_file = (i_size_read(inode) + blocksize - 1) >> blkbits;
|
last_block_in_file = (ext4_readpage_limit(inode) +
|
||||||
|
blocksize - 1) >> blkbits;
|
||||||
if (last_block > last_block_in_file)
|
if (last_block > last_block_in_file)
|
||||||
last_block = last_block_in_file;
|
last_block = last_block_in_file;
|
||||||
page_block = 0;
|
page_block = 0;
|
||||||
@ -218,6 +332,9 @@ int ext4_mpage_readpages(struct address_space *mapping,
|
|||||||
zero_user_segment(page, first_hole << blkbits,
|
zero_user_segment(page, first_hole << blkbits,
|
||||||
PAGE_SIZE);
|
PAGE_SIZE);
|
||||||
if (first_hole == 0) {
|
if (first_hole == 0) {
|
||||||
|
if (ext4_need_verity(inode, page->index) &&
|
||||||
|
!fsverity_verify_page(page))
|
||||||
|
goto set_error_page;
|
||||||
SetPageUptodate(page);
|
SetPageUptodate(page);
|
||||||
unlock_page(page);
|
unlock_page(page);
|
||||||
goto next_page;
|
goto next_page;
|
||||||
@ -241,18 +358,16 @@ int ext4_mpage_readpages(struct address_space *mapping,
|
|||||||
bio = NULL;
|
bio = NULL;
|
||||||
}
|
}
|
||||||
if (bio == NULL) {
|
if (bio == NULL) {
|
||||||
struct fscrypt_ctx *ctx = NULL;
|
struct bio_post_read_ctx *ctx;
|
||||||
|
|
||||||
if (IS_ENCRYPTED(inode) && S_ISREG(inode->i_mode)) {
|
|
||||||
ctx = fscrypt_get_ctx(GFP_NOFS);
|
|
||||||
if (IS_ERR(ctx))
|
|
||||||
goto set_error_page;
|
|
||||||
}
|
|
||||||
bio = bio_alloc(GFP_KERNEL,
|
bio = bio_alloc(GFP_KERNEL,
|
||||||
min_t(int, nr_pages, BIO_MAX_PAGES));
|
min_t(int, nr_pages, BIO_MAX_PAGES));
|
||||||
if (!bio) {
|
if (!bio)
|
||||||
if (ctx)
|
goto set_error_page;
|
||||||
fscrypt_release_ctx(ctx);
|
ctx = get_bio_post_read_ctx(inode, bio, page->index);
|
||||||
|
if (IS_ERR(ctx)) {
|
||||||
|
bio_put(bio);
|
||||||
|
bio = NULL;
|
||||||
goto set_error_page;
|
goto set_error_page;
|
||||||
}
|
}
|
||||||
bio_set_dev(bio, bdev);
|
bio_set_dev(bio, bdev);
|
||||||
@ -293,3 +408,29 @@ int ext4_mpage_readpages(struct address_space *mapping,
|
|||||||
submit_bio(bio);
|
submit_bio(bio);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int __init ext4_init_post_read_processing(void)
|
||||||
|
{
|
||||||
|
bio_post_read_ctx_cache =
|
||||||
|
kmem_cache_create("ext4_bio_post_read_ctx",
|
||||||
|
sizeof(struct bio_post_read_ctx), 0, 0, NULL);
|
||||||
|
if (!bio_post_read_ctx_cache)
|
||||||
|
goto fail;
|
||||||
|
bio_post_read_ctx_pool =
|
||||||
|
mempool_create_slab_pool(NUM_PREALLOC_POST_READ_CTXS,
|
||||||
|
bio_post_read_ctx_cache);
|
||||||
|
if (!bio_post_read_ctx_pool)
|
||||||
|
goto fail_free_cache;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail_free_cache:
|
||||||
|
kmem_cache_destroy(bio_post_read_ctx_cache);
|
||||||
|
fail:
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ext4_exit_post_read_processing(void)
|
||||||
|
{
|
||||||
|
mempool_destroy(bio_post_read_ctx_pool);
|
||||||
|
kmem_cache_destroy(bio_post_read_ctx_cache);
|
||||||
|
}
|
||||||
|
@ -1182,6 +1182,7 @@ void ext4_clear_inode(struct inode *inode)
|
|||||||
EXT4_I(inode)->jinode = NULL;
|
EXT4_I(inode)->jinode = NULL;
|
||||||
}
|
}
|
||||||
fscrypt_put_encryption_info(inode);
|
fscrypt_put_encryption_info(inode);
|
||||||
|
fsverity_cleanup_inode(inode);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct inode *ext4_nfs_get_inode(struct super_block *sb,
|
static struct inode *ext4_nfs_get_inode(struct super_block *sb,
|
||||||
@ -4275,6 +4276,9 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
|
|||||||
#ifdef CONFIG_FS_ENCRYPTION
|
#ifdef CONFIG_FS_ENCRYPTION
|
||||||
sb->s_cop = &ext4_cryptops;
|
sb->s_cop = &ext4_cryptops;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef CONFIG_FS_VERITY
|
||||||
|
sb->s_vop = &ext4_verityops;
|
||||||
|
#endif
|
||||||
#ifdef CONFIG_QUOTA
|
#ifdef CONFIG_QUOTA
|
||||||
sb->dq_op = &ext4_quota_operations;
|
sb->dq_op = &ext4_quota_operations;
|
||||||
if (ext4_has_feature_quota(sb))
|
if (ext4_has_feature_quota(sb))
|
||||||
@ -4422,6 +4426,11 @@ no_journal:
|
|||||||
goto failed_mount_wq;
|
goto failed_mount_wq;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ext4_has_feature_verity(sb) && blocksize != PAGE_SIZE) {
|
||||||
|
ext4_msg(sb, KERN_ERR, "Unsupported blocksize for fs-verity");
|
||||||
|
goto failed_mount_wq;
|
||||||
|
}
|
||||||
|
|
||||||
if (DUMMY_ENCRYPTION_ENABLED(sbi) && !sb_rdonly(sb) &&
|
if (DUMMY_ENCRYPTION_ENABLED(sbi) && !sb_rdonly(sb) &&
|
||||||
!ext4_has_feature_encrypt(sb)) {
|
!ext4_has_feature_encrypt(sb)) {
|
||||||
ext4_set_feature_encrypt(sb);
|
ext4_set_feature_encrypt(sb);
|
||||||
@ -6097,6 +6106,10 @@ static int __init ext4_init_fs(void)
|
|||||||
return err;
|
return err;
|
||||||
|
|
||||||
err = ext4_init_pending();
|
err = ext4_init_pending();
|
||||||
|
if (err)
|
||||||
|
goto out7;
|
||||||
|
|
||||||
|
err = ext4_init_post_read_processing();
|
||||||
if (err)
|
if (err)
|
||||||
goto out6;
|
goto out6;
|
||||||
|
|
||||||
@ -6138,8 +6151,10 @@ out3:
|
|||||||
out4:
|
out4:
|
||||||
ext4_exit_pageio();
|
ext4_exit_pageio();
|
||||||
out5:
|
out5:
|
||||||
ext4_exit_pending();
|
ext4_exit_post_read_processing();
|
||||||
out6:
|
out6:
|
||||||
|
ext4_exit_pending();
|
||||||
|
out7:
|
||||||
ext4_exit_es();
|
ext4_exit_es();
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
@ -6156,6 +6171,7 @@ static void __exit ext4_exit_fs(void)
|
|||||||
ext4_exit_sysfs();
|
ext4_exit_sysfs();
|
||||||
ext4_exit_system_zone();
|
ext4_exit_system_zone();
|
||||||
ext4_exit_pageio();
|
ext4_exit_pageio();
|
||||||
|
ext4_exit_post_read_processing();
|
||||||
ext4_exit_es();
|
ext4_exit_es();
|
||||||
ext4_exit_pending();
|
ext4_exit_pending();
|
||||||
}
|
}
|
||||||
|
@ -242,6 +242,9 @@ EXT4_ATTR_FEATURE(encryption);
|
|||||||
#ifdef CONFIG_UNICODE
|
#ifdef CONFIG_UNICODE
|
||||||
EXT4_ATTR_FEATURE(casefold);
|
EXT4_ATTR_FEATURE(casefold);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef CONFIG_FS_VERITY
|
||||||
|
EXT4_ATTR_FEATURE(verity);
|
||||||
|
#endif
|
||||||
EXT4_ATTR_FEATURE(metadata_csum_seed);
|
EXT4_ATTR_FEATURE(metadata_csum_seed);
|
||||||
|
|
||||||
static struct attribute *ext4_feat_attrs[] = {
|
static struct attribute *ext4_feat_attrs[] = {
|
||||||
@ -253,6 +256,9 @@ static struct attribute *ext4_feat_attrs[] = {
|
|||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_UNICODE
|
#ifdef CONFIG_UNICODE
|
||||||
ATTR_LIST(casefold),
|
ATTR_LIST(casefold),
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_FS_VERITY
|
||||||
|
ATTR_LIST(verity),
|
||||||
#endif
|
#endif
|
||||||
ATTR_LIST(metadata_csum_seed),
|
ATTR_LIST(metadata_csum_seed),
|
||||||
NULL,
|
NULL,
|
||||||
|
367
fs/ext4/verity.c
Normal file
367
fs/ext4/verity.c
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* fs/ext4/verity.c: fs-verity support for ext4
|
||||||
|
*
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Implementation of fsverity_operations for ext4.
|
||||||
|
*
|
||||||
|
* ext4 stores the verity metadata (Merkle tree and fsverity_descriptor) past
|
||||||
|
* the end of the file, starting at the first 64K boundary beyond i_size. This
|
||||||
|
* approach works because (a) verity files are readonly, and (b) pages fully
|
||||||
|
* beyond i_size aren't visible to userspace but can be read/written internally
|
||||||
|
* by ext4 with only some relatively small changes to ext4. This approach
|
||||||
|
* avoids having to depend on the EA_INODE feature and on rearchitecturing
|
||||||
|
* ext4's xattr support to support paging multi-gigabyte xattrs into memory, and
|
||||||
|
* to support encrypting xattrs. Note that the verity metadata *must* be
|
||||||
|
* encrypted when the file is, since it contains hashes of the plaintext data.
|
||||||
|
*
|
||||||
|
* Using a 64K boundary rather than a 4K one keeps things ready for
|
||||||
|
* architectures with 64K pages, and it doesn't necessarily waste space on-disk
|
||||||
|
* since there can be a hole between i_size and the start of the Merkle tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/quotaops.h>
|
||||||
|
|
||||||
|
#include "ext4.h"
|
||||||
|
#include "ext4_extents.h"
|
||||||
|
#include "ext4_jbd2.h"
|
||||||
|
|
||||||
|
static inline loff_t ext4_verity_metadata_pos(const struct inode *inode)
|
||||||
|
{
|
||||||
|
return round_up(inode->i_size, 65536);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read some verity metadata from the inode. __vfs_read() can't be used because
|
||||||
|
* we need to read beyond i_size.
|
||||||
|
*/
|
||||||
|
static int pagecache_read(struct inode *inode, void *buf, size_t count,
|
||||||
|
loff_t pos)
|
||||||
|
{
|
||||||
|
while (count) {
|
||||||
|
size_t n = min_t(size_t, count,
|
||||||
|
PAGE_SIZE - offset_in_page(pos));
|
||||||
|
struct page *page;
|
||||||
|
void *addr;
|
||||||
|
|
||||||
|
page = read_mapping_page(inode->i_mapping, pos >> PAGE_SHIFT,
|
||||||
|
NULL);
|
||||||
|
if (IS_ERR(page))
|
||||||
|
return PTR_ERR(page);
|
||||||
|
|
||||||
|
addr = kmap_atomic(page);
|
||||||
|
memcpy(buf, addr + offset_in_page(pos), n);
|
||||||
|
kunmap_atomic(addr);
|
||||||
|
|
||||||
|
put_page(page);
|
||||||
|
|
||||||
|
buf += n;
|
||||||
|
pos += n;
|
||||||
|
count -= n;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write some verity metadata to the inode for FS_IOC_ENABLE_VERITY.
|
||||||
|
* kernel_write() can't be used because the file descriptor is readonly.
|
||||||
|
*/
|
||||||
|
static int pagecache_write(struct inode *inode, const void *buf, size_t count,
|
||||||
|
loff_t pos)
|
||||||
|
{
|
||||||
|
if (pos + count > inode->i_sb->s_maxbytes)
|
||||||
|
return -EFBIG;
|
||||||
|
|
||||||
|
while (count) {
|
||||||
|
size_t n = min_t(size_t, count,
|
||||||
|
PAGE_SIZE - offset_in_page(pos));
|
||||||
|
struct page *page;
|
||||||
|
void *fsdata;
|
||||||
|
void *addr;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
res = pagecache_write_begin(NULL, inode->i_mapping, pos, n, 0,
|
||||||
|
&page, &fsdata);
|
||||||
|
if (res)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
addr = kmap_atomic(page);
|
||||||
|
memcpy(addr + offset_in_page(pos), buf, n);
|
||||||
|
kunmap_atomic(addr);
|
||||||
|
|
||||||
|
res = pagecache_write_end(NULL, inode->i_mapping, pos, n, n,
|
||||||
|
page, fsdata);
|
||||||
|
if (res < 0)
|
||||||
|
return res;
|
||||||
|
if (res != n)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
buf += n;
|
||||||
|
pos += n;
|
||||||
|
count -= n;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext4_begin_enable_verity(struct file *filp)
|
||||||
|
{
|
||||||
|
struct inode *inode = file_inode(filp);
|
||||||
|
const int credits = 2; /* superblock and inode for ext4_orphan_add() */
|
||||||
|
handle_t *handle;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (ext4_verity_in_progress(inode))
|
||||||
|
return -EBUSY;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since the file was opened readonly, we have to initialize the jbd
|
||||||
|
* inode and quotas here and not rely on ->open() doing it. This must
|
||||||
|
* be done before evicting the inline data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
err = ext4_inode_attach_jinode(inode);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
err = dquot_initialize(inode);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
err = ext4_convert_inline_data(inode);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (!ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {
|
||||||
|
ext4_warning_inode(inode,
|
||||||
|
"verity is only allowed on extent-based files");
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ext4 uses the last allocated block to find the verity descriptor, so
|
||||||
|
* we must remove any other blocks past EOF which might confuse things.
|
||||||
|
*/
|
||||||
|
err = ext4_truncate(inode);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
handle = ext4_journal_start(inode, EXT4_HT_INODE, credits);
|
||||||
|
if (IS_ERR(handle))
|
||||||
|
return PTR_ERR(handle);
|
||||||
|
|
||||||
|
err = ext4_orphan_add(handle, inode);
|
||||||
|
if (err == 0)
|
||||||
|
ext4_set_inode_state(inode, EXT4_STATE_VERITY_IN_PROGRESS);
|
||||||
|
|
||||||
|
ext4_journal_stop(handle);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ext4 stores the verity descriptor beginning on the next filesystem block
|
||||||
|
* boundary after the Merkle tree. Then, the descriptor size is stored in the
|
||||||
|
* last 4 bytes of the last allocated filesystem block --- which is either the
|
||||||
|
* block in which the descriptor ends, or the next block after that if there
|
||||||
|
* weren't at least 4 bytes remaining.
|
||||||
|
*
|
||||||
|
* We can't simply store the descriptor in an xattr because it *must* be
|
||||||
|
* encrypted when ext4 encryption is used, but ext4 encryption doesn't encrypt
|
||||||
|
* xattrs. Also, if the descriptor includes a large signature blob it may be
|
||||||
|
* too large to store in an xattr without the EA_INODE feature.
|
||||||
|
*/
|
||||||
|
static int ext4_write_verity_descriptor(struct inode *inode, const void *desc,
|
||||||
|
size_t desc_size, u64 merkle_tree_size)
|
||||||
|
{
|
||||||
|
const u64 desc_pos = round_up(ext4_verity_metadata_pos(inode) +
|
||||||
|
merkle_tree_size, i_blocksize(inode));
|
||||||
|
const u64 desc_end = desc_pos + desc_size;
|
||||||
|
const __le32 desc_size_disk = cpu_to_le32(desc_size);
|
||||||
|
const u64 desc_size_pos = round_up(desc_end + sizeof(desc_size_disk),
|
||||||
|
i_blocksize(inode)) -
|
||||||
|
sizeof(desc_size_disk);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = pagecache_write(inode, desc, desc_size, desc_pos);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
return pagecache_write(inode, &desc_size_disk, sizeof(desc_size_disk),
|
||||||
|
desc_size_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext4_end_enable_verity(struct file *filp, const void *desc,
|
||||||
|
size_t desc_size, u64 merkle_tree_size)
|
||||||
|
{
|
||||||
|
struct inode *inode = file_inode(filp);
|
||||||
|
const int credits = 2; /* superblock and inode for ext4_orphan_del() */
|
||||||
|
handle_t *handle;
|
||||||
|
int err = 0;
|
||||||
|
int err2;
|
||||||
|
|
||||||
|
if (desc != NULL) {
|
||||||
|
/* Succeeded; write the verity descriptor. */
|
||||||
|
err = ext4_write_verity_descriptor(inode, desc, desc_size,
|
||||||
|
merkle_tree_size);
|
||||||
|
|
||||||
|
/* Write all pages before clearing VERITY_IN_PROGRESS. */
|
||||||
|
if (!err)
|
||||||
|
err = filemap_write_and_wait(inode->i_mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we failed, truncate anything we wrote past i_size. */
|
||||||
|
if (desc == NULL || err)
|
||||||
|
ext4_truncate(inode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We must always clean up by clearing EXT4_STATE_VERITY_IN_PROGRESS and
|
||||||
|
* deleting the inode from the orphan list, even if something failed.
|
||||||
|
* If everything succeeded, we'll also set the verity bit in the same
|
||||||
|
* transaction.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ext4_clear_inode_state(inode, EXT4_STATE_VERITY_IN_PROGRESS);
|
||||||
|
|
||||||
|
handle = ext4_journal_start(inode, EXT4_HT_INODE, credits);
|
||||||
|
if (IS_ERR(handle)) {
|
||||||
|
ext4_orphan_del(NULL, inode);
|
||||||
|
return PTR_ERR(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
err2 = ext4_orphan_del(handle, inode);
|
||||||
|
if (err2)
|
||||||
|
goto out_stop;
|
||||||
|
|
||||||
|
if (desc != NULL && !err) {
|
||||||
|
struct ext4_iloc iloc;
|
||||||
|
|
||||||
|
err = ext4_reserve_inode_write(handle, inode, &iloc);
|
||||||
|
if (err)
|
||||||
|
goto out_stop;
|
||||||
|
ext4_set_inode_flag(inode, EXT4_INODE_VERITY);
|
||||||
|
ext4_set_inode_flags(inode);
|
||||||
|
err = ext4_mark_iloc_dirty(handle, inode, &iloc);
|
||||||
|
}
|
||||||
|
out_stop:
|
||||||
|
ext4_journal_stop(handle);
|
||||||
|
return err ?: err2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext4_get_verity_descriptor_location(struct inode *inode,
|
||||||
|
size_t *desc_size_ret,
|
||||||
|
u64 *desc_pos_ret)
|
||||||
|
{
|
||||||
|
struct ext4_ext_path *path;
|
||||||
|
struct ext4_extent *last_extent;
|
||||||
|
u32 end_lblk;
|
||||||
|
u64 desc_size_pos;
|
||||||
|
__le32 desc_size_disk;
|
||||||
|
u32 desc_size;
|
||||||
|
u64 desc_pos;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Descriptor size is in last 4 bytes of last allocated block.
|
||||||
|
* See ext4_write_verity_descriptor().
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {
|
||||||
|
EXT4_ERROR_INODE(inode, "verity file doesn't use extents");
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = ext4_find_extent(inode, EXT_MAX_BLOCKS - 1, NULL, 0);
|
||||||
|
if (IS_ERR(path))
|
||||||
|
return PTR_ERR(path);
|
||||||
|
|
||||||
|
last_extent = path[path->p_depth].p_ext;
|
||||||
|
if (!last_extent) {
|
||||||
|
EXT4_ERROR_INODE(inode, "verity file has no extents");
|
||||||
|
ext4_ext_drop_refs(path);
|
||||||
|
kfree(path);
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
end_lblk = le32_to_cpu(last_extent->ee_block) +
|
||||||
|
ext4_ext_get_actual_len(last_extent);
|
||||||
|
desc_size_pos = (u64)end_lblk << inode->i_blkbits;
|
||||||
|
ext4_ext_drop_refs(path);
|
||||||
|
kfree(path);
|
||||||
|
|
||||||
|
if (desc_size_pos < sizeof(desc_size_disk))
|
||||||
|
goto bad;
|
||||||
|
desc_size_pos -= sizeof(desc_size_disk);
|
||||||
|
|
||||||
|
err = pagecache_read(inode, &desc_size_disk, sizeof(desc_size_disk),
|
||||||
|
desc_size_pos);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
desc_size = le32_to_cpu(desc_size_disk);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The descriptor is stored just before the desc_size_disk, but starting
|
||||||
|
* on a filesystem block boundary.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (desc_size > INT_MAX || desc_size > desc_size_pos)
|
||||||
|
goto bad;
|
||||||
|
|
||||||
|
desc_pos = round_down(desc_size_pos - desc_size, i_blocksize(inode));
|
||||||
|
if (desc_pos < ext4_verity_metadata_pos(inode))
|
||||||
|
goto bad;
|
||||||
|
|
||||||
|
*desc_size_ret = desc_size;
|
||||||
|
*desc_pos_ret = desc_pos;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
bad:
|
||||||
|
EXT4_ERROR_INODE(inode, "verity file corrupted; can't find descriptor");
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext4_get_verity_descriptor(struct inode *inode, void *buf,
|
||||||
|
size_t buf_size)
|
||||||
|
{
|
||||||
|
size_t desc_size = 0;
|
||||||
|
u64 desc_pos = 0;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = ext4_get_verity_descriptor_location(inode, &desc_size, &desc_pos);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (buf_size) {
|
||||||
|
if (desc_size > buf_size)
|
||||||
|
return -ERANGE;
|
||||||
|
err = pagecache_read(inode, buf, desc_size, desc_pos);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
return desc_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct page *ext4_read_merkle_tree_page(struct inode *inode,
|
||||||
|
pgoff_t index)
|
||||||
|
{
|
||||||
|
index += ext4_verity_metadata_pos(inode) >> PAGE_SHIFT;
|
||||||
|
|
||||||
|
return read_mapping_page(inode->i_mapping, index, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext4_write_merkle_tree_block(struct inode *inode, const void *buf,
|
||||||
|
u64 index, int log_blocksize)
|
||||||
|
{
|
||||||
|
loff_t pos = ext4_verity_metadata_pos(inode) + (index << log_blocksize);
|
||||||
|
|
||||||
|
return pagecache_write(inode, buf, 1 << log_blocksize, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct fsverity_operations ext4_verityops = {
|
||||||
|
.begin_enable_verity = ext4_begin_enable_verity,
|
||||||
|
.end_enable_verity = ext4_end_enable_verity,
|
||||||
|
.get_verity_descriptor = ext4_get_verity_descriptor,
|
||||||
|
.read_merkle_tree_page = ext4_read_merkle_tree_page,
|
||||||
|
.write_merkle_tree_block = ext4_write_merkle_tree_block,
|
||||||
|
};
|
@ -8,3 +8,4 @@ f2fs-$(CONFIG_F2FS_STAT_FS) += debug.o
|
|||||||
f2fs-$(CONFIG_F2FS_FS_XATTR) += xattr.o
|
f2fs-$(CONFIG_F2FS_FS_XATTR) += xattr.o
|
||||||
f2fs-$(CONFIG_F2FS_FS_POSIX_ACL) += acl.o
|
f2fs-$(CONFIG_F2FS_FS_POSIX_ACL) += acl.o
|
||||||
f2fs-$(CONFIG_F2FS_IO_TRACE) += trace.o
|
f2fs-$(CONFIG_F2FS_IO_TRACE) += trace.o
|
||||||
|
f2fs-$(CONFIG_FS_VERITY) += verity.o
|
||||||
|
@ -74,6 +74,7 @@ static enum count_type __read_io_type(struct page *page)
|
|||||||
enum bio_post_read_step {
|
enum bio_post_read_step {
|
||||||
STEP_INITIAL = 0,
|
STEP_INITIAL = 0,
|
||||||
STEP_DECRYPT,
|
STEP_DECRYPT,
|
||||||
|
STEP_VERITY,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct bio_post_read_ctx {
|
struct bio_post_read_ctx {
|
||||||
@ -120,8 +121,23 @@ static void decrypt_work(struct work_struct *work)
|
|||||||
bio_post_read_processing(ctx);
|
bio_post_read_processing(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void verity_work(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct bio_post_read_ctx *ctx =
|
||||||
|
container_of(work, struct bio_post_read_ctx, work);
|
||||||
|
|
||||||
|
fsverity_verify_bio(ctx->bio);
|
||||||
|
|
||||||
|
bio_post_read_processing(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
static void bio_post_read_processing(struct bio_post_read_ctx *ctx)
|
static void bio_post_read_processing(struct bio_post_read_ctx *ctx)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
switch (++ctx->cur_step) {
|
switch (++ctx->cur_step) {
|
||||||
case STEP_DECRYPT:
|
case STEP_DECRYPT:
|
||||||
if (ctx->enabled_steps & (1 << STEP_DECRYPT)) {
|
if (ctx->enabled_steps & (1 << STEP_DECRYPT)) {
|
||||||
@ -131,6 +147,14 @@ static void bio_post_read_processing(struct bio_post_read_ctx *ctx)
|
|||||||
}
|
}
|
||||||
ctx->cur_step++;
|
ctx->cur_step++;
|
||||||
/* fall-through */
|
/* fall-through */
|
||||||
|
case STEP_VERITY:
|
||||||
|
if (ctx->enabled_steps & (1 << STEP_VERITY)) {
|
||||||
|
INIT_WORK(&ctx->work, verity_work);
|
||||||
|
fsverity_enqueue_verify_work(&ctx->work);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx->cur_step++;
|
||||||
|
/* fall-through */
|
||||||
default:
|
default:
|
||||||
__read_end_io(ctx->bio);
|
__read_end_io(ctx->bio);
|
||||||
}
|
}
|
||||||
@ -608,8 +632,15 @@ out:
|
|||||||
up_write(&io->io_rwsem);
|
up_write(&io->io_rwsem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool f2fs_need_verity(const struct inode *inode, pgoff_t idx)
|
||||||
|
{
|
||||||
|
return fsverity_active(inode) &&
|
||||||
|
idx < DIV_ROUND_UP(inode->i_size, PAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
|
static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
|
||||||
unsigned nr_pages, unsigned op_flag)
|
unsigned nr_pages, unsigned op_flag,
|
||||||
|
pgoff_t first_idx)
|
||||||
{
|
{
|
||||||
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
|
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
|
||||||
struct bio *bio;
|
struct bio *bio;
|
||||||
@ -625,6 +656,10 @@ static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
|
|||||||
|
|
||||||
if (f2fs_encrypted_file(inode))
|
if (f2fs_encrypted_file(inode))
|
||||||
post_read_steps |= 1 << STEP_DECRYPT;
|
post_read_steps |= 1 << STEP_DECRYPT;
|
||||||
|
|
||||||
|
if (f2fs_need_verity(inode, first_idx))
|
||||||
|
post_read_steps |= 1 << STEP_VERITY;
|
||||||
|
|
||||||
if (post_read_steps) {
|
if (post_read_steps) {
|
||||||
ctx = mempool_alloc(bio_post_read_ctx_pool, GFP_NOFS);
|
ctx = mempool_alloc(bio_post_read_ctx_pool, GFP_NOFS);
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
@ -646,7 +681,7 @@ static int f2fs_submit_page_read(struct inode *inode, struct page *page,
|
|||||||
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
|
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
|
||||||
struct bio *bio;
|
struct bio *bio;
|
||||||
|
|
||||||
bio = f2fs_grab_read_bio(inode, blkaddr, 1, 0);
|
bio = f2fs_grab_read_bio(inode, blkaddr, 1, 0, page->index);
|
||||||
if (IS_ERR(bio))
|
if (IS_ERR(bio))
|
||||||
return PTR_ERR(bio);
|
return PTR_ERR(bio);
|
||||||
|
|
||||||
@ -1569,6 +1604,15 @@ out:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline loff_t f2fs_readpage_limit(struct inode *inode)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_FS_VERITY) &&
|
||||||
|
(IS_VERITY(inode) || f2fs_verity_in_progress(inode)))
|
||||||
|
return inode->i_sb->s_maxbytes;
|
||||||
|
|
||||||
|
return i_size_read(inode);
|
||||||
|
}
|
||||||
|
|
||||||
static int f2fs_read_single_page(struct inode *inode, struct page *page,
|
static int f2fs_read_single_page(struct inode *inode, struct page *page,
|
||||||
unsigned nr_pages,
|
unsigned nr_pages,
|
||||||
struct f2fs_map_blocks *map,
|
struct f2fs_map_blocks *map,
|
||||||
@ -1587,7 +1631,7 @@ static int f2fs_read_single_page(struct inode *inode, struct page *page,
|
|||||||
|
|
||||||
block_in_file = (sector_t)page_index(page);
|
block_in_file = (sector_t)page_index(page);
|
||||||
last_block = block_in_file + nr_pages;
|
last_block = block_in_file + nr_pages;
|
||||||
last_block_in_file = (i_size_read(inode) + blocksize - 1) >>
|
last_block_in_file = (f2fs_readpage_limit(inode) + blocksize - 1) >>
|
||||||
blkbits;
|
blkbits;
|
||||||
if (last_block > last_block_in_file)
|
if (last_block > last_block_in_file)
|
||||||
last_block = last_block_in_file;
|
last_block = last_block_in_file;
|
||||||
@ -1632,6 +1676,11 @@ got_it:
|
|||||||
} else {
|
} else {
|
||||||
zero_out:
|
zero_out:
|
||||||
zero_user_segment(page, 0, PAGE_SIZE);
|
zero_user_segment(page, 0, PAGE_SIZE);
|
||||||
|
if (f2fs_need_verity(inode, page->index) &&
|
||||||
|
!fsverity_verify_page(page)) {
|
||||||
|
ret = -EIO;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
if (!PageUptodate(page))
|
if (!PageUptodate(page))
|
||||||
SetPageUptodate(page);
|
SetPageUptodate(page);
|
||||||
unlock_page(page);
|
unlock_page(page);
|
||||||
@ -1650,7 +1699,7 @@ submit_and_realloc:
|
|||||||
}
|
}
|
||||||
if (bio == NULL) {
|
if (bio == NULL) {
|
||||||
bio = f2fs_grab_read_bio(inode, block_nr, nr_pages,
|
bio = f2fs_grab_read_bio(inode, block_nr, nr_pages,
|
||||||
is_readahead ? REQ_RAHEAD : 0);
|
is_readahead ? REQ_RAHEAD : 0, page->index);
|
||||||
if (IS_ERR(bio)) {
|
if (IS_ERR(bio)) {
|
||||||
ret = PTR_ERR(bio);
|
ret = PTR_ERR(bio);
|
||||||
bio = NULL;
|
bio = NULL;
|
||||||
@ -2052,7 +2101,7 @@ static int __write_data_page(struct page *page, bool *submitted,
|
|||||||
if (unlikely(is_sbi_flag_set(sbi, SBI_POR_DOING)))
|
if (unlikely(is_sbi_flag_set(sbi, SBI_POR_DOING)))
|
||||||
goto redirty_out;
|
goto redirty_out;
|
||||||
|
|
||||||
if (page->index < end_index)
|
if (page->index < end_index || f2fs_verity_in_progress(inode))
|
||||||
goto write;
|
goto write;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2427,7 +2476,8 @@ static void f2fs_write_failed(struct address_space *mapping, loff_t to)
|
|||||||
struct inode *inode = mapping->host;
|
struct inode *inode = mapping->host;
|
||||||
loff_t i_size = i_size_read(inode);
|
loff_t i_size = i_size_read(inode);
|
||||||
|
|
||||||
if (to > i_size) {
|
/* In the fs-verity case, f2fs_end_enable_verity() does the truncate */
|
||||||
|
if (to > i_size && !f2fs_verity_in_progress(inode)) {
|
||||||
down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
|
down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
|
||||||
down_write(&F2FS_I(inode)->i_mmap_sem);
|
down_write(&F2FS_I(inode)->i_mmap_sem);
|
||||||
|
|
||||||
@ -2458,7 +2508,8 @@ static int prepare_write_begin(struct f2fs_sb_info *sbi,
|
|||||||
* the block addresses when there is no need to fill the page.
|
* the block addresses when there is no need to fill the page.
|
||||||
*/
|
*/
|
||||||
if (!f2fs_has_inline_data(inode) && len == PAGE_SIZE &&
|
if (!f2fs_has_inline_data(inode) && len == PAGE_SIZE &&
|
||||||
!is_inode_flag_set(inode, FI_NO_PREALLOC))
|
!is_inode_flag_set(inode, FI_NO_PREALLOC) &&
|
||||||
|
!f2fs_verity_in_progress(inode))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* f2fs_lock_op avoids race between write CP and convert_inline_page */
|
/* f2fs_lock_op avoids race between write CP and convert_inline_page */
|
||||||
@ -2597,7 +2648,8 @@ repeat:
|
|||||||
if (len == PAGE_SIZE || PageUptodate(page))
|
if (len == PAGE_SIZE || PageUptodate(page))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (!(pos & (PAGE_SIZE - 1)) && (pos + len) >= i_size_read(inode)) {
|
if (!(pos & (PAGE_SIZE - 1)) && (pos + len) >= i_size_read(inode) &&
|
||||||
|
!f2fs_verity_in_progress(inode)) {
|
||||||
zero_user_segment(page, len, PAGE_SIZE);
|
zero_user_segment(page, len, PAGE_SIZE);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -2660,7 +2712,8 @@ static int f2fs_write_end(struct file *file,
|
|||||||
|
|
||||||
set_page_dirty(page);
|
set_page_dirty(page);
|
||||||
|
|
||||||
if (pos + copied > i_size_read(inode))
|
if (pos + copied > i_size_read(inode) &&
|
||||||
|
!f2fs_verity_in_progress(inode))
|
||||||
f2fs_i_size_write(inode, pos + copied);
|
f2fs_i_size_write(inode, pos + copied);
|
||||||
unlock_out:
|
unlock_out:
|
||||||
f2fs_put_page(page, 1);
|
f2fs_put_page(page, 1);
|
||||||
@ -3104,7 +3157,9 @@ void f2fs_clear_page_cache_dirty_tag(struct page *page)
|
|||||||
|
|
||||||
int __init f2fs_init_post_read_processing(void)
|
int __init f2fs_init_post_read_processing(void)
|
||||||
{
|
{
|
||||||
bio_post_read_ctx_cache = KMEM_CACHE(bio_post_read_ctx, 0);
|
bio_post_read_ctx_cache =
|
||||||
|
kmem_cache_create("f2fs_bio_post_read_ctx",
|
||||||
|
sizeof(struct bio_post_read_ctx), 0, 0, NULL);
|
||||||
if (!bio_post_read_ctx_cache)
|
if (!bio_post_read_ctx_cache)
|
||||||
goto fail;
|
goto fail;
|
||||||
bio_post_read_ctx_pool =
|
bio_post_read_ctx_pool =
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include <crypto/hash.h>
|
#include <crypto/hash.h>
|
||||||
|
|
||||||
#include <linux/fscrypt.h>
|
#include <linux/fscrypt.h>
|
||||||
|
#include <linux/fsverity.h>
|
||||||
|
|
||||||
#ifdef CONFIG_F2FS_CHECK_FS
|
#ifdef CONFIG_F2FS_CHECK_FS
|
||||||
#define f2fs_bug_on(sbi, condition) BUG_ON(condition)
|
#define f2fs_bug_on(sbi, condition) BUG_ON(condition)
|
||||||
@ -151,7 +152,7 @@ struct f2fs_mount_info {
|
|||||||
#define F2FS_FEATURE_QUOTA_INO 0x0080
|
#define F2FS_FEATURE_QUOTA_INO 0x0080
|
||||||
#define F2FS_FEATURE_INODE_CRTIME 0x0100
|
#define F2FS_FEATURE_INODE_CRTIME 0x0100
|
||||||
#define F2FS_FEATURE_LOST_FOUND 0x0200
|
#define F2FS_FEATURE_LOST_FOUND 0x0200
|
||||||
#define F2FS_FEATURE_VERITY 0x0400 /* reserved */
|
#define F2FS_FEATURE_VERITY 0x0400
|
||||||
#define F2FS_FEATURE_SB_CHKSUM 0x0800
|
#define F2FS_FEATURE_SB_CHKSUM 0x0800
|
||||||
|
|
||||||
#define __F2FS_HAS_FEATURE(raw_super, mask) \
|
#define __F2FS_HAS_FEATURE(raw_super, mask) \
|
||||||
@ -630,7 +631,7 @@ enum {
|
|||||||
#define FADVISE_ENC_NAME_BIT 0x08
|
#define FADVISE_ENC_NAME_BIT 0x08
|
||||||
#define FADVISE_KEEP_SIZE_BIT 0x10
|
#define FADVISE_KEEP_SIZE_BIT 0x10
|
||||||
#define FADVISE_HOT_BIT 0x20
|
#define FADVISE_HOT_BIT 0x20
|
||||||
#define FADVISE_VERITY_BIT 0x40 /* reserved */
|
#define FADVISE_VERITY_BIT 0x40
|
||||||
|
|
||||||
#define FADVISE_MODIFIABLE_BITS (FADVISE_COLD_BIT | FADVISE_HOT_BIT)
|
#define FADVISE_MODIFIABLE_BITS (FADVISE_COLD_BIT | FADVISE_HOT_BIT)
|
||||||
|
|
||||||
@ -650,6 +651,8 @@ enum {
|
|||||||
#define file_is_hot(inode) is_file(inode, FADVISE_HOT_BIT)
|
#define file_is_hot(inode) is_file(inode, FADVISE_HOT_BIT)
|
||||||
#define file_set_hot(inode) set_file(inode, FADVISE_HOT_BIT)
|
#define file_set_hot(inode) set_file(inode, FADVISE_HOT_BIT)
|
||||||
#define file_clear_hot(inode) clear_file(inode, FADVISE_HOT_BIT)
|
#define file_clear_hot(inode) clear_file(inode, FADVISE_HOT_BIT)
|
||||||
|
#define file_is_verity(inode) is_file(inode, FADVISE_VERITY_BIT)
|
||||||
|
#define file_set_verity(inode) set_file(inode, FADVISE_VERITY_BIT)
|
||||||
|
|
||||||
#define DEF_DIR_LEVEL 0
|
#define DEF_DIR_LEVEL 0
|
||||||
|
|
||||||
@ -2412,6 +2415,7 @@ enum {
|
|||||||
FI_PROJ_INHERIT, /* indicate file inherits projectid */
|
FI_PROJ_INHERIT, /* indicate file inherits projectid */
|
||||||
FI_PIN_FILE, /* indicate file should not be gced */
|
FI_PIN_FILE, /* indicate file should not be gced */
|
||||||
FI_ATOMIC_REVOKE_REQUEST, /* request to drop atomic data */
|
FI_ATOMIC_REVOKE_REQUEST, /* request to drop atomic data */
|
||||||
|
FI_VERITY_IN_PROGRESS, /* building fs-verity Merkle tree */
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void __mark_inode_dirty_flag(struct inode *inode,
|
static inline void __mark_inode_dirty_flag(struct inode *inode,
|
||||||
@ -2451,6 +2455,12 @@ static inline void clear_inode_flag(struct inode *inode, int flag)
|
|||||||
__mark_inode_dirty_flag(inode, flag, false);
|
__mark_inode_dirty_flag(inode, flag, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool f2fs_verity_in_progress(struct inode *inode)
|
||||||
|
{
|
||||||
|
return IS_ENABLED(CONFIG_FS_VERITY) &&
|
||||||
|
is_inode_flag_set(inode, FI_VERITY_IN_PROGRESS);
|
||||||
|
}
|
||||||
|
|
||||||
static inline void set_acl_inode(struct inode *inode, umode_t mode)
|
static inline void set_acl_inode(struct inode *inode, umode_t mode)
|
||||||
{
|
{
|
||||||
F2FS_I(inode)->i_acl_mode = mode;
|
F2FS_I(inode)->i_acl_mode = mode;
|
||||||
@ -3521,6 +3531,9 @@ void f2fs_exit_sysfs(void);
|
|||||||
int f2fs_register_sysfs(struct f2fs_sb_info *sbi);
|
int f2fs_register_sysfs(struct f2fs_sb_info *sbi);
|
||||||
void f2fs_unregister_sysfs(struct f2fs_sb_info *sbi);
|
void f2fs_unregister_sysfs(struct f2fs_sb_info *sbi);
|
||||||
|
|
||||||
|
/* verity.c */
|
||||||
|
extern const struct fsverity_operations f2fs_verityops;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* crypto support
|
* crypto support
|
||||||
*/
|
*/
|
||||||
@ -3543,7 +3556,7 @@ static inline void f2fs_set_encrypted_inode(struct inode *inode)
|
|||||||
*/
|
*/
|
||||||
static inline bool f2fs_post_read_required(struct inode *inode)
|
static inline bool f2fs_post_read_required(struct inode *inode)
|
||||||
{
|
{
|
||||||
return f2fs_encrypted_file(inode);
|
return f2fs_encrypted_file(inode) || fsverity_active(inode);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define F2FS_FEATURE_FUNCS(name, flagname) \
|
#define F2FS_FEATURE_FUNCS(name, flagname) \
|
||||||
@ -3561,6 +3574,7 @@ F2FS_FEATURE_FUNCS(flexible_inline_xattr, FLEXIBLE_INLINE_XATTR);
|
|||||||
F2FS_FEATURE_FUNCS(quota_ino, QUOTA_INO);
|
F2FS_FEATURE_FUNCS(quota_ino, QUOTA_INO);
|
||||||
F2FS_FEATURE_FUNCS(inode_crtime, INODE_CRTIME);
|
F2FS_FEATURE_FUNCS(inode_crtime, INODE_CRTIME);
|
||||||
F2FS_FEATURE_FUNCS(lost_found, LOST_FOUND);
|
F2FS_FEATURE_FUNCS(lost_found, LOST_FOUND);
|
||||||
|
F2FS_FEATURE_FUNCS(verity, VERITY);
|
||||||
F2FS_FEATURE_FUNCS(sb_chksum, SB_CHKSUM);
|
F2FS_FEATURE_FUNCS(sb_chksum, SB_CHKSUM);
|
||||||
|
|
||||||
#ifdef CONFIG_BLK_DEV_ZONED
|
#ifdef CONFIG_BLK_DEV_ZONED
|
||||||
|
@ -493,6 +493,10 @@ static int f2fs_file_open(struct inode *inode, struct file *filp)
|
|||||||
{
|
{
|
||||||
int err = fscrypt_file_open(inode, filp);
|
int err = fscrypt_file_open(inode, filp);
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
err = fsverity_file_open(inode, filp);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
@ -778,6 +782,10 @@ int f2fs_setattr(struct dentry *dentry, struct iattr *attr)
|
|||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
|
err = fsverity_prepare_setattr(dentry, attr);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
if (is_quota_modification(inode, attr)) {
|
if (is_quota_modification(inode, attr)) {
|
||||||
err = dquot_initialize(inode);
|
err = dquot_initialize(inode);
|
||||||
if (err)
|
if (err)
|
||||||
@ -1705,7 +1713,8 @@ static const struct {
|
|||||||
FS_PROJINHERIT_FL | \
|
FS_PROJINHERIT_FL | \
|
||||||
FS_ENCRYPT_FL | \
|
FS_ENCRYPT_FL | \
|
||||||
FS_INLINE_DATA_FL | \
|
FS_INLINE_DATA_FL | \
|
||||||
FS_NOCOW_FL)
|
FS_NOCOW_FL | \
|
||||||
|
FS_VERITY_FL)
|
||||||
|
|
||||||
#define F2FS_SETTABLE_FS_FL ( \
|
#define F2FS_SETTABLE_FS_FL ( \
|
||||||
FS_SYNC_FL | \
|
FS_SYNC_FL | \
|
||||||
@ -1750,6 +1759,8 @@ static int f2fs_ioc_getflags(struct file *filp, unsigned long arg)
|
|||||||
|
|
||||||
if (IS_ENCRYPTED(inode))
|
if (IS_ENCRYPTED(inode))
|
||||||
fsflags |= FS_ENCRYPT_FL;
|
fsflags |= FS_ENCRYPT_FL;
|
||||||
|
if (IS_VERITY(inode))
|
||||||
|
fsflags |= FS_VERITY_FL;
|
||||||
if (f2fs_has_inline_data(inode) || f2fs_has_inline_dentry(inode))
|
if (f2fs_has_inline_data(inode) || f2fs_has_inline_dentry(inode))
|
||||||
fsflags |= FS_INLINE_DATA_FL;
|
fsflags |= FS_INLINE_DATA_FL;
|
||||||
if (is_inode_flag_set(inode, FI_PIN_FILE))
|
if (is_inode_flag_set(inode, FI_PIN_FILE))
|
||||||
@ -3103,6 +3114,30 @@ static int f2fs_ioc_resize_fs(struct file *filp, unsigned long arg)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int f2fs_ioc_enable_verity(struct file *filp, unsigned long arg)
|
||||||
|
{
|
||||||
|
struct inode *inode = file_inode(filp);
|
||||||
|
|
||||||
|
f2fs_update_time(F2FS_I_SB(inode), REQ_TIME);
|
||||||
|
|
||||||
|
if (!f2fs_sb_has_verity(F2FS_I_SB(inode))) {
|
||||||
|
f2fs_warn(F2FS_I_SB(inode),
|
||||||
|
"Can't enable fs-verity on inode %lu: the verity feature is not enabled on this filesystem.\n",
|
||||||
|
inode->i_ino);
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fsverity_ioctl_enable(filp, (const void __user *)arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f2fs_ioc_measure_verity(struct file *filp, unsigned long arg)
|
||||||
|
{
|
||||||
|
if (!f2fs_sb_has_verity(F2FS_I_SB(file_inode(filp))))
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
return fsverity_ioctl_measure(filp, (void __user *)arg);
|
||||||
|
}
|
||||||
|
|
||||||
long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||||
{
|
{
|
||||||
if (unlikely(f2fs_cp_error(F2FS_I_SB(file_inode(filp)))))
|
if (unlikely(f2fs_cp_error(F2FS_I_SB(file_inode(filp)))))
|
||||||
@ -3171,6 +3206,10 @@ long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|||||||
return f2fs_ioc_precache_extents(filp, arg);
|
return f2fs_ioc_precache_extents(filp, arg);
|
||||||
case F2FS_IOC_RESIZE_FS:
|
case F2FS_IOC_RESIZE_FS:
|
||||||
return f2fs_ioc_resize_fs(filp, arg);
|
return f2fs_ioc_resize_fs(filp, arg);
|
||||||
|
case FS_IOC_ENABLE_VERITY:
|
||||||
|
return f2fs_ioc_enable_verity(filp, arg);
|
||||||
|
case FS_IOC_MEASURE_VERITY:
|
||||||
|
return f2fs_ioc_measure_verity(filp, arg);
|
||||||
default:
|
default:
|
||||||
return -ENOTTY;
|
return -ENOTTY;
|
||||||
}
|
}
|
||||||
@ -3290,6 +3329,8 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||||||
case F2FS_IOC_SET_PIN_FILE:
|
case F2FS_IOC_SET_PIN_FILE:
|
||||||
case F2FS_IOC_PRECACHE_EXTENTS:
|
case F2FS_IOC_PRECACHE_EXTENTS:
|
||||||
case F2FS_IOC_RESIZE_FS:
|
case F2FS_IOC_RESIZE_FS:
|
||||||
|
case FS_IOC_ENABLE_VERITY:
|
||||||
|
case FS_IOC_MEASURE_VERITY:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return -ENOIOCTLCMD;
|
return -ENOIOCTLCMD;
|
||||||
|
@ -46,9 +46,11 @@ void f2fs_set_inode_flags(struct inode *inode)
|
|||||||
new_fl |= S_DIRSYNC;
|
new_fl |= S_DIRSYNC;
|
||||||
if (file_is_encrypt(inode))
|
if (file_is_encrypt(inode))
|
||||||
new_fl |= S_ENCRYPTED;
|
new_fl |= S_ENCRYPTED;
|
||||||
|
if (file_is_verity(inode))
|
||||||
|
new_fl |= S_VERITY;
|
||||||
inode_set_flags(inode, new_fl,
|
inode_set_flags(inode, new_fl,
|
||||||
S_SYNC|S_APPEND|S_IMMUTABLE|S_NOATIME|S_DIRSYNC|
|
S_SYNC|S_APPEND|S_IMMUTABLE|S_NOATIME|S_DIRSYNC|
|
||||||
S_ENCRYPTED);
|
S_ENCRYPTED|S_VERITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __get_inode_rdev(struct inode *inode, struct f2fs_inode *ri)
|
static void __get_inode_rdev(struct inode *inode, struct f2fs_inode *ri)
|
||||||
@ -733,6 +735,7 @@ no_delete:
|
|||||||
}
|
}
|
||||||
out_clear:
|
out_clear:
|
||||||
fscrypt_put_encryption_info(inode);
|
fscrypt_put_encryption_info(inode);
|
||||||
|
fsverity_cleanup_inode(inode);
|
||||||
clear_inode(inode);
|
clear_inode(inode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3145,6 +3145,9 @@ try_onemore:
|
|||||||
sb->s_op = &f2fs_sops;
|
sb->s_op = &f2fs_sops;
|
||||||
#ifdef CONFIG_FS_ENCRYPTION
|
#ifdef CONFIG_FS_ENCRYPTION
|
||||||
sb->s_cop = &f2fs_cryptops;
|
sb->s_cop = &f2fs_cryptops;
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_FS_VERITY
|
||||||
|
sb->s_vop = &f2fs_verityops;
|
||||||
#endif
|
#endif
|
||||||
sb->s_xattr = f2fs_xattr_handlers;
|
sb->s_xattr = f2fs_xattr_handlers;
|
||||||
sb->s_export_op = &f2fs_export_ops;
|
sb->s_export_op = &f2fs_export_ops;
|
||||||
|
@ -131,6 +131,9 @@ static ssize_t features_show(struct f2fs_attr *a,
|
|||||||
if (f2fs_sb_has_lost_found(sbi))
|
if (f2fs_sb_has_lost_found(sbi))
|
||||||
len += snprintf(buf + len, PAGE_SIZE - len, "%s%s",
|
len += snprintf(buf + len, PAGE_SIZE - len, "%s%s",
|
||||||
len ? ", " : "", "lost_found");
|
len ? ", " : "", "lost_found");
|
||||||
|
if (f2fs_sb_has_verity(sbi))
|
||||||
|
len += snprintf(buf + len, PAGE_SIZE - len, "%s%s",
|
||||||
|
len ? ", " : "", "verity");
|
||||||
if (f2fs_sb_has_sb_chksum(sbi))
|
if (f2fs_sb_has_sb_chksum(sbi))
|
||||||
len += snprintf(buf + len, PAGE_SIZE - len, "%s%s",
|
len += snprintf(buf + len, PAGE_SIZE - len, "%s%s",
|
||||||
len ? ", " : "", "sb_checksum");
|
len ? ", " : "", "sb_checksum");
|
||||||
@ -364,6 +367,7 @@ enum feat_id {
|
|||||||
FEAT_QUOTA_INO,
|
FEAT_QUOTA_INO,
|
||||||
FEAT_INODE_CRTIME,
|
FEAT_INODE_CRTIME,
|
||||||
FEAT_LOST_FOUND,
|
FEAT_LOST_FOUND,
|
||||||
|
FEAT_VERITY,
|
||||||
FEAT_SB_CHECKSUM,
|
FEAT_SB_CHECKSUM,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -381,6 +385,7 @@ static ssize_t f2fs_feature_show(struct f2fs_attr *a,
|
|||||||
case FEAT_QUOTA_INO:
|
case FEAT_QUOTA_INO:
|
||||||
case FEAT_INODE_CRTIME:
|
case FEAT_INODE_CRTIME:
|
||||||
case FEAT_LOST_FOUND:
|
case FEAT_LOST_FOUND:
|
||||||
|
case FEAT_VERITY:
|
||||||
case FEAT_SB_CHECKSUM:
|
case FEAT_SB_CHECKSUM:
|
||||||
return snprintf(buf, PAGE_SIZE, "supported\n");
|
return snprintf(buf, PAGE_SIZE, "supported\n");
|
||||||
}
|
}
|
||||||
@ -470,6 +475,9 @@ F2FS_FEATURE_RO_ATTR(flexible_inline_xattr, FEAT_FLEXIBLE_INLINE_XATTR);
|
|||||||
F2FS_FEATURE_RO_ATTR(quota_ino, FEAT_QUOTA_INO);
|
F2FS_FEATURE_RO_ATTR(quota_ino, FEAT_QUOTA_INO);
|
||||||
F2FS_FEATURE_RO_ATTR(inode_crtime, FEAT_INODE_CRTIME);
|
F2FS_FEATURE_RO_ATTR(inode_crtime, FEAT_INODE_CRTIME);
|
||||||
F2FS_FEATURE_RO_ATTR(lost_found, FEAT_LOST_FOUND);
|
F2FS_FEATURE_RO_ATTR(lost_found, FEAT_LOST_FOUND);
|
||||||
|
#ifdef CONFIG_FS_VERITY
|
||||||
|
F2FS_FEATURE_RO_ATTR(verity, FEAT_VERITY);
|
||||||
|
#endif
|
||||||
F2FS_FEATURE_RO_ATTR(sb_checksum, FEAT_SB_CHECKSUM);
|
F2FS_FEATURE_RO_ATTR(sb_checksum, FEAT_SB_CHECKSUM);
|
||||||
|
|
||||||
#define ATTR_LIST(name) (&f2fs_attr_##name.attr)
|
#define ATTR_LIST(name) (&f2fs_attr_##name.attr)
|
||||||
@ -534,6 +542,9 @@ static struct attribute *f2fs_feat_attrs[] = {
|
|||||||
ATTR_LIST(quota_ino),
|
ATTR_LIST(quota_ino),
|
||||||
ATTR_LIST(inode_crtime),
|
ATTR_LIST(inode_crtime),
|
||||||
ATTR_LIST(lost_found),
|
ATTR_LIST(lost_found),
|
||||||
|
#ifdef CONFIG_FS_VERITY
|
||||||
|
ATTR_LIST(verity),
|
||||||
|
#endif
|
||||||
ATTR_LIST(sb_checksum),
|
ATTR_LIST(sb_checksum),
|
||||||
NULL,
|
NULL,
|
||||||
};
|
};
|
||||||
|
247
fs/f2fs/verity.c
Normal file
247
fs/f2fs/verity.c
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* fs/f2fs/verity.c: fs-verity support for f2fs
|
||||||
|
*
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Implementation of fsverity_operations for f2fs.
|
||||||
|
*
|
||||||
|
* Like ext4, f2fs stores the verity metadata (Merkle tree and
|
||||||
|
* fsverity_descriptor) past the end of the file, starting at the first 64K
|
||||||
|
* boundary beyond i_size. This approach works because (a) verity files are
|
||||||
|
* readonly, and (b) pages fully beyond i_size aren't visible to userspace but
|
||||||
|
* can be read/written internally by f2fs with only some relatively small
|
||||||
|
* changes to f2fs. Extended attributes cannot be used because (a) f2fs limits
|
||||||
|
* the total size of an inode's xattr entries to 4096 bytes, which wouldn't be
|
||||||
|
* enough for even a single Merkle tree block, and (b) f2fs encryption doesn't
|
||||||
|
* encrypt xattrs, yet the verity metadata *must* be encrypted when the file is
|
||||||
|
* because it contains hashes of the plaintext data.
|
||||||
|
*
|
||||||
|
* Using a 64K boundary rather than a 4K one keeps things ready for
|
||||||
|
* architectures with 64K pages, and it doesn't necessarily waste space on-disk
|
||||||
|
* since there can be a hole between i_size and the start of the Merkle tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/f2fs_fs.h>
|
||||||
|
|
||||||
|
#include "f2fs.h"
|
||||||
|
#include "xattr.h"
|
||||||
|
|
||||||
|
static inline loff_t f2fs_verity_metadata_pos(const struct inode *inode)
|
||||||
|
{
|
||||||
|
return round_up(inode->i_size, 65536);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read some verity metadata from the inode. __vfs_read() can't be used because
|
||||||
|
* we need to read beyond i_size.
|
||||||
|
*/
|
||||||
|
static int pagecache_read(struct inode *inode, void *buf, size_t count,
|
||||||
|
loff_t pos)
|
||||||
|
{
|
||||||
|
while (count) {
|
||||||
|
size_t n = min_t(size_t, count,
|
||||||
|
PAGE_SIZE - offset_in_page(pos));
|
||||||
|
struct page *page;
|
||||||
|
void *addr;
|
||||||
|
|
||||||
|
page = read_mapping_page(inode->i_mapping, pos >> PAGE_SHIFT,
|
||||||
|
NULL);
|
||||||
|
if (IS_ERR(page))
|
||||||
|
return PTR_ERR(page);
|
||||||
|
|
||||||
|
addr = kmap_atomic(page);
|
||||||
|
memcpy(buf, addr + offset_in_page(pos), n);
|
||||||
|
kunmap_atomic(addr);
|
||||||
|
|
||||||
|
put_page(page);
|
||||||
|
|
||||||
|
buf += n;
|
||||||
|
pos += n;
|
||||||
|
count -= n;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write some verity metadata to the inode for FS_IOC_ENABLE_VERITY.
|
||||||
|
* kernel_write() can't be used because the file descriptor is readonly.
|
||||||
|
*/
|
||||||
|
static int pagecache_write(struct inode *inode, const void *buf, size_t count,
|
||||||
|
loff_t pos)
|
||||||
|
{
|
||||||
|
if (pos + count > inode->i_sb->s_maxbytes)
|
||||||
|
return -EFBIG;
|
||||||
|
|
||||||
|
while (count) {
|
||||||
|
size_t n = min_t(size_t, count,
|
||||||
|
PAGE_SIZE - offset_in_page(pos));
|
||||||
|
struct page *page;
|
||||||
|
void *fsdata;
|
||||||
|
void *addr;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
res = pagecache_write_begin(NULL, inode->i_mapping, pos, n, 0,
|
||||||
|
&page, &fsdata);
|
||||||
|
if (res)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
addr = kmap_atomic(page);
|
||||||
|
memcpy(addr + offset_in_page(pos), buf, n);
|
||||||
|
kunmap_atomic(addr);
|
||||||
|
|
||||||
|
res = pagecache_write_end(NULL, inode->i_mapping, pos, n, n,
|
||||||
|
page, fsdata);
|
||||||
|
if (res < 0)
|
||||||
|
return res;
|
||||||
|
if (res != n)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
buf += n;
|
||||||
|
pos += n;
|
||||||
|
count -= n;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Format of f2fs verity xattr. This points to the location of the verity
|
||||||
|
* descriptor within the file data rather than containing it directly because
|
||||||
|
* the verity descriptor *must* be encrypted when f2fs encryption is used. But,
|
||||||
|
* f2fs encryption does not encrypt xattrs.
|
||||||
|
*/
|
||||||
|
struct fsverity_descriptor_location {
|
||||||
|
__le32 version;
|
||||||
|
__le32 size;
|
||||||
|
__le64 pos;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int f2fs_begin_enable_verity(struct file *filp)
|
||||||
|
{
|
||||||
|
struct inode *inode = file_inode(filp);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (f2fs_verity_in_progress(inode))
|
||||||
|
return -EBUSY;
|
||||||
|
|
||||||
|
if (f2fs_is_atomic_file(inode) || f2fs_is_volatile_file(inode))
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since the file was opened readonly, we have to initialize the quotas
|
||||||
|
* here and not rely on ->open() doing it. This must be done before
|
||||||
|
* evicting the inline data.
|
||||||
|
*/
|
||||||
|
err = dquot_initialize(inode);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
err = f2fs_convert_inline_inode(inode);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
set_inode_flag(inode, FI_VERITY_IN_PROGRESS);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f2fs_end_enable_verity(struct file *filp, const void *desc,
|
||||||
|
size_t desc_size, u64 merkle_tree_size)
|
||||||
|
{
|
||||||
|
struct inode *inode = file_inode(filp);
|
||||||
|
u64 desc_pos = f2fs_verity_metadata_pos(inode) + merkle_tree_size;
|
||||||
|
struct fsverity_descriptor_location dloc = {
|
||||||
|
.version = cpu_to_le32(1),
|
||||||
|
.size = cpu_to_le32(desc_size),
|
||||||
|
.pos = cpu_to_le64(desc_pos),
|
||||||
|
};
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
if (desc != NULL) {
|
||||||
|
/* Succeeded; write the verity descriptor. */
|
||||||
|
err = pagecache_write(inode, desc, desc_size, desc_pos);
|
||||||
|
|
||||||
|
/* Write all pages before clearing FI_VERITY_IN_PROGRESS. */
|
||||||
|
if (!err)
|
||||||
|
err = filemap_write_and_wait(inode->i_mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we failed, truncate anything we wrote past i_size. */
|
||||||
|
if (desc == NULL || err)
|
||||||
|
f2fs_truncate(inode);
|
||||||
|
|
||||||
|
clear_inode_flag(inode, FI_VERITY_IN_PROGRESS);
|
||||||
|
|
||||||
|
if (desc != NULL && !err) {
|
||||||
|
err = f2fs_setxattr(inode, F2FS_XATTR_INDEX_VERITY,
|
||||||
|
F2FS_XATTR_NAME_VERITY, &dloc, sizeof(dloc),
|
||||||
|
NULL, XATTR_CREATE);
|
||||||
|
if (!err) {
|
||||||
|
file_set_verity(inode);
|
||||||
|
f2fs_set_inode_flags(inode);
|
||||||
|
f2fs_mark_inode_dirty_sync(inode, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f2fs_get_verity_descriptor(struct inode *inode, void *buf,
|
||||||
|
size_t buf_size)
|
||||||
|
{
|
||||||
|
struct fsverity_descriptor_location dloc;
|
||||||
|
int res;
|
||||||
|
u32 size;
|
||||||
|
u64 pos;
|
||||||
|
|
||||||
|
/* Get the descriptor location */
|
||||||
|
res = f2fs_getxattr(inode, F2FS_XATTR_INDEX_VERITY,
|
||||||
|
F2FS_XATTR_NAME_VERITY, &dloc, sizeof(dloc), NULL);
|
||||||
|
if (res < 0 && res != -ERANGE)
|
||||||
|
return res;
|
||||||
|
if (res != sizeof(dloc) || dloc.version != cpu_to_le32(1)) {
|
||||||
|
f2fs_warn(F2FS_I_SB(inode), "unknown verity xattr format");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
size = le32_to_cpu(dloc.size);
|
||||||
|
pos = le64_to_cpu(dloc.pos);
|
||||||
|
|
||||||
|
/* Get the descriptor */
|
||||||
|
if (pos + size < pos || pos + size > inode->i_sb->s_maxbytes ||
|
||||||
|
pos < f2fs_verity_metadata_pos(inode) || size > INT_MAX) {
|
||||||
|
f2fs_warn(F2FS_I_SB(inode), "invalid verity xattr");
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
}
|
||||||
|
if (buf_size) {
|
||||||
|
if (size > buf_size)
|
||||||
|
return -ERANGE;
|
||||||
|
res = pagecache_read(inode, buf, size, pos);
|
||||||
|
if (res)
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct page *f2fs_read_merkle_tree_page(struct inode *inode,
|
||||||
|
pgoff_t index)
|
||||||
|
{
|
||||||
|
index += f2fs_verity_metadata_pos(inode) >> PAGE_SHIFT;
|
||||||
|
|
||||||
|
return read_mapping_page(inode->i_mapping, index, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f2fs_write_merkle_tree_block(struct inode *inode, const void *buf,
|
||||||
|
u64 index, int log_blocksize)
|
||||||
|
{
|
||||||
|
loff_t pos = f2fs_verity_metadata_pos(inode) + (index << log_blocksize);
|
||||||
|
|
||||||
|
return pagecache_write(inode, buf, 1 << log_blocksize, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct fsverity_operations f2fs_verityops = {
|
||||||
|
.begin_enable_verity = f2fs_begin_enable_verity,
|
||||||
|
.end_enable_verity = f2fs_end_enable_verity,
|
||||||
|
.get_verity_descriptor = f2fs_get_verity_descriptor,
|
||||||
|
.read_merkle_tree_page = f2fs_read_merkle_tree_page,
|
||||||
|
.write_merkle_tree_block = f2fs_write_merkle_tree_block,
|
||||||
|
};
|
@ -34,8 +34,10 @@
|
|||||||
#define F2FS_XATTR_INDEX_ADVISE 7
|
#define F2FS_XATTR_INDEX_ADVISE 7
|
||||||
/* Should be same as EXT4_XATTR_INDEX_ENCRYPTION */
|
/* Should be same as EXT4_XATTR_INDEX_ENCRYPTION */
|
||||||
#define F2FS_XATTR_INDEX_ENCRYPTION 9
|
#define F2FS_XATTR_INDEX_ENCRYPTION 9
|
||||||
|
#define F2FS_XATTR_INDEX_VERITY 11
|
||||||
|
|
||||||
#define F2FS_XATTR_NAME_ENCRYPTION_CONTEXT "c"
|
#define F2FS_XATTR_NAME_ENCRYPTION_CONTEXT "c"
|
||||||
|
#define F2FS_XATTR_NAME_VERITY "v"
|
||||||
|
|
||||||
struct f2fs_xattr_header {
|
struct f2fs_xattr_header {
|
||||||
__le32 h_magic; /* magic number for identification */
|
__le32 h_magic; /* magic number for identification */
|
||||||
|
55
fs/verity/Kconfig
Normal file
55
fs/verity/Kconfig
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
config FS_VERITY
|
||||||
|
bool "FS Verity (read-only file-based authenticity protection)"
|
||||||
|
select CRYPTO
|
||||||
|
# SHA-256 is selected as it's intended to be the default hash algorithm.
|
||||||
|
# To avoid bloat, other wanted algorithms must be selected explicitly.
|
||||||
|
select CRYPTO_SHA256
|
||||||
|
help
|
||||||
|
This option enables fs-verity. fs-verity is the dm-verity
|
||||||
|
mechanism implemented at the file level. On supported
|
||||||
|
filesystems (currently EXT4 and F2FS), userspace can use an
|
||||||
|
ioctl to enable verity for a file, which causes the filesystem
|
||||||
|
to build a Merkle tree for the file. The filesystem will then
|
||||||
|
transparently verify any data read from the file against the
|
||||||
|
Merkle tree. The file is also made read-only.
|
||||||
|
|
||||||
|
This serves as an integrity check, but the availability of the
|
||||||
|
Merkle tree root hash also allows efficiently supporting
|
||||||
|
various use cases where normally the whole file would need to
|
||||||
|
be hashed at once, such as: (a) auditing (logging the file's
|
||||||
|
hash), or (b) authenticity verification (comparing the hash
|
||||||
|
against a known good value, e.g. from a digital signature).
|
||||||
|
|
||||||
|
fs-verity is especially useful on large files where not all
|
||||||
|
the contents may actually be needed. Also, fs-verity verifies
|
||||||
|
data each time it is paged back in, which provides better
|
||||||
|
protection against malicious disks vs. an ahead-of-time hash.
|
||||||
|
|
||||||
|
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
|
||||||
|
bool "FS Verity builtin signature support"
|
||||||
|
depends on FS_VERITY
|
||||||
|
select SYSTEM_DATA_VERIFICATION
|
||||||
|
help
|
||||||
|
Support verifying signatures of verity files against the X.509
|
||||||
|
certificates that have been loaded into the ".fs-verity"
|
||||||
|
kernel keyring.
|
||||||
|
|
||||||
|
This is meant as a relatively simple mechanism that can be
|
||||||
|
used to provide an authenticity guarantee for verity files, as
|
||||||
|
an alternative to IMA appraisal. Userspace programs still
|
||||||
|
need to check that the verity bit is set in order to get an
|
||||||
|
authenticity guarantee.
|
||||||
|
|
||||||
|
If unsure, say N.
|
10
fs/verity/Makefile
Normal file
10
fs/verity/Makefile
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
obj-$(CONFIG_FS_VERITY) += enable.o \
|
||||||
|
hash_algs.o \
|
||||||
|
init.o \
|
||||||
|
measure.o \
|
||||||
|
open.o \
|
||||||
|
verify.o
|
||||||
|
|
||||||
|
obj-$(CONFIG_FS_VERITY_BUILTIN_SIGNATURES) += signature.o
|
377
fs/verity/enable.c
Normal file
377
fs/verity/enable.c
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* fs/verity/enable.c: ioctl to enable verity on a file
|
||||||
|
*
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fsverity_private.h"
|
||||||
|
|
||||||
|
#include <crypto/hash.h>
|
||||||
|
#include <linux/mount.h>
|
||||||
|
#include <linux/pagemap.h>
|
||||||
|
#include <linux/sched/signal.h>
|
||||||
|
#include <linux/uaccess.h>
|
||||||
|
|
||||||
|
static int build_merkle_tree_level(struct inode *inode, unsigned int level,
|
||||||
|
u64 num_blocks_to_hash,
|
||||||
|
const struct merkle_tree_params *params,
|
||||||
|
u8 *pending_hashes,
|
||||||
|
struct ahash_request *req)
|
||||||
|
{
|
||||||
|
const struct fsverity_operations *vops = inode->i_sb->s_vop;
|
||||||
|
unsigned int pending_size = 0;
|
||||||
|
u64 dst_block_num;
|
||||||
|
u64 i;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (WARN_ON(params->block_size != PAGE_SIZE)) /* checked earlier too */
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (level < params->num_levels) {
|
||||||
|
dst_block_num = params->level_start[level];
|
||||||
|
} else {
|
||||||
|
if (WARN_ON(num_blocks_to_hash != 1))
|
||||||
|
return -EINVAL;
|
||||||
|
dst_block_num = 0; /* unused */
|
||||||
|
}
|
||||||
|
|
||||||
|
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_mapping_page(inode->i_mapping, i, NULL);
|
||||||
|
if (IS_ERR(src_page)) {
|
||||||
|
err = PTR_ERR(src_page);
|
||||||
|
fsverity_err(inode,
|
||||||
|
"Error %d reading data page %llu",
|
||||||
|
err, i);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Non-leaf: hashing hash block from level below */
|
||||||
|
src_page = vops->read_merkle_tree_page(inode,
|
||||||
|
params->level_start[level - 1] + i);
|
||||||
|
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)
|
||||||
|
return err;
|
||||||
|
pending_size += params->digest_size;
|
||||||
|
|
||||||
|
if (level == params->num_levels) /* Root hash? */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (pending_size + params->digest_size > params->block_size ||
|
||||||
|
i + 1 == num_blocks_to_hash) {
|
||||||
|
/* Flush the pending hash block */
|
||||||
|
memset(&pending_hashes[pending_size], 0,
|
||||||
|
params->block_size - pending_size);
|
||||||
|
err = vops->write_merkle_tree_block(inode,
|
||||||
|
pending_hashes,
|
||||||
|
dst_block_num,
|
||||||
|
params->log_blocksize);
|
||||||
|
if (err) {
|
||||||
|
fsverity_err(inode,
|
||||||
|
"Error %d writing Merkle tree block %llu",
|
||||||
|
err, dst_block_num);
|
||||||
|
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 inode using the given parameters, and
|
||||||
|
* return the root hash in @root_hash.
|
||||||
|
*
|
||||||
|
* The tree is written to a filesystem-specific location as determined by the
|
||||||
|
* ->write_merkle_tree_block() method. However, the blocks that comprise the
|
||||||
|
* tree are the same for all filesystems.
|
||||||
|
*/
|
||||||
|
static int build_merkle_tree(struct inode *inode,
|
||||||
|
const struct merkle_tree_params *params,
|
||||||
|
u8 *root_hash)
|
||||||
|
{
|
||||||
|
u8 *pending_hashes;
|
||||||
|
struct ahash_request *req;
|
||||||
|
u64 blocks;
|
||||||
|
unsigned int level;
|
||||||
|
int err = -ENOMEM;
|
||||||
|
|
||||||
|
if (inode->i_size == 0) {
|
||||||
|
/* Empty file is a special case; root hash is all 0's */
|
||||||
|
memset(root_hash, 0, params->digest_size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pending_hashes = kmalloc(params->block_size, GFP_KERNEL);
|
||||||
|
req = ahash_request_alloc(params->hash_alg->tfm, GFP_KERNEL);
|
||||||
|
if (!pending_hashes || !req)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build each level of the Merkle tree, starting at the leaf level
|
||||||
|
* (level 0) and ascending to the root node (level 'num_levels - 1').
|
||||||
|
* Then at the end (level 'num_levels'), calculate the root hash.
|
||||||
|
*/
|
||||||
|
blocks = (inode->i_size + params->block_size - 1) >>
|
||||||
|
params->log_blocksize;
|
||||||
|
for (level = 0; level <= params->num_levels; level++) {
|
||||||
|
err = build_merkle_tree_level(inode, level, blocks, params,
|
||||||
|
pending_hashes, req);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
blocks = (blocks + params->hashes_per_block - 1) >>
|
||||||
|
params->log_arity;
|
||||||
|
}
|
||||||
|
memcpy(root_hash, pending_hashes, params->digest_size);
|
||||||
|
err = 0;
|
||||||
|
out:
|
||||||
|
kfree(pending_hashes);
|
||||||
|
ahash_request_free(req);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int enable_verity(struct file *filp,
|
||||||
|
const struct fsverity_enable_arg *arg)
|
||||||
|
{
|
||||||
|
struct inode *inode = file_inode(filp);
|
||||||
|
const struct fsverity_operations *vops = inode->i_sb->s_vop;
|
||||||
|
struct merkle_tree_params params = { };
|
||||||
|
struct fsverity_descriptor *desc;
|
||||||
|
size_t desc_size = sizeof(*desc) + arg->sig_size;
|
||||||
|
struct fsverity_info *vi;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* Start initializing the fsverity_descriptor */
|
||||||
|
desc = kzalloc(desc_size, GFP_KERNEL);
|
||||||
|
if (!desc)
|
||||||
|
return -ENOMEM;
|
||||||
|
desc->version = 1;
|
||||||
|
desc->hash_algorithm = arg->hash_algorithm;
|
||||||
|
desc->log_blocksize = ilog2(arg->block_size);
|
||||||
|
|
||||||
|
/* Get the salt if the user provided one */
|
||||||
|
if (arg->salt_size &&
|
||||||
|
copy_from_user(desc->salt,
|
||||||
|
(const u8 __user *)(uintptr_t)arg->salt_ptr,
|
||||||
|
arg->salt_size)) {
|
||||||
|
err = -EFAULT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
desc->salt_size = arg->salt_size;
|
||||||
|
|
||||||
|
/* Get the signature if the user provided one */
|
||||||
|
if (arg->sig_size &&
|
||||||
|
copy_from_user(desc->signature,
|
||||||
|
(const u8 __user *)(uintptr_t)arg->sig_ptr,
|
||||||
|
arg->sig_size)) {
|
||||||
|
err = -EFAULT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
desc->sig_size = cpu_to_le32(arg->sig_size);
|
||||||
|
|
||||||
|
desc->data_size = cpu_to_le64(inode->i_size);
|
||||||
|
|
||||||
|
/* Prepare the Merkle tree parameters */
|
||||||
|
err = fsverity_init_merkle_tree_params(¶ms, inode,
|
||||||
|
arg->hash_algorithm,
|
||||||
|
desc->log_blocksize,
|
||||||
|
desc->salt, desc->salt_size);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start enabling verity on this file, serialized by the inode lock.
|
||||||
|
* Fail if verity is already enabled or is already being enabled.
|
||||||
|
*/
|
||||||
|
inode_lock(inode);
|
||||||
|
if (IS_VERITY(inode))
|
||||||
|
err = -EEXIST;
|
||||||
|
else
|
||||||
|
err = vops->begin_enable_verity(filp);
|
||||||
|
inode_unlock(inode);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build the Merkle tree. Don't hold the inode lock during this, since
|
||||||
|
* on huge files this may take a very long time and we don't want to
|
||||||
|
* force unrelated syscalls like chown() to block forever. We don't
|
||||||
|
* need the inode lock here because deny_write_access() already prevents
|
||||||
|
* the file from being written to or truncated, and we still serialize
|
||||||
|
* ->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.
|
||||||
|
*/
|
||||||
|
pr_debug("Building Merkle tree...\n");
|
||||||
|
BUILD_BUG_ON(sizeof(desc->root_hash) < FS_VERITY_MAX_DIGEST_SIZE);
|
||||||
|
err = build_merkle_tree(inode, ¶ms, desc->root_hash);
|
||||||
|
if (err) {
|
||||||
|
fsverity_err(inode, "Error %d building Merkle tree", err);
|
||||||
|
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
|
||||||
|
* reusing the merkle_tree_params from above. Instead, just create the
|
||||||
|
* fsverity_info from the fsverity_descriptor as if it were just loaded
|
||||||
|
* from disk. This is simpler, and it serves as an extra check that the
|
||||||
|
* metadata we're writing is valid before actually enabling verity.
|
||||||
|
*/
|
||||||
|
vi = fsverity_create_info(inode, desc, desc_size);
|
||||||
|
if (IS_ERR(vi)) {
|
||||||
|
err = PTR_ERR(vi);
|
||||||
|
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.
|
||||||
|
* Serialized with ->begin_enable_verity() by the inode lock.
|
||||||
|
*/
|
||||||
|
inode_lock(inode);
|
||||||
|
err = vops->end_enable_verity(filp, desc, desc_size, params.tree_size);
|
||||||
|
inode_unlock(inode);
|
||||||
|
if (err) {
|
||||||
|
fsverity_err(inode, "%ps() failed with err %d",
|
||||||
|
vops->end_enable_verity, err);
|
||||||
|
fsverity_free_info(vi);
|
||||||
|
} else if (WARN_ON(!IS_VERITY(inode))) {
|
||||||
|
err = -EINVAL;
|
||||||
|
fsverity_free_info(vi);
|
||||||
|
} else {
|
||||||
|
/* Successfully enabled verity */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Readers can start using ->i_verity_info immediately, so it
|
||||||
|
* can't be rolled back once set. So don't set it until just
|
||||||
|
* after the filesystem has successfully enabled verity.
|
||||||
|
*/
|
||||||
|
fsverity_set_info(inode, vi);
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
kfree(params.hashstate);
|
||||||
|
kfree(desc);
|
||||||
|
return err;
|
||||||
|
|
||||||
|
rollback:
|
||||||
|
inode_lock(inode);
|
||||||
|
(void)vops->end_enable_verity(filp, NULL, 0, params.tree_size);
|
||||||
|
inode_unlock(inode);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_ioctl_enable() - enable verity on a file
|
||||||
|
*
|
||||||
|
* Enable fs-verity on a file. See the "FS_IOC_ENABLE_VERITY" section of
|
||||||
|
* Documentation/filesystems/fsverity.rst for the documentation.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, -errno on failure
|
||||||
|
*/
|
||||||
|
int fsverity_ioctl_enable(struct file *filp, const void __user *uarg)
|
||||||
|
{
|
||||||
|
struct inode *inode = file_inode(filp);
|
||||||
|
struct fsverity_enable_arg arg;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (copy_from_user(&arg, uarg, sizeof(arg)))
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
if (arg.version != 1)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (arg.__reserved1 ||
|
||||||
|
memchr_inv(arg.__reserved2, 0, sizeof(arg.__reserved2)))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (arg.block_size != PAGE_SIZE)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (arg.salt_size > FIELD_SIZEOF(struct fsverity_descriptor, salt))
|
||||||
|
return -EMSGSIZE;
|
||||||
|
|
||||||
|
if (arg.sig_size > FS_VERITY_MAX_SIGNATURE_SIZE)
|
||||||
|
return -EMSGSIZE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Require a regular file with write access. But the actual fd must
|
||||||
|
* still be readonly so that we can lock out all writers. This is
|
||||||
|
* needed to guarantee that no writable fds exist to the file once it
|
||||||
|
* has verity enabled, and to stabilize the data being hashed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
err = inode_permission(inode, MAY_WRITE);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (IS_APPEND(inode))
|
||||||
|
return -EPERM;
|
||||||
|
|
||||||
|
if (S_ISDIR(inode->i_mode))
|
||||||
|
return -EISDIR;
|
||||||
|
|
||||||
|
if (!S_ISREG(inode->i_mode))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
err = mnt_want_write_file(filp);
|
||||||
|
if (err) /* -EROFS */
|
||||||
|
return err;
|
||||||
|
|
||||||
|
err = deny_write_access(filp);
|
||||||
|
if (err) /* -ETXTBSY */
|
||||||
|
goto out_drop_write;
|
||||||
|
|
||||||
|
err = enable_verity(filp, &arg);
|
||||||
|
if (err)
|
||||||
|
goto out_allow_write_access;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some pages of the file may have been evicted from pagecache after
|
||||||
|
* being used in the Merkle tree construction, then read into pagecache
|
||||||
|
* again by another process reading from the file concurrently. Since
|
||||||
|
* these pages didn't undergo verification against the file measurement
|
||||||
|
* which fs-verity now claims to be enforcing, we have to wipe the
|
||||||
|
* pagecache to ensure that all future reads are verified.
|
||||||
|
*/
|
||||||
|
filemap_write_and_wait(inode->i_mapping);
|
||||||
|
invalidate_inode_pages2(inode->i_mapping);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* allow_write_access() is needed to pair with deny_write_access().
|
||||||
|
* Regardless, the filesystem won't allow writing to verity files.
|
||||||
|
*/
|
||||||
|
out_allow_write_access:
|
||||||
|
allow_write_access(filp);
|
||||||
|
out_drop_write:
|
||||||
|
mnt_drop_write_file(filp);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(fsverity_ioctl_enable);
|
185
fs/verity/fsverity_private.h
Normal file
185
fs/verity/fsverity_private.h
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* fs-verity: read-only file-based authenticity protection
|
||||||
|
*
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _FSVERITY_PRIVATE_H
|
||||||
|
#define _FSVERITY_PRIVATE_H
|
||||||
|
|
||||||
|
#ifdef CONFIG_FS_VERITY_DEBUG
|
||||||
|
#define DEBUG
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) "fs-verity: " fmt
|
||||||
|
|
||||||
|
#include <crypto/sha.h>
|
||||||
|
#include <linux/fsverity.h>
|
||||||
|
|
||||||
|
struct ahash_request;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Implementation limit: maximum depth of the Merkle tree. For now 8 is plenty;
|
||||||
|
* it's enough for over U64_MAX bytes of data using SHA-256 and 4K blocks.
|
||||||
|
*/
|
||||||
|
#define FS_VERITY_MAX_LEVELS 8
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Largest digest size among all hash algorithms supported by fs-verity.
|
||||||
|
* Currently assumed to be <= size of fsverity_descriptor::root_hash.
|
||||||
|
*/
|
||||||
|
#define FS_VERITY_MAX_DIGEST_SIZE SHA512_DIGEST_SIZE
|
||||||
|
|
||||||
|
/* A hash algorithm supported by fs-verity */
|
||||||
|
struct fsverity_hash_alg {
|
||||||
|
struct crypto_ahash *tfm; /* hash tfm, allocated on demand */
|
||||||
|
const char *name; /* crypto API name, e.g. sha256 */
|
||||||
|
unsigned int digest_size; /* digest size in bytes, e.g. 32 for SHA-256 */
|
||||||
|
unsigned int block_size; /* block size in bytes, e.g. 64 for SHA-256 */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Merkle tree parameters: hash algorithm, initial hash state, and topology */
|
||||||
|
struct merkle_tree_params {
|
||||||
|
const struct fsverity_hash_alg *hash_alg; /* the hash algorithm */
|
||||||
|
const u8 *hashstate; /* initial hash state or NULL */
|
||||||
|
unsigned int digest_size; /* same as hash_alg->digest_size */
|
||||||
|
unsigned int block_size; /* size of data and tree blocks */
|
||||||
|
unsigned int hashes_per_block; /* number of hashes per tree block */
|
||||||
|
unsigned int log_blocksize; /* log2(block_size) */
|
||||||
|
unsigned int log_arity; /* log2(hashes_per_block) */
|
||||||
|
unsigned int num_levels; /* number of levels in Merkle tree */
|
||||||
|
u64 tree_size; /* Merkle tree size in bytes */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Starting block index for each tree level, ordered from leaf level (0)
|
||||||
|
* to root level ('num_levels - 1')
|
||||||
|
*/
|
||||||
|
u64 level_start[FS_VERITY_MAX_LEVELS];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_info - cached verity metadata for an inode
|
||||||
|
*
|
||||||
|
* When a verity file is first opened, an instance of this struct is allocated
|
||||||
|
* and stored in ->i_verity_info; it remains until the inode is evicted. It
|
||||||
|
* caches information about the Merkle tree that's needed to efficiently verify
|
||||||
|
* data read from the file. It also caches the file measurement. The Merkle
|
||||||
|
* tree pages themselves are not cached here, but the filesystem may cache them.
|
||||||
|
*/
|
||||||
|
struct fsverity_info {
|
||||||
|
struct merkle_tree_params tree_params;
|
||||||
|
u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE];
|
||||||
|
u8 measurement[FS_VERITY_MAX_DIGEST_SIZE];
|
||||||
|
const struct inode *inode;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Merkle tree properties. The file measurement is the hash of this structure
|
||||||
|
* excluding the signature and with the sig_size field set to 0.
|
||||||
|
*/
|
||||||
|
struct fsverity_descriptor {
|
||||||
|
__u8 version; /* must be 1 */
|
||||||
|
__u8 hash_algorithm; /* Merkle tree hash algorithm */
|
||||||
|
__u8 log_blocksize; /* log2 of size of data and tree blocks */
|
||||||
|
__u8 salt_size; /* size of salt in bytes; 0 if none */
|
||||||
|
__le32 sig_size; /* size of signature in bytes; 0 if none */
|
||||||
|
__le64 data_size; /* size of file the Merkle tree is built over */
|
||||||
|
__u8 root_hash[64]; /* Merkle tree root hash */
|
||||||
|
__u8 salt[32]; /* salt prepended to each hashed block */
|
||||||
|
__u8 __reserved[144]; /* must be 0's */
|
||||||
|
__u8 signature[]; /* optional PKCS#7 signature */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Arbitrary limit to bound the kmalloc() size. Can be changed. */
|
||||||
|
#define FS_VERITY_MAX_DESCRIPTOR_SIZE 16384
|
||||||
|
|
||||||
|
#define FS_VERITY_MAX_SIGNATURE_SIZE (FS_VERITY_MAX_DESCRIPTOR_SIZE - \
|
||||||
|
sizeof(struct fsverity_descriptor))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Format in which verity file measurements are signed. This is the same as
|
||||||
|
* 'struct fsverity_digest', except here some magic bytes are prepended to
|
||||||
|
* provide some context about what is being signed in case the same key is used
|
||||||
|
* for non-fsverity purposes, and here the fields have fixed endianness.
|
||||||
|
*/
|
||||||
|
struct fsverity_signed_digest {
|
||||||
|
char magic[8]; /* must be "FSVerity" */
|
||||||
|
__le16 digest_algorithm;
|
||||||
|
__le16 digest_size;
|
||||||
|
__u8 digest[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* hash_algs.c */
|
||||||
|
|
||||||
|
extern struct fsverity_hash_alg fsverity_hash_algs[];
|
||||||
|
|
||||||
|
const struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode,
|
||||||
|
unsigned int num);
|
||||||
|
const u8 *fsverity_prepare_hash_state(const struct fsverity_hash_alg *alg,
|
||||||
|
const u8 *salt, size_t salt_size);
|
||||||
|
int fsverity_hash_page(const struct merkle_tree_params *params,
|
||||||
|
const struct inode *inode,
|
||||||
|
struct ahash_request *req, struct page *page, u8 *out);
|
||||||
|
int fsverity_hash_buffer(const struct fsverity_hash_alg *alg,
|
||||||
|
const void *data, size_t size, u8 *out);
|
||||||
|
void __init fsverity_check_hash_algs(void);
|
||||||
|
|
||||||
|
/* init.c */
|
||||||
|
|
||||||
|
extern void __printf(3, 4) __cold
|
||||||
|
fsverity_msg(const struct inode *inode, const char *level,
|
||||||
|
const char *fmt, ...);
|
||||||
|
|
||||||
|
#define fsverity_warn(inode, fmt, ...) \
|
||||||
|
fsverity_msg((inode), KERN_WARNING, fmt, ##__VA_ARGS__)
|
||||||
|
#define fsverity_err(inode, fmt, ...) \
|
||||||
|
fsverity_msg((inode), KERN_ERR, fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
/* open.c */
|
||||||
|
|
||||||
|
int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
|
||||||
|
const struct inode *inode,
|
||||||
|
unsigned int hash_algorithm,
|
||||||
|
unsigned int log_blocksize,
|
||||||
|
const u8 *salt, size_t salt_size);
|
||||||
|
|
||||||
|
struct fsverity_info *fsverity_create_info(const struct inode *inode,
|
||||||
|
void *desc, size_t desc_size);
|
||||||
|
|
||||||
|
void fsverity_set_info(struct inode *inode, struct fsverity_info *vi);
|
||||||
|
|
||||||
|
void fsverity_free_info(struct fsverity_info *vi);
|
||||||
|
|
||||||
|
int __init fsverity_init_info_cache(void);
|
||||||
|
void __init fsverity_exit_info_cache(void);
|
||||||
|
|
||||||
|
/* signature.c */
|
||||||
|
|
||||||
|
#ifdef CONFIG_FS_VERITY_BUILTIN_SIGNATURES
|
||||||
|
int fsverity_verify_signature(const struct fsverity_info *vi,
|
||||||
|
const struct fsverity_descriptor *desc,
|
||||||
|
size_t desc_size);
|
||||||
|
|
||||||
|
int __init fsverity_init_signature(void);
|
||||||
|
#else /* !CONFIG_FS_VERITY_BUILTIN_SIGNATURES */
|
||||||
|
static inline int
|
||||||
|
fsverity_verify_signature(const struct fsverity_info *vi,
|
||||||
|
const struct fsverity_descriptor *desc,
|
||||||
|
size_t desc_size)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int fsverity_init_signature(void)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* !CONFIG_FS_VERITY_BUILTIN_SIGNATURES */
|
||||||
|
|
||||||
|
/* verify.c */
|
||||||
|
|
||||||
|
int __init fsverity_init_workqueue(void);
|
||||||
|
void __init fsverity_exit_workqueue(void);
|
||||||
|
|
||||||
|
#endif /* _FSVERITY_PRIVATE_H */
|
280
fs/verity/hash_algs.c
Normal file
280
fs/verity/hash_algs.c
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* fs/verity/hash_algs.c: fs-verity hash algorithms
|
||||||
|
*
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fsverity_private.h"
|
||||||
|
|
||||||
|
#include <crypto/hash.h>
|
||||||
|
#include <linux/scatterlist.h>
|
||||||
|
|
||||||
|
/* The hash algorithms supported by fs-verity */
|
||||||
|
struct fsverity_hash_alg fsverity_hash_algs[] = {
|
||||||
|
[FS_VERITY_HASH_ALG_SHA256] = {
|
||||||
|
.name = "sha256",
|
||||||
|
.digest_size = SHA256_DIGEST_SIZE,
|
||||||
|
.block_size = SHA256_BLOCK_SIZE,
|
||||||
|
},
|
||||||
|
[FS_VERITY_HASH_ALG_SHA512] = {
|
||||||
|
.name = "sha512",
|
||||||
|
.digest_size = SHA512_DIGEST_SIZE,
|
||||||
|
.block_size = SHA512_BLOCK_SIZE,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_get_hash_alg() - validate and prepare a hash algorithm
|
||||||
|
* @inode: optional inode for logging purposes
|
||||||
|
* @num: the hash algorithm number
|
||||||
|
*
|
||||||
|
* Get the struct fsverity_hash_alg for the given hash algorithm number, and
|
||||||
|
* ensure it has a hash transform ready to go. The hash transforms are
|
||||||
|
* allocated on-demand so that we don't waste resources unnecessarily, and
|
||||||
|
* because the crypto modules may be initialized later than fs/verity/.
|
||||||
|
*
|
||||||
|
* Return: pointer to the hash alg on success, else an ERR_PTR()
|
||||||
|
*/
|
||||||
|
const struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode,
|
||||||
|
unsigned int num)
|
||||||
|
{
|
||||||
|
struct fsverity_hash_alg *alg;
|
||||||
|
struct crypto_ahash *tfm;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (num >= ARRAY_SIZE(fsverity_hash_algs) ||
|
||||||
|
!fsverity_hash_algs[num].name) {
|
||||||
|
fsverity_warn(inode, "Unknown hash algorithm number: %u", num);
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
alg = &fsverity_hash_algs[num];
|
||||||
|
|
||||||
|
/* pairs with cmpxchg() below */
|
||||||
|
tfm = READ_ONCE(alg->tfm);
|
||||||
|
if (likely(tfm != NULL))
|
||||||
|
return alg;
|
||||||
|
/*
|
||||||
|
* Using the shash API would make things a bit simpler, but the ahash
|
||||||
|
* API is preferable as it allows the use of crypto accelerators.
|
||||||
|
*/
|
||||||
|
tfm = crypto_alloc_ahash(alg->name, 0, 0);
|
||||||
|
if (IS_ERR(tfm)) {
|
||||||
|
if (PTR_ERR(tfm) == -ENOENT) {
|
||||||
|
fsverity_warn(inode,
|
||||||
|
"Missing crypto API support for hash algorithm \"%s\"",
|
||||||
|
alg->name);
|
||||||
|
return ERR_PTR(-ENOPKG);
|
||||||
|
}
|
||||||
|
fsverity_err(inode,
|
||||||
|
"Error allocating hash algorithm \"%s\": %ld",
|
||||||
|
alg->name, PTR_ERR(tfm));
|
||||||
|
return ERR_CAST(tfm);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = -EINVAL;
|
||||||
|
if (WARN_ON(alg->digest_size != crypto_ahash_digestsize(tfm)))
|
||||||
|
goto err_free_tfm;
|
||||||
|
if (WARN_ON(alg->block_size != crypto_ahash_blocksize(tfm)))
|
||||||
|
goto err_free_tfm;
|
||||||
|
|
||||||
|
pr_info("%s using implementation \"%s\"\n",
|
||||||
|
alg->name, crypto_ahash_driver_name(tfm));
|
||||||
|
|
||||||
|
/* pairs with READ_ONCE() above */
|
||||||
|
if (cmpxchg(&alg->tfm, NULL, tfm) != NULL)
|
||||||
|
crypto_free_ahash(tfm);
|
||||||
|
|
||||||
|
return alg;
|
||||||
|
|
||||||
|
err_free_tfm:
|
||||||
|
crypto_free_ahash(tfm);
|
||||||
|
return ERR_PTR(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_prepare_hash_state() - precompute the initial hash state
|
||||||
|
* @alg: hash algorithm
|
||||||
|
* @salt: a salt which is to be prepended to all data to be hashed
|
||||||
|
* @salt_size: salt size in bytes, possibly 0
|
||||||
|
*
|
||||||
|
* Return: NULL if the salt is empty, otherwise the kmalloc()'ed precomputed
|
||||||
|
* initial hash state on success or an ERR_PTR() on failure.
|
||||||
|
*/
|
||||||
|
const u8 *fsverity_prepare_hash_state(const struct fsverity_hash_alg *alg,
|
||||||
|
const u8 *salt, size_t salt_size)
|
||||||
|
{
|
||||||
|
u8 *hashstate = NULL;
|
||||||
|
struct ahash_request *req = NULL;
|
||||||
|
u8 *padded_salt = NULL;
|
||||||
|
size_t padded_salt_size;
|
||||||
|
struct scatterlist sg;
|
||||||
|
DECLARE_CRYPTO_WAIT(wait);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (salt_size == 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
hashstate = kmalloc(crypto_ahash_statesize(alg->tfm), GFP_KERNEL);
|
||||||
|
if (!hashstate)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
req = ahash_request_alloc(alg->tfm, GFP_KERNEL);
|
||||||
|
if (!req) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto err_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Zero-pad the salt to the next multiple of the input size of the hash
|
||||||
|
* algorithm's compression function, e.g. 64 bytes for SHA-256 or 128
|
||||||
|
* bytes for SHA-512. This ensures that the hash algorithm won't have
|
||||||
|
* any bytes buffered internally after processing the salt, thus making
|
||||||
|
* salted hashing just as fast as unsalted hashing.
|
||||||
|
*/
|
||||||
|
padded_salt_size = round_up(salt_size, alg->block_size);
|
||||||
|
padded_salt = kzalloc(padded_salt_size, GFP_KERNEL);
|
||||||
|
if (!padded_salt) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
goto err_free;
|
||||||
|
}
|
||||||
|
memcpy(padded_salt, salt, salt_size);
|
||||||
|
|
||||||
|
sg_init_one(&sg, padded_salt, padded_salt_size);
|
||||||
|
ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP |
|
||||||
|
CRYPTO_TFM_REQ_MAY_BACKLOG,
|
||||||
|
crypto_req_done, &wait);
|
||||||
|
ahash_request_set_crypt(req, &sg, NULL, padded_salt_size);
|
||||||
|
|
||||||
|
err = crypto_wait_req(crypto_ahash_init(req), &wait);
|
||||||
|
if (err)
|
||||||
|
goto err_free;
|
||||||
|
|
||||||
|
err = crypto_wait_req(crypto_ahash_update(req), &wait);
|
||||||
|
if (err)
|
||||||
|
goto err_free;
|
||||||
|
|
||||||
|
err = crypto_ahash_export(req, hashstate);
|
||||||
|
if (err)
|
||||||
|
goto err_free;
|
||||||
|
out:
|
||||||
|
ahash_request_free(req);
|
||||||
|
kfree(padded_salt);
|
||||||
|
return hashstate;
|
||||||
|
|
||||||
|
err_free:
|
||||||
|
kfree(hashstate);
|
||||||
|
hashstate = ERR_PTR(err);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_hash_page() - hash a single data or hash page
|
||||||
|
* @params: the Merkle tree's parameters
|
||||||
|
* @inode: inode for which the hashing is being done
|
||||||
|
* @req: preallocated hash request
|
||||||
|
* @page: the page to hash
|
||||||
|
* @out: output digest, size 'params->digest_size' bytes
|
||||||
|
*
|
||||||
|
* Hash a single data or hash block, assuming block_size == PAGE_SIZE.
|
||||||
|
* The hash is salted if a salt is specified in the Merkle tree parameters.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, -errno on failure
|
||||||
|
*/
|
||||||
|
int fsverity_hash_page(const struct merkle_tree_params *params,
|
||||||
|
const struct inode *inode,
|
||||||
|
struct ahash_request *req, struct page *page, u8 *out)
|
||||||
|
{
|
||||||
|
struct scatterlist sg;
|
||||||
|
DECLARE_CRYPTO_WAIT(wait);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (WARN_ON(params->block_size != PAGE_SIZE))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
sg_init_table(&sg, 1);
|
||||||
|
sg_set_page(&sg, page, PAGE_SIZE, 0);
|
||||||
|
ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP |
|
||||||
|
CRYPTO_TFM_REQ_MAY_BACKLOG,
|
||||||
|
crypto_req_done, &wait);
|
||||||
|
ahash_request_set_crypt(req, &sg, out, PAGE_SIZE);
|
||||||
|
|
||||||
|
if (params->hashstate) {
|
||||||
|
err = crypto_ahash_import(req, params->hashstate);
|
||||||
|
if (err) {
|
||||||
|
fsverity_err(inode,
|
||||||
|
"Error %d importing hash state", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
err = crypto_ahash_finup(req);
|
||||||
|
} else {
|
||||||
|
err = crypto_ahash_digest(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = crypto_wait_req(err, &wait);
|
||||||
|
if (err)
|
||||||
|
fsverity_err(inode, "Error %d computing page hash", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_hash_buffer() - hash some data
|
||||||
|
* @alg: the hash algorithm to use
|
||||||
|
* @data: the data to hash
|
||||||
|
* @size: size of data to hash, in bytes
|
||||||
|
* @out: output digest, size 'alg->digest_size' bytes
|
||||||
|
*
|
||||||
|
* Hash some data which is located in physically contiguous memory (i.e. memory
|
||||||
|
* allocated by kmalloc(), not by vmalloc()). No salt is used.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, -errno on failure
|
||||||
|
*/
|
||||||
|
int fsverity_hash_buffer(const struct fsverity_hash_alg *alg,
|
||||||
|
const void *data, size_t size, u8 *out)
|
||||||
|
{
|
||||||
|
struct ahash_request *req;
|
||||||
|
struct scatterlist sg;
|
||||||
|
DECLARE_CRYPTO_WAIT(wait);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
req = ahash_request_alloc(alg->tfm, GFP_KERNEL);
|
||||||
|
if (!req)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
sg_init_one(&sg, data, size);
|
||||||
|
ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP |
|
||||||
|
CRYPTO_TFM_REQ_MAY_BACKLOG,
|
||||||
|
crypto_req_done, &wait);
|
||||||
|
ahash_request_set_crypt(req, &sg, out, size);
|
||||||
|
|
||||||
|
err = crypto_wait_req(crypto_ahash_digest(req), &wait);
|
||||||
|
|
||||||
|
ahash_request_free(req);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __init fsverity_check_hash_algs(void)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sanity check the hash algorithms (could be a build-time check, but
|
||||||
|
* they're in an array)
|
||||||
|
*/
|
||||||
|
for (i = 0; i < ARRAY_SIZE(fsverity_hash_algs); i++) {
|
||||||
|
const struct fsverity_hash_alg *alg = &fsverity_hash_algs[i];
|
||||||
|
|
||||||
|
if (!alg->name)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
BUG_ON(alg->digest_size > FS_VERITY_MAX_DIGEST_SIZE);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For efficiency, the implementation currently assumes the
|
||||||
|
* digest and block sizes are powers of 2. This limitation can
|
||||||
|
* be lifted if the code is updated to handle other values.
|
||||||
|
*/
|
||||||
|
BUG_ON(!is_power_of_2(alg->digest_size));
|
||||||
|
BUG_ON(!is_power_of_2(alg->block_size));
|
||||||
|
}
|
||||||
|
}
|
61
fs/verity/init.c
Normal file
61
fs/verity/init.c
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* fs/verity/init.c: fs-verity module initialization and logging
|
||||||
|
*
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fsverity_private.h"
|
||||||
|
|
||||||
|
#include <linux/ratelimit.h>
|
||||||
|
|
||||||
|
void fsverity_msg(const struct inode *inode, const char *level,
|
||||||
|
const char *fmt, ...)
|
||||||
|
{
|
||||||
|
static DEFINE_RATELIMIT_STATE(rs, DEFAULT_RATELIMIT_INTERVAL,
|
||||||
|
DEFAULT_RATELIMIT_BURST);
|
||||||
|
struct va_format vaf;
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
if (!__ratelimit(&rs))
|
||||||
|
return;
|
||||||
|
|
||||||
|
va_start(args, fmt);
|
||||||
|
vaf.fmt = fmt;
|
||||||
|
vaf.va = &args;
|
||||||
|
if (inode)
|
||||||
|
printk("%sfs-verity (%s, inode %lu): %pV\n",
|
||||||
|
level, inode->i_sb->s_id, inode->i_ino, &vaf);
|
||||||
|
else
|
||||||
|
printk("%sfs-verity: %pV\n", level, &vaf);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init fsverity_init(void)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
fsverity_check_hash_algs();
|
||||||
|
|
||||||
|
err = fsverity_init_info_cache();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
err = fsverity_init_workqueue();
|
||||||
|
if (err)
|
||||||
|
goto err_exit_info_cache;
|
||||||
|
|
||||||
|
err = fsverity_init_signature();
|
||||||
|
if (err)
|
||||||
|
goto err_exit_workqueue;
|
||||||
|
|
||||||
|
pr_debug("Initialized fs-verity\n");
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_exit_workqueue:
|
||||||
|
fsverity_exit_workqueue();
|
||||||
|
err_exit_info_cache:
|
||||||
|
fsverity_exit_info_cache();
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
late_initcall(fsverity_init)
|
57
fs/verity/measure.c
Normal file
57
fs/verity/measure.c
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* fs/verity/measure.c: ioctl to get a verity file's measurement
|
||||||
|
*
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fsverity_private.h"
|
||||||
|
|
||||||
|
#include <linux/uaccess.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_ioctl_measure() - get a verity file's measurement
|
||||||
|
*
|
||||||
|
* Retrieve the file measurement that the kernel is enforcing for reads from a
|
||||||
|
* verity file. See the "FS_IOC_MEASURE_VERITY" section of
|
||||||
|
* Documentation/filesystems/fsverity.rst for the documentation.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, -errno on failure
|
||||||
|
*/
|
||||||
|
int fsverity_ioctl_measure(struct file *filp, void __user *_uarg)
|
||||||
|
{
|
||||||
|
const struct inode *inode = file_inode(filp);
|
||||||
|
struct fsverity_digest __user *uarg = _uarg;
|
||||||
|
const struct fsverity_info *vi;
|
||||||
|
const struct fsverity_hash_alg *hash_alg;
|
||||||
|
struct fsverity_digest arg;
|
||||||
|
|
||||||
|
vi = fsverity_get_info(inode);
|
||||||
|
if (!vi)
|
||||||
|
return -ENODATA; /* not a verity file */
|
||||||
|
hash_alg = vi->tree_params.hash_alg;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The user specifies the digest_size their buffer has space for; we can
|
||||||
|
* return the digest if it fits in the available space. We write back
|
||||||
|
* the actual size, which may be shorter than the user-specified size.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (get_user(arg.digest_size, &uarg->digest_size))
|
||||||
|
return -EFAULT;
|
||||||
|
if (arg.digest_size < hash_alg->digest_size)
|
||||||
|
return -EOVERFLOW;
|
||||||
|
|
||||||
|
memset(&arg, 0, sizeof(arg));
|
||||||
|
arg.digest_algorithm = hash_alg - fsverity_hash_algs;
|
||||||
|
arg.digest_size = hash_alg->digest_size;
|
||||||
|
|
||||||
|
if (copy_to_user(uarg, &arg, sizeof(arg)))
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
if (copy_to_user(uarg->digest, vi->measurement, hash_alg->digest_size))
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(fsverity_ioctl_measure);
|
356
fs/verity/open.c
Normal file
356
fs/verity/open.c
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* fs/verity/open.c: opening fs-verity files
|
||||||
|
*
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fsverity_private.h"
|
||||||
|
|
||||||
|
#include <linux/slab.h>
|
||||||
|
|
||||||
|
static struct kmem_cache *fsverity_info_cachep;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_init_merkle_tree_params() - initialize Merkle tree parameters
|
||||||
|
* @params: the parameters struct to initialize
|
||||||
|
* @inode: the inode for which the Merkle tree is being built
|
||||||
|
* @hash_algorithm: number of hash algorithm to use
|
||||||
|
* @log_blocksize: log base 2 of block size to use
|
||||||
|
* @salt: pointer to salt (optional)
|
||||||
|
* @salt_size: size of salt, possibly 0
|
||||||
|
*
|
||||||
|
* Validate the hash algorithm and block size, then compute the tree topology
|
||||||
|
* (num levels, num blocks in each level, etc.) and initialize @params.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, -errno on failure
|
||||||
|
*/
|
||||||
|
int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
|
||||||
|
const struct inode *inode,
|
||||||
|
unsigned int hash_algorithm,
|
||||||
|
unsigned int log_blocksize,
|
||||||
|
const u8 *salt, size_t salt_size)
|
||||||
|
{
|
||||||
|
const struct fsverity_hash_alg *hash_alg;
|
||||||
|
int err;
|
||||||
|
u64 blocks;
|
||||||
|
u64 offset;
|
||||||
|
int level;
|
||||||
|
|
||||||
|
memset(params, 0, sizeof(*params));
|
||||||
|
|
||||||
|
hash_alg = fsverity_get_hash_alg(inode, hash_algorithm);
|
||||||
|
if (IS_ERR(hash_alg))
|
||||||
|
return PTR_ERR(hash_alg);
|
||||||
|
params->hash_alg = hash_alg;
|
||||||
|
params->digest_size = hash_alg->digest_size;
|
||||||
|
|
||||||
|
params->hashstate = fsverity_prepare_hash_state(hash_alg, salt,
|
||||||
|
salt_size);
|
||||||
|
if (IS_ERR(params->hashstate)) {
|
||||||
|
err = PTR_ERR(params->hashstate);
|
||||||
|
params->hashstate = NULL;
|
||||||
|
fsverity_err(inode, "Error %d preparing hash state", err);
|
||||||
|
goto out_err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log_blocksize != PAGE_SHIFT) {
|
||||||
|
fsverity_warn(inode, "Unsupported log_blocksize: %u",
|
||||||
|
log_blocksize);
|
||||||
|
err = -EINVAL;
|
||||||
|
goto out_err;
|
||||||
|
}
|
||||||
|
params->log_blocksize = log_blocksize;
|
||||||
|
params->block_size = 1 << log_blocksize;
|
||||||
|
|
||||||
|
if (WARN_ON(!is_power_of_2(params->digest_size))) {
|
||||||
|
err = -EINVAL;
|
||||||
|
goto out_err;
|
||||||
|
}
|
||||||
|
if (params->block_size < 2 * params->digest_size) {
|
||||||
|
fsverity_warn(inode,
|
||||||
|
"Merkle tree block size (%u) too small for hash algorithm \"%s\"",
|
||||||
|
params->block_size, hash_alg->name);
|
||||||
|
err = -EINVAL;
|
||||||
|
goto out_err;
|
||||||
|
}
|
||||||
|
params->log_arity = params->log_blocksize - ilog2(params->digest_size);
|
||||||
|
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
|
||||||
|
* level to the starting block of that level. Level 'num_levels - 1' is
|
||||||
|
* the root and is stored first. Level 0 is the level directly "above"
|
||||||
|
* the data blocks and is stored last.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Compute number of levels and the number of blocks in each level */
|
||||||
|
blocks = (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) {
|
||||||
|
if (params->num_levels >= FS_VERITY_MAX_LEVELS) {
|
||||||
|
fsverity_err(inode, "Too many levels in Merkle tree");
|
||||||
|
err = -EINVAL;
|
||||||
|
goto out_err;
|
||||||
|
}
|
||||||
|
blocks = (blocks + params->hashes_per_block - 1) >>
|
||||||
|
params->log_arity;
|
||||||
|
/* temporarily using level_start[] to store blocks in level */
|
||||||
|
params->level_start[params->num_levels++] = blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compute the starting block of each level */
|
||||||
|
offset = 0;
|
||||||
|
for (level = (int)params->num_levels - 1; level >= 0; level--) {
|
||||||
|
blocks = params->level_start[level];
|
||||||
|
params->level_start[level] = offset;
|
||||||
|
pr_debug("Level %d is %llu blocks starting at index %llu\n",
|
||||||
|
level, blocks, offset);
|
||||||
|
offset += blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
params->tree_size = offset << log_blocksize;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
out_err:
|
||||||
|
kfree(params->hashstate);
|
||||||
|
memset(params, 0, sizeof(*params));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compute the file measurement by hashing the fsverity_descriptor excluding the
|
||||||
|
* signature and with the sig_size field set to 0.
|
||||||
|
*/
|
||||||
|
static int compute_file_measurement(const struct fsverity_hash_alg *hash_alg,
|
||||||
|
struct fsverity_descriptor *desc,
|
||||||
|
u8 *measurement)
|
||||||
|
{
|
||||||
|
__le32 sig_size = desc->sig_size;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
desc->sig_size = 0;
|
||||||
|
err = fsverity_hash_buffer(hash_alg, desc, sizeof(*desc), measurement);
|
||||||
|
desc->sig_size = sig_size;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Validate the given fsverity_descriptor and create a new fsverity_info from
|
||||||
|
* it. The signature (if present) is also checked.
|
||||||
|
*/
|
||||||
|
struct fsverity_info *fsverity_create_info(const struct inode *inode,
|
||||||
|
void *_desc, size_t desc_size)
|
||||||
|
{
|
||||||
|
struct fsverity_descriptor *desc = _desc;
|
||||||
|
struct fsverity_info *vi;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (desc_size < sizeof(*desc)) {
|
||||||
|
fsverity_err(inode, "Unrecognized descriptor size: %zu bytes",
|
||||||
|
desc_size);
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (desc->version != 1) {
|
||||||
|
fsverity_err(inode, "Unrecognized descriptor version: %u",
|
||||||
|
desc->version);
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memchr_inv(desc->__reserved, 0, sizeof(desc->__reserved))) {
|
||||||
|
fsverity_err(inode, "Reserved bits set in descriptor");
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (desc->salt_size > sizeof(desc->salt)) {
|
||||||
|
fsverity_err(inode, "Invalid salt_size: %u", desc->salt_size);
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (le64_to_cpu(desc->data_size) != inode->i_size) {
|
||||||
|
fsverity_err(inode,
|
||||||
|
"Wrong data_size: %llu (desc) != %lld (inode)",
|
||||||
|
le64_to_cpu(desc->data_size), inode->i_size);
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
vi = kmem_cache_zalloc(fsverity_info_cachep, GFP_KERNEL);
|
||||||
|
if (!vi)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
vi->inode = inode;
|
||||||
|
|
||||||
|
err = fsverity_init_merkle_tree_params(&vi->tree_params, inode,
|
||||||
|
desc->hash_algorithm,
|
||||||
|
desc->log_blocksize,
|
||||||
|
desc->salt, desc->salt_size);
|
||||||
|
if (err) {
|
||||||
|
fsverity_err(inode,
|
||||||
|
"Error %d initializing Merkle tree parameters",
|
||||||
|
err);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(vi->root_hash, desc->root_hash, vi->tree_params.digest_size);
|
||||||
|
|
||||||
|
err = compute_file_measurement(vi->tree_params.hash_alg, desc,
|
||||||
|
vi->measurement);
|
||||||
|
if (err) {
|
||||||
|
fsverity_err(inode, "Error %d computing file measurement", err);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
pr_debug("Computed file measurement: %s:%*phN\n",
|
||||||
|
vi->tree_params.hash_alg->name,
|
||||||
|
vi->tree_params.digest_size, vi->measurement);
|
||||||
|
|
||||||
|
err = fsverity_verify_signature(vi, desc, desc_size);
|
||||||
|
out:
|
||||||
|
if (err) {
|
||||||
|
fsverity_free_info(vi);
|
||||||
|
vi = ERR_PTR(err);
|
||||||
|
}
|
||||||
|
return vi;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fsverity_set_info(struct inode *inode, struct fsverity_info *vi)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Multiple processes may race to set ->i_verity_info, so use cmpxchg.
|
||||||
|
* This pairs with the READ_ONCE() in fsverity_get_info().
|
||||||
|
*/
|
||||||
|
if (cmpxchg(&inode->i_verity_info, NULL, vi) != NULL)
|
||||||
|
fsverity_free_info(vi);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fsverity_free_info(struct fsverity_info *vi)
|
||||||
|
{
|
||||||
|
if (!vi)
|
||||||
|
return;
|
||||||
|
kfree(vi->tree_params.hashstate);
|
||||||
|
kmem_cache_free(fsverity_info_cachep, vi);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure the inode has an ->i_verity_info */
|
||||||
|
static int ensure_verity_info(struct inode *inode)
|
||||||
|
{
|
||||||
|
struct fsverity_info *vi = fsverity_get_info(inode);
|
||||||
|
struct fsverity_descriptor *desc;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (vi)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
res = inode->i_sb->s_vop->get_verity_descriptor(inode, NULL, 0);
|
||||||
|
if (res < 0) {
|
||||||
|
fsverity_err(inode,
|
||||||
|
"Error %d getting verity descriptor size", res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (res > FS_VERITY_MAX_DESCRIPTOR_SIZE) {
|
||||||
|
fsverity_err(inode, "Verity descriptor is too large (%d bytes)",
|
||||||
|
res);
|
||||||
|
return -EMSGSIZE;
|
||||||
|
}
|
||||||
|
desc = kmalloc(res, GFP_KERNEL);
|
||||||
|
if (!desc)
|
||||||
|
return -ENOMEM;
|
||||||
|
res = inode->i_sb->s_vop->get_verity_descriptor(inode, desc, res);
|
||||||
|
if (res < 0) {
|
||||||
|
fsverity_err(inode, "Error %d reading verity descriptor", res);
|
||||||
|
goto out_free_desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
vi = fsverity_create_info(inode, desc, res);
|
||||||
|
if (IS_ERR(vi)) {
|
||||||
|
res = PTR_ERR(vi);
|
||||||
|
goto out_free_desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
fsverity_set_info(inode, vi);
|
||||||
|
res = 0;
|
||||||
|
out_free_desc:
|
||||||
|
kfree(desc);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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))
|
||||||
|
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 ensure_verity_info(inode);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(fsverity_file_open);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)) {
|
||||||
|
pr_debug("Denying truncate of verity file (ino %lu)\n",
|
||||||
|
d_inode(dentry)->i_ino);
|
||||||
|
return -EPERM;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(fsverity_prepare_setattr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_cleanup_inode() - free the inode's verity info, if present
|
||||||
|
*
|
||||||
|
* 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);
|
||||||
|
inode->i_verity_info = NULL;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(fsverity_cleanup_inode);
|
||||||
|
|
||||||
|
int __init fsverity_init_info_cache(void)
|
||||||
|
{
|
||||||
|
fsverity_info_cachep = KMEM_CACHE_USERCOPY(fsverity_info,
|
||||||
|
SLAB_RECLAIM_ACCOUNT,
|
||||||
|
measurement);
|
||||||
|
if (!fsverity_info_cachep)
|
||||||
|
return -ENOMEM;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __init fsverity_exit_info_cache(void)
|
||||||
|
{
|
||||||
|
kmem_cache_destroy(fsverity_info_cachep);
|
||||||
|
fsverity_info_cachep = NULL;
|
||||||
|
}
|
157
fs/verity/signature.c
Normal file
157
fs/verity/signature.c
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* fs/verity/signature.c: verification of builtin signatures
|
||||||
|
*
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fsverity_private.h"
|
||||||
|
|
||||||
|
#include <linux/cred.h>
|
||||||
|
#include <linux/key.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/verification.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* /proc/sys/fs/verity/require_signatures
|
||||||
|
* If 1, all verity files must have a valid builtin signature.
|
||||||
|
*/
|
||||||
|
static int fsverity_require_signatures;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keyring that contains the trusted X.509 certificates.
|
||||||
|
*
|
||||||
|
* Only root (kuid=0) can modify this. Also, root may use
|
||||||
|
* keyctl_restrict_keyring() to prevent any more additions.
|
||||||
|
*/
|
||||||
|
static struct key *fsverity_keyring;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_verify_signature() - check a verity file's signature
|
||||||
|
*
|
||||||
|
* If the file's fs-verity descriptor includes a signature of the file
|
||||||
|
* measurement, verify it against the certificates in the fs-verity keyring.
|
||||||
|
*
|
||||||
|
* Return: 0 on success (signature valid or not required); -errno on failure
|
||||||
|
*/
|
||||||
|
int fsverity_verify_signature(const struct fsverity_info *vi,
|
||||||
|
const struct fsverity_descriptor *desc,
|
||||||
|
size_t desc_size)
|
||||||
|
{
|
||||||
|
const struct inode *inode = vi->inode;
|
||||||
|
const struct fsverity_hash_alg *hash_alg = vi->tree_params.hash_alg;
|
||||||
|
const u32 sig_size = le32_to_cpu(desc->sig_size);
|
||||||
|
struct fsverity_signed_digest *d;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (sig_size == 0) {
|
||||||
|
if (fsverity_require_signatures) {
|
||||||
|
fsverity_err(inode,
|
||||||
|
"require_signatures=1, rejecting unsigned file!");
|
||||||
|
return -EPERM;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sig_size > desc_size - sizeof(*desc)) {
|
||||||
|
fsverity_err(inode, "Signature overflows verity descriptor");
|
||||||
|
return -EBADMSG;
|
||||||
|
}
|
||||||
|
|
||||||
|
d = kzalloc(sizeof(*d) + hash_alg->digest_size, GFP_KERNEL);
|
||||||
|
if (!d)
|
||||||
|
return -ENOMEM;
|
||||||
|
memcpy(d->magic, "FSVerity", 8);
|
||||||
|
d->digest_algorithm = cpu_to_le16(hash_alg - fsverity_hash_algs);
|
||||||
|
d->digest_size = cpu_to_le16(hash_alg->digest_size);
|
||||||
|
memcpy(d->digest, vi->measurement, hash_alg->digest_size);
|
||||||
|
|
||||||
|
err = verify_pkcs7_signature(d, sizeof(*d) + hash_alg->digest_size,
|
||||||
|
desc->signature, sig_size,
|
||||||
|
fsverity_keyring,
|
||||||
|
VERIFYING_UNSPECIFIED_SIGNATURE,
|
||||||
|
NULL, NULL);
|
||||||
|
kfree(d);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
if (err == -ENOKEY)
|
||||||
|
fsverity_err(inode,
|
||||||
|
"File's signing cert isn't in the fs-verity keyring");
|
||||||
|
else if (err == -EKEYREJECTED)
|
||||||
|
fsverity_err(inode, "Incorrect file signature");
|
||||||
|
else if (err == -EBADMSG)
|
||||||
|
fsverity_err(inode, "Malformed file signature");
|
||||||
|
else
|
||||||
|
fsverity_err(inode, "Error %d verifying file signature",
|
||||||
|
err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_debug("Valid signature for file measurement %s:%*phN\n",
|
||||||
|
hash_alg->name, hash_alg->digest_size, vi->measurement);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_SYSCTL
|
||||||
|
static struct ctl_table_header *fsverity_sysctl_header;
|
||||||
|
|
||||||
|
static const struct ctl_path fsverity_sysctl_path[] = {
|
||||||
|
{ .procname = "fs", },
|
||||||
|
{ .procname = "verity", },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct ctl_table fsverity_sysctl_table[] = {
|
||||||
|
{
|
||||||
|
.procname = "require_signatures",
|
||||||
|
.data = &fsverity_require_signatures,
|
||||||
|
.maxlen = sizeof(int),
|
||||||
|
.mode = 0644,
|
||||||
|
.proc_handler = proc_dointvec_minmax,
|
||||||
|
.extra1 = SYSCTL_ZERO,
|
||||||
|
.extra2 = SYSCTL_ONE,
|
||||||
|
},
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init fsverity_sysctl_init(void)
|
||||||
|
{
|
||||||
|
fsverity_sysctl_header = register_sysctl_paths(fsverity_sysctl_path,
|
||||||
|
fsverity_sysctl_table);
|
||||||
|
if (!fsverity_sysctl_header) {
|
||||||
|
pr_err("sysctl registration failed!\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else /* !CONFIG_SYSCTL */
|
||||||
|
static inline int __init fsverity_sysctl_init(void)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* !CONFIG_SYSCTL */
|
||||||
|
|
||||||
|
int __init fsverity_init_signature(void)
|
||||||
|
{
|
||||||
|
struct key *ring;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
ring = keyring_alloc(".fs-verity", KUIDT_INIT(0), KGIDT_INIT(0),
|
||||||
|
current_cred(), KEY_POS_SEARCH |
|
||||||
|
KEY_USR_VIEW | KEY_USR_READ | KEY_USR_WRITE |
|
||||||
|
KEY_USR_SEARCH | KEY_USR_SETATTR,
|
||||||
|
KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
|
||||||
|
if (IS_ERR(ring))
|
||||||
|
return PTR_ERR(ring);
|
||||||
|
|
||||||
|
err = fsverity_sysctl_init();
|
||||||
|
if (err)
|
||||||
|
goto err_put_ring;
|
||||||
|
|
||||||
|
fsverity_keyring = ring;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_put_ring:
|
||||||
|
key_put(ring);
|
||||||
|
return err;
|
||||||
|
}
|
281
fs/verity/verify.c
Normal file
281
fs/verity/verify.c
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* fs/verity/verify.c: data verification functions, i.e. hooks for ->readpages()
|
||||||
|
*
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fsverity_private.h"
|
||||||
|
|
||||||
|
#include <crypto/hash.h>
|
||||||
|
#include <linux/bio.h>
|
||||||
|
#include <linux/ratelimit.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extract a hash from a hash page */
|
||||||
|
static void extract_hash(struct page *hpage, unsigned int hoffset,
|
||||||
|
unsigned int hsize, u8 *out)
|
||||||
|
{
|
||||||
|
void *virt = kmap_atomic(hpage);
|
||||||
|
|
||||||
|
memcpy(out, virt + hoffset, hsize);
|
||||||
|
kunmap_atomic(virt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int cmp_hashes(const struct fsverity_info *vi,
|
||||||
|
const u8 *want_hash, const u8 *real_hash,
|
||||||
|
pgoff_t index, int level)
|
||||||
|
{
|
||||||
|
const unsigned int hsize = vi->tree_params.digest_size;
|
||||||
|
|
||||||
|
if (memcmp(want_hash, real_hash, hsize) == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fsverity_err(vi->inode,
|
||||||
|
"FILE CORRUPTED! index=%lu, level=%d, want_hash=%s:%*phN, real_hash=%s:%*phN",
|
||||||
|
index, level,
|
||||||
|
vi->tree_params.hash_alg->name, hsize, want_hash,
|
||||||
|
vi->tree_params.hash_alg->name, hsize, real_hash);
|
||||||
|
return -EBADMSG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verify a single data page against the file's Merkle tree.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* only ascend the tree until an already-verified page is seen, as indicated by
|
||||||
|
* the PageChecked bit being set; then verify the path to that page.
|
||||||
|
*
|
||||||
|
* This code currently only supports the case where the verity block size is
|
||||||
|
* 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,
|
||||||
|
struct ahash_request *req, struct page *data_page)
|
||||||
|
{
|
||||||
|
const struct merkle_tree_params *params = &vi->tree_params;
|
||||||
|
const unsigned int hsize = params->digest_size;
|
||||||
|
const pgoff_t index = data_page->index;
|
||||||
|
int level;
|
||||||
|
u8 _want_hash[FS_VERITY_MAX_DIGEST_SIZE];
|
||||||
|
const u8 *want_hash;
|
||||||
|
u8 real_hash[FS_VERITY_MAX_DIGEST_SIZE];
|
||||||
|
struct page *hpages[FS_VERITY_MAX_LEVELS];
|
||||||
|
unsigned int hoffsets[FS_VERITY_MAX_LEVELS];
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (WARN_ON_ONCE(!PageLocked(data_page) || PageUptodate(data_page)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
pr_debug_ratelimited("Verifying data page %lu...\n", index);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Starting at the leaf level, ascend the tree saving hash pages along
|
||||||
|
* the way until we find a verified hash page, indicated by PageChecked;
|
||||||
|
* or until we reach the root.
|
||||||
|
*/
|
||||||
|
for (level = 0; level < params->num_levels; level++) {
|
||||||
|
pgoff_t hindex;
|
||||||
|
unsigned int hoffset;
|
||||||
|
struct page *hpage;
|
||||||
|
|
||||||
|
hash_at_level(params, index, level, &hindex, &hoffset);
|
||||||
|
|
||||||
|
pr_debug_ratelimited("Level %d: hindex=%lu, hoffset=%u\n",
|
||||||
|
level, hindex, hoffset);
|
||||||
|
|
||||||
|
hpage = inode->i_sb->s_vop->read_merkle_tree_page(inode,
|
||||||
|
hindex);
|
||||||
|
if (IS_ERR(hpage)) {
|
||||||
|
err = PTR_ERR(hpage);
|
||||||
|
fsverity_err(inode,
|
||||||
|
"Error %d reading Merkle tree page %lu",
|
||||||
|
err, hindex);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PageChecked(hpage)) {
|
||||||
|
extract_hash(hpage, hoffset, hsize, _want_hash);
|
||||||
|
want_hash = _want_hash;
|
||||||
|
put_page(hpage);
|
||||||
|
pr_debug_ratelimited("Hash page already checked, want %s:%*phN\n",
|
||||||
|
params->hash_alg->name,
|
||||||
|
hsize, want_hash);
|
||||||
|
goto descend;
|
||||||
|
}
|
||||||
|
pr_debug_ratelimited("Hash page not yet checked\n");
|
||||||
|
hpages[level] = hpage;
|
||||||
|
hoffsets[level] = hoffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
want_hash = vi->root_hash;
|
||||||
|
pr_debug("Want root hash: %s:%*phN\n",
|
||||||
|
params->hash_alg->name, hsize, want_hash);
|
||||||
|
descend:
|
||||||
|
/* Descend the tree verifying hash pages */
|
||||||
|
for (; level > 0; level--) {
|
||||||
|
struct page *hpage = hpages[level - 1];
|
||||||
|
unsigned int hoffset = hoffsets[level - 1];
|
||||||
|
|
||||||
|
err = fsverity_hash_page(params, inode, req, hpage, real_hash);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
err = cmp_hashes(vi, want_hash, real_hash, index, level - 1);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
SetPageChecked(hpage);
|
||||||
|
extract_hash(hpage, hoffset, hsize, _want_hash);
|
||||||
|
want_hash = _want_hash;
|
||||||
|
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 */
|
||||||
|
err = fsverity_hash_page(params, inode, req, data_page, real_hash);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
err = cmp_hashes(vi, want_hash, real_hash, index, -1);
|
||||||
|
out:
|
||||||
|
for (; level > 0; level--)
|
||||||
|
put_page(hpages[level - 1]);
|
||||||
|
|
||||||
|
return err == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_verify_page() - verify a data page
|
||||||
|
*
|
||||||
|
* 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 struct fsverity_info *vi = inode->i_verity_info;
|
||||||
|
struct ahash_request *req;
|
||||||
|
bool valid;
|
||||||
|
|
||||||
|
req = ahash_request_alloc(vi->tree_params.hash_alg->tfm, GFP_NOFS);
|
||||||
|
if (unlikely(!req))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
valid = verify_page(inode, vi, req, page);
|
||||||
|
|
||||||
|
ahash_request_free(req);
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(fsverity_verify_page);
|
||||||
|
|
||||||
|
#ifdef CONFIG_BLOCK
|
||||||
|
/**
|
||||||
|
* fsverity_verify_bio() - verify a 'read' bio that has just completed
|
||||||
|
*
|
||||||
|
* Verify a set of pages that have just been read from a verity file. The pages
|
||||||
|
* must be pagecache pages that are still locked and not yet uptodate. Pages
|
||||||
|
* that fail verification are set to the Error state. Verification is skipped
|
||||||
|
* for pages already in the Error state, e.g. due to fscrypt decryption failure.
|
||||||
|
*
|
||||||
|
* This is a helper function for use by the ->readpages() method of filesystems
|
||||||
|
* that issue bios to read data directly into the page cache. Filesystems that
|
||||||
|
* populate the page cache without issuing bios (e.g. non block-based
|
||||||
|
* filesystems) must instead call fsverity_verify_page() directly on each page.
|
||||||
|
* All filesystems must also call fsverity_verify_page() on holes.
|
||||||
|
*/
|
||||||
|
void fsverity_verify_bio(struct bio *bio)
|
||||||
|
{
|
||||||
|
struct inode *inode = bio_first_page_all(bio)->mapping->host;
|
||||||
|
const struct fsverity_info *vi = inode->i_verity_info;
|
||||||
|
struct ahash_request *req;
|
||||||
|
struct bio_vec *bv;
|
||||||
|
struct bvec_iter_all iter_all;
|
||||||
|
|
||||||
|
req = ahash_request_alloc(vi->tree_params.hash_alg->tfm, GFP_NOFS);
|
||||||
|
if (unlikely(!req)) {
|
||||||
|
bio_for_each_segment_all(bv, bio, iter_all)
|
||||||
|
SetPageError(bv->bv_page);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bio_for_each_segment_all(bv, bio, iter_all) {
|
||||||
|
struct page *page = bv->bv_page;
|
||||||
|
|
||||||
|
if (!PageError(page) && !verify_page(inode, vi, req, page))
|
||||||
|
SetPageError(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
ahash_request_free(req);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(fsverity_verify_bio);
|
||||||
|
#endif /* CONFIG_BLOCK */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_enqueue_verify_work() - enqueue work on the fs-verity workqueue
|
||||||
|
*
|
||||||
|
* Enqueue verification work for asynchronous processing.
|
||||||
|
*/
|
||||||
|
void fsverity_enqueue_verify_work(struct work_struct *work)
|
||||||
|
{
|
||||||
|
queue_work(fsverity_read_workqueue, work);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(fsverity_enqueue_verify_work);
|
||||||
|
|
||||||
|
int __init fsverity_init_workqueue(void)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Use an unbound workqueue to allow bios to be verified in parallel
|
||||||
|
* even when they happen to complete on the same CPU. This sacrifices
|
||||||
|
* locality, but it's worthwhile since hashing is CPU-intensive.
|
||||||
|
*
|
||||||
|
* Also use a high-priority workqueue to prioritize verification work,
|
||||||
|
* which blocks reads from completing, over regular application tasks.
|
||||||
|
*/
|
||||||
|
fsverity_read_workqueue = alloc_workqueue("fsverity_read_queue",
|
||||||
|
WQ_UNBOUND | WQ_HIGHPRI,
|
||||||
|
num_online_cpus());
|
||||||
|
if (!fsverity_read_workqueue)
|
||||||
|
return -ENOMEM;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __init fsverity_exit_workqueue(void)
|
||||||
|
{
|
||||||
|
destroy_workqueue(fsverity_read_workqueue);
|
||||||
|
fsverity_read_workqueue = NULL;
|
||||||
|
}
|
@ -64,6 +64,8 @@ struct workqueue_struct;
|
|||||||
struct iov_iter;
|
struct iov_iter;
|
||||||
struct fscrypt_info;
|
struct fscrypt_info;
|
||||||
struct fscrypt_operations;
|
struct fscrypt_operations;
|
||||||
|
struct fsverity_info;
|
||||||
|
struct fsverity_operations;
|
||||||
struct fs_context;
|
struct fs_context;
|
||||||
struct fs_parameter_description;
|
struct fs_parameter_description;
|
||||||
|
|
||||||
@ -723,6 +725,10 @@ struct inode {
|
|||||||
struct fscrypt_info *i_crypt_info;
|
struct fscrypt_info *i_crypt_info;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_FS_VERITY
|
||||||
|
struct fsverity_info *i_verity_info;
|
||||||
|
#endif
|
||||||
|
|
||||||
void *i_private; /* fs or device private pointer */
|
void *i_private; /* fs or device private pointer */
|
||||||
} __randomize_layout;
|
} __randomize_layout;
|
||||||
|
|
||||||
@ -1428,6 +1434,9 @@ struct super_block {
|
|||||||
#ifdef CONFIG_FS_ENCRYPTION
|
#ifdef CONFIG_FS_ENCRYPTION
|
||||||
const struct fscrypt_operations *s_cop;
|
const struct fscrypt_operations *s_cop;
|
||||||
struct key *s_master_keys; /* master crypto keys in use */
|
struct key *s_master_keys; /* master crypto keys in use */
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_FS_VERITY
|
||||||
|
const struct fsverity_operations *s_vop;
|
||||||
#endif
|
#endif
|
||||||
struct hlist_bl_head s_roots; /* alternate root dentries for NFS */
|
struct hlist_bl_head s_roots; /* alternate root dentries for NFS */
|
||||||
struct list_head s_mounts; /* list of mounts; _not_ for fs use */
|
struct list_head s_mounts; /* list of mounts; _not_ for fs use */
|
||||||
@ -1966,6 +1975,7 @@ struct super_operations {
|
|||||||
#endif
|
#endif
|
||||||
#define S_ENCRYPTED 16384 /* Encrypted file (using fs/crypto/) */
|
#define S_ENCRYPTED 16384 /* Encrypted file (using fs/crypto/) */
|
||||||
#define S_CASEFOLD 32768 /* Casefolded file */
|
#define S_CASEFOLD 32768 /* Casefolded file */
|
||||||
|
#define S_VERITY 65536 /* Verity file (using fs/verity/) */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note that nosuid etc flags are inode-specific: setting some file-system
|
* Note that nosuid etc flags are inode-specific: setting some file-system
|
||||||
@ -2007,6 +2017,7 @@ static inline bool sb_rdonly(const struct super_block *sb) { return sb->s_flags
|
|||||||
#define IS_DAX(inode) ((inode)->i_flags & S_DAX)
|
#define IS_DAX(inode) ((inode)->i_flags & S_DAX)
|
||||||
#define IS_ENCRYPTED(inode) ((inode)->i_flags & S_ENCRYPTED)
|
#define IS_ENCRYPTED(inode) ((inode)->i_flags & S_ENCRYPTED)
|
||||||
#define IS_CASEFOLDED(inode) ((inode)->i_flags & S_CASEFOLD)
|
#define IS_CASEFOLDED(inode) ((inode)->i_flags & S_CASEFOLD)
|
||||||
|
#define IS_VERITY(inode) ((inode)->i_flags & S_VERITY)
|
||||||
|
|
||||||
#define IS_WHITEOUT(inode) (S_ISCHR(inode->i_mode) && \
|
#define IS_WHITEOUT(inode) (S_ISCHR(inode->i_mode) && \
|
||||||
(inode)->i_rdev == WHITEOUT_DEV)
|
(inode)->i_rdev == WHITEOUT_DEV)
|
||||||
|
211
include/linux/fsverity.h
Normal file
211
include/linux/fsverity.h
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* fs-verity: read-only file-based authenticity protection
|
||||||
|
*
|
||||||
|
* This header declares the interface between the fs/verity/ support layer and
|
||||||
|
* filesystems that support fs-verity.
|
||||||
|
*
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_FSVERITY_H
|
||||||
|
#define _LINUX_FSVERITY_H
|
||||||
|
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <uapi/linux/fsverity.h>
|
||||||
|
|
||||||
|
/* Verity operations for filesystems */
|
||||||
|
struct fsverity_operations {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin enabling verity on the given file.
|
||||||
|
*
|
||||||
|
* @filp: a readonly file descriptor for the file
|
||||||
|
*
|
||||||
|
* The filesystem must do any needed filesystem-specific preparations
|
||||||
|
* for enabling verity, e.g. evicting inline data. It also must return
|
||||||
|
* -EBUSY if verity is already being enabled on the given file.
|
||||||
|
*
|
||||||
|
* i_rwsem is held for write.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, -errno on failure
|
||||||
|
*/
|
||||||
|
int (*begin_enable_verity)(struct file *filp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End enabling verity on the given file.
|
||||||
|
*
|
||||||
|
* @filp: a readonly file descriptor for the file
|
||||||
|
* @desc: the verity descriptor to write, or NULL on failure
|
||||||
|
* @desc_size: size of verity descriptor, or 0 on failure
|
||||||
|
* @merkle_tree_size: total bytes the Merkle tree took up
|
||||||
|
*
|
||||||
|
* If desc == NULL, then enabling verity failed and the filesystem only
|
||||||
|
* must do any necessary cleanups. Else, it must also store the given
|
||||||
|
* verity descriptor to a fs-specific location associated with the inode
|
||||||
|
* and do any fs-specific actions needed to mark the inode as a verity
|
||||||
|
* inode, e.g. setting a bit in the on-disk inode. The filesystem is
|
||||||
|
* also responsible for setting the S_VERITY flag in the VFS inode.
|
||||||
|
*
|
||||||
|
* i_rwsem is held for write, but it may have been dropped between
|
||||||
|
* ->begin_enable_verity() and ->end_enable_verity().
|
||||||
|
*
|
||||||
|
* Return: 0 on success, -errno on failure
|
||||||
|
*/
|
||||||
|
int (*end_enable_verity)(struct file *filp, const void *desc,
|
||||||
|
size_t desc_size, u64 merkle_tree_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the verity descriptor of the given inode.
|
||||||
|
*
|
||||||
|
* @inode: an inode with the S_VERITY flag set
|
||||||
|
* @buf: buffer in which to place the verity descriptor
|
||||||
|
* @bufsize: size of @buf, or 0 to retrieve the size only
|
||||||
|
*
|
||||||
|
* If bufsize == 0, then the size of the verity descriptor is returned.
|
||||||
|
* Otherwise the verity descriptor is written to 'buf' and its actual
|
||||||
|
* size is returned; -ERANGE is returned if it's too large. This may be
|
||||||
|
* called by multiple processes concurrently on the same inode.
|
||||||
|
*
|
||||||
|
* Return: the size on success, -errno on failure
|
||||||
|
*/
|
||||||
|
int (*get_verity_descriptor)(struct inode *inode, void *buf,
|
||||||
|
size_t bufsize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a Merkle tree page of the given inode.
|
||||||
|
*
|
||||||
|
* @inode: the inode
|
||||||
|
* @index: 0-based index of the page within the Merkle tree
|
||||||
|
*
|
||||||
|
* This can be called at any time on an open verity file, as well as
|
||||||
|
* between ->begin_enable_verity() and ->end_enable_verity(). It may be
|
||||||
|
* called by multiple processes concurrently, even with the same page.
|
||||||
|
*
|
||||||
|
* Note that this must retrieve a *page*, not necessarily a *block*.
|
||||||
|
*
|
||||||
|
* Return: the page on success, ERR_PTR() on failure
|
||||||
|
*/
|
||||||
|
struct page *(*read_merkle_tree_page)(struct inode *inode,
|
||||||
|
pgoff_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a Merkle tree block to the given inode.
|
||||||
|
*
|
||||||
|
* @inode: the inode for which the Merkle tree is being built
|
||||||
|
* @buf: block to write
|
||||||
|
* @index: 0-based index of the block within the Merkle tree
|
||||||
|
* @log_blocksize: log base 2 of the Merkle tree block size
|
||||||
|
*
|
||||||
|
* This is only called between ->begin_enable_verity() and
|
||||||
|
* ->end_enable_verity().
|
||||||
|
*
|
||||||
|
* Return: 0 on success, -errno on failure
|
||||||
|
*/
|
||||||
|
int (*write_merkle_tree_block)(struct inode *inode, const void *buf,
|
||||||
|
u64 index, int log_blocksize);
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef CONFIG_FS_VERITY
|
||||||
|
|
||||||
|
static inline struct fsverity_info *fsverity_get_info(const struct inode *inode)
|
||||||
|
{
|
||||||
|
/* pairs with the cmpxchg() in fsverity_set_info() */
|
||||||
|
return READ_ONCE(inode->i_verity_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* enable.c */
|
||||||
|
|
||||||
|
extern int fsverity_ioctl_enable(struct file *filp, const void __user *arg);
|
||||||
|
|
||||||
|
/* measure.c */
|
||||||
|
|
||||||
|
extern int fsverity_ioctl_measure(struct file *filp, void __user *arg);
|
||||||
|
|
||||||
|
/* open.c */
|
||||||
|
|
||||||
|
extern int fsverity_file_open(struct inode *inode, struct file *filp);
|
||||||
|
extern int fsverity_prepare_setattr(struct dentry *dentry, struct iattr *attr);
|
||||||
|
extern void fsverity_cleanup_inode(struct inode *inode);
|
||||||
|
|
||||||
|
/* verify.c */
|
||||||
|
|
||||||
|
extern bool fsverity_verify_page(struct page *page);
|
||||||
|
extern void fsverity_verify_bio(struct bio *bio);
|
||||||
|
extern void fsverity_enqueue_verify_work(struct work_struct *work);
|
||||||
|
|
||||||
|
#else /* !CONFIG_FS_VERITY */
|
||||||
|
|
||||||
|
static inline struct fsverity_info *fsverity_get_info(const struct inode *inode)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* enable.c */
|
||||||
|
|
||||||
|
static inline int fsverity_ioctl_enable(struct file *filp,
|
||||||
|
const void __user *arg)
|
||||||
|
{
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* measure.c */
|
||||||
|
|
||||||
|
static inline int fsverity_ioctl_measure(struct file *filp, void __user *arg)
|
||||||
|
{
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* open.c */
|
||||||
|
|
||||||
|
static inline int fsverity_file_open(struct inode *inode, struct file *filp)
|
||||||
|
{
|
||||||
|
return IS_VERITY(inode) ? -EOPNOTSUPP : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int fsverity_prepare_setattr(struct dentry *dentry,
|
||||||
|
struct iattr *attr)
|
||||||
|
{
|
||||||
|
return IS_VERITY(d_inode(dentry)) ? -EOPNOTSUPP : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void fsverity_cleanup_inode(struct inode *inode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/* verify.c */
|
||||||
|
|
||||||
|
static inline bool fsverity_verify_page(struct page *page)
|
||||||
|
{
|
||||||
|
WARN_ON(1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void fsverity_verify_bio(struct bio *bio)
|
||||||
|
{
|
||||||
|
WARN_ON(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void fsverity_enqueue_verify_work(struct work_struct *work)
|
||||||
|
{
|
||||||
|
WARN_ON(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* !CONFIG_FS_VERITY */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_active() - do reads from the inode need to go through fs-verity?
|
||||||
|
*
|
||||||
|
* This checks whether ->i_verity_info has been set.
|
||||||
|
*
|
||||||
|
* Filesystems call this from ->readpages() to check whether the pages need to
|
||||||
|
* be verified or not. Don't use IS_VERITY() for this purpose; it's subject to
|
||||||
|
* a race condition where the file is being read concurrently with
|
||||||
|
* FS_IOC_ENABLE_VERITY completing. (S_VERITY is set before ->i_verity_info.)
|
||||||
|
*/
|
||||||
|
static inline bool fsverity_active(const struct inode *inode)
|
||||||
|
{
|
||||||
|
return fsverity_get_info(inode) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _LINUX_FSVERITY_H */
|
@ -258,6 +258,7 @@ struct fsxattr {
|
|||||||
#define FS_TOPDIR_FL 0x00020000 /* Top of directory hierarchies*/
|
#define FS_TOPDIR_FL 0x00020000 /* Top of directory hierarchies*/
|
||||||
#define FS_HUGE_FILE_FL 0x00040000 /* Reserved for ext4 */
|
#define FS_HUGE_FILE_FL 0x00040000 /* Reserved for ext4 */
|
||||||
#define FS_EXTENT_FL 0x00080000 /* Extents */
|
#define FS_EXTENT_FL 0x00080000 /* Extents */
|
||||||
|
#define FS_VERITY_FL 0x00100000 /* Verity protected inode */
|
||||||
#define FS_EA_INODE_FL 0x00200000 /* Inode used for large EA */
|
#define FS_EA_INODE_FL 0x00200000 /* Inode used for large EA */
|
||||||
#define FS_EOFBLOCKS_FL 0x00400000 /* Reserved for ext4 */
|
#define FS_EOFBLOCKS_FL 0x00400000 /* Reserved for ext4 */
|
||||||
#define FS_NOCOW_FL 0x00800000 /* Do not cow file */
|
#define FS_NOCOW_FL 0x00800000 /* Do not cow file */
|
||||||
|
40
include/uapi/linux/fsverity.h
Normal file
40
include/uapi/linux/fsverity.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||||
|
/*
|
||||||
|
* fs-verity user API
|
||||||
|
*
|
||||||
|
* These ioctls can be used on filesystems that support fs-verity. See the
|
||||||
|
* "User API" section of Documentation/filesystems/fsverity.rst.
|
||||||
|
*
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
*/
|
||||||
|
#ifndef _UAPI_LINUX_FSVERITY_H
|
||||||
|
#define _UAPI_LINUX_FSVERITY_H
|
||||||
|
|
||||||
|
#include <linux/ioctl.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
#define FS_VERITY_HASH_ALG_SHA256 1
|
||||||
|
#define FS_VERITY_HASH_ALG_SHA512 2
|
||||||
|
|
||||||
|
struct fsverity_enable_arg {
|
||||||
|
__u32 version;
|
||||||
|
__u32 hash_algorithm;
|
||||||
|
__u32 block_size;
|
||||||
|
__u32 salt_size;
|
||||||
|
__u64 salt_ptr;
|
||||||
|
__u32 sig_size;
|
||||||
|
__u32 __reserved1;
|
||||||
|
__u64 sig_ptr;
|
||||||
|
__u64 __reserved2[11];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fsverity_digest {
|
||||||
|
__u16 digest_algorithm;
|
||||||
|
__u16 digest_size; /* input/output */
|
||||||
|
__u8 digest[];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define FS_IOC_ENABLE_VERITY _IOW('f', 133, struct fsverity_enable_arg)
|
||||||
|
#define FS_IOC_MEASURE_VERITY _IOWR('f', 134, struct fsverity_digest)
|
||||||
|
|
||||||
|
#endif /* _UAPI_LINUX_FSVERITY_H */
|
Loading…
Reference in New Issue
Block a user