From 644664627d21648735adfa733956f94064d3417d Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 14 Apr 2022 16:46:39 -0400 Subject: ima: fix 'd-ng' comments and documentation Initially the 'd-ng' template field did not prefix the digest with either "md5" or "sha1" hash algorithms. Prior to being upstreamed this changed, but the comments and documentation were not updated. Fix the comments and documentation. Fixes: 4d7aeee73f53 ("ima: define new template ima-ng and template fields d-ng and n-ng") Reported-by: Eric Biggers Reviewed-by: Stefan Berger Signed-off-by: Mimi Zohar --- Documentation/security/IMA-templates.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'Documentation/security') diff --git a/Documentation/security/IMA-templates.rst b/Documentation/security/IMA-templates.rst index 1a91d92950a7..cab97f49971d 100644 --- a/Documentation/security/IMA-templates.rst +++ b/Documentation/security/IMA-templates.rst @@ -66,8 +66,7 @@ descriptors by adding their identifier to the format string calculated with the SHA1 or MD5 hash algorithm; - '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: [:]digest, where the digest - prefix is shown only if the hash algorithm is not SHA1 or MD5); + algorithm (field format: :digest); - 'd-modsig': the digest of the event without the appended modsig; - 'n-ng': the name of the event, without size limitations; - 'sig': the file signature, or the EVM portable signature if the file -- cgit v1.2.3 From 989dc72511f7b57b94b42eabfcbe79d9070de6e3 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 23 Dec 2021 12:29:56 -0500 Subject: ima: define a new template field named 'd-ngv2' and templates In preparation to differentiate between unsigned regular IMA file hashes and fs-verity's file digests in the IMA measurement list, define a new template field named 'd-ngv2'. Also define two new templates named 'ima-ngv2' and 'ima-sigv2', which include the new 'd-ngv2' field. Signed-off-by: Mimi Zohar --- Documentation/admin-guide/kernel-parameters.txt | 3 +- Documentation/security/IMA-templates.rst | 4 ++ security/integrity/ima/ima_template.c | 4 ++ security/integrity/ima/ima_template_lib.c | 76 +++++++++++++++++++++---- security/integrity/ima/ima_template_lib.h | 4 ++ 5 files changed, 79 insertions(+), 12 deletions(-) (limited to 'Documentation/security') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 3f1cc5e317ed..5e866be89f5d 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1903,7 +1903,8 @@ ima_template= [IMA] Select one of defined IMA measurements template formats. - Formats: { "ima" | "ima-ng" | "ima-sig" } + Formats: { "ima" | "ima-ng" | "ima-ngv2" | "ima-sig" | + "ima-sigv2" } Default: "ima-ng" ima_template_fmt= diff --git a/Documentation/security/IMA-templates.rst b/Documentation/security/IMA-templates.rst index cab97f49971d..eafc4e34f890 100644 --- a/Documentation/security/IMA-templates.rst +++ b/Documentation/security/IMA-templates.rst @@ -67,6 +67,8 @@ 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: :digest); + - 'd-ngv2': same as d-ng, but prefixed with the "ima" digest type + (field format: ::digest); - 'd-modsig': the digest of the event without the appended modsig; - 'n-ng': the name of the event, without size limitations; - 'sig': the file signature, or the EVM portable signature if the file @@ -87,7 +89,9 @@ Below, there is the list of defined template descriptors: - "ima": its format is ``d|n``; - "ima-ng" (default): its format is ``d-ng|n-ng``; + - "ima-ngv2": its format is ``d-ngv2|n-ng``; - "ima-sig": its format is ``d-ng|n-ng|sig``; + - "ima-sigv2": its format is ``d-ngv2|n-ng|sig``; - "ima-buf": its format is ``d-ng|n-ng|buf``; - "ima-modsig": its format is ``d-ng|n-ng|sig|d-modsig|modsig``; - "evm-sig": its format is ``d-ng|n-ng|evmsig|xattrnames|xattrlengths|xattrvalues|iuid|igid|imode``; diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index db1ad6d7a57f..c25079faa208 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -20,6 +20,8 @@ static struct ima_template_desc builtin_templates[] = { {.name = IMA_TEMPLATE_IMA_NAME, .fmt = IMA_TEMPLATE_IMA_FMT}, {.name = "ima-ng", .fmt = "d-ng|n-ng"}, {.name = "ima-sig", .fmt = "d-ng|n-ng|sig"}, + {.name = "ima-ngv2", .fmt = "d-ngv2|n-ng"}, + {.name = "ima-sigv2", .fmt = "d-ngv2|n-ng|sig"}, {.name = "ima-buf", .fmt = "d-ng|n-ng|buf"}, {.name = "ima-modsig", .fmt = "d-ng|n-ng|sig|d-modsig|modsig"}, {.name = "evm-sig", @@ -38,6 +40,8 @@ static const struct ima_template_field supported_fields[] = { .field_show = ima_show_template_string}, {.field_id = "d-ng", .field_init = ima_eventdigest_ng_init, .field_show = ima_show_template_digest_ng}, + {.field_id = "d-ngv2", .field_init = ima_eventdigest_ngv2_init, + .field_show = ima_show_template_digest_ngv2}, {.field_id = "n-ng", .field_init = ima_eventname_ng_init, .field_show = ima_show_template_string}, {.field_id = "sig", .field_init = ima_eventsig_init, diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index 4b6706f864d4..409023e620d6 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -24,11 +24,22 @@ static bool ima_template_hash_algo_allowed(u8 algo) enum data_formats { DATA_FMT_DIGEST = 0, DATA_FMT_DIGEST_WITH_ALGO, + DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO, DATA_FMT_STRING, DATA_FMT_HEX, DATA_FMT_UINT }; +enum digest_type { + DIGEST_TYPE_IMA, + DIGEST_TYPE__LAST +}; + +#define DIGEST_TYPE_NAME_LEN_MAX 4 /* including NUL */ +static const char * const digest_type_name[DIGEST_TYPE__LAST] = { + [DIGEST_TYPE_IMA] = "ima" +}; + static int ima_write_template_field_data(const void *data, const u32 datalen, enum data_formats datafmt, struct ima_field_data *field_data) @@ -72,8 +83,9 @@ static void ima_show_template_data_ascii(struct seq_file *m, u32 buflen = field_data->len; switch (datafmt) { + case DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO: case DATA_FMT_DIGEST_WITH_ALGO: - buf_ptr = strnchr(field_data->data, buflen, ':'); + buf_ptr = strrchr(field_data->data, ':'); if (buf_ptr != field_data->data) seq_printf(m, "%s", field_data->data); @@ -178,6 +190,14 @@ void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show, field_data); } +void ima_show_template_digest_ngv2(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data) +{ + ima_show_template_field_data(m, show, + DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO, + field_data); +} + void ima_show_template_string(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data) { @@ -265,28 +285,35 @@ int ima_parse_buf(void *bufstartp, void *bufendp, void **bufcurp, } static int ima_eventdigest_init_common(const u8 *digest, u32 digestsize, - u8 hash_algo, + u8 digest_type, u8 hash_algo, struct ima_field_data *field_data) { /* * digest formats: * - DATA_FMT_DIGEST: digest * - DATA_FMT_DIGEST_WITH_ALGO: + ':' + '\0' + digest, + * - DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO: + * + ':' + + ':' + '\0' + digest, * * where 'DATA_FMT_DIGEST' is the original digest format ('d') * with a hash size limitation of 20 bytes, + * where is "ima", * where is the hash_algo_name[] string. */ - u8 buffer[CRYPTO_MAX_ALG_NAME + 2 + IMA_MAX_DIGEST_SIZE] = { 0 }; + u8 buffer[DIGEST_TYPE_NAME_LEN_MAX + CRYPTO_MAX_ALG_NAME + 2 + + IMA_MAX_DIGEST_SIZE] = { 0 }; enum data_formats fmt = DATA_FMT_DIGEST; u32 offset = 0; - if (hash_algo < HASH_ALGO__LAST) { + if (digest_type < DIGEST_TYPE__LAST && hash_algo < HASH_ALGO__LAST) { + fmt = DATA_FMT_DIGEST_WITH_TYPE_AND_ALGO; + offset += 1 + sprintf(buffer, "%s:%s:", + digest_type_name[digest_type], + hash_algo_name[hash_algo]); + } else if (hash_algo < HASH_ALGO__LAST) { fmt = DATA_FMT_DIGEST_WITH_ALGO; - offset += snprintf(buffer, CRYPTO_MAX_ALG_NAME + 1, "%s", - hash_algo_name[hash_algo]); - buffer[offset] = ':'; - offset += 2; + offset += 1 + sprintf(buffer, "%s:", + hash_algo_name[hash_algo]); } if (digest) @@ -361,7 +388,8 @@ int ima_eventdigest_init(struct ima_event_data *event_data, cur_digestsize = hash.hdr.length; out: return ima_eventdigest_init_common(cur_digest, cur_digestsize, - HASH_ALGO__LAST, field_data); + DIGEST_TYPE__LAST, HASH_ALGO__LAST, + field_data); } /* @@ -382,7 +410,32 @@ int ima_eventdigest_ng_init(struct ima_event_data *event_data, hash_algo = event_data->iint->ima_hash->algo; out: return ima_eventdigest_init_common(cur_digest, cur_digestsize, - hash_algo, field_data); + DIGEST_TYPE__LAST, hash_algo, + field_data); +} + +/* + * This function writes the digest of an event (without size limit), + * prefixed with both the digest type and hash algorithm. + */ +int ima_eventdigest_ngv2_init(struct ima_event_data *event_data, + struct ima_field_data *field_data) +{ + u8 *cur_digest = NULL, hash_algo = ima_hash_algo; + u32 cur_digestsize = 0; + u8 digest_type = DIGEST_TYPE_IMA; + + if (event_data->violation) /* recording a violation. */ + goto out; + + cur_digest = event_data->iint->ima_hash->digest; + cur_digestsize = event_data->iint->ima_hash->length; + + hash_algo = event_data->iint->ima_hash->algo; +out: + return ima_eventdigest_init_common(cur_digest, cur_digestsize, + digest_type, hash_algo, + field_data); } /* @@ -417,7 +470,8 @@ int ima_eventdigest_modsig_init(struct ima_event_data *event_data, } return ima_eventdigest_init_common(cur_digest, cur_digestsize, - hash_algo, field_data); + DIGEST_TYPE__LAST, hash_algo, + field_data); } static int ima_eventname_init_common(struct ima_event_data *event_data, diff --git a/security/integrity/ima/ima_template_lib.h b/security/integrity/ima/ima_template_lib.h index c71f1de95753..9f7c335f304f 100644 --- a/security/integrity/ima/ima_template_lib.h +++ b/security/integrity/ima/ima_template_lib.h @@ -21,6 +21,8 @@ void ima_show_template_digest(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); +void ima_show_template_digest_ngv2(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data); void ima_show_template_string(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); void ima_show_template_sig(struct seq_file *m, enum ima_show_type show, @@ -38,6 +40,8 @@ int ima_eventname_init(struct ima_event_data *event_data, struct ima_field_data *field_data); int ima_eventdigest_ng_init(struct ima_event_data *event_data, struct ima_field_data *field_data); +int ima_eventdigest_ngv2_init(struct ima_event_data *event_data, + struct ima_field_data *field_data); int ima_eventdigest_modsig_init(struct ima_event_data *event_data, struct ima_field_data *field_data); int ima_eventname_ng_init(struct ima_event_data *event_data, -- cgit v1.2.3 From 54f03916fb892441f9a9b579db9ad7925cdeb395 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 23 Dec 2021 12:29:56 -0500 Subject: 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: Stefan Berger Signed-off-by: Mimi Zohar --- Documentation/ABI/testing/ima_policy | 14 +++++++-- Documentation/security/IMA-templates.rst | 2 +- security/integrity/ima/ima_api.c | 47 +++++++++++++++++++++++++++++-- security/integrity/ima/ima_main.c | 2 +- security/integrity/ima/ima_policy.c | 38 ++++++++++++++++++++++++- security/integrity/ima/ima_template_lib.c | 10 +++++-- security/integrity/integrity.h | 1 + 7 files changed, 103 insertions(+), 11 deletions(-) (limited to 'Documentation/security') diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy index 839fab811b18..0a8caed393e3 100644 --- a/Documentation/ABI/testing/ima_policy +++ b/Documentation/ABI/testing/ima_policy @@ -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 diff --git a/Documentation/security/IMA-templates.rst b/Documentation/security/IMA-templates.rst index eafc4e34f890..09b5fac38195 100644 --- a/Documentation/security/IMA-templates.rst +++ b/Documentation/security/IMA-templates.rst @@ -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: :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); - 'd-modsig': the digest of the event without the appended modsig; - 'n-ng': the name of the event, without size limitations; diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index c6805af46211..c1e76282b5ee 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -14,6 +14,7 @@ #include #include #include +#include #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; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 1aebf63ad7a6..040b03ddc1c7 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -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 */ diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index eea6e92500b8..390a8faa77f9 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -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) diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index 409023e620d6..08fd74217e2c 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -32,12 +32,14 @@ enum data_formats { enum digest_type { DIGEST_TYPE_IMA, + DIGEST_TYPE_VERITY, DIGEST_TYPE__LAST }; -#define DIGEST_TYPE_NAME_LEN_MAX 4 /* including NUL */ +#define DIGEST_TYPE_NAME_LEN_MAX 7 /* including NUL */ static const char * const digest_type_name[DIGEST_TYPE__LAST] = { - [DIGEST_TYPE_IMA] = "ima" + [DIGEST_TYPE_IMA] = "ima", + [DIGEST_TYPE_VERITY] = "verity" }; static int ima_write_template_field_data(const void *data, const u32 datalen, @@ -297,7 +299,7 @@ static int ima_eventdigest_init_common(const u8 *digest, u32 digestsize, * * where 'DATA_FMT_DIGEST' is the original digest format ('d') * with a hash size limitation of 20 bytes, - * where is "ima", + * where is either "ima" or "verity", * where is the hash_algo_name[] string. */ u8 buffer[DIGEST_TYPE_NAME_LEN_MAX + CRYPTO_MAX_ALG_NAME + 2 + @@ -432,6 +434,8 @@ int ima_eventdigest_ngv2_init(struct ima_event_data *event_data, cur_digestsize = event_data->iint->ima_hash->length; hash_algo = event_data->iint->ima_hash->algo; + if (event_data->iint->flags & IMA_VERITY_REQUIRED) + digest_type = DIGEST_TYPE_VERITY; out: return ima_eventdigest_init_common(cur_digest, cur_digestsize, digest_type, hash_algo, diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 3510e413ea17..04e2b99cd912 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -40,6 +40,7 @@ #define IMA_FAIL_UNVERIFIABLE_SIGS 0x10000000 #define IMA_MODSIG_ALLOWED 0x20000000 #define IMA_CHECK_BLACKLIST 0x40000000 +#define IMA_VERITY_REQUIRED 0x80000000 #define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \ IMA_HASH | IMA_APPRAISE_SUBMASK) -- cgit v1.2.3 From 398c42e2c46c88b186ec29097a05b7a8d93b7ce5 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 24 Nov 2021 10:56:33 -0500 Subject: ima: support fs-verity file digest based version 3 signatures IMA may verify a file's integrity against a "good" value stored in the 'security.ima' xattr or as an appended signature, based on policy. When the "good value" is stored in the xattr, the xattr may contain a file hash or signature. In either case, the "good" value is preceded by a header. The first byte of the xattr header indicates the type of data - hash, signature - stored in the xattr. To support storing fs-verity signatures in the 'security.ima' xattr requires further differentiating the fs-verity signature from the existing IMA signature. In addition the signatures stored in 'security.ima' xattr, need to be disambiguated. Instead of directly signing the fs-verity digest, a new signature format version 3 is defined as the hash of the ima_file_id structure, which identifies the type of signature and the digest. The IMA policy defines "which" files are to be measured, verified, and/or audited. For those files being verified, the policy rules indicate "how" the file should be verified. For example to require a file be signed, the appraise policy rule must include the 'appraise_type' option. appraise_type:= [imasig] | [imasig|modsig] | [sigv3] where 'imasig' is the original or signature format v2 (default), where 'modsig' is an appended signature, where 'sigv3' is the signature format v3. The policy rule must also indicate the type of digest, if not the IMA default, by first specifying the digest type: digest_type:= [verity] The following policy rule requires fsverity signatures. The rule may be constrained, for example based on a fsuuid or LSM label. appraise func=BPRM_CHECK digest_type=verity appraise_type=sigv3 Acked-by: Stefan Berger Signed-off-by: Mimi Zohar --- Documentation/ABI/testing/ima_policy | 31 +++++++- Documentation/security/IMA-templates.rst | 4 +- security/integrity/digsig.c | 3 +- security/integrity/ima/ima_appraise.c | 114 ++++++++++++++++++++++++++++-- security/integrity/ima/ima_policy.c | 46 +++++++++--- security/integrity/ima/ima_template_lib.c | 4 +- security/integrity/integrity.h | 26 ++++++- 7 files changed, 209 insertions(+), 19 deletions(-) (limited to 'Documentation/security') diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy index 0a8caed393e3..db17fc8a0c9f 100644 --- a/Documentation/ABI/testing/ima_policy +++ b/Documentation/ABI/testing/ima_policy @@ -48,7 +48,15 @@ Description: fgroup:= decimal value lsm: are LSM specific option: - appraise_type:= [imasig] [imasig|modsig] + appraise_type:= [imasig] | [imasig|modsig] | [sigv3] + where 'imasig' is the original or the signature + format v2. + where 'modsig' is an appended signature, + where 'sigv3' is the signature format v3. (Currently + limited to fsverity digest based signatures + stored in security.ima xattr. Requires + specifying "digest_type=verity" first.) + appraise_flag:= [check_blacklist] Currently, blacklist check is only for files signed with appended signature. @@ -159,3 +167,24 @@ Description: measure func=FILE_CHECK digest_type=verity \ template=ima-ngv2 + + Example of 'measure' and 'appraise' rules requiring fs-verity + signatures (format version 3) stored in security.ima xattr. + + The 'measure' rule specifies the 'ima-sigv3' template option, + which includes the indication of type of digest and the file + signature in the measurement list. + + measure func=BPRM_CHECK digest_type=verity \ + template=ima-sigv3 + + + The 'appraise' rule specifies the type and signature format + version (sigv3) required. + + appraise func=BPRM_CHECK digest_type=verity \ + appraise_type=sigv3 + + All of these policy rules could, for example, be constrained + either based on a filesystem's UUID (fsuuid) or based on LSM + labels. diff --git a/Documentation/security/IMA-templates.rst b/Documentation/security/IMA-templates.rst index 09b5fac38195..15b4add314fc 100644 --- a/Documentation/security/IMA-templates.rst +++ b/Documentation/security/IMA-templates.rst @@ -71,8 +71,8 @@ descriptors by adding their identifier to the format string (field format: ::digest); - 'd-modsig': the digest of the event without the appended modsig; - '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 'security.ima' contains a file hash. - '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/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 #include #include +#include #include +#include #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]; @@ -225,6 +232,40 @@ int ima_read_xattr(struct dentry *dentry, return ret; } +/* + * 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 * @@ -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, @@ -296,6 +357,44 @@ static int xattr_verify(enum ima_hooks func, struct integrity_iint_cache *iint, } else { *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; @@ -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 */ -- cgit v1.2.3