1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
|
// 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;
}
|