Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
boot_policy.c
+117
View File
@@ -0,0 +1,117 @@
# 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 CRYPTO_LIB_SHA256
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.
config IPE_POLICY_SIG_SECONDARY_KEYRING
bool "IPE policy update verification with secondary keyring"
default y
depends on SECONDARY_TRUSTED_KEYRING
help
Also allow the secondary trusted keyring to verify IPE policy
updates.
If unsure, answer Y.
config IPE_POLICY_SIG_PLATFORM_KEYRING
bool "IPE policy update verification with platform keyring"
default y
depends on INTEGRITY_PLATFORM_KEYRING
help
Also allow the platform keyring to verify IPE policy updates.
If unsure, answer Y.
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
+31
View File
@@ -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 \
+279
View File
@@ -0,0 +1,279 @@
// 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/sha2.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" /* keep in sync with audit_policy() */
#define AUDIT_POLICY_LOAD_FMT "policy_name=\"%s\" policy_version=%hu.%hu.%hu "\
"policy_digest=" IPE_AUDIT_HASH_ALG ":"
#define AUDIT_POLICY_LOAD_NULL_FMT "policy_name=? policy_version=? "\
"policy_digest=?"
#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",
"BPRM_CREDS_FOR_EXEC",
"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=%llu", 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)
{
u8 digest[SHA256_DIGEST_SIZE];
sha256(p->pkcs7, p->pkcs7len, digest);
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, sizeof(digest));
}
/**
* 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 loading event.
* @p: Supplies a pointer to the policy to audit or an error pointer.
*/
void ipe_audit_policy_load(const struct ipe_policy *const p)
{
struct audit_buffer *ab;
int err = 0;
ab = audit_log_start(audit_context(), GFP_KERNEL,
AUDIT_IPE_POLICY_LOAD);
if (!ab)
return;
if (!IS_ERR(p)) {
audit_policy(ab, AUDIT_POLICY_LOAD_FMT, p);
} else {
audit_log_format(ab, AUDIT_POLICY_LOAD_NULL_FMT);
err = PTR_ERR(p);
}
audit_log_format(ab, " auid=%u ses=%u lsm=ipe res=%d errno=%d",
from_kuid(&init_user_ns, audit_get_loginuid(current)),
audit_get_sessionid(current), !err, err);
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);
}
+19
View File
@@ -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 */
+119
View File
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
*/
#include <linux/hex.h>
#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_obj(*info);
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);
}
+26
View File
@@ -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 */
+393
View File
@@ -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");
+70
View File
@@ -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 */
+246
View File
@@ -0,0 +1,246 @@
// 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 *root __ro_after_init;
struct dentry *policy_root __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
* * %-ENOKEY - Policy signing key not found
* * %-EKEYREJECTED - Policy signature verification failed
*/
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)) {
rc = -EPERM;
goto out;
}
copy = memdup_user_nul(data, len);
if (IS_ERR(copy)) {
rc = PTR_ERR(copy);
copy = NULL;
goto out;
}
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;
out:
kfree(copy);
if (rc < 0) {
ipe_free_policy(p);
ipe_audit_policy_load(ERR_PTR(rc));
} else {
ipe_audit_policy_load(p);
}
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.
*/
int __init ipe_init_securityfs(void)
{
int rc = 0;
struct ipe_policy *ap;
struct dentry *dentry;
if (!ipe_enabled)
return -EOPNOTSUPP;
root = securityfs_create_dir("ipe", NULL);
if (IS_ERR(root))
return PTR_ERR(root);
dentry = securityfs_create_file("success_audit", 0600, root,
NULL, &audit_fops);
if (IS_ERR(dentry)) {
rc = PTR_ERR(dentry);
goto err;
}
dentry = securityfs_create_file("enforce", 0600, root, NULL,
&enforce_fops);
if (IS_ERR(dentry)) {
rc = PTR_ERR(dentry);
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;
}
dentry = securityfs_create_file("new_policy", 0200, root, NULL, &np_fops);
if (IS_ERR(dentry)) {
rc = PTR_ERR(dentry);
goto err;
}
return 0;
err:
securityfs_remove(root);
return rc;
}
+16
View File
@@ -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 */
+342
View File
@@ -0,0 +1,342 @@
// 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_bprm_creds_for_exec() - ipe security hook function for bprm creds check.
* @bprm: Supplies a pointer to a linux_binprm structure to source the file
* being evaluated.
*
* This LSM hook is called when userspace signals the kernel to check a file
* for execution through the execveat syscall with the AT_EXECVE_CHECK flag.
* The hook triggers IPE policy evaluation on the script file and returns
* the policy decision to userspace. The userspace program receives the
* return code and can decide whether to proceed with script execution.
*
* Return:
* * %0 - Success
* * %-EACCES - Did not pass IPE policy
*/
int ipe_bprm_creds_for_exec(struct linux_binprm *bprm)
{
struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT;
if (!bprm->is_check)
return 0;
ipe_build_eval_ctx(&ctx, bprm->file, IPE_OP_EXEC,
IPE_HOOK_BPRM_CREDS_FOR_EXEC);
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:
case READING_MODULE_COMPRESSED:
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_obj(*info);
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_IPE_PROP_FS_VERITY_BUILTIN_SIG */
+55
View File
@@ -0,0 +1,55 @@
/* 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_BPRM_CREDS_FOR_EXEC,
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_bprm_creds_for_exec(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 */
+100
View File
@@ -0,0 +1,100 @@
// 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(bprm_creds_for_exec, ipe_bprm_creds_for_exec),
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) = {
.id = &ipe_lsmid,
.init = ipe_init,
.blobs = &ipe_blobs,
.initcall_fs = ipe_init_securityfs,
};
+28
View File
@@ -0,0 +1,28 @@
/* 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 */
int ipe_init_securityfs(void);
#endif /* _IPE_H */
+244
View File
@@ -0,0 +1,244 @@
// 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 - Success
* * %-ENOENT - Policy was deleted while updating
* * %-EINVAL - Policy name mismatch
* * %-ESTALE - Policy version too old
*/
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 = -ESTALE;
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
* * %-ENOKEY - Policy signing key not found
* * %-EKEYREJECTED - Policy signature verification failed
*/
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_obj(*new);
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,
#ifdef CONFIG_IPE_POLICY_SIG_SECONDARY_KEYRING
VERIFY_USE_SECONDARY_KEYRING,
#else
NULL,
#endif
VERIFYING_UNSPECIFIED_SIGNATURE,
set_pkcs7_data, new);
#ifdef CONFIG_IPE_POLICY_SIG_PLATFORM_KEYRING
if (rc == -ENOKEY || rc == -EKEYREJECTED)
rc = verify_pkcs7_signature(NULL, 0, new->pkcs7, pkcs7len,
VERIFY_USE_PLATFORM_KEYRING,
VERIFYING_UNSPECIFIED_SIGNATURE,
set_pkcs7_data, new);
#endif
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;
}
+98
View File
@@ -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 */
+490
View File
@@ -0,0 +1,490 @@
// 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"
#include "audit.h"
#define MAX_VERSION_SIZE ARRAY_SIZE("65535.65535.65535")
/**
* struct ipefs_file - defines a file in securityfs.
*
* @name: file name inside the policy subdirectory
* @access: file permissions
* @fops: &file_operations specific to this file
*/
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 - Success
* * %-EPERM - Insufficient permission
* * %-ENOMEM - Out of memory (OOM)
* * %-ENOENT - Policy was deleted while updating
* * %-EINVAL - Policy name mismatch
* * %-ESTALE - Policy version too old
*/
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)) {
rc = -EPERM;
goto out;
}
copy = memdup_user(data, len);
if (IS_ERR(copy)) {
rc = PTR_ERR(copy);
copy = NULL;
goto out;
}
root = d_inode(f->f_path.dentry->d_parent);
inode_lock(root);
rc = ipe_update_policy(root, NULL, 0, copy, len);
inode_unlock(root);
out:
kfree(copy);
if (rc) {
ipe_audit_policy_load(ERR_PTR(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_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_remove(policyfs);
return rc;
}
+559
View File
@@ -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_obj(*p);
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_obj(*p);
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_obj(*r);
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;
}
+11
View File
@@ -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 */
+297
View File
@@ -0,0 +1,297 @@
// 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);