// 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;
inode_unlock(root);
return 0 ;
err:
securityfs_remove(policyfs);
return rc;
}
Messung V0.5 in Prozent C=98 H=88 G=93
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland