LTS: Merge android-4.14-q (4.14.222) into android-msm-pixel-4.14

Merge android-4.14-q common kernel (4.14.222) into C2F2/S5 sc-dev kernel.

Bug: 181732917
Bug: 178998220
Test: Manual testing, SST, vts/vts-kernel, pts/base, pts/postsubmit-long
Signed-off-by: Lucas Wei <lucaswei@google.com>
Change-Id: I3b1e8eba8030aa0155b6a9e3da6f9e420b2bbf28
diff --git a/arch/arm64/configs/floral_defconfig b/arch/arm64/configs/floral_defconfig
index e988bfa..b3eea1c 100644
--- a/arch/arm64/configs/floral_defconfig
+++ b/arch/arm64/configs/floral_defconfig
@@ -706,8 +706,6 @@
 CONFIG_SCHED_STACK_END_CHECK=y
 # CONFIG_DEBUG_PREEMPT is not set
 CONFIG_IPC_LOGGING=y
-CONFIG_QCOM_RTB=y
-CONFIG_QCOM_RTB_SEPARATE_CPUS=y
 CONFIG_LKDTM=m
 CONFIG_BUG_ON_DATA_CORRUPTION=y
 CONFIG_CC_WERROR=y
diff --git a/build.config.floral_debug_memory_accounting b/build.config.floral_debug_memory_accounting
index 2e57582..c0e7154 100644
--- a/build.config.floral_debug_memory_accounting
+++ b/build.config.floral_debug_memory_accounting
@@ -4,7 +4,8 @@
 
 function update_debug_config() {
     ${KERNEL_DIR}/scripts/config --file ${OUT_DIR}/.config \
-         -e CONFIG_PAGE_OWNER
+         -e CONFIG_PAGE_OWNER \
+         -e CONFIG_PAGE_OWNER_ENABLE_DEFAULT
     (cd ${OUT_DIR} && \
      make ${CC_LD_ARG} O=${OUT_DIR} olddefconfig)
 }
diff --git a/build.config.sunfish_debug_memory_accounting b/build.config.sunfish_debug_memory_accounting
index 3a3f2c3..dec56c4 100644
--- a/build.config.sunfish_debug_memory_accounting
+++ b/build.config.sunfish_debug_memory_accounting
@@ -4,7 +4,8 @@
 
 function update_debug_config() {
     ${KERNEL_DIR}/scripts/config --file ${OUT_DIR}/.config \
-         -e CONFIG_PAGE_OWNER
+         -e CONFIG_PAGE_OWNER \
+         -e CONFIG_PAGE_OWNER_ENABLE_DEFAULT
     (cd ${OUT_DIR} && \
      make ${CC_LD_ARG} O=${OUT_DIR} olddefconfig)
 }
diff --git a/fs/incfs/Makefile b/fs/incfs/Makefile
index 0c3f6d3..3503eda 100644
--- a/fs/incfs/Makefile
+++ b/fs/incfs/Makefile
@@ -8,3 +8,5 @@
 	main.o \
 	pseudo_files.o \
 	vfs.o
+
+incrementalfs-$(CONFIG_FS_VERITY) += verity.o
diff --git a/fs/incfs/data_mgmt.c b/fs/incfs/data_mgmt.c
index 79c8f5a..eb8e304 100644
--- a/fs/incfs/data_mgmt.c
+++ b/fs/incfs/data_mgmt.c
@@ -5,6 +5,7 @@
 #include <linux/crc32.h>
 #include <linux/delay.h>
 #include <linux/file.h>
+#include <linux/fsverity.h>
 #include <linux/gfp.h>
 #include <linux/ktime.h>
 #include <linux/lz4.h>
@@ -18,6 +19,7 @@
 #include "data_mgmt.h"
 #include "format.h"
 #include "integrity.h"
+#include "verity.h"
 
 static int incfs_scan_metadata_chain(struct data_file *df);
 
@@ -260,6 +262,8 @@
 		goto out;
 	}
 
+	mutex_init(&df->df_enable_verity);
+
 	df->df_backing_file_context = bfc;
 	df->df_mount_info = mi;
 	for (i = 0; i < ARRAY_SIZE(df->df_segments); i++)
@@ -328,6 +332,10 @@
 
 	incfs_free_mtree(df->df_hash_tree);
 	incfs_free_bfc(df->df_backing_file_context);
+	kfree(df->df_signature);
+	kfree(df->df_verity_file_digest.data);
+	kfree(df->df_verity_signature);
+	mutex_destroy(&df->df_enable_verity);
 	kfree(df);
 }
 
@@ -593,7 +601,11 @@
 	int hash_per_block;
 	pgoff_t file_pages;
 
-	tree = df->df_hash_tree;
+	/*
+	 * Memory barrier to make sure tree is fully present if added via enable
+	 * verity
+	 */
+	tree = smp_load_acquire(&df->df_hash_tree);
 	sig = df->df_signature;
 	if (!tree || !sig)
 		return 0;
@@ -1460,7 +1472,31 @@
 		   df->df_initial_hash_blocks_written);
 
 	df->df_status_offset = handler->md_record_offset;
+	return 0;
+}
 
+static int process_file_verity_signature_md(
+		struct incfs_file_verity_signature *vs,
+		struct metadata_handler *handler)
+{
+	struct data_file *df = handler->context;
+	struct incfs_df_verity_signature *verity_signature;
+
+	if (!df)
+		return -EFAULT;
+
+	verity_signature = kzalloc(sizeof(*verity_signature), GFP_NOFS);
+	if (!verity_signature)
+		return -ENOMEM;
+
+	verity_signature->offset = le64_to_cpu(vs->vs_offset);
+	verity_signature->size = le32_to_cpu(vs->vs_size);
+	if (verity_signature->size > FS_VERITY_MAX_SIGNATURE_SIZE) {
+		kfree(verity_signature);
+		return -EFAULT;
+	}
+
+	df->df_verity_signature = verity_signature;
 	return 0;
 }
 
@@ -1471,6 +1507,7 @@
 	int records_count = 0;
 	int error = 0;
 	struct backing_file_context *bfc = NULL;
+	int nondata_block_count;
 
 	if (!df || !df->df_backing_file_context)
 		return -EFAULT;
@@ -1486,6 +1523,7 @@
 	handler->handle_blockmap = process_blockmap_md;
 	handler->handle_signature = process_file_signature_md;
 	handler->handle_status = process_status_md;
+	handler->handle_verity_signature = process_file_verity_signature_md;
 
 	while (handler->md_record_offset > 0) {
 		error = incfs_read_next_metadata_record(bfc, handler);
@@ -1504,15 +1542,25 @@
 	} else
 		result = records_count;
 
+	nondata_block_count = df->df_total_block_count -
+		df->df_data_block_count;
 	if (df->df_hash_tree) {
 		int hash_block_count = get_blocks_count_for_size(
 			df->df_hash_tree->hash_tree_area_size);
 
-		if (df->df_data_block_count + hash_block_count !=
-		    df->df_total_block_count)
+		/*
+		 * Files that were created with a hash tree have the hash tree
+		 * included in the block map, i.e. nondata_block_count ==
+		 * hash_block_count.  Files whose hash tree was added by
+		 * FS_IOC_ENABLE_VERITY will still have the original block
+		 * count, i.e. nondata_block_count == 0.
+		 */
+		if (nondata_block_count != hash_block_count &&
+		    nondata_block_count != 0)
 			result = -EINVAL;
-	} else if (df->df_data_block_count != df->df_total_block_count)
+	} else if (nondata_block_count != 0) {
 		result = -EINVAL;
+	}
 
 	kfree(handler);
 	return result;
diff --git a/fs/incfs/data_mgmt.h b/fs/incfs/data_mgmt.h
index 77e0f55..013c5b0 100644
--- a/fs/incfs/data_mgmt.h
+++ b/fs/incfs/data_mgmt.h
@@ -273,9 +273,32 @@
 	/* Offset to status metadata header */
 	loff_t df_status_offset;
 
+	/*
+	 * Mutex acquired while enabling verity. Note that df_hash_tree is set
+	 * by enable verity.
+	 *
+	 * The backing file mutex bc_mutex  may be taken while this mutex is
+	 * held.
+	 */
+	struct mutex df_enable_verity;
+
+	/*
+	 * Set either at construction time or during enabling verity. In the
+	 * latter case, set via smp_store_release, so use smp_load_acquire to
+	 * read it.
+	 */
 	struct mtree *df_hash_tree;
 
+	/* Guaranteed set if df_hash_tree is set. */
 	struct incfs_df_signature *df_signature;
+
+	/*
+	 * The verity file digest, set when verity is enabled and the file has
+	 * been opened
+	 */
+	struct mem_range df_verity_file_digest;
+
+	struct incfs_df_verity_signature *df_verity_signature;
 };
 
 struct dir_file {
diff --git a/fs/incfs/format.c b/fs/incfs/format.c
index 7b0dbd6..81076f4 100644
--- a/fs/incfs/format.c
+++ b/fs/incfs/format.c
@@ -246,7 +246,8 @@
 }
 
 int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,
-					  struct mem_range sig, u32 tree_size)
+					struct mem_range sig, u32 tree_size,
+					loff_t *tree_offset, loff_t *sig_offset)
 {
 	struct incfs_file_signature sg = {};
 	int result = 0;
@@ -265,12 +266,10 @@
 	sg.sg_header.h_record_size = cpu_to_le16(sizeof(sg));
 	sg.sg_header.h_next_md_offset = cpu_to_le64(0);
 	if (sig.data != NULL && sig.len > 0) {
-		loff_t pos = incfs_get_end_offset(bfc->bc_file);
-
 		sg.sg_sig_size = cpu_to_le32(sig.len);
-		sg.sg_sig_offset = cpu_to_le64(pos);
+		sg.sg_sig_offset = cpu_to_le64(rollback_pos);
 
-		result = write_to_bf(bfc, sig.data, sig.len, pos);
+		result = write_to_bf(bfc, sig.data, sig.len, rollback_pos);
 		if (result)
 			goto err;
 	}
@@ -306,6 +305,13 @@
 	if (result)
 		/* Error, rollback file changes */
 		truncate_backing_file(bfc, rollback_pos);
+	else {
+		if (tree_offset)
+			*tree_offset = tree_area_pos;
+		if (sig_offset)
+			*sig_offset = rollback_pos;
+	}
+
 	return result;
 }
 
@@ -324,9 +330,6 @@
 		.is_hash_blocks_written = cpu_to_le32(hash_blocks_written),
 	};
 
-	if (!bfc)
-		return -EFAULT;
-
 	LOCK_REQUIRED(bfc->bc_mutex);
 	rollback_pos = incfs_get_end_offset(bfc->bc_file);
 	result = append_md_to_backing_file(bfc, &is.is_header);
@@ -344,6 +347,9 @@
 	struct incfs_status is;
 	int result;
 
+	if (!bfc)
+		return -EFAULT;
+
 	if (status_offset == 0)
 		return write_new_status_to_backing_file(bfc,
 				data_blocks_written, hash_blocks_written);
@@ -361,6 +367,46 @@
 	return 0;
 }
 
