2019-01-16 23:46:06 +08:00
|
|
|
// 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
|
2019-04-11 00:55:48 +08:00
|
|
|
* variables pointed to by @parent and @child will get updated but this
|
2019-01-16 23:46:06 +08:00
|
|
|
* function will return an error.
|
2019-04-11 00:55:48 +08:00
|
|
|
* Contents of @buf may be modified.
|
2019-01-16 23:46:06 +08:00
|
|
|
*/
|
2019-04-11 00:55:48 +08:00
|
|
|
static int parse_policy_line(
|
|
|
|
struct file *file, char *buf, kuid_t *parent, kuid_t *child)
|
2019-01-16 23:46:06 +08:00
|
|
|
{
|
2019-04-11 00:55:48 +08:00
|
|
|
char *child_str;
|
2019-01-16 23:46:06 +08:00
|
|
|
int ret;
|
2019-04-11 00:55:48 +08:00
|
|
|
u32 parsed_parent, parsed_child;
|
2019-01-16 23:46:06 +08:00
|
|
|
|
2019-04-11 00:55:48 +08:00
|
|
|
/* Format of |buf| string should be <UID>:<UID>. */
|
|
|
|
child_str = strchr(buf, ':');
|
|
|
|
if (child_str == NULL)
|
|
|
|
return -EINVAL;
|
|
|
|
*child_str = '\0';
|
|
|
|
child_str++;
|
2019-01-16 23:46:06 +08:00
|
|
|
|
2019-04-11 00:55:48 +08:00
|
|
|
ret = kstrtou32(buf, 0, &parsed_parent);
|
2019-01-16 23:46:06 +08:00
|
|
|
if (ret)
|
2019-04-11 00:55:48 +08:00
|
|
|
return ret;
|
2019-01-16 23:46:06 +08:00
|
|
|
|
2019-04-11 00:55:48 +08:00
|
|
|
ret = kstrtou32(child_str, 0, &parsed_child);
|
2019-01-16 23:46:06 +08:00
|
|
|
if (ret)
|
2019-04-11 00:55:48 +08:00
|
|
|
return ret;
|
2019-01-16 23:46:06 +08:00
|
|
|
|
2019-04-11 00:55:58 +08:00
|
|
|
*parent = make_kuid(file->f_cred->user_ns, parsed_parent);
|
|
|
|
*child = make_kuid(file->f_cred->user_ns, parsed_child);
|
2019-04-11 00:55:48 +08:00
|
|
|
if (!uid_valid(*parent) || !uid_valid(*child))
|
|
|
|
return -EINVAL;
|
2019-01-16 23:46:06 +08:00
|
|
|
|
2019-04-11 00:55:48 +08:00
|
|
|
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);
|
2019-01-16 23:46:06 +08:00
|
|
|
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;
|
|
|
|
|
2019-04-11 00:55:58 +08:00
|
|
|
if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
|
2019-01-16 23:46:06 +08:00
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
if (*ppos != 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
switch (file_entry->type) {
|
|
|
|
case SAFESETID_WHITELIST_FLUSH:
|
|
|
|
flush_safesetid_whitelist_entries();
|
|
|
|
break;
|
|
|
|
case SAFESETID_WHITELIST_ADD:
|
2019-04-11 00:55:48 +08:00
|
|
|
ret = parse_safesetid_whitelist_policy(file, buf, len,
|
|
|
|
&parent, &child);
|
2019-01-16 23:46:06 +08:00
|
|
|
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);
|
2019-02-12 15:58:31 +08:00
|
|
|
if (IS_ERR(safesetid_policy_dir)) {
|
2019-01-16 23:46:06 +08:00
|
|
|
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);
|