mirror of
https://github.com/torvalds/linux.git
synced 2026-01-25 15:03:52 +08:00
IPE is designed to provide system level trust guarantees, this usually implies that trust starts from bootup with a hardware root of trust, which validates the bootloader. After this, the bootloader verifies the kernel and the initramfs. As there's no currently supported integrity method for initramfs, and it's typically already verified by the bootloader. This patch introduces a new IPE property `boot_verified` which allows author of IPE policy to indicate trust for files from initramfs. The implementation of this feature utilizes the newly added `initramfs_populated` hook. This hook marks the superblock of the rootfs after the initramfs has been unpacked into it. Before mounting the real rootfs on top of the initramfs, initramfs script will recursively remove all files and directories on the initramfs. This is typically implemented by using switch_root(8) (https://man7.org/linux/man-pages/man8/switch_root.8.html). Therefore the initramfs will be empty and not accessible after the real rootfs takes over. It is advised to switch to a different policy that doesn't rely on the `boot_verified` property after this point. This ensures that the trust policies remain relevant and effective throughout the system's operation. 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>
532 lines
11 KiB
C
532 lines
11 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"
|
|
|
|
#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);
|
|
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_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;
|
|
|
|
p = kzalloc(sizeof(*p), GFP_KERNEL);
|
|
if (!p)
|
|
return -ENOMEM;
|
|
|
|
token = match_token(t, property_tokens, args);
|
|
|
|
switch (token) {
|
|
case IPE_PROP_BOOT_VERIFIED_FALSE:
|
|
case IPE_PROP_BOOT_VERIFIED_TRUE:
|
|
p->type = token;
|
|
break;
|
|
default:
|
|
rc = -EBADMSG;
|
|
break;
|
|
}
|
|
if (rc)
|
|
goto err;
|
|
list_add_tail(&p->next, &r->props);
|
|
|
|
return rc;
|
|
err:
|
|
kfree(p);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|