| // 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(sizeof(*p), GFP_KERNEL); |
| 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(sizeof(*p), GFP_KERNEL); |
| 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(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; |
| } |
| |
| 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; |
| } |