1aeca4e2cSMicah Morton // SPDX-License-Identifier: GPL-2.0
2aeca4e2cSMicah Morton /*
3aeca4e2cSMicah Morton  * SafeSetID Linux Security Module
4aeca4e2cSMicah Morton  *
5aeca4e2cSMicah Morton  * Author: Micah Morton <mortonm@chromium.org>
6aeca4e2cSMicah Morton  *
7aeca4e2cSMicah Morton  * Copyright (C) 2018 The Chromium OS Authors.
8aeca4e2cSMicah Morton  *
9aeca4e2cSMicah Morton  * This program is free software; you can redistribute it and/or modify
10aeca4e2cSMicah Morton  * it under the terms of the GNU General Public License version 2, as
11aeca4e2cSMicah Morton  * published by the Free Software Foundation.
12aeca4e2cSMicah Morton  *
13aeca4e2cSMicah Morton  */
14aeca4e2cSMicah Morton #include <linux/security.h>
15aeca4e2cSMicah Morton #include <linux/cred.h>
16aeca4e2cSMicah Morton 
17aeca4e2cSMicah Morton #include "lsm.h"
18aeca4e2cSMicah Morton 
19aeca4e2cSMicah Morton static struct dentry *safesetid_policy_dir;
20aeca4e2cSMicah Morton 
21aeca4e2cSMicah Morton struct safesetid_file_entry {
22aeca4e2cSMicah Morton 	const char *name;
23aeca4e2cSMicah Morton 	enum safesetid_whitelist_file_write_type type;
24aeca4e2cSMicah Morton 	struct dentry *dentry;
25aeca4e2cSMicah Morton };
26aeca4e2cSMicah Morton 
27aeca4e2cSMicah Morton static struct safesetid_file_entry safesetid_files[] = {
28aeca4e2cSMicah Morton 	{.name = "add_whitelist_policy",
29aeca4e2cSMicah Morton 	 .type = SAFESETID_WHITELIST_ADD},
30aeca4e2cSMicah Morton 	{.name = "flush_whitelist_policies",
31aeca4e2cSMicah Morton 	 .type = SAFESETID_WHITELIST_FLUSH},
32aeca4e2cSMicah Morton };
33aeca4e2cSMicah Morton 
34aeca4e2cSMicah Morton /*
35aeca4e2cSMicah Morton  * In the case the input buffer contains one or more invalid UIDs, the kuid_t
3678ae7df9SJann Horn  * variables pointed to by @parent and @child will get updated but this
37aeca4e2cSMicah Morton  * function will return an error.
3878ae7df9SJann Horn  * Contents of @buf may be modified.
39aeca4e2cSMicah Morton  */
4078ae7df9SJann Horn static int parse_policy_line(
4178ae7df9SJann Horn 	struct file *file, char *buf, kuid_t *parent, kuid_t *child)
42aeca4e2cSMicah Morton {
4378ae7df9SJann Horn 	char *child_str;
44aeca4e2cSMicah Morton 	int ret;
4578ae7df9SJann Horn 	u32 parsed_parent, parsed_child;
46aeca4e2cSMicah Morton 
4778ae7df9SJann Horn 	/* Format of |buf| string should be <UID>:<UID>. */
4878ae7df9SJann Horn 	child_str = strchr(buf, ':');
4978ae7df9SJann Horn 	if (child_str == NULL)
5078ae7df9SJann Horn 		return -EINVAL;
5178ae7df9SJann Horn 	*child_str = '\0';
5278ae7df9SJann Horn 	child_str++;
53aeca4e2cSMicah Morton 
5478ae7df9SJann Horn 	ret = kstrtou32(buf, 0, &parsed_parent);
55aeca4e2cSMicah Morton 	if (ret)
5678ae7df9SJann Horn 		return ret;
57aeca4e2cSMicah Morton 
5878ae7df9SJann Horn 	ret = kstrtou32(child_str, 0, &parsed_child);
59aeca4e2cSMicah Morton 	if (ret)
6078ae7df9SJann Horn 		return ret;
61aeca4e2cSMicah Morton 
62aeca4e2cSMicah Morton 	*parent = make_kuid(current_user_ns(), parsed_parent);
63aeca4e2cSMicah Morton 	*child = make_kuid(current_user_ns(), parsed_child);
6478ae7df9SJann Horn 	if (!uid_valid(*parent) || !uid_valid(*child))
6578ae7df9SJann Horn 		return -EINVAL;
6678ae7df9SJann Horn 
6778ae7df9SJann Horn 	return 0;
68aeca4e2cSMicah Morton }
69aeca4e2cSMicah Morton 
7078ae7df9SJann Horn static int parse_safesetid_whitelist_policy(
7178ae7df9SJann Horn 	struct file *file, const char __user *buf, size_t len,
7278ae7df9SJann Horn 	kuid_t *parent, kuid_t *child)
7378ae7df9SJann Horn {
7478ae7df9SJann Horn 	char *kern_buf = memdup_user_nul(buf, len);
7578ae7df9SJann Horn 	int ret;
7678ae7df9SJann Horn 
7778ae7df9SJann Horn 	if (IS_ERR(kern_buf))
7878ae7df9SJann Horn 		return PTR_ERR(kern_buf);
7978ae7df9SJann Horn 	ret = parse_policy_line(file, kern_buf, parent, child);
80aeca4e2cSMicah Morton 	kfree(kern_buf);
81aeca4e2cSMicah Morton 	return ret;
82aeca4e2cSMicah Morton }
83aeca4e2cSMicah Morton 
84aeca4e2cSMicah Morton static ssize_t safesetid_file_write(struct file *file,
85aeca4e2cSMicah Morton 				    const char __user *buf,
86aeca4e2cSMicah Morton 				    size_t len,
87aeca4e2cSMicah Morton 				    loff_t *ppos)
88aeca4e2cSMicah Morton {
89aeca4e2cSMicah Morton 	struct safesetid_file_entry *file_entry =
90aeca4e2cSMicah Morton 		file->f_inode->i_private;
91aeca4e2cSMicah Morton 	kuid_t parent;
92aeca4e2cSMicah Morton 	kuid_t child;
93aeca4e2cSMicah Morton 	int ret;
94aeca4e2cSMicah Morton 
95aeca4e2cSMicah Morton 	if (!ns_capable(current_user_ns(), CAP_MAC_ADMIN))
96aeca4e2cSMicah Morton 		return -EPERM;
97aeca4e2cSMicah Morton 
98aeca4e2cSMicah Morton 	if (*ppos != 0)
99aeca4e2cSMicah Morton 		return -EINVAL;
100aeca4e2cSMicah Morton 
101aeca4e2cSMicah Morton 	switch (file_entry->type) {
102aeca4e2cSMicah Morton 	case SAFESETID_WHITELIST_FLUSH:
103aeca4e2cSMicah Morton 		flush_safesetid_whitelist_entries();
104aeca4e2cSMicah Morton 		break;
105aeca4e2cSMicah Morton 	case SAFESETID_WHITELIST_ADD:
10678ae7df9SJann Horn 		ret = parse_safesetid_whitelist_policy(file, buf, len,
10778ae7df9SJann Horn 						       &parent, &child);
108aeca4e2cSMicah Morton 		if (ret)
109aeca4e2cSMicah Morton 			return ret;
110aeca4e2cSMicah Morton 
111aeca4e2cSMicah Morton 		ret = add_safesetid_whitelist_entry(parent, child);
112aeca4e2cSMicah Morton 		if (ret)
113aeca4e2cSMicah Morton 			return ret;
114aeca4e2cSMicah Morton 		break;
115aeca4e2cSMicah Morton 	default:
116aeca4e2cSMicah Morton 		pr_warn("Unknown securityfs file %d\n", file_entry->type);
117aeca4e2cSMicah Morton 		break;
118aeca4e2cSMicah Morton 	}
119aeca4e2cSMicah Morton 
120aeca4e2cSMicah Morton 	/* Return len on success so caller won't keep trying to write */
121aeca4e2cSMicah Morton 	return len;
122aeca4e2cSMicah Morton }
123aeca4e2cSMicah Morton 
124aeca4e2cSMicah Morton static const struct file_operations safesetid_file_fops = {
125aeca4e2cSMicah Morton 	.write = safesetid_file_write,
126aeca4e2cSMicah Morton };
127aeca4e2cSMicah Morton 
128aeca4e2cSMicah Morton static void safesetid_shutdown_securityfs(void)
129aeca4e2cSMicah Morton {
130aeca4e2cSMicah Morton 	int i;
131aeca4e2cSMicah Morton 
132aeca4e2cSMicah Morton 	for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) {
133aeca4e2cSMicah Morton 		struct safesetid_file_entry *entry =
134aeca4e2cSMicah Morton 			&safesetid_files[i];
135aeca4e2cSMicah Morton 		securityfs_remove(entry->dentry);
136aeca4e2cSMicah Morton 		entry->dentry = NULL;
137aeca4e2cSMicah Morton 	}
138aeca4e2cSMicah Morton 
139aeca4e2cSMicah Morton 	securityfs_remove(safesetid_policy_dir);
140aeca4e2cSMicah Morton 	safesetid_policy_dir = NULL;
141aeca4e2cSMicah Morton }
142aeca4e2cSMicah Morton 
143aeca4e2cSMicah Morton static int __init safesetid_init_securityfs(void)
144aeca4e2cSMicah Morton {
145aeca4e2cSMicah Morton 	int i;
146aeca4e2cSMicah Morton 	int ret;
147aeca4e2cSMicah Morton 
148aeca4e2cSMicah Morton 	if (!safesetid_initialized)
149aeca4e2cSMicah Morton 		return 0;
150aeca4e2cSMicah Morton 
151aeca4e2cSMicah Morton 	safesetid_policy_dir = securityfs_create_dir("safesetid", NULL);
152e7a44cfdSWei Yongjun 	if (IS_ERR(safesetid_policy_dir)) {
153aeca4e2cSMicah Morton 		ret = PTR_ERR(safesetid_policy_dir);
154aeca4e2cSMicah Morton 		goto error;
155aeca4e2cSMicah Morton 	}
156aeca4e2cSMicah Morton 
157aeca4e2cSMicah Morton 	for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) {
158aeca4e2cSMicah Morton 		struct safesetid_file_entry *entry =
159aeca4e2cSMicah Morton 			&safesetid_files[i];
160aeca4e2cSMicah Morton 		entry->dentry = securityfs_create_file(
161aeca4e2cSMicah Morton 			entry->name, 0200, safesetid_policy_dir,
162aeca4e2cSMicah Morton 			entry, &safesetid_file_fops);
163aeca4e2cSMicah Morton 		if (IS_ERR(entry->dentry)) {
164aeca4e2cSMicah Morton 			ret = PTR_ERR(entry->dentry);
165aeca4e2cSMicah Morton 			goto error;
166aeca4e2cSMicah Morton 		}
167aeca4e2cSMicah Morton 	}
168aeca4e2cSMicah Morton 
169aeca4e2cSMicah Morton 	return 0;
170aeca4e2cSMicah Morton 
171aeca4e2cSMicah Morton error:
172aeca4e2cSMicah Morton 	safesetid_shutdown_securityfs();
173aeca4e2cSMicah Morton 	return ret;
174aeca4e2cSMicah Morton }
175aeca4e2cSMicah Morton fs_initcall(safesetid_init_securityfs);
176