473 lines
11 KiB
C
473 lines
11 KiB
C
|
// 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;
|
||
|
}
|