| // 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(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; | 
 | } |