| 1 | |
| 2 | |
| 3 | * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved.
|
| 4 |
|
| 5 |
|
| 6 | #include <linux/errno.h>
|
| 7 | #include <linux/verification.h>
|
| 8 |
|
| 9 | #include "ipe.h"
|
| 10 | #include "eval.h"
|
| 11 | #include "fs.h"
|
| 12 | #include "policy.h"
|
| 13 | #include "policy_parser.h"
|
| 14 | #include "audit.h"
|
| 15 |
|
| 16 |
|
| 17 | DEFINE_MUTEX(ipe_policy_lock);
|
| 18 |
|
| 19 | |
| 20 | * ver_to_u64() - Convert an internal ipe_policy_version to a u64.
|
| 21 | * @p: Policy to extract the version from.
|
| 22 | *
|
| 23 | * Bits (LSB is index 0):
|
| 24 | * [48,32] -> Major
|
| 25 | * [32,16] -> Minor
|
| 26 | * [16, 0] -> Revision
|
| 27 | *
|
| 28 | * Return: u64 version of the embedded version structure.
|
| 29 |
|
| 30 | static inline u64 ver_to_u64(const struct ipe_policy *const p)
|
| 31 | {
|
| 32 | u64 r;
|
| 33 |
|
| 34 | r = (((u64)p->parsed->version.major) << 32)
|
| 35 | | (((u64)p->parsed->version.minor) << 16)
|
| 36 | | ((u64)(p->parsed->version.rev));
|
| 37 |
|
| 38 | return r;
|
| 39 | }
|
| 40 |
|
| 41 | |
| 42 | * ipe_free_policy() - Deallocate a given IPE policy.
|
| 43 | * @p: Supplies the policy to free.
|
| 44 | *
|
| 45 | * Safe to call on IS_ERR/NULL.
|
| 46 |
|
| 47 | void ipe_free_policy(struct ipe_policy *p)
|
| 48 | {
|
| 49 | if (IS_ERR_OR_NULL(p))
|
| 50 | return;
|
| 51 |
|
| 52 | ipe_del_policyfs_node(p);
|
| 53 | ipe_free_parsed_policy(p->parsed);
|
| 54 | |
| 55 | * p->text is allocated only when p->pkcs7 is not NULL
|
| 56 | * otherwise it points to the plaintext data inside the pkcs7
|
| 57 |
|
| 58 | if (!p->pkcs7)
|
| 59 | kfree(p->text);
|
| 60 | kfree(p->pkcs7);
|
| 61 | kfree(p);
|
| 62 | }
|
| 63 |
|
| 64 | static int set_pkcs7_data(void *ctx, const void *data, size_t len,
|
| 65 | size_t asn1hdrlen __always_unused)
|
| 66 | {
|
| 67 | struct ipe_policy *p = ctx;
|
| 68 |
|
| 69 | p->text = (const char *)data;
|
| 70 | p->textlen = len;
|
| 71 |
|
| 72 | return 0;
|
| 73 | }
|
| 74 |
|
| 75 | |
| 76 | * ipe_update_policy() - parse a new policy and replace old with it.
|
| 77 | * @root: Supplies a pointer to the securityfs inode saved the policy.
|
| 78 | * @text: Supplies a pointer to the plain text policy.
|
| 79 | * @textlen: Supplies the length of @text.
|
| 80 | * @pkcs7: Supplies a pointer to a buffer containing a pkcs7 message.
|
| 81 | * @pkcs7len: Supplies the length of @pkcs7len.
|
| 82 | *
|
| 83 | * @text/@textlen is mutually exclusive with @pkcs7/@pkcs7len - see
|
| 84 | * ipe_new_policy.
|
| 85 | *
|
| 86 | * Context: Requires root->i_rwsem to be held.
|
| 87 | * Return:
|
| 88 | * * %0 - Success
|
| 89 | * * %-ENOENT - Policy was deleted while updating
|
| 90 | * * %-EINVAL - Policy name mismatch
|
| 91 | * * %-ESTALE - Policy version too old
|
| 92 |
|
| 93 | int ipe_update_policy(struct inode *root, const char *text, size_t textlen,
|
| 94 | const char *pkcs7, size_t pkcs7len)
|
| 95 | {
|
| 96 | struct ipe_policy *old, *ap, *new = NULL;
|
| 97 | int rc = 0;
|
| 98 |
|
| 99 | old = (struct ipe_policy *)root->i_private;
|
| 100 | if (!old)
|
| 101 | return -ENOENT;
|
| 102 |
|
| 103 | new = ipe_new_policy(text, textlen, pkcs7, pkcs7len);
|
| 104 | if (IS_ERR(new))
|
| 105 | return PTR_ERR(new);
|
| 106 |
|
| 107 | if (strcmp(new->parsed->name, old->parsed->name)) {
|
| 108 | rc = -EINVAL;
|
| 109 | goto err;
|
| 110 | }
|
| 111 |
|
| 112 | if (ver_to_u64(old) >= ver_to_u64(new)) {
|
| 113 | rc = -ESTALE;
|
| 114 | goto err;
|
| 115 | }
|
| 116 |
|
| 117 | root->i_private = new;
|
| 118 | swap(new->policyfs, old->policyfs);
|
| 119 | ipe_audit_policy_load(new);
|
| 120 |
|
| 121 | mutex_lock(&ipe_policy_lock);
|
| 122 | ap = rcu_dereference_protected(ipe_active_policy,
|
| 123 | lockdep_is_held(&ipe_policy_lock));
|
| 124 | if (old == ap) {
|
| 125 | rcu_assign_pointer(ipe_active_policy, new);
|
| 126 | mutex_unlock(&ipe_policy_lock);
|
| 127 | ipe_audit_policy_activation(old, new);
|
| 128 | } else {
|
| 129 | mutex_unlock(&ipe_policy_lock);
|
| 130 | }
|
| 131 | synchronize_rcu();
|
| 132 | ipe_free_policy(old);
|
| 133 |
|
| 134 | return 0;
|
| 135 | err:
|
| 136 | ipe_free_policy(new);
|
| 137 | return rc;
|
| 138 | }
|
| 139 |
|
| 140 | |
| 141 | * ipe_new_policy() - Allocate and parse an ipe_policy structure.
|
| 142 | *
|
| 143 | * @text: Supplies a pointer to the plain-text policy to parse.
|
| 144 | * @textlen: Supplies the length of @text.
|
| 145 | * @pkcs7: Supplies a pointer to a pkcs7-signed IPE policy.
|
| 146 | * @pkcs7len: Supplies the length of @pkcs7.
|
| 147 | *
|
| 148 | * @text/@textlen Should be NULL/0 if @pkcs7/@pkcs7len is set.
|
| 149 | *
|
| 150 | * Return:
|
| 151 | * * a pointer to the ipe_policy structure - Success
|
| 152 | * * %-EBADMSG - Policy is invalid
|
| 153 | * * %-ENOMEM - Out of memory (OOM)
|
| 154 | * * %-ERANGE - Policy version number overflow
|
| 155 | * * %-EINVAL - Policy version parsing error
|
| 156 | * * %-ENOKEY - Policy signing key not found
|
| 157 | * * %-EKEYREJECTED - Policy signature verification failed
|
| 158 |
|
| 159 | struct ipe_policy *ipe_new_policy(const char *text, size_t textlen,
|
| 160 | const char *pkcs7, size_t pkcs7len)
|
| 161 | {
|
| 162 | struct ipe_policy *new = NULL;
|
| 163 | int rc = 0;
|
| 164 |
|
| 165 | new = kzalloc_obj(*new);
|
| 166 | if (!new)
|
| 167 | return ERR_PTR(-ENOMEM);
|
| 168 |
|
| 169 | if (!text) {
|
| 170 | new->pkcs7len = pkcs7len;
|
| 171 | new->pkcs7 = kmemdup(pkcs7, pkcs7len, GFP_KERNEL);
|
| 172 | if (!new->pkcs7) {
|
| 173 | rc = -ENOMEM;
|
| 174 | goto err;
|
| 175 | }
|
| 176 |
|
| 177 | rc = verify_pkcs7_signature(NULL, 0, new->pkcs7, pkcs7len,
|
| 178 | #ifdef CONFIG_IPE_POLICY_SIG_SECONDARY_KEYRING
|
| 179 | VERIFY_USE_SECONDARY_KEYRING,
|
| 180 | #else
|
| 181 | NULL,
|
| 182 | #endif
|
| 183 | VERIFYING_UNSPECIFIED_SIGNATURE,
|
| 184 | set_pkcs7_data, new);
|
| 185 | #ifdef CONFIG_IPE_POLICY_SIG_PLATFORM_KEYRING
|
| 186 | if (rc == -ENOKEY || rc == -EKEYREJECTED)
|
| 187 | rc = verify_pkcs7_signature(NULL, 0, new->pkcs7, pkcs7len,
|
| 188 | VERIFY_USE_PLATFORM_KEYRING,
|
| 189 | VERIFYING_UNSPECIFIED_SIGNATURE,
|
| 190 | set_pkcs7_data, new);
|
| 191 | #endif
|
| 192 | if (rc)
|
| 193 | goto err;
|
| 194 | } else {
|
| 195 | new->textlen = textlen;
|
| 196 | new->text = kstrdup(text, GFP_KERNEL);
|
| 197 | if (!new->text) {
|
| 198 | rc = -ENOMEM;
|
| 199 | goto err;
|
| 200 | }
|
| 201 | }
|
| 202 |
|
| 203 | rc = ipe_parse_policy(new);
|
| 204 | if (rc)
|
| 205 | goto err;
|
| 206 |
|
| 207 | return new;
|
| 208 | err:
|
| 209 | ipe_free_policy(new);
|
| 210 | return ERR_PTR(rc);
|
| 211 | }
|
| 212 |
|
| 213 | |
| 214 | * ipe_set_active_pol() - Make @p the active policy.
|
| 215 | * @p: Supplies a pointer to the policy to make active.
|
| 216 | *
|
| 217 | * Context: Requires root->i_rwsem, which i_private has the policy, to be held.
|
| 218 | * Return:
|
| 219 | * * %0 - Success
|
| 220 | * * %-EINVAL - New active policy version is invalid
|
| 221 |
|
| 222 | int ipe_set_active_pol(const struct ipe_policy *p)
|
| 223 | {
|
| 224 | struct ipe_policy *ap = NULL;
|
| 225 |
|
| 226 | mutex_lock(&ipe_policy_lock);
|
| 227 |
|
| 228 | ap = rcu_dereference_protected(ipe_active_policy,
|
| 229 | lockdep_is_held(&ipe_policy_lock));
|
| 230 | if (ap == p) {
|
| 231 | mutex_unlock(&ipe_policy_lock);
|
| 232 | return 0;
|
| 233 | }
|
| 234 | if (ap && ver_to_u64(ap) > ver_to_u64(p)) {
|
| 235 | mutex_unlock(&ipe_policy_lock);
|
| 236 | return -EINVAL;
|
| 237 | }
|
| 238 |
|
| 239 | rcu_assign_pointer(ipe_active_policy, p);
|
| 240 | ipe_audit_policy_activation(ap, p);
|
| 241 | mutex_unlock(&ipe_policy_lock);
|
| 242 |
|
| 243 | return 0;
|
| 244 | }
|