244 lines · 5.7 KB · C
RawDownload
// 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;}