@@ -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
@@ -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
@@ -13,3 +13,4 @@ obj-$(CONFIG_SECURITY_IPE) += \
policy.o \
policy_fs.o \
policy_parser.o \
+ audit.o \
new file mode 100644
@@ -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);
+}
new file mode 100644
@@ -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 */
@@ -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");
@@ -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 */
@@ -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;
}
@@ -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);
}
@@ -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,
@@ -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;