| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. | |
| */ | |
| #include <linux/fs.h> | |
| #include <linux/namei.h> | |
| #include <linux/types.h> | |
| #include <linux/dcache.h> | |
| #include <linux/security.h> | |
| #include "ipe.h" | |
| #include "policy.h" | |
| #include "eval.h" | |
| #include "fs.h" | |
| #include "audit.h" | |
| #define MAX_VERSION_SIZE ARRAY_SIZE("65535.65535.65535") | |
| /** | |
| * struct ipefs_file - defines a file in securityfs. | |
| * | |
| * @name: file name inside the policy subdirectory | |
| * @access: file permissions | |
| * @fops: &file_operations specific to this file | |
| */ | |
| struct ipefs_file { | |
| const char *name; | |
| umode_t access; | |
| const struct file_operations *fops; | |
| }; | |
| /** | |
| * read_pkcs7() - Read handler for "ipe/policies/$name/pkcs7". | |
| * @f: Supplies a file structure representing the securityfs node. | |
| * @data: Supplies a buffer passed to the write syscall. | |
| * @len: Supplies the length of @data. | |
| * @offset: unused. | |
| * | |
| * @data will be populated with the pkcs7 blob representing the policy | |
| * on success. If the policy is unsigned (like the boot policy), this | |
| * will return -ENOENT. | |
| * | |
| * Return: | |
| * * Length of buffer written - Success | |
| * * %-ENOENT - Policy initializing/deleted or is unsigned | |
| */ | |
| static ssize_t read_pkcs7(struct file *f, char __user *data, | |
| size_t len, loff_t *offset) | |
| { | |
| const struct ipe_policy *p = NULL; | |
| struct inode *root = NULL; | |
| int rc = 0; | |
| root = d_inode(f->f_path.dentry->d_parent); | |
| inode_lock_shared(root); | |
| p = (struct ipe_policy *)root->i_private; | |
| if (!p) { | |
| rc = -ENOENT; | |
| goto out; | |
| } | |
| if (!p->pkcs7) { | |
| rc = -ENOENT; | |
| goto out; | |
| } | |
| rc = simple_read_from_buffer(data, len, offset, p->pkcs7, p->pkcs7len); | |
| out: | |
| inode_unlock_shared(root); | |
| return rc; | |
| } | |
| /** | |
| * read_policy() - Read handler for "ipe/policies/$name/policy". | |
| * @f: Supplies a file structure representing the securityfs node. | |
| * @data: Supplies a buffer passed to the write syscall. | |
| * @len: Supplies the length of @data. | |
| * @offset: unused. | |
| * | |
| * @data will be populated with the plain-text version of the policy | |
| * on success. | |
| * | |
| * Return: | |
| * * Length of buffer written - Success | |
| * * %-ENOENT - Policy initializing/deleted | |
| */ | |
| static ssize_t read_policy(struct file *f, char __user *data, | |
| size_t len, loff_t *offset) | |
| { | |
| const struct ipe_policy *p = NULL; | |
| struct inode *root = NULL; | |
| int rc = 0; | |
| root = d_inode(f->f_path.dentry->d_parent); | |
| inode_lock_shared(root); | |
| p = (struct ipe_policy *)root->i_private; | |
| if (!p) { | |
| rc = -ENOENT; | |
| goto out; | |
| } | |
| rc = simple_read_from_buffer(data, len, offset, p->text, p->textlen); | |
| out: | |
| inode_unlock_shared(root); | |
| return rc; | |
| } | |
| /** | |
| * read_name() - Read handler for "ipe/policies/$name/name". | |
| * @f: Supplies a file structure representing the securityfs node. | |
| * @data: Supplies a buffer passed to the write syscall. | |
| * @len: Supplies the length of @data. | |
| * @offset: unused. | |
| * | |
| * @data will be populated with the policy_name attribute on success. | |
| * | |
| * Return: | |
| * * Length of buffer written - Success | |
| * * %-ENOENT - Policy initializing/deleted | |
| */ | |
| static ssize_t read_name(struct file *f, char __user *data, | |
| size_t len, loff_t *offset) | |
| { | |
| const struct ipe_policy *p = NULL; | |
| struct inode *root = NULL; | |
| int rc = 0; | |
| root = d_inode(f->f_path.dentry->d_parent); | |
| inode_lock_shared(root); | |
| p = (struct ipe_policy *)root->i_private; | |
| if (!p) { | |
| rc = -ENOENT; | |
| goto out; | |
| } | |
| rc = simple_read_from_buffer(data, len, offset, p->parsed->name, | |
| strlen(p->parsed->name)); | |
| out: | |
| inode_unlock_shared(root); | |
| return rc; | |
| } | |
| /** | |
| * read_version() - Read handler for "ipe/policies/$name/version". | |
| * @f: Supplies a file structure representing the securityfs node. | |
| * @data: Supplies a buffer passed to the write syscall. | |
| * @len: Supplies the length of @data. | |
| * @offset: unused. | |
| * | |
| * @data will be populated with the version string on success. | |
| * | |
| * Return: | |
| * * Length of buffer written - Success | |
| * * %-ENOENT - Policy initializing/deleted | |
| */ | |
| static ssize_t read_version(struct file *f, char __user *data, | |
| size_t len, loff_t *offset) | |
| { | |
| char buffer[MAX_VERSION_SIZE] = { 0 }; | |
| const struct ipe_policy *p = NULL; | |
| struct inode *root = NULL; | |
| size_t strsize = 0; | |
| ssize_t rc = 0; | |
| root = d_inode(f->f_path.dentry->d_parent); | |
| inode_lock_shared(root); | |
| p = (struct ipe_policy *)root->i_private; | |
| if (!p) { | |
| rc = -ENOENT; | |
| goto out; | |
| } | |
| strsize = scnprintf(buffer, ARRAY_SIZE(buffer), "%hu.%hu.%hu", | |
| p->parsed->version.major, p->parsed->version.minor, | |
| p->parsed->version.rev); | |
| rc = simple_read_from_buffer(data, len, offset, buffer, strsize); | |
| out: | |
| inode_unlock_shared(root); | |
| return rc; | |
| } | |
| /** | |
| * setactive() - Write handler for "ipe/policies/$name/active". | |
| * @f: Supplies a file structure representing the securityfs node. | |
| * @data: Supplies a buffer passed to the write syscall. | |
| * @len: Supplies the length of @data. | |
| * @offset: unused. | |
| * | |
| * Return: | |
| * * Length of buffer written - Success | |
| * * %-EPERM - Insufficient permission | |
| * * %-EINVAL - Invalid input | |
| * * %-ENOENT - Policy initializing/deleted | |
| */ | |
| static ssize_t setactive(struct file *f, const char __user *data, | |
| size_t len, loff_t *offset) | |
| { | |
| const struct ipe_policy *p = NULL; | |
| struct inode *root = NULL; | |
| bool value = false; | |
| int rc = 0; | |
| if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) | |
| return -EPERM; | |
| rc = kstrtobool_from_user(data, len, &value); | |
| if (rc) | |
| return rc; | |
| if (!value) | |
| return -EINVAL; | |
| root = d_inode(f->f_path.dentry->d_parent); | |
| inode_lock(root); | |
| p = (struct ipe_policy *)root->i_private; | |
| if (!p) { | |
| rc = -ENOENT; | |
| goto out; | |
| } | |
| rc = ipe_set_active_pol(p); | |
| out: | |
| inode_unlock(root); | |
| return (rc < 0) ? rc : len; | |
| } | |
| /** | |
| * getactive() - Read handler for "ipe/policies/$name/active". | |
| * @f: Supplies a file structure representing the securityfs node. | |
| * @data: Supplies a buffer passed to the write syscall. | |
| * @len: Supplies the length of @data. | |
| * @offset: unused. | |
| * | |
| * @data will be populated with the 1 or 0 depending on if the | |
| * corresponding policy is active. | |
| * | |
| * Return: | |
| * * Length of buffer written - Success | |
| * * %-ENOENT - Policy initializing/deleted | |
| */ | |
| static ssize_t getactive(struct file *f, char __user *data, | |
| size_t len, loff_t *offset) | |
| { | |
| const struct ipe_policy *p = NULL; | |
| struct inode *root = NULL; | |
| const char *str; | |
| int rc = 0; | |
| root = d_inode(f->f_path.dentry->d_parent); | |
| inode_lock_shared(root); | |
| p = (struct ipe_policy *)root->i_private; | |
| if (!p) { | |
| inode_unlock_shared(root); | |
| return -ENOENT; | |
| } | |
| inode_unlock_shared(root); | |
| str = (p == rcu_access_pointer(ipe_active_policy)) ? "1" : "0"; | |
| rc = simple_read_from_buffer(data, len, offset, str, 1); | |
| return rc; | |
| } | |
| /** | |
| * update_policy() - Write handler for "ipe/policies/$name/update". | |
| * @f: Supplies a file structure representing the securityfs node. | |
| * @data: Supplies a buffer passed to the write syscall. | |
| * @len: Supplies the length of @data. | |
| * @offset: unused. | |
| * | |
| * On success this updates the policy represented by $name, | |
| * in-place. | |
| * | |
| * Return: | |
| * * Length of buffer written - Success | |
| * * %-EPERM - Insufficient permission | |
| * * %-ENOMEM - Out of memory (OOM) | |
| * * %-ENOENT - Policy was deleted while updating | |
| * * %-EINVAL - Policy name mismatch | |
| * * %-ESTALE - Policy version too old | |
| */ | |
| static ssize_t update_policy(struct file *f, const char __user *data, | |
| size_t len, loff_t *offset) | |
| { | |
| struct inode *root = NULL; | |
| char *copy = NULL; | |
| int rc = 0; | |
| if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) { | |
| rc = -EPERM; | |
| goto out; | |
| } | |
| copy = memdup_user(data, len); | |
| if (IS_ERR(copy)) { | |
| rc = PTR_ERR(copy); | |
| copy = NULL; | |
| goto out; | |
| } | |
| root = d_inode(f->f_path.dentry->d_parent); | |
| inode_lock(root); | |
| rc = ipe_update_policy(root, NULL, 0, copy, len); | |
| inode_unlock(root); | |
| out: | |
| kfree(copy); | |
| if (rc) { | |
| ipe_audit_policy_load(ERR_PTR(rc)); | |
| return rc; | |
| } | |
| return len; | |
| } | |
| /** | |
| * delete_policy() - write handler for "ipe/policies/$name/delete". | |
| * @f: Supplies a file structure representing the securityfs node. | |
| * @data: Supplies a buffer passed to the write syscall. | |
| * @len: Supplies the length of @data. | |
| * @offset: unused. | |
| * | |
| * On success this deletes the policy represented by $name. | |
| * | |
| * Return: | |
| * * Length of buffer written - Success | |
| * * %-EPERM - Insufficient permission/deleting active policy | |
| * * %-EINVAL - Invalid input | |
| * * %-ENOENT - Policy initializing/deleted | |
| */ | |
| static ssize_t delete_policy(struct file *f, const char __user *data, | |
| size_t len, loff_t *offset) | |
| { | |
| struct ipe_policy *ap = NULL; | |
| struct ipe_policy *p = NULL; | |
| struct inode *root = NULL; | |
| bool value = false; | |
| int rc = 0; | |
| if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) | |
| return -EPERM; | |
| rc = kstrtobool_from_user(data, len, &value); | |
| if (rc) | |
| return rc; | |
| if (!value) | |
| return -EINVAL; | |
| root = d_inode(f->f_path.dentry->d_parent); | |
| inode_lock(root); | |
| p = (struct ipe_policy *)root->i_private; | |
| if (!p) { | |
| inode_unlock(root); | |
| return -ENOENT; | |
| } | |
| mutex_lock(&ipe_policy_lock); | |
| ap = rcu_dereference_protected(ipe_active_policy, | |
| lockdep_is_held(&ipe_policy_lock)); | |
| if (p == ap) { | |
| mutex_unlock(&ipe_policy_lock); | |
| inode_unlock(root); | |
| return -EPERM; | |
| } | |
| mutex_unlock(&ipe_policy_lock); | |
| root->i_private = NULL; | |
| inode_unlock(root); | |
| synchronize_rcu(); | |
| ipe_free_policy(p); | |
| return len; | |
| } | |
| static const struct file_operations content_fops = { | |
| .read = read_policy, | |
| }; | |
| static const struct file_operations pkcs7_fops = { | |
| .read = read_pkcs7, | |
| }; | |
| static const struct file_operations name_fops = { | |
| .read = read_name, | |
| }; | |
| static const struct file_operations ver_fops = { | |
| .read = read_version, | |
| }; | |
| static const struct file_operations active_fops = { | |
| .write = setactive, | |
| .read = getactive, | |
| }; | |
| static const struct file_operations update_fops = { | |
| .write = update_policy, | |
| }; | |
| static const struct file_operations delete_fops = { | |
| .write = delete_policy, | |
| }; | |
| /* | |
| * policy_subdir - files under a policy subdirectory | |
| */ | |
| static const struct ipefs_file policy_subdir[] = { | |
| { "pkcs7", 0444, &pkcs7_fops }, | |
| { "policy", 0444, &content_fops }, | |
| { "name", 0444, &name_fops }, | |
| { "version", 0444, &ver_fops }, | |
| { "active", 0600, &active_fops }, | |
| { "update", 0200, &update_fops }, | |
| { "delete", 0200, &delete_fops }, | |
| }; | |
| /** | |
| * ipe_del_policyfs_node() - Delete a securityfs entry for @p. | |
| * @p: Supplies a pointer to the policy to delete a securityfs entry for. | |
| */ | |
| void ipe_del_policyfs_node(struct ipe_policy *p) | |
| { | |
| securityfs_remove(p->policyfs); | |
| p->policyfs = NULL; | |
| } | |
| /** | |
| * ipe_new_policyfs_node() - Create a securityfs entry for @p. | |
| * @p: Supplies a pointer to the policy to create a securityfs entry for. | |
| * | |
| * Return: %0 on success. If an error occurs, the function will return | |
| * the -errno. | |
| */ | |
| int ipe_new_policyfs_node(struct ipe_policy *p) | |
| { | |
| const struct ipefs_file *f = NULL; | |
| struct dentry *policyfs = NULL; | |
| struct inode *root = NULL; | |
| struct dentry *d = NULL; | |
| size_t i = 0; | |
| int rc = 0; | |
| if (p->policyfs) | |
| return 0; | |
| policyfs = securityfs_create_dir(p->parsed->name, policy_root); | |
| if (IS_ERR(policyfs)) | |
| return PTR_ERR(policyfs); | |
| root = d_inode(policyfs); | |
| for (i = 0; i < ARRAY_SIZE(policy_subdir); ++i) { | |
| f = &policy_subdir[i]; | |
| d = securityfs_create_file(f->name, f->access, policyfs, | |
| NULL, f->fops); | |
| if (IS_ERR(d)) { | |
| rc = PTR_ERR(d); | |
| goto err; | |
| } | |
| } | |
| inode_lock(root); | |
| p->policyfs = policyfs; | |
| root->i_private = p; | |
| /* Only audit signed policies from userspace */ | |
| if (p->pkcs7) | |
| ipe_audit_policy_load(p); | |
| inode_unlock(root); | |
| return 0; | |
| err: | |
| securityfs_remove(policyfs); | |
| return rc; | |
| } |