[PATCH v2 02/12] fs-verity: add setup code, UAPI, and Kconfig

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Eric Biggers <ebiggers@xxxxxxxxxx>

Add the beginnings of fs-verity support, including:

- The fs-verity Kconfig option (CONFIG_FS_VERITY)
- The fs-verity UAPI declarations (uapi/linux/fsverity.h)
- The internal API header for filesystems to use (linux/fsverity.h)
- The "setup" code which parses the fs-verity descriptor to create an
  fsverity_info structure that is attached to the in-memory inode; this
  structure describes the Merkle tree properties and contains the file
  measurement.  This is called from the ->open() and ->getattr() hooks.
- Hash algorithm management; initially supporting SHA-256 only.

The actual ->readpages() data verification, the ioctl implementations,
ext4 and f2fs support, and other functionality comes in later patches.

For more information about fs-verity, see the documentation file
Documentation/filesystems/fsverity.rst.

Signed-off-by: Eric Biggers <ebiggers@xxxxxxxxxx>
---
 Documentation/ioctl/ioctl-number.txt |   1 +
 fs/Kconfig                           |   2 +
 fs/Makefile                          |   1 +
 fs/verity/Kconfig                    |  35 ++
 fs/verity/Makefile                   |   3 +
 fs/verity/fsverity_private.h         |  98 ++++
 fs/verity/hash_algs.c                | 106 ++++
 fs/verity/setup.c                    | 823 +++++++++++++++++++++++++++
 include/linux/fs.h                   |   9 +
 include/linux/fsverity.h             |  62 ++
 include/uapi/linux/fsverity.h        |  86 +++
 11 files changed, 1226 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/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt
