diff options
Diffstat (limited to 'security/integrity/ima/ima_policy.c')
| -rw-r--r-- | security/integrity/ima/ima_policy.c | 2119 |
1 files changed, 1896 insertions, 223 deletions
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 399433ad614e..8fbd8755f5bc 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -1,32 +1,44 @@ +// 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 - * + * - 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> #include <linux/parser.h> #include <linux/slab.h> -#include <linux/genhd.h> +#include <linux/rculist.h> +#include <linux/seq_file.h> +#include <linux/ima.h> #include "ima.h" /* flags definitions */ -#define IMA_FUNC 0x0001 -#define IMA_MASK 0x0002 +#define IMA_FUNC 0x0001 +#define IMA_MASK 0x0002 #define IMA_FSMAGIC 0x0004 #define IMA_UID 0x0008 #define IMA_FOWNER 0x0010 #define IMA_FSUUID 0x0020 +#define IMA_INMASK 0x0040 +#define IMA_EUID 0x0080 +#define IMA_PCR 0x0100 +#define IMA_FSNAME 0x0200 +#define IMA_KEYRINGS 0x0400 +#define IMA_LABEL 0x0800 +#define IMA_VALIDATE_ALGOS 0x1000 +#define IMA_GID 0x2000 +#define IMA_EGID 0x4000 +#define IMA_FGROUP 0x8000 +#define IMA_FS_SUBTYPE 0x10000 #define UNKNOWN 0 #define MEASURE 0x0001 /* same as IMA_MEASURE */ @@ -34,12 +46,57 @@ #define APPRAISE 0x0004 /* same as IMA_APPRAISE */ #define DONT_APPRAISE 0x0008 #define AUDIT 0x0040 +#define DONT_AUDIT 0x0080 +#define HASH 0x0100 +#define DONT_HASH 0x0200 + +#define INVALID_PCR(a) (((a) < 0) || \ + (a) >= (sizeof_field(struct ima_iint_cache, measured_pcrs) * 8)) + +int ima_policy_flag; +static int temp_ima_appraise; +static int build_ima_appraise __ro_after_init; + +atomic_t ima_setxattr_allowed_hash_algorithms; #define MAX_LSM_RULES 6 enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE, LSM_SUBJ_USER, LSM_SUBJ_ROLE, LSM_SUBJ_TYPE }; +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[] __counted_by(count); +}; + +/* + * These comparators are needed nowhere outside of ima so just define them here. + * This pattern should hopefully never be needed outside of ima. + */ +static inline bool vfsuid_gt_kuid(vfsuid_t vfsuid, kuid_t kuid) +{ + return __vfsuid_val(vfsuid) > __kuid_val(kuid); +} + +static inline bool vfsgid_gt_kgid(vfsgid_t vfsgid, kgid_t kgid) +{ + return __vfsgid_val(vfsgid) > __kgid_val(kgid); +} + +static inline bool vfsuid_lt_kuid(vfsuid_t vfsuid, kuid_t kuid) +{ + return __vfsuid_val(vfsuid) < __kuid_val(kuid); +} + +static inline bool vfsgid_lt_kgid(vfsgid_t vfsgid, kgid_t kgid) +{ + return __vfsgid_val(vfsgid) < __kgid_val(kgid); +} + struct ima_rule_entry { struct list_head list; int action; @@ -47,19 +104,41 @@ struct ima_rule_entry { enum ima_hooks func; int mask; unsigned long fsmagic; - u8 fsuuid[16]; + uuid_t fsuuid; kuid_t uid; + kgid_t gid; kuid_t fowner; + kgid_t fgroup; + bool (*uid_op)(kuid_t cred_uid, kuid_t rule_uid); /* Handlers for operators */ + bool (*gid_op)(kgid_t cred_gid, kgid_t rule_gid); + bool (*fowner_op)(vfsuid_t vfsuid, kuid_t rule_uid); /* vfsuid_eq_kuid(), vfsuid_gt_kuid(), vfsuid_lt_kuid() */ + bool (*fgroup_op)(vfsgid_t vfsgid, kgid_t rule_gid); /* vfsgid_eq_kgid(), vfsgid_gt_kgid(), vfsgid_lt_kgid() */ + int pcr; + unsigned int allowed_algos; /* bitfield of allowed hash algorithms */ 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; + char *fs_subtype; + struct ima_rule_opt_list *keyrings; /* Measure keys added to these keyrings */ + struct ima_rule_opt_list *label; /* Measure data grouped under this label */ + struct ima_template_desc *template; }; /* + * sanity check in case the kernels gains more hash algorithms that can + * fit in an unsigned int + */ +static_assert( + 8 * sizeof(unsigned int) >= HASH_ALGO__LAST, + "The bitfield allowed_algos in ima_rule_entry is too small to contain all the supported hash algorithms, consider using a bigger type"); + +/* * Without LSM specific knowledge, the default policy can only be - * written in terms of .action, .func, .mask, .fsmagic, .uid, and .fowner + * written in terms of .action, .func, .mask, .fsmagic, .uid, .gid, + * .fowner, and .fgroup */ /* @@ -68,299 +147,959 @@ struct ima_rule_entry { * normal users can easily run the machine out of memory simply building * and running executables. */ -static struct ima_rule_entry default_rules[] = { - {.action = DONT_MEASURE,.fsmagic = PROC_SUPER_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = SYSFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = DEBUGFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = TMPFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = RAMFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = DEVPTS_SUPER_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = BINFMTFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = SECURITYFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = SELINUX_MAGIC,.flags = IMA_FSMAGIC}, - {.action = MEASURE,.func = MMAP_CHECK,.mask = MAY_EXEC, +static struct ima_rule_entry dont_measure_rules[] __ro_after_init = { + {.action = DONT_MEASURE, .fsmagic = PROC_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = SYSFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = DEBUGFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = TMPFS_MAGIC, .func = FILE_CHECK, + .flags = IMA_FSMAGIC | IMA_FUNC}, + {.action = DONT_MEASURE, .fsmagic = DEVPTS_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = BINFMTFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = SECURITYFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = SELINUX_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = SMACK_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = CGROUP_SUPER_MAGIC, + .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 = EFIVARFS_MAGIC, .flags = IMA_FSMAGIC} +}; + +static struct ima_rule_entry original_measurement_rules[] __ro_after_init = { + {.action = MEASURE, .func = MMAP_CHECK, .mask = MAY_EXEC, .flags = IMA_FUNC | IMA_MASK}, - {.action = MEASURE,.func = BPRM_CHECK,.mask = MAY_EXEC, + {.action = MEASURE, .func = BPRM_CHECK, .mask = MAY_EXEC, .flags = IMA_FUNC | IMA_MASK}, - {.action = MEASURE,.func = FILE_CHECK,.mask = MAY_READ,.uid = GLOBAL_ROOT_UID, + {.action = MEASURE, .func = FILE_CHECK, .mask = MAY_READ, + .uid = GLOBAL_ROOT_UID, .uid_op = &uid_eq, .flags = IMA_FUNC | IMA_MASK | IMA_UID}, - {.action = MEASURE,.func = MODULE_CHECK, .flags = IMA_FUNC}, + {.action = MEASURE, .func = MODULE_CHECK, .flags = IMA_FUNC}, + {.action = MEASURE, .func = FIRMWARE_CHECK, .flags = IMA_FUNC}, +}; + +static struct ima_rule_entry default_measurement_rules[] __ro_after_init = { + {.action = MEASURE, .func = MMAP_CHECK, .mask = MAY_EXEC, + .flags = IMA_FUNC | IMA_MASK}, + {.action = MEASURE, .func = BPRM_CHECK, .mask = MAY_EXEC, + .flags = IMA_FUNC | IMA_MASK}, + {.action = MEASURE, .func = FILE_CHECK, .mask = MAY_READ, + .uid = GLOBAL_ROOT_UID, .uid_op = &uid_eq, + .flags = IMA_FUNC | IMA_INMASK | IMA_EUID}, + {.action = MEASURE, .func = FILE_CHECK, .mask = MAY_READ, + .uid = GLOBAL_ROOT_UID, .uid_op = &uid_eq, + .flags = IMA_FUNC | IMA_INMASK | IMA_UID}, + {.action = MEASURE, .func = MODULE_CHECK, .flags = IMA_FUNC}, + {.action = MEASURE, .func = FIRMWARE_CHECK, .flags = IMA_FUNC}, + {.action = MEASURE, .func = POLICY_CHECK, .flags = IMA_FUNC}, +}; + +static struct ima_rule_entry default_appraise_rules[] __ro_after_init = { + {.action = DONT_APPRAISE, .fsmagic = PROC_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = SYSFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = DEBUGFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = TMPFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = RAMFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = DEVPTS_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = BINFMTFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = SECURITYFS_MAGIC, .flags = IMA_FSMAGIC}, + {.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 + {.action = APPRAISE, .func = POLICY_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, +#endif +#ifndef CONFIG_IMA_APPRAISE_SIGNED_INIT + {.action = APPRAISE, .fowner = GLOBAL_ROOT_UID, .fowner_op = &vfsuid_eq_kuid, + .flags = IMA_FOWNER}, +#else + /* force signature */ + {.action = APPRAISE, .fowner = GLOBAL_ROOT_UID, .fowner_op = &vfsuid_eq_kuid, + .flags = IMA_FOWNER | IMA_DIGSIG_REQUIRED}, +#endif }; -static struct ima_rule_entry default_appraise_rules[] = { - {.action = DONT_APPRAISE,.fsmagic = PROC_SUPER_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_APPRAISE,.fsmagic = SYSFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_APPRAISE,.fsmagic = DEBUGFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_APPRAISE,.fsmagic = TMPFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_APPRAISE,.fsmagic = RAMFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_APPRAISE,.fsmagic = DEVPTS_SUPER_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_APPRAISE,.fsmagic = BINFMTFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_APPRAISE,.fsmagic = SECURITYFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_APPRAISE,.fsmagic = SELINUX_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_APPRAISE,.fsmagic = CGROUP_SUPER_MAGIC,.flags = IMA_FSMAGIC}, - {.action = APPRAISE,.fowner = GLOBAL_ROOT_UID,.flags = IMA_FOWNER}, +static struct ima_rule_entry build_appraise_rules[] __ro_after_init = { +#ifdef CONFIG_IMA_APPRAISE_REQUIRE_MODULE_SIGS + {.action = APPRAISE, .func = MODULE_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, +#endif +#ifdef CONFIG_IMA_APPRAISE_REQUIRE_FIRMWARE_SIGS + {.action = APPRAISE, .func = FIRMWARE_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, +#endif +#ifdef CONFIG_IMA_APPRAISE_REQUIRE_KEXEC_SIGS + {.action = APPRAISE, .func = KEXEC_KERNEL_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, +#endif +#ifdef CONFIG_IMA_APPRAISE_REQUIRE_POLICY_SIGS + {.action = APPRAISE, .func = POLICY_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, +#endif }; +static struct ima_rule_entry secure_boot_rules[] __ro_after_init = { + {.action = APPRAISE, .func = MODULE_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED | IMA_MODSIG_ALLOWED | + IMA_CHECK_BLACKLIST}, + {.action = APPRAISE, .func = FIRMWARE_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, + {.action = APPRAISE, .func = KEXEC_KERNEL_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, + {.action = APPRAISE, .func = POLICY_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, +}; + +static struct ima_rule_entry critical_data_rules[] __ro_after_init = { + {.action = MEASURE, .func = CRITICAL_DATA, .flags = IMA_FUNC}, +}; + +/* 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 struct list_head *ima_rules; +static LIST_HEAD(ima_temp_rules); +static struct list_head __rcu *ima_rules = (struct list_head __rcu *)(&ima_default_rules); -static DEFINE_MUTEX(ima_rules_mutex); +static int ima_policy __initdata; -static bool ima_use_tcb __initdata; static int __init default_measure_policy_setup(char *str) { - ima_use_tcb = 1; + if (ima_policy) + return 1; + + ima_policy = ORIGINAL_TCB; return 1; } __setup("ima_tcb", default_measure_policy_setup); static bool ima_use_appraise_tcb __initdata; +static bool ima_use_secure_boot __initdata; +static bool ima_use_critical_data __initdata; +static bool ima_fail_unverifiable_sigs __ro_after_init; +static int __init policy_setup(char *str) +{ + char *p; + + while ((p = strsep(&str, " |\n")) != NULL) { + if (*p == ' ') + continue; + if ((strcmp(p, "tcb") == 0) && !ima_policy) + ima_policy = DEFAULT_TCB; + else if (strcmp(p, "appraise_tcb") == 0) + ima_use_appraise_tcb = true; + else if (strcmp(p, "secure_boot") == 0) + ima_use_secure_boot = true; + else if (strcmp(p, "critical_data") == 0) + ima_use_critical_data = true; + else if (strcmp(p, "fail_securely") == 0) + ima_fail_unverifiable_sigs = true; + else + pr_err("policy \"%s\" not found", p); + } + + return 1; +} +__setup("ima_policy=", policy_setup); + static int __init default_appraise_policy_setup(char *str) { - ima_use_appraise_tcb = 1; + ima_use_appraise_tcb = true; return 1; } __setup("ima_appraise_tcb", default_appraise_policy_setup); -/* - * Although the IMA policy does not change, 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. +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); + } + opt_list->count = count; + + /* + * 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; + } + + 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); + kfree(entry->fs_subtype); + 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, + gfp_t gfp) +{ + 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); + 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, + gfp); + 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, GFP_KERNEL); + 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 references 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. */ static void ima_lsm_update_rules(void) { - struct ima_rule_entry *entry, *tmp; + struct ima_rule_entry *entry, *e; int result; - int i; - mutex_lock(&ima_rules_mutex); - list_for_each_entry_safe(entry, tmp, &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_rule_data - determine whether func_data matches the policy rule + * @rule: a pointer to a rule + * @func_data: data to match against the measure rule data + * @cred: a pointer to a credentials structure for user validation + * + * Returns true if func_data matches one in the rule, false otherwise. + */ +static bool ima_match_rule_data(struct ima_rule_entry *rule, + const char *func_data, + const struct cred *cred) +{ + const struct ima_rule_opt_list *opt_list = NULL; + bool matched = false; + size_t i; + + if ((rule->flags & IMA_UID) && !rule->uid_op(cred->uid, rule->uid)) + return false; + + switch (rule->func) { + case KEY_CHECK: + if (!rule->keyrings) + return true; + + opt_list = rule->keyrings; + break; + case CRITICAL_DATA: + if (!rule->label) + return true; + + opt_list = rule->label; + break; + default: + return false; + } + + if (!func_data) + return false; + + for (i = 0; i < opt_list->count; i++) { + if (!strcmp(opt_list->items[i], func_data)) { + matched = true; + break; } } - mutex_unlock(&ima_rules_mutex); + + return matched; } /** - * ima_match_rules - determine whether an inode matches the measure rule. + * ima_match_rules - determine whether an inode matches the policy rule. * @rule: a pointer to a rule + * @idmap: idmap of the mount the inode was found from * @inode: a pointer to an inode + * @cred: a pointer to a credentials structure for user validation + * @prop: LSM properties of the task to be validated * @func: LIM hook identifier * @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC) + * @func_data: func specific data, may be NULL * * Returns true on rule match, false on failure. */ static bool ima_match_rules(struct ima_rule_entry *rule, - struct inode *inode, enum ima_hooks func, int mask) + struct mnt_idmap *idmap, + struct inode *inode, const struct cred *cred, + struct lsm_prop *prop, enum ima_hooks func, int mask, + const char *func_data) { - struct task_struct *tsk = current; - const struct cred *cred = current_cred(); int i; + bool result = false; + struct ima_rule_entry *lsm_rule = rule; + bool rule_reinitialized = false; + + if ((rule->flags & IMA_FUNC) && + (rule->func != func && func != POST_SETATTR)) + return false; + + switch (func) { + case KEY_CHECK: + case CRITICAL_DATA: + return ((rule->func == func) && + ima_match_rule_data(rule, func_data, cred)); + default: + break; + } - if ((rule->flags & IMA_FUNC) && rule->func != func) + if ((rule->flags & IMA_MASK) && + (rule->mask != mask && func != POST_SETATTR)) return false; - if ((rule->flags & IMA_MASK) && rule->mask != mask) + if ((rule->flags & IMA_INMASK) && + (!(rule->mask & mask) && func != POST_SETATTR)) return false; if ((rule->flags & IMA_FSMAGIC) && rule->fsmagic != inode->i_sb->s_magic) return false; + if ((rule->flags & IMA_FSNAME) + && strcmp(rule->fsname, inode->i_sb->s_type->name)) + return false; + if (rule->flags & IMA_FS_SUBTYPE) { + if (!inode->i_sb->s_subtype) + return false; + if (strcmp(rule->fs_subtype, inode->i_sb->s_subtype)) + return false; + } if ((rule->flags & IMA_FSUUID) && - memcmp(rule->fsuuid, inode->i_sb->s_uuid, sizeof(rule->fsuuid))) + !uuid_equal(&rule->fsuuid, &inode->i_sb->s_uuid)) + return false; + if ((rule->flags & IMA_UID) && !rule->uid_op(cred->uid, rule->uid)) + return false; + if (rule->flags & IMA_EUID) { + if (has_capability_noaudit(current, CAP_SETUID)) { + if (!rule->uid_op(cred->euid, rule->uid) + && !rule->uid_op(cred->suid, rule->uid) + && !rule->uid_op(cred->uid, rule->uid)) + return false; + } else if (!rule->uid_op(cred->euid, rule->uid)) + return false; + } + if ((rule->flags & IMA_GID) && !rule->gid_op(cred->gid, rule->gid)) return false; - if ((rule->flags & IMA_UID) && !uid_eq(rule->uid, cred->uid)) + if (rule->flags & IMA_EGID) { + if (has_capability_noaudit(current, CAP_SETGID)) { + if (!rule->gid_op(cred->egid, rule->gid) + && !rule->gid_op(cred->sgid, rule->gid) + && !rule->gid_op(cred->gid, rule->gid)) + return false; + } else if (!rule->gid_op(cred->egid, rule->gid)) + return false; + } + if ((rule->flags & IMA_FOWNER) && + !rule->fowner_op(i_uid_into_vfsuid(idmap, inode), + rule->fowner)) return false; - if ((rule->flags & IMA_FOWNER) && !uid_eq(rule->fowner, inode->i_uid)) + if ((rule->flags & IMA_FGROUP) && + !rule->fgroup_op(i_gid_into_vfsgid(idmap, inode), + rule->fgroup)) return false; for (i = 0; i < MAX_LSM_RULES; i++) { int rc = 0; - u32 osid, sid; - int retried = 0; + struct lsm_prop inode_prop = { }; + + if (!lsm_rule->lsm[i].rule) { + if (!lsm_rule->lsm[i].args_p) + continue; + else + return false; + } - if (!rule->lsm[i].rule) - continue; 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); + security_inode_getlsmprop(inode, &inode_prop); + rc = ima_filter_rule_match(&inode_prop, + 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: - security_task_getsecid(tsk, &sid); - rc = security_filter_rule_match(sid, - rule->lsm[i].type, - Audit_equal, - rule->lsm[i].rule, - NULL); + rc = ima_filter_rule_match(prop, 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) - return false; + + if (rc == -ESTALE && !rule_reinitialized) { + lsm_rule = ima_lsm_copy_rule(rule, GFP_ATOMIC); + if (lsm_rule) { + rule_reinitialized = true; + goto retry; + } + } + if (rc <= 0) { + 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; } /* * In addition to knowing that we need to appraise the file in general, * we need to differentiate between calling hooks, for hook specific rules. */ -static int get_subaction(struct ima_rule_entry *rule, int func) +static int get_subaction(struct ima_rule_entry *rule, enum ima_hooks func) { if (!(rule->flags & IMA_FUNC)) return IMA_FILE_APPRAISE; - switch(func) { + switch (func) { case MMAP_CHECK: + case MMAP_CHECK_REQPROT: return IMA_MMAP_APPRAISE; case BPRM_CHECK: return IMA_BPRM_APPRAISE; - case MODULE_CHECK: - return IMA_MODULE_APPRAISE; + case CREDS_CHECK: + return IMA_CREDS_APPRAISE; case FILE_CHECK: - default: + case POST_SETATTR: return IMA_FILE_APPRAISE; + case MODULE_CHECK ... MAX_CHECK - 1: + default: + return IMA_READ_APPRAISE; } } /** * ima_match_policy - decision based on LSM and other conditions + * @idmap: idmap of the mount the inode was found from * @inode: pointer to an inode for which the policy decision is being made + * @cred: pointer to a credentials structure for which the policy decision is + * being made + * @prop: LSM properties 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 + * @func_data: func specific data, may be NULL + * @allowed_algos: allowlist of hash algorithms for the IMA xattr * * Measure decision based on func/mask/fsmagic and LSM(subj/obj/type) * conditions. * - * (There is no need for locking when walking the policy list, - * as elements in the list are never deleted, nor does the list - * change.) + * Since the IMA policy may be updated multiple times we need to lock the + * list when walking it. Reads are many orders of magnitude more numerous + * than writes so ima_match_policy() is classical RCU candidate. */ -int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask, - int flags) +int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode, + const struct cred *cred, struct lsm_prop *prop, + enum ima_hooks func, int mask, int flags, int *pcr, + struct ima_template_desc **template_desc, + const char *func_data, unsigned int *allowed_algos) { struct ima_rule_entry *entry; int action = 0, actmask = flags | (flags << 1); + struct list_head *ima_rules_tmp; - list_for_each_entry(entry, ima_rules, list) { + if (template_desc && !*template_desc) + *template_desc = ima_template_desc_current(); + + rcu_read_lock(); + ima_rules_tmp = rcu_dereference(ima_rules); + list_for_each_entry_rcu(entry, ima_rules_tmp, list) { if (!(entry->action & actmask)) continue; - if (!ima_match_rules(entry, inode, func, mask)) + if (!ima_match_rules(entry, idmap, inode, cred, prop, + func, mask, func_data)) continue; - action |= entry->flags & IMA_ACTION_FLAGS; + action |= entry->flags & IMA_NONACTION_FLAGS; action |= entry->action & IMA_DO_MASK; - if (entry->action & IMA_APPRAISE) + if (entry->action & IMA_APPRAISE) { action |= get_subaction(entry, func); + action &= ~IMA_HASH; + if (ima_fail_unverifiable_sigs) + action |= IMA_FAIL_UNVERIFIABLE_SIGS; + + if (allowed_algos && + entry->flags & IMA_VALIDATE_ALGOS) + *allowed_algos = entry->allowed_algos; + } if (entry->action & IMA_DO_MASK) actmask &= ~(entry->action | entry->action << 1); else actmask &= ~(entry->action | entry->action >> 1); + if ((pcr) && (entry->flags & IMA_PCR)) + *pcr = entry->pcr; + + if (template_desc && entry->template) + *template_desc = entry->template; + if (!actmask) break; } + rcu_read_unlock(); return action; } /** + * ima_update_policy_flags() - Update global IMA variables + * + * Update ima_policy_flag and ima_setxattr_allowed_hash_algorithms + * based on the currently loaded policy. + * + * With ima_policy_flag, the decision to short circuit out of a function + * or not call the function in the first place can be made earlier. + * + * With ima_setxattr_allowed_hash_algorithms, the policy can restrict the + * set of hash algorithms accepted when updating the security.ima xattr of + * a file. + * + * Context: called after a policy update and at system initialization. + */ +void ima_update_policy_flags(void) +{ + struct ima_rule_entry *entry; + int new_policy_flag = 0; + struct list_head *ima_rules_tmp; + + rcu_read_lock(); + ima_rules_tmp = rcu_dereference(ima_rules); + list_for_each_entry_rcu(entry, ima_rules_tmp, list) { + /* + * SETXATTR_CHECK rules do not implement a full policy check + * because rule checking would probably have an important + * performance impact on setxattr(). As a consequence, only one + * SETXATTR_CHECK can be active at a given time. + * Because we want to preserve that property, we set out to use + * atomic_cmpxchg. Either: + * - the atomic was non-zero: a setxattr hash policy is + * already enforced, we do nothing + * - the atomic was zero: no setxattr policy was set, enable + * the setxattr hash policy + */ + if (entry->func == SETXATTR_CHECK) { + atomic_cmpxchg(&ima_setxattr_allowed_hash_algorithms, + 0, entry->allowed_algos); + /* SETXATTR_CHECK doesn't impact ima_policy_flag */ + continue; + } + + if (entry->action & IMA_DO_MASK) + new_policy_flag |= entry->action; + } + rcu_read_unlock(); + + ima_appraise |= (build_ima_appraise | temp_ima_appraise); + if (!ima_appraise) + new_policy_flag &= ~IMA_APPRAISE; + + ima_policy_flag = new_policy_flag; +} + +static int ima_appraise_flag(enum ima_hooks func) +{ + if (func == MODULE_CHECK) + return IMA_APPRAISE_MODULES; + else if (func == FIRMWARE_CHECK) + return IMA_APPRAISE_FIRMWARE; + else if (func == POLICY_CHECK) + return IMA_APPRAISE_POLICY; + else if (func == KEXEC_KERNEL_CHECK) + return IMA_APPRAISE_KEXEC; + 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 = strscpy(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. * - * ima_rules points to either the ima_default_rules or the - * the new ima_policy_rules. + * ima_rules points to either the ima_default_rules or the new ima_policy_rules. */ void __init ima_init_policy(void) { - int i, measure_entries, appraise_entries; - - /* if !ima_use_tcb set entries = 0 so we load NO default rules */ - measure_entries = ima_use_tcb ? ARRAY_SIZE(default_rules) : 0; - appraise_entries = ima_use_appraise_tcb ? - ARRAY_SIZE(default_appraise_rules) : 0; - - for (i = 0; i < measure_entries + appraise_entries; i++) { - if (i < measure_entries) - list_add_tail(&default_rules[i].list, - &ima_default_rules); - else { - int j = i - measure_entries; - - list_add_tail(&default_appraise_rules[j].list, - &ima_default_rules); - } + int build_appraise_entries, arch_entries; + + /* 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: + add_rules(original_measurement_rules, + ARRAY_SIZE(original_measurement_rules), + IMA_DEFAULT_POLICY); + break; + case DEFAULT_TCB: + add_rules(default_measurement_rules, + ARRAY_SIZE(default_measurement_rules), + IMA_DEFAULT_POLICY); + break; + default: + break; + } + + /* + * 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) + */ + 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. As the secure boot rules includes all of the build time + * rules, include either one or the other set of rules, but not both. + */ + 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); } - ima_rules = &ima_default_rules; + if (ima_use_appraise_tcb) + add_rules(default_appraise_rules, + ARRAY_SIZE(default_appraise_rules), + IMA_DEFAULT_POLICY); + + if (ima_use_critical_data) + add_rules(critical_data_rules, + ARRAY_SIZE(critical_data_rules), + IMA_DEFAULT_POLICY); + + atomic_set(&ima_setxattr_allowed_hash_algorithms, 0); + + ima_update_policy_flags(); +} + +/* Make sure we have a valid policy, at least containing some rules. */ +int ima_check_policy(void) +{ + if (list_empty(&ima_temp_rules)) + return -EINVAL; + return 0; } /** * ima_update_policy - update default_rules with new measure rules * * Called on file .release to update the default rules with a complete new - * policy. Once updated, the policy is locked, no additional rules can be - * added to the policy. + * policy. What we do here is to splice ima_policy_rules and ima_temp_rules so + * they make a queue. The policy may be updated multiple times and this is the + * RCU updater. + * + * Policy rules are never deleted so ima_policy_flag gets zeroed only once when + * we switch from the default policy to user defined. */ void ima_update_policy(void) { - const char *op = "policy_update"; - const char *cause = "already exists"; - int result = 1; - int audit_info = 0; + struct list_head *policy = &ima_policy_rules; + + list_splice_tail_init_rcu(&ima_temp_rules, policy, synchronize_rcu); + + if (ima_rules != (struct list_head __rcu *)policy) { + ima_policy_flag = 0; - if (ima_rules == &ima_default_rules) { - ima_rules = &ima_policy_rules; - cause = "complete"; - result = 0; + rcu_assign_pointer(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); } - integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, - NULL, op, cause, result, audit_info); + ima_update_policy_flags(); + + /* Custom IMA policy has been loaded */ + ima_process_queued_keys(); } -enum { - Opt_err = -1, - Opt_measure = 1, Opt_dont_measure, +/* Keep the enumeration in sync with the policy_tokens! */ +enum policy_opt { + Opt_measure, Opt_dont_measure, Opt_appraise, Opt_dont_appraise, - Opt_audit, + Opt_audit, Opt_dont_audit, Opt_hash, Opt_dont_hash, Opt_obj_user, Opt_obj_role, Opt_obj_type, Opt_subj_user, Opt_subj_role, Opt_subj_type, - Opt_func, Opt_mask, Opt_fsmagic, Opt_uid, Opt_fowner, - Opt_appraise_type, Opt_fsuuid + Opt_func, Opt_mask, Opt_fsmagic, Opt_fsname, Opt_fs_subtype, Opt_fsuuid, + Opt_uid_eq, Opt_euid_eq, Opt_gid_eq, Opt_egid_eq, + Opt_fowner_eq, Opt_fgroup_eq, + Opt_uid_gt, Opt_euid_gt, Opt_gid_gt, Opt_egid_gt, + 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 }; -static match_table_t policy_tokens = { +static const match_table_t policy_tokens = { {Opt_measure, "measure"}, {Opt_dont_measure, "dont_measure"}, {Opt_appraise, "appraise"}, {Opt_dont_appraise, "dont_appraise"}, {Opt_audit, "audit"}, + {Opt_dont_audit, "dont_audit"}, + {Opt_hash, "hash"}, + {Opt_dont_hash, "dont_hash"}, {Opt_obj_user, "obj_user=%s"}, {Opt_obj_role, "obj_role=%s"}, {Opt_obj_type, "obj_type=%s"}, @@ -370,10 +1109,36 @@ static match_table_t policy_tokens = { {Opt_func, "func=%s"}, {Opt_mask, "mask=%s"}, {Opt_fsmagic, "fsmagic=%s"}, + {Opt_fsname, "fsname=%s"}, + {Opt_fs_subtype, "fs_subtype=%s"}, {Opt_fsuuid, "fsuuid=%s"}, - {Opt_uid, "uid=%s"}, - {Opt_fowner, "fowner=%s"}, + {Opt_uid_eq, "uid=%s"}, + {Opt_euid_eq, "euid=%s"}, + {Opt_gid_eq, "gid=%s"}, + {Opt_egid_eq, "egid=%s"}, + {Opt_fowner_eq, "fowner=%s"}, + {Opt_fgroup_eq, "fgroup=%s"}, + {Opt_uid_gt, "uid>%s"}, + {Opt_euid_gt, "euid>%s"}, + {Opt_gid_gt, "gid>%s"}, + {Opt_egid_gt, "egid>%s"}, + {Opt_fowner_gt, "fowner>%s"}, + {Opt_fgroup_gt, "fgroup>%s"}, + {Opt_uid_lt, "uid<%s"}, + {Opt_euid_lt, "euid<%s"}, + {Opt_gid_lt, "gid<%s"}, + {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"}, + {Opt_permit_directio, "permit_directio"}, + {Opt_pcr, "pcr=%s"}, + {Opt_template, "template=%s"}, + {Opt_keyrings, "keyrings=%s"}, + {Opt_label, "label=%s"}, {Opt_err, NULL} }; @@ -390,42 +1155,300 @@ static int ima_lsm_rule_init(struct ima_rule_entry *entry, 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, + GFP_KERNEL); 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 == (struct list_head __rcu *)(&ima_default_rules)) { + kfree(entry->lsm[lsm_rule].args_p); + entry->lsm[lsm_rule].args_p = NULL; + result = -EINVAL; + } else + result = 0; } return result; } +static void ima_log_string_op(struct audit_buffer *ab, char *key, char *value, + enum policy_opt rule_operator) +{ + if (!ab) + return; + + switch (rule_operator) { + case Opt_uid_gt: + case Opt_euid_gt: + case Opt_gid_gt: + case Opt_egid_gt: + case Opt_fowner_gt: + case Opt_fgroup_gt: + audit_log_format(ab, "%s>", key); + break; + case Opt_uid_lt: + case Opt_euid_lt: + case Opt_gid_lt: + case Opt_egid_lt: + case Opt_fowner_lt: + case Opt_fgroup_lt: + audit_log_format(ab, "%s<", key); + break; + default: + audit_log_format(ab, "%s=", key); + } + audit_log_format(ab, "%s ", value); +} static void ima_log_string(struct audit_buffer *ab, char *key, char *value) { - audit_log_format(ab, "%s=", key); - audit_log_untrustedstring(ab, value); - audit_log_format(ab, " "); + ima_log_string_op(ab, key, value, Opt_err); +} + +/* + * 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 +} + +/* + * 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 */ + 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 | IMA_VALIDATE_ALGOS)) + 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 MMAP_CHECK_REQPROT: + 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_FS_SUBTYPE | + IMA_GID | IMA_EGID | + IMA_FGROUP | IMA_DIGSIG_REQUIRED | + IMA_PERMIT_DIRECTIO | IMA_VALIDATE_ALGOS | + IMA_CHECK_BLACKLIST | IMA_VERITY_REQUIRED)) + 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_FS_SUBTYPE | + IMA_GID | IMA_EGID | + IMA_FGROUP | IMA_DIGSIG_REQUIRED | + IMA_PERMIT_DIRECTIO | IMA_MODSIG_ALLOWED | + IMA_CHECK_BLACKLIST | IMA_VALIDATE_ALGOS)) + 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 | IMA_FS_SUBTYPE | + IMA_GID | IMA_EGID | + IMA_FGROUP)) + return false; + + break; + case KEY_CHECK: + if (entry->action & ~(MEASURE | DONT_MEASURE)) + return false; + + if (entry->flags & ~(IMA_FUNC | IMA_UID | IMA_GID | IMA_PCR | + IMA_KEYRINGS)) + return false; + + if (ima_rule_contains_lsm_cond(entry)) + return false; + + break; + case CRITICAL_DATA: + if (entry->action & ~(MEASURE | DONT_MEASURE)) + return false; + + if (entry->flags & ~(IMA_FUNC | IMA_UID | IMA_GID | IMA_PCR | + IMA_LABEL)) + return false; + + if (ima_rule_contains_lsm_cond(entry)) + return false; + + break; + case SETXATTR_CHECK: + /* any action other than APPRAISE is unsupported */ + if (entry->action != APPRAISE) + return false; + + /* SETXATTR_CHECK requires an appraise_algos parameter */ + if (!(entry->flags & IMA_VALIDATE_ALGOS)) + return false; + + /* + * full policies are not supported, they would have too + * much of a performance impact + */ + if (entry->flags & ~(IMA_FUNC | IMA_VALIDATE_ALGOS)) + 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_DIGSIG_REQUIRED)) + 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; +} + +static unsigned int ima_parse_appraise_algos(char *arg) +{ + unsigned int res = 0; + int idx; + char *token; + + while ((token = strsep(&arg, ",")) != NULL) { + idx = match_string(hash_algo_name, HASH_ALGO__LAST, token); + + if (idx < 0) { + pr_err("unknown hash algorithm \"%s\"", + token); + return 0; + } + + if (!crypto_has_alg(hash_algo_name[idx], 0, 0)) { + pr_err("unavailable hash algorithm \"%s\", check your kernel configuration", + token); + return 0; + } + + /* Add the hash algorithm to the 'allowed' bitfield */ + res |= (1U << idx); + } + + return res; } static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) { struct audit_buffer *ab; + char *from; char *p; + bool eid_token; /* either euid or egid */ + struct ima_template_desc *template_desc; int result = 0; - ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_INTEGRITY_RULE); + ab = integrity_audit_log_start(audit_context(), GFP_KERNEL, + AUDIT_INTEGRITY_POLICY_RULE); entry->uid = INVALID_UID; + entry->gid = INVALID_GID; entry->fowner = INVALID_UID; + entry->fgroup = INVALID_GID; + entry->uid_op = &uid_eq; + entry->gid_op = &gid_eq; + entry->fowner_op = &vfsuid_eq_kuid; + entry->fgroup_op = &vfsgid_eq_kgid; entry->action = UNKNOWN; while ((p = strsep(&rule, " \t")) != NULL) { substring_t args[MAX_OPT_ARGS]; int token; unsigned long lnum; - if (result < 0) + if (result < 0 || *p == '#') /* ignore suffixed comment */ break; if ((*p == '\0') || (*p == ' ') || (*p == '\t')) continue; @@ -471,6 +1494,30 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) entry->action = AUDIT; break; + case Opt_dont_audit: + ima_log_string(ab, "action", "dont_audit"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = DONT_AUDIT; + break; + case Opt_hash: + ima_log_string(ab, "action", "hash"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = HASH; + break; + case Opt_dont_hash: + ima_log_string(ab, "action", "dont_hash"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = DONT_HASH; + break; case Opt_func: ima_log_string(ab, "func", args[0].from); @@ -484,11 +1531,34 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) entry->func = FILE_CHECK; else if (strcmp(args[0].from, "MODULE_CHECK") == 0) entry->func = MODULE_CHECK; + else if (strcmp(args[0].from, "FIRMWARE_CHECK") == 0) + entry->func = FIRMWARE_CHECK; else if ((strcmp(args[0].from, "FILE_MMAP") == 0) || (strcmp(args[0].from, "MMAP_CHECK") == 0)) entry->func = MMAP_CHECK; + else if ((strcmp(args[0].from, "MMAP_CHECK_REQPROT") == 0)) + entry->func = MMAP_CHECK_REQPROT; else if (strcmp(args[0].from, "BPRM_CHECK") == 0) entry->func = BPRM_CHECK; + else if (strcmp(args[0].from, "CREDS_CHECK") == 0) + entry->func = CREDS_CHECK; + else if (strcmp(args[0].from, "KEXEC_KERNEL_CHECK") == + 0) + entry->func = KEXEC_KERNEL_CHECK; + else if (strcmp(args[0].from, "KEXEC_INITRAMFS_CHECK") + == 0) + 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 if (strcmp(args[0].from, "CRITICAL_DATA") == 0) + entry->func = CRITICAL_DATA; + else if (strcmp(args[0].from, "SETXATTR_CHECK") == 0) + entry->func = SETXATTR_CHECK; else result = -EINVAL; if (!result) @@ -500,18 +1570,23 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) if (entry->mask) result = -EINVAL; - if ((strcmp(args[0].from, "MAY_EXEC")) == 0) + from = args[0].from; + if (*from == '^') + from++; + + if ((strcmp(from, "MAY_EXEC")) == 0) entry->mask = MAY_EXEC; - else if (strcmp(args[0].from, "MAY_WRITE") == 0) + else if (strcmp(from, "MAY_WRITE") == 0) entry->mask = MAY_WRITE; - else if (strcmp(args[0].from, "MAY_READ") == 0) + else if (strcmp(from, "MAY_READ") == 0) entry->mask = MAY_READ; - else if (strcmp(args[0].from, "MAY_APPEND") == 0) + else if (strcmp(from, "MAY_APPEND") == 0) entry->mask = MAY_APPEND; else result = -EINVAL; if (!result) - entry->flags |= IMA_MASK; + entry->flags |= (*args[0].from == '^') + ? IMA_INMASK : IMA_MASK; break; case Opt_fsmagic: ima_log_string(ab, "fsmagic", args[0].from); @@ -521,59 +1596,206 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) break; } - result = strict_strtoul(args[0].from, 16, - &entry->fsmagic); + result = kstrtoul(args[0].from, 16, &entry->fsmagic); if (!result) entry->flags |= IMA_FSMAGIC; break; + case Opt_fsname: + ima_log_string(ab, "fsname", args[0].from); + + entry->fsname = kstrdup(args[0].from, GFP_KERNEL); + if (!entry->fsname) { + result = -ENOMEM; + break; + } + result = 0; + entry->flags |= IMA_FSNAME; + break; + case Opt_fs_subtype: + ima_log_string(ab, "fs_subtype", args[0].from); + + if (entry->fs_subtype) { + result = -EINVAL; + break; + } + + entry->fs_subtype = kstrdup(args[0].from, GFP_KERNEL); + if (!entry->fs_subtype) { + result = -ENOMEM; + break; + } + result = 0; + entry->flags |= IMA_FS_SUBTYPE; + 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_label: + ima_log_string(ab, "label", args[0].from); + + if (entry->label) { + result = -EINVAL; + break; + } + + entry->label = ima_alloc_rule_opt_list(args); + if (IS_ERR(entry->label)) { + result = PTR_ERR(entry->label); + entry->label = NULL; + break; + } + + entry->flags |= IMA_LABEL; + break; case Opt_fsuuid: ima_log_string(ab, "fsuuid", args[0].from); - if (memchr_inv(entry->fsuuid, 0x00, - sizeof(entry->fsuuid))) { + if (!uuid_is_null(&entry->fsuuid)) { result = -EINVAL; break; } - result = blk_part_pack_uuid(args[0].from, - entry->fsuuid); + result = uuid_parse(args[0].from, &entry->fsuuid); if (!result) entry->flags |= IMA_FSUUID; break; - case Opt_uid: - ima_log_string(ab, "uid", args[0].from); + 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: + eid_token = (token == Opt_euid_eq) || + (token == Opt_euid_gt) || + (token == Opt_euid_lt); + + ima_log_string_op(ab, eid_token ? "euid" : "uid", + args[0].from, token); if (uid_valid(entry->uid)) { result = -EINVAL; break; } - result = strict_strtoul(args[0].from, 10, &lnum); + result = kstrtoul(args[0].from, 10, &lnum); if (!result) { - entry->uid = make_kuid(current_user_ns(), (uid_t)lnum); - if (!uid_valid(entry->uid) || (((uid_t)lnum) != lnum)) + entry->uid = make_kuid(current_user_ns(), + (uid_t) lnum); + if (!uid_valid(entry->uid) || + (uid_t)lnum != lnum) result = -EINVAL; else - entry->flags |= IMA_UID; + entry->flags |= eid_token + ? IMA_EUID : IMA_UID; } break; - case Opt_fowner: - ima_log_string(ab, "fowner", args[0].from); + case Opt_gid_gt: + case Opt_egid_gt: + entry->gid_op = &gid_gt; + fallthrough; + case Opt_gid_lt: + case Opt_egid_lt: + if ((token == Opt_gid_lt) || (token == Opt_egid_lt)) + entry->gid_op = &gid_lt; + fallthrough; + case Opt_gid_eq: + case Opt_egid_eq: + eid_token = (token == Opt_egid_eq) || + (token == Opt_egid_gt) || + (token == Opt_egid_lt); + + ima_log_string_op(ab, eid_token ? "egid" : "gid", + args[0].from, token); + + if (gid_valid(entry->gid)) { + result = -EINVAL; + break; + } + + result = kstrtoul(args[0].from, 10, &lnum); + if (!result) { + entry->gid = make_kgid(current_user_ns(), + (gid_t)lnum); + if (!gid_valid(entry->gid) || + (((gid_t)lnum) != lnum)) + result = -EINVAL; + else + entry->flags |= eid_token + ? IMA_EGID : IMA_GID; + } + break; + case Opt_fowner_gt: + entry->fowner_op = &vfsuid_gt_kuid; + fallthrough; + case Opt_fowner_lt: + if (token == Opt_fowner_lt) + entry->fowner_op = &vfsuid_lt_kuid; + fallthrough; + case Opt_fowner_eq: + ima_log_string_op(ab, "fowner", args[0].from, token); if (uid_valid(entry->fowner)) { result = -EINVAL; break; } - result = strict_strtoul(args[0].from, 10, &lnum); + result = kstrtoul(args[0].from, 10, &lnum); if (!result) { - entry->fowner = make_kuid(current_user_ns(), (uid_t)lnum); - if (!uid_valid(entry->fowner) || (((uid_t)lnum) != lnum)) + entry->fowner = make_kuid(current_user_ns(), + (uid_t)lnum); + if (!uid_valid(entry->fowner) || + (((uid_t)lnum) != lnum)) result = -EINVAL; else entry->flags |= IMA_FOWNER; } break; + case Opt_fgroup_gt: + entry->fgroup_op = &vfsgid_gt_kgid; + fallthrough; + case Opt_fgroup_lt: + if (token == Opt_fgroup_lt) + entry->fgroup_op = &vfsgid_lt_kgid; + fallthrough; + case Opt_fgroup_eq: + ima_log_string_op(ab, "fgroup", args[0].from, token); + + if (gid_valid(entry->fgroup)) { + result = -EINVAL; + break; + } + + result = kstrtoul(args[0].from, 10, &lnum); + if (!result) { + entry->fgroup = make_kgid(current_user_ns(), + (gid_t)lnum); + if (!gid_valid(entry->fgroup) || + (((gid_t)lnum) != lnum)) + result = -EINVAL; + else + entry->flags |= IMA_FGROUP; + } + break; case Opt_obj_user: ima_log_string(ab, "obj_user", args[0].from); result = ima_lsm_rule_init(entry, args, @@ -610,17 +1832,96 @@ 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 (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: - if (entry->action != APPRAISE) { + ima_log_string(ab, "appraise_type", args[0].from); + + if ((strcmp(args[0].from, "imasig")) == 0) { + if (entry->flags & IMA_VERITY_REQUIRED) + result = -EINVAL; + else + entry->flags |= IMA_DIGSIG_REQUIRED | IMA_CHECK_BLACKLIST; + } 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 | IMA_CHECK_BLACKLIST; + 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 | IMA_CHECK_BLACKLIST; + } else { + result = -EINVAL; + } + break; + case Opt_appraise_flag: + ima_log_string(ab, "appraise_flag", args[0].from); + break; + case Opt_appraise_algos: + ima_log_string(ab, "appraise_algos", args[0].from); + + if (entry->allowed_algos) { result = -EINVAL; break; } - ima_log_string(ab, "appraise_type", args[0].from); - if ((strcmp(args[0].from, "imasig")) == 0) - entry->flags |= IMA_DIGSIG_REQUIRED; + entry->allowed_algos = + ima_parse_appraise_algos(args[0].from); + /* invalid or empty list of algorithms */ + if (!entry->allowed_algos) { + result = -EINVAL; + break; + } + + entry->flags |= IMA_VALIDATE_ALGOS; + + break; + case Opt_permit_directio: + entry->flags |= IMA_PERMIT_DIRECTIO; + break; + case Opt_pcr: + ima_log_string(ab, "pcr", args[0].from); + + result = kstrtoint(args[0].from, 10, &entry->pcr); + if (result || INVALID_PCR(entry->pcr)) + result = -EINVAL; else + 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); @@ -628,10 +1929,26 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) break; } } - if (!result && (entry->action == UNKNOWN)) + if (!result && !ima_validate_rule(entry)) result = -EINVAL; - else if (entry->func == MODULE_CHECK) - ima_appraise |= IMA_APPRAISE_MODULES; + 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); + } + + /* 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; @@ -639,26 +1956,25 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) /** * ima_parse_add_rule - add a rule to ima_policy_rules - * @rule - ima measurement policy rule + * @rule: ima measurement policy rule * - * Uses a mutex to protect the policy list from multiple concurrent writers. + * 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 */ ssize_t ima_parse_add_rule(char *rule) { - const char *op = "update_policy"; + static const char op[] = "update_policy"; char *p; struct ima_rule_entry *entry; ssize_t result, len; int audit_info = 0; - /* Prevent installed policy from changing */ - if (ima_rules != &ima_default_rules) { - integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, - NULL, op, "already exists", - -EACCES, audit_info); - return -EACCES; - } + p = strsep(&rule, "\n"); + len = strlen(p) + 1; + p += strspn(p, " \t"); + + if (*p == '#' || *p == '\0') + return len; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) { @@ -669,43 +1985,400 @@ ssize_t ima_parse_add_rule(char *rule) INIT_LIST_HEAD(&entry->list); - p = strsep(&rule, "\n"); - len = strlen(p) + 1; - - if (*p == '#') { - kfree(entry); - return len; - } - 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, + NULL, op, "invalid-policy", result, audit_info); return result; } - mutex_lock(&ima_rules_mutex); - list_add_tail(&entry->list, &ima_policy_rules); - mutex_unlock(&ima_rules_mutex); + list_add_tail(&entry->list, &ima_temp_rules); return len; } -/* ima_delete_rules called to cleanup invalid policy */ +/** + * ima_delete_rules() - called to cleanup invalid in-flight policy. + * + * We don't need locking as we operate on the temp list, which is + * different from the active one. There is also only one user of + * ima_delete_rules() at a time. + */ void ima_delete_rules(void) { struct ima_rule_entry *entry, *tmp; + + temp_ima_appraise = 0; + list_for_each_entry_safe(entry, tmp, &ima_temp_rules, list) { + list_del(&entry->list); + 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 { + mask_exec = 0, mask_write, mask_read, mask_append +}; + +static const char *const mask_tokens[] = { + "^MAY_EXEC", + "^MAY_WRITE", + "^MAY_READ", + "^MAY_APPEND" +}; + +void *ima_policy_start(struct seq_file *m, loff_t *pos) +{ + loff_t l = *pos; + struct ima_rule_entry *entry; + struct list_head *ima_rules_tmp; + + rcu_read_lock(); + ima_rules_tmp = rcu_dereference(ima_rules); + list_for_each_entry_rcu(entry, ima_rules_tmp, list) { + if (!l--) { + rcu_read_unlock(); + return entry; + } + } + rcu_read_unlock(); + return NULL; +} + +void *ima_policy_next(struct seq_file *m, void *v, loff_t *pos) +{ + struct ima_rule_entry *entry = v; + + rcu_read_lock(); + entry = list_entry_rcu(entry->list.next, struct ima_rule_entry, list); + rcu_read_unlock(); + (*pos)++; + + return (&entry->list == &ima_default_rules || + &entry->list == &ima_policy_rules) ? NULL : entry; +} + +void ima_policy_stop(struct seq_file *m, void *v) +{ +} + +#define pt(token) policy_tokens[token].pattern +#define mt(token) mask_tokens[token] + +/* + * policy_func_show - display the ima_hooks policy rule + */ +static void policy_func_show(struct seq_file *m, enum ima_hooks func) +{ + if (func > 0 && func < MAX_CHECK) + seq_printf(m, "func=%s ", func_tokens[func]); + else + 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]); +} + +static void ima_policy_show_appraise_algos(struct seq_file *m, + unsigned int allowed_hashes) +{ + int idx, list_size = 0; + + for (idx = 0; idx < HASH_ALGO__LAST; idx++) { + if (!(allowed_hashes & (1U << idx))) + continue; + + /* only add commas if the list contains multiple entries */ + if (list_size++) + seq_puts(m, ","); + + seq_puts(m, hash_algo_name[idx]); + } +} + +int ima_policy_show(struct seq_file *m, void *v) +{ + struct ima_rule_entry *entry = v; int i; + char tbuf[64] = {0,}; + int offset = 0; - mutex_lock(&ima_rules_mutex); - list_for_each_entry_safe(entry, tmp, &ima_policy_rules, list) { - for (i = 0; i < MAX_LSM_RULES; i++) - kfree(entry->lsm[i].args_p); + rcu_read_lock(); - list_del(&entry->list); - kfree(entry); + /* 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)); + if (entry->action & DONT_MEASURE) + seq_puts(m, pt(Opt_dont_measure)); + if (entry->action & APPRAISE) + seq_puts(m, pt(Opt_appraise)); + if (entry->action & DONT_APPRAISE) + seq_puts(m, pt(Opt_dont_appraise)); + if (entry->action & AUDIT) + seq_puts(m, pt(Opt_audit)); + if (entry->action & DONT_AUDIT) + seq_puts(m, pt(Opt_dont_audit)); + if (entry->action & HASH) + seq_puts(m, pt(Opt_hash)); + if (entry->action & DONT_HASH) + seq_puts(m, pt(Opt_dont_hash)); + + seq_puts(m, " "); + + if (entry->flags & IMA_FUNC) + policy_func_show(m, entry->func); + + if ((entry->flags & IMA_MASK) || (entry->flags & IMA_INMASK)) { + if (entry->flags & IMA_MASK) + offset = 1; + if (entry->mask & MAY_EXEC) + seq_printf(m, pt(Opt_mask), mt(mask_exec) + offset); + if (entry->mask & MAY_WRITE) + seq_printf(m, pt(Opt_mask), mt(mask_write) + offset); + if (entry->mask & MAY_READ) + seq_printf(m, pt(Opt_mask), mt(mask_read) + offset); + if (entry->mask & MAY_APPEND) + seq_printf(m, pt(Opt_mask), mt(mask_append) + offset); + seq_puts(m, " "); + } + + if (entry->flags & IMA_FSMAGIC) { + snprintf(tbuf, sizeof(tbuf), "0x%lx", entry->fsmagic); + seq_printf(m, pt(Opt_fsmagic), tbuf); + seq_puts(m, " "); + } + + 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_FS_SUBTYPE) { + snprintf(tbuf, sizeof(tbuf), "%s", entry->fs_subtype); + seq_printf(m, pt(Opt_fs_subtype), tbuf); + seq_puts(m, " "); + } + + if (entry->flags & IMA_KEYRINGS) { + seq_puts(m, "keyrings="); + ima_show_rule_opt_list(m, entry->keyrings); + seq_puts(m, " "); + } + + if (entry->flags & IMA_LABEL) { + seq_puts(m, "label="); + ima_show_rule_opt_list(m, entry->label); + seq_puts(m, " "); + } + + if (entry->flags & IMA_PCR) { + snprintf(tbuf, sizeof(tbuf), "%d", entry->pcr); + seq_printf(m, pt(Opt_pcr), tbuf); + seq_puts(m, " "); + } + + if (entry->flags & IMA_FSUUID) { + seq_printf(m, "fsuuid=%pU", &entry->fsuuid); + seq_puts(m, " "); + } + + if (entry->flags & IMA_UID) { + snprintf(tbuf, sizeof(tbuf), "%d", __kuid_val(entry->uid)); + if (entry->uid_op == &uid_gt) + seq_printf(m, pt(Opt_uid_gt), tbuf); + else if (entry->uid_op == &uid_lt) + seq_printf(m, pt(Opt_uid_lt), tbuf); + else + seq_printf(m, pt(Opt_uid_eq), tbuf); + seq_puts(m, " "); + } + + if (entry->flags & IMA_EUID) { + snprintf(tbuf, sizeof(tbuf), "%d", __kuid_val(entry->uid)); + if (entry->uid_op == &uid_gt) + seq_printf(m, pt(Opt_euid_gt), tbuf); + else if (entry->uid_op == &uid_lt) + seq_printf(m, pt(Opt_euid_lt), tbuf); + else + seq_printf(m, pt(Opt_euid_eq), tbuf); + seq_puts(m, " "); + } + + if (entry->flags & IMA_GID) { + snprintf(tbuf, sizeof(tbuf), "%d", __kgid_val(entry->gid)); + if (entry->gid_op == &gid_gt) + seq_printf(m, pt(Opt_gid_gt), tbuf); + else if (entry->gid_op == &gid_lt) + seq_printf(m, pt(Opt_gid_lt), tbuf); + else + seq_printf(m, pt(Opt_gid_eq), tbuf); + seq_puts(m, " "); + } + + if (entry->flags & IMA_EGID) { + snprintf(tbuf, sizeof(tbuf), "%d", __kgid_val(entry->gid)); + if (entry->gid_op == &gid_gt) + seq_printf(m, pt(Opt_egid_gt), tbuf); + else if (entry->gid_op == &gid_lt) + seq_printf(m, pt(Opt_egid_lt), tbuf); + else + seq_printf(m, pt(Opt_egid_eq), tbuf); + seq_puts(m, " "); + } + + if (entry->flags & IMA_FOWNER) { + snprintf(tbuf, sizeof(tbuf), "%d", __kuid_val(entry->fowner)); + if (entry->fowner_op == &vfsuid_gt_kuid) + seq_printf(m, pt(Opt_fowner_gt), tbuf); + else if (entry->fowner_op == &vfsuid_lt_kuid) + seq_printf(m, pt(Opt_fowner_lt), tbuf); + else + seq_printf(m, pt(Opt_fowner_eq), tbuf); + seq_puts(m, " "); + } + + if (entry->flags & IMA_FGROUP) { + snprintf(tbuf, sizeof(tbuf), "%d", __kgid_val(entry->fgroup)); + if (entry->fgroup_op == &vfsgid_gt_kgid) + seq_printf(m, pt(Opt_fgroup_gt), tbuf); + else if (entry->fgroup_op == &vfsgid_lt_kgid) + seq_printf(m, pt(Opt_fgroup_lt), tbuf); + else + seq_printf(m, pt(Opt_fgroup_eq), tbuf); + seq_puts(m, " "); } - mutex_unlock(&ima_rules_mutex); + + if (entry->flags & IMA_VALIDATE_ALGOS) { + seq_puts(m, "appraise_algos="); + ima_policy_show_appraise_algos(m, entry->allowed_algos); + seq_puts(m, " "); + } + + for (i = 0; i < MAX_LSM_RULES; i++) { + if (entry->lsm[i].rule) { + switch (i) { + case LSM_OBJ_USER: + seq_printf(m, pt(Opt_obj_user), + entry->lsm[i].args_p); + break; + case LSM_OBJ_ROLE: + seq_printf(m, pt(Opt_obj_role), + entry->lsm[i].args_p); + break; + case LSM_OBJ_TYPE: + seq_printf(m, pt(Opt_obj_type), + entry->lsm[i].args_p); + break; + case LSM_SUBJ_USER: + seq_printf(m, pt(Opt_subj_user), + entry->lsm[i].args_p); + break; + case LSM_SUBJ_ROLE: + seq_printf(m, pt(Opt_subj_role), + entry->lsm[i].args_p); + break; + case LSM_SUBJ_TYPE: + seq_printf(m, pt(Opt_subj_type), + entry->lsm[i].args_p); + break; + } + seq_puts(m, " "); + } + } + if (entry->template) + seq_printf(m, "template=%s ", entry->template->name); + if (entry->flags & IMA_DIGSIG_REQUIRED) { + 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 "); + } + if (entry->flags & IMA_VERITY_REQUIRED) + seq_puts(m, "digest_type=verity "); + if (entry->flags & IMA_PERMIT_DIRECTIO) + seq_puts(m, "permit_directio "); + rcu_read_unlock(); + seq_puts(m, "\n"); + 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; + struct list_head *ima_rules_tmp; + + 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(); + ima_rules_tmp = rcu_dereference(ima_rules); + list_for_each_entry_rcu(entry, ima_rules_tmp, 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 */ |
