linux/security/integrity/evm/evm_secfs.c

322 lines
7.2 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2010 IBM Corporation
*
* Authors:
* Mimi Zohar <zohar@us.ibm.com>
*
* File: evm_secfs.c
* - Used to signal when key is on keyring
* - Get the key and enable EVM
*/
#include <linux/audit.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/mutex.h>
#include "evm.h"
static struct dentry *evm_dir;
static struct dentry *evm_init_tpm;
static struct dentry *evm_symlink;
#ifdef CONFIG_EVM_ADD_XATTRS
static struct dentry *evm_xattrs;
static DEFINE_MUTEX(xattr_list_mutex);
static int evm_xattrs_locked;
#endif
/**
* evm_read_key - read() for <securityfs>/evm
*
* @filp: file pointer, not actually used
* @buf: where to put the result
* @count: maximum to send along
* @ppos: where to start
*
* Returns number of bytes read or error code, as appropriate
*/
static ssize_t evm_read_key(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
char temp[80];
ssize_t rc;
if (*ppos != 0)
return 0;
sprintf(temp, "%d", (evm_initialized & ~EVM_SETUP_COMPLETE));
rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp));
return rc;
}
/**
* evm_write_key - write() for <securityfs>/evm
* @file: file pointer, not actually used
* @buf: where to get the data from
* @count: bytes sent
* @ppos: where to start
*
* Used to signal that key is on the kernel key ring.
* - get the integrity hmac key from the kernel key ring
* - create list of hmac protected extended attributes
* Returns number of bytes written or error code, as appropriate
*/
static ssize_t evm_write_key(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
int i, ret;
if (!capable(CAP_SYS_ADMIN) || (evm_initialized & EVM_SETUP_COMPLETE))
return -EPERM;
ret = kstrtoint_from_user(buf, count, 0, &i);
if (ret)
return ret;
/* Reject invalid values */
if (!i || (i & ~EVM_INIT_MASK) != 0)
return -EINVAL;
/* Don't allow a request to freshly enable metadata writes if
* keys are loaded.
*/
if ((i & EVM_ALLOW_METADATA_WRITES) &&
((evm_initialized & EVM_KEY_MASK) != 0) &&
!(evm_initialized & EVM_ALLOW_METADATA_WRITES))
return -EPERM;
if (i & EVM_INIT_HMAC) {
ret = evm_init_key();
if (ret != 0)
return ret;
/* Forbid further writes after the symmetric key is loaded */
i |= EVM_SETUP_COMPLETE;
}
evm_initialized |= i;
/* Don't allow protected metadata modification if a symmetric key
* is loaded
*/
if (evm_initialized & EVM_INIT_HMAC)
evm_initialized &= ~(EVM_ALLOW_METADATA_WRITES);
return count;
}
static const struct file_operations evm_key_ops = {
.read = evm_read_key,
.write = evm_write_key,
};
#ifdef CONFIG_EVM_ADD_XATTRS
/**
* evm_read_xattrs - read() for <securityfs>/evm_xattrs
*
* @filp: file pointer, not actually used
* @buf: where to put the result
* @count: maximum to send along
* @ppos: where to start
*
* Returns number of bytes read or error code, as appropriate
*/
static ssize_t evm_read_xattrs(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
char *temp;
int offset = 0;
ssize_t rc, size = 0;
struct xattr_list *xattr;
if (*ppos != 0)
return 0;
rc = mutex_lock_interruptible(&xattr_list_mutex);
if (rc)
return -ERESTARTSYS;
list_for_each_entry(xattr, &evm_config_xattrnames, list)
size += strlen(xattr->name) + 1;
temp = kmalloc(size + 1, GFP_KERNEL);
if (!temp) {
mutex_unlock(&xattr_list_mutex);
return -ENOMEM;
}
list_for_each_entry(xattr, &evm_config_xattrnames, list) {
sprintf(temp + offset, "%s\n", xattr->name);
offset += strlen(xattr->name) + 1;
}
mutex_unlock(&xattr_list_mutex);
rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp));
kfree(temp);
return rc;
}
/**
* evm_write_xattrs - write() for <securityfs>/evm_xattrs
* @file: file pointer, not actually used
* @buf: where to get the data from
* @count: bytes sent
* @ppos: where to start
*
* Returns number of bytes written or error code, as appropriate
*/
static ssize_t evm_write_xattrs(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
int len, err;
struct xattr_list *xattr, *tmp;
struct audit_buffer *ab;
struct iattr newattrs;
struct inode *inode;
if (!capable(CAP_SYS_ADMIN) || evm_xattrs_locked)
return -EPERM;
if (*ppos != 0)
return -EINVAL;
if (count > XATTR_NAME_MAX)
return -E2BIG;
ab = audit_log_start(audit_context(), GFP_KERNEL,
AUDIT_INTEGRITY_EVM_XATTR);
if (!ab)
return -ENOMEM;
xattr = kmalloc(sizeof(struct xattr_list), GFP_KERNEL);
if (!xattr) {
err = -ENOMEM;
goto out;
}
xattr->name = memdup_user_nul(buf, count);
if (IS_ERR(xattr->name)) {
err = PTR_ERR(xattr->name);
xattr->name = NULL;
goto out;
}
/* Remove any trailing newline */
len = strlen(xattr->name);
if (len && xattr->name[len-1] == '\n')
xattr->name[len-1] = '\0';
audit_log_format(ab, "xattr=");
audit_log_untrustedstring(ab, xattr->name);
if (strcmp(xattr->name, ".") == 0) {
evm_xattrs_locked = 1;
newattrs.ia_mode = S_IFREG | 0440;
newattrs.ia_valid = ATTR_MODE;
inode = evm_xattrs->d_inode;
inode_lock(inode);
err = simple_setattr(&init_user_ns, evm_xattrs, &newattrs);
inode_unlock(inode);
if (!err)
err = count;
goto out;
}
if (strncmp(xattr->name, XATTR_SECURITY_PREFIX,
XATTR_SECURITY_PREFIX_LEN) != 0) {
err = -EINVAL;
goto out;
}
/*
* xattr_list_mutex guards against races in evm_read_xattrs().
* Entries are only added to the evm_config_xattrnames list
* and never deleted. Therefore, the list is traversed
* using list_for_each_entry_lockless() without holding
* the mutex in evm_calc_hmac_or_hash(), evm_find_protected_xattrs()
* and evm_protected_xattr().
*/
mutex_lock(&xattr_list_mutex);
list_for_each_entry(tmp, &evm_config_xattrnames, list) {
if (strcmp(xattr->name, tmp->name) == 0) {
err = -EEXIST;
mutex_unlock(&xattr_list_mutex);
goto out;
}
}
list_add_tail_rcu(&xattr->list, &evm_config_xattrnames);
mutex_unlock(&xattr_list_mutex);
audit_log_format(ab, " res=0");
audit_log_end(ab);
return count;
out:
audit_log_format(ab, " res=%d", err);
audit_log_end(ab);
if (xattr) {
kfree(xattr->name);
kfree(xattr);
}
return err;
}
static const struct file_operations evm_xattr_ops = {
.read = evm_read_xattrs,
.write = evm_write_xattrs,
};
static int evm_init_xattrs(void)
{
evm_xattrs = securityfs_create_file("evm_xattrs", 0660, evm_dir, NULL,
&evm_xattr_ops);
if (!evm_xattrs || IS_ERR(evm_xattrs))
return -EFAULT;
return 0;
}
#else
static int evm_init_xattrs(void)
{
return 0;
}
#endif
int __init evm_init_secfs(void)
{
int error = 0;
evm_dir = securityfs_create_dir("evm", integrity_dir);
if (!evm_dir || IS_ERR(evm_dir))
return -EFAULT;
evm_init_tpm = securityfs_create_file("evm", 0660,
evm_dir, NULL, &evm_key_ops);
if (!evm_init_tpm || IS_ERR(evm_init_tpm)) {
error = -EFAULT;
goto out;
}
evm_symlink = securityfs_create_symlink("evm", NULL,
"integrity/evm/evm", NULL);
if (!evm_symlink || IS_ERR(evm_symlink)) {
error = -EFAULT;
goto out;
}
if (evm_init_xattrs() != 0) {
error = -EFAULT;
goto out;
}
return 0;
out:
securityfs_remove(evm_symlink);
securityfs_remove(evm_init_tpm);
securityfs_remove(evm_dir);
return error;
}