1
linux/security/ipe/policy_parser.c
Fan Wu 31f8c8682f ipe: enable support for fs-verity as a trust provider
Enable IPE policy authors to indicate trust for a singular fsverity
file, identified by the digest information, through "fsverity_digest"
and all files using valid fsverity builtin signatures via
"fsverity_signature".

This enables file-level integrity claims to be expressed in IPE,
allowing individual files to be authorized, giving some flexibility
for policy authors. Such file-level claims are important to be expressed
for enforcing the integrity of packages, as well as address some of the
scalability issues in a sole dm-verity based solution (# of loop back
devices, etc).

This solution cannot be done in userspace as the minimum threat that
IPE should mitigate is an attacker downloads malicious payload with
all required dependencies. These dependencies can lack the userspace
check, bypassing the protection entirely. A similar attack succeeds if
the userspace component is replaced with a version that does not
perform the check. As a result, this can only be done in the common
entry point - the kernel.

Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
Signed-off-by: Fan Wu <wufan@linux.microsoft.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2024-08-20 14:03:35 -04:00

560 lines
12 KiB
C

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