diff options
Diffstat (limited to 'security')
-rw-r--r-- | security/integrity/digsig.c | 3 | ||||
-rw-r--r-- | security/integrity/ima/ima_appraise.c | 114 | ||||
-rw-r--r-- | security/integrity/ima/ima_policy.c | 46 | ||||
-rw-r--r-- | security/integrity/ima/ima_template_lib.c | 4 | ||||
-rw-r--r-- | security/integrity/integrity.h | 26 |
5 files changed, 177 insertions, 16 deletions
diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index c8c8a4a4e7a0..8a82a6c7f48a 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -75,7 +75,8 @@ int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, /* v1 API expect signature without xattr type */ return digsig_verify(keyring, sig + 1, siglen - 1, digest, digestlen); - case 2: + case 2: /* regular file data hash based signature */ + case 3: /* struct ima_file_id data based signature */ return asymmetric_verify(keyring, sig, siglen, digest, digestlen); } diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 17232bbfb9f9..cdb84dccd24e 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -13,7 +13,9 @@ #include <linux/magic.h> #include <linux/ima.h> #include <linux/evm.h> +#include <linux/fsverity.h> #include <keys/system_keyring.h> +#include <uapi/linux/fsverity.h> #include "ima.h" @@ -183,13 +185,18 @@ enum hash_algo ima_get_hash_algo(const struct evm_ima_xattr_data *xattr_value, return ima_hash_algo; switch (xattr_value->type) { + case IMA_VERITY_DIGSIG: + sig = (typeof(sig))xattr_value; + if (sig->version != 3 || xattr_len <= sizeof(*sig) || + sig->hash_algo >= HASH_ALGO__LAST) + return ima_hash_algo; + return sig->hash_algo; case EVM_IMA_XATTR_DIGSIG: sig = (typeof(sig))xattr_value; if (sig->version != 2 || xattr_len <= sizeof(*sig) || sig->hash_algo >= HASH_ALGO__LAST) return ima_hash_algo; return sig->hash_algo; - break; case IMA_XATTR_DIGEST_NG: /* first byte contains algorithm id */ ret = xattr_value->data[0]; @@ -226,6 +233,40 @@ int ima_read_xattr(struct dentry *dentry, } /* + * calc_file_id_hash - calculate the hash of the ima_file_id struct data + * @type: xattr type [enum evm_ima_xattr_type] + * @algo: hash algorithm [enum hash_algo] + * @digest: pointer to the digest to be hashed + * @hash: (out) pointer to the hash + * + * IMA signature version 3 disambiguates the data that is signed by + * indirectly signing the hash of the ima_file_id structure data. + * + * Signing the ima_file_id struct is currently only supported for + * IMA_VERITY_DIGSIG type xattrs. + * + * Return 0 on success, error code otherwise. + */ +static int calc_file_id_hash(enum evm_ima_xattr_type type, + enum hash_algo algo, const u8 *digest, + struct ima_digest_data *hash) +{ + struct ima_file_id file_id = { + .hash_type = IMA_VERITY_DIGSIG, .hash_algorithm = algo}; + unsigned int unused = HASH_MAX_DIGESTSIZE - hash_digest_size[algo]; + + if (type != IMA_VERITY_DIGSIG) + return -EINVAL; + + memcpy(file_id.hash, digest, hash_digest_size[algo]); + + hash->algo = algo; + hash->length = hash_digest_size[algo]; + + return ima_calc_buffer_hash(&file_id, sizeof(file_id) - unused, hash); +} + +/* * xattr_verify - verify xattr digest or signature * * Verify whether the hash or signature matches the file contents. @@ -236,7 +277,10 @@ static int xattr_verify(enum ima_hooks func, struct integrity_iint_cache *iint, struct evm_ima_xattr_data *xattr_value, int xattr_len, enum integrity_status *status, const char **cause) { + struct ima_max_digest_data hash; + struct signature_v2_hdr *sig; int rc = -EINVAL, hash_start = 0; + int mask; switch (xattr_value->type) { case IMA_XATTR_DIGEST_NG: @@ -246,7 +290,10 @@ static int xattr_verify(enum ima_hooks func, struct integrity_iint_cache *iint, case IMA_XATTR_DIGEST: if (*status != INTEGRITY_PASS_IMMUTABLE) { if (iint->flags & IMA_DIGSIG_REQUIRED) { - *cause = "IMA-signature-required"; + if (iint->flags & IMA_VERITY_REQUIRED) + *cause = "verity-signature-required"; + else + *cause = "IMA-signature-required"; *status = INTEGRITY_FAIL; break; } @@ -274,6 +321,20 @@ static int xattr_verify(enum ima_hooks func, struct integrity_iint_cache *iint, break; case EVM_IMA_XATTR_DIGSIG: set_bit(IMA_DIGSIG, &iint->atomic_flags); + + mask = IMA_DIGSIG_REQUIRED | IMA_VERITY_REQUIRED; + if ((iint->flags & mask) == mask) { + *cause = "verity-signature-required"; + *status = INTEGRITY_FAIL; + break; + } + + sig = (typeof(sig))xattr_value; + if (sig->version >= 3) { + *cause = "invalid-signature-version"; + *status = INTEGRITY_FAIL; + break; + } rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, (const char *)xattr_value, xattr_len, @@ -297,6 +358,44 @@ static int xattr_verify(enum ima_hooks func, struct integrity_iint_cache *iint, *status = INTEGRITY_PASS; } break; + case IMA_VERITY_DIGSIG: + set_bit(IMA_DIGSIG, &iint->atomic_flags); + + if (iint->flags & IMA_DIGSIG_REQUIRED) { + if (!(iint->flags & IMA_VERITY_REQUIRED)) { + *cause = "IMA-signature-required"; + *status = INTEGRITY_FAIL; + break; + } + } + + sig = (typeof(sig))xattr_value; + if (sig->version != 3) { + *cause = "invalid-signature-version"; + *status = INTEGRITY_FAIL; + break; + } + + rc = calc_file_id_hash(IMA_VERITY_DIGSIG, iint->ima_hash->algo, + iint->ima_hash->digest, &hash.hdr); + if (rc) { + *cause = "sigv3-hashing-error"; + *status = INTEGRITY_FAIL; + break; + } + + rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, + (const char *)xattr_value, + xattr_len, hash.digest, + hash.hdr.length); + if (rc) { + *cause = "invalid-verity-signature"; + *status = INTEGRITY_FAIL; + } else { + *status = INTEGRITY_PASS; + } + + break; default: *status = INTEGRITY_UNKNOWN; *cause = "unknown-ima-data"; @@ -396,8 +495,15 @@ int ima_appraise_measurement(enum ima_hooks func, if (rc && rc != -ENODATA) goto out; - cause = iint->flags & IMA_DIGSIG_REQUIRED ? - "IMA-signature-required" : "missing-hash"; + if (iint->flags & IMA_DIGSIG_REQUIRED) { + if (iint->flags & IMA_VERITY_REQUIRED) + cause = "verity-signature-required"; + else + cause = "IMA-signature-required"; + } else { + cause = "missing-hash"; + } + status = INTEGRITY_NOLABEL; if (file->f_mode & FMODE_CREATED) iint->flags |= IMA_NEW_FILE; diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 390a8faa77f9..73917413365b 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -1310,6 +1310,18 @@ static bool ima_validate_rule(struct ima_rule_entry *entry) !(entry->flags & IMA_MODSIG_ALLOWED)) return false; + /* + * Unlike for regular IMA 'appraise' policy rules where security.ima + * xattr may contain either a file hash or signature, the security.ima + * xattr for fsverity must contain a file signature (sigv3). Ensure + * that 'appraise' rules for fsverity require file signatures by + * checking the IMA_DIGSIG_REQUIRED flag is set. + */ + if (entry->action == APPRAISE && + (entry->flags & IMA_VERITY_REQUIRED) && + !(entry->flags & IMA_DIGSIG_REQUIRED)) + return false; + return true; } @@ -1727,21 +1739,37 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) break; case Opt_digest_type: ima_log_string(ab, "digest_type", args[0].from); - if ((strcmp(args[0].from, "verity")) == 0) + if (entry->flags & IMA_DIGSIG_REQUIRED) + result = -EINVAL; + else 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) - entry->flags |= IMA_DIGSIG_REQUIRED; - else if (IS_ENABLED(CONFIG_IMA_APPRAISE_MODSIG) && - strcmp(args[0].from, "imasig|modsig") == 0) - entry->flags |= IMA_DIGSIG_REQUIRED | + + if ((strcmp(args[0].from, "imasig")) == 0) { + if (entry->flags & IMA_VERITY_REQUIRED) + result = -EINVAL; + else + entry->flags |= IMA_DIGSIG_REQUIRED; + } else if (strcmp(args[0].from, "sigv3") == 0) { + /* Only fsverity supports sigv3 for now */ + if (entry->flags & IMA_VERITY_REQUIRED) + entry->flags |= IMA_DIGSIG_REQUIRED; + else + result = -EINVAL; + } else if (IS_ENABLED(CONFIG_IMA_APPRAISE_MODSIG) && + strcmp(args[0].from, "imasig|modsig") == 0) { + if (entry->flags & IMA_VERITY_REQUIRED) + result = -EINVAL; + else + entry->flags |= IMA_DIGSIG_REQUIRED | IMA_MODSIG_ALLOWED; - else + } else { result = -EINVAL; + } break; case Opt_appraise_flag: ima_log_string(ab, "appraise_flag", args[0].from); @@ -2183,7 +2211,9 @@ int ima_policy_show(struct seq_file *m, void *v) if (entry->template) seq_printf(m, "template=%s ", entry->template->name); if (entry->flags & IMA_DIGSIG_REQUIRED) { - if (entry->flags & IMA_MODSIG_ALLOWED) + if (entry->flags & IMA_VERITY_REQUIRED) + seq_puts(m, "appraise_type=sigv3 "); + else if (entry->flags & IMA_MODSIG_ALLOWED) seq_puts(m, "appraise_type=imasig|modsig "); else seq_puts(m, "appraise_type=imasig "); diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index 08fd74217e2c..c877f01a5471 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -535,7 +535,9 @@ int ima_eventsig_init(struct ima_event_data *event_data, { struct evm_ima_xattr_data *xattr_value = event_data->xattr_value; - if ((!xattr_value) || (xattr_value->type != EVM_IMA_XATTR_DIGSIG)) + if (!xattr_value || + (xattr_value->type != EVM_IMA_XATTR_DIGSIG && + xattr_value->type != IMA_VERITY_DIGSIG)) return ima_eventevmsig_init(event_data, field_data); return ima_write_template_field_data(xattr_value, event_data->xattr_len, diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 04e2b99cd912..7167a6e99bdc 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -79,6 +79,7 @@ enum evm_ima_xattr_type { EVM_IMA_XATTR_DIGSIG, IMA_XATTR_DIGEST_NG, EVM_XATTR_PORTABLE_DIGSIG, + IMA_VERITY_DIGSIG, IMA_XATTR_LAST }; @@ -93,7 +94,7 @@ struct evm_xattr { u8 digest[SHA1_DIGEST_SIZE]; } __packed; -#define IMA_MAX_DIGEST_SIZE 64 +#define IMA_MAX_DIGEST_SIZE HASH_MAX_DIGESTSIZE struct ima_digest_data { u8 algo; @@ -122,7 +123,14 @@ struct ima_max_digest_data { } __packed; /* - * signature format v2 - for using with asymmetric keys + * signature header format v2 - for using with asymmetric keys + * + * The signature_v2_hdr struct includes a signature format version + * to simplify defining new signature formats. + * + * signature format: + * version 2: regular file data hash based signature + * version 3: struct ima_file_id data based signature */ struct signature_v2_hdr { uint8_t type; /* xattr type */ @@ -133,6 +141,20 @@ struct signature_v2_hdr { uint8_t sig[]; /* signature payload */ } __packed; +/* + * IMA signature version 3 disambiguates the data that is signed, by + * indirectly signing the hash of the ima_file_id structure data, + * containing either the fsverity_descriptor struct digest or, in the + * future, the regular IMA file hash. + * + * (The hash of the ima_file_id structure is only of the portion used.) + */ +struct ima_file_id { + __u8 hash_type; /* xattr type [enum evm_ima_xattr_type] */ + __u8 hash_algorithm; /* Digest algorithm [enum hash_algo] */ + __u8 hash[HASH_MAX_DIGESTSIZE]; +} __packed; + /* integrity data associated with an inode */ struct integrity_iint_cache { struct rb_node rb_node; /* rooted in integrity_iint_tree */ |