| // 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" |
| #include "digest.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", |
| "dmverity_roothash=", |
| "dmverity_signature=FALSE", |
| "dmverity_signature=TRUE", |
| "fsverity_digest=", |
| "fsverity_signature=FALSE", |
| "fsverity_signature=TRUE", |
| }; |
| |
| /** |
| * audit_dmv_roothash() - audit the roothash of a dmverity_roothash property. |
| * @ab: Supplies a pointer to the audit_buffer to append to. |
| * @rh: Supplies a pointer to the digest structure. |
| */ |
| static void audit_dmv_roothash(struct audit_buffer *ab, const void *rh) |
| { |
| audit_log_format(ab, "%s", audit_prop_names[IPE_PROP_DMV_ROOTHASH]); |
| ipe_digest_audit(ab, rh); |
| } |
| |
| /** |
| * audit_fsv_digest() - audit the digest of a fsverity_digest property. |
| * @ab: Supplies a pointer to the audit_buffer to append to. |
| * @d: Supplies a pointer to the digest structure. |
| */ |
| static void audit_fsv_digest(struct audit_buffer *ab, const void *d) |
| { |
| audit_log_format(ab, "%s", audit_prop_names[IPE_PROP_FSV_DIGEST]); |
| ipe_digest_audit(ab, d); |
| } |
| |
| /** |
| * 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) { |
| switch (ptr->type) { |
| case IPE_PROP_DMV_ROOTHASH: |
| audit_dmv_roothash(ab, ptr->value); |
| break; |
| case IPE_PROP_FSV_DIGEST: |
| audit_fsv_digest(ab, ptr->value); |
| break; |
| default: |
| audit_log_format(ab, "%s", audit_prop_names[ptr->type]); |
| break; |
| } |
| |
| audit_log_format(ab, " "); |
| } |
| |
| 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 enforcing=%d pid=%d comm=", |
| op, audit_hook_names[ctx->hook], READ_ONCE(enforce), |
| 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); |
| } |
| |
| /** |
| * ipe_audit_enforce() - Audit a change in IPE's enforcement state. |
| * @new_enforce: The new value enforce to be set. |
| * @old_enforce: The old value currently in enforce. |
| */ |
| void ipe_audit_enforce(bool new_enforce, bool old_enforce) |
| { |
| struct audit_buffer *ab; |
| |
| ab = audit_log_start(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS); |
| if (!ab) |
| return; |
| |
| audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS, |
| "enforcing=%d old_enforcing=%d auid=%u ses=%u" |
| " enabled=1 old-enabled=1 lsm=ipe res=1", |
| new_enforce, old_enforce, |
| from_kuid(&init_user_ns, audit_get_loginuid(current)), |
| audit_get_sessionid(current)); |
| |
| audit_log_end(ab); |
| } |