Re: [PATCH RFC v10 2/17] ipe: add policy parser

Paul Moore
2023-07-08T05:37:16Z

On Jun 28, 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>
> ---
> security/ipe/Makefile | 2 +
> security/ipe/policy.c | 97 +++++++
> security/ipe/policy.h | 83 ++++++
> security/ipe/policy_parser.c | 488 +++++++++++++++++++++++++++++++++++
> security/ipe/policy_parser.h | 11 +
> 5 files changed, 681 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..4069ff075093
> --- /dev/null
> +++ b/security/ipe/policy.c
> @@ -0,0 +1,97 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) Microsoft Corporation. All rights reserved.
> + */
> +
> +#include <linux/errno.h>
> +#include <linux/verification.h>
> +
> +#include "ipe.h"
> +#include "policy.h"
> +#include "policy_parser.h"
> +
> +/**
> + * 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;
> +
> + free_parsed_policy(p->parsed);
> + if (!p->pkcs7)
> + kfree(p->text);

Since it's safe to kfree(NULL), you could kfree(p->text) without
having to check if p->pkcs7 was non-NULL, correct?

> + kfree(p->pkcs7);
> + kfree(p);
> +}

...

> diff --git a/security/ipe/policy.h b/security/ipe/policy.h
> new file mode 100644
> index 000000000000..113a037f0d71
> --- /dev/null
> +++ b/security/ipe/policy.h
> @@ -0,0 +1,83 @@
> +/* 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>
> +
> +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_IMA_POLICY,
> + __IPE_OP_IMA_X509,
> + __IPE_OP_MAX
> +};

Thanks for capitalizing the enums, that helps make IPE consistent with
the majority of the kernel. However, when I talked about using
underscores for "__IPE_OP_MAX", I was talking about *only*
"__IPE_OP_MAX" to help indicate it is a sentinel value and not an enum
value that would normally be used by itself.

Here is what I was intending:

enum ipe_op_type {
IPE_OP_EXEC = 0,
IPE_OP_FIRMWARE,
...
IPE_OP_IMA_X509,
__IPE_OP_MAX
};

> +#define __IPE_OP_INVALID __IPE_OP_MAX

Similarly, I would remove the underscores from "__IPE_OP_INVALID":

#define IPE_OP_INVALID __IPE_OP_MAX

Both of these comments would apply to the other IPE enums as well.

> diff --git a/security/ipe/policy_parser.c b/security/ipe/policy_parser.c
> new file mode 100644
> index 000000000000..27e5767480b0
> --- /dev/null
> +++ b/security/ipe/policy_parser.c
> @@ -0,0 +1,488 @@

...

> +/**
> + * 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, " \t")) != NULL) {

It might be nice to define a macro to help reinforce that " \t" are
the IPE policy delimiters, how about IPE_POLICY_DELIM?

#define IPE_POLICY_DELIM " \t"

> + int token;
> +
> + if (*t == '\0')
> + continue;

Why would you want to continue if you run into a NUL byte? You would
only run into a NUL byte if the line was trimmed due to comments or
whitespace, correct? If that is the case, wouldn't you want to
break out of this loop when hitting a NUL byte?

> + if (idx >= __IPE_HEADER_MAX) {
> + rc = -EBADMSG;
> + goto err;
> + }
> +
> + token = match_token(t, header_tokens, args);
> + if (token != idx) {
> + rc = -EBADMSG;
> + goto err;
> + }
> +
> + 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 err;
> + ++idx;
> + }
> +
> + if (idx != __IPE_HEADER_MAX) {
> + rc = -EBADMSG;
> + goto err;
> + }
> +
> +out:
> + kfree(ver);
> + return rc;
> +err:
> + kfree(p->name);
> + p->name = NULL;
> + goto out;

Do we need to worry about ipe_parsed_policy::name here? If we are
returning an error the caller will call free_parsed_policy() for us,
right? This would allow us to get rid of the 'err' jump label and
simply use 'out' for both success and failure.

> +}

...

> +/**
> + * 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, " \t"), line) {

See my previous comment about IPE_POLICY_DELIM.

> + if (*t == '\0')
> + continue;

I still wonder why continuing here is the desired behavior, can you
help me understand?

> + 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);
> +
> +out:
> + return rc;
> +err:
> + free_rule(r);
> + goto out;

In keeping with the rule of not jumping to a label only to
immediately return, and considering that the only place where we jump
to 'out' is in the 'err' code, let's get rid of the 'out' label and
have 'err' "return rc" instead of "goto out".

> +}

--
paul-moore.com