Commit 54f03916 authored by Mimi Zohar's avatar Mimi Zohar
Browse files

ima: permit fsverity's file digests in the IMA measurement list



Permit fsverity's file digest (a hash of struct fsverity_descriptor) to
be included in the IMA measurement list, based on the new measurement
policy rule 'digest_type=verity' option.

To differentiate between a regular IMA file hash from an fsverity's
file digest, use the new d-ngv2 format field included in the ima-ngv2
template.

The following policy rule requires fsverity file digests and specifies
the new 'ima-ngv2' template, which contains the new 'd-ngv2' field.  The
policy rule may be constrained, for example based on a fsuuid or LSM
label.

measure func=FILE_CHECK digest_type=verity template=ima-ngv2

Acked-by: default avatarStefan Berger <stefanb@linux.ibm.com>
Signed-off-by: default avatarMimi Zohar <zohar@linux.ibm.com>
parent 989dc725
Loading
Loading
Loading
Loading
+12 −2
Original line number Diff line number Diff line
@@ -27,8 +27,9 @@ Description:
				[fowner=] [fgroup=]]
			lsm:	[[subj_user=] [subj_role=] [subj_type=]
				 [obj_user=] [obj_role=] [obj_type=]]
			option:	[[appraise_type=]] [template=] [permit_directio]
				[appraise_flag=] [appraise_algos=] [keyrings=]
			option:	[digest_type=] [template=] [permit_directio]
				[appraise_type=] [appraise_flag=]
				[appraise_algos=] [keyrings=]
		  base:
			func:= [BPRM_CHECK][MMAP_CHECK][CREDS_CHECK][FILE_CHECK][MODULE_CHECK]
				[FIRMWARE_CHECK]
@@ -51,6 +52,9 @@ Description:
			appraise_flag:= [check_blacklist]
			Currently, blacklist check is only for files signed with appended
			signature.
			digest_type:= verity
			    Require fs-verity's file digest instead of the
			    regular IMA file hash.
			keyrings:= list of keyrings
			(eg, .builtin_trusted_keys|.ima). Only valid
			when action is "measure" and func is KEY_CHECK.
@@ -149,3 +153,9 @@ Description:
		security.ima xattr of a file:

			appraise func=SETXATTR_CHECK appraise_algos=sha256,sha384,sha512

		Example of a 'measure' rule requiring fs-verity's digests
		with indication of type of digest in the measurement list.

			measure func=FILE_CHECK digest_type=verity \
				template=ima-ngv2
+1 −1
Original line number Diff line number Diff line
@@ -67,7 +67,7 @@ descriptors by adding their identifier to the format string
 - 'n': the name of the event (i.e. the file name), with size up to 255 bytes;
 - 'd-ng': the digest of the event, calculated with an arbitrary hash
   algorithm (field format: <hash algo>:digest);
 - 'd-ngv2': same as d-ng, but prefixed with the "ima" digest type
 - 'd-ngv2': same as d-ng, but prefixed with the "ima" or "verity" digest type
   (field format: <digest type>:<hash algo>:digest);
 - 'd-modsig': the digest of the event without the appended modsig;
 - 'n-ng': the name of the event, without size limitations;
+44 −3
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
#include <linux/xattr.h>
#include <linux/evm.h>
#include <linux/iversion.h>
#include <linux/fsverity.h>

#include "ima.h"

@@ -200,6 +201,32 @@ 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_max_digest_data *hash)
{
	enum hash_algo verity_alg;
	int ret;

	/*
	 * On failure, 'measure' policy rules will result in a file data
	 * hash containing 0's.
	 */
	ret = fsverity_get_digest(iint->inode, hash->digest, &verity_alg);
	if (ret)
		return ret;

	/*
	 * Unlike in the case of actually calculating the file hash, in
	 * the fsverity case regardless of the hash algorithm, return
	 * the verity digest to be included in the measurement list. A
	 * mismatch between the verity algorithm and the xattr signature
	 * algorithm, if one exists, will be detected later.
	 */
	hash->hdr.algo = verity_alg;
	hash->hdr.length = hash_digest_size[verity_alg];
	return 0;
}

/*
 * ima_collect_measurement - collect file measurement
 *
@@ -242,16 +269,30 @@ int ima_collect_measurement(struct integrity_iint_cache *iint,
	 */
	i_version = inode_query_iversion(inode);
	hash.hdr.algo = algo;
	hash.hdr.length = hash_digest_size[algo];

	/* Initialize hash digest to 0's in case of failure */
	memset(&hash.digest, 0, sizeof(hash.digest));

	if (buf)
	if (iint->flags & IMA_VERITY_REQUIRED) {
		result = ima_get_verity_digest(iint, &hash);
		switch (result) {
		case 0:
			break;
		case -ENODATA:
			audit_cause = "no-verity-digest";
			break;
		default:
			audit_cause = "invalid-verity-digest";
			break;
		}
	} else if (buf) {
		result = ima_calc_buffer_hash(buf, size, &hash.hdr);
	else
	} else {
		result = ima_calc_file_hash(file, &hash.hdr);
	}

	if (result && result != -EBADF && result != -EINVAL)
	if (result == -ENOMEM)
		goto out;

	length = sizeof(hash.hdr) + hash.hdr.length;
