mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	In case of error, the function securityfs_create_dir() returns ERR_PTR()
and never returns NULL. The NULL test in the return value check should
be replaced with IS_ERR().
Fixes: aeca4e2ca6 ("LSM: add SafeSetID module that gates setid calls")
Signed-off-by: Wei Yongjun <weiyongjun1@huawei.com>
Acked-by: Kees Cook <keescook@chromium.org>
Signed-off-by: James Morris <james.morris@microsoft.com>
		
	
			
		
			
				
	
	
		
			193 lines
		
	
	
	
		
			4.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
	
		
			4.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// 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.
 | 
						|
 */
 | 
						|
static int parse_safesetid_whitelist_policy(const char __user *buf,
 | 
						|
					    size_t len,
 | 
						|
					    kuid_t *parent,
 | 
						|
					    kuid_t *child)
 | 
						|
{
 | 
						|
	char *kern_buf;
 | 
						|
	char *parent_buf;
 | 
						|
	char *child_buf;
 | 
						|
	const char separator[] = ":";
 | 
						|
	int ret;
 | 
						|
	size_t first_substring_length;
 | 
						|
	long parsed_parent;
 | 
						|
	long parsed_child;
 | 
						|
 | 
						|
	/* Duplicate string from user memory and NULL-terminate */
 | 
						|
	kern_buf = memdup_user_nul(buf, len);
 | 
						|
	if (IS_ERR(kern_buf))
 | 
						|
		return PTR_ERR(kern_buf);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Format of |buf| string should be <UID>:<UID>.
 | 
						|
	 * Find location of ":" in kern_buf (copied from |buf|).
 | 
						|
	 */
 | 
						|
	first_substring_length = strcspn(kern_buf, separator);
 | 
						|
	if (first_substring_length == 0 || first_substring_length == len) {
 | 
						|
		ret = -EINVAL;
 | 
						|
		goto free_kern;
 | 
						|
	}
 | 
						|
 | 
						|
	parent_buf = kmemdup_nul(kern_buf, first_substring_length, GFP_KERNEL);
 | 
						|
	if (!parent_buf) {
 | 
						|
		ret = -ENOMEM;
 | 
						|
		goto free_kern;
 | 
						|
	}
 | 
						|
 | 
						|
	ret = kstrtol(parent_buf, 0, &parsed_parent);
 | 
						|
	if (ret)
 | 
						|
		goto free_both;
 | 
						|
 | 
						|
	child_buf = kern_buf + first_substring_length + 1;
 | 
						|
	ret = kstrtol(child_buf, 0, &parsed_child);
 | 
						|
	if (ret)
 | 
						|
		goto free_both;
 | 
						|
 | 
						|
	*parent = make_kuid(current_user_ns(), parsed_parent);
 | 
						|
	if (!uid_valid(*parent)) {
 | 
						|
		ret = -EINVAL;
 | 
						|
		goto free_both;
 | 
						|
	}
 | 
						|
 | 
						|
	*child = make_kuid(current_user_ns(), parsed_child);
 | 
						|
	if (!uid_valid(*child)) {
 | 
						|
		ret = -EINVAL;
 | 
						|
		goto free_both;
 | 
						|
	}
 | 
						|
 | 
						|
free_both:
 | 
						|
	kfree(parent_buf);
 | 
						|
free_kern:
 | 
						|
	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 (!ns_capable(current_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(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);
 |