| // 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" |
| |
| #define MAX_VERSION_SIZE ARRAY_SIZE("65535.65535.65535") |
| |
| /** |
| * ipefs_file - defines a file in securityfs. |
| */ |
| 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 on success. If an error occurs, |
| * the function will return the -errno. |
| */ |
| 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)) |
| return -EPERM; |
| |
| copy = memdup_user(data, len); |
| if (IS_ERR(copy)) |
| return PTR_ERR(copy); |
| |
| root = d_inode(f->f_path.dentry->d_parent); |
| inode_lock(root); |
| rc = ipe_update_policy(root, NULL, 0, copy, len); |
| inode_unlock(root); |
| |
| kfree(copy); |
| if (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_recursive_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; |
| inode_unlock(root); |
| |
| return 0; |
| err: |
| securityfs_recursive_remove(policyfs); |
| return rc; |
| } |