diff options
Diffstat (limited to 'security/ipe')
-rw-r--r-- | security/ipe/.gitignore | 2 | ||||
-rw-r--r-- | security/ipe/Kconfig | 97 | ||||
-rw-r--r-- | security/ipe/Makefile | 31 | ||||
-rw-r--r-- | security/ipe/audit.c | 292 | ||||
-rw-r--r-- | security/ipe/audit.h | 19 | ||||
-rw-r--r-- | security/ipe/digest.c | 118 | ||||
-rw-r--r-- | security/ipe/digest.h | 26 | ||||
-rw-r--r-- | security/ipe/eval.c | 393 | ||||
-rw-r--r-- | security/ipe/eval.h | 70 | ||||
-rw-r--r-- | security/ipe/fs.c | 247 | ||||
-rw-r--r-- | security/ipe/fs.h | 16 | ||||
-rw-r--r-- | security/ipe/hooks.c | 314 | ||||
-rw-r--r-- | security/ipe/hooks.h | 52 | ||||
-rw-r--r-- | security/ipe/ipe.c | 98 | ||||
-rw-r--r-- | security/ipe/ipe.h | 26 | ||||
-rw-r--r-- | security/ipe/policy.c | 227 | ||||
-rw-r--r-- | security/ipe/policy.h | 98 | ||||
-rw-r--r-- | security/ipe/policy_fs.c | 472 | ||||
-rw-r--r-- | security/ipe/policy_parser.c | 559 | ||||
-rw-r--r-- | security/ipe/policy_parser.h | 11 | ||||
-rw-r--r-- | security/ipe/policy_tests.c | 296 |
21 files changed, 3464 insertions, 0 deletions
diff --git a/security/ipe/.gitignore b/security/ipe/.gitignore new file mode 100644 index 000000000000..6e9939be1cb7 --- /dev/null +++ b/security/ipe/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +boot_policy.c diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig new file mode 100644 index 000000000000..3ab582606ed2 --- /dev/null +++ b/security/ipe/Kconfig @@ -0,0 +1,97 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Integrity Policy Enforcement (IPE) configuration +# + +menuconfig SECURITY_IPE + bool "Integrity Policy Enforcement (IPE)" + depends on SECURITY && SECURITYFS && AUDIT && AUDITSYSCALL + select PKCS7_MESSAGE_PARSER + select SYSTEM_DATA_VERIFICATION + select IPE_PROP_DM_VERITY if DM_VERITY + select IPE_PROP_DM_VERITY_SIGNATURE if DM_VERITY && DM_VERITY_VERIFY_ROOTHASH_SIG + select IPE_PROP_FS_VERITY if FS_VERITY + select IPE_PROP_FS_VERITY_BUILTIN_SIG if FS_VERITY && FS_VERITY_BUILTIN_SIGNATURES + help + This option enables the Integrity Policy Enforcement LSM + allowing users to define a policy to enforce a trust-based access + control. A key feature of IPE is a customizable policy to allow + admins to reconfigure trust requirements on the fly. + + If unsure, answer N. + +if SECURITY_IPE +config IPE_BOOT_POLICY + string "Integrity policy to apply on system startup" + help + This option specifies a filepath to an IPE policy that is compiled + into the kernel. This policy will be enforced until a policy update + is deployed via the $securityfs/ipe/policies/$policy_name/active + interface. + + If unsure, leave blank. + +menu "IPE Trust Providers" + +config IPE_PROP_DM_VERITY + bool "Enable support for dm-verity based on root hash" + depends on DM_VERITY + help + This option enables the 'dmverity_roothash' property within IPE + policies. The property evaluates to TRUE when a file from a dm-verity + volume is evaluated, and the volume's root hash matches the value + supplied in the policy. + +config IPE_PROP_DM_VERITY_SIGNATURE + bool "Enable support for dm-verity based on root hash signature" + depends on DM_VERITY && DM_VERITY_VERIFY_ROOTHASH_SIG + help + This option enables the 'dmverity_signature' property within IPE + policies. The property evaluates to TRUE when a file from a dm-verity + volume, which has been mounted with a valid signed root hash, + is evaluated. + + If unsure, answer Y. + +config IPE_PROP_FS_VERITY + bool "Enable support for fs-verity based on file digest" + depends on FS_VERITY + help + This option enables the 'fsverity_digest' property within IPE + policies. The property evaluates to TRUE when a file is fsverity + enabled and its digest matches the supplied digest value in the + policy. + + if unsure, answer Y. + +config IPE_PROP_FS_VERITY_BUILTIN_SIG + bool "Enable support for fs-verity based on builtin signature" + depends on FS_VERITY && FS_VERITY_BUILTIN_SIGNATURES + help + This option enables the 'fsverity_signature' property within IPE + policies. The property evaluates to TRUE when a file is fsverity + enabled and it has a valid builtin signature whose signing cert + is in the .fs-verity keyring. + + if unsure, answer Y. + +endmenu + +config SECURITY_IPE_KUNIT_TEST + bool "Build KUnit tests for IPE" if !KUNIT_ALL_TESTS + depends on KUNIT=y + default KUNIT_ALL_TESTS + help + This builds the IPE KUnit tests. + + KUnit tests run during boot and output the results to the debug log + in TAP format (https://testanything.org/). Only useful for kernel devs + running KUnit test harness and are not for inclusion into a + production build. + + For more information on KUnit and unit tests in general please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + +endif diff --git a/security/ipe/Makefile b/security/ipe/Makefile new file mode 100644 index 000000000000..2ffabfa63fe9 --- /dev/null +++ b/security/ipe/Makefile @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. +# +# Makefile for building the IPE module as part of the kernel tree. +# + +quiet_cmd_polgen = IPE_POL $(2) + cmd_polgen = scripts/ipe/polgen/polgen security/ipe/boot_policy.c $(2) + +targets += boot_policy.c + +$(obj)/boot_policy.c: scripts/ipe/polgen/polgen $(CONFIG_IPE_BOOT_POLICY) FORCE + $(call if_changed,polgen,$(CONFIG_IPE_BOOT_POLICY)) + +obj-$(CONFIG_SECURITY_IPE) += \ + boot_policy.o \ + digest.o \ + eval.o \ + hooks.o \ + fs.o \ + ipe.o \ + policy.o \ + policy_fs.o \ + policy_parser.o \ + audit.o \ + +clean-files := boot_policy.c \ + +obj-$(CONFIG_SECURITY_IPE_KUNIT_TEST) += \ + policy_tests.o \ diff --git a/security/ipe/audit.c b/security/ipe/audit.c new file mode 100644 index 000000000000..f05f0caa4850 --- /dev/null +++ b/security/ipe/audit.c @@ -0,0 +1,292 @@ +// 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); +} diff --git a/security/ipe/audit.h b/security/ipe/audit.h new file mode 100644 index 000000000000..ed2620846a79 --- /dev/null +++ b/security/ipe/audit.h @@ -0,0 +1,19 @@ +/* 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); +void ipe_audit_enforce(bool new_enforce, bool old_enforce); + +#endif /* _IPE_AUDIT_H */ diff --git a/security/ipe/digest.c b/security/ipe/digest.c new file mode 100644 index 000000000000..493716370570 --- /dev/null +++ b/security/ipe/digest.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#include "digest.h" + +/** + * ipe_digest_parse() - parse a digest in IPE's policy. + * @valstr: Supplies the string parsed from the policy. + * + * Digests in IPE are defined in a standard way: + * <alg_name>:<hex> + * + * Use this function to create a property to parse the digest + * consistently. The parsed digest will be saved in @value in IPE's + * policy. + * + * Return: The parsed digest_info structure on success. If an error occurs, + * the function will return the error value (via ERR_PTR). + */ +struct digest_info *ipe_digest_parse(const char *valstr) +{ + struct digest_info *info = NULL; + char *sep, *raw_digest; + size_t raw_digest_len; + u8 *digest = NULL; + char *alg = NULL; + int rc = 0; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return ERR_PTR(-ENOMEM); + + sep = strchr(valstr, ':'); + if (!sep) { + rc = -EBADMSG; + goto err; + } + + alg = kstrndup(valstr, sep - valstr, GFP_KERNEL); + if (!alg) { + rc = -ENOMEM; + goto err; + } + + raw_digest = sep + 1; + raw_digest_len = strlen(raw_digest); + + info->digest_len = (raw_digest_len + 1) / 2; + digest = kzalloc(info->digest_len, GFP_KERNEL); + if (!digest) { + rc = -ENOMEM; + goto err; + } + + rc = hex2bin(digest, raw_digest, info->digest_len); + if (rc < 0) { + rc = -EINVAL; + goto err; + } + + info->alg = alg; + info->digest = digest; + return info; + +err: + kfree(alg); + kfree(digest); + kfree(info); + return ERR_PTR(rc); +} + +/** + * ipe_digest_eval() - evaluate an IPE digest against another digest. + * @expected: Supplies the policy-provided digest value. + * @digest: Supplies the digest to compare against the policy digest value. + * + * Return: + * * %true - digests match + * * %false - digests do not match + */ +bool ipe_digest_eval(const struct digest_info *expected, + const struct digest_info *digest) +{ + return (expected->digest_len == digest->digest_len) && + (!strcmp(expected->alg, digest->alg)) && + (!memcmp(expected->digest, digest->digest, expected->digest_len)); +} + +/** + * ipe_digest_free() - free an IPE digest. + * @info: Supplies a pointer the policy-provided digest to free. + */ +void ipe_digest_free(struct digest_info *info) +{ + if (IS_ERR_OR_NULL(info)) + return; + + kfree(info->alg); + kfree(info->digest); + kfree(info); +} + +/** + * ipe_digest_audit() - audit a digest that was sourced from IPE's policy. + * @ab: Supplies the audit_buffer to append the formatted result. + * @info: Supplies a pointer to source the audit record from. + * + * Digests in IPE are audited in this format: + * <alg_name>:<hex> + */ +void ipe_digest_audit(struct audit_buffer *ab, const struct digest_info *info) +{ + audit_log_untrustedstring(ab, info->alg); + audit_log_format(ab, ":"); + audit_log_n_hex(ab, info->digest, info->digest_len); +} diff --git a/security/ipe/digest.h b/security/ipe/digest.h new file mode 100644 index 000000000000..52c9b3844a38 --- /dev/null +++ b/security/ipe/digest.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#ifndef _IPE_DIGEST_H +#define _IPE_DIGEST_H + +#include <linux/types.h> +#include <linux/audit.h> + +#include "policy.h" + +struct digest_info { + const char *alg; + const u8 *digest; + size_t digest_len; +}; + +struct digest_info *ipe_digest_parse(const char *valstr); +void ipe_digest_free(struct digest_info *digest_info); +void ipe_digest_audit(struct audit_buffer *ab, const struct digest_info *val); +bool ipe_digest_eval(const struct digest_info *expected, + const struct digest_info *digest); + +#endif /* _IPE_DIGEST_H */ diff --git a/security/ipe/eval.c b/security/ipe/eval.c new file mode 100644 index 000000000000..21439c5be336 --- /dev/null +++ b/security/ipe/eval.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#include <linux/fs.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/file.h> +#include <linux/sched.h> +#include <linux/rcupdate.h> +#include <linux/moduleparam.h> +#include <linux/fsverity.h> + +#include "ipe.h" +#include "eval.h" +#include "policy.h" +#include "audit.h" +#include "digest.h" + +struct ipe_policy __rcu *ipe_active_policy; +bool success_audit; +bool enforce = true; +#define INO_BLOCK_DEV(ino) ((ino)->i_sb->s_bdev) + +#define FILE_SUPERBLOCK(f) ((f)->f_path.mnt->mnt_sb) + +/** + * build_ipe_sb_ctx() - Build initramfs field of an ipe evaluation context. + * @ctx: Supplies a pointer to the context to be populated. + * @file: Supplies the file struct of the file triggered IPE event. + */ +static void build_ipe_sb_ctx(struct ipe_eval_ctx *ctx, const struct file *const file) +{ + ctx->initramfs = ipe_sb(FILE_SUPERBLOCK(file))->initramfs; +} + +#ifdef CONFIG_IPE_PROP_DM_VERITY +/** + * build_ipe_bdev_ctx() - Build ipe_bdev field of an evaluation context. + * @ctx: Supplies a pointer to the context to be populated. + * @ino: Supplies the inode struct of the file triggered IPE event. + */ +static void build_ipe_bdev_ctx(struct ipe_eval_ctx *ctx, const struct inode *const ino) +{ + if (INO_BLOCK_DEV(ino)) + ctx->ipe_bdev = ipe_bdev(INO_BLOCK_DEV(ino)); +} +#else +static void build_ipe_bdev_ctx(struct ipe_eval_ctx *ctx, const struct inode *const ino) +{ +} +#endif /* CONFIG_IPE_PROP_DM_VERITY */ + +#ifdef CONFIG_IPE_PROP_FS_VERITY +#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG +static void build_ipe_inode_blob_ctx(struct ipe_eval_ctx *ctx, + const struct inode *const ino) +{ + ctx->ipe_inode = ipe_inode(ctx->ino); +} +#else +static inline void build_ipe_inode_blob_ctx(struct ipe_eval_ctx *ctx, + const struct inode *const ino) +{ +} +#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ + +/** + * build_ipe_inode_ctx() - Build inode fields of an evaluation context. + * @ctx: Supplies a pointer to the context to be populated. + * @ino: Supplies the inode struct of the file triggered IPE event. + */ +static void build_ipe_inode_ctx(struct ipe_eval_ctx *ctx, const struct inode *const ino) +{ + ctx->ino = ino; + build_ipe_inode_blob_ctx(ctx, ino); +} +#else +static void build_ipe_inode_ctx(struct ipe_eval_ctx *ctx, const struct inode *const ino) +{ +} +#endif /* CONFIG_IPE_PROP_FS_VERITY */ + +/** + * ipe_build_eval_ctx() - Build an ipe evaluation context. + * @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_hook_type hook) +{ + struct inode *ino; + + ctx->file = file; + ctx->op = op; + ctx->hook = hook; + + if (file) { + build_ipe_sb_ctx(ctx, file); + ino = d_real_inode(file->f_path.dentry); + build_ipe_bdev_ctx(ctx, ino); + build_ipe_inode_ctx(ctx, ino); + } +} + +/** + * evaluate_boot_verified() - Evaluate @ctx for the boot verified property. + * @ctx: Supplies a pointer to the context being evaluated. + * + * Return: + * * %true - The current @ctx match the @p + * * %false - The current @ctx doesn't match the @p + */ +static bool evaluate_boot_verified(const struct ipe_eval_ctx *const ctx) +{ + return ctx->initramfs; +} + +#ifdef CONFIG_IPE_PROP_DM_VERITY +/** + * evaluate_dmv_roothash() - Evaluate @ctx against a dmv roothash property. + * @ctx: Supplies a pointer to the context being evaluated. + * @p: Supplies a pointer to the property being evaluated. + * + * Return: + * * %true - The current @ctx match the @p + * * %false - The current @ctx doesn't match the @p + */ +static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx, + struct ipe_prop *p) +{ + return !!ctx->ipe_bdev && + !!ctx->ipe_bdev->root_hash && + ipe_digest_eval(p->value, + ctx->ipe_bdev->root_hash); +} +#else +static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx, + struct ipe_prop *p) +{ + return false; +} +#endif /* CONFIG_IPE_PROP_DM_VERITY */ + +#ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE +/** + * evaluate_dmv_sig_false() - Evaluate @ctx against a dmv sig false property. + * @ctx: Supplies a pointer to the context being evaluated. + * + * Return: + * * %true - The current @ctx match the property + * * %false - The current @ctx doesn't match the property + */ +static bool evaluate_dmv_sig_false(const struct ipe_eval_ctx *const ctx) +{ + return !ctx->ipe_bdev || (!ctx->ipe_bdev->dm_verity_signed); +} + +/** + * evaluate_dmv_sig_true() - Evaluate @ctx against a dmv sig true property. + * @ctx: Supplies a pointer to the context being evaluated. + * + * Return: + * * %true - The current @ctx match the property + * * %false - The current @ctx doesn't match the property + */ +static bool evaluate_dmv_sig_true(const struct ipe_eval_ctx *const ctx) +{ + return !evaluate_dmv_sig_false(ctx); +} +#else +static bool evaluate_dmv_sig_false(const struct ipe_eval_ctx *const ctx) +{ + return false; +} + +static bool evaluate_dmv_sig_true(const struct ipe_eval_ctx *const ctx) +{ + return false; +} +#endif /* CONFIG_IPE_PROP_DM_VERITY_SIGNATURE */ + +#ifdef CONFIG_IPE_PROP_FS_VERITY +/** + * evaluate_fsv_digest() - Evaluate @ctx against a fsv digest property. + * @ctx: Supplies a pointer to the context being evaluated. + * @p: Supplies a pointer to the property being evaluated. + * + * Return: + * * %true - The current @ctx match the @p + * * %false - The current @ctx doesn't match the @p + */ +static bool evaluate_fsv_digest(const struct ipe_eval_ctx *const ctx, + struct ipe_prop *p) +{ + enum hash_algo alg; + u8 digest[FS_VERITY_MAX_DIGEST_SIZE]; + struct digest_info info; + + if (!ctx->ino) + return false; + if (!fsverity_get_digest((struct inode *)ctx->ino, + digest, + NULL, + &alg)) + return false; + + info.alg = hash_algo_name[alg]; + info.digest = digest; + info.digest_len = hash_digest_size[alg]; + + return ipe_digest_eval(p->value, &info); +} +#else +static bool evaluate_fsv_digest(const struct ipe_eval_ctx *const ctx, + struct ipe_prop *p) +{ + return false; +} +#endif /* CONFIG_IPE_PROP_FS_VERITY */ + +#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG +/** + * evaluate_fsv_sig_false() - Evaluate @ctx against a fsv sig false property. + * @ctx: Supplies a pointer to the context being evaluated. + * + * Return: + * * %true - The current @ctx match the property + * * %false - The current @ctx doesn't match the property + */ +static bool evaluate_fsv_sig_false(const struct ipe_eval_ctx *const ctx) +{ + return !ctx->ino || + !IS_VERITY(ctx->ino) || + !ctx->ipe_inode || + !ctx->ipe_inode->fs_verity_signed; +} + +/** + * evaluate_fsv_sig_true() - Evaluate @ctx against a fsv sig true property. + * @ctx: Supplies a pointer to the context being evaluated. + * + * Return: + * * %true - The current @ctx match the property + * * %false - The current @ctx doesn't match the property + */ +static bool evaluate_fsv_sig_true(const struct ipe_eval_ctx *const ctx) +{ + return !evaluate_fsv_sig_false(ctx); +} +#else +static bool evaluate_fsv_sig_false(const struct ipe_eval_ctx *const ctx) +{ + return false; +} + +static bool evaluate_fsv_sig_true(const struct ipe_eval_ctx *const ctx) +{ + return false; +} +#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ + +/** + * evaluate_property() - Analyze @ctx against a rule property. + * @ctx: Supplies a pointer to the context to be evaluated. + * @p: Supplies a pointer to the property to be evaluated. + * + * This function Determines whether the specified @ctx + * matches the conditions defined by a rule property @p. + * + * Return: + * * %true - The current @ctx match the @p + * * %false - The current @ctx doesn't match the @p + */ +static bool evaluate_property(const struct ipe_eval_ctx *const ctx, + struct ipe_prop *p) +{ + switch (p->type) { + case IPE_PROP_BOOT_VERIFIED_FALSE: + return !evaluate_boot_verified(ctx); + case IPE_PROP_BOOT_VERIFIED_TRUE: + return evaluate_boot_verified(ctx); + case IPE_PROP_DMV_ROOTHASH: + return evaluate_dmv_roothash(ctx, p); + case IPE_PROP_DMV_SIG_FALSE: + return evaluate_dmv_sig_false(ctx); + case IPE_PROP_DMV_SIG_TRUE: + return evaluate_dmv_sig_true(ctx); + case IPE_PROP_FSV_DIGEST: + return evaluate_fsv_digest(ctx, p); + case IPE_PROP_FSV_SIG_FALSE: + return evaluate_fsv_sig_false(ctx); + case IPE_PROP_FSV_SIG_TRUE: + return evaluate_fsv_sig_true(ctx); + default: + return false; + } +} + +/** + * ipe_evaluate_event() - Analyze @ctx against the current active policy. + * @ctx: Supplies a pointer to the context to be evaluated. + * + * This is the loop where all policy evaluations happen against the IPE policy. + * + * Return: + * * %0 - Success + * * %-EACCES - @ctx did not pass evaluation + */ +int ipe_evaluate_event(const struct ipe_eval_ctx *const ctx) +{ + const struct ipe_op_table *rules = NULL; + const struct ipe_rule *rule = NULL; + struct ipe_policy *pol = NULL; + struct ipe_prop *prop = NULL; + enum ipe_action_type action; + enum ipe_match match_type; + bool match = false; + int rc = 0; + + rcu_read_lock(); + + pol = rcu_dereference(ipe_active_policy); + if (!pol) { + rcu_read_unlock(); + return 0; + } + + if (ctx->op == IPE_OP_INVALID) { + if (pol->parsed->global_default_action == IPE_ACTION_INVALID) { + WARN(1, "no default rule set for unknown op, ALLOW it"); + action = IPE_ACTION_ALLOW; + } else { + action = pol->parsed->global_default_action; + } + match_type = IPE_MATCH_GLOBAL; + goto eval; + } + + rules = &pol->parsed->rules[ctx->op]; + + list_for_each_entry(rule, &rules->rules, next) { + match = true; + + list_for_each_entry(prop, &rule->props, next) { + match = evaluate_property(ctx, prop); + if (!match) + break; + } + + if (match) + break; + } + + if (match) { + action = rule->action; + match_type = IPE_MATCH_RULE; + } else if (rules->default_action != IPE_ACTION_INVALID) { + action = rules->default_action; + 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) + rc = -EACCES; + + if (!READ_ONCE(enforce)) + rc = 0; + + return rc; +} + +/* 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"); +module_param(enforce, bool, 0400); +MODULE_PARM_DESC(enforce, "Start IPE in enforce or permissive mode"); diff --git a/security/ipe/eval.h b/security/ipe/eval.h new file mode 100644 index 000000000000..fef65a36468c --- /dev/null +++ b/security/ipe/eval.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#ifndef _IPE_EVAL_H +#define _IPE_EVAL_H + +#include <linux/file.h> +#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; +extern bool enforce; + +struct ipe_superblock { + bool initramfs; +}; + +#ifdef CONFIG_IPE_PROP_DM_VERITY +struct ipe_bdev { +#ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE + bool dm_verity_signed; +#endif /* CONFIG_IPE_PROP_DM_VERITY_SIGNATURE */ + struct digest_info *root_hash; +}; +#endif /* CONFIG_IPE_PROP_DM_VERITY */ + +#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG +struct ipe_inode { + bool fs_verity_signed; +}; +#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ + +struct ipe_eval_ctx { + enum ipe_op_type op; + enum ipe_hook_type hook; + + const struct file *file; + bool initramfs; +#ifdef CONFIG_IPE_PROP_DM_VERITY + const struct ipe_bdev *ipe_bdev; +#endif /* CONFIG_IPE_PROP_DM_VERITY */ +#ifdef CONFIG_IPE_PROP_FS_VERITY + const struct inode *ino; +#endif /* CONFIG_IPE_PROP_FS_VERITY */ +#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG + const struct ipe_inode *ipe_inode; +#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ +}; + +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_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 new file mode 100644 index 000000000000..5b6d19fb844a --- /dev/null +++ b/security/ipe/fs.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#include <linux/dcache.h> +#include <linux/security.h> + +#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; +static struct dentry *enforce_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); +} + +/** + * setenforce() - Write handler for the securityfs node, "ipe/enforce" + * @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 setenforce(struct file *f, const char __user *data, + size_t len, loff_t *offset) +{ + int rc = 0; + bool new_value, old_value; + + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) + return -EPERM; + + old_value = READ_ONCE(enforce); + rc = kstrtobool_from_user(data, len, &new_value); + if (rc) + return rc; + + if (new_value != old_value) { + ipe_audit_enforce(new_value, old_value); + WRITE_ONCE(enforce, new_value); + } + + return len; +} + +/** + * getenforce() - Read handler for the securityfs node, "ipe/enforce" + * @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 getenforce(struct file *f, char __user *data, + size_t len, loff_t *offset) +{ + const char *result; + + result = ((READ_ONCE(enforce)) ? "1" : "0"); + + return simple_read_from_buffer(data, len, offset, result, 1); +} + +/** + * new_policy() - Write handler for the securityfs node, "ipe/new_policy". + * @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 + * * %-ENOMEM - Out of memory (OOM) + * * %-EBADMSG - Policy is invalid + * * %-ERANGE - Policy version number overflow + * * %-EINVAL - Policy version parsing error + * * %-EEXIST - Same name policy already deployed + */ +static ssize_t new_policy(struct file *f, const char __user *data, + size_t len, loff_t *offset) +{ + struct ipe_policy *p = NULL; + char *copy = NULL; + int rc = 0; + + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) + return -EPERM; + + copy = memdup_user_nul(data, len); + if (IS_ERR(copy)) + return PTR_ERR(copy); + + p = ipe_new_policy(NULL, 0, copy, len); + if (IS_ERR(p)) { + rc = PTR_ERR(p); + goto out; + } + + rc = ipe_new_policyfs_node(p); + if (rc) + goto out; + + ipe_audit_policy_load(p); + +out: + if (rc < 0) + ipe_free_policy(p); + kfree(copy); + return (rc < 0) ? rc : len; +} + +static const struct file_operations np_fops = { + .write = new_policy, +}; + +static const struct file_operations audit_fops = { + .write = setaudit, + .read = getaudit, +}; + +static const struct file_operations enforce_fops = { + .write = setenforce, + .read = getenforce, +}; + +/** + * ipe_init_securityfs() - Initialize IPE's securityfs tree at fsinit. + * + * Return: %0 on success. If an error occurs, the function will return + * the -errno. + */ +static int __init ipe_init_securityfs(void) +{ + int rc = 0; + struct ipe_policy *ap; + + if (!ipe_enabled) + return -EOPNOTSUPP; + + root = securityfs_create_dir("ipe", NULL); + if (IS_ERR(root)) { + rc = PTR_ERR(root); + 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; + } + + enforce_node = securityfs_create_file("enforce", 0600, root, NULL, + &enforce_fops); + if (IS_ERR(enforce_node)) { + rc = PTR_ERR(enforce_node); + goto err; + } + + policy_root = securityfs_create_dir("policies", root); + if (IS_ERR(policy_root)) { + rc = PTR_ERR(policy_root); + goto err; + } + + ap = rcu_access_pointer(ipe_active_policy); + if (ap) { + rc = ipe_new_policyfs_node(ap); + if (rc) + goto err; + } + + np = securityfs_create_file("new_policy", 0200, root, NULL, &np_fops); + if (IS_ERR(np)) { + rc = PTR_ERR(np); + goto err; + } + + return 0; +err: + securityfs_remove(np); + securityfs_remove(policy_root); + securityfs_remove(enforce_node); + securityfs_remove(audit_node); + securityfs_remove(root); + return rc; +} + +fs_initcall(ipe_init_securityfs); diff --git a/security/ipe/fs.h b/security/ipe/fs.h new file mode 100644 index 000000000000..0141ae8e86ec --- /dev/null +++ b/security/ipe/fs.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#ifndef _IPE_FS_H +#define _IPE_FS_H + +#include "policy.h" + +extern struct dentry *policy_root __ro_after_init; + +int ipe_new_policyfs_node(struct ipe_policy *p); +void ipe_del_policyfs_node(struct ipe_policy *p); + +#endif /* _IPE_FS_H */ diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c new file mode 100644 index 000000000000..d0323b81cd8f --- /dev/null +++ b/security/ipe/hooks.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#include <linux/fs.h> +#include <linux/fs_struct.h> +#include <linux/types.h> +#include <linux/binfmts.h> +#include <linux/mman.h> +#include <linux/blk_types.h> + +#include "ipe.h" +#include "hooks.h" +#include "eval.h" +#include "digest.h" + +/** + * ipe_bprm_check_security() - ipe security hook function for bprm check. + * @bprm: Supplies a pointer to a linux_binprm structure to source the file + * being evaluated. + * + * This LSM hook is called when a binary is loaded through the exec + * family of system calls. + * + * Return: + * * %0 - Success + * * %-EACCES - Did not pass IPE policy + */ +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_HOOK_BPRM_CHECK); + return ipe_evaluate_event(&ctx); +} + +/** + * ipe_mmap_file() - ipe security hook function for mmap check. + * @f: File being mmap'd. Can be NULL in the case of anonymous memory. + * @reqprot: The requested protection on the mmap, passed from usermode. + * @prot: The effective protection on the mmap, resolved from reqprot and + * system configuration. + * @flags: Unused. + * + * This hook is called when a file is loaded through the mmap + * family of system calls. + * + * Return: + * * %0 - Success + * * %-EACCES - Did not pass IPE policy + */ +int ipe_mmap_file(struct file *f, unsigned long reqprot __always_unused, + unsigned long prot, unsigned long flags) +{ + struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT; + + if (prot & PROT_EXEC) { + ipe_build_eval_ctx(&ctx, f, IPE_OP_EXEC, IPE_HOOK_MMAP); + return ipe_evaluate_event(&ctx); + } + + return 0; +} + +/** + * ipe_file_mprotect() - ipe security hook function for mprotect check. + * @vma: Existing virtual memory area created by mmap or similar. + * @reqprot: The requested protection on the mmap, passed from usermode. + * @prot: The effective protection on the mmap, resolved from reqprot and + * system configuration. + * + * This LSM hook is called when a mmap'd region of memory is changing + * its protections via mprotect. + * + * Return: + * * %0 - Success + * * %-EACCES - Did not pass IPE policy + */ +int ipe_file_mprotect(struct vm_area_struct *vma, + unsigned long reqprot __always_unused, + unsigned long prot) +{ + struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT; + + /* Already Executable */ + if (vma->vm_flags & VM_EXEC) + return 0; + + if (prot & PROT_EXEC) { + ipe_build_eval_ctx(&ctx, vma->vm_file, IPE_OP_EXEC, IPE_HOOK_MPROTECT); + return ipe_evaluate_event(&ctx); + } + + return 0; +} + +/** + * ipe_kernel_read_file() - ipe security hook function for kernel read. + * @file: Supplies a pointer to the file structure being read in from disk. + * @id: Supplies the enumeration identifying the purpose of the read. + * @contents: Unused. + * + * This LSM hook is called when a file is read from disk in the kernel. + * + * Return: + * * %0 - Success + * * %-EACCES - Did not pass IPE policy + */ +int ipe_kernel_read_file(struct file *file, enum kernel_read_file_id id, + bool contents) +{ + struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT; + enum ipe_op_type op; + + switch (id) { + case READING_FIRMWARE: + op = IPE_OP_FIRMWARE; + break; + case READING_MODULE: + op = IPE_OP_KERNEL_MODULE; + break; + case READING_KEXEC_INITRAMFS: + op = IPE_OP_KEXEC_INITRAMFS; + break; + case READING_KEXEC_IMAGE: + op = IPE_OP_KEXEC_IMAGE; + break; + case READING_POLICY: + op = IPE_OP_POLICY; + break; + case READING_X509_CERTIFICATE: + op = IPE_OP_X509; + break; + default: + op = IPE_OP_INVALID; + WARN(1, "no rule setup for kernel_read_file enum %d", id); + } + + ipe_build_eval_ctx(&ctx, file, op, IPE_HOOK_KERNEL_READ); + return ipe_evaluate_event(&ctx); +} + +/** + * ipe_kernel_load_data() - ipe security hook function for kernel load data. + * @id: Supplies the enumeration identifying the purpose of the load. + * @contents: Unused. + * + * This LSM hook is called when a data buffer provided by userspace is loading + * into the kernel. + * + * Return: + * * %0 - Success + * * %-EACCES - Did not pass IPE policy + */ +int ipe_kernel_load_data(enum kernel_load_data_id id, bool contents) +{ + struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT; + enum ipe_op_type op; + + switch (id) { + case LOADING_FIRMWARE: + op = IPE_OP_FIRMWARE; + break; + case LOADING_MODULE: + op = IPE_OP_KERNEL_MODULE; + break; + case LOADING_KEXEC_INITRAMFS: + op = IPE_OP_KEXEC_INITRAMFS; + break; + case LOADING_KEXEC_IMAGE: + op = IPE_OP_KEXEC_IMAGE; + break; + case LOADING_POLICY: + op = IPE_OP_POLICY; + break; + case LOADING_X509_CERTIFICATE: + op = IPE_OP_X509; + break; + default: + op = IPE_OP_INVALID; + WARN(1, "no rule setup for kernel_load_data enum %d", id); + } + + ipe_build_eval_ctx(&ctx, NULL, op, IPE_HOOK_KERNEL_LOAD); + return ipe_evaluate_event(&ctx); +} + +/** + * ipe_unpack_initramfs() - Mark the current rootfs as initramfs. + */ +void ipe_unpack_initramfs(void) +{ + ipe_sb(current->fs->root.mnt->mnt_sb)->initramfs = true; +} + +#ifdef CONFIG_IPE_PROP_DM_VERITY +/** + * ipe_bdev_free_security() - Free IPE's LSM blob of block_devices. + * @bdev: Supplies a pointer to a block_device that contains the structure + * to free. + */ +void ipe_bdev_free_security(struct block_device *bdev) +{ + struct ipe_bdev *blob = ipe_bdev(bdev); + + ipe_digest_free(blob->root_hash); +} + +#ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE +static void ipe_set_dmverity_signature(struct ipe_bdev *blob, + const void *value, + size_t size) +{ + blob->dm_verity_signed = size > 0 && value; +} +#else +static inline void ipe_set_dmverity_signature(struct ipe_bdev *blob, + const void *value, + size_t size) +{ +} +#endif /* CONFIG_IPE_PROP_DM_VERITY_SIGNATURE */ + +/** + * ipe_bdev_setintegrity() - Save integrity data from a bdev to IPE's LSM blob. + * @bdev: Supplies a pointer to a block_device that contains the LSM blob. + * @type: Supplies the integrity type. + * @value: Supplies the value to store. + * @size: The size of @value. + * + * This hook is currently used to save dm-verity's root hash or the existence + * of a validated signed dm-verity root hash into LSM blob. + * + * Return: %0 on success. If an error occurs, the function will return the + * -errno. + */ +int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type type, + const void *value, size_t size) +{ + const struct dm_verity_digest *digest = NULL; + struct ipe_bdev *blob = ipe_bdev(bdev); + struct digest_info *info = NULL; + + if (type == LSM_INT_DMVERITY_SIG_VALID) { + ipe_set_dmverity_signature(blob, value, size); + + return 0; + } + + if (type != LSM_INT_DMVERITY_ROOTHASH) + return -EINVAL; + + if (!value) { + ipe_digest_free(blob->root_hash); + blob->root_hash = NULL; + + return 0; + } + digest = value; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->digest = kmemdup(digest->digest, digest->digest_len, GFP_KERNEL); + if (!info->digest) + goto err; + + info->alg = kstrdup(digest->alg, GFP_KERNEL); + if (!info->alg) + goto err; + + info->digest_len = digest->digest_len; + + ipe_digest_free(blob->root_hash); + blob->root_hash = info; + + return 0; +err: + ipe_digest_free(info); + + return -ENOMEM; +} +#endif /* CONFIG_IPE_PROP_DM_VERITY */ + +#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG +/** + * ipe_inode_setintegrity() - save integrity data from a inode to IPE's LSM blob. + * @inode: The inode to source the security blob from. + * @type: Supplies the integrity type. + * @value: The value to be stored. + * @size: The size of @value. + * + * This hook is currently used to save the existence of a validated fs-verity + * builtin signature into LSM blob. + * + * Return: %0 on success. If an error occurs, the function will return the + * -errno. + */ +int ipe_inode_setintegrity(const struct inode *inode, + enum lsm_integrity_type type, + const void *value, size_t size) +{ + struct ipe_inode *inode_sec = ipe_inode(inode); + + if (type == LSM_INT_FSVERITY_BUILTINSIG_VALID) { + inode_sec->fs_verity_signed = size > 0 && value; + return 0; + } + + return -EINVAL; +} +#endif /* CONFIG_CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ diff --git a/security/ipe/hooks.h b/security/ipe/hooks.h new file mode 100644 index 000000000000..38d4a387d039 --- /dev/null +++ b/security/ipe/hooks.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ +#ifndef _IPE_HOOKS_H +#define _IPE_HOOKS_H + +#include <linux/fs.h> +#include <linux/binfmts.h> +#include <linux/security.h> +#include <linux/blk_types.h> +#include <linux/fsverity.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, + unsigned long flags); + +int ipe_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot, + unsigned long prot); + +int ipe_kernel_read_file(struct file *file, enum kernel_read_file_id id, + bool contents); + +int ipe_kernel_load_data(enum kernel_load_data_id id, bool contents); + +void ipe_unpack_initramfs(void); + +#ifdef CONFIG_IPE_PROP_DM_VERITY +void ipe_bdev_free_security(struct block_device *bdev); + +int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type type, + const void *value, size_t len); +#endif /* CONFIG_IPE_PROP_DM_VERITY */ + +#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG +int ipe_inode_setintegrity(const struct inode *inode, enum lsm_integrity_type type, + const void *value, size_t size); +#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ + +#endif /* _IPE_HOOKS_H */ diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c new file mode 100644 index 000000000000..4317134cb0da --- /dev/null +++ b/security/ipe/ipe.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ +#include <uapi/linux/lsm.h> + +#include "ipe.h" +#include "eval.h" +#include "hooks.h" + +extern const char *const ipe_boot_policy; +bool ipe_enabled; + +static struct lsm_blob_sizes ipe_blobs __ro_after_init = { + .lbs_superblock = sizeof(struct ipe_superblock), +#ifdef CONFIG_IPE_PROP_DM_VERITY + .lbs_bdev = sizeof(struct ipe_bdev), +#endif /* CONFIG_IPE_PROP_DM_VERITY */ +#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG + .lbs_inode = sizeof(struct ipe_inode), +#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ +}; + +static const struct lsm_id ipe_lsmid = { + .name = "ipe", + .id = LSM_ID_IPE, +}; + +struct ipe_superblock *ipe_sb(const struct super_block *sb) +{ + return sb->s_security + ipe_blobs.lbs_superblock; +} + +#ifdef CONFIG_IPE_PROP_DM_VERITY +struct ipe_bdev *ipe_bdev(struct block_device *b) +{ + return b->bd_security + ipe_blobs.lbs_bdev; +} +#endif /* CONFIG_IPE_PROP_DM_VERITY */ + +#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG +struct ipe_inode *ipe_inode(const struct inode *inode) +{ + return inode->i_security + ipe_blobs.lbs_inode; +} +#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ + +static struct security_hook_list ipe_hooks[] __ro_after_init = { + LSM_HOOK_INIT(bprm_check_security, ipe_bprm_check_security), + LSM_HOOK_INIT(mmap_file, ipe_mmap_file), + LSM_HOOK_INIT(file_mprotect, ipe_file_mprotect), + LSM_HOOK_INIT(kernel_read_file, ipe_kernel_read_file), + LSM_HOOK_INIT(kernel_load_data, ipe_kernel_load_data), + LSM_HOOK_INIT(initramfs_populated, ipe_unpack_initramfs), +#ifdef CONFIG_IPE_PROP_DM_VERITY + LSM_HOOK_INIT(bdev_free_security, ipe_bdev_free_security), + LSM_HOOK_INIT(bdev_setintegrity, ipe_bdev_setintegrity), +#endif /* CONFIG_IPE_PROP_DM_VERITY */ +#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG + LSM_HOOK_INIT(inode_setintegrity, ipe_inode_setintegrity), +#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ +}; + +/** + * ipe_init() - Entry point of IPE. + * + * This is called at LSM init, which happens occurs early during kernel + * start up. During this phase, IPE registers its hooks and loads the + * builtin boot policy. + * + * Return: + * * %0 - OK + * * %-ENOMEM - Out of memory (OOM) + */ +static int __init ipe_init(void) +{ + struct ipe_policy *p = NULL; + + security_add_hooks(ipe_hooks, ARRAY_SIZE(ipe_hooks), &ipe_lsmid); + ipe_enabled = true; + + if (ipe_boot_policy) { + p = ipe_new_policy(ipe_boot_policy, strlen(ipe_boot_policy), + NULL, 0); + if (IS_ERR(p)) + return PTR_ERR(p); + + rcu_assign_pointer(ipe_active_policy, p); + } + + return 0; +} + +DEFINE_LSM(ipe) = { + .name = "ipe", + .init = ipe_init, + .blobs = &ipe_blobs, +}; diff --git a/security/ipe/ipe.h b/security/ipe/ipe.h new file mode 100644 index 000000000000..fb37513812dd --- /dev/null +++ b/security/ipe/ipe.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#ifndef _IPE_H +#define _IPE_H + +#ifdef pr_fmt +#undef pr_fmt +#endif +#define pr_fmt(fmt) "ipe: " fmt + +#include <linux/lsm_hooks.h> +struct ipe_superblock *ipe_sb(const struct super_block *sb); + +extern bool ipe_enabled; + +#ifdef CONFIG_IPE_PROP_DM_VERITY +struct ipe_bdev *ipe_bdev(struct block_device *b); +#endif /* CONFIG_IPE_PROP_DM_VERITY */ +#ifdef CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG +struct ipe_inode *ipe_inode(const struct inode *inode); +#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ + +#endif /* _IPE_H */ diff --git a/security/ipe/policy.c b/security/ipe/policy.c new file mode 100644 index 000000000000..d8e7db857a2e --- /dev/null +++ b/security/ipe/policy.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#include <linux/errno.h> +#include <linux/verification.h> + +#include "ipe.h" +#include "eval.h" +#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); + +/** + * ver_to_u64() - Convert an internal ipe_policy_version to a u64. + * @p: Policy to extract the version from. + * + * Bits (LSB is index 0): + * [48,32] -> Major + * [32,16] -> Minor + * [16, 0] -> Revision + * + * Return: u64 version of the embedded version structure. + */ +static inline u64 ver_to_u64(const struct ipe_policy *const p) +{ + u64 r; + + r = (((u64)p->parsed->version.major) << 32) + | (((u64)p->parsed->version.minor) << 16) + | ((u64)(p->parsed->version.rev)); + + return r; +} + +/** + * ipe_free_policy() - Deallocate a given IPE policy. + * @p: Supplies the policy to free. + * + * Safe to call on IS_ERR/NULL. + */ +void ipe_free_policy(struct ipe_policy *p) +{ + if (IS_ERR_OR_NULL(p)) + return; + + ipe_del_policyfs_node(p); + ipe_free_parsed_policy(p->parsed); + /* + * p->text is allocated only when p->pkcs7 is not NULL + * otherwise it points to the plaintext data inside the pkcs7 + */ + if (!p->pkcs7) + kfree(p->text); + kfree(p->pkcs7); + kfree(p); +} + +static int set_pkcs7_data(void *ctx, const void *data, size_t len, + size_t asn1hdrlen __always_unused) +{ + struct ipe_policy *p = ctx; + + p->text = (const char *)data; + p->textlen = len; + + return 0; +} + +/** + * ipe_update_policy() - parse a new policy and replace old with it. + * @root: Supplies a pointer to the securityfs inode saved the policy. + * @text: Supplies a pointer to the plain text policy. + * @textlen: Supplies the length of @text. + * @pkcs7: Supplies a pointer to a buffer containing a pkcs7 message. + * @pkcs7len: Supplies the length of @pkcs7len. + * + * @text/@textlen is mutually exclusive with @pkcs7/@pkcs7len - see + * ipe_new_policy. + * + * Context: Requires root->i_rwsem to be held. + * Return: %0 on success. If an error occurs, the function will return + * the -errno. + */ +int ipe_update_policy(struct inode *root, const char *text, size_t textlen, + const char *pkcs7, size_t pkcs7len) +{ + struct ipe_policy *old, *ap, *new = NULL; + int rc = 0; + + old = (struct ipe_policy *)root->i_private; + if (!old) + return -ENOENT; + + new = ipe_new_policy(text, textlen, pkcs7, pkcs7len); + if (IS_ERR(new)) + return PTR_ERR(new); + + if (strcmp(new->parsed->name, old->parsed->name)) { + rc = -EINVAL; + goto err; + } + + if (ver_to_u64(old) > ver_to_u64(new)) { + rc = -EINVAL; + goto err; + } + + 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, + lockdep_is_held(&ipe_policy_lock)); + 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); + } + synchronize_rcu(); + ipe_free_policy(old); + + return 0; +err: + ipe_free_policy(new); + return rc; +} + +/** + * ipe_new_policy() - Allocate and parse an ipe_policy structure. + * + * @text: Supplies a pointer to the plain-text policy to parse. + * @textlen: Supplies the length of @text. + * @pkcs7: Supplies a pointer to a pkcs7-signed IPE policy. + * @pkcs7len: Supplies the length of @pkcs7. + * + * @text/@textlen Should be NULL/0 if @pkcs7/@pkcs7len is set. + * + * Return: + * * a pointer to the ipe_policy structure - Success + * * %-EBADMSG - Policy is invalid + * * %-ENOMEM - Out of memory (OOM) + * * %-ERANGE - Policy version number overflow + * * %-EINVAL - Policy version parsing error + */ +struct ipe_policy *ipe_new_policy(const char *text, size_t textlen, + const char *pkcs7, size_t pkcs7len) +{ + struct ipe_policy *new = NULL; + int rc = 0; + + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!new) + return ERR_PTR(-ENOMEM); + + if (!text) { + new->pkcs7len = pkcs7len; + new->pkcs7 = kmemdup(pkcs7, pkcs7len, GFP_KERNEL); + if (!new->pkcs7) { + rc = -ENOMEM; + goto err; + } + + rc = verify_pkcs7_signature(NULL, 0, new->pkcs7, pkcs7len, NULL, + VERIFYING_UNSPECIFIED_SIGNATURE, + set_pkcs7_data, new); + if (rc) + goto err; + } else { + new->textlen = textlen; + new->text = kstrdup(text, GFP_KERNEL); + if (!new->text) { + rc = -ENOMEM; + goto err; + } + } + + rc = ipe_parse_policy(new); + if (rc) + goto err; + + return new; +err: + ipe_free_policy(new); + return ERR_PTR(rc); +} + +/** + * ipe_set_active_pol() - Make @p the active policy. + * @p: Supplies a pointer to the policy to make active. + * + * Context: Requires root->i_rwsem, which i_private has the policy, to be held. + * Return: + * * %0 - Success + * * %-EINVAL - New active policy version is invalid + */ +int ipe_set_active_pol(const struct ipe_policy *p) +{ + struct ipe_policy *ap = NULL; + + mutex_lock(&ipe_policy_lock); + + ap = rcu_dereference_protected(ipe_active_policy, + lockdep_is_held(&ipe_policy_lock)); + if (ap == p) { + mutex_unlock(&ipe_policy_lock); + return 0; + } + if (ap && ver_to_u64(ap) > ver_to_u64(p)) { + mutex_unlock(&ipe_policy_lock); + return -EINVAL; + } + + rcu_assign_pointer(ipe_active_policy, p); + ipe_audit_policy_activation(ap, p); + mutex_unlock(&ipe_policy_lock); + + return 0; +} diff --git a/security/ipe/policy.h b/security/ipe/policy.h new file mode 100644 index 000000000000..5bfbdbddeef8 --- /dev/null +++ b/security/ipe/policy.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ +#ifndef _IPE_POLICY_H +#define _IPE_POLICY_H + +#include <linux/list.h> +#include <linux/types.h> +#include <linux/fs.h> + +enum ipe_op_type { + IPE_OP_EXEC = 0, + IPE_OP_FIRMWARE, + IPE_OP_KERNEL_MODULE, + IPE_OP_KEXEC_IMAGE, + IPE_OP_KEXEC_INITRAMFS, + IPE_OP_POLICY, + IPE_OP_X509, + __IPE_OP_MAX, +}; + +#define IPE_OP_INVALID __IPE_OP_MAX + +enum ipe_action_type { + IPE_ACTION_ALLOW = 0, + IPE_ACTION_DENY, + __IPE_ACTION_MAX +}; + +#define IPE_ACTION_INVALID __IPE_ACTION_MAX + +enum ipe_prop_type { + IPE_PROP_BOOT_VERIFIED_FALSE, + IPE_PROP_BOOT_VERIFIED_TRUE, + IPE_PROP_DMV_ROOTHASH, + IPE_PROP_DMV_SIG_FALSE, + IPE_PROP_DMV_SIG_TRUE, + IPE_PROP_FSV_DIGEST, + IPE_PROP_FSV_SIG_FALSE, + IPE_PROP_FSV_SIG_TRUE, + __IPE_PROP_MAX +}; + +#define IPE_PROP_INVALID __IPE_PROP_MAX + +struct ipe_prop { + struct list_head next; + enum ipe_prop_type type; + void *value; +}; + +struct ipe_rule { + enum ipe_op_type op; + enum ipe_action_type action; + struct list_head props; + struct list_head next; +}; + +struct ipe_op_table { + struct list_head rules; + enum ipe_action_type default_action; +}; + +struct ipe_parsed_policy { + const char *name; + struct { + u16 major; + u16 minor; + u16 rev; + } version; + + enum ipe_action_type global_default_action; + + struct ipe_op_table rules[__IPE_OP_MAX]; +}; + +struct ipe_policy { + const char *pkcs7; + size_t pkcs7len; + + const char *text; + size_t textlen; + + struct ipe_parsed_policy *parsed; + + struct dentry *policyfs; +}; + +struct ipe_policy *ipe_new_policy(const char *text, size_t textlen, + const char *pkcs7, size_t pkcs7len); +void ipe_free_policy(struct ipe_policy *pol); +int ipe_update_policy(struct inode *root, const char *text, size_t textlen, + const char *pkcs7, size_t pkcs7len); +int ipe_set_active_pol(const struct ipe_policy *p); +extern struct mutex ipe_policy_lock; + +#endif /* _IPE_POLICY_H */ diff --git a/security/ipe/policy_fs.c b/security/ipe/policy_fs.c new file mode 100644 index 000000000000..3bcd8cbd09df --- /dev/null +++ b/security/ipe/policy_fs.c @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ +#include <linux/fs.h> +#include <linux/namei.h> +#include <linux/types.h> +#include <linux/dcache.h> +#include <linux/security.h> + +#include "ipe.h" +#include "policy.h" +#include "eval.h" +#include "fs.h" + +#define MAX_VERSION_SIZE ARRAY_SIZE("65535.65535.65535") + +/** + * ipefs_file - defines a file in securityfs. + */ +struct ipefs_file { + const char *name; + umode_t access; + const struct file_operations *fops; +}; + +/** + * read_pkcs7() - Read handler for "ipe/policies/$name/pkcs7". + * @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. + * + * @data will be populated with the pkcs7 blob representing the policy + * on success. If the policy is unsigned (like the boot policy), this + * will return -ENOENT. + * + * Return: + * * Length of buffer written - Success + * * %-ENOENT - Policy initializing/deleted or is unsigned + */ +static ssize_t read_pkcs7(struct file *f, char __user *data, + size_t len, loff_t *offset) +{ + const struct ipe_policy *p = NULL; + struct inode *root = NULL; + int rc = 0; + + root = d_inode(f->f_path.dentry->d_parent); + + inode_lock_shared(root); + p = (struct ipe_policy *)root->i_private; + if (!p) { + rc = -ENOENT; + goto out; + } + + if (!p->pkcs7) { + rc = -ENOENT; + goto out; + } + + rc = simple_read_from_buffer(data, len, offset, p->pkcs7, p->pkcs7len); + +out: + inode_unlock_shared(root); + + return rc; +} + +/** + * read_policy() - Read handler for "ipe/policies/$name/policy". + * @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. + * + * @data will be populated with the plain-text version of the policy + * on success. + * + * Return: + * * Length of buffer written - Success + * * %-ENOENT - Policy initializing/deleted + */ +static ssize_t read_policy(struct file *f, char __user *data, + size_t len, loff_t *offset) +{ + const struct ipe_policy *p = NULL; + struct inode *root = NULL; + int rc = 0; + + root = d_inode(f->f_path.dentry->d_parent); + + inode_lock_shared(root); + p = (struct ipe_policy *)root->i_private; + if (!p) { + rc = -ENOENT; + goto out; + } + + rc = simple_read_from_buffer(data, len, offset, p->text, p->textlen); + +out: + inode_unlock_shared(root); + + return rc; +} + +/** + * read_name() - Read handler for "ipe/policies/$name/name". + * @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. + * + * @data will be populated with the policy_name attribute on success. + * + * Return: + * * Length of buffer written - Success + * * %-ENOENT - Policy initializing/deleted + */ +static ssize_t read_name(struct file *f, char __user *data, + size_t len, loff_t *offset) +{ + const struct ipe_policy *p = NULL; + struct inode *root = NULL; + int rc = 0; + + root = d_inode(f->f_path.dentry->d_parent); + + inode_lock_shared(root); + p = (struct ipe_policy *)root->i_private; + if (!p) { + rc = -ENOENT; + goto out; + } + + rc = simple_read_from_buffer(data, len, offset, p->parsed->name, + strlen(p->parsed->name)); + +out: + inode_unlock_shared(root); + + return rc; +} + +/** + * read_version() - Read handler for "ipe/policies/$name/version". + * @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. + * + * @data will be populated with the version string on success. + * + * Return: + * * Length of buffer written - Success + * * %-ENOENT - Policy initializing/deleted + */ +static ssize_t read_version(struct file *f, char __user *data, + size_t len, loff_t *offset) +{ + char buffer[MAX_VERSION_SIZE] = { 0 }; + const struct ipe_policy *p = NULL; + struct inode *root = NULL; + size_t strsize = 0; + ssize_t rc = 0; + + root = d_inode(f->f_path.dentry->d_parent); + + inode_lock_shared(root); + p = (struct ipe_policy *)root->i_private; + if (!p) { + rc = -ENOENT; + goto out; + } + + strsize = scnprintf(buffer, ARRAY_SIZE(buffer), "%hu.%hu.%hu", + p->parsed->version.major, p->parsed->version.minor, + p->parsed->version.rev); + + rc = simple_read_from_buffer(data, len, offset, buffer, strsize); + +out: + inode_unlock_shared(root); + + return rc; +} + +/** + * setactive() - Write handler for "ipe/policies/$name/active". + * @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 + * * %-EINVAL - Invalid input + * * %-ENOENT - Policy initializing/deleted + */ +static ssize_t setactive(struct file *f, const char __user *data, + size_t len, loff_t *offset) +{ + const struct ipe_policy *p = NULL; + struct inode *root = NULL; + bool value = false; + int rc = 0; + + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) + return -EPERM; + + rc = kstrtobool_from_user(data, len, &value); + if (rc) + return rc; + + if (!value) + return -EINVAL; + + root = d_inode(f->f_path.dentry->d_parent); + inode_lock(root); + + p = (struct ipe_policy *)root->i_private; + if (!p) { + rc = -ENOENT; + goto out; + } + + rc = ipe_set_active_pol(p); + +out: + inode_unlock(root); + return (rc < 0) ? rc : len; +} + +/** + * getactive() - Read handler for "ipe/policies/$name/active". + * @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. + * + * @data will be populated with the 1 or 0 depending on if the + * corresponding policy is active. + * + * Return: + * * Length of buffer written - Success + * * %-ENOENT - Policy initializing/deleted + */ +static ssize_t getactive(struct file *f, char __user *data, + size_t len, loff_t *offset) +{ + const struct ipe_policy *p = NULL; + struct inode *root = NULL; + const char *str; + int rc = 0; + + root = d_inode(f->f_path.dentry->d_parent); + + inode_lock_shared(root); + p = (struct ipe_policy *)root->i_private; + if (!p) { + inode_unlock_shared(root); + return -ENOENT; + } + inode_unlock_shared(root); + + str = (p == rcu_access_pointer(ipe_active_policy)) ? "1" : "0"; + rc = simple_read_from_buffer(data, len, offset, str, 1); + + return rc; +} + +/** + * update_policy() - Write handler for "ipe/policies/$name/update". + * @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. + * + * On success this updates the policy represented by $name, + * in-place. + * + * Return: Length of buffer written on success. If an error occurs, + * the function will return the -errno. + */ +static ssize_t update_policy(struct file *f, const char __user *data, + size_t len, loff_t *offset) +{ + struct inode *root = NULL; + char *copy = NULL; + int rc = 0; + + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) + return -EPERM; + + copy = memdup_user(data, len); + if (IS_ERR(copy)) + return PTR_ERR(copy); + + root = d_inode(f->f_path.dentry->d_parent); + inode_lock(root); + rc = ipe_update_policy(root, NULL, 0, copy, len); + inode_unlock(root); + + kfree(copy); + if (rc) + return rc; + + return len; +} + +/** + * delete_policy() - write handler for "ipe/policies/$name/delete". + * @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. + * + * On success this deletes the policy represented by $name. + * + * Return: + * * Length of buffer written - Success + * * %-EPERM - Insufficient permission/deleting active policy + * * %-EINVAL - Invalid input + * * %-ENOENT - Policy initializing/deleted + */ +static ssize_t delete_policy(struct file *f, const char __user *data, + size_t len, loff_t *offset) +{ + struct ipe_policy *ap = NULL; + struct ipe_policy *p = NULL; + struct inode *root = NULL; + bool value = false; + int rc = 0; + + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) + return -EPERM; + + rc = kstrtobool_from_user(data, len, &value); + if (rc) + return rc; + + if (!value) + return -EINVAL; + + root = d_inode(f->f_path.dentry->d_parent); + inode_lock(root); + p = (struct ipe_policy *)root->i_private; + if (!p) { + inode_unlock(root); + return -ENOENT; + } + + mutex_lock(&ipe_policy_lock); + ap = rcu_dereference_protected(ipe_active_policy, + lockdep_is_held(&ipe_policy_lock)); + if (p == ap) { + mutex_unlock(&ipe_policy_lock); + inode_unlock(root); + return -EPERM; + } + mutex_unlock(&ipe_policy_lock); + + root->i_private = NULL; + inode_unlock(root); + + synchronize_rcu(); + ipe_free_policy(p); + + return len; +} + +static const struct file_operations content_fops = { + .read = read_policy, +}; + +static const struct file_operations pkcs7_fops = { + .read = read_pkcs7, +}; + +static const struct file_operations name_fops = { + .read = read_name, +}; + +static const struct file_operations ver_fops = { + .read = read_version, +}; + +static const struct file_operations active_fops = { + .write = setactive, + .read = getactive, +}; + +static const struct file_operations update_fops = { + .write = update_policy, +}; + +static const struct file_operations delete_fops = { + .write = delete_policy, +}; + +/** + * policy_subdir - files under a policy subdirectory + */ +static const struct ipefs_file policy_subdir[] = { + { "pkcs7", 0444, &pkcs7_fops }, + { "policy", 0444, &content_fops }, + { "name", 0444, &name_fops }, + { "version", 0444, &ver_fops }, + { "active", 0600, &active_fops }, + { "update", 0200, &update_fops }, + { "delete", 0200, &delete_fops }, +}; + +/** + * ipe_del_policyfs_node() - Delete a securityfs entry for @p. + * @p: Supplies a pointer to the policy to delete a securityfs entry for. + */ +void ipe_del_policyfs_node(struct ipe_policy *p) +{ + securityfs_recursive_remove(p->policyfs); + p->policyfs = NULL; +} + +/** + * ipe_new_policyfs_node() - Create a securityfs entry for @p. + * @p: Supplies a pointer to the policy to create a securityfs entry for. + * + * Return: %0 on success. If an error occurs, the function will return + * the -errno. + */ +int ipe_new_policyfs_node(struct ipe_policy *p) +{ + const struct ipefs_file *f = NULL; + struct dentry *policyfs = NULL; + struct inode *root = NULL; + struct dentry *d = NULL; + size_t i = 0; + int rc = 0; + + if (p->policyfs) + return 0; + + policyfs = securityfs_create_dir(p->parsed->name, policy_root); + if (IS_ERR(policyfs)) + return PTR_ERR(policyfs); + + root = d_inode(policyfs); + + for (i = 0; i < ARRAY_SIZE(policy_subdir); ++i) { + f = &policy_subdir[i]; + + d = securityfs_create_file(f->name, f->access, policyfs, + NULL, f->fops); + if (IS_ERR(d)) { + rc = PTR_ERR(d); + goto err; + } + } + + inode_lock(root); + p->policyfs = policyfs; + root->i_private = p; + inode_unlock(root); + + return 0; +err: + securityfs_recursive_remove(policyfs); + return rc; +} diff --git a/security/ipe/policy_parser.c b/security/ipe/policy_parser.c new file mode 100644 index 000000000000..7f27e39931d6 --- /dev/null +++ b/security/ipe/policy_parser.c @@ -0,0 +1,559 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/parser.h> +#include <linux/types.h> +#include <linux/ctype.h> + +#include "policy.h" +#include "policy_parser.h" +#include "digest.h" + +#define START_COMMENT '#' +#define IPE_POLICY_DELIM " \t" +#define IPE_LINE_DELIM "\n\r" + +/** + * new_parsed_policy() - Allocate and initialize a parsed policy. + * + * Return: + * * a pointer to the ipe_parsed_policy structure - Success + * * %-ENOMEM - Out of memory (OOM) + */ +static struct ipe_parsed_policy *new_parsed_policy(void) +{ + struct ipe_parsed_policy *p = NULL; + struct ipe_op_table *t = NULL; + size_t i = 0; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return ERR_PTR(-ENOMEM); + + p->global_default_action = IPE_ACTION_INVALID; + + for (i = 0; i < ARRAY_SIZE(p->rules); ++i) { + t = &p->rules[i]; + + t->default_action = IPE_ACTION_INVALID; + INIT_LIST_HEAD(&t->rules); + } + + return p; +} + +/** + * remove_comment() - Truncate all chars following START_COMMENT in a string. + * + * @line: Supplies a policy line string for preprocessing. + */ +static void remove_comment(char *line) +{ + line = strchr(line, START_COMMENT); + + if (line) + *line = '\0'; +} + +/** + * remove_trailing_spaces() - Truncate all trailing spaces in a string. + * + * @line: Supplies a policy line string for preprocessing. + * + * Return: The length of truncated string. + */ +static size_t remove_trailing_spaces(char *line) +{ + size_t i = 0; + + i = strlen(line); + while (i > 0 && isspace(line[i - 1])) + i--; + + line[i] = '\0'; + + return i; +} + +/** + * parse_version() - Parse policy version. + * @ver: Supplies a version string to be parsed. + * @p: Supplies the partial parsed policy. + * + * Return: + * * %0 - Success + * * %-EBADMSG - Version string is invalid + * * %-ERANGE - Version number overflow + * * %-EINVAL - Parsing error + */ +static int parse_version(char *ver, struct ipe_parsed_policy *p) +{ + u16 *const cv[] = { &p->version.major, &p->version.minor, &p->version.rev }; + size_t sep_count = 0; + char *token; + int rc = 0; + + while ((token = strsep(&ver, ".")) != NULL) { + /* prevent overflow */ + if (sep_count >= ARRAY_SIZE(cv)) + return -EBADMSG; + + rc = kstrtou16(token, 10, cv[sep_count]); + if (rc) + return rc; + + ++sep_count; + } + + /* prevent underflow */ + if (sep_count != ARRAY_SIZE(cv)) + return -EBADMSG; + + return 0; +} + +enum header_opt { + IPE_HEADER_POLICY_NAME = 0, + IPE_HEADER_POLICY_VERSION, + __IPE_HEADER_MAX +}; + +static const match_table_t header_tokens = { + {IPE_HEADER_POLICY_NAME, "policy_name=%s"}, + {IPE_HEADER_POLICY_VERSION, "policy_version=%s"}, + {__IPE_HEADER_MAX, NULL} +}; + +/** + * parse_header() - Parse policy header information. + * @line: Supplies header line to be parsed. + * @p: Supplies the partial parsed policy. + * + * Return: + * * %0 - Success + * * %-EBADMSG - Header string is invalid + * * %-ENOMEM - Out of memory (OOM) + * * %-ERANGE - Version number overflow + * * %-EINVAL - Version parsing error + */ +static int parse_header(char *line, struct ipe_parsed_policy *p) +{ + substring_t args[MAX_OPT_ARGS]; + char *t, *ver = NULL; + size_t idx = 0; + int rc = 0; + + while ((t = strsep(&line, IPE_POLICY_DELIM)) != NULL) { + int token; + + if (*t == '\0') + continue; + if (idx >= __IPE_HEADER_MAX) { + rc = -EBADMSG; + goto out; + } + + token = match_token(t, header_tokens, args); + if (token != idx) { + rc = -EBADMSG; + goto out; + } + + switch (token) { + case IPE_HEADER_POLICY_NAME: + p->name = match_strdup(&args[0]); + if (!p->name) + rc = -ENOMEM; + break; + case IPE_HEADER_POLICY_VERSION: + ver = match_strdup(&args[0]); + if (!ver) { + rc = -ENOMEM; + break; + } + rc = parse_version(ver, p); + break; + default: + rc = -EBADMSG; + } + if (rc) + goto out; + ++idx; + } + + if (idx != __IPE_HEADER_MAX) + rc = -EBADMSG; + +out: + kfree(ver); + return rc; +} + +/** + * token_default() - Determine if the given token is "DEFAULT". + * @token: Supplies the token string to be compared. + * + * Return: + * * %false - The token is not "DEFAULT" + * * %true - The token is "DEFAULT" + */ +static bool token_default(char *token) +{ + return !strcmp(token, "DEFAULT"); +} + +/** + * free_rule() - Free the supplied ipe_rule struct. + * @r: Supplies the ipe_rule struct to be freed. + * + * Free a ipe_rule struct @r. Note @r must be removed from any lists before + * calling this function. + */ +static void free_rule(struct ipe_rule *r) +{ + struct ipe_prop *p, *t; + + if (IS_ERR_OR_NULL(r)) + return; + + list_for_each_entry_safe(p, t, &r->props, next) { + list_del(&p->next); + ipe_digest_free(p->value); + kfree(p); + } + + kfree(r); +} + +static const match_table_t operation_tokens = { + {IPE_OP_EXEC, "op=EXECUTE"}, + {IPE_OP_FIRMWARE, "op=FIRMWARE"}, + {IPE_OP_KERNEL_MODULE, "op=KMODULE"}, + {IPE_OP_KEXEC_IMAGE, "op=KEXEC_IMAGE"}, + {IPE_OP_KEXEC_INITRAMFS, "op=KEXEC_INITRAMFS"}, + {IPE_OP_POLICY, "op=POLICY"}, + {IPE_OP_X509, "op=X509_CERT"}, + {IPE_OP_INVALID, NULL} +}; + +/** + * parse_operation() - Parse the operation type given a token string. + * @t: Supplies the token string to be parsed. + * + * Return: The parsed operation type. + */ +static enum ipe_op_type parse_operation(char *t) +{ + substring_t args[MAX_OPT_ARGS]; + + return match_token(t, operation_tokens, args); +} + +static const match_table_t action_tokens = { + {IPE_ACTION_ALLOW, "action=ALLOW"}, + {IPE_ACTION_DENY, "action=DENY"}, + {IPE_ACTION_INVALID, NULL} +}; + +/** + * parse_action() - Parse the action type given a token string. + * @t: Supplies the token string to be parsed. + * + * Return: The parsed action type. + */ +static enum ipe_action_type parse_action(char *t) +{ + substring_t args[MAX_OPT_ARGS]; + + return match_token(t, action_tokens, args); +} + +static const match_table_t property_tokens = { + {IPE_PROP_BOOT_VERIFIED_FALSE, "boot_verified=FALSE"}, + {IPE_PROP_BOOT_VERIFIED_TRUE, "boot_verified=TRUE"}, + {IPE_PROP_DMV_ROOTHASH, "dmverity_roothash=%s"}, + {IPE_PROP_DMV_SIG_FALSE, "dmverity_signature=FALSE"}, + {IPE_PROP_DMV_SIG_TRUE, "dmverity_signature=TRUE"}, + {IPE_PROP_FSV_DIGEST, "fsverity_digest=%s"}, + {IPE_PROP_FSV_SIG_FALSE, "fsverity_signature=FALSE"}, + {IPE_PROP_FSV_SIG_TRUE, "fsverity_signature=TRUE"}, + {IPE_PROP_INVALID, NULL} +}; + +/** + * parse_property() - Parse a rule property given a token string. + * @t: Supplies the token string to be parsed. + * @r: Supplies the ipe_rule the parsed property will be associated with. + * + * This function parses and associates a property with an IPE rule based + * on a token string. + * + * Return: + * * %0 - Success + * * %-ENOMEM - Out of memory (OOM) + * * %-EBADMSG - The supplied token cannot be parsed + */ +static int parse_property(char *t, struct ipe_rule *r) +{ + substring_t args[MAX_OPT_ARGS]; + struct ipe_prop *p = NULL; + int rc = 0; + int token; + char *dup = NULL; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + token = match_token(t, property_tokens, args); + + switch (token) { + case IPE_PROP_DMV_ROOTHASH: + case IPE_PROP_FSV_DIGEST: + dup = match_strdup(&args[0]); + if (!dup) { + rc = -ENOMEM; + goto err; + } + p->value = ipe_digest_parse(dup); + if (IS_ERR(p->value)) { + rc = PTR_ERR(p->value); + goto err; + } + fallthrough; + case IPE_PROP_BOOT_VERIFIED_FALSE: + case IPE_PROP_BOOT_VERIFIED_TRUE: + case IPE_PROP_DMV_SIG_FALSE: + case IPE_PROP_DMV_SIG_TRUE: + case IPE_PROP_FSV_SIG_FALSE: + case IPE_PROP_FSV_SIG_TRUE: + p->type = token; + break; + default: + rc = -EBADMSG; + break; + } + if (rc) + goto err; + list_add_tail(&p->next, &r->props); + +out: + kfree(dup); + return rc; +err: + kfree(p); + goto out; +} + +/** + * parse_rule() - parse a policy rule line. + * @line: Supplies rule line to be parsed. + * @p: Supplies the partial parsed policy. + * + * Return: + * * 0 - Success + * * %-ENOMEM - Out of memory (OOM) + * * %-EBADMSG - Policy syntax error + */ +static int parse_rule(char *line, struct ipe_parsed_policy *p) +{ + enum ipe_action_type action = IPE_ACTION_INVALID; + enum ipe_op_type op = IPE_OP_INVALID; + bool is_default_rule = false; + struct ipe_rule *r = NULL; + bool first_token = true; + bool op_parsed = false; + int rc = 0; + char *t; + + if (IS_ERR_OR_NULL(line)) + return -EBADMSG; + + r = kzalloc(sizeof(*r), GFP_KERNEL); + if (!r) + return -ENOMEM; + + INIT_LIST_HEAD(&r->next); + INIT_LIST_HEAD(&r->props); + + while (t = strsep(&line, IPE_POLICY_DELIM), line) { + if (*t == '\0') + continue; + if (first_token && token_default(t)) { + is_default_rule = true; + } else { + if (!op_parsed) { + op = parse_operation(t); + if (op == IPE_OP_INVALID) + rc = -EBADMSG; + else + op_parsed = true; + } else { + rc = parse_property(t, r); + } + } + + if (rc) + goto err; + first_token = false; + } + + action = parse_action(t); + if (action == IPE_ACTION_INVALID) { + rc = -EBADMSG; + goto err; + } + + if (is_default_rule) { + if (!list_empty(&r->props)) { + rc = -EBADMSG; + } else if (op == IPE_OP_INVALID) { + if (p->global_default_action != IPE_ACTION_INVALID) + rc = -EBADMSG; + else + p->global_default_action = action; + } else { + if (p->rules[op].default_action != IPE_ACTION_INVALID) + rc = -EBADMSG; + else + p->rules[op].default_action = action; + } + } else if (op != IPE_OP_INVALID && action != IPE_ACTION_INVALID) { + r->op = op; + r->action = action; + } else { + rc = -EBADMSG; + } + + if (rc) + goto err; + if (!is_default_rule) + list_add_tail(&r->next, &p->rules[op].rules); + else + free_rule(r); + + return rc; +err: + free_rule(r); + return rc; +} + +/** + * ipe_free_parsed_policy() - free a parsed policy structure. + * @p: Supplies the parsed policy. + */ +void ipe_free_parsed_policy(struct ipe_parsed_policy *p) +{ + struct ipe_rule *pp, *t; + size_t i = 0; + + if (IS_ERR_OR_NULL(p)) + return; + + for (i = 0; i < ARRAY_SIZE(p->rules); ++i) + list_for_each_entry_safe(pp, t, &p->rules[i].rules, next) { + list_del(&pp->next); + free_rule(pp); + } + + kfree(p->name); + kfree(p); +} + +/** + * validate_policy() - validate a parsed policy. + * @p: Supplies the fully parsed policy. + * + * Given a policy structure that was just parsed, validate that all + * operations have their default rules or a global default rule is set. + * + * Return: + * * %0 - Success + * * %-EBADMSG - Policy is invalid + */ +static int validate_policy(const struct ipe_parsed_policy *p) +{ + size_t i = 0; + + if (p->global_default_action != IPE_ACTION_INVALID) + return 0; + + for (i = 0; i < ARRAY_SIZE(p->rules); ++i) { + if (p->rules[i].default_action == IPE_ACTION_INVALID) + return -EBADMSG; + } + + return 0; +} + +/** + * ipe_parse_policy() - Given a string, parse the string into an IPE policy. + * @p: partially filled ipe_policy structure to populate with the result. + * it must have text and textlen set. + * + * Return: + * * %0 - Success + * * %-EBADMSG - Policy is invalid + * * %-ENOMEM - Out of Memory + * * %-ERANGE - Policy version number overflow + * * %-EINVAL - Policy version parsing error + */ +int ipe_parse_policy(struct ipe_policy *p) +{ + struct ipe_parsed_policy *pp = NULL; + char *policy = NULL, *dup = NULL; + bool header_parsed = false; + char *line = NULL; + size_t len; + int rc = 0; + + if (!p->textlen) + return -EBADMSG; + + policy = kmemdup_nul(p->text, p->textlen, GFP_KERNEL); + if (!policy) + return -ENOMEM; + dup = policy; + + pp = new_parsed_policy(); + if (IS_ERR(pp)) { + rc = PTR_ERR(pp); + goto out; + } + + while ((line = strsep(&policy, IPE_LINE_DELIM)) != NULL) { + remove_comment(line); + len = remove_trailing_spaces(line); + if (!len) + continue; + + if (!header_parsed) { + rc = parse_header(line, pp); + if (rc) + goto err; + header_parsed = true; + } else { + rc = parse_rule(line, pp); + if (rc) + goto err; + } + } + + if (!header_parsed || validate_policy(pp)) { + rc = -EBADMSG; + goto err; + } + + p->parsed = pp; + +out: + kfree(dup); + return rc; +err: + ipe_free_parsed_policy(pp); + goto out; +} diff --git a/security/ipe/policy_parser.h b/security/ipe/policy_parser.h new file mode 100644 index 000000000000..62b6209019a2 --- /dev/null +++ b/security/ipe/policy_parser.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ +#ifndef _IPE_POLICY_PARSER_H +#define _IPE_POLICY_PARSER_H + +int ipe_parse_policy(struct ipe_policy *p); +void ipe_free_parsed_policy(struct ipe_parsed_policy *p); + +#endif /* _IPE_POLICY_PARSER_H */ diff --git a/security/ipe/policy_tests.c b/security/ipe/policy_tests.c new file mode 100644 index 000000000000..89521f6b9994 --- /dev/null +++ b/security/ipe/policy_tests.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/list.h> +#include <kunit/test.h> +#include "policy.h" +struct policy_case { + const char *const policy; + int errno; + const char *const desc; +}; + +static const struct policy_case policy_cases[] = { + { + "policy_name=allowall policy_version=0.0.0\n" + "DEFAULT action=ALLOW", + 0, + "basic", + }, + { + "policy_name=trailing_comment policy_version=152.0.0 #This is comment\n" + "DEFAULT action=ALLOW", + 0, + "trailing comment", + }, + { + "policy_name=allowallnewline policy_version=0.2.0\n" + "DEFAULT action=ALLOW\n" + "\n", + 0, + "trailing newline", + }, + { + "policy_name=carriagereturnlinefeed policy_version=0.0.1\n" + "DEFAULT action=ALLOW\n" + "\r\n", + 0, + "clrf newline", + }, + { + "policy_name=whitespace policy_version=0.0.0\n" + "DEFAULT\taction=ALLOW\n" + " \t DEFAULT \t op=EXECUTE action=DENY\n" + "op=EXECUTE boot_verified=TRUE action=ALLOW\n" + "# this is a\tcomment\t\t\t\t\n" + "DEFAULT \t op=KMODULE\t\t\t action=DENY\r\n" + "op=KMODULE boot_verified=TRUE action=ALLOW\n", + 0, + "various whitespaces and nested default", + }, + { + "policy_name=boot_verified policy_version=-1236.0.0\n" + "DEFAULT\taction=ALLOW\n", + -EINVAL, + "negative version", + }, + { + "policy_name=$@!*&^%%\\:;{}() policy_version=0.0.0\n" + "DEFAULT action=ALLOW", + 0, + "special characters", + }, + { + "policy_name=test policy_version=999999.0.0\n" + "DEFAULT action=ALLOW", + -ERANGE, + "overflow version", + }, + { + "policy_name=test policy_version=255.0\n" + "DEFAULT action=ALLOW", + -EBADMSG, + "incomplete version", + }, + { + "policy_name=test policy_version=111.0.0.0\n" + "DEFAULT action=ALLOW", + -EBADMSG, + "extra version", + }, + { + "", + -EBADMSG, + "0-length policy", + }, + { + "policy_name=test\0policy_version=0.0.0\n" + "DEFAULT action=ALLOW", + -EBADMSG, + "random null in header", + }, + { + "policy_name=test policy_version=0.0.0\n" + "\0DEFAULT action=ALLOW", + -EBADMSG, + "incomplete policy from NULL", + }, + { + "policy_name=test policy_version=0.0.0\n" + "DEFAULT action=DENY\n\0" + "op=EXECUTE dmverity_signature=TRUE action=ALLOW\n", + 0, + "NULL truncates policy", + }, + { + "policy_name=test policy_version=0.0.0\n" + "DEFAULT action=ALLOW\n" + "op=EXECUTE dmverity_signature=abc action=ALLOW", + -EBADMSG, + "invalid property type", + }, + { + "DEFAULT action=ALLOW", + -EBADMSG, + "missing policy header", + }, + { + "policy_name=test policy_version=0.0.0\n", + -EBADMSG, + "missing default definition", + }, + { + "policy_name=test policy_version=0.0.0\n" + "DEFAULT action=ALLOW\n" + "dmverity_signature=TRUE op=EXECUTE action=ALLOW", + -EBADMSG, + "invalid rule ordering" + }, + { + "policy_name=test policy_version=0.0.0\n" + "DEFAULT action=ALLOW\n" + "action=ALLOW op=EXECUTE dmverity_signature=TRUE", + -EBADMSG, + "invalid rule ordering (2)", + }, + { + "policy_name=test policy_version=0.0\n" + "DEFAULT action=ALLOW\n" + "op=EXECUTE dmverity_signature=TRUE action=ALLOW", + -EBADMSG, + "invalid version", + }, + { + "policy_name=test policy_version=0.0.0\n" + "DEFAULT action=ALLOW\n" + "op=UNKNOWN dmverity_signature=TRUE action=ALLOW", + -EBADMSG, + "unknown operation", + }, + { + "policy_name=asdvpolicy_version=0.0.0\n" + "DEFAULT action=ALLOW\n", + -EBADMSG, + "missing space after policy name", + }, + { + "policy_name=test\xFF\xEF policy_version=0.0.0\n" + "DEFAULT action=ALLOW\n" + "op=EXECUTE dmverity_signature=TRUE action=ALLOW", + 0, + "expanded ascii", + }, + { + "policy_name=test\xFF\xEF policy_version=0.0.0\n" + "DEFAULT action=ALLOW\n" + "op=EXECUTE dmverity_roothash=GOOD_DOG action=ALLOW", + -EBADMSG, + "invalid property value (2)", + }, + { + "policy_name=test policy_version=0.0.0\n" + "policy_name=test policy_version=0.1.0\n" + "DEFAULT action=ALLOW", + -EBADMSG, + "double header" + }, + { + "policy_name=test policy_version=0.0.0\n" + "DEFAULT action=ALLOW\n" + "DEFAULT action=ALLOW\n", + -EBADMSG, + "double default" + }, + { + "policy_name=test policy_version=0.0.0\n" + "DEFAULT action=ALLOW\n" + "DEFAULT op=EXECUTE action=DENY\n" + "DEFAULT op=EXECUTE action=ALLOW\n", + -EBADMSG, + "double operation default" + }, + { + "policy_name=test policy_version=0.0.0\n" + "DEFAULT action=ALLOW\n" + "DEFAULT op=EXECUTE action=DEN\n", + -EBADMSG, + "invalid action value" + }, + { + "policy_name=test policy_version=0.0.0\n" + "DEFAULT action=ALLOW\n" + "DEFAULT op=EXECUTE action\n", + -EBADMSG, + "invalid action value (2)" + }, + { + "policy_name=test policy_version=0.0.0\n" + "DEFAULT action=ALLOW\n" + "UNKNOWN value=true\n", + -EBADMSG, + "unrecognized statement" + }, + { + "policy_name=test policy_version=0.0.0\n" + "DEFAULT action=ALLOW\n" + "op=EXECUTE dmverity_roothash=1c0d7ee1f8343b7fbe418378e8eb22c061d7dec7 action=DENY\n", + -EBADMSG, + "old-style digest" + }, + { + "policy_name=test policy_version=0.0.0\n" + "DEFAULT action=ALLOW\n" + "op=EXECUTE fsverity_digest=1c0d7ee1f8343b7fbe418378e8eb22c061d7dec7 action=DENY\n", + -EBADMSG, + "old-style digest" + } +}; + +static void pol_to_desc(const struct policy_case *c, char *desc) +{ + strscpy(desc, c->desc, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(ipe_policies, policy_cases, pol_to_desc); + +/** + * ipe_parser_unsigned_test - Test the parser by passing unsigned policies. + * @test: Supplies a pointer to a kunit structure. + * + * This is called by the kunit harness. This test does not check the correctness + * of the policy, but ensures that errors are handled correctly. + */ +static void ipe_parser_unsigned_test(struct kunit *test) +{ + const struct policy_case *p = test->param_value; + struct ipe_policy *pol; + + pol = ipe_new_policy(p->policy, strlen(p->policy), NULL, 0); + + if (p->errno) { + KUNIT_EXPECT_EQ(test, PTR_ERR(pol), p->errno); + return; + } + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pol); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, pol->parsed); + KUNIT_EXPECT_STREQ(test, pol->text, p->policy); + KUNIT_EXPECT_PTR_EQ(test, NULL, pol->pkcs7); + KUNIT_EXPECT_EQ(test, 0, pol->pkcs7len); + + ipe_free_policy(pol); +} + +/** + * ipe_parser_widestring_test - Ensure parser fail on a wide string policy. + * @test: Supplies a pointer to a kunit structure. + * + * This is called by the kunit harness. + */ +static void ipe_parser_widestring_test(struct kunit *test) +{ + const unsigned short policy[] = L"policy_name=Test policy_version=0.0.0\n" + L"DEFAULT action=ALLOW"; + struct ipe_policy *pol = NULL; + + pol = ipe_new_policy((const char *)policy, (ARRAY_SIZE(policy) - 1) * 2, NULL, 0); + KUNIT_EXPECT_TRUE(test, IS_ERR_OR_NULL(pol)); + + ipe_free_policy(pol); +} + +static struct kunit_case ipe_parser_test_cases[] = { + KUNIT_CASE_PARAM(ipe_parser_unsigned_test, ipe_policies_gen_params), + KUNIT_CASE(ipe_parser_widestring_test), +}; + +static struct kunit_suite ipe_parser_test_suite = { + .name = "ipe-parser", + .test_cases = ipe_parser_test_cases, +}; + +kunit_test_suite(ipe_parser_test_suite); |