Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/klassert/ipsec
[linux-2.6-microblaze.git] / security / safesetid / securityfs.c
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);