// 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 - Success * * %-ENOENT - Policy was deleted while updating * * %-EINVAL - Policy name mismatch * * %-ESTALE - Policy version too old */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 * * %-ENOKEY - Policy signing key not found * * %-EKEYREJECTED - Policy signature verification failed */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_obj(*new); 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;}
// 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 - Success
* * %-ENOENT - Policy was deleted while updating
* * %-EINVAL - Policy name mismatch
* * %-ESTALE - Policy version too old
*/
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
* * %-ENOKEY - Policy signing key not found
* * %-EKEYREJECTED - Policy signature verification failed
*/
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_obj(*new);
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;
}