root/security/integrity/evm/evm_secfs.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_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)
{
        unsigned int i;
        int ret;

        if (!capable(CAP_SYS_ADMIN) || (evm_initialized & EVM_SETUP_COMPLETE))
                return -EPERM;

        ret = kstrtouint_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 enable metadata writes if
         * an HMAC key is loaded.
         */
        if ((i & EVM_ALLOW_METADATA_WRITES) &&
            (evm_initialized & EVM_INIT_HMAC) != 0)
                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) {
                if (!xattr->enabled)
                        continue;

                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) {
                if (!xattr->enabled)
                        continue;

                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 && IS_ENABLED(CONFIG_AUDIT))
                return -ENOMEM;

        xattr = kmalloc_obj(struct xattr_list);
        if (!xattr) {
                err = -ENOMEM;
                goto out;
        }

        xattr->enabled = true;
        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(&nop_mnt_idmap, 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;
                        if (!tmp->enabled) {
                                tmp->enabled = true;
                                err = count;
                        }
                        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 < 0) ? err : 0);
        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 (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;
        struct dentry *dentry;

        error = integrity_fs_init();
        if (error < 0)
                return -EFAULT;

        evm_dir = securityfs_create_dir("evm", integrity_dir);
        if (IS_ERR(evm_dir)) {
                error = -EFAULT;
                goto out;
        }

        dentry = securityfs_create_file("evm", 0660,
                                      evm_dir, NULL, &evm_key_ops);
        if (IS_ERR(dentry)) {
                error = -EFAULT;
                goto out;
        }

        evm_symlink = securityfs_create_symlink("evm", NULL,
                                                "integrity/evm/evm", NULL);
        if (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_dir);
        integrity_fs_fini();
        return error;
}