diff --git a/fs/verity/enable.c b/fs/verity/enable.c
index 60a4372aa4d7..dae21c09e518 100644
--- a/fs/verity/enable.c
+++ b/fs/verity/enable.c
@@ -343,6 +343,9 @@ int fsverity_ioctl_enable(struct file *filp, const void __user *uarg)
 	struct fsverity_enable_arg arg;
 	int err;
 
+	if (fsverity_disabled())
+		return -EPERM;
+
 	if (copy_from_user(&arg, uarg, sizeof(arg)))
 		return -EFAULT;
 
diff --git a/fs/verity/fsverity_private.h b/fs/verity/fsverity_private.h
index c416c1cd9371..05faaa827a9d 100644
--- a/fs/verity/fsverity_private.h
+++ b/fs/verity/fsverity_private.h
@@ -140,6 +140,8 @@ void __init fsverity_exit_info_cache(void);
 #ifdef CONFIG_SYSCTL
 int __init fsverity_sysctl_init(void);
 void __init fsverity_exit_sysctl(void);
+bool fsverity_disabled(void);
+bool fsverity_enforced(void);
 #else /* !CONFIG_SYSCTL */
 static inline int __init fsverity_sysctl_init(void)
 {
@@ -148,6 +150,14 @@ static inline int __init fsverity_sysctl_init(void)
 static inline void __init fsverity_exit_sysctl(void)
 {
 }
+static inline bool fsverity_disabled(void)
+{
+	return true;
+}
+static inline bool fsverity_enforced(void)
+{
+	return false;
+}
 #endif /* !CONFIG_SYSCTL */
 
 /* signature.c */
diff --git a/fs/verity/measure.c b/fs/verity/measure.c
index f0d7b30c62db..f17efaa919e3 100644
--- a/fs/verity/measure.c
+++ b/fs/verity/measure.c
@@ -28,6 +28,9 @@ int fsverity_ioctl_measure(struct file *filp, void __user *_uarg)
 	const struct fsverity_hash_alg *hash_alg;
 	struct fsverity_digest arg;
 
+	if (fsverity_disabled())
+		return -EPERM;
+
 	vi = fsverity_get_info(inode);
 	if (!vi)
 		return -ENODATA; /* not a verity file */
diff --git a/fs/verity/open.c b/fs/verity/open.c
index 92df87f5fa38..840ad62bf6ac 100644
--- a/fs/verity/open.c
+++ b/fs/verity/open.c
@@ -177,7 +177,7 @@ struct fsverity_info *fsverity_create_info(const struct inode *inode,
 		fsverity_err(inode, "Error %d computing file digest", err);
 		goto out;
 	}
-	pr_debug("Computed file digest: %s:%*phN\n",
+	pr_info("Computed file digest: %s:%*phN\n",
 		 vi->tree_params.hash_alg->name,
 		 vi->tree_params.digest_size, vi->file_digest);
 
@@ -344,6 +344,11 @@ static int ensure_verity_info(struct inode *inode)
  */
 int fsverity_file_open(struct inode *inode, struct file *filp)
 {
+	int ret;
+
+	if (fsverity_disabled())
+		return 0;
+
 	if (!IS_VERITY(inode))
 		return 0;
 
@@ -353,7 +358,12 @@ int fsverity_file_open(struct inode *inode, struct file *filp)
 		return -EPERM;
 	}
 
-	return ensure_verity_info(inode);
+	ret = ensure_verity_info(inode);
+	if (!fsverity_enforced()) {
+		fsverity_warn(inode, "AUDIT ONLY: ignore missing verity info");
+		return 0;
+	}
+	return ret;
 }
 EXPORT_SYMBOL_GPL(fsverity_file_open);
 
diff --git a/fs/verity/read_metadata.c b/fs/verity/read_metadata.c
index 7e2d0c7bdf0d..5773fbaabf1e 100644
--- a/fs/verity/read_metadata.c
+++ b/fs/verity/read_metadata.c
@@ -157,6 +157,9 @@ int fsverity_ioctl_read_metadata(struct file *filp, const void __user *uarg)
 	int length;
 	void __user *buf;
 
+	if (fsverity_disabled())
+		return -EPERM;
+
 	vi = fsverity_get_info(inode);
 	if (!vi)
 		return -ENODATA; /* not a verity file */
diff --git a/fs/verity/signature.c b/fs/verity/signature.c
index 67a471e4b570..b10515817d8a 100644
--- a/fs/verity/signature.c
+++ b/fs/verity/signature.c
@@ -42,12 +42,18 @@ int fsverity_verify_signature(const struct fsverity_info *vi,
 	int err;
 
 	if (sig_size == 0) {
+		err = 0;
 		if (fsverity_require_signatures) {
 			fsverity_err(inode,
 				     "require_signatures=1, rejecting unsigned file!");
-			return -EPERM;
+			if (fsverity_enforced()) {
+				err = -EPERM;
+			} else {
+				fsverity_warn(vi->inode, "AUDIT ONLY. ignore unsigned");
+				err = 0;
+			}
 		}
-		return 0;
+		return err;
 	}
 
 	d = kzalloc(sizeof(*d) + hash_alg->digest_size, GFP_KERNEL);
@@ -75,6 +81,10 @@ int fsverity_verify_signature(const struct fsverity_info *vi,
 		else
 			fsverity_err(inode, "Error %d verifying file signature",
 				     err);
+		if (!fsverity_enforced()) {
+			fsverity_warn(vi->inode, "AUDIT ONLY. ignore signature error");
+			err = 0;
+		}
 		return err;
 	}
 
diff --git a/fs/verity/sysctl.c b/fs/verity/sysctl.c
index 3ba7b02282db..94ffb78f745c 100644
--- a/fs/verity/sysctl.c
+++ b/fs/verity/sysctl.c
@@ -9,6 +9,21 @@
  * If 1, all verity files must have a valid builtin signature.
  */
 int fsverity_require_signatures;
+/*
+ * /proc/sys/fs/verity/mode
+ * If "disable": disable verity, don't verify, fail all ioctls.
+ * If "audit": allow ioctls, and run verification logic, but only log on verification failure.
+ * If "enforce": fully enforce, verification failure returns errors.
+ * default: "enforce"
+ */
+#define FSVERITY_MODE_LEN 10
+static const char * const fsverity_modes[] = {
+	"disable",
+	"audit",
+	"enforce",
+	NULL
+};
+static char fsverity_mode[FSVERITY_MODE_LEN] = "enforce";
 
 #ifdef CONFIG_SYSCTL
 static struct ctl_table_header *fsverity_sysctl_header;
@@ -19,6 +34,34 @@ static const struct ctl_path fsverity_sysctl_path[] = {
 	{ }
 };
 
+static int proc_do_fsverity_mode(struct ctl_table *table, int write,
+				 void *buffer, size_t *lenp, loff_t *ppos)
+{
+	char tmp_mode[FSVERITY_MODE_LEN];
+	const char *const *mode = fsverity_modes;
+	struct ctl_table tmp = {
+		.data = tmp_mode,
+		.maxlen = FSVERITY_MODE_LEN,
+		.mode = table->mode,
+	};
+	int ret;
+
+	strncpy(tmp_mode, fsverity_mode, FSVERITY_MODE_LEN);
+	ret = proc_dostring(&tmp, write, buffer, lenp, ppos);
+	if (write) {
+		while (*mode) {
+			if (!strcmp(*mode, tmp_mode))
+				break;
+			++mode;
+		}
+		if (!*mode)
+			ret = -EINVAL;
+		else
+			strncpy(fsverity_mode, *mode, FSVERITY_MODE_LEN);
+	}
+	return ret;
+}
+
 static struct ctl_table fsverity_sysctl_table[] = {
 	{
 		.procname       = "require_signatures",
@@ -29,6 +72,13 @@ static struct ctl_table fsverity_sysctl_table[] = {
 		.extra1         = SYSCTL_ZERO,
 		.extra2         = SYSCTL_ONE,
 	},
+	{
+		.procname       = "mode",
+		.data           = fsverity_mode,
+		.maxlen         = FSVERITY_MODE_LEN,
+		.mode           = 0644,
+		.proc_handler   = proc_do_fsverity_mode,
+	},
 	{ }
 };
 
@@ -48,4 +98,13 @@ void __init fsverity_exit_sysctl(void)
 	unregister_sysctl_table(fsverity_sysctl_header);
 	fsverity_sysctl_header = NULL;
 }
+
+bool fsverity_disabled(void)
+{
+	return !strcmp(fsverity_mode, "disable");
+}
+bool fsverity_enforced(void)
+{
+	return !strcmp(fsverity_mode, "enforce");
+}
 #endif /* !CONFIG_SYSCTL */
diff --git a/fs/verity/verify.c b/fs/verity/verify.c
index 14e2fb49cff5..aedd8f8d864f 100644
--- a/fs/verity/verify.c
+++ b/fs/verity/verify.c
@@ -7,6 +7,7 @@
 
 #include "fsverity_private.h"
 
+#include "linux/export.h"
 #include <crypto/hash.h>
 #include <linux/bio.h>
 #include <linux/ratelimit.h>
@@ -63,6 +64,10 @@ static inline int cmp_hashes(const struct fsverity_info *vi,
 		     index, level,
 		     vi->tree_params.hash_alg->name, hsize, want_hash,
 		     vi->tree_params.hash_alg->name, hsize, real_hash);
+	if (!fsverity_enforced()) {
+		fsverity_warn(vi->inode, "AUDIT ONLY. ignore corruption");
+		return 0;
+	}
 	return -EBADMSG;
 }
 
@@ -98,8 +103,10 @@ static bool verify_page(struct inode *inode, const struct fsverity_info *vi,
 	unsigned int hoffsets[FS_VERITY_MAX_LEVELS];
 	int err;
 
-	if (WARN_ON_ONCE(!PageLocked(data_page) || PageUptodate(data_page)))
-		return false;
+	if (WARN_ON_ONCE(!PageLocked(data_page) || PageUptodate(data_page))) {
+		err = -EINVAL;
+		goto out;
+	}
 
 	pr_debug_ratelimited("Verifying data page %lu...\n", index);
 
@@ -193,6 +200,8 @@ bool fsverity_verify_page(struct page *page)
 	struct ahash_request *req;
 	bool valid;
 
+	if (fsverity_disabled())
+		return true;
 	/* This allocation never fails, since it's mempool-backed. */
 	req = fsverity_alloc_hash_request(vi->tree_params.hash_alg, GFP_NOFS);
 
@@ -276,6 +285,27 @@ void fsverity_enqueue_verify_work(struct work_struct *work)
 }
 EXPORT_SYMBOL_GPL(fsverity_enqueue_verify_work);
 
+/**
+ * fsverity_active() - do reads from the inode need to go through fs-verity?
+ * @inode: inode to check
+ *
+ * This checks whether ->i_verity_info has been set.
+ *
+ * Filesystems call this from ->readahead() to check whether the pages need to
+ * be verified or not.  Don't use IS_VERITY() for this purpose; it's subject to
+ * a race condition where the file is being read concurrently with
+ * FS_IOC_ENABLE_VERITY completing.  (S_VERITY is set before ->i_verity_info.)
+ *
+ * Return: true if reads need to go through fs-verity, otherwise false
+ */
+bool fsverity_active(const struct inode *inode)
+{
+	if (fsverity_disabled())
+		return false;
+	return fsverity_get_info(inode) != NULL;
+}
+EXPORT_SYMBOL_GPL(fsverity_active);
+
 int __init fsverity_init_workqueue(void)
 {
 	/*
diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h
index a7afc800bd8d..51ba4adf7b73 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -147,6 +147,7 @@ int fsverity_ioctl_read_metadata(struct file *filp, const void __user *uarg);
 bool fsverity_verify_page(struct page *page);
 void fsverity_verify_bio(struct bio *bio);
 void fsverity_enqueue_verify_work(struct work_struct *work);
+bool fsverity_active(const struct inode *inode);
 
 #else /* !CONFIG_FS_VERITY */
 
@@ -213,8 +214,6 @@ static inline void fsverity_enqueue_verify_work(struct work_struct *work)
 	WARN_ON(1);
 }
 
-#endif	/* !CONFIG_FS_VERITY */
-
 /**
  * fsverity_active() - do reads from the inode need to go through fs-verity?
  * @inode: inode to check
@@ -232,5 +231,6 @@ static inline bool fsverity_active(const struct inode *inode)
 {
 	return fsverity_get_info(inode) != NULL;
 }
+#endif	/* !CONFIG_FS_VERITY */
 
 #endif	/* _LINUX_FSVERITY_H */