index af6f6ba1fe804..e9ab862adbf90 100644
--- a/Documentation/ioctl/ioctl-number.txt
+++ b/Documentation/ioctl/ioctl-number.txt
@@ -224,6 +224,7 @@ Code  Seq#(hex)	Include File		Comments
 'f'	00-0F	fs/ext4/ext4.h		conflict!
 'f'	00-0F	linux/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'	20-2F	linux/usb/g_printer.h
 'h'	00-7F				conflict! Charon filesystem
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..102c46ebe275f
--- /dev/null
+++ b/fs/verity/Kconfig
@@ -0,0 +1,35 @@
+config FS_VERITY
+	tristate "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, 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..acc29825a0ed7
--- /dev/null
+++ b/fs/verity/fsverity_private.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * fs-verity: read-only file-based authenticity protection
+ *
+ * Copyright 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 <crypto/sha.h>
+#define __FS_HAS_VERITY 1
+#include <linux/fsverity.h>
+
+/*
+ * 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;			/* num levels in the Merkle tree */
+	u8 *hashstate;			/* salted initial hash state */
+	loff_t data_i_size;		/* original file size */
+	loff_t metadata_end;		/* offset to end of verity 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 page *fsverity_read_metadata_page(struct inode *inode, pgoff_t index);
+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)
+{
+	/* Make sure the in-memory i_size is set to the data i_size */
+	i_size_write(inode, vi->data_i_size);
+
+	/* pairs with smp_load_acquire() in get_fsverity_info() */
+	return cmpxchg_release(&inode->i_verity_info, NULL, vi) == NULL;
+}
+
+#endif /* _FSVERITY_PRIVATE_H */
diff --git a/fs/verity/hash_algs.c b/fs/verity/hash_algs.c
new file mode 100644
index 0000000000000..9c19c9553f120
--- /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 2018 Google LLC
+ *
+ * Written by Eric Biggers.
+ */
+
+#include "fsverity_private.h"
+
+#include <crypto/hash.h>
+
+/* 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..925970fbe084d
--- /dev/null
+++ b/fs/verity/setup.c
@@ -0,0 +1,823 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * fs/verity/setup.c: fs-verity module initialization and descriptor parsing
+ *
+ * Copyright 2018 Google LLC
+ *
+ * Originally written by Jaegeuk Kim and Michael Halcrow;
+ * heavily rewritten by Eric Biggers.
+ */
+
+#include "fsverity_private.h"
+
+#include <crypto/hash.h>
+#include <linux/highmem.h>
+#include <linux/list_sort.h>
+#include <linux/module.h>
+#include <linux/pagemap.h>
+#include <linux/scatterlist.h>
+#include <linux/vmalloc.h>
+
+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;
+
+	/* Authenticated extensions */
+	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)
+{
+	unsigned int alg_num;
+	unsigned int hashes_per_block;
+	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 */
+	vi->data_i_size = le64_to_cpu(desc->orig_file_size);
+	if (vi->data_i_size <= 0) {
+		pr_warn("Original file size is 0 or negative; this is unsupported\n");
+		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.  Level 0 is the level directly "above" the data
+ * blocks and is stored last, 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 = ((u64)vi->data_i_size + (1 << vi->block_bits) - 1) >>
+			vi->block_bits;
+	u64 offset = blocks;	/* assuming Merkle tree past EOF */
+	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 metadata
+ *
+ * Allow the fs-verity footer to be padded with zeroes.  This is needed by ext4,
+ * which stores the fs-verity metadata beyond EOF but sets i_size = data_i_size.
+ * Then, the fs-verity footer must be found implicitly via the last extent.
+ *
+ * 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;
+}
+
+struct page *fsverity_read_metadata_page(struct inode *inode, pgoff_t index)
+{
+	/*
+	 * For now we assume that the verity metadata is stored in the same data
+	 * stream as the actual file contents (as ext4 and f2fs do), so we read
+	 * the metadata directly from the inode's page cache.  If any
+	 * filesystems need to do things differently, this should be replaced
+	 * with a method fsverity_operations.read_metadata_page().
+	 */
+	return read_mapping_page(inode->i_mapping, index, NULL);
+}
+
+/**
+ * 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 metadata_end,
+			struct page *desc_pages[MAX_DESCRIPTOR_PAGES],
+			int *nr_desc_pages, int *desc_len, loff_t *desc_start)
+{
+	const int last_validsize = ((metadata_end - 1) & ~PAGE_MASK) + 1;
+	const pgoff_t last_pgoff = (metadata_end - 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;
+
+	last_page = fsverity_read_metadata_page(inode, last_pgoff);
+	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;
+	}
+	metadata_end -= (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 > metadata_end) {
+		pr_warn("Unexpected desc_reverse_offset: %u\n",
+			desc_reverse_offset);
+		err = -EINVAL;
+		goto err_out;
+	}
+	*desc_start = metadata_end - 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 = fsverity_read_metadata_page(inode, pgoff);
+		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)
+{
+	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);
+
+	if (enabling) {
+		/* file is in fsveritysetup format */
+		vi->metadata_end = i_size_read(inode);
+	} else {
+		/* verity metadata may be in a filesystem-specific location */
+		err = inode->i_sb->s_vop->get_metadata_end(inode,
+							   &vi->metadata_end);
+		if (err)
+			goto out;
+	}
+
+	desc = map_fsverity_descriptor(inode, vi->metadata_end, 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);
+	if (desc_auth_len < 0) {
+		err = desc_auth_len;
+		goto out;
+	}
+	if (vi->data_i_size > i_size_read(inode)) {
+		pr_warn("Bad data_i_size: %llu\n", vi->data_i_size);
+		err = -EINVAL;
+		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
+ *
+ * This only needs to be called by filesystems that set the on-disk i_size of
+ * verity files to something other than the data size, as then this is needed to
+ * override i_size so that stat() shows the correct size.
+ *
+ * When the filesystem supports fscrypt too, it must make sure to set up the
+ * inode's encryption key (if needed) before calling this.
+ *
+ * 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 file size
+ *
+ * If the file has fs-verity set up, return the full file size including the
+ * verity metadata.  Otherwise just return i_size.  This is only meaningful when
+ * the filesystem stores the verity metadata past EOF.
+ */
+loff_t fsverity_full_i_size(const struct inode *inode)
+{
+	struct fsverity_info *vi = get_fsverity_info(inode);
+
+	if (vi)
+		return vi->metadata_end;
+
+	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 authenticity protection");
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 8252df30b9a16..bcfc400627574 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);
@@ -702,6 +704,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;
 
@@ -1400,6 +1406,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..c9422a579c160
--- /dev/null
+++ b/include/linux/fsverity.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * fs-verity: read-only file-based authenticity protection
+ *
+ * Copyright 2018 Google LLC
+ */
+
+#ifndef _LINUX_FSVERITY_H
+#define _LINUX_FSVERITY_H
+
+#include <linux/fs.h>
+#include <uapi/linux/fsverity.h>
+
+/*
+ * fs-verity operations for filesystems
+ */
+struct fsverity_operations {
+	int (*set_verity)(struct inode *inode, loff_t data_i_size);
+	int (*get_metadata_end)(struct inode *inode, loff_t *metadata_end_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..55b9f32676220
--- /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 <linux/limits.h>
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/* ========== 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 file 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 */
-- 
2.19.1.568.g152ad8e336-goog




[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Linux Kernel]     [Linux Kernel Hardening]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux SCSI]

  Powered by Linux