summaryrefslogblamecommitdiffstats
path: root/security/safesetid/securityfs.c
blob: 77d301f0ff7aef41e5e4c93759dc6e894111fd54 (plain) (tree)


































                                                                             
                                                                       
                                 
                                    
   

                                                                    
 
                        
                
                                        
 





                                                           
 
                                                
                
                           
 
                                                     
                
                           
 

                                                                  

                                                      
 












                                                               














                                                           
                                                                 









                                                    

                                                                        











































                                                                          
                                           






















                                                                
// SPDX-License-Identifier: GPL-2.0
/*
 * SafeSetID Linux Security Module
 *
 * Author: Micah Morton <mortonm@chromium.org>
 *
 * Copyright (C) 2018 The Chromium OS Authors.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2, as
 * published by the Free Software Foundation.
 *
 */
#include <linux/security.h>
#include <linux/cred.h>

#include "lsm.h"

static struct dentry *safesetid_policy_dir;

struct safesetid_file_entry {
	const char *name;
	enum safesetid_whitelist_file_write_type type;
	struct dentry *dentry;
};

static struct safesetid_file_entry safesetid_files[] = {
	{.name = "add_whitelist_policy",
	 .type = SAFESETID_WHITELIST_ADD},
	{.name = "flush_whitelist_policies",
	 .type = SAFESETID_WHITELIST_FLUSH},
};

/*
 * In the case the input buffer contains one or more invalid UIDs, the kuid_t
 * variables pointed to by @parent and @child will get updated but this
 * function will return an error.
 * Contents of @buf may be modified.
 */
static int parse_policy_line(
	struct file *file, char *buf, kuid_t *parent, kuid_t *child)
{
	char *child_str;
	int ret;
	u32 parsed_parent, parsed_child;

	/* Format of |buf| string should be <UID>:<UID>. */
	child_str = strchr(buf, ':');
	if (child_str == NULL)
		return -EINVAL;
	*child_str = '\0';
	child_str++;

	ret = kstrtou32(buf, 0, &parsed_parent);
	if (ret)
		return ret;

	ret = kstrtou32(child_str, 0, &parsed_child);
	if (ret)
		return ret;

	*parent = make_kuid(file->f_cred->user_ns, parsed_parent);
	*child = make_kuid(file->f_cred->user_ns, parsed_child);
	if (!uid_valid(*parent) || !uid_valid(*child))
		return -EINVAL;

	return 0;
}

static int parse_safesetid_whitelist_policy(
	struct file *file, const char __user *buf, size_t len,
	kuid_t *parent, kuid_t *child)
{
	char *kern_buf = memdup_user_nul(buf, len);
	int ret;

	if (IS_ERR(kern_buf))
		return PTR_ERR(kern_buf);
	ret = parse_policy_line(file, kern_buf, parent, child);
	kfree(kern_buf);
	return ret;
}

static ssize_t safesetid_file_write(struct file *file,
				    const char __user *buf,
				    size_t len,
				    loff_t *ppos)
{
	struct safesetid_file_entry *file_entry =
		file->f_inode->i_private;
	kuid_t parent;
	kuid_t child;
	int ret;

	if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
		return -EPERM;

	if (*ppos != 0)
		return -EINVAL;

	switch (file_entry->type) {
	case SAFESETID_WHITELIST_FLUSH:
		flush_safesetid_whitelist_entries();
		break;
	case SAFESETID_WHITELIST_ADD:
		ret = parse_safesetid_whitelist_policy(file, buf, len,
						       &parent, &child);
		if (ret)
			return ret;

		ret = add_safesetid_whitelist_entry(parent, child);
		if (ret)
			return ret;
		break;
	default:
		pr_warn("Unknown securityfs file %d\n", file_entry->type);
		break;
	}

	/* Return len on success so caller won't keep trying to write */
	return len;
}

static const struct file_operations safesetid_file_fops = {
	.write = safesetid_file_write,
};

static void safesetid_shutdown_securityfs(void)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) {
		struct safesetid_file_entry *entry =
			&safesetid_files[i];
		securityfs_remove(entry->dentry);
		entry->dentry = NULL;
	}

	securityfs_remove(safesetid_policy_dir);
	safesetid_policy_dir = NULL;
}

static int __init safesetid_init_securityfs(void)
{
	int i;
	int ret;

	if (!safesetid_initialized)
		return 0;

	safesetid_policy_dir = securityfs_create_dir("safesetid", NULL);
	if (IS_ERR(safesetid_policy_dir)) {
		ret = PTR_ERR(safesetid_policy_dir);
		goto error;
	}

	for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) {
		struct safesetid_file_entry *entry =
			&safesetid_files[i];
		entry->dentry = securityfs_create_file(
			entry->name, 0200, safesetid_policy_dir,
			entry, &safesetid_file_fops);
		if (IS_ERR(entry->dentry)) {
			ret = PTR_ERR(entry->dentry);
			goto error;
		}
	}

	return 0;

error:
	safesetid_shutdown_securityfs();
	return ret;
}
fs_initcall(safesetid_init_securityfs);