IPE's interpretation of the what the user trusts is accomplished through its policy. IPE's design is to not provide support for a single trust provider, but to support multiple providers to enable the end-user to choose the best one to seek their needs. This requires the policy to be rather flexible and modular so that integrity providers, like fs-verity, dm-verity, dm-integrity, or some other system, can plug into the policy with minimal code changes, and IPE can Signed-off-by: Deven Bowers <deven.desai@xxxxxxxxxxxxxxxxxxx> --- v2: + Split evaluation loop, access control hooks, and evaluation loop from policy parser and userspace interface to pass mailing list character limit v3: + Move policy load and activation audit event to 03/12 + Fix a potential panic when a policy failed to load. + use pr_warn for a failure to parse instead of an audit record + Remove comments from headers + Add lockdep assertions to ipe_update_active_policy and ipe_activate_policy + Fix up warnings with checkpatch --strict + Use file_ns_capable for CAP_MAC_ADMIN for securityfs nodes. + Use memdup_user instead of kzalloc+simple_write_to_buffer. + Remove strict_parse command line parameter, as it is added by the sysctl command line. + Prefix extern variables with ipe_ v4: + Remove securityfs to reverse-dependency + Add SHA1 reverse dependency. + Add versioning scheme for IPE properties, and associated interface to query the versioning scheme. + Cause a parser to always return an error on unknown syntax. + Remove strict_parse option + Change active_policy interface from sysctl, to securityfs, and change scheme. v5: + Cause an error if a default action is not defined for each operaiton. + Minor function renames v6: + No changes v7: + Further split parser and userspace interface into two separate commits, for easier review. + Refactor policy parser to make code cleaner via introducing a more modular design, for easier extension of policy, and easier review. v8: + remove unnecessary pr_info emission on parser loading + add explicit newline to the pr_err emitted when a parser fails to load. --- include/asm-generic/vmlinux.lds.h | 16 + security/ipe/Makefile | 6 + security/ipe/ipe.c | 61 ++ security/ipe/ipe.h | 5 + security/ipe/ipe_parser.h | 59 ++ security/ipe/modules.c | 109 +++ security/ipe/modules.h | 17 + security/ipe/modules/ipe_module.h | 33 + security/ipe/parsers.c | 143 ++++ security/ipe/parsers/Makefile | 12 + security/ipe/parsers/default.c | 106 +++ security/ipe/parsers/policy_header.c | 126 ++++ security/ipe/policy.c | 946 +++++++++++++++++++++++++++ security/ipe/policy.h | 97 +++ 14 files changed, 1736 insertions(+) create mode 100644 security/ipe/ipe_parser.h create mode 100644 security/ipe/modules.c create mode 100644 security/ipe/modules.h create mode 100644 security/ipe/modules/ipe_module.h create mode 100644 security/ipe/parsers.c create mode 100644 security/ipe/parsers/Makefile create mode 100644 security/ipe/parsers/default.c create mode 100644 security/ipe/parsers/policy_header.c create mode 100644 security/ipe/policy.c create mode 100644 security/ipe/policy.h diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 7515a465ec03..8549b98d0cf6 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -284,6 +284,20 @@ #define EARLY_LSM_TABLE() #endif +#ifdef CONFIG_SECURITY_IPE +#define IPE_PARSER_TABLE() . = ALIGN(8); \ + __start_ipe_parsers = .; \ + KEEP(*(.ipe_parsers)) \ + __end_ipe_parsers = .; +#define IPE_MODULE_TABLE() . = ALIGN(8); \ + __start_ipe_modules = .; \ + KEEP(*(.ipe_modules)) \ + __end_ipe_modules = .; +#else +#define IPE_PARSER_TABLE() +#define IPE_MODULE_TABLE() +#endif + #define ___OF_TABLE(cfg, name) _OF_TABLE_##cfg(name) #define __OF_TABLE(cfg, name) ___OF_TABLE(cfg, name) #define OF_TABLE(cfg, name) __OF_TABLE(IS_ENABLED(cfg), name) @@ -436,6 +450,8 @@ KEEP(*(__tracepoints_ptrs)) /* Tracepoints: pointer array */ \ __stop___tracepoints_ptrs = .; \ *(__tracepoints_strings)/* Tracepoints: strings */ \ + IPE_PARSER_TABLE() \ + IPE_MODULE_TABLE() \ } \ \ .rodata1 : AT(ADDR(.rodata1) - LOAD_OFFSET) { \ diff --git a/security/ipe/Makefile b/security/ipe/Makefile index ba3df729e252..9a97efd8a190 100644 --- a/security/ipe/Makefile +++ b/security/ipe/Makefile @@ -5,7 +5,13 @@ # Makefile for building the IPE module as part of the kernel tree. # +ccflags-y := -I$(srctree)/security/ipe/modules + obj-$(CONFIG_SECURITY_IPE) += \ ctx.o \ hooks.o \ ipe.o \ + modules.o \ + parsers/ \ + parsers.o \ + policy.o \ diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c index 8a1e0b1c7240..3f387f4ab38a 100644 --- a/security/ipe/ipe.c +++ b/security/ipe/ipe.c @@ -6,6 +6,9 @@ #include "ipe.h" #include "ctx.h" #include "hooks.h" +#include "ipe_parser.h" +#include "modules/ipe_module.h" +#include "modules.h" #include <linux/fs.h> #include <linux/sched.h> @@ -40,6 +43,56 @@ struct ipe_context __rcu **ipe_tsk_ctx(struct task_struct *tsk) return tsk->security + ipe_blobs.lbs_task; } +/* + * load_parsers: Load all the parsers compiled into IPE. This needs + * to be called prior to the boot policy being loaded. + * + * Return: + * 0 - OK + * !0 - Error + */ +static int load_parsers(void) +{ + int rc = 0; + struct ipe_parser *parser; + + for (parser = __start_ipe_parsers; parser < __end_ipe_parsers; ++parser) { + rc = ipe_register_parser(parser); + if (rc) { + pr_err("failed to initialize '%s'", parser->first_token); + return rc; + } + + pr_info("initialized parser module '%s'", parser->first_token); + } + + return 0; +} + +/** + * load_modules: Load all the modules compiled into IPE. This needs + * to be called prior to the boot policy being loaded. + * + * Return: + * 0 - OK + * !0 - Error + */ +static int load_modules(void) +{ + int rc = 0; + struct ipe_module *m; + + for (m = __start_ipe_modules; m < __end_ipe_modules; ++m) { + rc = ipe_register_module(m); + if (rc) { + pr_err("failed to initialize '%s'\n", m->name); + return rc; + } + } + + return 0; +} + /** * ipe_init: Entry point of IPE. * @@ -57,6 +110,14 @@ static int __init ipe_init(void) { int rc = 0; + rc = load_parsers(); + if (rc) + return rc; + + rc = load_modules(); + if (rc) + return rc; + rc = ipe_init_ctx(); if (rc) return rc; diff --git a/security/ipe/ipe.h b/security/ipe/ipe.h index 152ae28f3cdd..417b474e0543 100644 --- a/security/ipe/ipe.h +++ b/security/ipe/ipe.h @@ -9,9 +9,14 @@ #define pr_fmt(fmt) "IPE " fmt "\n" #include "ctx.h" +#include "ipe_parser.h" +#include "modules/ipe_module.h" #include <linux/types.h> #include <linux/sched.h> #include <linux/lsm_hooks.h> +extern struct ipe_parser __start_ipe_parsers[], __end_ipe_parsers[]; +extern struct ipe_module __start_ipe_modules[], __end_ipe_modules[]; + #endif /* IPE_H */ diff --git a/security/ipe/ipe_parser.h b/security/ipe/ipe_parser.h new file mode 100644 index 000000000000..f7c5c11bde44 --- /dev/null +++ b/security/ipe/ipe_parser.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ +#ifndef IPE_PARSER_H +#define IPE_PARSER_H + +#include "policy.h" + +#include <linux/list.h> +#include <linux/types.h> + +/* + * Struct used to define internal parsers that effect the policy, + * but do not belong as policy modules, as they are not used to make + * decisions in the event loop, and only effect the internal workings + * of IPE. + * + * These structures are used in pass2, and policy deallocation. + */ +struct ipe_parser { + u8 version; + const char *first_token; + + int (*parse)(const struct ipe_policy_line *line, + struct ipe_parsed_policy *pol); + int (*free)(struct ipe_parsed_policy *pol); + int (*validate)(const struct ipe_parsed_policy *pol); +}; + +int ipe_parse_op(const struct ipe_policy_token *tok, + enum ipe_operation *op); + +int ipe_parse_action(const struct ipe_policy_token *tok, + enum ipe_action *action); + +/* + * Optional struct to make structured parsers easier. + */ +struct ipe_token_parser { + const char *key; + int (*parse_token)(const struct ipe_policy_token *t, + struct ipe_parsed_policy *p); +}; + +const struct ipe_parser *ipe_lookup_parser(const char *first_token); + +int ipe_for_each_parser(int (*view)(const struct ipe_parser *parser, + void *ctx), + void *ctx); + +int ipe_register_parser(struct ipe_parser *p); + +#define IPE_PARSER(parser) \ + static struct ipe_parser __ipe_parser_##parser \ + __used __section(".ipe_parsers") \ + __aligned(sizeof(unsigned long)) + +#endif /* IPE_PARSER_MODULE_H */ diff --git a/security/ipe/modules.c b/security/ipe/modules.c new file mode 100644 index 000000000000..fb100c14cce5 --- /dev/null +++ b/security/ipe/modules.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe.h" +#include "modules.h" + +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/rbtree.h> + +static struct rb_root module_root = RB_ROOT; + +struct module_container { + struct rb_node node; + const struct ipe_module *mod; +}; + +/** + * cmp_node: Comparator for a node in the module lookup tree. + * @n: First node to compare + * @nn: Second node to compare + * + * Return: + * <0 - @n's key is lexigraphically before @nn. + * 0 - n's key is identical to @nn + * >0 - n's key is legxigraphically after @nn + */ +static int cmp_node(struct rb_node *n, const struct rb_node *nn) +{ + const struct module_container *c1; + const struct module_container *c2; + + c1 = container_of(n, struct module_container, node); + c2 = container_of(nn, struct module_container, node); + + return strcmp(c1->mod->name, c2->mod->name); +} + +/** + * cmp_key: Comparator to find a module in the tree by key. + * @key: Supplies a pointer to a null-terminated string key + * @n: Node to compare @key against + * + * Return: + * <0 - Desired node is to the left of @n + * 0 - @n is the desired node + * >0 - Desired node is to the right of @n + */ +static int cmp_key(const void *key, const struct rb_node *n) +{ + struct module_container *mod; + + mod = container_of(n, struct module_container, node); + + return strcmp((const char *)key, mod->mod->name); +} + +/** + * ipe_lookup_module: Attempt to find a ipe_pmodule structure by @key. + * @key: The key to look for in the tree. + * + * Return: + * !NULL - OK + * NULL - No property exists under @key + */ +const struct ipe_module *ipe_lookup_module(const char *key) +{ + struct rb_node *n; + + n = rb_find(key, &module_root, cmp_key); + if (!n) + return NULL; + + return container_of(n, struct module_container, node)->mod; +} + +/** + * ipe_register_module: Register a policy module to be used in IPE's policy. + * @m: Module to register. + * + * This function allows parsers (policy constructs that represent integrations + * with other subsystems, to be leveraged in rules) to be leveraged in IPE policy. + * This must be called prior to any policies being loaded. + * + * Return: + * 0 - OK + * !0 - Error + */ +int ipe_register_module(struct ipe_module *m) +{ + struct rb_node *n = NULL; + struct module_container *c = NULL; + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return -ENOMEM; + + c->mod = m; + + n = rb_find_add(&c->node, &module_root, cmp_node); + if (n) { + kfree(c); + return -EEXIST; + } + + return 0; +} diff --git a/security/ipe/modules.h b/security/ipe/modules.h new file mode 100644 index 000000000000..7b897bdd870b --- /dev/null +++ b/security/ipe/modules.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ +#ifndef IPE_MODULES_H +#define IPE_MODULES_H + +#include "ipe.h" +#include "ipe_module.h" + +#include <linux/types.h> +#include <linux/rbtree.h> + +const struct ipe_module *ipe_lookup_module(const char *key); +int ipe_register_module(struct ipe_module *m); + +#endif /* IPE_MODULES_H */ diff --git a/security/ipe/modules/ipe_module.h b/security/ipe/modules/ipe_module.h new file mode 100644 index 000000000000..c4e98b548df3 --- /dev/null +++ b/security/ipe/modules/ipe_module.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ +#ifndef IPE_MODULE_H +#define IPE_MODULE_H + +#include <linux/types.h> + +/** + * ipe_module: definition of an extensible module for IPE properties. + * These structures are used to implement 'key=value' pairs + * in IPE policy, which will be evaluated on every IPE policy + * evaluation. + * + * Integrity mechanisms should be define as a module, and modules + * should manage their own dependencies via KConfig. @name is both + * the key half of the key=value pair in the policy, and the unique + * identifier for the module. + */ +struct ipe_module { + const char *const name; /* required */ + u16 version; /* required */ + int (*parse)(const char *valstr, void **value); /* required */ + void (*free)(void **value); /* optional */ +}; + +#define IPE_MODULE(parser) \ + static struct ipe_module __ipe_module_##parser \ + __used __section(".ipe_modules") \ + __aligned(sizeof(unsigned long)) + +#endif /* IPE_MODULE_H */ diff --git a/security/ipe/parsers.c b/security/ipe/parsers.c new file mode 100644 index 000000000000..87006270b9cd --- /dev/null +++ b/security/ipe/parsers.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "policy.h" +#include "ipe_parser.h" + +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/rbtree.h> + +/* + * locking unnecessary, this is called by lsm_init, + * and is read-only after that point. + */ +static struct rb_root ipe_parser_root = RB_ROOT; + +struct parser_container { + struct rb_node node; + const struct ipe_parser *parser; +}; + +/** + * cmp_key: Comparator for the nodes within the parser tree by key + * @key: Supplies a the key to evaluate nodes against + * @n: Supplies a pointer to the node to compare. + * + * Return: + * <0 - @key is to the left of @n + * 0 - @key identifies @n + * >0 - @key is to the right of @n + */ +static int cmp_key(const void *key, const struct rb_node *n) +{ + const struct parser_container *node; + + node = container_of(n, struct parser_container, node); + + return strcmp((const char *)key, node->parser->first_token); +} + +/** + * cmp_node: Comparator for the nodes within the parser tree + * @n: Supplies a pointer to the node to compare + * @nn: Supplies a pointer to the another node to compare. + * + * Return: + * <0 - @n is lexigraphically before @nn + * 0 - @n is identical @nn + * >0 - @n is lexigraphically after @nn + */ +static int cmp_node(struct rb_node *n, const struct rb_node *nn) +{ + const struct parser_container *c1; + const struct parser_container *c2; + + c1 = container_of(n, struct parser_container, node); + c2 = container_of(nn, struct parser_container, node); + + return strcmp(c1->parser->first_token, c2->parser->first_token); +} + +/** + * ipe_lookup_parser: Attempt to find a ipe_property structure by @first_token. + * @first_token: The key to look for in the tree. + * + * Return: + * !NULL - OK + * NULL - No property exists under @key + */ +const struct ipe_parser *ipe_lookup_parser(const char *first_token) +{ + struct rb_node *n; + + n = rb_find(first_token, &ipe_parser_root, cmp_key); + if (!n) + return NULL; + + return container_of(n, struct parser_container, node)->parser; +} + +/** + * ipe_for_each_parser: Iterate over all currently-registered parsers + * calling @fn on the values, and providing @view @ctx. + * @view: The function to call for each property. This is given the property + * structure as the first argument, and @ctx as the second. + * @ctx: caller-specified context that is passed to the function. Can be NULL. + * + * Return: + * 0 - OK + * !0 - Proper errno as returned by @view. + */ +int ipe_for_each_parser(int (*view)(const struct ipe_parser *parser, + void *ctx), + void *ctx) +{ + int rc = 0; + struct rb_node *node; + struct parser_container *val; + + for (node = rb_first(&ipe_parser_root); node; node = rb_next(node)) { + val = container_of(node, struct parser_container, node); + + rc = view(val->parser, ctx); + if (rc) + return rc; + } + + return rc; +} + +/** + * ipe_register_parser: Register a parser to be used in IPE's policy. + * @p: Parser to register. + * + * This function allows parsers (policy constructs that effect IPE's + * internal functionality) to be leveraged in IPE policy. This must + * be called prior to any policies being loaded. + * + * Return: + * 0 - OK + * !0 - Error + */ +int ipe_register_parser(struct ipe_parser *p) +{ + struct rb_node *n = NULL; + struct parser_container *c = NULL; + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return -ENOMEM; + + c->parser = p; + + n = rb_find_add(&c->node, &ipe_parser_root, cmp_node); + if (n) { + kfree(c); + return -EEXIST; + } + + return 0; +} diff --git a/security/ipe/parsers/Makefile b/security/ipe/parsers/Makefile new file mode 100644 index 000000000000..1a19a094724f --- /dev/null +++ b/security/ipe/parsers/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) Microsoft Corporation. All rights reserved. +# +# Makefile for building the IPE module as part of the kernel tree. +# + +ccflags-y := -I$(srctree)/security/ipe + +obj-$(CONFIG_SECURITY_IPE) += \ + default.o \ + policy_header.o \ diff --git a/security/ipe/parsers/default.c b/security/ipe/parsers/default.c new file mode 100644 index 000000000000..30181d2cc4ed --- /dev/null +++ b/security/ipe/parsers/default.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/errno.h> +#include <linux/string.h> + +#include "ipe_parser.h" + +static int set_op_default(enum ipe_operation op, enum ipe_action act, + struct ipe_parsed_policy *pol) +{ + size_t i, remap_len; + const enum ipe_operation *remap; + + if (!ipe_is_op_alias(op, &remap, &remap_len)) { + if (pol->rules[op].default_action != ipe_action_max) + return -EBADMSG; + + pol->rules[op].default_action = act; + return 0; + } + + for (i = 0; i < remap_len; ++i) { + if (pol->rules[remap[i]].default_action != ipe_action_max) + return -EBADMSG; + + pol->rules[remap[i]].default_action = act; + } + + return 0; +} + +static int parse_default(const struct ipe_policy_line *line, + struct ipe_parsed_policy *pol) +{ + int rc = 0; + size_t idx = 0; + struct ipe_policy_token *tok = NULL; + enum ipe_operation op = ipe_operation_max; + enum ipe_action act = ipe_action_max; + + list_for_each_entry(tok, &line->tokens, next) { + switch (idx) { + case 0: + if (strcmp("DEFAULT", tok->key) || tok->value) + return -EBADMSG; + break; + case 1: + /* schema 1 - operation, followed by action */ + rc = ipe_parse_op(tok, &op); + if (!rc) { + ++idx; + continue; + } + + if (pol->global_default != ipe_action_max) + return -EBADMSG; + + /* schema 2 - action */ + rc = ipe_parse_action(tok, &pol->global_default); + if (!rc) + return rc; + + return -EBADMSG; + case 2: + rc = ipe_parse_action(tok, &act); + if (rc) + return rc; + + return set_op_default(op, act, pol); + default: + return -EBADMSG; + } + ++idx; + } + + /* met no schema */ + return -EBADMSG; +} + +static int validate_defaults(const struct ipe_parsed_policy *p) +{ + size_t i = 0; + + if (p->global_default != ipe_action_max) + return 0; + + for (i = 0; i < ARRAY_SIZE(p->rules); ++i) { + if (p->rules[i].default_action == ipe_action_max) + return -EBADMSG; + } + + return 0; +} + +IPE_PARSER(default_decl) = { + .first_token = "DEFAULT", + .version = 1, + .parse = parse_default, + .free = NULL, + .validate = validate_defaults, +}; diff --git a/security/ipe/parsers/policy_header.c b/security/ipe/parsers/policy_header.c new file mode 100644 index 000000000000..4d3c1a42c915 --- /dev/null +++ b/security/ipe/parsers/policy_header.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe.h" +#include "ipe_parser.h" + +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/errno.h> +#include <linux/string.h> + +#include "ipe_parser.h" + +static int parse_name(const struct ipe_policy_token *t, + struct ipe_parsed_policy *p) +{ + if (p->name) + return -EBADMSG; + + p->name = kstrdup_const(t->value, GFP_KERNEL); + if (!p->name) + return -ENOMEM; + + return 0; +} + +static int parse_ver(const struct ipe_policy_token *t, + struct ipe_parsed_policy *p) +{ + int rc = 0; + char *dup = NULL; + char *dup2 = NULL; + char *token = NULL; + size_t sep_count = 0; + u16 *const cv[] = { &p->version.major, &p->version.minor, &p->version.rev }; + + dup = kstrdup(t->value, GFP_KERNEL); + if (!dup) { + rc = -ENOMEM; + goto err; + } + + dup2 = dup; + + while ((token = strsep(&dup, ".\n")) != NULL) { + /* prevent overflow */ + if (sep_count >= ARRAY_SIZE(cv)) { + rc = -EBADMSG; + goto err; + } + + rc = kstrtou16(token, 10, cv[sep_count]); + if (rc) + goto err; + + ++sep_count; + } + + /* prevent underflow */ + if (sep_count != ARRAY_SIZE(cv)) + rc = -EBADMSG; + +err: + kfree(dup2); + return rc; +} + +static const struct ipe_token_parser parsers[] = { + { .key = "policy_name", .parse_token = parse_name }, + { .key = "policy_version", .parse_token = parse_ver }, +}; + +static int parse_policy_hdr(const struct ipe_policy_line *line, + struct ipe_parsed_policy *pol) +{ + int rc = 0; + size_t idx = 0; + struct ipe_policy_token *tok = NULL; + const struct ipe_token_parser *p = NULL; + + list_for_each_entry(tok, &line->tokens, next) { + if (!tok->value || idx >= sizeof(parsers)) { + rc = -EBADMSG; + goto err; + } + + p = &parsers[idx]; + + if (strcmp(p->key, tok->key)) { + rc = -EBADMSG; + goto err; + } + + rc = p->parse_token(tok, pol); + if (rc) + goto err; + + ++idx; + } + + return 0; + +err: + return rc; +} + +static int free_policy_hdr(struct ipe_parsed_policy *pol) +{ + kfree(pol->name); + return 0; +} + +static int validate_policy_hdr(const struct ipe_parsed_policy *p) +{ + return !p->name ? -EBADMSG : 0; +} + +IPE_PARSER(policy_header) = { + .first_token = "policy_name", + .version = 1, + .parse = parse_policy_hdr, + .free = free_policy_hdr, + .validate = validate_policy_hdr, +}; diff --git a/security/ipe/policy.c b/security/ipe/policy.c new file mode 100644 index 000000000000..6381fa7c2ec6 --- /dev/null +++ b/security/ipe/policy.c @@ -0,0 +1,946 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe.h" +#include "policy.h" +#include "ipe_parser.h" +#include "modules.h" + +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/parser.h> +#include <linux/verification.h> + +#define START_COMMENT '#' +#define KEYVAL_DELIMIT '=' + +static inline bool is_quote(char ch) +{ + return ch == '\'' || ch == '\"'; +} + +/** + * is_key_char: Determine whether @ch is an acceptable character for a + * key type + * @ch: Supplies the character to evaluate. + * + * Return: + * true - Character is acceptable. + * false - Character is not acceptable. + */ +static inline bool is_key_char(char ch) +{ + return isalnum(ch) || ch == '_'; +} + +/** + * is_val_char: Determine whether @ch is an acceptable character for a + * value type + * @ch: Supplies the character to evaluate. + * + * Return: + * true - Character is acceptable. + * false - Character is not acceptable. + */ +static inline bool is_val_char(char ch) +{ + return isgraph(ch) || ch == ' ' || ch == '\t'; +} + +/** + * free_parser: Callback to invoke, freeing data allocated by parsers. + * @parser: parser to free data. + * @ctx: ctx object passed to ipe_for_each_parser. + * + * This function is intended to be used with ipe_for_each_parser only. + * + * Return: + * 0 - Always + */ +static int free_parser(const struct ipe_parser *parser, void *ctx) +{ + struct ipe_parsed_policy *pol = ctx; + + if (parser->free) + parser->free(pol); + + return 0; +} + +/** + * free_rule: free an ipe_rule. + * @r: Supplies the rule to free. + * + * This function is safe to call if @r is NULL or ERR_PTR. + */ +static void free_rule(struct ipe_rule *r) +{ + struct ipe_policy_mod *p, *t; + + if (IS_ERR_OR_NULL(r)) + return; + + list_for_each_entry_safe(p, t, &r->modules, next) { + if (p->mod->free) + p->mod->free(&p->mod_value); + + kfree(p); + } + + kfree(r); +} + +static void free_parsed_policy(struct ipe_parsed_policy *p) +{ + size_t i = 0; + struct ipe_rule *pp, *t; + + 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) + free_rule(pp); + + (void)ipe_for_each_parser(free_parser, p); + kfree(p); +} + +/** + * free_parsed_line: free a single parsed line of tokens. + * @line: Supplies the line to free. + * + * This function is safe to call if @line is NULL or ERR_PTR. + */ +static void free_parsed_line(struct ipe_policy_line *line) +{ + struct ipe_policy_token *p, *t; + + if (IS_ERR_OR_NULL(line)) + return; + + list_for_each_entry_safe(p, t, &line->tokens, next) + kfree(p); +} + +/** + * free_parsed_text: free a 2D list representing a tokenized policy. + * @parsed: Supplies the policy to free. + * + * This function is safe to call if @parsed is NULL or ERR_PTR. + */ +static void free_parsed_text(struct list_head *parsed) +{ + struct ipe_policy_line *p, *t; + + if (IS_ERR_OR_NULL(parsed)) + return; + + list_for_each_entry_safe(p, t, parsed, next) + free_parsed_line(p); +} + +/** + * trim_quotes: Edit @str to remove a single instance of a trailing and + * leading quotes. + * @str: Supplies the string to edit. + * + * If the string is not quoted, @str will be returned. This function is + * safe to call if @str is NULL. + * + * Return: + * !0 - OK + * ERR_PTR(-EBADMSG) - Quote mismatch. + */ +static char *trim_quotes(char *str) +{ + char s; + size_t len; + + if (!str) + return str; + + s = *str; + + if (is_quote(s)) { + len = strlen(str) - 1; + + if (str[len] != s) + return ERR_PTR(-EBADMSG); + + str[len] = '\0'; + ++str; + } + + return str; +} + +/** + * parse_token: Parse a string into a proper token representation. + * @token: Supplies the token string to parse. + * + * @token will be edited destructively. Pass a copy if you wish to retain + * the state of the original. + * + * This function will emit an error to pr_err when a parsing error occurs. + * + * Return: + * !0 - OK + * ERR_PTR(-EBADMSG) - An invalid character was encountered while parsing. + * ERR_PTR(-ENOMEM) - No Memory + */ +static struct ipe_policy_token *parse_token(char *token) +{ + size_t i, len = 0; + char *key = token; + char *value = NULL; + struct ipe_policy_token *local = NULL; + + len = strlen(token); + + for (i = 0; (i < len) && token[i] != KEYVAL_DELIMIT; ++i) + if (!is_key_char(token[i])) + return ERR_PTR(-EBADMSG); + + token[i] = '\0'; + ++i; + + /* there is a value */ + if (i < len) { + value = trim_quotes(&token[i]); + if (IS_ERR(value)) + return ERR_PTR(-EBADMSG); + + len = strlen(value); + + for (i = 0; i < len; ++i) + if (!is_val_char(value[i])) + return ERR_PTR(-EBADMSG); + } + + local = kzalloc(sizeof(*local), GFP_KERNEL); + if (!local) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&local->next); + local->key = key; + local->value = value; + + return local; +} + +/** + * append_token: Parse and append a token into an ipe_policy_line structure. + * @p: Supplies the ipe_policy_line structure to append to. + * @token: Supplies the token to parse and append to. + * + * @token will be edited during the parsing destructively. Pass a copy if you + * wish to retain the original. + * + * Return: + * 0 - OK + * -EBADMSG - Parsing error of @token + */ +static int append_token(struct ipe_policy_line *p, char *token) +{ + struct ipe_policy_token *t = NULL; + + t = parse_token(token); + if (IS_ERR(t)) + return PTR_ERR(t); + + list_add_tail(&t->next, &p->tokens); + + return 0; +} + +/** + * alloc_line: Allocate an ipe_policy_line structure. + * + * Return: + * !0 - OK + * -EBADMSG - Parsing error of @token + */ +static struct ipe_policy_line *alloc_line(void) +{ + struct ipe_policy_line *l = NULL; + + l = kzalloc(sizeof(*l), GFP_KERNEL); + if (!l) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&l->next); + INIT_LIST_HEAD(&l->tokens); + + return l; +} + +/** + * insert_token: Append a token to @line. + * @token: Supplies the token to append to @line. + * @line: Supplies a pointer to the ipe_policy_line structure to append to. + * + * If @line is NULL, it will be allocated on behalf of the caller. + * + * Return: + * 0 - OK + * -ENOMEM - No Memory + * -EBADMSG - Parsing error of @token + */ +static int insert_token(char *token, struct ipe_policy_line **line) +{ + int rc = 0; + struct ipe_policy_line *local = *line; + + if (!local) { + local = alloc_line(); + if (IS_ERR(local)) + return PTR_ERR(local); + + *line = local; + } + + rc = append_token(local, token); + if (rc) + return rc; + + return 0; +} + +/** + * ipe_tokenize_line: Parse a line of text into a list of token structures. + * @line: Supplies the line to parse. + * + * The final result can be NULL, which represents no tokens were parsed. + * + * Return: + * !0 - OK + * NULL - OK, no tokens were parsed. + * ERR_PTR(-EBADMSG) - Invalid policy syntax + * ERR_PTR(-ENOMEM) - No Memory + */ +static struct ipe_policy_line *tokenize_line(char *line) +{ + int rc = 0; + size_t i = 0; + size_t len = 0; + char *tok = NULL; + char quote = '\0'; + struct ipe_policy_line *p = NULL; + + /* nullterm guaranteed by strsep */ + len = strlen(line); + + for (i = 0; i < len; ++i) { + if (quote == '\0' && is_quote(line[i])) { + quote = line[i]; + continue; + } + + if (quote != '\0' && line[i] == quote) { + quote = '\0'; + continue; + } + + if (quote == '\0' && line[i] == START_COMMENT) { + tok = NULL; + break; + } + + if (isgraph(line[i]) && !tok) + tok = &line[i]; + + if (quote == '\0' && isspace(line[i])) { + line[i] = '\0'; + + if (!tok) + continue; + + rc = insert_token(tok, &p); + if (rc) + goto err; + + tok = NULL; + } + } + + if (quote != '\0') { + rc = -EBADMSG; + goto err; + } + + if (tok) { + rc = insert_token(tok, &p); + if (rc) + goto err; + } + + return p; + +err: + free_parsed_line(p); + return ERR_PTR(rc); +} + +/** + * parse_pass1: parse @policy into a 2D list, representing tokens on each line. + * @policy: Supplies the policy to parse. Must be nullterminated, and is + * edited. + * + * In pass1 of the parser, the policy is tokenized. Minor structure checks + * are done (mismatching quotes, invalid characters). + * + * Caller must maintain the lifetime of @policy while the return value is + * alive. + * + * Return: + * !0 - OK + * ERR_PTR(-ENOMEM) - Out of Memory + * ERR_PTR(-EBADMSG) - Parsing Error + */ +static int parse_pass1(char *policy, struct list_head *tokens) +{ + int rc = 0; + char *p = NULL; + + while ((p = strsep(&policy, "\n\0")) != NULL) { + struct ipe_policy_line *t = NULL; + + t = tokenize_line(p); + if (IS_ERR(t)) { + rc = PTR_ERR(t); + goto err_free_parsed; + } + + if (!t) + continue; + + list_add_tail(&t->next, tokens); + } + + return 0; + +err_free_parsed: + free_parsed_text(tokens); + return rc; +} + +/** + * parse_pass2: Take the 2D list of tokens generated from pass1, and transform + * it into a partial ipe_policy. + * @parsed: Supplies the list of tokens generated from pass1. + * @p: Policy to manipulate with parsed tokens. + * + * This function is where various declarations and references are parsed into + * policy. All declarations and references required to parse rules should be + * done here as a parser, and then in pass3 these can be utilized. + * + * Return: + * !0 - OK + * -EBADMSG - Syntax Parsing Errors + * -ENOENT - No handler for a token. + * -ENOMEM - Out of memory + */ +static int parse_pass2(struct list_head *parsed, struct ipe_parsed_policy *pol) +{ + int rc = 0; + const struct ipe_parser *p = NULL; + struct ipe_policy_line *line = NULL; + const struct ipe_policy_token *token = NULL; + + list_for_each_entry(line, parsed, next) { + token = list_first_entry(&line->tokens, struct ipe_policy_token, next); + p = ipe_lookup_parser(token->key); + if (!p) + continue; + + rc = p->parse(line, pol); + if (rc) + return rc; + + line->consumed = true; + } + + return rc; +} + +/** + * ipe_parse_op: parse a token to an operation value. + * @tok: Token to parse + * @op: Operation Parsed. + * + * Return: + * 0 - OK + * -EINVAL - Invalid key or value. + */ +int ipe_parse_op(const struct ipe_policy_token *tok, + enum ipe_operation *op) +{ + substring_t match[MAX_OPT_ARGS] = { 0 }; + const match_table_t ops = { + { ipe_op_alias_max, NULL }, + }; + + if (strcmp(tok->key, "op") || !tok->value) + return -EINVAL; + + *op = match_token((char *)tok->value, ops, match); + if ((*op) == (int)ipe_op_alias_max) + return -ENOENT; + + return 0; +} + +/** + * ipe_parse_action: parse a token to an operation value. + * @tok: Token to parse + * @action: action parsed. + * + * Return: + * 0 - OK + * -EINVAL - Invalid key or value. + */ +int ipe_parse_action(const struct ipe_policy_token *tok, + enum ipe_action *action) +{ + substring_t match[MAX_OPT_ARGS] = { 0 }; + const match_table_t actions = { + { ipe_action_allow, "ALLOW" }, + { ipe_action_deny, "DENY" }, + { ipe_action_max, NULL }, + }; + + if (strcmp(tok->key, "action") || !tok->value) + return -EINVAL; + + *action = match_token((char *)tok->value, actions, match); + + if (*action == ipe_action_max) + return -EINVAL; + + return 0; +} + +/** + * parse_mod_to_rule: Parse a module token and append the values to the + * provided rule. + * @t: Supplies the token to parse. + * @r: Supplies the rule to modify with the result. + * + * Return: + * 0 - OK + * -ENOENT - No such module to handle @t. + * -ENOMEM - No memory. + * Others - Module defined errors. + */ +static int parse_mod_to_rule(const struct ipe_policy_token *t, struct ipe_rule *r) +{ + int rc = 0; + struct ipe_policy_mod *p = NULL; + const struct ipe_module *m = NULL; + + m = ipe_lookup_module(t->key); + if (IS_ERR_OR_NULL(m)) { + rc = (m) ? PTR_ERR(m) : -ENOENT; + goto err; + } + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) { + rc = -ENOMEM; + goto err; + } + INIT_LIST_HEAD(&p->next); + p->mod = m; + + rc = m->parse(t->value, &p->mod_value); + if (rc) + goto err2; + + list_add_tail(&p->next, &r->modules); + return 0; +err2: + kfree(p); +err: + return rc; +} + +/** + * parse_rule: Parse a policy line into an ipe_rule structure. + * @line: Supplies the line to parse. + * + * Return: + * Valid ipe_rule - OK + * ERR_PTR(-ENOMEM) - Out of Memory + * ERR_PTR(-ENOENT) - No such module to handle a token + * ERR_PTR(-EINVAL) - An unacceptable value has been encountered. + * ERR_PTR(...) - Module defined errors. + */ +static struct ipe_rule *parse_rule(const struct ipe_policy_line *line) +{ + int rc = 0; + struct ipe_rule *r = NULL; + const struct list_head *node = NULL; + + r = kzalloc(sizeof(*r), GFP_KERNEL); + if (!r) { + rc = -ENOMEM; + goto err; + } + + INIT_LIST_HEAD(&r->next); + INIT_LIST_HEAD(&r->modules); + r->op = (int)ipe_op_alias_max; + r->action = ipe_action_max; + + list_for_each(node, &line->tokens) { + const struct ipe_policy_token *token = NULL; + + token = container_of(node, struct ipe_policy_token, next); + + if (list_is_first(node, &line->tokens)) { + enum ipe_operation op; + + rc = ipe_parse_op(token, &op); + if (rc) + goto err; + + r->op = op; + continue; + } + + if (list_is_last(node, &line->tokens)) { + enum ipe_action action; + + rc = ipe_parse_action(token, &action); + if (rc) + goto err; + + r->action = action; + continue; + } + + rc = parse_mod_to_rule(token, r); + if (rc) + goto err; + } + + if (r->action == ipe_action_max || r->op == (int)ipe_op_alias_max) { + rc = -EBADMSG; + goto err; + } + + return r; +err: + free_rule(r); + return ERR_PTR(rc); +} + +/** + * parse_pass3: Take the partially parsed list of tokens from pass 1 and the + * parial policy from pass 2, and finalize the policy. + * @parsed: Supplies the tokens parsed from pass 1. + * @p: Supplies the partial policy from pass 2. + * + * This function finalizes the IPE policy by parsing all rules in the + * policy. This must occur in pass3, as in pass2, references are resolved + * that can be used in pass3. + * + * Return: + * 0 - OK + * !0 - Standard errno + */ +static int parse_pass3(struct list_head *parsed, + struct ipe_parsed_policy *p) +{ + int rc = 0; + size_t i = 0; + size_t remap_len = 0; + struct ipe_rule *rule = NULL; + struct ipe_policy_line *line = NULL; + const enum ipe_operation *remap; + + list_for_each_entry(line, parsed, next) { + if (line->consumed) + continue; + + rule = parse_rule(line); + if (IS_ERR(rule)) { + rc = PTR_ERR(rule); + goto err; + } + + if (ipe_is_op_alias(rule->op, &remap, &remap_len)) { + for (i = 0; i < remap_len; ++i) { + rule->op = remap[i]; + list_add_tail(&rule->next, &p->rules[rule->op].rules); + rule = parse_rule(line); + } + + free_rule(rule); + } else { + list_add_tail(&rule->next, &p->rules[rule->op].rules); + } + + line->consumed = true; + } + + return 0; +err: + free_rule(rule); + return rc; +} + +/** + * parser_validate: Callback to invoke, validating parsers as necessary + * @parser: parser to call to validate data. + * @ctx: ctx object passed to ipe_for_each_parser. + * + * This function is intended to be used with ipe_for_each_parser only. + * + * Return: + * 0 - OK + * !0 - Validation failed. + */ +static int parser_validate(const struct ipe_parser *parser, void *ctx) +{ + int rc = 0; + const struct ipe_parsed_policy *pol = ctx; + + if (parser->validate) + rc = parser->validate(pol); + + return rc; +} + +/** + * validate_policy: Given a policy structure that was just parsed, validate + * that all necessary fields are present, initialized + * correctly, and all lines parsed are have been consumed. + * @parsed: Supplies the policy lines that were parsed in pass1. + * @policy: Supplies the fully parsed policy. + * + * A parsed policy can be an invalid state for use (a default was undefined, + * a header was undefined) by just parsing the policy. + * + * Return: + * 0 - OK + * -EBADMSG - Policy is invalid. + */ +static int validate_policy(const struct list_head *parsed, + const struct ipe_parsed_policy *p) +{ + int rc = 0; + const struct ipe_policy_line *line = NULL; + + list_for_each_entry(line, parsed, next) { + if (!line->consumed) + return -EBADMSG; + } + + rc = ipe_for_each_parser(parser_validate, + (struct ipe_parsed_policy *)p); + + return rc; +} + +/** + * new_parsed_policy: Allocate and initialize a parsed policy to its default + * values. + * + * Return: + * !IS_ERR - OK + */ +static struct ipe_parsed_policy *new_parsed_policy(void) +{ + size_t i = 0; + struct ipe_parsed_policy *p = NULL; + struct ipe_operation_table *t = NULL; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return ERR_PTR(-ENOMEM); + + p->global_default = ipe_action_max; + + for (i = 0; i < ARRAY_SIZE(p->rules); ++i) { + t = &p->rules[i]; + + t->default_action = ipe_action_max; + INIT_LIST_HEAD(&t->rules); + } + + return p; +} + +/** + * parse_policy: Given a string, parse the string into an IPE policy + * structure. + * @p: partially filled ipe_policy structure to populate with the result. + * + * @p must have text and textlen set. + * + * Return: + * Valid ipe_policy structure - OK + * ERR_PTR(-EBADMSG) - Invalid Policy Syntax (Unrecoverable) + * ERR_PTR(-ENOMEM) - Out of Memory + */ +static int parse_policy(struct ipe_policy *p) +{ + int rc = 0; + char *dup = NULL; + LIST_HEAD(parsed); + struct ipe_parsed_policy *pp = NULL; + + if (!p->textlen) + return -EBADMSG; + + dup = kmemdup_nul(p->text, p->textlen, GFP_KERNEL); + if (!dup) + return -ENOMEM; + + pp = new_parsed_policy(); + if (IS_ERR(pp)) { + rc = PTR_ERR(pp); + goto out; + } + + rc = parse_pass1(dup, &parsed); + if (rc) + goto err; + + rc = parse_pass2(&parsed, pp); + if (rc) + goto err; + + rc = parse_pass3(&parsed, pp); + if (rc) + goto err; + + rc = validate_policy(&parsed, pp); + if (rc) + goto err; + + p->parsed = pp; + + goto out; +err: + free_parsed_policy(pp); +out: + free_parsed_text(&parsed); + kfree(dup); + + return rc; +} + +/** + * ipe_is_op_alias: Determine if @op is an alias for one or more operations + * @op: Supplies the operation to check. Should be either ipe_operation or + * ipe_op_alias. + * @map: Supplies a pointer to populate with the mapping if @op is an alias + * @size: Supplies the size of @map if @op is an alias. + * + * Return: + * true - @op is an alias + * false - @op is not an alias + */ +bool ipe_is_op_alias(int op, const enum ipe_operation **map, size_t *size) +{ + switch (op) { + default: + return false; + } +} + +/** + * ipe_put_policy: Deallocate a given IPE policy. + * @p: Supplies the policy to free. + * + * Safe to call on IS_ERR/NULL. + */ +void ipe_put_policy(struct ipe_policy *p) +{ + if (IS_ERR_OR_NULL(p) || !refcount_dec_and_test(&p->refcount)) + return; + + free_parsed_policy(p->parsed); + 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) +{ + struct ipe_policy *p = ctx; + + p->text = (const char *)data; + p->textlen = len; + + return 0; +} + +/** + * 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. + * + * The result will still need to be associated with a context via + * ipe_add_policy. + * + * Return: + * !IS_ERR - Success + */ +struct ipe_policy *ipe_new_policy(const char *text, size_t textlen, + const char *pkcs7, size_t pkcs7len) +{ + int rc = 0; + struct ipe_policy *new = NULL; + + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!new) + return ERR_PTR(-ENOMEM); + + refcount_set(&new->refcount, 1); + + 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 = kstrndup(text, textlen, GFP_KERNEL); + if (!new->text) { + rc = -ENOMEM; + goto err; + } + } + + rc = parse_policy(new); + if (rc) + goto err; + + return new; +err: + ipe_put_policy(new); + return ERR_PTR(rc); +} diff --git a/security/ipe/policy.h b/security/ipe/policy.h new file mode 100644 index 000000000000..d78788db238c --- /dev/null +++ b/security/ipe/policy.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ +#ifndef IPE_POLICY_H +#define IPE_POLICY_H + +#include <linux/list.h> +#include <linux/types.h> +#include <linux/refcount.h> + +struct ipe_policy_token { + struct list_head next; /* type: policy_token */ + + const char *key; + const char *value; +}; + +struct ipe_policy_line { + struct list_head next; /* type: policy_line */ + struct list_head tokens; /* type: policy_token */ + + bool consumed; +}; + +struct ipe_module; + +enum ipe_operation { + ipe_operation_max = 0, +}; + +/* + * Extension to ipe_operation, representing operations + * that are just one or more operations under the hood + */ +enum ipe_op_alias { + ipe_op_alias_max = ipe_operation_max, +}; + +enum ipe_action { + ipe_action_allow = 0, + ipe_action_deny, + ipe_action_max, +}; + +struct ipe_policy_mod { + const struct ipe_module *mod; + void *mod_value; + + struct list_head next; +}; + +struct ipe_rule { + enum ipe_operation op; + enum ipe_action action; + + struct list_head modules; + + struct list_head next; +}; + +struct ipe_operation_table { + struct list_head rules; + enum ipe_action default_action; +}; + +struct ipe_parsed_policy { + const char *name; + struct { + u16 major; + u16 minor; + u16 rev; + } version; + + enum ipe_action global_default; + + struct ipe_operation_table rules[ipe_operation_max]; +}; + +struct ipe_policy { + const char *pkcs7; + size_t pkcs7len; + + const char *text; + size_t textlen; + + struct ipe_parsed_policy *parsed; + + refcount_t refcount; +}; + +struct ipe_policy *ipe_new_policy(const char *text, size_t textlen, + const char *pkcs7, size_t pkcs7len); +void ipe_put_policy(struct ipe_policy *pol); +bool ipe_is_op_alias(int op, const enum ipe_operation **map, size_t *size); + +#endif /* IPE_POLICY_H */ -- 2.25.1 -- dm-devel mailing list dm-devel@xxxxxxxxxx https://listman.redhat.com/mailman/listinfo/dm-devel