diff mbox series

[RFC,v3a,09/11] ima: Add support for fsverity signatures

Message ID 20220127184614.2837938-5-roberto.sassu@huawei.com (mailing list archive)
State New, archived
Headers show
Series ima: support fs-verity digests and signatures (alternative) | expand

Commit Message

Roberto Sassu Jan. 27, 2022, 6:46 p.m. UTC
Since fsverity signatures are in PKCS#7 format, handle them as the same as
kernel modules, using the modsig code.

The main differences with modsig are: ima_read_fsverity_sig() gets the
fsverity signature with fsverity_get_signature() instead of getting it from
the file content; ima_collect_fsverity() gets the data to be hashed from
fsverity_get_formatted_digest(), instead of hashing the file content.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 Documentation/ABI/testing/ima_policy     | 10 ++++
 Documentation/security/IMA-templates.rst |  7 ++-
 include/linux/evm.h                      |  5 ++
 security/integrity/ima/ima.h             | 19 ++++++
 security/integrity/ima/ima_api.c         | 38 ++++--------
 security/integrity/ima/ima_appraise.c    | 67 ++++++++++++++++++---
 security/integrity/ima/ima_main.c        | 32 ++++++++++
 security/integrity/ima/ima_modsig.c      | 75 ++++++++++++++++++++++++
 8 files changed, 217 insertions(+), 36 deletions(-)
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy
index 444bb7ccbe03..8602f08d06bb 100644
--- a/Documentation/ABI/testing/ima_policy
+++ b/Documentation/ABI/testing/ima_policy
@@ -151,6 +151,16 @@  Description:
 
 			appraise func=SETXATTR_CHECK appraise_algos=sha256,sha384,sha512
 
+		Example of measure and appraise rules allowing fs-verity
+		signed digests on a particular filesystem identified by
+		it's fsuuid:
+
+			measure func=BPRM_CHECK digest_type=hash|verity \
+				fsuuid=... template=ima-modsig
+			appraise func=BPRM_CHECK digest_type=hash|verity \
+				ima_appraise_type=imasig fsuuid=...
+
+
 		Example of measure rule allowing fs-verity's digests on a
 		particular filesystem with indication of type of digest.
 
diff --git a/Documentation/security/IMA-templates.rst b/Documentation/security/IMA-templates.rst
index 5e31513e8ec4..96654e72a36e 100644
--- a/Documentation/security/IMA-templates.rst
+++ b/Documentation/security/IMA-templates.rst
@@ -68,11 +68,12 @@  descriptors by adding their identifier to the format string
  - 'd-ng': the digest of the event, calculated with an arbitrary hash
    algorithm (field format: [<hash algo>:]digest, where the digest
    prefix is shown only if the hash algorithm is not SHA1 or MD5);
- - 'd-modsig': the digest of the event without the appended modsig;
+ - 'd-modsig': the digest of the event without the appended modsig, or the
+   digest of an fsverity formatted digest;
  - 'd-type': the type of file digest (e.g. hash, verity[1]);
  - 'n-ng': the name of the event, without size limitations;
- - 'sig': the file signature, or the EVM portable signature if the file
-   signature is not found;
+ - 'sig': the file signature, based on either the file's/fsverity's digest[1],
+   or the EVM portable signature if the file signature is not found;
  - 'modsig' the appended file signature;
  - 'buf': the buffer data that was used to generate the hash without size limitations;
  - 'evmsig': the EVM portable signature;
diff --git a/include/linux/evm.h b/include/linux/evm.h
index 4c374be70247..3da25393b011 100644
--- a/include/linux/evm.h
+++ b/include/linux/evm.h
@@ -14,6 +14,11 @@ 
 
 struct integrity_iint_cache;
 
