diff options
Diffstat (limited to 'security/safesetid/securityfs.c')
-rw-r--r-- | security/safesetid/securityfs.c | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/security/safesetid/securityfs.c b/security/safesetid/securityfs.c new file mode 100644 index 000000000000..2c6c829be044 --- /dev/null +++ b/security/safesetid/securityfs.c | |||
@@ -0,0 +1,193 @@ | |||
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); | ||