+int incfs_write_verity_signature_to_backing_file(
+		struct backing_file_context *bfc, struct mem_range signature,
+		loff_t *offset)
+{
+	struct incfs_file_verity_signature vs = {};
+	int result;
+	loff_t pos;
+
+	/* No verity signature section is equivalent to an empty section */
+	if (signature.data == NULL || signature.len == 0)
+		return 0;
+
+	pos = incfs_get_end_offset(bfc->bc_file);
+
+	vs = (struct incfs_file_verity_signature) {
+		.vs_header = (struct incfs_md_header) {
+			.h_md_entry_type = INCFS_MD_VERITY_SIGNATURE,
+			.h_record_size = cpu_to_le16(sizeof(vs)),
+			.h_next_md_offset = cpu_to_le64(0),
+		},
+		.vs_size = cpu_to_le32(signature.len),
+		.vs_offset = cpu_to_le64(pos),
+	};
+
+	result = write_to_bf(bfc, signature.data, signature.len, pos);
+	if (result)
+		goto err;
+
+	result = append_md_to_backing_file(bfc, &vs.vs_header);
+	if (result)
+		goto err;
+
+	*offset = pos;
+err:
+	if (result)
+		/* Error, rollback file changes */
+		truncate_backing_file(bfc, pos);
+	return result;
+}
+
 /*
  * Write a backing file header
  * It should always be called only on empty file.
@@ -659,6 +705,11 @@
 			res = handler->handle_status(
 				&handler->md_buffer.status, handler);
 		break;
+	case INCFS_MD_VERITY_SIGNATURE:
+		if (handler->handle_verity_signature)
+			res = handler->handle_verity_signature(
+				&handler->md_buffer.verity_signature, handler);
+		break;
 	default:
 		res = -ENOTSUPP;
 		break;
diff --git a/fs/incfs/format.h b/fs/incfs/format.h
index 13a69ab..1b9231b8b 100644
--- a/fs/incfs/format.h
+++ b/fs/incfs/format.h
@@ -120,6 +120,7 @@
 	INCFS_MD_FILE_ATTR = 2,
 	INCFS_MD_SIGNATURE = 3,
 	INCFS_MD_STATUS = 4,
+	INCFS_MD_VERITY_SIGNATURE = 5,
 };
 
 enum incfs_file_header_flags {
@@ -228,7 +229,18 @@
 	__le32 m_block_count;
 } __packed;
 
-/* Metadata record for file signature. Type = INCFS_MD_SIGNATURE */
+/*
+ * Metadata record for file signature. Type = INCFS_MD_SIGNATURE
+ *
+ * The signature stored here is the APK V4 signature data blob. See the
+ * definition of incfs_new_file_args::signature_info for an explanation of this
+ * blob. Specifically, it contains the root hash, but it does *not* contain
+ * anything that the kernel treats as a signature.
+ *
+ * When FS_IOC_ENABLE_VERITY is called on a file without this record, an APK V4
+ * signature blob and a hash tree are added to the file, and then this metadata
+ * record is created to record their locations.
+ */
 struct incfs_file_signature {
 	struct incfs_md_header sg_header;
 
@@ -259,6 +271,29 @@
 	__le32 is_dummy[6]; /* Spare fields */
 } __packed;
 
+/*
+ * Metadata record for verity signature. Type = INCFS_MD_VERITY_SIGNATURE
+ *
+ * This record will only exist for verity-enabled files with signatures. Verity
+ * enabled files without signatures do not have this record. This signature is
+ * checked by fs-verity identically to any other fs-verity signature.
+ */
+struct incfs_file_verity_signature {
+	struct incfs_md_header vs_header;
+
+	 /* The size of the signature */
+	__le32 vs_size;
+
+	 /* Signature's offset in the backing file */
+	__le64 vs_offset;
+} __packed;
+
+/* In memory version of above */
+struct incfs_df_verity_signature {
+	u32 size;
+	u64 offset;
+};
+
 /* State of the backing file. */
 struct backing_file_context {
 	/* Protects writes to bc_file */
@@ -291,6 +326,7 @@
 		struct incfs_blockmap blockmap;
 		struct incfs_file_signature signature;
 		struct incfs_status status;
+		struct incfs_file_verity_signature verity_signature;
 	} md_buffer;
 
 	int (*handle_blockmap)(struct incfs_blockmap *bm,
@@ -299,6 +335,8 @@
 				 struct metadata_handler *handler);
 	int (*handle_status)(struct incfs_status *sig,
 				 struct metadata_handler *handler);
+	int (*handle_verity_signature)(struct incfs_file_verity_signature *s,
+					struct metadata_handler *handler);
 };
 #define INCFS_MAX_METADATA_RECORD_SIZE \
 	FIELD_SIZEOF(struct metadata_handler, md_buffer)
@@ -333,12 +371,16 @@
 					   loff_t file_size);
 
 int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,
-					  struct mem_range sig, u32 tree_size);
+				struct mem_range sig, u32 tree_size,
+				loff_t *tree_offset, loff_t *sig_offset);
 
 int incfs_write_status_to_backing_file(struct backing_file_context *bfc,
 				       loff_t status_offset,
 				       u32 data_blocks_written,
 				       u32 hash_blocks_written);
+int incfs_write_verity_signature_to_backing_file(
+		struct backing_file_context *bfc, struct mem_range signature,
+		loff_t *offset);
 
 /* Reading stuff */
 int incfs_read_file_header(struct backing_file_context *bfc,
diff --git a/fs/incfs/internal.h b/fs/incfs/internal.h
index 0a85eae..c2df8bf 100644
--- a/fs/incfs/internal.h
+++ b/fs/incfs/internal.h
@@ -18,4 +18,6 @@
 
 #define LOCK_REQUIRED(lock)  WARN_ON_ONCE(!mutex_is_locked(&lock))
 
+#define EFSCORRUPTED EUCLEAN
+
 #endif /* _INCFS_INTERNAL_H */
diff --git a/fs/incfs/pseudo_files.c b/fs/incfs/pseudo_files.c
index 52cd78c..1e100cc 100644
--- a/fs/incfs/pseudo_files.c
+++ b/fs/incfs/pseudo_files.c
@@ -5,6 +5,7 @@
 
 #include <linux/file.h>
 #include <linux/fs.h>
+#include <linux/fsnotify.h>
 #include <linux/namei.h>
 #include <linux/poll.h>
 #include <linux/syscalls.h>
@@ -235,13 +236,16 @@
 static int dir_relative_path_resolve(
 			struct mount_info *mi,
 			const char __user *relative_path,
-			struct path *result_path)
+			struct path *result_path,
+			struct path *base_path)
 {
-	struct path *base_path = &mi->mi_backing_dir_path;
 	int dir_fd = get_unused_fd_flags(0);
 	struct file *dir_f = NULL;
 	int error = 0;
 
+	if (!base_path)
+		base_path = &mi->mi_backing_dir_path;
+
 	if (dir_fd < 0)
 		return dir_fd;
 
@@ -266,7 +270,7 @@
 out:
 	sys_close(dir_fd);
 	if (error)
-		pr_debug("incfs: %s %d\n", __func__, error);
+		pr_debug("Error: %d\n", error);
 	return error;
 }
 
@@ -354,8 +358,9 @@
 			goto out;
 		}
 
-		error = incfs_write_signature_to_backing_file(
-			bfc, raw_signature, hash_tree->hash_tree_area_size);
+		error = incfs_write_signature_to_backing_file(bfc,
+				raw_signature, hash_tree->hash_tree_area_size,
+				NULL, NULL);
 		if (error)
 			goto out;
 
@@ -368,6 +373,7 @@
 
 	if (error)
 		goto out;
+
 out:
 	if (bfc) {
 		mutex_unlock(&bfc->bc_mutex);
@@ -381,9 +387,86 @@
 	return error;
 }
 
-static long ioctl_create_file(struct mount_info *mi,
+static void notify_create(struct file *pending_reads_file,
+			  const char  __user *dir_name, const char *file_name,
+			  const char *file_id_str, bool incomplete_file)
+{
+	struct mount_info *mi =
+		get_mount_info(file_superblock(pending_reads_file));
+	struct path base_path = {
+		.mnt = pending_reads_file->f_path.mnt,
+		.dentry = pending_reads_file->f_path.dentry->d_parent,
+	};
+	struct path dir_path = {};
+	struct dentry *file = NULL;
+	struct dentry *dir = NULL;
+	int error;
+
+	error = dir_relative_path_resolve(mi, dir_name, &dir_path, &base_path);
+	if (error)
+		goto out;
+
+	file = incfs_lookup_dentry(dir_path.dentry, file_name);
+	if (IS_ERR(file)) {
+		error = PTR_ERR(file);
+		file = NULL;
+		goto out;
+	}
+
+	fsnotify_create(d_inode(dir_path.dentry), file);
+
+	if (file_id_str) {
+		dir = incfs_lookup_dentry(base_path.dentry, INCFS_INDEX_NAME);
+		if (IS_ERR(dir)) {
+			error = PTR_ERR(dir);
+			dir = NULL;
+			goto out;
+		}
+
+		dput(file);
+		file = incfs_lookup_dentry(dir, file_id_str);
+		if (IS_ERR(file)) {
+			error = PTR_ERR(file);
+			file = NULL;
+			goto out;
+		}
+
+		fsnotify_create(d_inode(dir), file);
+
+		if (incomplete_file) {
+			dput(dir);
+			dir = incfs_lookup_dentry(base_path.dentry,
+						  INCFS_INCOMPLETE_NAME);
+			if (IS_ERR(dir)) {
+				error = PTR_ERR(dir);
+				dir = NULL;
+				goto out;
+			}
+
+			dput(file);
+			file = incfs_lookup_dentry(dir, file_id_str);
+			if (IS_ERR(file)) {
+				error = PTR_ERR(file);
+				file = NULL;
+				goto out;
+			}
+
+			fsnotify_create(d_inode(dir), file);
+		}
+	}
+out:
+	if (error)
+		pr_warn("%s failed with error %d\n", __func__, error);
+
+	dput(dir);
+	dput(file);
+	path_put(&dir_path);
+}
+
+static long ioctl_create_file(struct file *file,
 			struct incfs_new_file_args __user *usr_args)
 {
+	struct mount_info *mi = get_mount_info(file_superblock(file));
 	struct incfs_new_file_args args;
 	char *file_id_str = NULL;
 	struct dentry *index_file_dentry = NULL;
@@ -435,7 +518,7 @@
 	/* Find a directory to put the file into. */
 	error = dir_relative_path_resolve(mi,
 			u64_to_user_ptr(args.directory_path),
-			&parent_dir_path);
+			&parent_dir_path, NULL);
 	if (error)
 		goto out;
 
@@ -594,6 +677,9 @@
 		incomplete_linked = true;
 	}
 
+	notify_create(file, u64_to_user_ptr(args.directory_path), file_name,
+		      file_id_str, args.size != 0);
+
 out:
 	if (error) {
 		pr_debug("incfs: %s err:%d\n", __func__, error);
@@ -614,6 +700,7 @@
 	path_put(&parent_dir_path);
 	if (locked)
 		mutex_unlock(&mi->mi_dir_struct_mutex);
+
 	return error;
 }
 
@@ -665,8 +752,9 @@
 	return error;
 }
 
