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
36aeca4e2cSMicah Morton  * variables pointed to by 'parent' and 'child' will get updated but this
37aeca4e2cSMicah Morton  * function will return an error.
38aeca4e2cSMicah Morton  */
39aeca4e2cSMicah Morton static int parse_safesetid_whitelist_policy(const char __user *buf,
40aeca4e2cSMicah Morton 					    size_t len,
41aeca4e2cSMicah Morton 					    kuid_t *parent,
42aeca4e2cSMicah Morton 					    kuid_t *child)
43aeca4e2cSMicah Morton {
44aeca4e2cSMicah Morton 	char *kern_buf;
45aeca4e2cSMicah Morton 	char *parent_buf;
46aeca4e2cSMicah Morton 	char *child_buf;
47aeca4e2cSMicah Morton 	const char separator[] = ":";
48aeca4e2cSMicah Morton 	int ret;
49aeca4e2cSMicah Morton 	size_t first_substring_length;
50aeca4e2cSMicah Morton 	long parsed_parent;
51aeca4e2cSMicah Morton 	long parsed_child;
52aeca4e2cSMicah Morton 
53aeca4e2cSMicah Morton 	/* Duplicate string from user memory and NULL-terminate */
54aeca4e2cSMicah Morton 	kern_buf = memdup_user_nul(buf, len);
55aeca4e2cSMicah Morton 	if (IS_ERR(kern_buf))
56aeca4e2cSMicah Morton 		return PTR_ERR(kern_buf);
57aeca4e2cSMicah Morton 
58aeca4e2cSMicah Morton 	/*
59aeca4e2cSMicah Morton 	 * Format of |buf| string should be <UID>:<UID>.
60aeca4e2cSMicah Morton 	 * Find location of ":" in kern_buf (copied from |buf|).
61aeca4e2cSMicah Morton 	 */
62aeca4e2cSMicah Morton 	first_substring_length = strcspn(kern_buf, separator);
63aeca4e2cSMicah Morton 	if (first_substring_length == 0 || first_substring_length == len) {
64aeca4e2cSMicah Morton 		ret = -EINVAL;
65aeca4e2cSMicah Morton 		goto free_kern;
66aeca4e2cSMicah Morton 	}
67aeca4e2cSMicah Morton 
68aeca4e2cSMicah Morton 	parent_buf = kmemdup_nul(kern_buf, first_substring_length, GFP_KERNEL);
69aeca4e2cSMicah Morton 	if (!parent_buf) {
70aeca4e2cSMicah Morton 		ret = -ENOMEM;
71aeca4e2cSMicah Morton 		goto free_kern;
72aeca4e2cSMicah Morton 	}
73aeca4e2cSMicah Morton 
74aeca4e2cSMicah Morton 	ret = kstrtol(parent_buf, 0, &parsed_parent);
75aeca4e2cSMicah Morton 	if (ret)
76aeca4e2cSMicah Morton 		goto free_both;
77aeca4e2cSMicah Morton 
78aeca4e2cSMicah Morton 	child_buf = kern_buf + first_substring_length + 1;
79aeca4e2cSMicah Morton 	ret = kstrtol(child_buf, 0, &parsed_child);
80aeca4e2cSMicah Morton 	if (ret)
81aeca4e2cSMicah Morton 		goto free_both;
82aeca4e2cSMicah Morton 
83aeca4e2cSMicah Morton 	*parent = make_kuid(current_user_ns(), parsed_parent);
84aeca4e2cSMicah Morton 	if (!uid_valid(*parent)) {
85aeca4e2cSMicah Morton 		ret = -EINVAL;
86aeca4e2cSMicah Morton 		goto free_both;
87aeca4e2cSMicah Morton 	}
88aeca4e2cSMicah Morton 
89aeca4e2cSMicah Morton 	*child = make_kuid(current_user_ns(), parsed_child);
90aeca4e2cSMicah Morton 	if (!uid_valid(*child)) {
91aeca4e2cSMicah Morton 		ret = -EINVAL;
92aeca4e2cSMicah Morton 		goto free_both;
93aeca4e2cSMicah Morton 	}
94aeca4e2cSMicah Morton 
95aeca4e2cSMicah Morton free_both:
96aeca4e2cSMicah Morton 	kfree(parent_buf);
97aeca4e2cSMicah Morton free_kern:
98aeca4e2cSMicah Morton 	kfree(kern_buf);
99aeca4e2cSMicah Morton 	return ret;
100aeca4e2cSMicah Morton }
101aeca4e2cSMicah Morton 
102aeca4e2cSMicah Morton static ssize_t safesetid_file_write(struct file *file,
103aeca4e2cSMicah Morton 				    const char __user *buf,
104aeca4e2cSMicah Morton 				    size_t len,
105aeca4e2cSMicah Morton 				    loff_t *ppos)
106aeca4e2cSMicah Morton {
107aeca4e2cSMicah Morton 	struct safesetid_file_entry *file_entry =
108aeca4e2cSMicah Morton 		file->f_inode->i_private;
109aeca4e2cSMicah Morton 	kuid_t parent;
110aeca4e2cSMicah Morton 	kuid_t child;
111aeca4e2cSMicah Morton 	int ret;
112aeca4e2cSMicah Morton 
113aeca4e2cSMicah Morton 	if (!ns_capable(current_user_ns(), CAP_MAC_ADMIN))
114aeca4e2cSMicah Morton 		return -EPERM;
115aeca4e2cSMicah Morton 
116aeca4e2cSMicah Morton 	if (*ppos != 0)
117aeca4e2cSMicah Morton 		return -EINVAL;
118aeca4e2cSMicah Morton 
119aeca4e2cSMicah Morton 	switch (file_entry->type) {
120aeca4e2cSMicah Morton 	case SAFESETID_WHITELIST_FLUSH:
121aeca4e2cSMicah Morton 		flush_safesetid_whitelist_entries();
122aeca4e2cSMicah Morton 		break;
123aeca4e2cSMicah Morton 	case SAFESETID_WHITELIST_ADD:
124aeca4e2cSMicah Morton 		ret = parse_safesetid_whitelist_policy(buf, len, &parent,
125aeca4e2cSMicah Morton 								 &child);
126aeca4e2cSMicah Morton 		if (ret)
127aeca4e2cSMicah Morton 			return ret;
128aeca4e2cSMicah Morton 
129aeca4e2cSMicah Morton 		ret = add_safesetid_whitelist_entry(parent, child);
130aeca4e2cSMicah Morton 		if (ret)
131aeca4e2cSMicah Morton 			return ret;
132aeca4e2cSMicah Morton 		break;
133aeca4e2cSMicah Morton 	default:
134aeca4e2cSMicah Morton 		pr_warn("Unknown securityfs file %d\n", file_entry->type);
135aeca4e2cSMicah Morton 		break;
136aeca4e2cSMicah Morton 	}
137aeca4e2cSMicah Morton 
138aeca4e2cSMicah Morton 	/* Return len on success so caller won't keep trying to write */
139aeca4e2cSMicah Morton 	return len;
140aeca4e2cSMicah Morton }
141aeca4e2cSMicah Morton 
142aeca4e2cSMicah Morton static const struct file_operations safesetid_file_fops = {
143aeca4e2cSMicah Morton 	.write = safesetid_file_write,
144aeca4e2cSMicah Morton };
145aeca4e2cSMicah Morton 
146aeca4e2cSMicah Morton static void safesetid_shutdown_securityfs(void)
147aeca4e2cSMicah Morton {
148aeca4e2cSMicah Morton 	int i;
149aeca4e2cSMicah Morton 
150aeca4e2cSMicah Morton 	for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) {
151aeca4e2cSMicah Morton 		struct safesetid_file_entry *entry =
152aeca4e2cSMicah Morton 			&safesetid_files[i];
153aeca4e2cSMicah Morton 		securityfs_remove(entry->dentry);
154aeca4e2cSMicah Morton 		entry->dentry = NULL;
155aeca4e2cSMicah Morton 	}
156aeca4e2cSMicah Morton 
157aeca4e2cSMicah Morton 	securityfs_remove(safesetid_policy_dir);
158aeca4e2cSMicah Morton 	safesetid_policy_dir = NULL;
159aeca4e2cSMicah Morton }
160aeca4e2cSMicah Morton 
161aeca4e2cSMicah Morton static int __init safesetid_init_securityfs(void)
162aeca4e2cSMicah Morton {
163aeca4e2cSMicah Morton 	int i;
164aeca4e2cSMicah Morton 	int ret;
165aeca4e2cSMicah Morton 
166aeca4e2cSMicah Morton 	if (!safesetid_initialized)
167aeca4e2cSMicah Morton 		return 0;
168aeca4e2cSMicah Morton 
169aeca4e2cSMicah Morton 	safesetid_policy_dir = securityfs_create_dir("safesetid", NULL);
170e7a44cfdSWei Yongjun 	if (IS_ERR(safesetid_policy_dir)) {
171aeca4e2cSMicah Morton 		ret = PTR_ERR(safesetid_policy_dir);
172aeca4e2cSMicah Morton 		goto error;
173aeca4e2cSMicah Morton 	}
174aeca4e2cSMicah Morton 
175aeca4e2cSMicah Morton 	for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) {
176aeca4e2cSMicah Morton 		struct safesetid_file_entry *entry =
177aeca4e2cSMicah Morton 			&safesetid_files[i];
178aeca4e2cSMicah Morton 		entry->dentry = securityfs_create_file(
179aeca4e2cSMicah Morton 			entry->name, 0200, safesetid_policy_dir,
180aeca4e2cSMicah Morton 			entry, &safesetid_file_fops);
181aeca4e2cSMicah Morton 		if (IS_ERR(entry->dentry)) {
182aeca4e2cSMicah Morton 			ret = PTR_ERR(entry->dentry);
183aeca4e2cSMicah Morton 			goto error;
184aeca4e2cSMicah Morton 		}
185aeca4e2cSMicah Morton 	}
186aeca4e2cSMicah Morton 
187aeca4e2cSMicah Morton 	return 0;
188aeca4e2cSMicah Morton 
189aeca4e2cSMicah Morton error:
190aeca4e2cSMicah Morton 	safesetid_shutdown_securityfs();
191aeca4e2cSMicah Morton 	return ret;
192aeca4e2cSMicah Morton }
193aeca4e2cSMicah Morton fs_initcall(safesetid_init_securityfs);
194