Re: [PATCH RFC v11 2/19] ipe: add policy parser

Paul Moore
2023-10-24T03:52:28Z

On Oct 4, 2023 Fan Wu <wufan@linux.microsoft.com> wrote:
>
> 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.
>
> Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
> Signed-off-by: Fan Wu <wufan@linux.microsoft.com>
> ---
> 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
> operation.
> + 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.
>
> v9:
> + switch to match table to parse policy
> + remove quote syntax and KERNEL_READ operation
>
> v10:
> + Fix memory leaks in parser
> + Fix typos and change code styles
>
> v11:
> + Fix code style issues
> ---
> security/ipe/Makefile | 2 +
> security/ipe/policy.c | 101 ++++++++
> security/ipe/policy.h | 83 ++++++
> security/ipe/policy_parser.c | 487 +++++++++++++++++++++++++++++++++++
> security/ipe/policy_parser.h | 11 +
> 5 files changed, 684 insertions(+)
> create mode 100644 security/ipe/policy.c
> create mode 100644 security/ipe/policy.h
> create mode 100644 security/ipe/policy_parser.c
> create mode 100644 security/ipe/policy_parser.h

...

> diff --git a/security/ipe/policy.c b/security/ipe/policy.c
> new file mode 100644
> index 000000000000..3a529c7950a1
> --- /dev/null
> +++ b/security/ipe/policy.c
> @@ -0,0 +1,101 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) Microsoft Corporation. All rights reserved.
> + */

...

> +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;
> +}

The @asn1hdrlen parameter isn't used in this function, at least at this
point in the patchset, so you really should remove it. If it is needed
later in the patchset you can always update the function.

> diff --git a/security/ipe/policy_parser.c b/security/ipe/policy_parser.c
> new file mode 100644
> index 000000000000..c09458bd348d
> --- /dev/null
> +++ b/security/ipe/policy_parser.c
> @@ -0,0 +1,487 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) Microsoft Corporation. All rights reserved.
> + */

...

> +/**
> + * remove_trailing_spaces - Truncate all trailing spaces in a string.
> + *
> + * @line: Supplies a poilcy line string for preprocessing.
> + *
> + * Return: The length of truncated string.
> + */
> +static size_t remove_trailing_spaces(char *line)
> +{
> + size_t i = 0;
> +
> + for (i = strlen(line); i > 0 && (line[i - 1] == ' ' || line[i - 1] == '\t'); --i)
> + ;

Maybe I've asked this before, I can't remember: could you use the
isspace() macro here instead of explicitly checking for ' ' and '\t'?

i = strlen(line);
while (i > 0 && isspace(line[i - 1]))
i--;
line[i] = '\0';

> + 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 - OK
> + * * !0 - Standard errno
> + */
> +static int parse_version(char *ver, struct ipe_parsed_policy *p)
> +{
> + int rc = 0;
> + size_t sep_count = 0;
> + char *token;
> + u16 *const cv[] = { &p->version.major, &p->version.minor, &p->version.rev };
> +
> + 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))
> + rc = -EBADMSG;

You could always just 'return -EBADMSG' here and 'return 0' below to
simplify things a little.

> + return rc;
> +}
> +
> +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 - OK
> + * * !0 - Standard errno
> + */
> +static int parse_header(char *line, struct ipe_parsed_policy *p)
> +{
> + int rc = 0;
> + char *t, *ver = NULL;
> + substring_t args[MAX_OPT_ARGS];
> + size_t idx = 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;
> + goto out;

You probably don't need to 'goto out' here.

> + }
> +
> +out:
> + kfree(ver);
> + return rc;
> +}

...

> +/**
> + * parse_rule - parse a policy rule line.
> + * @line: Supplies rule line to be parsed.
> + * @p: Supplies the partial parsed policy.
> + *
> + * Return:
> + * * !IS_ERR - OK
> + * * -ENOMEM - Out of memory
> + * * -EBADMSG - Policy syntax error
> + */
> +static int parse_rule(char *line, struct ipe_parsed_policy *p)
> +{
> + int rc = 0;
> + bool first_token = true, is_default_rule = false;
> + bool op_parsed = false;
> + enum ipe_op_type op = IPE_OP_INVALID;
> + enum ipe_action_type action = IPE_ACTION_INVALID;
> + struct ipe_rule *r = NULL;
> + char *t;
> +
> + r = kzalloc(sizeof(*r), GFP_KERNEL);
> + if (!r)
> + return -ENOMEM;
> +
> + INIT_LIST_HEAD(&r->next);
> + INIT_LIST_HEAD(&r->props);
> +
> + while (t = strsep(&line, IPE_POLICY_DELIM), line) {
> + if (*t == '\0')
> + continue;
> + if (first_token && token_default(t)) {
> + is_default_rule = true;
> + } else {
> + if (!op_parsed) {
> + op = parse_operation(t);
> + if (op == IPE_OP_INVALID)
> + rc = -EBADMSG;
> + else
> + op_parsed = true;
> + } else {
> + rc = parse_property(t, r);
> + }
> + }
> +
> + if (rc)
> + goto err;
> + first_token = false;
> + }
> +
> + action = parse_action(t);
> + if (action == IPE_ACTION_INVALID) {
> + rc = -EBADMSG;
> + goto err;
> + }
> +
> + if (is_default_rule) {
> + if (!list_empty(&r->props)) {
> + rc = -EBADMSG;
> + } else if (op == IPE_OP_INVALID) {
> + if (p->global_default_action != IPE_ACTION_INVALID)
> + rc = -EBADMSG;
> + else
> + p->global_default_action = action;
> + } else {
> + if (p->rules[op].default_action != IPE_ACTION_INVALID)
> + rc = -EBADMSG;
> + else
> + p->rules[op].default_action = action;
> + }
> + } else if (op != IPE_OP_INVALID && action != IPE_ACTION_INVALID) {
> + r->op = op;
> + r->action = action;
> + } else {
> + rc = -EBADMSG;
> + }

I might be missing something important in the policy syntac, but could
this function, and perhaps the ipe_parsed_policy struct, be simplified
if the default action was an explicit rule?

"op=DEFAULT action=ALLOW"

> + 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;
> +}
> +
> +/**
> + * free_parsed_policy - free a parsed policy structure.
> + * @p: Supplies the parsed policy.
> + */
> +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) {
> + 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
> + * necessary fields are present, initialized correctly.
> + *
> + * A parsed policy can be in an invalid state for use (a default was
> + * undefined) by just parsing the policy.
> + *
> + * Return:
> + * * 0 - OK
> + * * -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;

Should the if conditional above be "==" and not "!="?

> + for (i = 0; i < ARRAY_SIZE(p->rules); ++i) {
> + if (p->rules[i].default_action == IPE_ACTION_INVALID)
> + return -EBADMSG;
> + }
> +
> + return 0;
> +}

--
paul-moore.com