+1 −1
Original line number Diff line number Diff line
@@ -335,7 +335,7 @@ static int process_measurement(struct file *file, const struct cred *cred,
	hash_algo = ima_get_hash_algo(xattr_value, xattr_len);

	rc = ima_collect_measurement(iint, file, buf, size, hash_algo, modsig);
	if (rc != 0 && rc != -EBADF && rc != -EINVAL)
	if (rc == -ENOMEM)
		goto out_locked;

	if (!pathbuf)	/* ima_rdwr_violation possibly pre-fetched */
+37 −1
Original line number Diff line number Diff line
@@ -1023,6 +1023,7 @@ enum policy_opt {
	Opt_fowner_gt, Opt_fgroup_gt,
	Opt_uid_lt, Opt_euid_lt, Opt_gid_lt, Opt_egid_lt,
	Opt_fowner_lt, Opt_fgroup_lt,
	Opt_digest_type,
	Opt_appraise_type, Opt_appraise_flag, Opt_appraise_algos,
	Opt_permit_directio, Opt_pcr, Opt_template, Opt_keyrings,
	Opt_label, Opt_err
@@ -1065,6 +1066,7 @@ static const match_table_t policy_tokens = {
	{Opt_egid_lt, "egid<%s"},
	{Opt_fowner_lt, "fowner<%s"},
	{Opt_fgroup_lt, "fgroup<%s"},
	{Opt_digest_type, "digest_type=%s"},
	{Opt_appraise_type, "appraise_type=%s"},
	{Opt_appraise_flag, "appraise_flag=%s"},
	{Opt_appraise_algos, "appraise_algos=%s"},
@@ -1172,6 +1174,21 @@ static void check_template_modsig(const struct ima_template_desc *template)
#undef MSG
}

/*
 * Warn if the template does not contain the given field.
 */
static void check_template_field(const struct ima_template_desc *template,
				 const char *field, const char *msg)
{
	int i;

	for (i = 0; i < template->num_fields; i++)
		if (!strcmp(template->fields[i]->field_id, field))
			return;

	pr_notice_once("%s", msg);
}

static bool ima_validate_rule(struct ima_rule_entry *entry)
{
	/* Ensure that the action is set and is compatible with the flags */
@@ -1214,7 +1231,8 @@ static bool ima_validate_rule(struct ima_rule_entry *entry)
				     IMA_INMASK | IMA_EUID | IMA_PCR |
				     IMA_FSNAME | IMA_GID | IMA_EGID |
				     IMA_FGROUP | IMA_DIGSIG_REQUIRED |
				     IMA_PERMIT_DIRECTIO | IMA_VALIDATE_ALGOS))
				     IMA_PERMIT_DIRECTIO | IMA_VALIDATE_ALGOS |
				     IMA_VERITY_REQUIRED))
			return false;

		break;
@@ -1707,6 +1725,13 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
						   LSM_SUBJ_TYPE,
						   AUDIT_SUBJ_TYPE);
			break;
		case Opt_digest_type:
			ima_log_string(ab, "digest_type", args[0].from);
			if ((strcmp(args[0].from, "verity")) == 0)
				entry->flags |= IMA_VERITY_REQUIRED;
			else
				result = -EINVAL;
			break;
		case Opt_appraise_type:
			ima_log_string(ab, "appraise_type", args[0].from);
			if ((strcmp(args[0].from, "imasig")) == 0)
@@ -1797,6 +1822,15 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
		check_template_modsig(template_desc);
	}

	/* d-ngv2 template field recommended for unsigned fs-verity digests */
	if (!result && entry->action == MEASURE &&
	    entry->flags & IMA_VERITY_REQUIRED) {
		template_desc = entry->template ? entry->template :
						  ima_template_desc_current();
		check_template_field(template_desc, "d-ngv2",
				     "verity rules should include d-ngv2");
	}

	audit_log_format(ab, "res=%d", !result);
	audit_log_end(ab);
	return result;
@@ -2154,6 +2188,8 @@ int ima_policy_show(struct seq_file *m, void *v)
		else
			seq_puts(m, "appraise_type=imasig ");
	}
	if (entry->flags & IMA_VERITY_REQUIRED)
		seq_puts(m, "digest_type=verity ");
	if (entry->flags & IMA_CHECK_BLACKLIST)
		seq_puts(m, "appraise_flag=check_blacklist ");
	if (entry->flags & IMA_PERMIT_DIRECTIO)
Loading