+static inline bool evm_protects_fsverity(void)
+{
+	return false;
+}
+
 #ifdef CONFIG_EVM
 extern int evm_set_key(void *key, size_t keylen);
 extern enum integrity_status evm_verifyxattr(struct dentry *dentry,
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 78395bed7fad..4a45a7b5743b 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -380,26 +380,45 @@  static inline int ima_read_xattr(struct dentry *dentry,
 #endif /* CONFIG_IMA_APPRAISE */
 
 #ifdef CONFIG_IMA_APPRAISE_MODSIG
+bool ima_modsig_is_verity(const struct modsig *modsig);
 int ima_read_modsig(enum ima_hooks func, const void *buf, loff_t buf_len,
 		    struct modsig **modsig);
+int ima_read_fsverity_sig(struct inode *inode, struct modsig **modsig);
 void ima_collect_modsig(struct modsig *modsig, const void *buf, loff_t size);
+void ima_collect_fsverity(struct modsig *modsig, const void *buf, loff_t size);
 int ima_get_modsig_digest(const struct modsig *modsig, enum hash_algo *algo,
 			  const u8 **digest, u32 *digest_size);
 int ima_get_raw_modsig(const struct modsig *modsig, const void **data,
 		       u32 *data_len);
 void ima_free_modsig(struct modsig *modsig);
 #else
+static inline bool ima_modsig_is_verity(const struct modsig *modsig)
+{
+	return false;
+}
+
 static inline int ima_read_modsig(enum ima_hooks func, const void *buf,
 				  loff_t buf_len, struct modsig **modsig)
 {
 	return -EOPNOTSUPP;
 }
 
+static inline int ima_read_fsverity_sig(struct inode *inode,
+					struct modsig **modsig)
+{
+	return -EOPNOTSUPP;
+}
+
 static inline void ima_collect_modsig(struct modsig *modsig, const void *buf,
 				      loff_t size)
 {
 }
 
+static inline void ima_collect_fsverity(struct modsig *modsig, const void *buf,
+					loff_t size)
+{
+}
+
 static inline int ima_get_modsig_digest(const struct modsig *modsig,
 					enum hash_algo *algo, const u8 **digest,
 					u32 *digest_size)
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index 8760c4874f7d..369f2222dd55 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -201,23 +201,6 @@  int ima_get_action(struct user_namespace *mnt_userns, struct inode *inode,
 				allowed_algos);
 }
 
-static int ima_get_verity_digest(struct integrity_iint_cache *iint,
-				 struct ima_digest_data *hash)
-{
-	u8 verity_digest[FS_VERITY_MAX_DIGEST_SIZE];
-	enum hash_algo verity_alg;
-	int rc;
-
-	rc = fsverity_get_digest(iint->inode, verity_digest, &verity_alg);
-	if (rc)
-		return -EINVAL;
-	if (hash->algo != verity_alg)
-		return -EINVAL;
-	hash->length = hash_digest_size[verity_alg];
-	memcpy(hash->digest, verity_digest, hash->length);
-	return 0;
-}
-
 /*
  * ima_collect_measurement - collect file measurement
  *
@@ -249,8 +232,12 @@  int ima_collect_measurement(struct integrity_iint_cache *iint,
 	 * the file digest without collecting the modsig in a previous
 	 * measurement rule.
 	 */
-	if (modsig)
-		ima_collect_modsig(modsig, buf, size);
+	if (modsig) {
+		if (!ima_modsig_is_verity(modsig))
+			ima_collect_modsig(modsig, buf, size);
+		else
+			ima_collect_fsverity(modsig, buf, size);
+	}
 
 	if (iint->flags & IMA_COLLECTED)
 		goto out;
@@ -266,14 +253,13 @@  int ima_collect_measurement(struct integrity_iint_cache *iint,
 	/* Initialize hash digest to 0's in case of failure */
 	memset(&hash.digest, 0, sizeof(hash.digest));
 
-	if (buf) {
+	if (buf && !(iint->flags & IMA_VERITY_DIGEST)) {
 		result = ima_calc_buffer_hash(buf, size, &hash.hdr);
-	} else if (iint->flags & IMA_VERITY_ALLOWED) {
-		result = ima_get_verity_digest(iint, &hash.hdr);
-		if (result < 0)
-			result = ima_calc_file_hash(file, &hash.hdr);
-		else
-			iint->flags |= IMA_VERITY_DIGEST;
+	} else if (buf && (iint->flags & IMA_VERITY_DIGEST)) {
+		hash.hdr.length = hash_digest_size[algo];
+		memcpy(hash.hdr.digest,
+		       ((struct fsverity_formatted_digest *)buf)->digest,
+		       hash_digest_size[algo]);
 	} else {
 		result = ima_calc_file_hash(file, &hash.hdr);
 	}
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index 17232bbfb9f9..f8dde59e64f5 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -320,7 +320,7 @@  static int modsig_verify(enum ima_hooks func, const struct modsig *modsig,
 
 	rc = integrity_modsig_verify(INTEGRITY_KEYRING_IMA, modsig);
 	if (IS_ENABLED(CONFIG_INTEGRITY_PLATFORM_KEYRING) && rc &&
-	    func == KEXEC_KERNEL_CHECK)
+	    func == KEXEC_KERNEL_CHECK && !ima_modsig_is_verity(modsig))
 		rc = integrity_modsig_verify(INTEGRITY_KEYRING_PLATFORM,
 					     modsig);
 	if (rc) {
@@ -333,6 +333,50 @@  static int modsig_verify(enum ima_hooks func, const struct modsig *modsig,
 	return rc;
 }
 
+/*
+ * fsverity_verify - verify fsverity signature
+ *
+ * Verify whether the fsverity signature is valid.
+ *
+ * Return 0 on success, error code otherwise.
+ */
+static int fsverity_verify(struct integrity_iint_cache *iint,
+			   const struct modsig *modsig,
+			   enum integrity_status *status, const char **cause)
+{
+	int rc = -EINVAL;
+
+	if (!modsig || !ima_modsig_is_verity(modsig)) {
+		if (!evm_protects_fsverity()) {
+			*cause = "EVM-fsverity-not-protected";
+			*status = INTEGRITY_FAIL;
+			return rc;
+		}
+
+		if (*status != INTEGRITY_PASS_IMMUTABLE) {
+			if (iint->flags & IMA_DIGSIG_REQUIRED) {
+				*cause = "IMA-signature-required";
+				*status = INTEGRITY_FAIL;
+				return rc;
+			}
+			clear_bit(IMA_DIGSIG, &iint->atomic_flags);
+		} else {
+			set_bit(IMA_DIGSIG, &iint->atomic_flags);
+		}
+
+		/*
+		 * EVM already verified the actual fsverity digest, nothing else
+		 * is required.
+		 */
+		*status = INTEGRITY_PASS;
+		rc = 0;
+	} else {
+		rc = modsig_verify(NONE, modsig, status, cause);
+	}
+
+	return rc;
+}
+
 /*
  * ima_check_blacklist - determine if the binary is blacklisted.
  *
@@ -352,7 +396,8 @@  int ima_check_blacklist(struct integrity_iint_cache *iint,
 	if (!(iint->flags & IMA_CHECK_BLACKLIST))
 		return 0;
 
-	if (iint->flags & IMA_MODSIG_ALLOWED && modsig) {
+	if (iint->flags & IMA_MODSIG_ALLOWED && modsig &&
+	    !ima_modsig_is_verity(modsig)) {
 		ima_get_modsig_digest(modsig, &hash_algo, &digest, &digestsize);
 
 		rc = is_binary_blacklisted(digest, digestsize);
@@ -385,14 +430,16 @@  int ima_appraise_measurement(enum ima_hooks func,
 	struct inode *inode = d_backing_inode(dentry);
 	enum integrity_status status = INTEGRITY_UNKNOWN;
 	int rc = xattr_len;
-	bool try_modsig = iint->flags & IMA_MODSIG_ALLOWED && modsig;
+	bool try_modsig = iint->flags & IMA_MODSIG_ALLOWED && modsig &&
+			  !ima_modsig_is_verity(modsig);
+	bool try_fsverity = iint->flags & IMA_VERITY_DIGEST;
 
-	/* If not appraising a modsig, we need an xattr. */
-	if (!(inode->i_opflags & IOP_XATTR) && !try_modsig)
+	/* If not appraising a modsig or an fsverity file, we need an xattr. */
+	if (!(inode->i_opflags & IOP_XATTR) && !try_modsig && !try_fsverity)
 		return INTEGRITY_UNKNOWN;
 
 	/* If reading the xattr failed and there's no modsig, error out. */
-	if (rc <= 0 && !try_modsig) {
+	if (rc <= 0 && !try_modsig && !try_fsverity) {
 		if (rc && rc != -ENODATA)
 			goto out;
 
@@ -446,6 +493,12 @@  int ima_appraise_measurement(enum ima_hooks func,
 	     rc == -ENOKEY))
 		rc = modsig_verify(func, modsig, &status, &cause);
 
+	/*
+	 * If we have a fsverity sig, no modsig and no imasig, then try
+	 * verifying the fsverity sig.
+	 */
+	if (try_fsverity)
+		rc = fsverity_verify(iint, modsig, &status, &cause);
 out:
 	/*
 	 * File signatures on some filesystems can not be properly verified.
@@ -463,7 +516,7 @@  int ima_appraise_measurement(enum ima_hooks func,
 	} else if (status != INTEGRITY_PASS) {
 		/* Fix mode, but don't replace file signatures. */
 		if ((ima_appraise & IMA_APPRAISE_FIX) && !try_modsig &&
-		    (!xattr_value ||
+		    !try_fsverity && (!xattr_value ||
 		     xattr_value->type != EVM_IMA_XATTR_DIGSIG)) {
 			if (!ima_fix_xattr(dentry, iint))
 				status = INTEGRITY_PASS;
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 774accb62275..1f78f31c3e89 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -25,6 +25,7 @@ 
 #include <linux/xattr.h>
 #include <linux/ima.h>
 #include <linux/iversion.h>
+#include <linux/fsverity.h>
 #include <linux/fs.h>
 
 #include "ima.h"
@@ -216,6 +217,8 @@  static int process_measurement(struct file *file, const struct cred *cred,
 	bool violation_check;
 	enum hash_algo hash_algo;
 	unsigned int allowed_algos = 0;
+	u8 fsverity_buf[FS_VERITY_MAX_FMT_DIGEST_SIZE];
+	ssize_t fsverity_buf_len;
 
 	if (!ima_policy_flag || !S_ISREG(inode->i_mode))
 		return 0;
@@ -330,9 +333,38 @@  static int process_measurement(struct file *file, const struct cred *cred,
 			    iint->flags & IMA_MEASURED)
 				action |= IMA_MEASURE;
 		}
+
+		/*
+		 * Read the fsverity sig if allowed by the policy, and allow
+		 * an additional measurement list entry, if needed, based on the
+		 * template format and whether the file was already measured.
+		 */
+		if (!modsig && IS_VERITY(inode) &&
+		    (iint->flags & IMA_VERITY_ALLOWED)) {
+			rc = ima_read_fsverity_sig(inode, &modsig);
+
+			if (!rc && ima_template_has_modsig(template_desc) &&
+			    iint->flags & IMA_MEASURED)
+				action |= IMA_MEASURE;
+		}
 	}
 
 	hash_algo = ima_get_hash_algo(xattr_value, xattr_len);
+	/*
+	 * Fsverity verification method is enabled only if all others are not
+	 * available.
+	 */
+	if (IS_VERITY(inode) && (iint->flags & IMA_VERITY_ALLOWED) &&
+	    !xattr_value && (!modsig || ima_modsig_is_verity(modsig))) {
+		fsverity_buf_len = fsverity_get_formatted_digest(inode,
+								 fsverity_buf,
+								 &hash_algo);
+		if (fsverity_buf_len > 0) {
+			buf = fsverity_buf;
+			size = fsverity_buf_len;
+			iint->flags |= IMA_VERITY_DIGEST;
+		}
+	}
 
 	rc = ima_collect_measurement(iint, file, buf, size, hash_algo, modsig);
 	if (rc != 0 && rc != -EBADF && rc != -EINVAL)
diff --git a/security/integrity/ima/ima_modsig.c b/security/integrity/ima/ima_modsig.c
index fb25723c65bc..66c19846477c 100644
--- a/security/integrity/ima/ima_modsig.c
+++ b/security/integrity/ima/ima_modsig.c
@@ -9,6 +9,7 @@ 
  */
 
 #include <linux/types.h>
+#include <linux/fsverity.h>
 #include <linux/module_signature.h>
 #include <keys/asymmetric-type.h>
 #include <crypto/pkcs7.h>
@@ -16,6 +17,8 @@ 
 #include "ima.h"
 
 struct modsig {
+	bool is_verity;
+
 	struct pkcs7_message *pkcs7_msg;
 
 	enum hash_algo hash_algo;
@@ -32,6 +35,11 @@  struct modsig {
 	u8 raw_pkcs7[];
 };
 
+bool ima_modsig_is_verity(const struct modsig *modsig)
+{
+	return modsig->is_verity;
+}
+
 /*
  * ima_read_modsig - Read modsig from buf.
  *
@@ -87,6 +95,51 @@  int ima_read_modsig(enum ima_hooks func, const void *buf, loff_t buf_len,
 	return 0;
 }
 
+/*
+ * ima_read_fsverity_sig - Read fsverity sig from inode.
+ *
+ * Return: 0 on success, error code otherwise.
+ */
+int ima_read_fsverity_sig(struct inode *inode, struct modsig **modsig)
+{
+	struct modsig *hdr;
+	u8 *signature;
+	ssize_t signature_size;
+	int rc;
+
+	signature_size = fsverity_get_signature(inode, &signature);
+	if (signature_size < 0)
+		return signature_size;
+
+	/*
+	 * Allocate signature_size additional bytes to hold the raw PKCS#7 data.
+	 */
+	hdr = kzalloc(sizeof(*hdr) + signature_size, GFP_KERNEL);
+	if (!hdr) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	hdr->pkcs7_msg = pkcs7_parse_message(signature, signature_size);
+	if (IS_ERR(hdr->pkcs7_msg)) {
+		rc = PTR_ERR(hdr->pkcs7_msg);
+		kfree(hdr);
+		goto out;
+	}
+
+	memcpy(hdr->raw_pkcs7, signature, signature_size);
+	hdr->raw_pkcs7_len = signature_size;
+
+	/* We don't know the hash algorithm yet. */
+	hdr->hash_algo = HASH_ALGO__LAST;
+	hdr->is_verity = true;
+
+	*modsig = hdr;
+out:
+	kfree(signature);
+	return rc;
+}
+
 /**
  * ima_collect_modsig - Calculate the file hash without the appended signature.
  *
@@ -113,6 +166,28 @@  void ima_collect_modsig(struct modsig *modsig, const void *buf, loff_t size)
 			      &modsig->digest_size, &modsig->hash_algo);
 }
 
+/**
+ * ima_collect_fsverity - Calculate the digest of the fsverity formatted digest.
+ *
+ * Pass the same data used to verify the fsverity signature in
+ * fs/verity/signature.c.
+ */
+void ima_collect_fsverity(struct modsig *modsig, const void *buf, loff_t size)
+{
+	int rc;
+
+	rc = pkcs7_supply_detached_data(modsig->pkcs7_msg, buf, size);
+	if (rc)
+		return;
+
+	/*
+	 * Ask the PKCS7 code to calculate the digest of the fsverity formatted
+	 * digest.
+	 */
+	rc = pkcs7_get_digest(modsig->pkcs7_msg, &modsig->digest,
+			      &modsig->digest_size, &modsig->hash_algo);
+}
+
 int ima_modsig_verify(struct key *keyring, const struct modsig *modsig)
 {
 	return verify_pkcs7_message_sig(NULL, 0, modsig->pkcs7_msg, keyring,