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