// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. */ #include #include #include #include #include #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; }