From 23fa18eaa71266feff7ba8d83022d9e1cc83c65a Mon Sep 17 00:00:00 2001 From: hc <hc@nodka.com> Date: Fri, 10 May 2024 07:42:03 +0000 Subject: [PATCH] disable pwm7 --- kernel/security/integrity/ima/ima_policy.c | 874 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 files changed, 742 insertions(+), 132 deletions(-) diff --git a/kernel/security/integrity/ima/ima_policy.c b/kernel/security/integrity/ima/ima_policy.c index 2d5a3da..1c403e8 100644 --- a/kernel/security/integrity/ima/ima_policy.c +++ b/kernel/security/integrity/ima/ima_policy.c @@ -1,17 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2008 IBM Corporation * Author: Mimi Zohar <zohar@us.ibm.com> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 2 of the License. - * * ima_policy.c * - initialize default measure policy rules - * */ -#include <linux/module.h> + +#include <linux/init.h> #include <linux/list.h> +#include <linux/kernel_read_file.h> #include <linux/fs.h> #include <linux/security.h> #include <linux/magic.h> @@ -20,6 +18,7 @@ #include <linux/rculist.h> #include <linux/genhd.h> #include <linux/seq_file.h> +#include <linux/ima.h> #include "ima.h" @@ -34,6 +33,7 @@ #define IMA_EUID 0x0080 #define IMA_PCR 0x0100 #define IMA_FSNAME 0x0200 +#define IMA_KEYRINGS 0x0400 #define UNKNOWN 0 #define MEASURE 0x0001 /* same as IMA_MEASURE */ @@ -45,7 +45,7 @@ #define DONT_HASH 0x0200 #define INVALID_PCR(a) (((a) < 0) || \ - (a) >= (FIELD_SIZEOF(struct integrity_iint_cache, measured_pcrs) * 8)) + (a) >= (sizeof_field(struct integrity_iint_cache, measured_pcrs) * 8)) int ima_policy_flag; static int temp_ima_appraise; @@ -57,6 +57,13 @@ }; enum policy_types { ORIGINAL_TCB = 1, DEFAULT_TCB }; + +enum policy_rule_list { IMA_DEFAULT_POLICY = 1, IMA_CUSTOM_POLICY }; + +struct ima_rule_opt_list { + size_t count; + char *items[]; +}; struct ima_rule_entry { struct list_head list; @@ -73,10 +80,12 @@ int pcr; struct { void *rule; /* LSM file metadata specific */ - void *args_p; /* audit value */ + char *args_p; /* audit value */ int type; /* audit type */ } lsm[MAX_LSM_RULES]; char *fsname; + struct ima_rule_opt_list *keyrings; /* Measure keys added to these keyrings */ + struct ima_template_desc *template; }; /* @@ -104,7 +113,8 @@ .flags = IMA_FSMAGIC}, {.action = DONT_MEASURE, .fsmagic = CGROUP2_SUPER_MAGIC, .flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE, .fsmagic = NSFS_MAGIC, .flags = IMA_FSMAGIC} + {.action = DONT_MEASURE, .fsmagic = NSFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = EFIVARFS_MAGIC, .flags = IMA_FSMAGIC} }; static struct ima_rule_entry original_measurement_rules[] __ro_after_init = { @@ -147,6 +157,7 @@ {.action = DONT_APPRAISE, .fsmagic = SELINUX_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = SMACK_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = NSFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = EFIVARFS_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = CGROUP_SUPER_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = CGROUP2_SUPER_MAGIC, .flags = IMA_FSMAGIC}, #ifdef CONFIG_IMA_WRITE_POLICY @@ -193,6 +204,9 @@ .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, }; +/* An array of architecture specific rules */ +static struct ima_rule_entry *arch_policy_entry __ro_after_init; + static LIST_HEAD(ima_default_rules); static LIST_HEAD(ima_policy_rules); static LIST_HEAD(ima_temp_rules); @@ -228,6 +242,8 @@ ima_use_secure_boot = true; else if (strcmp(p, "fail_securely") == 0) ima_fail_unverifiable_sigs = true; + else + pr_err("policy \"%s\" not found", p); } return 1; @@ -241,48 +257,256 @@ } __setup("ima_appraise_tcb", default_appraise_policy_setup); +static struct ima_rule_opt_list *ima_alloc_rule_opt_list(const substring_t *src) +{ + struct ima_rule_opt_list *opt_list; + size_t count = 0; + char *src_copy; + char *cur, *next; + size_t i; + + src_copy = match_strdup(src); + if (!src_copy) + return ERR_PTR(-ENOMEM); + + next = src_copy; + while ((cur = strsep(&next, "|"))) { + /* Don't accept an empty list item */ + if (!(*cur)) { + kfree(src_copy); + return ERR_PTR(-EINVAL); + } + count++; + } + + /* Don't accept an empty list */ + if (!count) { + kfree(src_copy); + return ERR_PTR(-EINVAL); + } + + opt_list = kzalloc(struct_size(opt_list, items, count), GFP_KERNEL); + if (!opt_list) { + kfree(src_copy); + return ERR_PTR(-ENOMEM); + } + + /* + * strsep() has already replaced all instances of '|' with '\0', + * leaving a byte sequence of NUL-terminated strings. Reference each + * string with the array of items. + * + * IMPORTANT: Ownership of the allocated buffer is transferred from + * src_copy to the first element in the items array. To free the + * buffer, kfree() must only be called on the first element of the + * array. + */ + for (i = 0, cur = src_copy; i < count; i++) { + opt_list->items[i] = cur; + cur = strchr(cur, '\0') + 1; + } + opt_list->count = count; + + return opt_list; +} + +static void ima_free_rule_opt_list(struct ima_rule_opt_list *opt_list) +{ + if (!opt_list) + return; + + if (opt_list->count) { + kfree(opt_list->items[0]); + opt_list->count = 0; + } + + kfree(opt_list); +} + +static void ima_lsm_free_rule(struct ima_rule_entry *entry) +{ + int i; + + for (i = 0; i < MAX_LSM_RULES; i++) { + ima_filter_rule_free(entry->lsm[i].rule); + kfree(entry->lsm[i].args_p); + } +} + +static void ima_free_rule(struct ima_rule_entry *entry) +{ + if (!entry) + return; + + /* + * entry->template->fields may be allocated in ima_parse_rule() but that + * reference is owned by the corresponding ima_template_desc element in + * the defined_templates list and cannot be freed here + */ + kfree(entry->fsname); + ima_free_rule_opt_list(entry->keyrings); + ima_lsm_free_rule(entry); + kfree(entry); +} + +static struct ima_rule_entry *ima_lsm_copy_rule(struct ima_rule_entry *entry) +{ + struct ima_rule_entry *nentry; + int i; + + /* + * Immutable elements are copied over as pointers and data; only + * lsm rules can change + */ + nentry = kmemdup(entry, sizeof(*nentry), GFP_KERNEL); + if (!nentry) + return NULL; + + memset(nentry->lsm, 0, sizeof_field(struct ima_rule_entry, lsm)); + + for (i = 0; i < MAX_LSM_RULES; i++) { + if (!entry->lsm[i].args_p) + continue; + + nentry->lsm[i].type = entry->lsm[i].type; + nentry->lsm[i].args_p = entry->lsm[i].args_p; + + ima_filter_rule_init(nentry->lsm[i].type, Audit_equal, + nentry->lsm[i].args_p, + &nentry->lsm[i].rule); + if (!nentry->lsm[i].rule) + pr_warn("rule for LSM \'%s\' is undefined\n", + nentry->lsm[i].args_p); + } + return nentry; +} + +static int ima_lsm_update_rule(struct ima_rule_entry *entry) +{ + int i; + struct ima_rule_entry *nentry; + + nentry = ima_lsm_copy_rule(entry); + if (!nentry) + return -ENOMEM; + + list_replace_rcu(&entry->list, &nentry->list); + synchronize_rcu(); + /* + * ima_lsm_copy_rule() shallow copied all references, except for the + * LSM references, from entry to nentry so we only want to free the LSM + * references and the entry itself. All other memory refrences will now + * be owned by nentry. + */ + for (i = 0; i < MAX_LSM_RULES; i++) + ima_filter_rule_free(entry->lsm[i].rule); + kfree(entry); + + return 0; +} + +static bool ima_rule_contains_lsm_cond(struct ima_rule_entry *entry) +{ + int i; + + for (i = 0; i < MAX_LSM_RULES; i++) + if (entry->lsm[i].args_p) + return true; + + return false; +} + /* * The LSM policy can be reloaded, leaving the IMA LSM based rules referring * to the old, stale LSM policy. Update the IMA LSM based rules to reflect - * the reloaded LSM policy. We assume the rules still exist; and BUG_ON() if - * they don't. + * the reloaded LSM policy. */ static void ima_lsm_update_rules(void) { - struct ima_rule_entry *entry; + struct ima_rule_entry *entry, *e; int result; - int i; - list_for_each_entry(entry, &ima_policy_rules, list) { - for (i = 0; i < MAX_LSM_RULES; i++) { - if (!entry->lsm[i].rule) - continue; - result = security_filter_rule_init(entry->lsm[i].type, - Audit_equal, - entry->lsm[i].args_p, - &entry->lsm[i].rule); - BUG_ON(!entry->lsm[i].rule); + list_for_each_entry_safe(entry, e, &ima_policy_rules, list) { + if (!ima_rule_contains_lsm_cond(entry)) + continue; + + result = ima_lsm_update_rule(entry); + if (result) { + pr_err("lsm rule update error %d\n", result); + return; } } } +int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event, + void *lsm_data) +{ + if (event != LSM_POLICY_CHANGE) + return NOTIFY_DONE; + + ima_lsm_update_rules(); + return NOTIFY_OK; +} + /** - * ima_match_rules - determine whether an inode matches the measure rule. + * ima_match_keyring - determine whether the keyring matches the measure rule + * @rule: a pointer to a rule + * @keyring: name of the keyring to match against the measure rule + * @cred: a pointer to a credentials structure for user validation + * + * Returns true if keyring matches one in the rule, false otherwise. + */ +static bool ima_match_keyring(struct ima_rule_entry *rule, + const char *keyring, const struct cred *cred) +{ + bool matched = false; + size_t i; + + if ((rule->flags & IMA_UID) && !rule->uid_op(cred->uid, rule->uid)) + return false; + + if (!rule->keyrings) + return true; + + if (!keyring) + return false; + + for (i = 0; i < rule->keyrings->count; i++) { + if (!strcmp(rule->keyrings->items[i], keyring)) { + matched = true; + break; + } + } + + return matched; +} + +/** + * ima_match_rules - determine whether an inode matches the policy rule. * @rule: a pointer to a rule * @inode: a pointer to an inode * @cred: a pointer to a credentials structure for user validation * @secid: the secid of the task to be validated * @func: LIM hook identifier * @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC) + * @keyring: keyring name to check in policy for KEY_CHECK func * * Returns true on rule match, false on failure. */ static bool ima_match_rules(struct ima_rule_entry *rule, struct inode *inode, const struct cred *cred, u32 secid, - enum ima_hooks func, int mask) + enum ima_hooks func, int mask, + const char *keyring) { int i; + bool result = false; + struct ima_rule_entry *lsm_rule = rule; + bool rule_reinitialized = false; + if (func == KEY_CHECK) { + return (rule->flags & IMA_FUNC) && (rule->func == func) && + ima_match_keyring(rule, keyring, cred); + } if ((rule->flags & IMA_FUNC) && (rule->func != func && func != POST_SETATTR)) return false; @@ -319,42 +543,56 @@ for (i = 0; i < MAX_LSM_RULES; i++) { int rc = 0; u32 osid; - int retried = 0; - if (!rule->lsm[i].rule) - continue; + if (!lsm_rule->lsm[i].rule) { + if (!lsm_rule->lsm[i].args_p) + continue; + else + return false; + } + retry: switch (i) { case LSM_OBJ_USER: case LSM_OBJ_ROLE: case LSM_OBJ_TYPE: security_inode_getsecid(inode, &osid); - rc = security_filter_rule_match(osid, - rule->lsm[i].type, - Audit_equal, - rule->lsm[i].rule, - NULL); + rc = ima_filter_rule_match(osid, lsm_rule->lsm[i].type, + Audit_equal, + lsm_rule->lsm[i].rule); break; case LSM_SUBJ_USER: case LSM_SUBJ_ROLE: case LSM_SUBJ_TYPE: - rc = security_filter_rule_match(secid, - rule->lsm[i].type, - Audit_equal, - rule->lsm[i].rule, - NULL); + rc = ima_filter_rule_match(secid, lsm_rule->lsm[i].type, + Audit_equal, + lsm_rule->lsm[i].rule); + break; default: break; } - if ((rc < 0) && (!retried)) { - retried = 1; - ima_lsm_update_rules(); - goto retry; + + if (rc == -ESTALE && !rule_reinitialized) { + lsm_rule = ima_lsm_copy_rule(rule); + if (lsm_rule) { + rule_reinitialized = true; + goto retry; + } } - if (!rc) - return false; + if (!rc) { + result = false; + goto out; + } } - return true; + result = true; + +out: + if (rule_reinitialized) { + for (i = 0; i < MAX_LSM_RULES; i++) + ima_filter_rule_free(lsm_rule->lsm[i].rule); + kfree(lsm_rule); + } + return result; } /* @@ -390,7 +628,11 @@ * @secid: LSM secid of the task to be validated * @func: IMA hook identifier * @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC) + * @flags: IMA actions to consider (e.g. IMA_MEASURE | IMA_APPRAISE) * @pcr: set the pcr to extend + * @template_desc: the template that should be used for this rule + * @keyring: the keyring name, if given, to be used to check in the policy. + * keyring can be NULL if func is anything other than KEY_CHECK. * * Measure decision based on func/mask/fsmagic and LSM(subj/obj/type) * conditions. @@ -400,10 +642,15 @@ * than writes so ima_match_policy() is classical RCU candidate. */ int ima_match_policy(struct inode *inode, const struct cred *cred, u32 secid, - enum ima_hooks func, int mask, int flags, int *pcr) + enum ima_hooks func, int mask, int flags, int *pcr, + struct ima_template_desc **template_desc, + const char *keyring) { struct ima_rule_entry *entry; int action = 0, actmask = flags | (flags << 1); + + if (template_desc) + *template_desc = ima_template_desc_current(); rcu_read_lock(); list_for_each_entry_rcu(entry, ima_rules, list) { @@ -411,7 +658,8 @@ if (!(entry->action & actmask)) continue; - if (!ima_match_rules(entry, inode, cred, secid, func, mask)) + if (!ima_match_rules(entry, inode, cred, secid, func, mask, + keyring)) continue; action |= entry->flags & IMA_ACTION_FLAGS; @@ -424,6 +672,7 @@ action |= IMA_FAIL_UNVERIFIABLE_SIGS; } + if (entry->action & IMA_DO_MASK) actmask &= ~(entry->action | entry->action << 1); else @@ -431,6 +680,9 @@ if ((pcr) && (entry->flags & IMA_PCR)) *pcr = entry->pcr; + + if (template_desc && entry->template) + *template_desc = entry->template; if (!actmask) break; @@ -473,6 +725,79 @@ return 0; } +static void add_rules(struct ima_rule_entry *entries, int count, + enum policy_rule_list policy_rule) +{ + int i = 0; + + for (i = 0; i < count; i++) { + struct ima_rule_entry *entry; + + if (policy_rule & IMA_DEFAULT_POLICY) + list_add_tail(&entries[i].list, &ima_default_rules); + + if (policy_rule & IMA_CUSTOM_POLICY) { + entry = kmemdup(&entries[i], sizeof(*entry), + GFP_KERNEL); + if (!entry) + continue; + + list_add_tail(&entry->list, &ima_policy_rules); + } + if (entries[i].action == APPRAISE) { + if (entries != build_appraise_rules) + temp_ima_appraise |= + ima_appraise_flag(entries[i].func); + else + build_ima_appraise |= + ima_appraise_flag(entries[i].func); + } + } +} + +static int ima_parse_rule(char *rule, struct ima_rule_entry *entry); + +static int __init ima_init_arch_policy(void) +{ + const char * const *arch_rules; + const char * const *rules; + int arch_entries = 0; + int i = 0; + + arch_rules = arch_get_ima_policy(); + if (!arch_rules) + return arch_entries; + + /* Get number of rules */ + for (rules = arch_rules; *rules != NULL; rules++) + arch_entries++; + + arch_policy_entry = kcalloc(arch_entries + 1, + sizeof(*arch_policy_entry), GFP_KERNEL); + if (!arch_policy_entry) + return 0; + + /* Convert each policy string rules to struct ima_rule_entry format */ + for (rules = arch_rules, i = 0; *rules != NULL; rules++) { + char rule[255]; + int result; + + result = strlcpy(rule, *rules, sizeof(rule)); + + INIT_LIST_HEAD(&arch_policy_entry[i].list); + result = ima_parse_rule(rule, &arch_policy_entry[i]); + if (result) { + pr_warn("Skipping unknown architecture policy rule: %s\n", + rule); + memset(&arch_policy_entry[i], 0, + sizeof(*arch_policy_entry)); + continue; + } + i++; + } + return i; +} + /** * ima_init_policy - initialize the default measure rules. * @@ -481,68 +806,69 @@ */ void __init ima_init_policy(void) { - int i, measure_entries, appraise_entries, secure_boot_entries; + int build_appraise_entries, arch_entries; - /* if !ima_policy set entries = 0 so we load NO default rules */ - measure_entries = ima_policy ? ARRAY_SIZE(dont_measure_rules) : 0; - appraise_entries = ima_use_appraise_tcb ? - ARRAY_SIZE(default_appraise_rules) : 0; - secure_boot_entries = ima_use_secure_boot ? - ARRAY_SIZE(secure_boot_rules) : 0; - - for (i = 0; i < measure_entries; i++) - list_add_tail(&dont_measure_rules[i].list, &ima_default_rules); + /* if !ima_policy, we load NO default rules */ + if (ima_policy) + add_rules(dont_measure_rules, ARRAY_SIZE(dont_measure_rules), + IMA_DEFAULT_POLICY); switch (ima_policy) { case ORIGINAL_TCB: - for (i = 0; i < ARRAY_SIZE(original_measurement_rules); i++) - list_add_tail(&original_measurement_rules[i].list, - &ima_default_rules); + add_rules(original_measurement_rules, + ARRAY_SIZE(original_measurement_rules), + IMA_DEFAULT_POLICY); break; case DEFAULT_TCB: - for (i = 0; i < ARRAY_SIZE(default_measurement_rules); i++) - list_add_tail(&default_measurement_rules[i].list, - &ima_default_rules); + add_rules(default_measurement_rules, + ARRAY_SIZE(default_measurement_rules), + IMA_DEFAULT_POLICY); + break; default: break; } /* - * Insert the builtin "secure_boot" policy rules requiring file - * signatures, prior to any other appraise rules. + * Based on runtime secure boot flags, insert arch specific measurement + * and appraise rules requiring file signatures for both the initial + * and custom policies, prior to other appraise rules. + * (Highest priority) */ - for (i = 0; i < secure_boot_entries; i++) { - list_add_tail(&secure_boot_rules[i].list, &ima_default_rules); - temp_ima_appraise |= - ima_appraise_flag(secure_boot_rules[i].func); - } + arch_entries = ima_init_arch_policy(); + if (!arch_entries) + pr_info("No architecture policies found\n"); + else + add_rules(arch_policy_entry, arch_entries, + IMA_DEFAULT_POLICY | IMA_CUSTOM_POLICY); + + /* + * Insert the builtin "secure_boot" policy rules requiring file + * signatures, prior to other appraise rules. + */ + if (ima_use_secure_boot) + add_rules(secure_boot_rules, ARRAY_SIZE(secure_boot_rules), + IMA_DEFAULT_POLICY); /* * Insert the build time appraise rules requiring file signatures * for both the initial and custom policies, prior to other appraise - * rules. + * rules. As the secure boot rules includes all of the build time + * rules, include either one or the other set of rules, but not both. */ - for (i = 0; i < ARRAY_SIZE(build_appraise_rules); i++) { - struct ima_rule_entry *entry; - - if (!secure_boot_entries) - list_add_tail(&build_appraise_rules[i].list, - &ima_default_rules); - - entry = kmemdup(&build_appraise_rules[i], sizeof(*entry), - GFP_KERNEL); - if (entry) - list_add_tail(&entry->list, &ima_policy_rules); - build_ima_appraise |= - ima_appraise_flag(build_appraise_rules[i].func); + build_appraise_entries = ARRAY_SIZE(build_appraise_rules); + if (build_appraise_entries) { + if (ima_use_secure_boot) + add_rules(build_appraise_rules, build_appraise_entries, + IMA_CUSTOM_POLICY); + else + add_rules(build_appraise_rules, build_appraise_entries, + IMA_DEFAULT_POLICY | IMA_CUSTOM_POLICY); } - for (i = 0; i < appraise_entries; i++) { - list_add_tail(&default_appraise_rules[i].list, - &ima_default_rules); - if (default_appraise_rules[i].func == POLICY_CHECK) - temp_ima_appraise |= IMA_APPRAISE_POLICY; - } + if (ima_use_appraise_tcb) + add_rules(default_appraise_rules, + ARRAY_SIZE(default_appraise_rules), + IMA_DEFAULT_POLICY); ima_update_policy_flag(); } @@ -575,13 +901,24 @@ if (ima_rules != policy) { ima_policy_flag = 0; ima_rules = policy; + + /* + * IMA architecture specific policy rules are specified + * as strings and converted to an array of ima_entry_rules + * on boot. After loading a custom policy, free the + * architecture specific rules stored as an array. + */ + kfree(arch_policy_entry); } ima_update_policy_flag(); + + /* Custom IMA policy has been loaded */ + ima_process_queued_keys(); } +/* Keep the enumeration in sync with the policy_tokens! */ enum { - Opt_err = -1, - Opt_measure = 1, Opt_dont_measure, + Opt_measure, Opt_dont_measure, Opt_appraise, Opt_dont_appraise, Opt_audit, Opt_hash, Opt_dont_hash, Opt_obj_user, Opt_obj_role, Opt_obj_type, @@ -590,11 +927,12 @@ Opt_fsuuid, Opt_uid_eq, Opt_euid_eq, Opt_fowner_eq, Opt_uid_gt, Opt_euid_gt, Opt_fowner_gt, Opt_uid_lt, Opt_euid_lt, Opt_fowner_lt, - Opt_appraise_type, Opt_permit_directio, - Opt_pcr + Opt_appraise_type, Opt_appraise_flag, + Opt_permit_directio, Opt_pcr, Opt_template, Opt_keyrings, + Opt_err }; -static match_table_t policy_tokens = { +static const match_table_t policy_tokens = { {Opt_measure, "measure"}, {Opt_dont_measure, "dont_measure"}, {Opt_appraise, "appraise"}, @@ -623,8 +961,11 @@ {Opt_euid_lt, "euid<%s"}, {Opt_fowner_lt, "fowner<%s"}, {Opt_appraise_type, "appraise_type=%s"}, + {Opt_appraise_flag, "appraise_flag=%s"}, {Opt_permit_directio, "permit_directio"}, {Opt_pcr, "pcr=%s"}, + {Opt_template, "template=%s"}, + {Opt_keyrings, "keyrings=%s"}, {Opt_err, NULL} }; @@ -641,13 +982,19 @@ return -ENOMEM; entry->lsm[lsm_rule].type = audit_type; - result = security_filter_rule_init(entry->lsm[lsm_rule].type, - Audit_equal, - entry->lsm[lsm_rule].args_p, - &entry->lsm[lsm_rule].rule); + result = ima_filter_rule_init(entry->lsm[lsm_rule].type, Audit_equal, + entry->lsm[lsm_rule].args_p, + &entry->lsm[lsm_rule].rule); if (!entry->lsm[lsm_rule].rule) { - kfree(entry->lsm[lsm_rule].args_p); - return -EINVAL; + pr_warn("rule for LSM \'%s\' is undefined\n", + entry->lsm[lsm_rule].args_p); + + if (ima_rules == &ima_default_rules) { + kfree(entry->lsm[lsm_rule].args_p); + entry->lsm[lsm_rule].args_p = NULL; + result = -EINVAL; + } else + result = 0; } return result; @@ -672,12 +1019,135 @@ ima_log_string_op(ab, key, value, NULL); } +/* + * Validating the appended signature included in the measurement list requires + * the file hash calculated without the appended signature (i.e., the 'd-modsig' + * field). Therefore, notify the user if they have the 'modsig' field but not + * the 'd-modsig' field in the template. + */ +static void check_template_modsig(const struct ima_template_desc *template) +{ +#define MSG "template with 'modsig' field also needs 'd-modsig' field\n" + bool has_modsig, has_dmodsig; + static bool checked; + int i; + + /* We only need to notify the user once. */ + if (checked) + return; + + has_modsig = has_dmodsig = false; + for (i = 0; i < template->num_fields; i++) { + if (!strcmp(template->fields[i]->field_id, "modsig")) + has_modsig = true; + else if (!strcmp(template->fields[i]->field_id, "d-modsig")) + has_dmodsig = true; + } + + if (has_modsig && !has_dmodsig) + pr_notice(MSG); + + checked = true; +#undef MSG +} + +static bool ima_validate_rule(struct ima_rule_entry *entry) +{ + /* Ensure that the action is set and is compatible with the flags */ + if (entry->action == UNKNOWN) + return false; + + if (entry->action != MEASURE && entry->flags & IMA_PCR) + return false; + + if (entry->action != APPRAISE && + entry->flags & (IMA_DIGSIG_REQUIRED | IMA_MODSIG_ALLOWED | IMA_CHECK_BLACKLIST)) + return false; + + /* + * The IMA_FUNC bit must be set if and only if there's a valid hook + * function specified, and vice versa. Enforcing this property allows + * for the NONE case below to validate a rule without an explicit hook + * function. + */ + if (((entry->flags & IMA_FUNC) && entry->func == NONE) || + (!(entry->flags & IMA_FUNC) && entry->func != NONE)) + return false; + + /* + * Ensure that the hook function is compatible with the other + * components of the rule + */ + switch (entry->func) { + case NONE: + case FILE_CHECK: + case MMAP_CHECK: + case BPRM_CHECK: + case CREDS_CHECK: + case POST_SETATTR: + case FIRMWARE_CHECK: + case POLICY_CHECK: + if (entry->flags & ~(IMA_FUNC | IMA_MASK | IMA_FSMAGIC | + IMA_UID | IMA_FOWNER | IMA_FSUUID | + IMA_INMASK | IMA_EUID | IMA_PCR | + IMA_FSNAME | IMA_DIGSIG_REQUIRED | + IMA_PERMIT_DIRECTIO)) + return false; + + break; + case MODULE_CHECK: + case KEXEC_KERNEL_CHECK: + case KEXEC_INITRAMFS_CHECK: + if (entry->flags & ~(IMA_FUNC | IMA_MASK | IMA_FSMAGIC | + IMA_UID | IMA_FOWNER | IMA_FSUUID | + IMA_INMASK | IMA_EUID | IMA_PCR | + IMA_FSNAME | IMA_DIGSIG_REQUIRED | + IMA_PERMIT_DIRECTIO | IMA_MODSIG_ALLOWED | + IMA_CHECK_BLACKLIST)) + return false; + + break; + case KEXEC_CMDLINE: + if (entry->action & ~(MEASURE | DONT_MEASURE)) + return false; + + if (entry->flags & ~(IMA_FUNC | IMA_FSMAGIC | IMA_UID | + IMA_FOWNER | IMA_FSUUID | IMA_EUID | + IMA_PCR | IMA_FSNAME)) + return false; + + break; + case KEY_CHECK: + if (entry->action & ~(MEASURE | DONT_MEASURE)) + return false; + + if (entry->flags & ~(IMA_FUNC | IMA_UID | IMA_PCR | + IMA_KEYRINGS)) + return false; + + if (ima_rule_contains_lsm_cond(entry)) + return false; + + break; + default: + return false; + } + + /* Ensure that combinations of flags are compatible with each other */ + if (entry->flags & IMA_CHECK_BLACKLIST && + !(entry->flags & IMA_MODSIG_ALLOWED)) + return false; + + return true; +} + static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) { struct audit_buffer *ab; char *from; char *p; bool uid_token; + struct ima_template_desc *template_desc; int result = 0; ab = integrity_audit_log_start(audit_context(), GFP_KERNEL, @@ -785,6 +1255,11 @@ entry->func = KEXEC_INITRAMFS_CHECK; else if (strcmp(args[0].from, "POLICY_CHECK") == 0) entry->func = POLICY_CHECK; + else if (strcmp(args[0].from, "KEXEC_CMDLINE") == 0) + entry->func = KEXEC_CMDLINE; + else if (IS_ENABLED(CONFIG_IMA_MEASURE_ASYMMETRIC_KEYS) && + strcmp(args[0].from, "KEY_CHECK") == 0) + entry->func = KEY_CHECK; else result = -EINVAL; if (!result) @@ -837,6 +1312,24 @@ result = 0; entry->flags |= IMA_FSNAME; break; + case Opt_keyrings: + ima_log_string(ab, "keyrings", args[0].from); + + if (!IS_ENABLED(CONFIG_IMA_MEASURE_ASYMMETRIC_KEYS) || + entry->keyrings) { + result = -EINVAL; + break; + } + + entry->keyrings = ima_alloc_rule_opt_list(args); + if (IS_ERR(entry->keyrings)) { + result = PTR_ERR(entry->keyrings); + entry->keyrings = NULL; + break; + } + + entry->flags |= IMA_KEYRINGS; + break; case Opt_fsuuid: ima_log_string(ab, "fsuuid", args[0].from); @@ -852,10 +1345,12 @@ case Opt_uid_gt: case Opt_euid_gt: entry->uid_op = &uid_gt; + fallthrough; case Opt_uid_lt: case Opt_euid_lt: if ((token == Opt_uid_lt) || (token == Opt_euid_lt)) entry->uid_op = &uid_lt; + fallthrough; case Opt_uid_eq: case Opt_euid_eq: uid_token = (token == Opt_uid_eq) || @@ -884,9 +1379,11 @@ break; case Opt_fowner_gt: entry->fowner_op = &uid_gt; + fallthrough; case Opt_fowner_lt: if (token == Opt_fowner_lt) entry->fowner_op = &uid_lt; + fallthrough; case Opt_fowner_eq: ima_log_string_op(ab, "fowner", args[0].from, entry->fowner_op); @@ -942,14 +1439,21 @@ AUDIT_SUBJ_TYPE); break; case Opt_appraise_type: - if (entry->action != APPRAISE) { - result = -EINVAL; - break; - } - 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 | + IMA_MODSIG_ALLOWED; + else + result = -EINVAL; + break; + case Opt_appraise_flag: + ima_log_string(ab, "appraise_flag", args[0].from); + if (IS_ENABLED(CONFIG_IMA_APPRAISE_MODSIG) && + strstr(args[0].from, "blacklist")) + entry->flags |= IMA_CHECK_BLACKLIST; else result = -EINVAL; break; @@ -957,10 +1461,6 @@ entry->flags |= IMA_PERMIT_DIRECTIO; break; case Opt_pcr: - if (entry->action != MEASURE) { - result = -EINVAL; - break; - } ima_log_string(ab, "pcr", args[0].from); result = kstrtoint(args[0].from, 10, &entry->pcr); @@ -970,16 +1470,44 @@ entry->flags |= IMA_PCR; break; + case Opt_template: + ima_log_string(ab, "template", args[0].from); + if (entry->action != MEASURE) { + result = -EINVAL; + break; + } + template_desc = lookup_template_desc(args[0].from); + if (!template_desc || entry->template) { + result = -EINVAL; + break; + } + + /* + * template_desc_init_fields() does nothing if + * the template is already initialised, so + * it's safe to do this unconditionally + */ + template_desc_init_fields(template_desc->fmt, + &(template_desc->fields), + &(template_desc->num_fields)); + entry->template = template_desc; + break; case Opt_err: ima_log_string(ab, "UNKNOWN", p); result = -EINVAL; break; } } - if (!result && (entry->action == UNKNOWN)) + if (!result && !ima_validate_rule(entry)) result = -EINVAL; else if (entry->action == APPRAISE) temp_ima_appraise |= ima_appraise_flag(entry->func); + + if (!result && entry->flags & IMA_MODSIG_ALLOWED) { + template_desc = entry->template ? entry->template : + ima_template_desc_current(); + check_template_modsig(template_desc); + } audit_log_format(ab, "res=%d", !result); audit_log_end(ab); @@ -988,7 +1516,7 @@ /** * ima_parse_add_rule - add a rule to ima_policy_rules - * @rule - ima measurement policy rule + * @rule: ima measurement policy rule * * Avoid locking by allowing just one writer at a time in ima_write_policy() * Returns the length of the rule parsed, an error code on failure @@ -1019,7 +1547,7 @@ result = ima_parse_rule(p, entry); if (result) { - kfree(entry); + ima_free_rule(entry); integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, op, "invalid-policy", result, audit_info); @@ -1040,17 +1568,19 @@ void ima_delete_rules(void) { struct ima_rule_entry *entry, *tmp; - int i; temp_ima_appraise = 0; list_for_each_entry_safe(entry, tmp, &ima_temp_rules, list) { - for (i = 0; i < MAX_LSM_RULES; i++) - kfree(entry->lsm[i].args_p); - list_del(&entry->list); - kfree(entry); + ima_free_rule(entry); } } + +#define __ima_hook_stringify(func, str) (#func), + +const char *const func_tokens[] = { + __ima_hooks(__ima_hook_stringify) +}; #ifdef CONFIG_IMA_READ_POLICY enum { @@ -1062,12 +1592,6 @@ "^MAY_WRITE", "^MAY_READ", "^MAY_APPEND" -}; - -#define __ima_hook_stringify(str) (#str), - -static const char *const func_tokens[] = { - __ima_hooks(__ima_hook_stringify) }; void *ima_policy_start(struct seq_file *m, loff_t *pos) @@ -1102,7 +1626,7 @@ { } -#define pt(token) policy_tokens[token + Opt_err].pattern +#define pt(token) policy_tokens[token].pattern #define mt(token) mask_tokens[token] /* @@ -1116,6 +1640,15 @@ seq_printf(m, "func=%d ", func); } +static void ima_show_rule_opt_list(struct seq_file *m, + const struct ima_rule_opt_list *opt_list) +{ + size_t i; + + for (i = 0; i < opt_list->count; i++) + seq_printf(m, "%s%s", i ? "|" : "", opt_list->items[i]); +} + int ima_policy_show(struct seq_file *m, void *v) { struct ima_rule_entry *entry = v; @@ -1124,6 +1657,14 @@ int offset = 0; rcu_read_lock(); + + /* Do not print rules with inactive LSM labels */ + for (i = 0; i < MAX_LSM_RULES; i++) { + if (entry->lsm[i].args_p && !entry->lsm[i].rule) { + rcu_read_unlock(); + return 0; + } + } if (entry->action & MEASURE) seq_puts(m, pt(Opt_measure)); @@ -1168,6 +1709,12 @@ if (entry->flags & IMA_FSNAME) { snprintf(tbuf, sizeof(tbuf), "%s", entry->fsname); seq_printf(m, pt(Opt_fsname), tbuf); + seq_puts(m, " "); + } + + if (entry->flags & IMA_KEYRINGS) { + seq_puts(m, "keyrings="); + ima_show_rule_opt_list(m, entry->keyrings); seq_puts(m, " "); } @@ -1220,33 +1767,42 @@ switch (i) { case LSM_OBJ_USER: seq_printf(m, pt(Opt_obj_user), - (char *)entry->lsm[i].args_p); + entry->lsm[i].args_p); break; case LSM_OBJ_ROLE: seq_printf(m, pt(Opt_obj_role), - (char *)entry->lsm[i].args_p); + entry->lsm[i].args_p); break; case LSM_OBJ_TYPE: seq_printf(m, pt(Opt_obj_type), - (char *)entry->lsm[i].args_p); + entry->lsm[i].args_p); break; case LSM_SUBJ_USER: seq_printf(m, pt(Opt_subj_user), - (char *)entry->lsm[i].args_p); + entry->lsm[i].args_p); break; case LSM_SUBJ_ROLE: seq_printf(m, pt(Opt_subj_role), - (char *)entry->lsm[i].args_p); + entry->lsm[i].args_p); break; case LSM_SUBJ_TYPE: seq_printf(m, pt(Opt_subj_type), - (char *)entry->lsm[i].args_p); + entry->lsm[i].args_p); break; } + seq_puts(m, " "); } } - if (entry->flags & IMA_DIGSIG_REQUIRED) - seq_puts(m, "appraise_type=imasig "); + if (entry->template) + seq_printf(m, "template=%s ", entry->template->name); + if (entry->flags & IMA_DIGSIG_REQUIRED) { + if (entry->flags & IMA_MODSIG_ALLOWED) + seq_puts(m, "appraise_type=imasig|modsig "); + else + seq_puts(m, "appraise_type=imasig "); + } + if (entry->flags & IMA_CHECK_BLACKLIST) + seq_puts(m, "appraise_flag=check_blacklist "); if (entry->flags & IMA_PERMIT_DIRECTIO) seq_puts(m, "permit_directio "); rcu_read_unlock(); @@ -1254,3 +1810,57 @@ return 0; } #endif /* CONFIG_IMA_READ_POLICY */ + +#if defined(CONFIG_IMA_APPRAISE) && defined(CONFIG_INTEGRITY_TRUSTED_KEYRING) +/* + * ima_appraise_signature: whether IMA will appraise a given function using + * an IMA digital signature. This is restricted to cases where the kernel + * has a set of built-in trusted keys in order to avoid an attacker simply + * loading additional keys. + */ +bool ima_appraise_signature(enum kernel_read_file_id id) +{ + struct ima_rule_entry *entry; + bool found = false; + enum ima_hooks func; + + if (id >= READING_MAX_ID) + return false; + + if (id == READING_KEXEC_IMAGE && !(ima_appraise & IMA_APPRAISE_ENFORCE) + && security_locked_down(LOCKDOWN_KEXEC)) + return false; + + func = read_idmap[id] ?: FILE_CHECK; + + rcu_read_lock(); + list_for_each_entry_rcu(entry, ima_rules, list) { + if (entry->action != APPRAISE) + continue; + + /* + * A generic entry will match, but otherwise require that it + * match the func we're looking for + */ + if (entry->func && entry->func != func) + continue; + + /* + * We require this to be a digital signature, not a raw IMA + * hash. + */ + if (entry->flags & IMA_DIGSIG_REQUIRED) + found = true; + + /* + * We've found a rule that matches, so break now even if it + * didn't require a digital signature - a later rule that does + * won't override it, so would be a false positive. + */ + break; + } + + rcu_read_unlock(); + return found; +} +#endif /* CONFIG_IMA_APPRAISE && CONFIG_INTEGRITY_TRUSTED_KEYRING */ -- Gitblit v1.6.2