-static long ioctl_create_mapped_file(struct mount_info *mi, void __user *arg)
+static long ioctl_create_mapped_file(struct file *file, void __user *arg)
 {
+	struct mount_info *mi = get_mount_info(file_superblock(file));
 	struct incfs_create_mapped_file_args __user *args_usr_ptr = arg;
 	struct incfs_create_mapped_file_args args = {};
 	char *file_name;
@@ -746,7 +834,7 @@
 	/* Find a directory to put the file into. */
 	error = dir_relative_path_resolve(mi,
 			u64_to_user_ptr(args.directory_path),
-			&parent_dir_path);
+			&parent_dir_path, NULL);
 	if (error)
 		goto out;
 
@@ -780,6 +868,12 @@
 	if (error)
 		goto out;
 
+	error = chmod(file_dentry, args.mode | 0222);
+	if (error) {
+		pr_debug("incfs: chmod err: %d\n", error);
+		goto delete_file;
+	}
+
 	/* Save the file's size as an xattr for easy fetching in future. */
 	size_attr_value = cpu_to_le64(args.size);
 	error = vfs_setxattr(file_dentry, INCFS_XATTR_SIZE_NAME,
@@ -795,6 +889,9 @@
 	if (error)
 		goto delete_file;
 
+	notify_create(file, u64_to_user_ptr(args.directory_path), file_name,
+		      NULL, false);
+
 	goto out;
 
 delete_file:
@@ -906,11 +1003,11 @@
 
 	switch (req) {
 	case INCFS_IOC_CREATE_FILE:
-		return ioctl_create_file(mi, (void __user *)arg);
+		return ioctl_create_file(f, (void __user *)arg);
 	case INCFS_IOC_PERMIT_FILL:
 		return ioctl_permit_fill(f, (void __user *)arg);
 	case INCFS_IOC_CREATE_MAPPED_FILE:
-		return ioctl_create_mapped_file(mi, (void __user *)arg);
+		return ioctl_create_mapped_file(f, (void __user *)arg);
 	case INCFS_IOC_GET_READ_TIMEOUTS:
 		return ioctl_get_read_timeouts(mi, (void __user *)arg);
 	case INCFS_IOC_SET_READ_TIMEOUTS:
diff --git a/fs/incfs/verity.c b/fs/incfs/verity.c
new file mode 100644
index 0000000..1131aa8
--- /dev/null
+++ b/fs/incfs/verity.c
@@ -0,0 +1,722 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2020 Google LLC
+ */
+
+/*
+ * fs-verity integration into incfs
+ *
+ * Since incfs has its own merkle tree implementation, most of fs-verity code
+ * is not needed. The key part that is needed is the signature check, since
+ * that is based on the private /proc/sys/fs/verity/require_signatures value
+ * and a private keyring. Thus the first change is to modify verity code to
+ * export a version of fsverity_verify_signature.
+ *
+ * fs-verity integration then consists of the following modifications:
+ *
+ * 1. Add the (optional) verity signature to the incfs file format
+ * 2. Add a pointer to the digest of the fs-verity descriptor struct to the
+ *    data_file struct that incfs attaches to each file inode.
+ * 3. Add the following ioclts:
+ *  - FS_IOC_ENABLE_VERITY
+ *  - FS_IOC_GETFLAGS
+ *  - FS_IOC_MEASURE_VERITY
+ * 4. When FS_IOC_ENABLE_VERITY is called on a non-verity file, the
+ *    fs-verity descriptor struct is populated and digested. If it passes the
+ *    signature check or the signature is NULL and
+ *    fs.verity.require_signatures=0, then the S_VERITY flag is set and the
+ *    xattr incfs.verity is set. If the signature is non-NULL, an
+ *    INCFS_MD_VERITY_SIGNATURE is added to the backing file containing the
+ *    signature.
+ * 5. When a file with an incfs.verity xattr's inode is initialized, the
+ *    inode’s S_VERITY flag is set.
+ * 6. When a file with the S_VERITY flag set on its inode is opened, the
+ *    data_file is checked for its verity digest. If the file doesn’t have a
+ *    digest, the file’s digest is calculated as above, checked, and set, or the
+ *    open is denied if it is not valid.
+ * 7. FS_IOC_GETFLAGS simply returns the value of the S_VERITY flag
+ * 8. FS_IOC_MEASURE_VERITY simply returns the cached digest
+ * 9. The final complication is that if FS_IOC_ENABLE_VERITY is called on a file
+ *    which doesn’t have a merkle tree, the merkle tree is calculated before the
+ *    rest of the process is completed.
+ */
+
+#include <crypto/hash.h>
+#include <crypto/sha.h>
+#include <linux/fsverity.h>
+#include <linux/mount.h>
+
+#include "verity.h"
+
+#include "data_mgmt.h"
+#include "format.h"
+#include "integrity.h"
+#include "vfs.h"
+
+#define FS_VERITY_MAX_SIGNATURE_SIZE	16128
+
+static int incfs_get_root_hash(struct file *filp, u8 *root_hash)
+{
+	struct data_file *df = get_incfs_data_file(filp);
+
+	if (!df)
+		return -EINVAL;
+
+	memcpy(root_hash, df->df_hash_tree->root_hash,
+	       df->df_hash_tree->alg->digest_size);
+
+	return 0;
+}
+
+static int incfs_end_enable_verity(struct file *filp, u8 *sig, size_t sig_size)
+{
+	struct inode *inode = file_inode(filp);
+	struct mem_range signature = {
+		.data = sig,
+		.len = sig_size,
+	};
+	struct data_file *df = get_incfs_data_file(filp);
+	struct backing_file_context *bfc;
+	int error;
+	struct incfs_df_verity_signature *vs;
+	loff_t offset;
+
+	if (!df || !df->df_backing_file_context)
+		return -EFSCORRUPTED;
+
+	vs = kzalloc(sizeof(*vs), GFP_NOFS);
+	if (!vs)
+		return -ENOMEM;
+
+	bfc = df->df_backing_file_context;
+	error = mutex_lock_interruptible(&bfc->bc_mutex);
+	if (error)
+		goto out;
+
+	error = incfs_write_verity_signature_to_backing_file(bfc, signature,
+							     &offset);
+	mutex_unlock(&bfc->bc_mutex);
+	if (error)
+		goto out;
+
+	/*
+	 * Set verity xattr so we can set S_VERITY without opening backing file
+	 */
+	error = vfs_setxattr(bfc->bc_file->f_path.dentry,
+			     INCFS_XATTR_VERITY_NAME, NULL, 0, XATTR_CREATE);
+	if (error) {
+		pr_warn("incfs: error setting verity xattr: %d\n", error);
+		goto out;
+	}
+
+	*vs = (struct incfs_df_verity_signature) {
+		.size = signature.len,
+		.offset = offset,
+	};
+
+	df->df_verity_signature = vs;
+	vs = NULL;
+	inode_set_flags(inode, S_VERITY, S_VERITY);
+
+out:
+	kfree(vs);
+	return error;
+}
+
+static int incfs_compute_file_digest(struct incfs_hash_alg *alg,
+				struct fsverity_descriptor *desc,
+				u8 *digest)
+{
+	SHASH_DESC_ON_STACK(d, alg->shash);
+
+	d->tfm = alg->shash;
+	return crypto_shash_digest(d, (u8 *)desc, sizeof(*desc), digest);
+}
+
+static enum incfs_hash_tree_algorithm incfs_convert_fsverity_hash_alg(
+								int hash_alg)
+{
+	switch (hash_alg) {
+	case FS_VERITY_HASH_ALG_SHA256:
+		return INCFS_HASH_TREE_SHA256;
+	default:
+		return -EINVAL;
+	}
+}
+
+static struct mem_range incfs_get_verity_digest(struct inode *inode)
+{
+	struct inode_info *node = get_incfs_node(inode);
+	struct data_file *df;
+	struct mem_range verity_file_digest;
+
+	if (!node) {
+		pr_warn("Invalid inode\n");
+		return range(NULL, 0);
+	}
+
+	df = node->n_file;
+
+	/*
+	 * Pairs with the cmpxchg_release() in incfs_set_verity_digest().
+	 * I.e., another task may publish ->df_verity_file_digest concurrently,
+	 * executing a RELEASE barrier.  We need to use smp_load_acquire() here
+	 * to safely ACQUIRE the memory the other task published.
+	 */
+	verity_file_digest.data = smp_load_acquire(
+					&df->df_verity_file_digest.data);
+	verity_file_digest.len = df->df_verity_file_digest.len;
+	return verity_file_digest;
+}
+
+static void incfs_set_verity_digest(struct inode *inode,
+				     struct mem_range verity_file_digest)
+{
+	struct inode_info *node = get_incfs_node(inode);
+	struct data_file *df;
+
+	if (!node) {
+		pr_warn("Invalid inode\n");
+		kfree(verity_file_digest.data);
+		return;
+	}
+
+	df = node->n_file;
+	df->df_verity_file_digest.len = verity_file_digest.len;
+
+	/*
+	 * Multiple tasks may race to set ->df_verity_file_digest.data, so use
+	 * cmpxchg_release().  This pairs with the smp_load_acquire() in
+	 * incfs_get_verity_digest().  I.e., here we publish
+	 * ->df_verity_file_digest.data, with a RELEASE barrier so that other
+	 * tasks can ACQUIRE it.
+	 */
+	if (cmpxchg_release(&df->df_verity_file_digest.data, NULL,
+			    verity_file_digest.data) != NULL)
+		/* Lost the race, so free the file_digest we allocated. */
+		kfree(verity_file_digest.data);
+}
+
+/*
+ * Calculate the digest of the fsverity_descriptor. The signature (if present)
+ * is also checked.
+ */
+static struct mem_range incfs_calc_verity_digest_from_desc(
+					const struct inode *inode,
+					struct fsverity_descriptor *desc,
+					u8 *signature, size_t sig_size)
+{
+	enum incfs_hash_tree_algorithm incfs_hash_alg;
+	struct mem_range verity_file_digest;
+	int err;
+	struct incfs_hash_alg *hash_alg;
+
+	incfs_hash_alg = incfs_convert_fsverity_hash_alg(desc->hash_algorithm);
+	if (incfs_hash_alg < 0)
+		return range(ERR_PTR(incfs_hash_alg), 0);
+
+	hash_alg = incfs_get_hash_alg(incfs_hash_alg);
+	if (IS_ERR(hash_alg))
+		return range((u8 *)hash_alg, 0);
+
+	verity_file_digest = range(kzalloc(hash_alg->digest_size, GFP_KERNEL),
+				   hash_alg->digest_size);
+	if (!verity_file_digest.data)
+		return range(ERR_PTR(-ENOMEM), 0);
+
+	err = incfs_compute_file_digest(hash_alg, desc,
+					verity_file_digest.data);
+	if (err) {
+		pr_err("Error %d computing file digest", err);
+		goto out;
+	}
+	pr_debug("Computed file digest: %s:%*phN\n",
+		 hash_alg->name, (int) verity_file_digest.len,
+		 verity_file_digest.data);
+
+	err = __fsverity_verify_signature(inode, signature, sig_size,
+					  verity_file_digest.data,
+					  desc->hash_algorithm);
+out:
+	if (err) {
+		kfree(verity_file_digest.data);
+		verity_file_digest = range(ERR_PTR(err), 0);
+	}
+	return verity_file_digest;
+}
+
+static struct mem_range incfs_calc_verity_digest(
+					struct inode *inode, struct file *filp,
+					u8 *signature, size_t signature_size,
+					int hash_algorithm)
+{
+	struct fsverity_descriptor *desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+	int err;
+	struct mem_range verity_file_digest;
+
+	if (!desc)
+		return range(ERR_PTR(-ENOMEM), 0);
+
+	*desc = (struct fsverity_descriptor) {
+		.version = 1,
+		.hash_algorithm = hash_algorithm,
+		.log_blocksize = ilog2(INCFS_DATA_FILE_BLOCK_SIZE),
+		.data_size = cpu_to_le64(inode->i_size),
+	};
+
+	err = incfs_get_root_hash(filp, desc->root_hash);
+	if (err)
+		goto out;
+
+	verity_file_digest = incfs_calc_verity_digest_from_desc(inode, desc,
+						signature, signature_size);
+
+out:
+	kfree(desc);
+	if (err)
+		return range(ERR_PTR(err), 0);
+	return verity_file_digest;
+}
+
+static int incfs_build_merkle_tree(struct file *f, struct data_file *df,
+			     struct backing_file_context *bfc,
+			     struct mtree *hash_tree, loff_t hash_offset,
+			     struct incfs_hash_alg *alg, struct mem_range hash)
+{
+	int error = 0;
+	int limit, lvl, i, result;
+	struct mem_range buf = {.len = INCFS_DATA_FILE_BLOCK_SIZE};
+	struct mem_range tmp = {.len = 2 * INCFS_DATA_FILE_BLOCK_SIZE};
+
+	buf.data = (u8 *)__get_free_pages(GFP_NOFS, get_order(buf.len));
+	tmp.data = (u8 *)__get_free_pages(GFP_NOFS, get_order(tmp.len));
+	if (!buf.data || !tmp.data) {
+		error = -ENOMEM;
+		goto out;
+	}
+
+	/*
+	 * lvl - 1 is the level we are reading, lvl the level we are writing
+	 * lvl == -1 means actual blocks
+	 * lvl == hash_tree->depth means root hash
+	 */
+	limit = df->df_data_block_count;
+	for (lvl = 0; lvl <= hash_tree->depth; lvl++) {
+		for (i = 0; i < limit; ++i) {
+			loff_t hash_level_offset;
+			struct mem_range partial_buf = buf;
+
+			if (lvl == 0)
+				result = incfs_read_data_file_block(partial_buf,
+							f, i, 0, 0, 0, tmp);
+			else {
+				hash_level_offset = hash_offset +
+				       hash_tree->hash_level_suboffset[lvl - 1];
+
+				result = incfs_kread(bfc, partial_buf.data,
+						partial_buf.len,
+						hash_level_offset + i *
+						INCFS_DATA_FILE_BLOCK_SIZE);
+			}
+
+			if (result < 0) {
+				error = result;
+				goto out;
+			}
+
+			partial_buf.len = result;
+			error = incfs_calc_digest(alg, partial_buf, hash);
+			if (error)
+				goto out;
+
+			/*
+			 * last level - only one hash to take and it is stored
+			 * in the incfs signature record
+			 */
+			if (lvl == hash_tree->depth)
+				break;
+
+			hash_level_offset = hash_offset +
+				hash_tree->hash_level_suboffset[lvl];
+
+			result = incfs_kwrite(bfc, hash.data, hash.len,
+					hash_level_offset + hash.len * i);
+
+			if (result < 0) {
+				error = result;
+				goto out;
+			}
+
+			if (result != hash.len) {
+				error = -EIO;
+				goto out;
+			}
+		}
+		limit = DIV_ROUND_UP(limit,
+				     INCFS_DATA_FILE_BLOCK_SIZE / hash.len);
+	}
+
+out:
+	free_pages((unsigned long)tmp.data, get_order(tmp.len));
+	free_pages((unsigned long)buf.data, get_order(buf.len));
+	return error;
+}
+
+/*
+ * incfs files have a signature record that is separate from the
+ * verity_signature record. The signature record does not actually contain a
+ * signature, rather it contains the size/offset of the hash tree, and a binary
+ * blob which contains the root hash and potentially a signature.
+ *
+ * If the file was created with a signature record, then this function simply
+ * returns.
+ *
+ * Otherwise it will create a signature record with a minimal binary blob as
+ * defined by the structure below, create space for the hash tree and then
+ * populate it using incfs_build_merkle_tree
+ */
+static int incfs_add_signature_record(struct file *f)
+{
+	/* See incfs_parse_signature */
+	struct {
+		__le32 version;
+		__le32 size_of_hash_info_section;
+		struct {
+			__le32 hash_algorithm;
+			u8 log2_blocksize;
+			__le32 salt_size;
+			u8 salt[0];
+			__le32 hash_size;
+			u8 root_hash[32];
+		} __packed hash_section;
+		__le32 size_of_signing_info_section;
+		u8 signing_info_section[0];
+	} __packed sig = {
+		.version = cpu_to_le32(INCFS_SIGNATURE_VERSION),
+		.size_of_hash_info_section =
+			cpu_to_le32(sizeof(sig.hash_section)),
+		.hash_section = {
+			.hash_algorithm = cpu_to_le32(INCFS_HASH_TREE_SHA256),
+			.log2_blocksize = ilog2(INCFS_DATA_FILE_BLOCK_SIZE),
+			.hash_size = cpu_to_le32(SHA256_DIGEST_SIZE),
+		},
+	};
+
+	struct data_file *df = get_incfs_data_file(f);
+	struct mtree *hash_tree = NULL;
+	struct backing_file_context *bfc;
+	int error;
+	loff_t hash_offset, sig_offset;
+	struct incfs_hash_alg *alg = incfs_get_hash_alg(INCFS_HASH_TREE_SHA256);
+	u8 hash_buf[INCFS_MAX_HASH_SIZE];
+	int hash_size = alg->digest_size;
+	struct mem_range hash = range(hash_buf, hash_size);
+	int result;
+	struct incfs_df_signature *signature = NULL;
+
+	if (!df)
+		return -EINVAL;
+
+	if (df->df_header_flags & INCFS_FILE_MAPPED)
+		return -EINVAL;
+
+	/* Already signed? */
+	if (df->df_signature && df->df_hash_tree)
+		return 0;
+
+	if (df->df_signature || df->df_hash_tree)
+		return -EFSCORRUPTED;
+
+	/* Add signature metadata record to file */
+	hash_tree = incfs_alloc_mtree(range((u8 *)&sig, sizeof(sig)),
+				      df->df_data_block_count);
+	if (IS_ERR(hash_tree))
+		return PTR_ERR(hash_tree);
+
+	bfc = df->df_backing_file_context;
+	if (!bfc) {
+		error = -EFSCORRUPTED;
+		goto out;
+	}
+
+	error = mutex_lock_interruptible(&bfc->bc_mutex);
+	if (error)
+		goto out;
+
+	error = incfs_write_signature_to_backing_file(bfc,
+				range((u8 *)&sig, sizeof(sig)),
+				hash_tree->hash_tree_area_size,
+				&hash_offset, &sig_offset);
+	mutex_unlock(&bfc->bc_mutex);
+	if (error)
+		goto out;
+
+	/* Populate merkle tree */
+	error = incfs_build_merkle_tree(f, df, bfc, hash_tree, hash_offset, alg,
+				  hash);
+	if (error)
+		goto out;
+
+	/* Update signature metadata record */
+	memcpy(sig.hash_section.root_hash, hash.data, alg->digest_size);
+	result = incfs_kwrite(bfc, &sig, sizeof(sig), sig_offset);
+	if (result < 0) {
+		error = result;
+		goto out;
+	}
+
+	if (result != sizeof(sig)) {
+		error = -EIO;
+		goto out;
+	}
+
+	/* Update in-memory records */
+	memcpy(hash_tree->root_hash, hash.data, alg->digest_size);
+	signature = kzalloc(sizeof(*signature), GFP_NOFS);
+	if (!signature) {
+		error = -ENOMEM;
+		goto out;
+	}
+	*signature = (struct incfs_df_signature) {
+		.hash_offset = hash_offset,
+		.hash_size = hash_tree->hash_tree_area_size,
+		.sig_offset = sig_offset,
+		.sig_size = sizeof(sig),
+	};
+	df->df_signature = signature;
+	signature = NULL;
+
+	/*
+	 * Use memory barrier to prevent readpage seeing the hash tree until
+	 * it's fully there
+	 */
+	smp_store_release(&df->df_hash_tree, hash_tree);
+	hash_tree = NULL;
+
+out:
+	kfree(signature);
+	kfree(hash_tree);
+	return error;
+}
+
+static int incfs_enable_verity(struct file *filp,
+			 const struct fsverity_enable_arg *arg)
+{
+	struct inode *inode = file_inode(filp);
+	struct data_file *df = get_incfs_data_file(filp);
+	u8 *signature = NULL;
+	struct mem_range verity_file_digest = range(NULL, 0);
+	int err;
+
+	if (!df)
+		return -EFSCORRUPTED;
+
+	err = mutex_lock_interruptible(&df->df_enable_verity);
+	if (err)
+		return err;
+
+	if (IS_VERITY(inode)) {
+		err = -EEXIST;
+		goto out;
+	}
+
+	err = incfs_add_signature_record(filp);
+	if (err)
+		goto out;
+
+	/* Get the signature if the user provided one */
+	if (arg->sig_size) {
+		signature = memdup_user(u64_to_user_ptr(arg->sig_ptr),
+					arg->sig_size);
+		if (IS_ERR(signature)) {
+			err = PTR_ERR(signature);
+			signature = NULL;
+			goto out;
+		}
+	}
+
+	verity_file_digest = incfs_calc_verity_digest(inode, filp, signature,
+					arg->sig_size, arg->hash_algorithm);
+	if (IS_ERR(verity_file_digest.data)) {
+		err = PTR_ERR(verity_file_digest.data);
+		verity_file_digest.data = NULL;
+		goto out;
+	}
+
+	err = incfs_end_enable_verity(filp, signature, arg->sig_size);
+	if (err)
+		goto out;
+
+	/* Successfully enabled verity */
+	incfs_set_verity_digest(inode, verity_file_digest);
+	verity_file_digest.data = NULL;
+out:
+	mutex_unlock(&df->df_enable_verity);
+	kfree(signature);
+	kfree(verity_file_digest.data);
+	if (err)
+		pr_err("%s failed with err %d\n", __func__, err);
+	return err;
+}
+
+int incfs_ioctl_enable_verity(struct file *filp, const void __user *uarg)
+{
+	struct inode *inode = file_inode(filp);
+	struct fsverity_enable_arg arg;
+
+	if (copy_from_user(&arg, uarg, sizeof(arg)))
+		return -EFAULT;
+
+	if (arg.version != 1)
+		return -EINVAL;
+
+	if (arg.__reserved1 ||
+	    memchr_inv(arg.__reserved2, 0, sizeof(arg.__reserved2)))
+		return -EINVAL;
+
+	if (arg.hash_algorithm != FS_VERITY_HASH_ALG_SHA256)
+		return -EINVAL;
+
+	if (arg.block_size != PAGE_SIZE)
+		return -EINVAL;
+
+	if (arg.salt_size)
+		return -EINVAL;
+
+	if (arg.sig_size > FS_VERITY_MAX_SIGNATURE_SIZE)
+		return -EMSGSIZE;
+
+	if (S_ISDIR(inode->i_mode))
+		return -EISDIR;
+
+	if (!S_ISREG(inode->i_mode))
+		return -EINVAL;
+
+	return incfs_enable_verity(filp, &arg);
+}
+
+static u8 *incfs_get_verity_signature(struct file *filp, size_t *sig_size)
+{
+	struct data_file *df = get_incfs_data_file(filp);
+	struct incfs_df_verity_signature *vs;
+	u8 *signature;
+	int res;
+
+	if (!df || !df->df_backing_file_context)
+		return ERR_PTR(-EFSCORRUPTED);
+
+	vs = df->df_verity_signature;
+	if (!vs) {
+		*sig_size = 0;
+		return NULL;
+	}
+
+	signature = kzalloc(vs->size, GFP_KERNEL);
+	if (!signature)
+		return ERR_PTR(-ENOMEM);
+
+	res = incfs_kread(df->df_backing_file_context,
+			  signature, vs->size, vs->offset);
+
+	if (res < 0)
+		goto err_out;
+
+	if (res != vs->size) {
+		res = -EINVAL;
+		goto err_out;
+	}
+
+	*sig_size = vs->size;
+	return signature;
+
+err_out:
+	kfree(signature);
+	return ERR_PTR(res);
+}
+
+/* Ensure data_file->df_verity_file_digest is populated */
+static int ensure_verity_info(struct inode *inode, struct file *filp)
+{
+	struct mem_range verity_file_digest;
+	u8 *signature = NULL;
+	size_t sig_size;
+	int err = 0;
+
+	/* See if this file's verity file digest is already cached */
+	verity_file_digest = incfs_get_verity_digest(inode);
+	if (verity_file_digest.data)
+		return 0;
+
+	signature = incfs_get_verity_signature(filp, &sig_size);
+	if (IS_ERR(signature))
+		return PTR_ERR(signature);
+
+	verity_file_digest = incfs_calc_verity_digest(inode, filp, signature,
+						     sig_size,
+						     FS_VERITY_HASH_ALG_SHA256);
+	if (IS_ERR(verity_file_digest.data)) {
+		err = PTR_ERR(verity_file_digest.data);
+		goto out;
+	}
+
+	incfs_set_verity_digest(inode, verity_file_digest);
+
+out:
+	kfree(signature);
+	return err;
+}
+
+/**
+ * incfs_fsverity_file_open() - prepare to open a file that may be
+ * verity-enabled
+ * @inode: the inode being opened
+ * @filp: the struct file being set up
+ *
+ * When opening a verity file, set up data_file->df_verity_file_digest if not
+ * already done. Note that incfs does not allow opening for writing, so there is
+ * no need for that check.
+ *
+ * Return: 0 on success, -errno on failure
+ */
+int incfs_fsverity_file_open(struct inode *inode, struct file *filp)
+{
+	if (IS_VERITY(inode))
+		return ensure_verity_info(inode, filp);
+
+	return 0;
+}
+
+int incfs_ioctl_measure_verity(struct file *filp, void __user *_uarg)
+{
+	struct inode *inode = file_inode(filp);
+	struct mem_range verity_file_digest = incfs_get_verity_digest(inode);
+	struct fsverity_digest __user *uarg = _uarg;
+	struct fsverity_digest arg;
+
+	if (!verity_file_digest.data || !verity_file_digest.len)
+		return -ENODATA; /* not a verity file */
+
+	/*
+	 * The user specifies the digest_size their buffer has space for; we can
+	 * return the digest if it fits in the available space.  We write back
+	 * the actual size, which may be shorter than the user-specified size.
+	 */
+
+	if (get_user(arg.digest_size, &uarg->digest_size))
+		return -EFAULT;
+	if (arg.digest_size < verity_file_digest.len)
+		return -EOVERFLOW;
+
+	memset(&arg, 0, sizeof(arg));
+	arg.digest_algorithm = FS_VERITY_HASH_ALG_SHA256;
+	arg.digest_size = verity_file_digest.len;
+
+	if (copy_to_user(uarg, &arg, sizeof(arg)))
+		return -EFAULT;
+
+	if (copy_to_user(uarg->digest, verity_file_digest.data,
+			 verity_file_digest.len))
+		return -EFAULT;
+
+	return 0;
+}
diff --git a/fs/incfs/verity.h b/fs/incfs/verity.h
new file mode 100644
index 0000000..3ba2306
--- /dev/null
+++ b/fs/incfs/verity.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2020 Google LLC
+ */
+
+#ifndef _INCFS_VERITY_H
+#define _INCFS_VERITY_H
+
+/* Arbitrary limit to bound the kmalloc() size.  Can be changed. */
+#define FS_VERITY_MAX_SIGNATURE_SIZE	16128
+
+#ifdef CONFIG_FS_VERITY
+
+int incfs_ioctl_enable_verity(struct file *filp, const void __user *uarg);
+int incfs_ioctl_measure_verity(struct file *filp, void __user *_uarg);
+
+int incfs_fsverity_file_open(struct inode *inode, struct file *filp);
+
+#else /* !CONFIG_FS_VERITY */
+
+static inline int incfs_ioctl_enable_verity(struct file *filp,
+					    const void __user *uarg)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int incfs_ioctl_measure_verity(struct file *filp,
+					     void __user *_uarg)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int incfs_fsverity_file_open(struct inode *inode,
+					   struct file *filp)
+{
+	return -EOPNOTSUPP;
+}
+
+#endif /* !CONFIG_FS_VERITY */
+
+#endif
diff --git a/fs/incfs/vfs.c b/fs/incfs/vfs.c
index 8cd7b0d..9db03be 100644
--- a/fs/incfs/vfs.c
+++ b/fs/incfs/vfs.c
@@ -4,9 +4,12 @@
  */
 
 #include <linux/blkdev.h>
+#include <linux/compat.h>
 #include <linux/file.h>
 #include <linux/fs.h>
 #include <linux/fs_stack.h>
+#include <linux/fsnotify.h>
+#include <linux/fsverity.h>
 #include <linux/namei.h>
 #include <linux/parser.h>
 #include <linux/seq_file.h>
@@ -19,6 +22,7 @@
 #include "format.h"
 #include "internal.h"
 #include "pseudo_files.h"
+#include "verity.h"
 
 static int incfs_remount_fs(struct super_block *sb, int *flags, char *data);
 
@@ -41,6 +45,11 @@
 static int read_single_page(struct file *f, struct page *page);
 static long dispatch_ioctl(struct file *f, unsigned int req, unsigned long arg);
 
+#ifdef CONFIG_COMPAT
+static long incfs_compat_ioctl(struct file *file, unsigned int cmd,
+			 unsigned long arg);
+#endif
+
 static struct inode *alloc_inode(struct super_block *sb);
 static void free_inode(struct inode *inode);
 static void evict_inode(struct inode *inode);
@@ -106,7 +115,9 @@
 	.splice_read = generic_file_splice_read,
 	.llseek = generic_file_llseek,
 	.unlocked_ioctl = dispatch_ioctl,
-	.compat_ioctl = dispatch_ioctl
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = incfs_compat_ioctl,
+#endif
 };
 
 const struct inode_operations incfs_file_inode_ops = {
@@ -147,6 +158,8 @@
 	struct dentry *backing_dentry;
 
 	size_t size;
+
+	bool verity;
 };
 
 enum parse_parameter {
@@ -243,6 +256,13 @@
 	return le64_to_cpu(attr_value);
 }
 
+/* Read verity flag from the attribute. Quicker than reading the header */
+static bool read_verity_attr(struct dentry *backing_dentry)
+{
+	return vfs_getxattr(backing_dentry, INCFS_XATTR_VERITY_NAME, NULL, 0)
+		>= 0;
+}
+
 static int inode_test(struct inode *inode, void *opaque)
 {
 	struct inode_search *search = opaque;
@@ -273,6 +293,8 @@
 		inode->i_op = &incfs_file_inode_ops;
 		inode->i_fop = &incfs_file_ops;
 		inode->i_mode &= ~0222;
+		if (search->verity)
+			inode_set_flags(inode, S_VERITY, S_VERITY);
 	} else if (S_ISDIR(inode->i_mode)) {
 		inode->i_size = 0;
 		inode->i_blocks = 1;
@@ -307,6 +329,7 @@
 		.ino = backing_inode->i_ino,
 		.backing_dentry = backing_dentry,
 		.size = read_size_attr(backing_dentry),
+		.verity = read_verity_attr(backing_dentry),
 	};
 	struct inode *inode = iget5_locked(sb, search.ino, inode_test,
 				inode_set, &search);
@@ -544,12 +567,62 @@
 	return error;
 }
 
