Yet another try for fs-verity
Fs‑verity works by associating a set of hashes with a file; the hash values can be used to check that the contents of the file have not been changed. In current implementations, the hashes are stored in a Merkle tree, which allows for quick verification when the file is accessed. The tree itself is hashed and signed, so modifications to the hash values can also be detected (and access to the file blocked). The intended use case is to protect critical Android packages even when an attacker is able to make changes to the local storage device.
Previous versions of the fs‑verity patches ran aground over objections to how the API worked. To protect a file, user space would need to generate and sign a Merkle tree, then append that tree to the file itself, aligned to the beginning of a filesystem block. After an ioctl() call, the kernel would hide the tree, making the file appear to be shorter than it really was, while using the tree to verify the file's contents. This mechanism was seen as being incompatible with how some filesystems manage space at the end of files; developers also complained that it exposed too much about how fs‑verity was implemented internally. In the end, an attempt to merge this code for 5.0 was not acted upon, and fs‑verity remained outside of the mainline.
The new patch set addresses these concerns by moving the generation of the Merkle tree into the kernel and hiding the details of where this tree is stored. To enable fs‑verity protection for a file, a user-space application starts by opening the file in question. Despite the fact that this operation changes the file (by adding the protection and making the file read-only), this file descriptor must be opened for read access only. Then, the new FS_IOC_ENABLE_VERITY ioctl() command is invoked on this file; the application passes in a structure that looks like this:
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]; };
The version field must be set to one; it is there to allow different fs‑verity implementations in the future. Similarly, the reserved fields must all be set to zero. hash_algorithm tells the kernel which algorithm to use for hashing the file's blocks; the only supported values at the moment are FS_VERITY_HASH_ALG_SHA256 and FS_VERITY_HASH_ALG_SHA512. The block size for the hash is set in block_size; it must match the filesystem block size. If salt_size and salt_ptr are set, they provide a "salt" value that is prepended to each block prior to hashing. A digital signature for the hash of the file can optionally be added using sig_ptr and sig_size; more on that shortly.
This ioctl() call will read through the entire file, generating the Merkle tree and storing it wherever the filesystem thinks is best. If the file is large, this operation can take some time; it can be interrupted with a fatal signal, leaving the file unchanged. Enabling fs‑verity will fail if there are any open, write-enabled file descriptors for the target file.
After the operation succeeds, the file will be in the fs‑verity mode. Opens for write access will fail, even if the file's permission bits would otherwise allow writing. Some metadata can still be changed, though, and the file can be renamed or deleted. Any attempt to read from the file will fail (with EIO) if the data of interest does not match the stored hash. If user space is counting on fs‑verity protection, though, it should, after opening the file, verify that this protection is present with the FS_IOC_MEASURE_VERITY ioctl() call, which takes a pointer to this structure:
struct fsverity_digest { __u16 digest_algorithm; __u16 digest_size; /* input/output */ __u8 digest[]; };
If the file is protected with fs‑verity, this structure will be filled in with summary hash information.
User space can use that information to verify that the digest data matches expectations; without that test, an attacker could substitute a new file with hostile contents and a matching Merkle tree. Alternatively, this digest can be signed and the kernel will verify that it matches at access time. What must actually be signed is this structure:
struct fsverity_signed_digest { char magic[8]; /* must be "FSVerity" */ __le16 digest_algorithm; __le16 digest_size; __u8 digest[]; };
The digest information can be obtained from the kernel using the FS_IOC_MEASURE_VERITY ioctl() described just above. So one way to add a signature to an fs‑verity file would be to create the file once, enable fs‑verity on the file without a signature, obtain the digest information, then create and enable the file a second time with the signature data. In practice, files to be protected this way (such as Android package files) will probably be shipped with the associated signature data, so this two-step process will not be necessary on the target systems.
The final piece for signature verification is the provision of a public key to verify against. The fs‑verity subsystem creates a new keyring (called .fs‑verity); a suitably privileged user can add certificates to this keyring for use in file verification. The signing key, of course, should not be on the target system at all; assuming that the attacker cannot obtain that key by other means, verification against the public key should provide assurance that the file has not been modified.
The ext4 and F2FS filesystems are supported in the current patch set. See the extensive documentation file provided for the patch set for a lot more details on how it all works. Some kernel features are added without sufficient documentation; fs‑verity does not look like it will be one of those.
Previous versions of the patch set have generated a lot of (sometimes
heated) discussion. This time, the response has been silent, prompting
Eric Biggers (the author of this work) to ask if
anybody has any comments. Unless somebody shows up with objections, the
logical conclusion is that the biggest concerns have been addressed and
that fs‑verity may be on track for merging into the 5.3 kernel.
Index entries for this article | |
---|---|
Kernel | Filesystems/fs-verity |
Kernel | Security/Integrity verification |
Security | Integrity management |
Posted Jun 3, 2019 22:07 UTC (Mon)
by bjartur (guest, #67801)
[Link] (1 responses)
Posted Jun 3, 2019 22:14 UTC (Mon)
by corbet (editor, #1)
[Link]
Posted Jun 3, 2019 23:07 UTC (Mon)
by Cyberax (✭ supporter ✭, #52523)
[Link] (3 responses)
Posted Jun 4, 2019 3:09 UTC (Tue)
by ebiggers (subscriber, #130760)
[Link] (2 responses)
Posted Jun 4, 2019 3:56 UTC (Tue)
by Cyberax (✭ supporter ✭, #52523)
[Link] (1 responses)
Posted Jun 4, 2019 20:32 UTC (Tue)
by ebiggers (subscriber, #130760)
[Link]
Posted Jun 3, 2019 23:10 UTC (Mon)
by skissane (subscriber, #38675)
[Link] (1 responses)
I can see from the documentation that they didn't use extended attributes in ext4 or FFS (which in some ways resemble forks/streams) because neither filesystem's extended attribute implementation is scalable to large attribute values, which Merkle trees require, and it is less work to put hidden data at the end of the file than to make the extended attribute implementations scalable.
Since the storage mechanism is per-filesystem, some other filesystem which has scalable extended attributes (or forks or streams) could use that instead.
Posted Jun 4, 2019 10:44 UTC (Tue)
by grawity (subscriber, #80596)
[Link]
Posted Jun 3, 2019 23:53 UTC (Mon)
by jhoblitt (subscriber, #77733)
[Link] (3 responses)
Posted Jun 4, 2019 0:34 UTC (Tue)
by skissane (subscriber, #38675)
[Link] (2 responses)
The documentation https://lwn.net/ml/linux-fsdevel/20190523161811.6259-2-eb... explains why this wasn't done:
All the above flaws in extended attribute support probably can and should be fixed. But it is a lot of work, especially since it is a problem across multiple filesystems. Not using extended attributes for this feature is a way to deliver this feature without being blocked by the need to fix those above flaws.
I guess another option would be to have an extended attribute which indicates the presence of a Merkle tree, but doesn't actually store the Merkle tree data. That would meet your requirement (for an easy way for user space to detect the feature is enabled on any given file) without running up against the above extended attribute limitations.
> It would also be nice, regardless of the config interface, if a protected file always showed the immutable bit as true
I agree with that.
Posted Jun 4, 2019 2:23 UTC (Tue)
by jhoblitt (subscriber, #77733)
[Link] (1 responses)
Posted Jun 4, 2019 2:43 UTC (Tue)
by ebiggers (subscriber, #130760)
[Link]
We're still planning to expose the verity bit through FS_IOC_GETFLAGS and possibly statx() too, just like the encrypt bit. So detecting verity files will still be straightforward; you don't need xattrs for this.
We aren't using the existing "immutable" bit because it already has specific semantics that include much more than just file contents immutability, e.g. it also prevents the file from being deleted, renamed, linked to, or have its owner or mode changed. So reusing the immutable bit to mean "fs-verity enabled" would not be appropriate.
Posted Jun 6, 2019 5:39 UTC (Thu)
by draco (subscriber, #1792)
[Link] (1 responses)
Will /bin/ls be tweaked in some way to notify users that the fs-verity mode is set? Currently SELinux contexts add '.' and ACLs add '+' to the end of the permissions string. To my mind, something similar should apply. Perhaps '#' (since the file is hashed)?
Posted Mar 7, 2025 13:45 UTC (Fri)
by cybertom (guest, #176384)
[Link]
Yet another try for fs-verity
Yes because the call will fall in that case.
Yet another try for fs-verity
Yet another try for fs-verity
Yet another try for fs-verity
Yet another try for fs-verity
Yet another try for fs-verity
Yet another try for fs-verity
Yet another try for fs-verity
Yet another try for fs-verity
Yet another try for fs-verity
1) many file systems impose a small limit on the extended attribute value size (e.g. 4096 bytes on F2FS), while these Merkle trees can be very big
2) other file systems support very big extended attribute values in theory, but perform very poorly with them (e.g. ext4)
3) some filesystems (e.g. ext4) support file encryption. Since the Merkle tree is based on the plaintext not the ciphertext, in that case the Merkle tree needs to be encrypted as well. However, while ext4 supports file encryption, it doesn't support the encryption of the extended attributes
Yet another try for fs-verity
Yet another try for fs-verity
Yet another try for fs-verity
Yet another try for fs-verity