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

Fan Wu wufan at linux.microsoft.com
Fri Jul 14 04:18:10 UTC 2023


On Sat, Jul 08, 2023 at 12:23:00AM -0400, Paul Moore wrote:
> On Jun 28, 2023 Fan Wu <wufan at 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 at linux.microsoft.com>
> > Signed-off-by: Fan Wu <wufan at 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?
> 
when p->pkcs7 is not NULL, p->text points to the plain text policy area inside
the data of p->pkcs7, for such cases p->text is not really an allocated memory chunk
so it cannot be passed to kfree.

I might better add a comment here to avoid confusion in the future.

> > +	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.
> 
Sorry for the mistake, I will update them.

> > 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"
> 
Sure, this is better, I will take this idea.

> > +		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?
> 
This happens when two spaces are passed, for example "DEFAULT<space><space>action=DENY"
has two spaces inside, the strsep will create a NUL string when it sees the first space,
so for such cases I think we should just skip to the next token.

> > +		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.
> 
Yes this is not necessary, I will remove this part.

> > +}
> 
> ...
> 
> > +/**
> > + * 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?
This one is the same to the parse header function, when two consecutive
delimitators is passed to strsep it will generate a '\0'.

> 
> > +		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".
> 
Sure I can change this part, yeah I agree this looks weird. 

-Fan
> > +}
> 
> --
> paul-moore.com



More information about the Linux-security-module-archive mailing list