Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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 <stefanb@linux.ibm.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>

+103 -11
+12 -2
Documentation/ABI/testing/ima_policy
··· 27 27 [fowner=] [fgroup=]] 28 28 lsm: [[subj_user=] [subj_role=] [subj_type=] 29 29 [obj_user=] [obj_role=] [obj_type=]] 30 - option: [[appraise_type=]] [template=] [permit_directio] 31 - [appraise_flag=] [appraise_algos=] [keyrings=] 30 + option: [digest_type=] [template=] [permit_directio] 31 + [appraise_type=] [appraise_flag=] 32 + [appraise_algos=] [keyrings=] 32 33 base: 33 34 func:= [BPRM_CHECK][MMAP_CHECK][CREDS_CHECK][FILE_CHECK][MODULE_CHECK] 34 35 [FIRMWARE_CHECK] ··· 52 51 appraise_flag:= [check_blacklist] 53 52 Currently, blacklist check is only for files signed with appended 54 53 signature. 54 + digest_type:= verity 55 + Require fs-verity's file digest instead of the 56 + regular IMA file hash. 55 57 keyrings:= list of keyrings 56 58 (eg, .builtin_trusted_keys|.ima). Only valid 57 59 when action is "measure" and func is KEY_CHECK. ··· 153 149 security.ima xattr of a file: 154 150 155 151 appraise func=SETXATTR_CHECK appraise_algos=sha256,sha384,sha512 152 + 153 + Example of a 'measure' rule requiring fs-verity's digests 154 + with indication of type of digest in the measurement list. 155 + 156 + measure func=FILE_CHECK digest_type=verity \ 157 + template=ima-ngv2
+1 -1
Documentation/security/IMA-templates.rst
··· 67 67 - 'n': the name of the event (i.e. the file name), with size up to 255 bytes; 68 68 - 'd-ng': the digest of the event, calculated with an arbitrary hash 69 69 algorithm (field format: <hash algo>:digest); 70 - - 'd-ngv2': same as d-ng, but prefixed with the "ima" digest type 70 + - 'd-ngv2': same as d-ng, but prefixed with the "ima" or "verity" digest type 71 71 (field format: <digest type>:<hash algo>:digest); 72 72 - 'd-modsig': the digest of the event without the appended modsig; 73 73 - 'n-ng': the name of the event, without size limitations;
+44 -3
security/integrity/ima/ima_api.c
··· 14 14 #include <linux/xattr.h> 15 15 #include <linux/evm.h> 16 16 #include <linux/iversion.h> 17 + #include <linux/fsverity.h> 17 18 18 19 #include "ima.h" 19 20 ··· 201 200 allowed_algos); 202 201 } 203 202 203 + static int ima_get_verity_digest(struct integrity_iint_cache *iint, 204 + struct ima_max_digest_data *hash) 205 + { 206 + enum hash_algo verity_alg; 207 + int ret; 208 + 209 + /* 210 + * On failure, 'measure' policy rules will result in a file data 211 + * hash containing 0's. 212 + */ 213 + ret = fsverity_get_digest(iint->inode, hash->digest, &verity_alg); 214 + if (ret) 215 + return ret; 216 + 217 + /* 218 + * Unlike in the case of actually calculating the file hash, in 219 + * the fsverity case regardless of the hash algorithm, return 220 + * the verity digest to be included in the measurement list. A 221 + * mismatch between the verity algorithm and the xattr signature 222 + * algorithm, if one exists, will be detected later. 223 + */ 224 + hash->hdr.algo = verity_alg; 225 + hash->hdr.length = hash_digest_size[verity_alg]; 226 + return 0; 227 + } 228 + 204 229 /* 205 230 * ima_collect_measurement - collect file measurement 206 231 * ··· 269 242 */ 270 243 i_version = inode_query_iversion(inode); 271 244 hash.hdr.algo = algo; 245 + hash.hdr.length = hash_digest_size[algo]; 272 246 273 247 /* Initialize hash digest to 0's in case of failure */ 274 248 memset(&hash.digest, 0, sizeof(hash.digest)); 275 249 276 - if (buf) 250 + if (iint->flags & IMA_VERITY_REQUIRED) { 251 + result = ima_get_verity_digest(iint, &hash); 252 + switch (result) { 253 + case 0: 254 + break; 255 + case -ENODATA: 256 + audit_cause = "no-verity-digest"; 257 + break; 258 + default: 259 + audit_cause = "invalid-verity-digest"; 260 + break; 261 + } 262 + } else if (buf) { 277 263 result = ima_calc_buffer_hash(buf, size, &hash.hdr); 278 - else 264 + } else { 279 265 result = ima_calc_file_hash(file, &hash.hdr); 266 + } 280 267 281 - if (result && result != -EBADF && result != -EINVAL) 268 + if (result == -ENOMEM) 282 269 goto out; 283 270 284 271 length = sizeof(hash.hdr) + hash.hdr.length;
+1 -1
security/integrity/ima/ima_main.c
··· 335 335 hash_algo = ima_get_hash_algo(xattr_value, xattr_len); 336 336 337 337 rc = ima_collect_measurement(iint, file, buf, size, hash_algo, modsig); 338 - if (rc != 0 && rc != -EBADF && rc != -EINVAL) 338 + if (rc == -ENOMEM) 339 339 goto out_locked; 340 340 341 341 if (!pathbuf) /* ima_rdwr_violation possibly pre-fetched */
+37 -1
security/integrity/ima/ima_policy.c
··· 1023 1023 Opt_fowner_gt, Opt_fgroup_gt, 1024 1024 Opt_uid_lt, Opt_euid_lt, Opt_gid_lt, Opt_egid_lt, 1025 1025 Opt_fowner_lt, Opt_fgroup_lt, 1026 + Opt_digest_type, 1026 1027 Opt_appraise_type, Opt_appraise_flag, Opt_appraise_algos, 1027 1028 Opt_permit_directio, Opt_pcr, Opt_template, Opt_keyrings, 1028 1029 Opt_label, Opt_err ··· 1066 1065 {Opt_egid_lt, "egid<%s"}, 1067 1066 {Opt_fowner_lt, "fowner<%s"}, 1068 1067 {Opt_fgroup_lt, "fgroup<%s"}, 1068 + {Opt_digest_type, "digest_type=%s"}, 1069 1069 {Opt_appraise_type, "appraise_type=%s"}, 1070 1070 {Opt_appraise_flag, "appraise_flag=%s"}, 1071 1071 {Opt_appraise_algos, "appraise_algos=%s"}, ··· 1174 1172 #undef MSG 1175 1173 } 1176 1174 1175 + /* 1176 + * Warn if the template does not contain the given field. 1177 + */ 1178 + static void check_template_field(const struct ima_template_desc *template, 1179 + const char *field, const char *msg) 1180 + { 1181 + int i; 1182 + 1183 + for (i = 0; i < template->num_fields; i++) 1184 + if (!strcmp(template->fields[i]->field_id, field)) 1185 + return; 1186 + 1187 + pr_notice_once("%s", msg); 1188 + } 1189 + 1177 1190 static bool ima_validate_rule(struct ima_rule_entry *entry) 1178 1191 { 1179 1192 /* Ensure that the action is set and is compatible with the flags */ ··· 1231 1214 IMA_INMASK | IMA_EUID | IMA_PCR | 1232 1215 IMA_FSNAME | IMA_GID | IMA_EGID | 1233 1216 IMA_FGROUP | IMA_DIGSIG_REQUIRED | 1234 - IMA_PERMIT_DIRECTIO | IMA_VALIDATE_ALGOS)) 1217 + IMA_PERMIT_DIRECTIO | IMA_VALIDATE_ALGOS | 1218 + IMA_VERITY_REQUIRED)) 1235 1219 return false; 1236 1220 1237 1221 break; ··· 1725 1707 LSM_SUBJ_TYPE, 1726 1708 AUDIT_SUBJ_TYPE); 1727 1709 break; 1710 + case Opt_digest_type: 1711 + ima_log_string(ab, "digest_type", args[0].from); 1712 + if ((strcmp(args[0].from, "verity")) == 0) 1713 + entry->flags |= IMA_VERITY_REQUIRED; 1714 + else 1715 + result = -EINVAL; 1716 + break; 1728 1717 case Opt_appraise_type: 1729 1718 ima_log_string(ab, "appraise_type", args[0].from); 1730 1719 if ((strcmp(args[0].from, "imasig")) == 0) ··· 1820 1795 template_desc = entry->template ? entry->template : 1821 1796 ima_template_desc_current(); 1822 1797 check_template_modsig(template_desc); 1798 + } 1799 + 1800 + /* d-ngv2 template field recommended for unsigned fs-verity digests */ 1801 + if (!result && entry->action == MEASURE && 1802 + entry->flags & IMA_VERITY_REQUIRED) { 1803 + template_desc = entry->template ? entry->template : 1804 + ima_template_desc_current(); 1805 + check_template_field(template_desc, "d-ngv2", 1806 + "verity rules should include d-ngv2"); 1823 1807 } 1824 1808 1825 1809 audit_log_format(ab, "res=%d", !result); ··· 2188 2154 else 2189 2155 seq_puts(m, "appraise_type=imasig "); 2190 2156 } 2157 + if (entry->flags & IMA_VERITY_REQUIRED) 2158 + seq_puts(m, "digest_type=verity "); 2191 2159 if (entry->flags & IMA_CHECK_BLACKLIST) 2192 2160 seq_puts(m, "appraise_flag=check_blacklist "); 2193 2161 if (entry->flags & IMA_PERMIT_DIRECTIO)
+7 -3
security/integrity/ima/ima_template_lib.c
··· 32 32 33 33 enum digest_type { 34 34 DIGEST_TYPE_IMA, 35 + DIGEST_TYPE_VERITY, 35 36 DIGEST_TYPE__LAST 36 37 }; 37 38 38 - #define DIGEST_TYPE_NAME_LEN_MAX 4 /* including NUL */ 39 + #define DIGEST_TYPE_NAME_LEN_MAX 7 /* including NUL */ 39 40 static const char * const digest_type_name[DIGEST_TYPE__LAST] = { 40 - [DIGEST_TYPE_IMA] = "ima" 41 + [DIGEST_TYPE_IMA] = "ima", 42 + [DIGEST_TYPE_VERITY] = "verity" 41 43 }; 42 44 43 45 static int ima_write_template_field_data(const void *data, const u32 datalen, ··· 299 297 * 300 298 * where 'DATA_FMT_DIGEST' is the original digest format ('d') 301 299 * with a hash size limitation of 20 bytes, 302 - * where <digest type> is "ima", 300 + * where <digest type> is either "ima" or "verity", 303 301 * where <hash algo> is the hash_algo_name[] string. 304 302 */ 305 303 u8 buffer[DIGEST_TYPE_NAME_LEN_MAX + CRYPTO_MAX_ALG_NAME + 2 + ··· 434 432 cur_digestsize = event_data->iint->ima_hash->length; 435 433 436 434 hash_algo = event_data->iint->ima_hash->algo; 435 + if (event_data->iint->flags & IMA_VERITY_REQUIRED) 436 + digest_type = DIGEST_TYPE_VERITY; 437 437 out: 438 438 return ima_eventdigest_init_common(cur_digest, cur_digestsize, 439 439 digest_type, hash_algo,
+1
security/integrity/integrity.h
··· 40 40 #define IMA_FAIL_UNVERIFIABLE_SIGS 0x10000000 41 41 #define IMA_MODSIG_ALLOWED 0x20000000 42 42 #define IMA_CHECK_BLACKLIST 0x40000000 43 + #define IMA_VERITY_REQUIRED 0x80000000 43 44 44 45 #define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \ 45 46 IMA_HASH | IMA_APPRAISE_SUBMASK)