From patchwork Fri Aug 24 16:16:33 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Eric Biggers X-Patchwork-Id: 10575605 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id DCB49174C for ; Fri, 24 Aug 2018 16:24:53 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CD9B12CA6B for ; Fri, 24 Aug 2018 16:24:53 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id C97732CA66; Fri, 24 Aug 2018 16:24:53 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 95D022CA6B for ; Fri, 24 Aug 2018 16:24:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727274AbeHXT6t (ORCPT ); Fri, 24 Aug 2018 15:58:49 -0400 Received: from mail.kernel.org ([198.145.29.99]:41390 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726484AbeHXT6s (ORCPT ); Fri, 24 Aug 2018 15:58:48 -0400 Received: from sol.localdomain (c-67-185-97-198.hsd1.wa.comcast.net [67.185.97.198]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPSA id EFD9321557; Fri, 24 Aug 2018 16:23:25 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=default; t=1535127806; bh=/OeMv8VCHiAHxXOS1WerpAPO/cnB7y07qaiL+hgMUg4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tnskCQHUuBrP6AdoFMOGOzR1GUVlKvQ0JtYgrNZYNiBZBB3DWO+N3mAcPgrblD7AE YI/mLUj5sE/8PX7+HvdGmuUFeeZ6jqpPvp1ry2kAFUfvad2PRsSyjGH6D9n8ye1H1W loVoEb7mOXAjV5dt6lZTi4kP1W7GOxCy1qAMmhpw= From: Eric Biggers To: linux-fsdevel@vger.kernel.org, linux-ext4@vger.kernel.org, linux-f2fs-devel@lists.sourceforge.net Cc: linux-integrity@vger.kernel.org, linux-fscrypt@vger.kernel.org, linux-kernel@vger.kernel.org, Mimi Zohar , Dmitry Kasatkin , Michael Halcrow , Victor Hsieh Subject: [RFC PATCH 01/10] fs-verity: add setup code, UAPI, and Kconfig Date: Fri, 24 Aug 2018 09:16:33 -0700 Message-Id: <20180824161642.1144-2-ebiggers@kernel.org> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180824161642.1144-1-ebiggers@kernel.org> References: <20180824161642.1144-1-ebiggers@kernel.org> Sender: linux-fscrypt-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fscrypt@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Eric Biggers fs-verity is a filesystem feature that provides efficient, transparent integrity verification and authentication of read-only files. It uses a dm-verity like mechanism at the file level: a Merkle tree hidden past the end of the file is used to verify any block in the file in log(filesize) time. It is implemented mainly by helper functions in fs/verity/ that will be shared by multiple filesystems. Essentially, fs-verity reports a file's hash in constant time, but reads that would violate that hash fail at runtime. This is useful when only a portion of the file is actually accessed, as only the accessed portion has to be hashed, and the latency to the first read is much reduced over a full file hash. On top of this hashing mechanism, auditing or authentication policies can be implemented to log or verify file hashes. Note that in general, fs-verity is *not* a replacement for IMA. fs-verity is a lower-level feature, primarily a way to hash a file; whereas IMA deals more with higher-level policy logic, like defining which files are "measured" and what to do with those measurements. We plan for IMA to support fs-verity measurements as an alternative to the traditional full file hash. Still, some users find fs-verity useful by itself, so it's also usable without IMA in simple cases, e.g. in cases where just retrieving the file measurement via an ioctl is enough. A structure containing the properties of the Merkle tree -- such as the hash algorithm used, the block size, and the root hash -- is also stored on-disk, following the Merkle tree. The actual file measurement hash that fs-verity reports is the hash of this structure. All fs-verity metadata is written by userspace; the kernel only reads it. Extended attributes aren't used because the Merkle tree may be much larger than XATTR_SIZE_MAX, we want the hash pages to be cached in the page cache as usual, and in the case of fs-verity combined with fscrypt we want the metadata to be encrypted to avoid leaking plaintext hashes. The fs-verity metadata is hidden from userspace by overriding the i_size of the in-memory VFS inode; ext4 additionally will override the on-disk i_size in order to make verity a RO_COMPAT filesystem feature. This initial patch only adds the fs-verity Kconfig option, UAPI, and setup code, e.g. the ->open() hook that parses the fs-verity descriptor. The actual ->readpages() data verification, the ioctls, ext4 and f2fs support, and other functionality comes in later patches. Signed-off-by: Eric Biggers --- fs/Kconfig | 2 + fs/Makefile | 1 + fs/verity/Kconfig | 36 ++ fs/verity/Makefile | 3 + fs/verity/fsverity_private.h | 99 ++++ fs/verity/hash_algs.c | 106 +++++ fs/verity/setup.c | 846 ++++++++++++++++++++++++++++++++++ include/linux/fs.h | 9 + include/linux/fsverity.h | 62 +++ include/uapi/linux/fsverity.h | 86 ++++ 10 files changed, 1250 insertions(+) create mode 100644 fs/verity/Kconfig create mode 100644 fs/verity/Makefile create mode 100644 fs/verity/fsverity_private.h create mode 100644 fs/verity/hash_algs.c create mode 100644 fs/verity/setup.c create mode 100644 include/linux/fsverity.h create mode 100644 include/uapi/linux/fsverity.h diff --git a/fs/Kconfig b/fs/Kconfig index ac474a61be379..ddadc4e999429 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -105,6 +105,8 @@ config MANDATORY_FILE_LOCKING source "fs/crypto/Kconfig" +source "fs/verity/Kconfig" + source "fs/notify/Kconfig" source "fs/quota/Kconfig" diff --git a/fs/Makefile b/fs/Makefile index 293733f61594b..10b37f651ffde 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_USERFAULTFD) += userfaultfd.o obj-$(CONFIG_AIO) += aio.o obj-$(CONFIG_FS_DAX) += dax.o obj-$(CONFIG_FS_ENCRYPTION) += crypto/ +obj-$(CONFIG_FS_VERITY) += verity/ obj-$(CONFIG_FILE_LOCKING) += locks.o obj-$(CONFIG_COMPAT) += compat.o compat_ioctl.o obj-$(CONFIG_BINFMT_AOUT) += binfmt_aout.o diff --git a/fs/verity/Kconfig b/fs/verity/Kconfig new file mode 100644 index 0000000000000..308d733a9401b --- /dev/null +++ b/fs/verity/Kconfig @@ -0,0 +1,36 @@ +config FS_VERITY + tristate "FS Verity (file-based integrity/authentication)" + depends on BLOCK + 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, userspace can append a Merkle tree (hash tree) to + a file, then enable fs-verity on 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. diff --git a/fs/verity/Makefile b/fs/verity/Makefile new file mode 100644 index 0000000000000..39e123805c827 --- /dev/null +++ b/fs/verity/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_FS_VERITY) += fsverity.o + +fsverity-y := hash_algs.o setup.o diff --git a/fs/verity/fsverity_private.h b/fs/verity/fsverity_private.h new file mode 100644 index 0000000000000..a18ff645695f4 --- /dev/null +++ b/fs/verity/fsverity_private.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * fs-verity: read-only file-based integrity/authentication + * + * Copyright (C) 2018 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 +#define __FS_HAS_VERITY 1 +#include + +/* + * Maximum depth of the Merkle tree. Up to 64 levels are theoretically possible + * with a very small block size, but we'd like to limit stack usage during + * verification, and in practice this is plenty. E.g., with SHA-256 and 4K + * blocks, a file with size UINT64_MAX bytes needs just 8 levels. + */ +#define FS_VERITY_MAX_LEVELS 16 + +/* + * Largest digest size among all hash algorithms supported by fs-verity. This + * can be increased if needed. + */ +#define FS_VERITY_MAX_DIGEST_SIZE SHA256_DIGEST_SIZE + +/* A hash algorithm supported by fs-verity */ +struct fsverity_hash_alg { + struct crypto_ahash *tfm; /* allocated on demand */ + const char *name; + unsigned int digest_size; + bool cryptographic; +}; + +/** + * 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 caches various values from the verity + * metadata, such as the tree topology and the root hash, which are needed to + * efficiently verify data read from the file. Once created, it remains until + * the inode is evicted. + * + * (The tree pages themselves are not cached here, though they may be cached in + * the inode's page cache.) + */ +struct fsverity_info { + const struct fsverity_hash_alg *hash_alg; /* hash algorithm */ + u8 block_bits; /* log2(block size) */ + u8 log_arity; /* log2(hashes per hash block) */ + u8 depth; /* depth of the Merkle tree */ + u8 *hashstate; /* salted initial hash state */ + u64 data_i_size; /* original file size */ + u64 full_i_size; /* full file size including metadata */ + u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE]; /* Merkle tree root hash */ + u8 measurement[FS_VERITY_MAX_DIGEST_SIZE]; /* file measurement */ + bool have_root_hash; /* have root hash from disk? */ + + /* Starting blocks for each tree level. 'depth-1' is the root level. */ + u64 hash_lvl_region_idx[FS_VERITY_MAX_LEVELS]; +}; + +/* hash_algs.c */ +extern struct fsverity_hash_alg fsverity_hash_algs[]; +const struct fsverity_hash_alg *fsverity_get_hash_alg(unsigned int num); +void __init fsverity_check_hash_algs(void); +void __exit fsverity_exit_hash_algs(void); + +/* setup.c */ +struct fsverity_info *create_fsverity_info(struct inode *inode, bool enabling); +void free_fsverity_info(struct fsverity_info *vi); + +static inline struct fsverity_info *get_fsverity_info(const struct inode *inode) +{ + /* pairs with cmpxchg_release() in set_fsverity_info() */ + return smp_load_acquire(&inode->i_verity_info); +} + +static inline bool set_fsverity_info(struct inode *inode, + struct fsverity_info *vi) +{ + /* pairs with smp_load_acquire() in get_fsverity_info() */ + if (cmpxchg_release(&inode->i_verity_info, NULL, vi) != NULL) + return false; + + /* Set the in-memory i_size to the data size */ + i_size_write(inode, vi->data_i_size); + return true; +} + +#endif /* _FSVERITY_PRIVATE_H */ diff --git a/fs/verity/hash_algs.c b/fs/verity/hash_algs.c new file mode 100644 index 0000000000000..424a26ee2f3c2 --- /dev/null +++ b/fs/verity/hash_algs.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * fs/verity/hash_algs.c: fs-verity hash algorithm management + * + * Copyright (C) 2018 Google LLC + * + * Written by Eric Biggers. + */ + +#include "fsverity_private.h" + +#include + +/* The list of hash algorithms supported by fs-verity */ +struct fsverity_hash_alg fsverity_hash_algs[] = { + [FS_VERITY_ALG_SHA256] = { + .name = "sha256", + .digest_size = 32, + .cryptographic = true, + }, +}; + +/* + * Translate the given fs-verity hash algorithm number into a struct describing + * the algorithm, and ensure it has a hash transform ready to go. The hash + * transforms are allocated on-demand firstly to not waste resources when they + * aren't needed, and secondly because the fs-verity module may be loaded + * earlier than the needed crypto modules. + */ +const struct fsverity_hash_alg *fsverity_get_hash_alg(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].digest_size) { + pr_warn("Unknown hash algorithm: %u\n", num); + return ERR_PTR(-EINVAL); + } + alg = &fsverity_hash_algs[num]; +retry: + /* pairs with cmpxchg_release() below */ + tfm = smp_load_acquire(&alg->tfm); + if (tfm) + 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) + pr_warn("Algorithm %u (%s) is unavailable\n", + num, alg->name); + else + pr_warn("Error allocating algorithm %u (%s): %ld\n", + num, 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; + + pr_info("%s using implementation \"%s\"\n", alg->name, + crypto_hash_alg_common(tfm)->base.cra_driver_name); + + /* pairs with smp_load_acquire() above */ + if (cmpxchg_release(&alg->tfm, NULL, tfm) != NULL) { + crypto_free_ahash(tfm); + goto retry; + } + + return alg; + +err_free_tfm: + crypto_free_ahash(tfm); + return ERR_PTR(err); +} + +void __init fsverity_check_hash_algs(void) +{ + int i; + + /* + * Sanity check the digest sizes (could be a build-time check, but + * they're in an array) + */ + for (i = 0; i < ARRAY_SIZE(fsverity_hash_algs); i++) { + struct fsverity_hash_alg *alg = &fsverity_hash_algs[i]; + + if (!alg->digest_size) + continue; + BUG_ON(alg->digest_size > FS_VERITY_MAX_DIGEST_SIZE); + BUG_ON(!is_power_of_2(alg->digest_size)); + } +} + +void __exit fsverity_exit_hash_algs(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(fsverity_hash_algs); i++) + crypto_free_ahash(fsverity_hash_algs[i].tfm); +} diff --git a/fs/verity/setup.c b/fs/verity/setup.c new file mode 100644 index 0000000000000..e675c52898d5b --- /dev/null +++ b/fs/verity/setup.c @@ -0,0 +1,846 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * fs/verity/setup.c: fs-verity module initialization and descriptor parsing + * + * Copyright (C) 2018 Google LLC + * + * Originally written by Jaegeuk Kim and Michael Halcrow; + * heavily rewritten by Eric Biggers. + */ + +#include "fsverity_private.h" + +#include +#include +#include +#include +#include +#include +#include + +static struct kmem_cache *fsverity_info_cachep; + +static void dump_fsverity_descriptor(const struct fsverity_descriptor *desc) +{ + pr_debug("magic = %.*s\n", (int)sizeof(desc->magic), desc->magic); + pr_debug("major_version = %u\n", desc->major_version); + pr_debug("minor_version = %u\n", desc->minor_version); + pr_debug("log_data_blocksize = %u\n", desc->log_data_blocksize); + pr_debug("log_tree_blocksize = %u\n", desc->log_tree_blocksize); + pr_debug("data_algorithm = %u\n", le16_to_cpu(desc->data_algorithm)); + pr_debug("tree_algorithm = %u\n", le16_to_cpu(desc->tree_algorithm)); + pr_debug("flags = %#x\n", le32_to_cpu(desc->flags)); + pr_debug("orig_file_size = %llu\n", le64_to_cpu(desc->orig_file_size)); + pr_debug("auth_ext_count = %u\n", le16_to_cpu(desc->auth_ext_count)); +} + +/* Precompute the salted initial hash state */ +static int set_salt(struct fsverity_info *vi, const u8 *salt, size_t saltlen) +{ + struct crypto_ahash *tfm = vi->hash_alg->tfm; + struct ahash_request *req; + unsigned int reqsize = sizeof(*req) + crypto_ahash_reqsize(tfm); + struct scatterlist sg; + DECLARE_CRYPTO_WAIT(wait); + u8 *saltbuf; + int err; + + vi->hashstate = kmalloc(crypto_ahash_statesize(tfm), GFP_KERNEL); + if (!vi->hashstate) + return -ENOMEM; + /* On error, vi->hashstate is freed by free_fsverity_info() */ + + /* + * Allocate a hash request buffer. Also reserve space for a copy of + * the salt, since the given 'salt' may point into vmap'ed memory, so + * sg_init_one() may not work on it. + */ + req = kmalloc(reqsize + saltlen, GFP_KERNEL); + if (!req) + return -ENOMEM; + saltbuf = (u8 *)req + reqsize; + memcpy(saltbuf, salt, saltlen); + sg_init_one(&sg, saltbuf, saltlen); + + ahash_request_set_tfm(req, tfm); + 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, saltlen); + + err = crypto_wait_req(crypto_ahash_init(req), &wait); + if (err) + goto out; + err = crypto_wait_req(crypto_ahash_update(req), &wait); + if (err) + goto out; + err = crypto_ahash_export(req, vi->hashstate); +out: + kfree(req); + return err; +} + +/* + * Copy in the root hash stored on disk. + * + * Note that the root hash could be computed by hashing the root block of the + * Merkle tree. But it works out a bit simpler to store the hash separately; + * then it gets included in the file measurement without special-casing it, and + * the root block gets verified on the ->readpages() path like the other blocks. + */ +static int parse_root_hash_extension(struct fsverity_info *vi, + const void *hash, size_t size) +{ + const struct fsverity_hash_alg *alg = vi->hash_alg; + + if (vi->have_root_hash) { + pr_warn("Multiple root hashes were found!\n"); + return -EINVAL; + } + if (size != alg->digest_size) { + pr_warn("Wrong root hash size; got %zu bytes, but expected %u for hash algorithm %s\n", + size, alg->digest_size, alg->name); + return -EINVAL; + } + memcpy(vi->root_hash, hash, size); + vi->have_root_hash = true; + pr_debug("Root hash: %s:%*phN\n", alg->name, + alg->digest_size, vi->root_hash); + return 0; +} + +static int parse_salt_extension(struct fsverity_info *vi, + const void *salt, size_t saltlen) +{ + if (vi->hashstate) { + pr_warn("Multiple salts were found!\n"); + return -EINVAL; + } + return set_salt(vi, salt, saltlen); +} + +/* The available types of extensions (variable-length metadata items) */ +static const struct extension_type { + int (*parse)(struct fsverity_info *vi, const void *_ext, + size_t extra_len); + size_t base_len; /* length of fixed-size part of payload, if any */ + bool unauthenticated; /* true if not included in file measurement */ +} extension_types[] = { + [FS_VERITY_EXT_ROOT_HASH] = { + .parse = parse_root_hash_extension, + }, + [FS_VERITY_EXT_SALT] = { + .parse = parse_salt_extension, + }, +}; + +static int do_parse_extensions(struct fsverity_info *vi, + const struct fsverity_extension **ext_hdr_p, + const void *end, int count, bool authenticated) +{ + const struct fsverity_extension *ext_hdr = *ext_hdr_p; + int i; + int err; + + for (i = 0; i < count; i++) { + const struct extension_type *type; + u32 len, rounded_len; + u16 type_code; + + if (end - (const void *)ext_hdr < sizeof(*ext_hdr)) { + pr_warn("Extension list overflows buffer\n"); + return -EINVAL; + } + type_code = le16_to_cpu(ext_hdr->type); + if (type_code >= ARRAY_SIZE(extension_types) || + !extension_types[type_code].parse) { + pr_warn("Unknown extension type: %u\n", type_code); + return -EINVAL; + } + type = &extension_types[type_code]; + if (authenticated != !type->unauthenticated) { + pr_warn("Extension type %u must be %sauthenticated\n", + type_code, type->unauthenticated ? "un" : ""); + return -EINVAL; + } + if (ext_hdr->reserved) { + pr_warn("Reserved bits set in extension header\n"); + return -EINVAL; + } + len = le32_to_cpu(ext_hdr->length); + if (len < sizeof(*ext_hdr)) { + pr_warn("Invalid length in extension header\n"); + return -EINVAL; + } + rounded_len = round_up(len, 8); + if (rounded_len == 0 || + rounded_len > end - (const void *)ext_hdr) { + pr_warn("Extension item overflows buffer\n"); + return -EINVAL; + } + if (len < sizeof(*ext_hdr) + type->base_len) { + pr_warn("Extension length too small for type\n"); + return -EINVAL; + } + err = type->parse(vi, ext_hdr + 1, + len - sizeof(*ext_hdr) - type->base_len); + if (err) + return err; + ext_hdr = (const void *)ext_hdr + rounded_len; + } + *ext_hdr_p = ext_hdr; + return 0; +} + +/* + * Parse the extension items following the fixed-size portion of the fs-verity + * descriptor. The fsverity_info is updated accordingly. + * + * Return: On success, the size of the authenticated portion of the descriptor + * (the fixed-size portion plus the authenticated extensions). + * Otherwise, a -errno value. + */ +static int parse_extensions(struct fsverity_info *vi, + const struct fsverity_descriptor *desc, + int desc_len) +{ + const struct fsverity_extension *ext_hdr = (const void *)(desc + 1); + const void *end = (const void *)desc + desc_len; + u16 auth_ext_count = le16_to_cpu(desc->auth_ext_count); + int auth_desc_len; + int err; + + err = do_parse_extensions(vi, &ext_hdr, end, auth_ext_count, true); + if (err) + return err; + auth_desc_len = (void *)ext_hdr - (void *)desc; + + /* + * Unauthenticated extensions (optional). Careful: an attacker able to + * corrupt the file can change these arbitrarily without being detected. + * Thus, only specific types of extensions are whitelisted here -- + * namely, the ones containing a signature of the file measurement, + * which by definition can't be included in the file measurement itself. + */ + if (end - (void *)ext_hdr >= 8) { + u16 unauth_ext_count = le16_to_cpup((__le16 *)ext_hdr); + + ext_hdr = (void *)ext_hdr + 8; + err = do_parse_extensions(vi, &ext_hdr, end, + unauth_ext_count, false); + if (err) + return err; + } + + return auth_desc_len; +} + +/* + * Parse an fs-verity descriptor, loading information into the fsverity_info. + * + * Return: On success, the size of the authenticated portion of the descriptor + * (the fixed-size portion plus the authenticated extensions). + * Otherwise, a -errno value. + */ +static int parse_fsverity_descriptor(struct fsverity_info *vi, + const struct fsverity_descriptor *desc, + int desc_len, loff_t desc_start) +{ + unsigned int alg_num; + unsigned int hashes_per_block; + u64 orig_file_size; + int desc_auth_len; + int err; + + BUILD_BUG_ON(sizeof(*desc) != 64); + + /* magic */ + if (memcmp(desc->magic, FS_VERITY_MAGIC, sizeof(desc->magic))) { + pr_warn("Wrong magic bytes\n"); + return -EINVAL; + } + + /* major_version */ + if (desc->major_version != 1) { + pr_warn("Unsupported major version (%u)\n", + desc->major_version); + return -EINVAL; + } + + /* minor_version */ + if (desc->minor_version != 0) { + pr_warn("Unsupported minor version (%u)\n", + desc->minor_version); + return -EINVAL; + } + + /* data_algorithm and tree_algorithm */ + alg_num = le16_to_cpu(desc->data_algorithm); + if (alg_num != le16_to_cpu(desc->tree_algorithm)) { + pr_warn("Unimplemented case: data (%u) and tree (%u) hash algorithms differ\n", + alg_num, le16_to_cpu(desc->tree_algorithm)); + return -EINVAL; + } + vi->hash_alg = fsverity_get_hash_alg(alg_num); + if (IS_ERR(vi->hash_alg)) + return PTR_ERR(vi->hash_alg); + + /* log_data_blocksize and log_tree_blocksize */ + if (desc->log_data_blocksize != PAGE_SHIFT) { + pr_warn("Unsupported log_blocksize (%u). Need block_size == PAGE_SIZE.\n", + desc->log_data_blocksize); + return -EINVAL; + } + if (desc->log_tree_blocksize != desc->log_data_blocksize) { + pr_warn("Unimplemented case: data (%u) and tree (%u) block sizes differ\n", + desc->log_data_blocksize, desc->log_data_blocksize); + return -EINVAL; + } + vi->block_bits = desc->log_data_blocksize; + hashes_per_block = (1 << vi->block_bits) / vi->hash_alg->digest_size; + if (!is_power_of_2(hashes_per_block)) { + pr_warn("Unimplemented case: hashes per block (%u) isn't a power of 2\n", + hashes_per_block); + return -EINVAL; + } + vi->log_arity = ilog2(hashes_per_block); + + /* flags */ + if (desc->flags) { + pr_warn("Unsupported flags (%#x)\n", le32_to_cpu(desc->flags)); + return -EINVAL; + } + + /* reserved fields */ + if (desc->reserved1 || + memchr_inv(desc->reserved2, 0, sizeof(desc->reserved2))) { + pr_warn("Reserved bits set in fsverity_descriptor\n"); + return -EINVAL; + } + + /* + * orig_file_size. For filesystems that set the on-disk i_size to + * data_i_size rather than to full_i_size, this field is redundant -- + * though it still must be included in the file measurement! Make sure + * it's really the same. + */ + orig_file_size = le64_to_cpu(desc->orig_file_size); + if (vi->data_i_size) { + if (orig_file_size != vi->data_i_size) { + pr_warn("fsverity_descriptor.orig_file_size (%llu) doesn't match i_size (%llu)!\n", + orig_file_size, vi->data_i_size); + return -EINVAL; + } + } else { + vi->data_i_size = orig_file_size; + } + if (vi->data_i_size == 0) { + pr_warn("Original file size is 0; this is not supported\n"); + return -EINVAL; + } + if (vi->data_i_size > desc_start) { + pr_warn("Original file size is too large (%llu)\n", + vi->data_i_size); + return -EINVAL; + } + + /* extensions */ + desc_auth_len = parse_extensions(vi, desc, desc_len); + if (desc_auth_len < 0) + return desc_auth_len; + + if (!vi->have_root_hash) { + pr_warn("Root hash wasn't found!\n"); + return -EINVAL; + } + + /* Use an empty salt if no salt was found in the extensions list */ + if (!vi->hashstate) { + err = set_salt(vi, "", 0); + if (err) + return err; + } + + return desc_auth_len; +} + +/* + * Calculate the depth of the Merkle tree, then create a map from level to the + * block offset at which that level's hash blocks start. Level 'depth - 1' is + * the root and is stored first in the file, in the first block following the + * original data. Level 0 is the level directly "above" the data blocks and is + * stored last in the file, just before the fsverity_descriptor. + */ +static int compute_tree_depth_and_offsets(struct fsverity_info *vi) +{ + unsigned int hashes_per_block = 1 << vi->log_arity; + u64 blocks = (vi->data_i_size + (1 << vi->block_bits) - 1) >> + vi->block_bits; + u64 offset = blocks; + int depth = 0; + int i; + + while (blocks > 1) { + if (depth >= FS_VERITY_MAX_LEVELS) { + pr_warn("Too many tree levels (max is %d)\n", + FS_VERITY_MAX_LEVELS); + return -EINVAL; + } + blocks = (blocks + hashes_per_block - 1) >> vi->log_arity; + vi->hash_lvl_region_idx[depth++] = blocks; + } + vi->depth = depth; + + for (i = depth - 1; i >= 0; i--) { + u64 next_count = vi->hash_lvl_region_idx[i]; + + vi->hash_lvl_region_idx[i] = offset; + pr_debug("Level %d is [%llu..%llu] (%llu blocks)\n", + i, offset, offset + next_count - 1, next_count); + offset += next_count; + } + return 0; +} + +/* Arbitrary limit, can be increased if needed */ +#define MAX_DESCRIPTOR_PAGES 16 + +/* + * Compute the file's measurement by hashing the first 'desc_auth_len' bytes of + * the fs-verity descriptor (which includes the Merkle tree root hash as an + * authenticated extension item). + * + * Note: 'desc' may point into vmap'ed memory, so it can't be passed directly to + * sg_set_buf() for the ahash API. Instead, we pass the pages directly. + */ +static int compute_measurement(const struct fsverity_info *vi, + const struct fsverity_descriptor *desc, + int desc_auth_len, + struct page *desc_pages[MAX_DESCRIPTOR_PAGES], + int nr_desc_pages, u8 *measurement) +{ + struct ahash_request *req; + DECLARE_CRYPTO_WAIT(wait); + struct scatterlist sg[MAX_DESCRIPTOR_PAGES]; + int offset, len, remaining; + int i; + int err; + + req = ahash_request_alloc(vi->hash_alg->tfm, GFP_KERNEL); + if (!req) + return -ENOMEM; + + sg_init_table(sg, nr_desc_pages); + offset = offset_in_page(desc); + remaining = desc_auth_len; + for (i = 0; i < nr_desc_pages && remaining; i++) { + len = min_t(int, PAGE_SIZE - offset, remaining); + sg_set_page(&sg[i], desc_pages[i], len, offset); + remaining -= len; + offset = 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, measurement, desc_auth_len); + err = crypto_wait_req(crypto_ahash_digest(req), &wait); + ahash_request_free(req); + return err; +} + +static struct fsverity_info *alloc_fsverity_info(void) +{ + return kmem_cache_zalloc(fsverity_info_cachep, GFP_NOFS); +} + +void free_fsverity_info(struct fsverity_info *vi) +{ + if (!vi) + return; + kfree(vi->hashstate); + kmem_cache_free(fsverity_info_cachep, vi); +} + +/** + * find_fsverity_footer - find the fsverity_footer in the last page of the file + * + * To find the fsverity_footer we have to scan backwards from the end, skipping + * zero bytes. This is needed because some filesystems (e.g. ext4) set the + * on-disk i_size to data_i_size rather than to full_i_size, and full_i_size is + * instead gotten indirectly via the end of the last extent. This causes + * full_i_size to be rounded up to the end of the filesystem block. + * + * Return: pointer to the footer if found, else NULL + */ +static const struct fsverity_footer * +find_fsverity_footer(const u8 *last_virt, size_t last_validsize) +{ + const u8 *p = last_virt + last_validsize; + const struct fsverity_footer *ftr; + + /* Find the last nonzero byte, which should be ftr->magic[7] */ + do { + if (p <= last_virt) + return NULL; + } while (*--p == 0); + + BUILD_BUG_ON(sizeof(ftr->magic) != 8); + BUILD_BUG_ON(offsetof(struct fsverity_footer, magic[8]) != + sizeof(*ftr)); + if (p - last_virt < offsetof(struct fsverity_footer, magic[7])) + return NULL; + ftr = container_of(p, struct fsverity_footer, magic[7]); + if (memcmp(ftr->magic, FS_VERITY_MAGIC, sizeof(ftr->magic))) + return NULL; + return ftr; +} + +/** + * map_fsverity_descriptor - map an inode's fs-verity descriptor into memory + * + * If the descriptor fits in one page, we use kmap; otherwise we use vmap. + * unmap_fsverity_descriptor() must be called later to unmap it. + * + * It's assumed that the file contents cannot be modified concurrently. + * (This is guaranteed by either deny_write_access() or by the verity bit.) + * + * Return: the virtual address of the start of the descriptor, in virtually + * contiguous memory. Also fills in desc_pages and returns in *desc_len the + * length of the descriptor including all extensions, and in *desc_start the + * offset of the descriptor from the start of the file, in bytes. + */ +static const struct fsverity_descriptor * +map_fsverity_descriptor(struct inode *inode, loff_t full_i_size, + struct page *desc_pages[MAX_DESCRIPTOR_PAGES], + int *nr_desc_pages, int *desc_len, loff_t *desc_start) +{ + const int last_validsize = ((full_i_size - 1) & ~PAGE_MASK) + 1; + const pgoff_t last_pgoff = (full_i_size - 1) >> PAGE_SHIFT; + struct page *last_page; + const void *last_virt; + const struct fsverity_footer *ftr; + pgoff_t first_pgoff; + u32 desc_reverse_offset; + pgoff_t pgoff; + const void *desc_virt; + int i; + int err; + + *nr_desc_pages = 0; + *desc_len = 0; + *desc_start = 0; + + if (full_i_size <= 0) { + pr_warn("File is empty!\n"); + return ERR_PTR(-EINVAL); + } + + last_page = read_mapping_page(inode->i_mapping, last_pgoff, NULL); + if (IS_ERR(last_page)) { + pr_warn("Error reading last page: %ld\n", PTR_ERR(last_page)); + return ERR_CAST(last_page); + } + last_virt = kmap(last_page); + + ftr = find_fsverity_footer(last_virt, last_validsize); + if (!ftr) { + pr_warn("No verity metadata found\n"); + err = -EINVAL; + goto err_out; + } + full_i_size -= (last_virt + last_validsize - sizeof(*ftr)) - + (void *)ftr; + + desc_reverse_offset = le32_to_cpu(ftr->desc_reverse_offset); + if (desc_reverse_offset < + sizeof(struct fsverity_descriptor) + sizeof(*ftr) || + desc_reverse_offset > full_i_size) { + pr_warn("Unexpected desc_reverse_offset: %u\n", + desc_reverse_offset); + err = -EINVAL; + goto err_out; + } + *desc_start = full_i_size - desc_reverse_offset; + if (*desc_start & 7) { + pr_warn("fs-verity descriptor is misaligned (desc_start=%lld)\n", + *desc_start); + err = -EINVAL; + goto err_out; + } + + first_pgoff = *desc_start >> PAGE_SHIFT; + if (last_pgoff - first_pgoff >= MAX_DESCRIPTOR_PAGES) { + pr_warn("fs-verity descriptor is too long (%lu pages)\n", + last_pgoff - first_pgoff + 1); + err = -EINVAL; + goto err_out; + } + + *desc_len = desc_reverse_offset - sizeof(__le32); + + if (first_pgoff == last_pgoff) { + /* Single-page descriptor; use the already-kmapped last page */ + desc_pages[0] = last_page; + *nr_desc_pages = 1; + return last_virt + (*desc_start & ~PAGE_MASK); + } + + /* Multi-page descriptor; map the additional pages into memory */ + + for (pgoff = first_pgoff; pgoff < last_pgoff; pgoff++) { + struct page *page; + + page = read_mapping_page(inode->i_mapping, pgoff, NULL); + if (IS_ERR(page)) { + err = PTR_ERR(page); + pr_warn("Error reading descriptor page: %d\n", err); + goto err_out; + } + desc_pages[(*nr_desc_pages)++] = page; + } + + desc_pages[(*nr_desc_pages)++] = last_page; + kunmap(last_page); + last_page = NULL; + + desc_virt = vmap(desc_pages, *nr_desc_pages, VM_MAP, PAGE_KERNEL_RO); + if (!desc_virt) { + err = -ENOMEM; + goto err_out; + } + + return desc_virt + (*desc_start & ~PAGE_MASK); + +err_out: + for (i = 0; i < *nr_desc_pages; i++) + put_page(desc_pages[i]); + if (last_page) { + kunmap(last_page); + put_page(last_page); + } + return ERR_PTR(err); +} + +static void +unmap_fsverity_descriptor(const struct fsverity_descriptor *desc, + struct page *desc_pages[MAX_DESCRIPTOR_PAGES], + int nr_desc_pages) +{ + int i; + + if (is_vmalloc_addr(desc)) { + vunmap((void *)((unsigned long)desc & PAGE_MASK)); + } else { + WARN_ON(nr_desc_pages != 1); + kunmap(desc_pages[0]); + } + for (i = 0; i < nr_desc_pages; i++) + put_page(desc_pages[i]); +} + +/* + * Read the file's fs-verity descriptor and create an fsverity_info for it. + */ +struct fsverity_info *create_fsverity_info(struct inode *inode, bool enabling) +{ + loff_t full_i_size; + struct fsverity_info *vi; + const struct fsverity_descriptor *desc = NULL; + struct page *desc_pages[MAX_DESCRIPTOR_PAGES]; + int nr_desc_pages; + int desc_len; + loff_t desc_start; + int desc_auth_len; + int err; + + vi = alloc_fsverity_info(); + if (!vi) + return ERR_PTR(-ENOMEM); + + full_i_size = i_size_read(inode); + + if (inode->i_sb->s_vop->get_full_i_size && !enabling) { + /* + * For filesystems that set the on-disk i_size to data_i_size + * rather than to full_i_size, we have to get full_i_size from + * somewhere else, e.g. the end of the last extent. + */ + vi->data_i_size = full_i_size; + err = inode->i_sb->s_vop->get_full_i_size(inode, &full_i_size); + if (err) + goto out; + } + vi->full_i_size = full_i_size; + pr_debug("full_i_size=%lld\n", full_i_size); + + desc = map_fsverity_descriptor(inode, full_i_size, desc_pages, + &nr_desc_pages, &desc_len, &desc_start); + if (IS_ERR(desc)) { + err = PTR_ERR(desc); + desc = NULL; + goto out; + } + + dump_fsverity_descriptor(desc); + desc_auth_len = parse_fsverity_descriptor(vi, desc, desc_len, + desc_start); + if (desc_auth_len < 0) { + err = desc_auth_len; + goto out; + } + + err = compute_tree_depth_and_offsets(vi); + if (err) + goto out; + err = compute_measurement(vi, desc, desc_auth_len, desc_pages, + nr_desc_pages, vi->measurement); +out: + if (desc) + unmap_fsverity_descriptor(desc, desc_pages, nr_desc_pages); + if (err) { + free_fsverity_info(vi); + vi = ERR_PTR(err); + } + return vi; +} + +/* Ensure the inode has an ->i_verity_info */ +static int setup_fsverity_info(struct inode *inode) +{ + struct fsverity_info *vi = get_fsverity_info(inode); + + if (vi) + return 0; + + vi = create_fsverity_info(inode, false); + if (IS_ERR(vi)) + return PTR_ERR(vi); + + if (!set_fsverity_info(inode, vi)) + free_fsverity_info(vi); + return 0; +} + +/** + * 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) by parsing the + * verity metadata at the end of the file. + * + * 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 (filp->f_mode & FMODE_WRITE) { + pr_debug("Denying opening verity file (ino %lu) for write\n", + inode->i_ino); + return -EPERM; + } + + return setup_fsverity_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 (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_prepare_getattr - prepare to get a verity inode's attributes + * @inode: the inode for which the attributes are being retrieved + * + * For filesystems that set the on-disk i_size to full_i_size rather than to + * data_i_size, to make st_size exclude the verity metadata even before the file + * has been opened for the first time we need to grab the original data size + * from the fs-verity descriptor. Currently, to implement this we just set up + * the ->i_verity_info, like in the ->open() hook. + * + * However, when combined with fscrypt, on an encrypted file this must only be + * called if the encryption key has been set up! + * + * Return: 0 on success, -errno on failure + */ +int fsverity_prepare_getattr(struct inode *inode) +{ + return setup_fsverity_info(inode); +} +EXPORT_SYMBOL_GPL(fsverity_prepare_getattr); + +/** + * 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) +{ + free_fsverity_info(inode->i_verity_info); + inode->i_verity_info = NULL; +} +EXPORT_SYMBOL_GPL(fsverity_cleanup_inode); + +/** + * fsverity_full_i_size - get the full (on-disk) file size + * + * If the inode has had its in-memory ->i_size overridden for fs-verity (to + * exclude the metadata at the end of the file), then return the full i_size + * which is stored on-disk. Otherwise, just return the in-memory ->i_size. + * + * Return: the full (on-disk) file size + */ +loff_t fsverity_full_i_size(const struct inode *inode) +{ + struct fsverity_info *vi = get_fsverity_info(inode); + + if (vi) + return vi->full_i_size; + + return i_size_read(inode); +} +EXPORT_SYMBOL_GPL(fsverity_full_i_size); + +static int __init fsverity_module_init(void) +{ + fsverity_info_cachep = KMEM_CACHE(fsverity_info, SLAB_RECLAIM_ACCOUNT); + if (!fsverity_info_cachep) + return -ENOMEM; + + fsverity_check_hash_algs(); + + pr_debug("Initialized fs-verity\n"); + return 0; +} + +static void __exit fsverity_module_exit(void) +{ + kmem_cache_destroy(fsverity_info_cachep); + fsverity_exit_hash_algs(); +} + +module_init(fsverity_module_init) +module_exit(fsverity_module_exit); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("fs-verity: read-only file-based integrity/authentication"); diff --git a/include/linux/fs.h b/include/linux/fs.h index 805bf22898cf2..26764ebcb7724 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -61,6 +61,8 @@ struct workqueue_struct; struct iov_iter; struct fscrypt_info; struct fscrypt_operations; +struct fsverity_info; +struct fsverity_operations; extern void __init inode_init(void); extern void __init inode_init_early(void); @@ -671,6 +673,10 @@ struct inode { struct fscrypt_info *i_crypt_info; #endif +#if IS_ENABLED(CONFIG_FS_VERITY) + struct fsverity_info *i_verity_info; +#endif + void *i_private; /* fs or device private pointer */ } __randomize_layout; @@ -1369,6 +1375,9 @@ struct super_block { const struct xattr_handler **s_xattr; #if IS_ENABLED(CONFIG_FS_ENCRYPTION) const struct fscrypt_operations *s_cop; +#endif +#if IS_ENABLED(CONFIG_FS_VERITY) + const struct fsverity_operations *s_vop; #endif struct hlist_bl_head s_roots; /* alternate root dentries for NFS */ struct list_head s_mounts; /* list of mounts; _not_ for fs use */ diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h new file mode 100644 index 0000000000000..3af55241046aa --- /dev/null +++ b/include/linux/fsverity.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * fs-verity: read-only file-based integrity/authentication + * + * Copyright (C) 2018 Google, Inc. + */ + +#ifndef _LINUX_FSVERITY_H +#define _LINUX_FSVERITY_H + +#include +#include + +/* + * fs-verity operations for filesystems + */ +struct fsverity_operations { + int (*set_verity)(struct inode *inode, loff_t data_i_size); + int (*get_full_i_size)(struct inode *inode, loff_t *full_i_size_ret); +}; + +#if __FS_HAS_VERITY + +/* setup.c */ +extern int fsverity_file_open(struct inode *inode, struct file *filp); +extern int fsverity_prepare_setattr(struct dentry *dentry, struct iattr *attr); +extern int fsverity_prepare_getattr(struct inode *inode); +extern void fsverity_cleanup_inode(struct inode *inode); +extern loff_t fsverity_full_i_size(const struct inode *inode); + +#else /* !__FS_HAS_VERITY */ + +/* setup.c */ + +static inline int fsverity_file_open(struct inode *inode, struct file *filp) +{ + return -EOPNOTSUPP; +} + +static inline int fsverity_prepare_setattr(struct dentry *dentry, + struct iattr *attr) +{ + return -EOPNOTSUPP; +} + +static inline int fsverity_prepare_getattr(struct inode *inode) +{ + return -EOPNOTSUPP; +} + +static inline void fsverity_cleanup_inode(struct inode *inode) +{ +} + +static inline loff_t fsverity_full_i_size(const struct inode *inode) +{ + return i_size_read(inode); +} + +#endif /* !__FS_HAS_VERITY */ + +#endif /* _LINUX_FSVERITY_H */ diff --git a/include/uapi/linux/fsverity.h b/include/uapi/linux/fsverity.h new file mode 100644 index 0000000000000..24ebb8b6ea0d4 --- /dev/null +++ b/include/uapi/linux/fsverity.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * fs-verity (file-based verity) support + * + * Copyright (C) 2018 Google LLC + */ +#ifndef _UAPI_LINUX_FSVERITY_H +#define _UAPI_LINUX_FSVERITY_H + +#include +#include +#include + +/* ========== Ioctls ========== */ + +struct fsverity_digest { + __u16 digest_algorithm; + __u16 digest_size; /* input/output */ + __u8 digest[]; +}; + +#define FS_IOC_ENABLE_VERITY _IO('f', 133) +#define FS_IOC_MEASURE_VERITY _IOWR('f', 134, struct fsverity_digest) + +/* ========== On-disk format ========== */ + +#define FS_VERITY_MAGIC "FSVerity" + +/* Supported hash algorithms */ +#define FS_VERITY_ALG_SHA256 1 + +/* Metadata stored near the end of verity files, after the Merkle tree */ +/* This structure is 64 bytes long */ +struct fsverity_descriptor { + __u8 magic[8]; /* must be FS_VERITY_MAGIC */ + __u8 major_version; /* must be 1 */ + __u8 minor_version; /* must be 0 */ + __u8 log_data_blocksize;/* log2(data-bytes-per-hash), e.g. 12 for 4KB */ + __u8 log_tree_blocksize;/* log2(tree-bytes-per-hash), e.g. 12 for 4KB */ + __le16 data_algorithm; /* hash algorithm for data blocks */ + __le16 tree_algorithm; /* hash algorithm for tree blocks */ + __le32 flags; /* flags */ + __le32 reserved1; /* must be 0 */ + __le64 orig_file_size; /* size of the original, unpadded data */ + __le16 auth_ext_count; /* number of authenticated extensions */ + __u8 reserved2[30]; /* must be 0 */ +}; +/* followed by list of 'auth_ext_count' authenticated extensions */ +/* + * then followed by '__le16 unauth_ext_count' padded to next 8-byte boundary, + * then a list of 'unauth_ext_count' (may be 0) unauthenticated extensions + */ + +/* Extension types */ +#define FS_VERITY_EXT_ROOT_HASH 1 +#define FS_VERITY_EXT_SALT 2 + +/* Header of each extension (variable-length metadata item) */ +struct fsverity_extension { + /* + * Length in bytes, including this header but excluding padding to next + * 8-byte boundary that is applied when advancing to the next extension. + */ + __le32 length; + __le16 type; /* Type of this extension (see codes above) */ + __le16 reserved; /* Reserved, must be 0 */ +}; +/* followed by the payload of 'length - 8' bytes */ + +/* Extension payload formats */ + +/* + * FS_VERITY_EXT_ROOT_HASH payload is just a byte array, with size equal to the + * digest size of the hash algorithm given in the fsverity_descriptor + */ + +/* FS_VERITY_EXT_SALT payload is just a byte array, any size */ + + +/* Fields stored at the very end of the file */ +struct fsverity_footer { + __le32 desc_reverse_offset; /* distance to fsverity_descriptor */ + __u8 magic[8]; /* FS_VERITY_MAGIC */ +} __packed; + +#endif /* _UAPI_LINUX_FSVERITY_H */