-static void maybe_delete_incomplete_file(struct data_file *df)
+static void notify_unlink(struct dentry *dentry, const char *file_id_str,
+			  const char *special_directory)
+{
+	struct dentry *root = dentry;
+	struct dentry *file = NULL;
+	struct dentry *dir = NULL;
+	int error = 0;
+	bool take_lock = root->d_parent != root->d_parent->d_parent;
+
+	while (root != root->d_parent)
+		root = root->d_parent;
+
+	if (take_lock)
+		dir = incfs_lookup_dentry(root, special_directory);
+	else
+		dir = lookup_one_len(special_directory, root,
+				     strlen(special_directory));
+
+	if (IS_ERR(dir)) {
+		error = PTR_ERR(dir);
+		goto out;
+	}
+	if (d_is_negative(dir)) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	file = incfs_lookup_dentry(dir, file_id_str);
+	if (IS_ERR(file)) {
+		error = PTR_ERR(file);
+		goto out;
+	}
+	if (d_is_negative(file)) {
+		error = -ENOENT;
+		goto out;
+	}
+
+	fsnotify_nameremove(file, 0);
+	d_delete(file);
+
+out:
+	if (error)
+		pr_warn("%s failed with error %d\n", __func__, error);
+
+	dput(dir);
+	dput(file);
+}
+
+static void maybe_delete_incomplete_file(struct file *f,
+					 struct data_file *df)
 {
 	struct mount_info *mi = df->df_mount_info;
 	char *file_id_str = NULL;
 	struct dentry *incomplete_file_dentry = NULL;
 	const struct cred *old_cred = override_creds(mi->mi_owner);
+	int error;
 
 	if (atomic_read(&df->df_data_blocks_written) < df->df_data_block_count)
 		goto out;
@@ -571,7 +644,13 @@
 		goto out;
 
 	vfs_fsync(df->df_backing_file_context->bc_file, 0);
-	incfs_unlink(incomplete_file_dentry);
+	error = incfs_unlink(incomplete_file_dentry);
+	if (error) {
+		pr_warn("incfs: Deleting incomplete file failed: %d\n", error);
+		goto out;
+	}
+
+	notify_unlink(f->f_path.dentry, file_id_str, INCFS_INCOMPLETE_NAME);
 
 out:
 	dput(incomplete_file_dentry);
@@ -640,7 +719,7 @@
 	if (data_buf)
 		free_pages((unsigned long)data_buf, get_order(data_buf_size));
 
-	maybe_delete_incomplete_file(df);
+	maybe_delete_incomplete_file(f, df);
 
 	/*
 	 * Only report the error if no records were processed, otherwise
@@ -746,6 +825,13 @@
 	return 0;
 }
 
+static int incfs_ioctl_get_flags(struct file *f, void __user *arg)
+{
+	u32 flags = IS_VERITY(file_inode(f)) ? FS_VERITY_FL : 0;
+
+	return put_user(flags, (int __user *) arg);
+}
+
 static long dispatch_ioctl(struct file *f, unsigned int req, unsigned long arg)
 {
 	switch (req) {
@@ -757,11 +843,39 @@
 		return ioctl_get_filled_blocks(f, (void __user *)arg);
 	case INCFS_IOC_GET_BLOCK_COUNT:
 		return ioctl_get_block_count(f, (void __user *)arg);
+	case FS_IOC_ENABLE_VERITY:
+		return incfs_ioctl_enable_verity(f, (const void __user *)arg);
+	case FS_IOC_GETFLAGS:
+		return incfs_ioctl_get_flags(f, (void __user *) arg);
+	case FS_IOC_MEASURE_VERITY:
+		return incfs_ioctl_measure_verity(f, (void __user *)arg);
 	default:
 		return -EINVAL;
 	}
 }
 
+#ifdef CONFIG_COMPAT
+static long incfs_compat_ioctl(struct file *file, unsigned int cmd,
+			       unsigned long arg)
+{
+	switch (cmd) {
+	case FS_IOC32_GETFLAGS:
+		cmd = FS_IOC_GETFLAGS;
+		break;
+	case INCFS_IOC_FILL_BLOCKS:
+	case INCFS_IOC_READ_FILE_SIGNATURE:
+	case INCFS_IOC_GET_FILLED_BLOCKS:
+	case INCFS_IOC_GET_BLOCK_COUNT:
+	case FS_IOC_ENABLE_VERITY:
+	case FS_IOC_MEASURE_VERITY:
+		break;
+	default:
+		return -ENOIOCTLCMD;
+	}
+	return dispatch_ioctl(file, cmd, (unsigned long) compat_ptr(arg));
+}
+#endif
+
 static struct dentry *dir_lookup(struct inode *dir_inode, struct dentry *dentry,
 				 unsigned int flags)
 {
@@ -917,9 +1031,8 @@
  * Delete file referenced by backing_dentry and if appropriate its hardlink
  * from .index and .incomplete
  */
-static int file_delete(struct mount_info *mi,
-			struct dentry *backing_dentry,
-			int nlink)
+static int file_delete(struct mount_info *mi, struct dentry *dentry,
+			struct dentry *backing_dentry, int nlink)
 {
 	struct dentry *index_file_dentry = NULL;
 	struct dentry *incomplete_file_dentry = NULL;
@@ -972,15 +1085,19 @@
 	if (nlink > 1)
 		goto just_unlink;
 
-	if (d_really_is_positive(index_file_dentry))
+	if (d_really_is_positive(index_file_dentry)) {
 		error = incfs_unlink(index_file_dentry);
-	if (error)
-		goto out;
+		if (error)
+			goto out;
+		notify_unlink(dentry, file_id_str, INCFS_INDEX_NAME);
+	}
 
-	if (d_really_is_positive(incomplete_file_dentry))
+	if (d_really_is_positive(incomplete_file_dentry)) {
 		error = incfs_unlink(incomplete_file_dentry);
-	if (error)
-		goto out;
+		if (error)
+			goto out;
+		notify_unlink(dentry, file_id_str, INCFS_INCOMPLETE_NAME);
+	}
 
 just_unlink:
 	error = incfs_unlink(backing_dentry);
@@ -1030,7 +1147,7 @@
 	if (err)
 		goto out;
 
-	err = file_delete(mi, backing_path.dentry, stat.nlink);
+	err = file_delete(mi, dentry, backing_path.dentry, stat.nlink);
 
 	d_drop(dentry);
 out:
@@ -1267,7 +1384,12 @@
 		file->private_data = fd;
 
 		err = make_inode_ready_for_data_ops(mi, inode, backing_file);
+		if (err)
+			goto out;
 
+		err = incfs_fsverity_file_open(inode, file);
+		if (err)
+			goto out;
 	} else if (S_ISDIR(inode->i_mode)) {
 		struct dir_file *dir = NULL;
 
@@ -1516,8 +1638,6 @@
 struct dentry *incfs_mount_fs(struct file_system_type *type, int flags,
 			      const char *dev_name, void *data)
 {
-	static const char index_name[] = ".index";
-	static const char incomplete_name[] = ".incomplete";
 	struct mount_options options = {};
 	struct mount_info *mi = NULL;
 	struct path backing_dir_path = {};
@@ -1576,7 +1696,7 @@
 	}
 
 	index_dir = open_or_create_special_dir(backing_dir_path.dentry,
-					       index_name);
+					       INCFS_INDEX_NAME);
 	if (IS_ERR_OR_NULL(index_dir)) {
 		error = PTR_ERR(index_dir);
 		pr_err("incfs: Can't find or create .index dir in %s\n",
@@ -1587,7 +1707,7 @@
 	mi->mi_index_dir = index_dir;
 
 	incomplete_dir = open_or_create_special_dir(backing_dir_path.dentry,
-						    incomplete_name);
+						    INCFS_INCOMPLETE_NAME);
 	if (IS_ERR_OR_NULL(incomplete_dir)) {
 		error = PTR_ERR(incomplete_dir);
 		pr_err("incfs: Can't find or create .incomplete dir in %s\n",
diff --git a/fs/verity/signature.c b/fs/verity/signature.c
index b42f6e8..ce76dd9 100644
--- a/fs/verity/signature.c
+++ b/fs/verity/signature.c
@@ -40,11 +40,38 @@
 int fsverity_verify_signature(const struct fsverity_info *vi,
 			      const u8 *signature, size_t sig_size)
 {
-	const struct inode *inode = vi->inode;
-	const struct fsverity_hash_alg *hash_alg = vi->tree_params.hash_alg;
+	unsigned int digest_algorithm =
+		vi->tree_params.hash_alg - fsverity_hash_algs;
+
+	return __fsverity_verify_signature(vi->inode, signature, sig_size,
+					   vi->file_digest, digest_algorithm);
+}
+
+/**
+ * __fsverity_verify_signature() - check a verity file's signature
+ * @inode: the file's inode
+ * @signature: the file's signature
+ * @sig_size: size of @signature. Can be 0 if there is no signature
+ * @file_digest: the file's digest
+ * @digest_algorithm: the digest algorithm used
+ *
+ * Takes the file's digest and optional signature and verifies the signature
+ * against the digest and the fs-verity keyring if appropriate
+ *
+ * Return: 0 on success (signature valid or not required); -errno on failure
+ */
+int __fsverity_verify_signature(const struct inode *inode, const u8 *signature,
+				size_t sig_size, const u8 *file_digest,
+				unsigned int digest_algorithm)
+{
 	struct fsverity_formatted_digest *d;
+	struct fsverity_hash_alg *hash_alg = fsverity_get_hash_alg(inode,
+							digest_algorithm);
 	int err;
 
+	if (IS_ERR(hash_alg))
+		return PTR_ERR(hash_alg);
+
 	if (sig_size == 0) {
 		if (fsverity_require_signatures) {
 			fsverity_err(inode,
@@ -60,7 +87,7 @@
 	memcpy(d->magic, "FSVerity", 8);
 	d->digest_algorithm = cpu_to_le16(hash_alg - fsverity_hash_algs);
 	d->digest_size = cpu_to_le16(hash_alg->digest_size);
-	memcpy(d->digest, vi->file_digest, hash_alg->digest_size);
+	memcpy(d->digest, file_digest, hash_alg->digest_size);
 
 	err = verify_pkcs7_signature(d, sizeof(*d) + hash_alg->digest_size,
 				     signature, sig_size, fsverity_keyring,
@@ -83,9 +110,10 @@
 	}
 
 	pr_debug("Valid signature for file digest %s:%*phN\n",
-		 hash_alg->name, hash_alg->digest_size, vi->file_digest);
+		 hash_alg->name, hash_alg->digest_size, file_digest);
 	return 0;
 }
+EXPORT_SYMBOL_GPL(__fsverity_verify_signature);
 
 #ifdef CONFIG_SYSCTL
 static struct ctl_table_header *fsverity_sysctl_header;
diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h
index b568b3c..19c118e 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -233,4 +233,18 @@
 	return fsverity_get_info(inode) != NULL;
 }
 
+#ifdef CONFIG_FS_VERITY_BUILTIN_SIGNATURES
+int __fsverity_verify_signature(const struct inode *inode, const u8 *signature,
+				size_t sig_size, const u8 *file_digest,
+				unsigned int digest_algorithm);
+#else /* !CONFIG_FS_VERITY_BUILTIN_SIGNATURES */
+static inline int __fsverity_verify_signature(const struct inode *inode,
+				const u8 *signature, size_t sig_size,
+				const u8 *file_digest,
+				unsigned int digest_algorithm)
+{
+	return 0;
+}
+#endif /* !CONFIG_FS_VERITY_BUILTIN_SIGNATURES */
+
 #endif	/* _LINUX_FSVERITY_H */
diff --git a/include/uapi/linux/incrementalfs.h b/include/uapi/linux/incrementalfs.h
index e59072c..4140c1d 100644
--- a/include/uapi/linux/incrementalfs.h
+++ b/include/uapi/linux/incrementalfs.h
@@ -28,12 +28,15 @@
 #define INCFS_MAX_HASH_SIZE 32
 #define INCFS_MAX_FILE_ATTR_SIZE 512
 
+#define INCFS_INDEX_NAME ".index"
+#define INCFS_INCOMPLETE_NAME ".incomplete"
 #define INCFS_PENDING_READS_FILENAME ".pending_reads"
 #define INCFS_LOG_FILENAME ".log"
 #define INCFS_BLOCKS_WRITTEN_FILENAME ".blocks_written"
 #define INCFS_XATTR_ID_NAME (XATTR_USER_PREFIX "incfs.id")
 #define INCFS_XATTR_SIZE_NAME (XATTR_USER_PREFIX "incfs.size")
 #define INCFS_XATTR_METADATA_NAME (XATTR_USER_PREFIX "incfs.metadata")
+#define INCFS_XATTR_VERITY_NAME (XATTR_USER_PREFIX "incfs.verity")
 
 #define INCFS_MAX_SIGNATURE_SIZE 8096
 #define INCFS_SIGNATURE_VERSION 2
diff --git a/tools/testing/selftests/filesystems/incfs/incfs_test.c b/tools/testing/selftests/filesystems/incfs/incfs_test.c
index 4d8c70f..02cdb23 100644
--- a/tools/testing/selftests/filesystems/incfs/incfs_test.c
+++ b/tools/testing/selftests/filesystems/incfs/incfs_test.c
@@ -17,6 +17,7 @@
 #include <unistd.h>
 #include <zstd.h>
 
+#include <sys/inotify.h>
 #include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -26,10 +27,18 @@
 #include <linux/random.h>
 #include <linux/unistd.h>
 
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+
 #include <kselftest.h>
+#include <include/uapi/linux/fsverity.h>
 
 #include "utils.h"
 
+/* Can't include uapi/linux/fs.h because it clashes with mount.h */
+#define	FS_IOC_GETFLAGS			_IOR('f', 1, long)
+#define FS_VERITY_FL			0x00100000 /* Verity protected inode */
+
 #define TEST_FAILURE 1
 #define TEST_SUCCESS 0
 
@@ -72,6 +81,23 @@
 #define TESTNE(statement, res)					\
 	TESTCOND((statement) != (res))
 
+void print_bytes(const void *data, size_t size)
+{
+	const uint8_t *bytes = data;
+	int i;
+
+	for (i = 0; i < size; ++i) {
+		if (i % 0x10 == 0)
+			printf("%08x:", i);
+		printf("%02x ", (unsigned int) bytes[i]);
+		if (i % 0x10 == 0x0f)
+			printf("\n");
+	}
+
+	if (i % 0x10 != 0)
+		printf("\n");
+}
+
 struct hash_block {
 	char data[INCFS_DATA_FILE_BLOCK_SIZE];
 };
@@ -1632,7 +1658,7 @@
 			goto failure;
 		}
 
-		if (access(filename_in_index, F_OK) != 0) {
+		if (access(filename_in_index, F_OK) != -1) {
 			ksft_print_msg("File %s is still present.\n",
 				filename_in_index);
 			goto failure;
@@ -3472,6 +3498,553 @@
 	return result;
 }
 
+#define DIRS 3
+static int inotify_test(const char *mount_dir)
+{
+	const char *mapped_file_name = "mapped_name";
+	struct test_file file = {
+		.name = "file",
+		.size = 16 * INCFS_DATA_FILE_BLOCK_SIZE
+	};
+
+	int result = TEST_FAILURE;
+	char *backing_dir = NULL, *index_dir = NULL, *incomplete_dir = NULL;
+	char *file_name = NULL;
+	int cmd_fd = -1;
+	int notify_fd = -1;
+	int wds[DIRS];
+	char buffer[DIRS * (sizeof(struct inotify_event) + NAME_MAX + 1)];
+	char *ptr = buffer;
+	struct inotify_event *event;
+	struct inotify_event *events[DIRS] = {};
+	const char *names[DIRS] = {};
+	int res;
+	char id[sizeof(incfs_uuid_t) * 2 + 1];
+	struct incfs_create_mapped_file_args mfa;
+
+	/* File creation triggers inotify events in .index and .incomplete? */
+	TEST(backing_dir = create_backing_dir(mount_dir), backing_dir);
+	TEST(index_dir = concat_file_name(mount_dir, ".index"), index_dir);
+	TEST(incomplete_dir = concat_file_name(mount_dir, ".incomplete"),
+	     incomplete_dir);
+	TESTEQUAL(mount_fs(mount_dir, backing_dir, 50), 0);
+	TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1);
+	TEST(notify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC),
+	     notify_fd != -1);
+	TEST(wds[0] = inotify_add_watch(notify_fd, mount_dir,
+					IN_CREATE | IN_DELETE),
+	     wds[0] != -1);
+	TEST(wds[1] = inotify_add_watch(notify_fd, index_dir,
+					IN_CREATE | IN_DELETE),
+	     wds[1] != -1);
+	TEST(wds[2] = inotify_add_watch(notify_fd, incomplete_dir,
+					IN_CREATE | IN_DELETE),
+	     wds[2] != -1);
+	TESTEQUAL(emit_file(cmd_fd, NULL, file.name, &file.id, file.size,
+			   NULL), 0);
+	TEST(res = read(notify_fd, buffer, sizeof(buffer)), res != -1);
+
+	while (ptr < buffer + res) {
+		int i;
+
+		event = (struct inotify_event *) ptr;
+		TESTCOND(ptr + sizeof(*event) <= buffer + res);
+		for (i = 0; i < DIRS; ++i)
+			if (event->wd == wds[i]) {
+				TESTEQUAL(events[i], NULL);
+				events[i] = event;
+				ptr += sizeof(*event);
+				names[i] = ptr;
+				ptr += events[i]->len;
+				TESTCOND(ptr <= buffer + res);
+				break;
+			}
+		TESTCOND(i < DIRS);
+	}
+
+	TESTNE(events[0], NULL);
+	TESTNE(events[1], NULL);
+	TESTNE(events[2], NULL);
+
+	bin2hex(id, file.id.bytes, sizeof(incfs_uuid_t));
+
+	TESTEQUAL(events[0]->mask, IN_CREATE);
+	TESTEQUAL(events[1]->mask, IN_CREATE);
+	TESTEQUAL(events[2]->mask, IN_CREATE);
+	TESTEQUAL(strcmp(names[0], file.name), 0);
+	TESTEQUAL(strcmp(names[1], id), 0);
+	TESTEQUAL(strcmp(names[2], id), 0);
+
+	/* Creating a mapped file triggers inotify event */
+	mfa = (struct incfs_create_mapped_file_args) {
+		.size = INCFS_DATA_FILE_BLOCK_SIZE,
+		.mode = 0664,
+		.file_name = ptr_to_u64(mapped_file_name),
+		.source_file_id = file.id,
+		.source_offset = INCFS_DATA_FILE_BLOCK_SIZE,
+	};
+
+	TEST(res = ioctl(cmd_fd, INCFS_IOC_CREATE_MAPPED_FILE, &mfa),
+	     res != -1);
+	TEST(res = read(notify_fd, buffer, sizeof(buffer)), res != -1);
+	event = (struct inotify_event *) buffer;
+	TESTEQUAL(event->wd, wds[0]);
+	TESTEQUAL(event->mask, IN_CREATE);
+	TESTEQUAL(strcmp(event->name, mapped_file_name), 0);
+
+	/* File completion triggers inotify event in .incomplete? */
+	TESTEQUAL(emit_test_file_data(mount_dir, &file), 0);
+	TEST(res = read(notify_fd, buffer, sizeof(buffer)), res != -1);
+	event = (struct inotify_event *) buffer;
+	TESTEQUAL(event->wd, wds[2]);
+	TESTEQUAL(event->mask, IN_DELETE);
+	TESTEQUAL(strcmp(event->name, id), 0);
+
+	/* File unlinking triggers inotify event in .index? */
+	TEST(file_name = concat_file_name(mount_dir, file.name), file_name);
+	TESTEQUAL(unlink(file_name), 0);
+	TEST(res = read(notify_fd, buffer, sizeof(buffer)), res != -1);
+	memset(events, 0, sizeof(events));
+	memset(names, 0, sizeof(names));
+	for (ptr = buffer; ptr < buffer + res;) {
+		event = (struct inotify_event *) ptr;
+		int i;
+
+		TESTCOND(ptr + sizeof(*event) <= buffer + res);
+		for (i = 0; i < DIRS; ++i)
+			if (event->wd == wds[i]) {
+				TESTEQUAL(events[i], NULL);
+				events[i] = event;
+				ptr += sizeof(*event);
+				names[i] = ptr;
+				ptr += events[i]->len;
+				TESTCOND(ptr <= buffer + res);
+				break;
+			}
+		TESTCOND(i < DIRS);
+	}
+
+	TESTNE(events[0], NULL);
+	TESTNE(events[1], NULL);
+	TESTEQUAL(events[2], NULL);
+
+	TESTEQUAL(events[0]->mask, IN_DELETE);
+	TESTEQUAL(events[1]->mask, IN_DELETE);
+	TESTEQUAL(strcmp(names[0], file.name), 0);
+	TESTEQUAL(strcmp(names[1], id), 0);
+
+	result = TEST_SUCCESS;
+out:
+	free(file_name);
+	close(notify_fd);
+	close(cmd_fd);
+	umount(mount_dir);
+	free(backing_dir);
+	free(index_dir);
+	free(incomplete_dir);
+	return result;
+}
+
+static EVP_PKEY *create_key(void)
+{
+	EVP_PKEY *pkey = NULL;
+	RSA *rsa = NULL;
+	BIGNUM *bn = NULL;
+
+	pkey = EVP_PKEY_new();
+	if (!pkey)
+		goto fail;
+
+	bn = BN_new();
+	BN_set_word(bn, RSA_F4);
+
+	rsa = RSA_new();
+	if (!rsa)
+		goto fail;
+
+	RSA_generate_key_ex(rsa, 4096, bn, NULL);
+	EVP_PKEY_assign_RSA(pkey, rsa);
+
+	BN_free(bn);
+	return pkey;
+
+fail:
+	BN_free(bn);
+	EVP_PKEY_free(pkey);
+	return NULL;
+}
+
+static X509 *get_cert(EVP_PKEY *key)
+{
+	X509 *x509 = NULL;
+	X509_NAME *name = NULL;
+
+	x509 = X509_new();
+	if (!x509)
+		return NULL;
+
+	ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
+	X509_gmtime_adj(X509_get_notBefore(x509), 0);
+	X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);
+	X509_set_pubkey(x509, key);
+
+	name = X509_get_subject_name(x509);
+	X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC,
+		   (const unsigned char *)"US", -1, -1, 0);
+	X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC,
+		   (const unsigned char *)"CA", -1, -1, 0);
+	X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC,
+		   (const unsigned char *)"San Jose", -1, -1, 0);
+	X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC,
+		   (const unsigned char *)"Example", -1, -1, 0);
+	X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC,
+		   (const unsigned char *)"Org", -1, -1, 0);
+	X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
+		   (const unsigned char *)"www.example.com", -1, -1, 0);
+	X509_set_issuer_name(x509, name);
+
+	if (!X509_sign(x509, key, EVP_sha256()))
+		return NULL;
+
+	return x509;
+}
+
+static int sign(EVP_PKEY *key, X509 *cert, const char *data, size_t len,
+		unsigned char **sig, size_t *sig_len)
+{
+	const int pkcs7_flags = PKCS7_BINARY | PKCS7_NOATTR | PKCS7_PARTIAL |
+			  PKCS7_DETACHED;
+	const EVP_MD *md = EVP_sha256();
+
+	int result = TEST_FAILURE;
+
+	BIO *bio = NULL;
+	PKCS7 *p7 = NULL;
+	unsigned char *bio_buffer;
+
+	TEST(bio = BIO_new_mem_buf(data, len), bio);
+	TEST(p7 = PKCS7_sign(NULL, NULL, NULL, bio, pkcs7_flags), p7);
+	TESTNE(PKCS7_sign_add_signer(p7, cert, key, md, pkcs7_flags), 0);
+	TESTEQUAL(PKCS7_final(p7, bio, pkcs7_flags), 1);
+	TEST(*sig_len = i2d_PKCS7(p7, NULL), *sig_len);
+	TEST(bio_buffer = malloc(*sig_len), bio_buffer);
+	*sig = bio_buffer;
+	TEST(*sig_len = i2d_PKCS7(p7, &bio_buffer), *sig_len);
+	TESTEQUAL(PKCS7_verify(p7, NULL, NULL, bio, NULL,
+			 pkcs7_flags | PKCS7_NOVERIFY | PKCS7_NOSIGS), 1);
+
+	result = TEST_SUCCESS;
+out:
+	PKCS7_free(p7);
+	BIO_free(bio);
+	return result;
+}
+
+static int verity_installed(const char *mount_dir, int cmd_fd, bool *installed)
+{
+	int result = TEST_FAILURE;
+	char *filename = NULL;
+	int fd = -1;
+	struct test_file *file = &get_test_files_set().files[0];
+
+	TESTEQUAL(emit_file(cmd_fd, NULL, file->name, &file->id, file->size,
+			    NULL), 0);
+	TEST(filename = concat_file_name(mount_dir, file->name), filename);
+	TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
+	TESTEQUAL(ioctl(fd, FS_IOC_ENABLE_VERITY, NULL), -1);
+	*installed = errno != EOPNOTSUPP;
+
+	result = TEST_SUCCESS;
+out:
+	close(fd);
+	if (filename)
+		remove(filename);
+	free(filename);
+	return result;
+}
+
+static int enable_verity(const char *mount_dir, struct test_file *file,
+			   EVP_PKEY *key, X509 *cert, bool use_signatures)
+{
+	int result = TEST_FAILURE;
+	char *filename = NULL;
+	int fd = -1;
+	struct fsverity_enable_arg fear = {
+		.version = 1,
+		.hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+		.block_size = INCFS_DATA_FILE_BLOCK_SIZE,
+		.sig_size = 0,
+		.sig_ptr = 0,
+	};
+	unsigned char *sig = NULL;
+	size_t sig_len = 0;
+	struct {
+		__u8 version;           /* must be 1 */
+		__u8 hash_algorithm;    /* Merkle tree hash algorithm */
+		__u8 log_blocksize;     /* log2 of size of data and tree blocks */
+		__u8 salt_size;         /* size of salt in bytes; 0 if none */
+		__le32 sig_size;        /* must be 0 */
+		__le64 data_size;       /* size of file the Merkle tree is built over */
+		__u8 root_hash[64];     /* Merkle tree root hash */
+		__u8 salt[32];          /* salt prepended to each hashed block */
+		__u8 __reserved[144];   /* must be 0's */
+	} __packed fsverity_descriptor = {
+		.version = 1,
+		.hash_algorithm = 1,
+		.log_blocksize = 12,
+		.data_size = file->size,
+	};
+	struct {
+		char magic[8];                  /* must be "FSVerity" */
+		__le16 digest_algorithm;
+		__le16 digest_size;
+		__u8 digest[32];
+	} __packed fsverity_signed_digest =  {
+		.digest_algorithm = 1,
+		.digest_size = 32
+	};
+	uint64_t flags;
+
+	memcpy(fsverity_signed_digest.magic, "FSVerity", 8);
+
+	TEST(filename = concat_file_name(mount_dir, file->name), filename);
+	TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
+	TESTEQUAL(ioctl(fd, FS_IOC_GETFLAGS, &flags), 0);
+	TESTEQUAL(flags & FS_VERITY_FL, 0);
+
+	/* First try to enable verity with random digest */
+	if (key) {
+		TESTEQUAL(sign(key, cert, (void *)&fsverity_signed_digest,
+			    sizeof(fsverity_signed_digest), &sig, &sig_len),
+			  0);
+
+		fear.sig_size = sig_len;
+		fear.sig_ptr = ptr_to_u64(sig);
+		TESTEQUAL(ioctl(fd, FS_IOC_ENABLE_VERITY, &fear), -1);
+	}
+
+	/* Now try with correct digest */
+	memcpy(fsverity_descriptor.root_hash, file->root_hash, 32);
+	sha256((char *)&fsverity_descriptor, sizeof(fsverity_descriptor),
+	       (char *)fsverity_signed_digest.digest);
+
+	if (ioctl(fd, FS_IOC_ENABLE_VERITY, NULL) == -1 &&
+	    errno == EOPNOTSUPP) {
+		result = TEST_SUCCESS;
+		goto out;
+	}
+
+	if (key)
+		TESTEQUAL(sign(key, cert, (void *)&fsverity_signed_digest,
+		       sizeof(fsverity_signed_digest), &sig, &sig_len),
+		  0);
+
+	if (use_signatures) {
+		fear.sig_size = sig_len;
+		fear.sig_ptr = ptr_to_u64(sig);
+	} else {
+		fear.sig_size = 0;
+		fear.sig_ptr = 0;
+	}
+	TESTEQUAL(ioctl(fd, FS_IOC_ENABLE_VERITY, &fear), 0);
+
+	result = TEST_SUCCESS;
+out:
+	free(sig);
+	close(fd);
+	free(filename);
+	return result;
+}
+
+static int validate_verity(const char *mount_dir, struct test_file *file)
+{
+	int result = TEST_FAILURE;
+	char *filename = concat_file_name(mount_dir, file->name);
+	int fd = -1;
+	uint64_t flags;
+	struct fsverity_digest *digest;
+
+	TEST(digest = malloc(sizeof(struct fsverity_digest) +
+			     INCFS_MAX_HASH_SIZE), digest != NULL);
+	TEST(filename = concat_file_name(mount_dir, file->name), filename);
+	TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
+	TESTEQUAL(ioctl(fd, FS_IOC_GETFLAGS, &flags), 0);
+	TESTEQUAL(flags & FS_VERITY_FL, FS_VERITY_FL);
+	digest->digest_size = INCFS_MAX_HASH_SIZE;
+	TESTEQUAL(ioctl(fd, FS_IOC_MEASURE_VERITY, digest), 0);
+	TESTEQUAL(digest->digest_algorithm, FS_VERITY_HASH_ALG_SHA256);
+	TESTEQUAL(digest->digest_size, 32);
+
+	result = TEST_SUCCESS;
+out:
+	close(fd);
+	free(filename);
+	free(digest);
+	return result;
+}
+
+static int verity_test_optional_sigs(const char *mount_dir, bool use_signatures)
+{
+	int result = TEST_FAILURE;
+	char *backing_dir = NULL;
+	bool installed;
+	int cmd_fd = -1;
+	int i;
+	struct test_files_set test = get_test_files_set();
+	const int file_num = test.files_count;
+	EVP_PKEY *key = NULL;
+	X509 *cert = NULL;
+	BIO *mem = NULL;
+	long len;
+	void *ptr;
+	FILE *proc_key_fd = NULL;
+	char *line = NULL;
+	size_t read = 0;
+	int key_id = -1;
+
+	TEST(backing_dir = create_backing_dir(mount_dir), backing_dir);
+	TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "readahead=0", false),
+		  0);
+	TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1);
+	TESTEQUAL(verity_installed(mount_dir, cmd_fd, &installed), 0);
+	if (!installed) {
+		result = TEST_SUCCESS;
+		goto out;
+	}
+	TEST(key = create_key(), key);
+	TEST(cert = get_cert(key), cert);
+
+	TEST(proc_key_fd = fopen("/proc/keys", "r"), proc_key_fd != NULL);
+	while (getline(&line, &read, proc_key_fd) != -1)
+		if (strstr(line, ".fs-verity"))
+			key_id = strtol(line, NULL, 16);
+
+	TEST(mem = BIO_new(BIO_s_mem()), mem != NULL);
+	TESTEQUAL(i2d_X509_bio(mem, cert), 1);
+	TEST(len = BIO_get_mem_data(mem, &ptr), len != 0);
+	TESTCOND(key_id == -1
+		 || syscall(__NR_add_key, "asymmetric", "test:key", ptr, len,
+			    key_id) != -1);
+
+	for (i = 0; i < file_num; i++) {
+		struct test_file *file = &test.files[i];
+
+		build_mtree(file);
+		TESTEQUAL(crypto_emit_file(cmd_fd, NULL, file->name, &file->id,
+				     file->size, file->root_hash,
+				     file->sig.add_data), 0);
+
+		TESTEQUAL(emit_partial_test_file_hash(mount_dir, file), 0);
+		TESTEQUAL(enable_verity(mount_dir, file, key, cert,
+					use_signatures),
+			  0);
+	}
+
+	for (i = 0; i < file_num; i++)
+		TESTEQUAL(validate_verity(mount_dir, &test.files[i]), 0);
+
+	close(cmd_fd);
+	cmd_fd = -1;
+	TESTEQUAL(umount(mount_dir), 0);
+	TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "readahead=0", false),
+		  0);
+
+	for (i = 0; i < file_num; i++)
+		TESTEQUAL(validate_verity(mount_dir, &test.files[i]), 0);
+
+	result = TEST_SUCCESS;
+out:
+	free(line);
+	BIO_free(mem);
+	X509_free(cert);
+	EVP_PKEY_free(key);
+	fclose(proc_key_fd);
+	close(cmd_fd);
+	umount(mount_dir);
+	free(backing_dir);
+	return result;
+}
+
+static int verity_test(const char *mount_dir)
+{
+	int result = TEST_FAILURE;
+
+	TESTEQUAL(verity_test_optional_sigs(mount_dir, true), TEST_SUCCESS);
+	TESTEQUAL(verity_test_optional_sigs(mount_dir, false), TEST_SUCCESS);
+	result = TEST_SUCCESS;
+out:
+	return result;
+}
+
+static int verity_file_valid(const char *mount_dir, struct test_file *file)
+{
+	int result = TEST_FAILURE;
+	char *filename = NULL;
+	int fd = -1;
+	uint8_t buffer[INCFS_DATA_FILE_BLOCK_SIZE];
+	struct incfs_get_file_sig_args gfsa = {
+		.file_signature = ptr_to_u64(buffer),
+		.file_signature_buf_size = sizeof(buffer),
+	};
+	int i;
+
+	TEST(filename = concat_file_name(mount_dir, file->name), filename);
+	TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1);
+	TESTEQUAL(ioctl(fd, INCFS_IOC_READ_FILE_SIGNATURE, &gfsa), 0);
+	for (i = 0; i < file->size; i += sizeof(buffer))
+		TESTEQUAL(pread(fd, buffer, sizeof(buffer), i),
+			  file->size - i > sizeof(buffer) ?
+				sizeof(buffer) : file->size - i);
+
+	result = TEST_SUCCESS;
+out:
+	close(fd);
+	free(filename);
+	return result;
+}
+
+static int enable_verity_test(const char *mount_dir)
+{
+	int result = TEST_FAILURE;
+	char *backing_dir = NULL;
+	bool installed;
+	int cmd_fd = -1;
+	struct test_files_set test = get_test_files_set();
+	int i;
+
+	TEST(backing_dir = create_backing_dir(mount_dir), backing_dir);
+	TESTEQUAL(mount_fs(mount_dir, backing_dir, 0), 0);
+	TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1);
+	TESTEQUAL(verity_installed(mount_dir, cmd_fd, &installed), 0);
+	if (!installed) {
+		result = TEST_SUCCESS;
+		goto out;
+	}
+	for (i = 0; i < test.files_count; ++i) {
+		struct test_file *file = &test.files[i];
+
+		TESTEQUAL(emit_file(cmd_fd, NULL, file->name, &file->id,
+				     file->size, NULL), 0);
+		TESTEQUAL(emit_test_file_data(mount_dir, file), 0);
+		TESTEQUAL(enable_verity(mount_dir, file, NULL, NULL, true), 0);
+	}
+
+	/* Check files are valid on disk */
+	close(cmd_fd);
+	cmd_fd = -1;
+	TESTEQUAL(umount(mount_dir), 0);
+	TESTEQUAL(mount_fs(mount_dir, backing_dir, 0), 0);
+	for (i = 0; i < test.files_count; ++i)
+		TESTEQUAL(verity_file_valid(mount_dir, &test.files[i]), 0);
+
+	result = TEST_SUCCESS;
+out:
+	close(cmd_fd);
+	umount(mount_dir);
+	free(backing_dir);
+	return result;
+}
+
 static char *setup_mount_dir()
 {
 	struct stat st;
@@ -3585,6 +4158,9 @@
 		MAKE_TEST(data_block_count_test),
 		MAKE_TEST(hash_block_count_test),
 		MAKE_TEST(per_uid_read_timeouts_test),
+		MAKE_TEST(inotify_test),
+		MAKE_TEST(verity_test),
+		MAKE_TEST(enable_verity_test),
 	};
 #undef MAKE_TEST