// 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 invalidLines 1–500 of 559. Load more
// 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