1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * SafeSetID Linux Security Module
4  *
5  * Author: Micah Morton <mortonm@chromium.org>
6  *
7  * Copyright (C) 2018 The Chromium OS Authors.
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License version 2, as
11  * published by the Free Software Foundation.
12  *
13  */
14 #include <linux/security.h>
15 #include <linux/cred.h>
16 
17 #include "lsm.h"
18 
19 static struct dentry *safesetid_policy_dir;
20 
21 struct safesetid_file_entry {
22 	const char *name;
23 	enum safesetid_whitelist_file_write_type type;
24 	struct dentry *dentry;
25 };
26 
27 static struct safesetid_file_entry safesetid_files[] = {
28 	{.name = "add_whitelist_policy",
29 	 .type = SAFESETID_WHITELIST_ADD},
30 	{.name = "flush_whitelist_policies",
31 	 .type = SAFESETID_WHITELIST_FLUSH},
32 };
33 
34 /*
35  * In the case the input buffer contains one or more invalid UIDs, the kuid_t
36  * variables pointed to by 'parent' and 'child' will get updated but this
37  * function will return an error.
38  */
39 static int parse_safesetid_whitelist_policy(const char __user *buf,
40 					    size_t len,
41 					    kuid_t *parent,
42 					    kuid_t *child)
43 {
44 	char *kern_buf;
45 	char *parent_buf;
46 	char *child_buf;
47 	const char separator[] = ":";
48 	int ret;
49 	size_t first_substring_length;
50 	long parsed_parent;
51 	long parsed_child;
52 
53 	/* Duplicate string from user memory and NULL-terminate */
54 	kern_buf = memdup_user_nul(buf, len);
55 	if (IS_ERR(kern_buf))
56 		return PTR_ERR(kern_buf);
57 
58 	/*
59 	 * Format of |buf| string should be <UID>:<UID>.
60 	 * Find location of ":" in kern_buf (copied from |buf|).
61 	 */
62 	first_substring_length = strcspn(kern_buf, separator);
63 	if (first_substring_length == 0 || first_substring_length == len) {
64 		ret = -EINVAL;
65 		goto free_kern;
66 	}
67 
68 	parent_buf = kmemdup_nul(kern_buf, first_substring_length, GFP_KERNEL);
69 	if (!parent_buf) {
70 		ret = -ENOMEM;
71 		goto free_kern;
72 	}
73 
74 	ret = kstrtol(parent_buf, 0, &parsed_parent);
75 	if (ret)
76 		goto free_both;
77 
78 	child_buf = kern_buf + first_substring_length + 1;
79 	ret = kstrtol(child_buf, 0, &parsed_child);
80 	if (ret)
81 		goto free_both;
82 
83 	*parent = make_kuid(current_user_ns(), parsed_parent);
84 	if (!uid_valid(*parent)) {
85 		ret = -EINVAL;
86 		goto free_both;
87 	}
88 
89 	*child = make_kuid(current_user_ns(), parsed_child);
90 	if (!uid_valid(*child)) {
91 		ret = -EINVAL;
92 		goto free_both;
93 	}
94 
95 free_both:
96 	kfree(parent_buf);
97 free_kern:
98 	kfree(kern_buf);
99 	return ret;
100 }
101 
102 static ssize_t safesetid_file_write(struct file *file,
103 				    const char __user *buf,
104 				    size_t len,
105 				    loff_t *ppos)
106 {
107 	struct safesetid_file_entry *file_entry =
108 		file->f_inode->i_private;
109 	kuid_t parent;
110 	kuid_t child;
111 	int ret;
112 
113 	if (!ns_capable(current_user_ns(), CAP_MAC_ADMIN))
114 		return -EPERM;
115 
116 	if (*ppos != 0)
117 		return -EINVAL;
118 
119 	switch (file_entry->type) {
120 	case SAFESETID_WHITELIST_FLUSH:
121 		flush_safesetid_whitelist_entries();
122 		break;
123 	case SAFESETID_WHITELIST_ADD:
124 		ret = parse_safesetid_whitelist_policy(buf, len, &parent,
125 								 &child);
126 		if (ret)
127 			return ret;
128 
129 		ret = add_safesetid_whitelist_entry(parent, child);
130 		if (ret)
131 			return ret;
132 		break;
133 	default:
134 		pr_warn("Unknown securityfs file %d\n", file_entry->type);
135 		break;
136 	}
137 
138 	/* Return len on success so caller won't keep trying to write */
139 	return len;
140 }
141 
142 static const struct file_operations safesetid_file_fops = {
143 	.write = safesetid_file_write,
144 };
145 
146 static void safesetid_shutdown_securityfs(void)
147 {
148 	int i;
149 
150 	for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) {
151 		struct safesetid_file_entry *entry =
152 			&safesetid_files[i];
153 		securityfs_remove(entry->dentry);
154 		entry->dentry = NULL;
155 	}
156 
157 	securityfs_remove(safesetid_policy_dir);
158 	safesetid_policy_dir = NULL;
159 }
160 
161 static int __init safesetid_init_securityfs(void)
162 {
163 	int i;
164 	int ret;
165 
166 	if (!safesetid_initialized)
167 		return 0;
168 
169 	safesetid_policy_dir = securityfs_create_dir("safesetid", NULL);
170 	if (IS_ERR(safesetid_policy_dir)) {
171 		ret = PTR_ERR(safesetid_policy_dir);
172 		goto error;
173 	}
174 
175 	for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) {
176 		struct safesetid_file_entry *entry =
177 			&safesetid_files[i];
178 		entry->dentry = securityfs_create_file(
179 			entry->name, 0200, safesetid_policy_dir,
180 			entry, &safesetid_file_fops);
181 		if (IS_ERR(entry->dentry)) {
182 			ret = PTR_ERR(entry->dentry);
183 			goto error;
184 		}
185 	}
186 
187 	return 0;
188 
189 error:
190 	safesetid_shutdown_securityfs();
191 	return ret;
192 }
193 fs_initcall(safesetid_init_securityfs);
194