mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2024-12-28 16:53:49 +00:00
f40998a8e6
If enabled, we fallback to the platform keyring if the trusted keyring doesn't have the key used to sign the ipe policy. But if pkcs7_verify() rejects the key for other reasons, such as usage restrictions, we do not fallback. Do so, following the same change in dm-verity. Signed-off-by: Luca Boccassi <bluca@debian.org> Suggested-by: Serge Hallyn <serge@hallyn.com> [FW: fixed some line length issues and a typo in the commit message] Signed-off-by: Fan Wu <wufan@kernel.org>
240 lines
5.6 KiB
C
240 lines
5.6 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/errno.h>
|
|
#include <linux/verification.h>
|
|
|
|
#include "ipe.h"
|
|
#include "eval.h"
|
|
#include "fs.h"
|
|
#include "policy.h"
|
|
#include "policy_parser.h"
|
|
#include "audit.h"
|
|
|
|
/* lock for synchronizing writers across ipe policy */
|
|
DEFINE_MUTEX(ipe_policy_lock);
|
|
|
|
/**
|
|
* ver_to_u64() - Convert an internal ipe_policy_version to a u64.
|
|
* @p: Policy to extract the version from.
|
|
*
|
|
* Bits (LSB is index 0):
|
|
* [48,32] -> Major
|
|
* [32,16] -> Minor
|
|
* [16, 0] -> Revision
|
|
*
|
|
* Return: u64 version of the embedded version structure.
|
|
*/
|
|
static inline u64 ver_to_u64(const struct ipe_policy *const p)
|
|
{
|
|
u64 r;
|
|
|
|
r = (((u64)p->parsed->version.major) << 32)
|
|
| (((u64)p->parsed->version.minor) << 16)
|
|
| ((u64)(p->parsed->version.rev));
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* ipe_free_policy() - Deallocate a given IPE policy.
|
|
* @p: Supplies the policy to free.
|
|
*
|
|
* Safe to call on IS_ERR/NULL.
|
|
*/
|
|
void ipe_free_policy(struct ipe_policy *p)
|
|
{
|
|
if (IS_ERR_OR_NULL(p))
|
|
return;
|
|
|
|
ipe_del_policyfs_node(p);
|
|
ipe_free_parsed_policy(p->parsed);
|
|
/*
|
|
* p->text is allocated only when p->pkcs7 is not NULL
|
|
* otherwise it points to the plaintext data inside the pkcs7
|
|
*/
|
|
if (!p->pkcs7)
|
|
kfree(p->text);
|
|
kfree(p->pkcs7);
|
|
kfree(p);
|
|
}
|
|
|
|
static int set_pkcs7_data(void *ctx, const void *data, size_t len,
|
|
size_t asn1hdrlen __always_unused)
|
|
{
|
|
struct ipe_policy *p = ctx;
|
|
|
|
p->text = (const char *)data;
|
|
p->textlen = len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipe_update_policy() - parse a new policy and replace old with it.
|
|
* @root: Supplies a pointer to the securityfs inode saved the policy.
|
|
* @text: Supplies a pointer to the plain text policy.
|
|
* @textlen: Supplies the length of @text.
|
|
* @pkcs7: Supplies a pointer to a buffer containing a pkcs7 message.
|
|
* @pkcs7len: Supplies the length of @pkcs7len.
|
|
*
|
|
* @text/@textlen is mutually exclusive with @pkcs7/@pkcs7len - see
|
|
* ipe_new_policy.
|
|
*
|
|
* Context: Requires root->i_rwsem to be held.
|
|
* Return: %0 on success. If an error occurs, the function will return
|
|
* the -errno.
|
|
*/
|
|
int ipe_update_policy(struct inode *root, const char *text, size_t textlen,
|
|
const char *pkcs7, size_t pkcs7len)
|
|
{
|
|
struct ipe_policy *old, *ap, *new = NULL;
|
|
int rc = 0;
|
|
|
|
old = (struct ipe_policy *)root->i_private;
|
|
if (!old)
|
|
return -ENOENT;
|
|
|
|
new = ipe_new_policy(text, textlen, pkcs7, pkcs7len);
|
|
if (IS_ERR(new))
|
|
return PTR_ERR(new);
|
|
|
|
if (strcmp(new->parsed->name, old->parsed->name)) {
|
|
rc = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
if (ver_to_u64(old) >= ver_to_u64(new)) {
|
|
rc = -ESTALE;
|
|
goto err;
|
|
}
|
|
|
|
root->i_private = new;
|
|
swap(new->policyfs, old->policyfs);
|
|
ipe_audit_policy_load(new);
|
|
|
|
mutex_lock(&ipe_policy_lock);
|
|
ap = rcu_dereference_protected(ipe_active_policy,
|
|
lockdep_is_held(&ipe_policy_lock));
|
|
if (old == ap) {
|
|
rcu_assign_pointer(ipe_active_policy, new);
|
|
mutex_unlock(&ipe_policy_lock);
|
|
ipe_audit_policy_activation(old, new);
|
|
} else {
|
|
mutex_unlock(&ipe_policy_lock);
|
|
}
|
|
synchronize_rcu();
|
|
ipe_free_policy(old);
|
|
|
|
return 0;
|
|
err:
|
|
ipe_free_policy(new);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipe_new_policy() - Allocate and parse an ipe_policy structure.
|
|
*
|
|
* @text: Supplies a pointer to the plain-text policy to parse.
|
|
* @textlen: Supplies the length of @text.
|
|
* @pkcs7: Supplies a pointer to a pkcs7-signed IPE policy.
|
|
* @pkcs7len: Supplies the length of @pkcs7.
|
|
*
|
|
* @text/@textlen Should be NULL/0 if @pkcs7/@pkcs7len is set.
|
|
*
|
|
* Return:
|
|
* * a pointer to the ipe_policy structure - Success
|
|
* * %-EBADMSG - Policy is invalid
|
|
* * %-ENOMEM - Out of memory (OOM)
|
|
* * %-ERANGE - Policy version number overflow
|
|
* * %-EINVAL - Policy version parsing error
|
|
*/
|
|
struct ipe_policy *ipe_new_policy(const char *text, size_t textlen,
|
|
const char *pkcs7, size_t pkcs7len)
|
|
{
|
|
struct ipe_policy *new = NULL;
|
|
int rc = 0;
|
|
|
|
new = kzalloc(sizeof(*new), GFP_KERNEL);
|
|
if (!new)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
if (!text) {
|
|
new->pkcs7len = pkcs7len;
|
|
new->pkcs7 = kmemdup(pkcs7, pkcs7len, GFP_KERNEL);
|
|
if (!new->pkcs7) {
|
|
rc = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
rc = verify_pkcs7_signature(NULL, 0, new->pkcs7, pkcs7len,
|
|
#ifdef CONFIG_IPE_POLICY_SIG_SECONDARY_KEYRING
|
|
VERIFY_USE_SECONDARY_KEYRING,
|
|
#else
|
|
NULL,
|
|
#endif
|
|
VERIFYING_UNSPECIFIED_SIGNATURE,
|
|
set_pkcs7_data, new);
|
|
#ifdef CONFIG_IPE_POLICY_SIG_PLATFORM_KEYRING
|
|
if (rc == -ENOKEY || rc == -EKEYREJECTED)
|
|
rc = verify_pkcs7_signature(NULL, 0, new->pkcs7, pkcs7len,
|
|
VERIFY_USE_PLATFORM_KEYRING,
|
|
VERIFYING_UNSPECIFIED_SIGNATURE,
|
|
set_pkcs7_data, new);
|
|
#endif
|
|
if (rc)
|
|
goto err;
|
|
} else {
|
|
new->textlen = textlen;
|
|
new->text = kstrdup(text, GFP_KERNEL);
|
|
if (!new->text) {
|
|
rc = -ENOMEM;
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
rc = ipe_parse_policy(new);
|
|
if (rc)
|
|
goto err;
|
|
|
|
return new;
|
|
err:
|
|
ipe_free_policy(new);
|
|
return ERR_PTR(rc);
|
|
}
|
|
|
|
/**
|
|
* ipe_set_active_pol() - Make @p the active policy.
|
|
* @p: Supplies a pointer to the policy to make active.
|
|
*
|
|
* Context: Requires root->i_rwsem, which i_private has the policy, to be held.
|
|
* Return:
|
|
* * %0 - Success
|
|
* * %-EINVAL - New active policy version is invalid
|
|
*/
|
|
int ipe_set_active_pol(const struct ipe_policy *p)
|
|
{
|
|
struct ipe_policy *ap = NULL;
|
|
|
|
mutex_lock(&ipe_policy_lock);
|
|
|
|
ap = rcu_dereference_protected(ipe_active_policy,
|
|
lockdep_is_held(&ipe_policy_lock));
|
|
if (ap == p) {
|
|
mutex_unlock(&ipe_policy_lock);
|
|
return 0;
|
|
}
|
|
if (ap && ver_to_u64(ap) > ver_to_u64(p)) {
|
|
mutex_unlock(&ipe_policy_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rcu_assign_pointer(ipe_active_policy, p);
|
|
ipe_audit_policy_activation(ap, p);
|
|
mutex_unlock(&ipe_policy_lock);
|
|
|
|
return 0;
|
|
}
|