diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index b4f8cad53ddb..c4488c4a6d8a 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -167,6 +167,7 @@ enum lsm_order { * @initcall_fs: LSM callback for fs_initcall setup, optional * @initcall_device: LSM callback for device_initcall() setup, optional * @initcall_late: LSM callback for late_initcall() setup, optional + * @initcall_late_sync: LSM callback for late_initcall_sync() setup, optional */ struct lsm_info { const struct lsm_id *id; @@ -182,6 +183,7 @@ struct lsm_info { int (*initcall_fs)(void); int (*initcall_device)(void); int (*initcall_late)(void); + int (*initcall_late_sync)(void); }; #define DEFINE_LSM(lsm) \ diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index f4d25e045808..b3a9f86809b0 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -347,4 +347,14 @@ config IMA_STAGING On kexec, staging is aborted and any staged measurement records are copied to the secondary kernel. +config IMA_INIT_LATE_SYNC + bool "Initialise IMA at late_initcall_sync" + default n + help + This option initialises IMA at late_initcall_sync for platforms + where TPM device probing is deferred. + When this option is enabled, modules that access files in the + initramfs through usermode helper calls such as request_module() + during initcall must not be built-in. Otherwise, IMA may miss + file measurements for them. endif diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index caaedd4b58fd..10214f73ca1e 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -55,6 +55,8 @@ enum binary_lists { /* current content of the policy */ extern int ima_policy_flag; +extern struct mutex ima_write_mutex; + /* bitset of digests algorithms allowed in the setxattr hook */ extern atomic_t ima_setxattr_allowed_hash_algorithms; @@ -75,6 +77,7 @@ extern struct ima_algo_desc *ima_algo_array __ro_after_init; extern int ima_appraise; extern struct tpm_chip *ima_tpm_chip; extern const char boot_aggregate_name[]; +extern const char boot_aggregate_late_name[]; /* IMA event related data */ struct ima_event_data { @@ -466,6 +469,8 @@ void *ima_policy_start(struct seq_file *m, loff_t *pos); void *ima_policy_next(struct seq_file *m, void *v, loff_t *pos); void ima_policy_stop(struct seq_file *m, void *v); int ima_policy_show(struct seq_file *m, void *v); +void ima_measure_loaded_policy(void); +int ima_measure_raw_policy(const char *buf, size_t buf_len); /* Appraise integrity measurements */ #define IMA_APPRAISE_ENFORCE 0x01 diff --git a/security/integrity/ima/ima_efi.c b/security/integrity/ima/ima_efi.c index bca57d836cb9..be7911009454 100644 --- a/security/integrity/ima/ima_efi.c +++ b/security/integrity/ima/ima_efi.c @@ -17,6 +17,8 @@ static const char * const sb_arch_rules[] = { #endif #if IS_ENABLED(CONFIG_INTEGRITY_MACHINE_KEYRING) && IS_ENABLED(CONFIG_IMA_KEYRINGS_PERMIT_SIGNED_BY_BUILTIN_OR_SECONDARY) "appraise func=POLICY_CHECK appraise_type=imasig", +#else + "measure func=CRITICAL_DATA label=ima_policy", #endif "measure func=MODULE_CHECK", NULL diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 174a94740da1..2a0bca554316 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -32,7 +32,9 @@ */ #define STAGED_REQ_LENGTH 21 -static DEFINE_MUTEX(ima_write_mutex); +/* lock for protecting concurrent IMA policy updates */ +DEFINE_MUTEX(ima_write_mutex); + static DEFINE_MUTEX(ima_measure_mutex); static long ima_measure_users; static struct task_struct *measure_writer; @@ -596,6 +598,7 @@ static ssize_t ima_write_policy(struct file *file, const char __user *buf, 1, 0); result = -EACCES; } else { + ima_measure_raw_policy(data, datalen); result = ima_parse_add_rule(data); } mutex_unlock(&ima_write_mutex); @@ -752,6 +755,10 @@ static int ima_release_policy(struct inode *inode, struct file *file) } ima_update_policy(); + + mutex_lock(&ima_write_mutex); + ima_measure_loaded_policy(); + mutex_unlock(&ima_write_mutex); #if !defined(CONFIG_IMA_WRITE_POLICY) && !defined(CONFIG_IMA_READ_POLICY) securityfs_remove(file->f_path.dentry); #elif defined(CONFIG_IMA_WRITE_POLICY) diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index 7e0aa09a12e6..d53f4d89a53e 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -22,6 +22,7 @@ /* name for boot aggregate entry */ const char boot_aggregate_name[] = "boot_aggregate"; +const char boot_aggregate_late_name[] = "boot_aggregate_late"; struct tpm_chip *ima_tpm_chip; /* Add the boot aggregate to the IMA measurement list and extend @@ -45,11 +46,11 @@ static int __init ima_add_boot_aggregate(void) const char *audit_cause = "ENOMEM"; struct ima_template_entry *entry; struct ima_iint_cache tmp_iint, *iint = &tmp_iint; - struct ima_event_data event_data = { .iint = iint, - .filename = boot_aggregate_name }; + struct ima_event_data event_data = { .iint = iint }; struct ima_max_digest_data hash; struct ima_digest_data *hash_hdr = container_of(&hash.hdr, struct ima_digest_data, hdr); + const char *filename; int result = -ENOMEM; int violation = 0; @@ -59,6 +60,12 @@ static int __init ima_add_boot_aggregate(void) iint->ima_hash->algo = ima_hash_algo; iint->ima_hash->length = hash_digest_size[ima_hash_algo]; + if (IS_ENABLED(CONFIG_IMA_INIT_LATE_SYNC)) + filename = boot_aggregate_late_name; + else + filename = boot_aggregate_name; + event_data.filename = filename; + /* * With TPM 2.0 hash agility, TPM chips could support multiple TPM * PCR banks, allowing firmware to configure and enable different @@ -86,7 +93,7 @@ static int __init ima_add_boot_aggregate(void) } result = ima_store_template(entry, violation, NULL, - boot_aggregate_name, + filename, CONFIG_IMA_MEASURE_PCR_IDX); if (result < 0) { ima_free_template_entry(entry); @@ -95,7 +102,7 @@ static int __init ima_add_boot_aggregate(void) } return 0; err_out: - integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, boot_aggregate_name, op, + integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, filename, op, audit_cause, result, 0); return result; } diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index ff52becc3031..ab1e53b3210d 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -1258,6 +1258,28 @@ int ima_measure_critical_data(const char *event_label, } EXPORT_SYMBOL_GPL(ima_measure_critical_data); +/** + * ima_measure_raw_policy - Measure the raw policy write buffer + * @buf: pointer to the buffer containing the raw policy data + * @buf_len: size of the buffer + * + * Measure the raw policy buffer sent to the IMA policy securityfs file. The + * buffer is written from userspace, and the measurement is performed before + * parsing it. This measurement includes any data written on the policy file + * such as malformed policy rules and comments. + * + * Return 0 on success, a negative value otherwise. + */ +int ima_measure_raw_policy(const char *buf, size_t buf_len) +{ + if (!buf || !buf_len) + return -EINVAL; + + return process_buffer_measurement(&nop_mnt_idmap, NULL, buf, buf_len, + "ima_policy_written", POLICY_CHECK, + 0, NULL, false, NULL, 0); +} + #ifdef CONFIG_INTEGRITY_ASYMMETRIC_KEYS /** @@ -1376,5 +1398,9 @@ DEFINE_LSM(ima) = { .order = LSM_ORDER_LAST, .blobs = &ima_blob_sizes, /* Start IMA after the TPM is available */ +#ifndef CONFIG_IMA_INIT_LATE_SYNC .initcall_late = init_ima, +#else + .initcall_late_sync = init_ima, +#endif }; diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index b1c010e8eb13..f79d07bb63c6 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -53,6 +53,8 @@ #define INVALID_PCR(a) (((a) < 0) || \ (a) >= (sizeof_field(struct ima_iint_cache, measured_pcrs) * 8)) +static size_t max_rule_len; + int ima_policy_flag; static int temp_ima_appraise; static int build_ima_appraise __ro_after_init; @@ -540,6 +542,8 @@ static bool ima_match_rule_data(struct ima_rule_entry *rule, opt_list = rule->label; break; + case POLICY_CHECK: + return true; default: return false; } @@ -586,6 +590,10 @@ static bool ima_match_rules(struct ima_rule_entry *rule, return false; switch (func) { + case POLICY_CHECK: + if (inode) + break; + fallthrough; case KEY_CHECK: case CRITICAL_DATA: return ((rule->func == func) && @@ -955,6 +963,8 @@ void __init ima_init_policy(void) { int build_appraise_entries, arch_entries; + max_rule_len = 255; + /* if !ima_policy, we load NO default rules */ if (ima_policy) add_rules(dont_measure_rules, ARRAY_SIZE(dont_measure_rules), @@ -1994,6 +2004,9 @@ ssize_t ima_parse_add_rule(char *rule) list_add_tail(&entry->list, &ima_temp_rules); + if (len > max_rule_len) + max_rule_len = len; + return len; } @@ -2021,7 +2034,6 @@ const char *const func_tokens[] = { __ima_hooks(__ima_hook_stringify) }; -#ifdef CONFIG_IMA_READ_POLICY enum { mask_exec = 0, mask_write, mask_read, mask_append }; @@ -2323,7 +2335,6 @@ int ima_policy_show(struct seq_file *m, void *v) seq_puts(m, "\n"); return 0; } -#endif /* CONFIG_IMA_READ_POLICY */ #if defined(CONFIG_IMA_APPRAISE) && defined(CONFIG_INTEGRITY_TRUSTED_KEYRING) /* @@ -2380,3 +2391,83 @@ bool ima_appraise_signature(enum kernel_read_file_id id) return found; } #endif /* CONFIG_IMA_APPRAISE && CONFIG_INTEGRITY_TRUSTED_KEYRING */ + +/** + * ima_measure_loaded_policy - measure the active IMA policy ruleset + * + * Must be called with ima_write_mutex held, as it performs two + * separate RCU read passes over ima_rules and relies on the mutex + * to prevent concurrent policy updates between them. + */ +void ima_measure_loaded_policy(void) +{ + const char *event_name = "ima_policy_loaded"; + const char *op = "measure_loaded_ima_policy"; + size_t rule_len = max_rule_len + 2; + struct ima_rule_entry *rule_entry; + struct list_head *ima_rules_tmp; + struct seq_file file = { 0 }; + int result = -ENOMEM; + size_t file_len = 0; + char *rule; + + lockdep_assert_held(&ima_write_mutex); + + rule = kmalloc(rule_len, GFP_KERNEL); + if (!rule) { + integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, event_name, + op, "ENOMEM", result, 0); + return; + } + + /* calculate IMA policy rules memory size */ + file.buf = rule; + file.read_pos = 0; + file.size = rule_len; + file.count = 0; + + rcu_read_lock(); + ima_rules_tmp = rcu_dereference(ima_rules); + list_for_each_entry_rcu(rule_entry, ima_rules_tmp, list) { + ima_policy_show(&file, rule_entry); + + if (seq_has_overflowed(&file)) { + result = -E2BIG; + integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, + event_name, op, "rule_length", + result, 0); + rcu_read_unlock(); + goto free_rule; + } + + file_len += file.count; + file.count = 0; + } + rcu_read_unlock(); + + /* copy IMA policy rules to a buffer for measuring */ + file.buf = kmalloc(file_len, GFP_KERNEL); + if (!file.buf) { + integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, event_name, + op, "ENOMEM", result, 0); + goto free_rule; + } + + file.read_pos = 0; + file.size = file_len; + file.count = 0; + + rcu_read_lock(); + ima_rules_tmp = rcu_dereference(ima_rules); + list_for_each_entry_rcu(rule_entry, ima_rules_tmp, list) { + ima_policy_show(&file, rule_entry); + } + rcu_read_unlock(); + + ima_measure_critical_data("ima_policy", event_name, file.buf, + file.count, false, NULL, 0); + + kfree(file.buf); +free_rule: + kfree(rule); +} diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index 0e627eac9c33..8a89236f926c 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -363,7 +363,8 @@ int ima_eventdigest_init(struct ima_event_data *event_data, goto out; } - if ((const char *)event_data->filename == boot_aggregate_name) { + if ((const char *)event_data->filename == boot_aggregate_name || + (const char *)event_data->filename == boot_aggregate_late_name) { if (ima_tpm_chip) { hash.hdr.algo = HASH_ALGO_SHA1; result = ima_calc_boot_aggregate(hash_hdr); diff --git a/security/lsm_init.c b/security/lsm_init.c index 7c0fd17f1601..a1ad641811de 100644 --- a/security/lsm_init.c +++ b/security/lsm_init.c @@ -556,13 +556,22 @@ device_initcall(security_initcall_device); * security_initcall_late - Run the LSM late initcalls */ static int __init security_initcall_late(void) +{ + return lsm_initcall(late); +} +late_initcall(security_initcall_late); + +/** + * security_initcall_late_sync - Run the LSM late initcalls sync + */ +static int __init security_initcall_late_sync(void) { int rc; - rc = lsm_initcall(late); + rc = lsm_initcall(late_sync); lsm_pr_dbg("all enabled LSMs fully activated\n"); call_blocking_lsm_notifier(LSM_STARTED_ALL, NULL); return rc; } -late_initcall(security_initcall_late); +late_initcall_sync(security_initcall_late_sync);