diff options
-rw-r--r-- | include/uapi/linux/audit.h | 3 | ||||
-rw-r--r-- | security/ipe/Kconfig | 2 | ||||
-rw-r--r-- | security/ipe/Makefile | 1 | ||||
-rw-r--r-- | security/ipe/audit.c | 227 | ||||
-rw-r--r-- | security/ipe/audit.h | 18 | ||||
-rw-r--r-- | security/ipe/eval.c | 45 | ||||
-rw-r--r-- | security/ipe/eval.h | 13 | ||||
-rw-r--r-- | security/ipe/fs.c | 68 | ||||
-rw-r--r-- | security/ipe/hooks.c | 10 | ||||
-rw-r--r-- | security/ipe/hooks.h | 11 | ||||
-rw-r--r-- | security/ipe/policy.c | 4 |
11 files changed, 384 insertions, 18 deletions
diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h index d676ed2b246e..75e21a135483 100644 --- a/include/uapi/linux/audit.h +++ b/include/uapi/linux/audit.h @@ -143,6 +143,9 @@ #define AUDIT_MAC_UNLBL_STCDEL 1417 /* NetLabel: del a static label */ #define AUDIT_MAC_CALIPSO_ADD 1418 /* NetLabel: add CALIPSO DOI entry */ #define AUDIT_MAC_CALIPSO_DEL 1419 /* NetLabel: del CALIPSO DOI entry */ +#define AUDIT_IPE_ACCESS 1420 /* IPE denial or grant */ +#define AUDIT_IPE_CONFIG_CHANGE 1421 /* IPE config change */ +#define AUDIT_IPE_POLICY_LOAD 1422 /* IPE policy load */ #define AUDIT_FIRST_KERN_ANOM_MSG 1700 #define AUDIT_LAST_KERN_ANOM_MSG 1799 diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig index e4875fb04883..ac4d558e69d5 100644 --- a/security/ipe/Kconfig +++ b/security/ipe/Kconfig @@ -5,7 +5,7 @@ menuconfig SECURITY_IPE bool "Integrity Policy Enforcement (IPE)" - depends on SECURITY && SECURITYFS + depends on SECURITY && SECURITYFS && AUDIT && AUDITSYSCALL select PKCS7_MESSAGE_PARSER select SYSTEM_DATA_VERIFICATION help diff --git a/security/ipe/Makefile b/security/ipe/Makefile index b97f8c10fe01..62caccba14b4 100644 --- a/security/ipe/Makefile +++ b/security/ipe/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_SECURITY_IPE) += \ policy.o \ policy_fs.o \ policy_parser.o \ + audit.o \ diff --git a/security/ipe/audit.c b/security/ipe/audit.c new file mode 100644 index 000000000000..5a859f88cfb4 --- /dev/null +++ b/security/ipe/audit.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#include <linux/slab.h> +#include <linux/audit.h> +#include <linux/types.h> +#include <crypto/hash.h> + +#include "ipe.h" +#include "eval.h" +#include "hooks.h" +#include "policy.h" +#include "audit.h" + +#define ACTSTR(x) ((x) == IPE_ACTION_ALLOW ? "ALLOW" : "DENY") + +#define IPE_AUDIT_HASH_ALG "sha256" + +#define AUDIT_POLICY_LOAD_FMT "policy_name=\"%s\" policy_version=%hu.%hu.%hu "\ + "policy_digest=" IPE_AUDIT_HASH_ALG ":" +#define AUDIT_OLD_ACTIVE_POLICY_FMT "old_active_pol_name=\"%s\" "\ + "old_active_pol_version=%hu.%hu.%hu "\ + "old_policy_digest=" IPE_AUDIT_HASH_ALG ":" +#define AUDIT_OLD_ACTIVE_POLICY_NULL_FMT "old_active_pol_name=? "\ + "old_active_pol_version=? "\ + "old_policy_digest=?" +#define AUDIT_NEW_ACTIVE_POLICY_FMT "new_active_pol_name=\"%s\" "\ + "new_active_pol_version=%hu.%hu.%hu "\ + "new_policy_digest=" IPE_AUDIT_HASH_ALG ":" + +static const char *const audit_op_names[__IPE_OP_MAX + 1] = { + "EXECUTE", + "FIRMWARE", + "KMODULE", + "KEXEC_IMAGE", + "KEXEC_INITRAMFS", + "POLICY", + "X509_CERT", + "UNKNOWN", +}; + +static const char *const audit_hook_names[__IPE_HOOK_MAX] = { + "BPRM_CHECK", + "MMAP", + "MPROTECT", + "KERNEL_READ", + "KERNEL_LOAD", +}; + +static const char *const audit_prop_names[__IPE_PROP_MAX] = { + "boot_verified=FALSE", + "boot_verified=TRUE", +}; + +/** + * audit_rule() - audit an IPE policy rule. + * @ab: Supplies a pointer to the audit_buffer to append to. + * @r: Supplies a pointer to the ipe_rule to approximate a string form for. + */ +static void audit_rule(struct audit_buffer *ab, const struct ipe_rule *r) +{ + const struct ipe_prop *ptr; + + audit_log_format(ab, " rule=\"op=%s ", audit_op_names[r->op]); + + list_for_each_entry(ptr, &r->props, next) + audit_log_format(ab, "%s ", audit_prop_names[ptr->type]); + + audit_log_format(ab, "action=%s\"", ACTSTR(r->action)); +} + +/** + * ipe_audit_match() - Audit a rule match in a policy evaluation. + * @ctx: Supplies a pointer to the evaluation context that was used in the + * evaluation. + * @match_type: Supplies the scope of the match: rule, operation default, + * global default. + * @act: Supplies the IPE's evaluation decision, deny or allow. + * @r: Supplies a pointer to the rule that was matched, if possible. + */ +void ipe_audit_match(const struct ipe_eval_ctx *const ctx, + enum ipe_match match_type, + enum ipe_action_type act, const struct ipe_rule *const r) +{ + const char *op = audit_op_names[ctx->op]; + char comm[sizeof(current->comm)]; + struct audit_buffer *ab; + struct inode *inode; + + if (act != IPE_ACTION_DENY && !READ_ONCE(success_audit)) + return; + + ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN, + AUDIT_IPE_ACCESS); + if (!ab) + return; + + audit_log_format(ab, "ipe_op=%s ipe_hook=%s pid=%d comm=", + op, audit_hook_names[ctx->hook], + task_tgid_nr(current)); + audit_log_untrustedstring(ab, get_task_comm(comm, current)); + + if (ctx->file) { + audit_log_d_path(ab, " path=", &ctx->file->f_path); + inode = file_inode(ctx->file); + if (inode) { + audit_log_format(ab, " dev="); + audit_log_untrustedstring(ab, inode->i_sb->s_id); + audit_log_format(ab, " ino=%lu", inode->i_ino); + } else { + audit_log_format(ab, " dev=? ino=?"); + } + } else { + audit_log_format(ab, " path=? dev=? ino=?"); + } + + if (match_type == IPE_MATCH_RULE) + audit_rule(ab, r); + else if (match_type == IPE_MATCH_TABLE) + audit_log_format(ab, " rule=\"DEFAULT op=%s action=%s\"", op, + ACTSTR(act)); + else + audit_log_format(ab, " rule=\"DEFAULT action=%s\"", + ACTSTR(act)); + + audit_log_end(ab); +} + +/** + * audit_policy() - Audit a policy's name, version and thumbprint to @ab. + * @ab: Supplies a pointer to the audit buffer to append to. + * @audit_format: Supplies a pointer to the audit format string + * @p: Supplies a pointer to the policy to audit. + */ +static void audit_policy(struct audit_buffer *ab, + const char *audit_format, + const struct ipe_policy *const p) +{ + SHASH_DESC_ON_STACK(desc, tfm); + struct crypto_shash *tfm; + u8 *digest = NULL; + + tfm = crypto_alloc_shash(IPE_AUDIT_HASH_ALG, 0, 0); + if (IS_ERR(tfm)) + return; + + desc->tfm = tfm; + + digest = kzalloc(crypto_shash_digestsize(tfm), GFP_KERNEL); + if (!digest) + goto out; + + if (crypto_shash_init(desc)) + goto out; + + if (crypto_shash_update(desc, p->pkcs7, p->pkcs7len)) + goto out; + + if (crypto_shash_final(desc, digest)) + goto out; + + audit_log_format(ab, audit_format, p->parsed->name, + p->parsed->version.major, p->parsed->version.minor, + p->parsed->version.rev); + audit_log_n_hex(ab, digest, crypto_shash_digestsize(tfm)); + +out: + kfree(digest); + crypto_free_shash(tfm); +} + +/** + * ipe_audit_policy_activation() - Audit a policy being activated. + * @op: Supplies a pointer to the previously activated policy to audit. + * @np: Supplies a pointer to the newly activated policy to audit. + */ +void ipe_audit_policy_activation(const struct ipe_policy *const op, + const struct ipe_policy *const np) +{ + struct audit_buffer *ab; + + ab = audit_log_start(audit_context(), GFP_KERNEL, + AUDIT_IPE_CONFIG_CHANGE); + if (!ab) + return; + + if (op) { + audit_policy(ab, AUDIT_OLD_ACTIVE_POLICY_FMT, op); + audit_log_format(ab, " "); + } else { + /* + * old active policy can be NULL if there is no kernel + * built-in policy + */ + audit_log_format(ab, AUDIT_OLD_ACTIVE_POLICY_NULL_FMT); + audit_log_format(ab, " "); + } + audit_policy(ab, AUDIT_NEW_ACTIVE_POLICY_FMT, np); + audit_log_format(ab, " auid=%u ses=%u lsm=ipe res=1", + from_kuid(&init_user_ns, audit_get_loginuid(current)), + audit_get_sessionid(current)); + + audit_log_end(ab); +} + +/** + * ipe_audit_policy_load() - Audit a policy being loaded into the kernel. + * @p: Supplies a pointer to the policy to audit. + */ +void ipe_audit_policy_load(const struct ipe_policy *const p) +{ + struct audit_buffer *ab; + + ab = audit_log_start(audit_context(), GFP_KERNEL, + AUDIT_IPE_POLICY_LOAD); + if (!ab) + return; + + audit_policy(ab, AUDIT_POLICY_LOAD_FMT, p); + audit_log_format(ab, " auid=%u ses=%u lsm=ipe res=1", + from_kuid(&init_user_ns, audit_get_loginuid(current)), + audit_get_sessionid(current)); + + audit_log_end(ab); +} diff --git a/security/ipe/audit.h b/security/ipe/audit.h new file mode 100644 index 000000000000..3ba8b8a91541 --- /dev/null +++ b/security/ipe/audit.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#ifndef _IPE_AUDIT_H +#define _IPE_AUDIT_H + +#include "policy.h" + +void ipe_audit_match(const struct ipe_eval_ctx *const ctx, + enum ipe_match match_type, + enum ipe_action_type act, const struct ipe_rule *const r); +void ipe_audit_policy_load(const struct ipe_policy *const p); +void ipe_audit_policy_activation(const struct ipe_policy *const op, + const struct ipe_policy *const np); + +#endif /* _IPE_AUDIT_H */ diff --git a/security/ipe/eval.c b/security/ipe/eval.c index d73d73dfed52..b99ed4bb09cf 100644 --- a/security/ipe/eval.c +++ b/security/ipe/eval.c @@ -9,12 +9,15 @@ #include <linux/file.h> #include <linux/sched.h> #include <linux/rcupdate.h> +#include <linux/moduleparam.h> #include "ipe.h" #include "eval.h" #include "policy.h" +#include "audit.h" struct ipe_policy __rcu *ipe_active_policy; +bool success_audit; #define FILE_SUPERBLOCK(f) ((f)->f_path.mnt->mnt_sb) @@ -33,13 +36,16 @@ static void build_ipe_sb_ctx(struct ipe_eval_ctx *ctx, const struct file *const * @ctx: Supplies a pointer to the context to be populated. * @file: Supplies a pointer to the file to associated with the evaluation. * @op: Supplies the IPE policy operation associated with the evaluation. + * @hook: Supplies the LSM hook associated with the evaluation. */ void ipe_build_eval_ctx(struct ipe_eval_ctx *ctx, const struct file *file, - enum ipe_op_type op) + enum ipe_op_type op, + enum ipe_hook_type hook) { ctx->file = file; ctx->op = op; + ctx->hook = hook; if (file) build_ipe_sb_ctx(ctx, file); @@ -100,6 +106,7 @@ int ipe_evaluate_event(const struct ipe_eval_ctx *const ctx) struct ipe_policy *pol = NULL; struct ipe_prop *prop = NULL; enum ipe_action_type action; + enum ipe_match match_type; bool match = false; rcu_read_lock(); @@ -111,14 +118,14 @@ int ipe_evaluate_event(const struct ipe_eval_ctx *const ctx) } if (ctx->op == IPE_OP_INVALID) { - if (pol->parsed->global_default_action == IPE_ACTION_DENY) { - rcu_read_unlock(); - return -EACCES; - } - if (pol->parsed->global_default_action == IPE_ACTION_INVALID) + if (pol->parsed->global_default_action == IPE_ACTION_INVALID) { WARN(1, "no default rule set for unknown op, ALLOW it"); - rcu_read_unlock(); - return 0; + action = IPE_ACTION_ALLOW; + } else { + action = pol->parsed->global_default_action; + } + match_type = IPE_MATCH_GLOBAL; + goto eval; } rules = &pol->parsed->rules[ctx->op]; @@ -136,16 +143,32 @@ int ipe_evaluate_event(const struct ipe_eval_ctx *const ctx) break; } - if (match) + if (match) { action = rule->action; - else if (rules->default_action != IPE_ACTION_INVALID) + match_type = IPE_MATCH_RULE; + } else if (rules->default_action != IPE_ACTION_INVALID) { action = rules->default_action; - else + match_type = IPE_MATCH_TABLE; + } else { action = pol->parsed->global_default_action; + match_type = IPE_MATCH_GLOBAL; + } +eval: + ipe_audit_match(ctx, match_type, action, rule); rcu_read_unlock(); + if (action == IPE_ACTION_DENY) return -EACCES; return 0; } + +/* Set the right module name */ +#ifdef KBUILD_MODNAME +#undef KBUILD_MODNAME +#define KBUILD_MODNAME "ipe" +#endif + +module_param(success_audit, bool, 0400); +MODULE_PARM_DESC(success_audit, "Start IPE with success auditing enabled"); diff --git a/security/ipe/eval.h b/security/ipe/eval.h index 0fa6492354dd..42b74a7a7c2b 100644 --- a/security/ipe/eval.h +++ b/security/ipe/eval.h @@ -10,10 +10,12 @@ #include <linux/types.h> #include "policy.h" +#include "hooks.h" #define IPE_EVAL_CTX_INIT ((struct ipe_eval_ctx){ 0 }) extern struct ipe_policy __rcu *ipe_active_policy; +extern bool success_audit; struct ipe_superblock { bool initramfs; @@ -21,14 +23,23 @@ struct ipe_superblock { struct ipe_eval_ctx { enum ipe_op_type op; + enum ipe_hook_type hook; const struct file *file; bool initramfs; }; +enum ipe_match { + IPE_MATCH_RULE = 0, + IPE_MATCH_TABLE, + IPE_MATCH_GLOBAL, + __IPE_MATCH_MAX +}; + void ipe_build_eval_ctx(struct ipe_eval_ctx *ctx, const struct file *file, - enum ipe_op_type op); + enum ipe_op_type op, + enum ipe_hook_type hook); int ipe_evaluate_event(const struct ipe_eval_ctx *const ctx); #endif /* _IPE_EVAL_H */ diff --git a/security/ipe/fs.c b/security/ipe/fs.c index 49484c8feead..9e410982b759 100644 --- a/security/ipe/fs.c +++ b/security/ipe/fs.c @@ -8,11 +8,62 @@ #include "ipe.h" #include "fs.h" +#include "eval.h" #include "policy.h" +#include "audit.h" static struct dentry *np __ro_after_init; static struct dentry *root __ro_after_init; struct dentry *policy_root __ro_after_init; +static struct dentry *audit_node __ro_after_init; + +/** + * setaudit() - Write handler for the securityfs node, "ipe/success_audit" + * @f: Supplies a file structure representing the securityfs node. + * @data: Supplies a buffer passed to the write syscall. + * @len: Supplies the length of @data. + * @offset: unused. + * + * Return: + * * Length of buffer written - Success + * * %-EPERM - Insufficient permission + */ +static ssize_t setaudit(struct file *f, const char __user *data, + size_t len, loff_t *offset) +{ + int rc = 0; + bool value; + + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) + return -EPERM; + + rc = kstrtobool_from_user(data, len, &value); + if (rc) + return rc; + + WRITE_ONCE(success_audit, value); + + return len; +} + +/** + * getaudit() - Read handler for the securityfs node, "ipe/success_audit" + * @f: Supplies a file structure representing the securityfs node. + * @data: Supplies a buffer passed to the read syscall. + * @len: Supplies the length of @data. + * @offset: unused. + * + * Return: Length of buffer written + */ +static ssize_t getaudit(struct file *f, char __user *data, + size_t len, loff_t *offset) +{ + const char *result; + + result = ((READ_ONCE(success_audit)) ? "1" : "0"); + + return simple_read_from_buffer(data, len, offset, result, 1); +} /** * new_policy() - Write handler for the securityfs node, "ipe/new_policy". @@ -51,6 +102,10 @@ static ssize_t new_policy(struct file *f, const char __user *data, } rc = ipe_new_policyfs_node(p); + if (rc) + goto out; + + ipe_audit_policy_load(p); out: if (rc < 0) @@ -63,6 +118,11 @@ static const struct file_operations np_fops = { .write = new_policy, }; +static const struct file_operations audit_fops = { + .write = setaudit, + .read = getaudit, +}; + /** * ipe_init_securityfs() - Initialize IPE's securityfs tree at fsinit. * @@ -82,6 +142,13 @@ static int __init ipe_init_securityfs(void) goto err; } + audit_node = securityfs_create_file("success_audit", 0600, root, + NULL, &audit_fops); + if (IS_ERR(audit_node)) { + rc = PTR_ERR(audit_node); + goto err; + } + policy_root = securityfs_create_dir("policies", root); if (IS_ERR(policy_root)) { rc = PTR_ERR(policy_root); @@ -98,6 +165,7 @@ static int __init ipe_init_securityfs(void) err: securityfs_remove(np); securityfs_remove(policy_root); + securityfs_remove(audit_node); securityfs_remove(root); return rc; } diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c index 0bd351e2b32a..e92228723784 100644 --- a/security/ipe/hooks.c +++ b/security/ipe/hooks.c @@ -29,7 +29,7 @@ int ipe_bprm_check_security(struct linux_binprm *bprm) { struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT; - ipe_build_eval_ctx(&ctx, bprm->file, IPE_OP_EXEC); + ipe_build_eval_ctx(&ctx, bprm->file, IPE_OP_EXEC, IPE_HOOK_BPRM_CHECK); return ipe_evaluate_event(&ctx); } @@ -54,7 +54,7 @@ int ipe_mmap_file(struct file *f, unsigned long reqprot __always_unused, struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT; if (prot & PROT_EXEC) { - ipe_build_eval_ctx(&ctx, f, IPE_OP_EXEC); + ipe_build_eval_ctx(&ctx, f, IPE_OP_EXEC, IPE_HOOK_MMAP); return ipe_evaluate_event(&ctx); } @@ -86,7 +86,7 @@ int ipe_file_mprotect(struct vm_area_struct *vma, return 0; if (prot & PROT_EXEC) { - ipe_build_eval_ctx(&ctx, vma->vm_file, IPE_OP_EXEC); + ipe_build_eval_ctx(&ctx, vma->vm_file, IPE_OP_EXEC, IPE_HOOK_MPROTECT); return ipe_evaluate_event(&ctx); } @@ -135,7 +135,7 @@ int ipe_kernel_read_file(struct file *file, enum kernel_read_file_id id, WARN(1, "no rule setup for kernel_read_file enum %d", id); } - ipe_build_eval_ctx(&ctx, file, op); + ipe_build_eval_ctx(&ctx, file, op, IPE_HOOK_KERNEL_READ); return ipe_evaluate_event(&ctx); } @@ -180,7 +180,7 @@ int ipe_kernel_load_data(enum kernel_load_data_id id, bool contents) WARN(1, "no rule setup for kernel_load_data enum %d", id); } - ipe_build_eval_ctx(&ctx, NULL, op); + ipe_build_eval_ctx(&ctx, NULL, op, IPE_HOOK_KERNEL_LOAD); return ipe_evaluate_event(&ctx); } diff --git a/security/ipe/hooks.h b/security/ipe/hooks.h index 4de5fabebd54..f4f0b544ddcc 100644 --- a/security/ipe/hooks.h +++ b/security/ipe/hooks.h @@ -9,6 +9,17 @@ #include <linux/binfmts.h> #include <linux/security.h> +enum ipe_hook_type { + IPE_HOOK_BPRM_CHECK = 0, + IPE_HOOK_MMAP, + IPE_HOOK_MPROTECT, + IPE_HOOK_KERNEL_READ, + IPE_HOOK_KERNEL_LOAD, + __IPE_HOOK_MAX +}; + +#define IPE_HOOK_INVALID __IPE_HOOK_MAX + int ipe_bprm_check_security(struct linux_binprm *bprm); int ipe_mmap_file(struct file *f, unsigned long reqprot, unsigned long prot, diff --git a/security/ipe/policy.c b/security/ipe/policy.c index be9808b27e49..d8e7db857a2e 100644 --- a/security/ipe/policy.c +++ b/security/ipe/policy.c @@ -11,6 +11,7 @@ #include "fs.h" #include "policy.h" #include "policy_parser.h" +#include "audit.h" /* lock for synchronizing writers across ipe policy */ DEFINE_MUTEX(ipe_policy_lock); @@ -112,6 +113,7 @@ int ipe_update_policy(struct inode *root, const char *text, size_t textlen, root->i_private = new; swap(new->policyfs, old->policyfs); + ipe_audit_policy_load(new); mutex_lock(&ipe_policy_lock); ap = rcu_dereference_protected(ipe_active_policy, @@ -119,6 +121,7 @@ int ipe_update_policy(struct inode *root, const char *text, size_t textlen, if (old == ap) { rcu_assign_pointer(ipe_active_policy, new); mutex_unlock(&ipe_policy_lock); + ipe_audit_policy_activation(old, new); } else { mutex_unlock(&ipe_policy_lock); } @@ -217,6 +220,7 @@ int ipe_set_active_pol(const struct ipe_policy *p) } rcu_assign_pointer(ipe_active_policy, p); + ipe_audit_policy_activation(ap, p); mutex_unlock(&ipe_policy_lock); return 0; |