Blame view
security/device_cgroup.c
21.1 KB
b24413180 License cleanup: ... |
1 |
// SPDX-License-Identifier: GPL-2.0 |
08ce5f16e cgroups: implemen... |
2 |
/* |
47c59803b devcgroup: remove... |
3 |
* device_cgroup.c - device cgroup subsystem |
08ce5f16e cgroups: implemen... |
4 5 6 7 8 9 10 11 12 |
* * Copyright 2007 IBM Corp */ #include <linux/device_cgroup.h> #include <linux/cgroup.h> #include <linux/ctype.h> #include <linux/list.h> #include <linux/uaccess.h> |
29486df32 cgroups: introduc... |
13 |
#include <linux/seq_file.h> |
5a0e3ad6a include cleanup: ... |
14 |
#include <linux/slab.h> |
47c59803b devcgroup: remove... |
15 |
#include <linux/rcupdate.h> |
b4046f00e devcgroup: avoid ... |
16 |
#include <linux/mutex.h> |
08ce5f16e cgroups: implemen... |
17 |
|
eec8fd027 device_cgroup: Cl... |
18 |
#ifdef CONFIG_CGROUP_DEVICE |
b4046f00e devcgroup: avoid ... |
19 |
static DEFINE_MUTEX(devcgroup_mutex); |
c39a2a301 devcg: prepare ma... |
20 21 22 23 24 |
enum devcg_behavior { DEVCG_DEFAULT_NONE, DEVCG_DEFAULT_ALLOW, DEVCG_DEFAULT_DENY, }; |
08ce5f16e cgroups: implemen... |
25 |
/* |
db9aeca97 device_cgroup: re... |
26 |
* exception list locking rules: |
b4046f00e devcgroup: avoid ... |
27 |
* hold devcgroup_mutex for update/read. |
47c59803b devcgroup: remove... |
28 |
* hold rcu_read_lock() for read. |
08ce5f16e cgroups: implemen... |
29 |
*/ |
db9aeca97 device_cgroup: re... |
30 |
struct dev_exception_item { |
08ce5f16e cgroups: implemen... |
31 32 33 34 |
u32 major, minor; short type; short access; struct list_head list; |
4efd1a1b2 devcgroup: relax ... |
35 |
struct rcu_head rcu; |
08ce5f16e cgroups: implemen... |
36 37 38 39 |
}; struct dev_cgroup { struct cgroup_subsys_state css; |
db9aeca97 device_cgroup: re... |
40 |
struct list_head exceptions; |
c39a2a301 devcg: prepare ma... |
41 |
enum devcg_behavior behavior; |
08ce5f16e cgroups: implemen... |
42 |
}; |
b66862f76 devcgroup: make a... |
43 44 |
static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s) { |
a7c6d554a cgroup: add/updat... |
45 |
return s ? container_of(s, struct dev_cgroup, css) : NULL; |
b66862f76 devcgroup: make a... |
46 |
} |
f92523e3a cgroup files: con... |
47 48 |
static inline struct dev_cgroup *task_devcgroup(struct task_struct *task) { |
073219e99 cgroup: clean up ... |
49 |
return css_to_devcgroup(task_css(task, devices_cgrp_id)); |
f92523e3a cgroup files: con... |
50 |
} |
08ce5f16e cgroups: implemen... |
51 |
/* |
b4046f00e devcgroup: avoid ... |
52 |
* called under devcgroup_mutex |
08ce5f16e cgroups: implemen... |
53 |
*/ |
db9aeca97 device_cgroup: re... |
54 |
static int dev_exceptions_copy(struct list_head *dest, struct list_head *orig) |
08ce5f16e cgroups: implemen... |
55 |
{ |
db9aeca97 device_cgroup: re... |
56 |
struct dev_exception_item *ex, *tmp, *new; |
08ce5f16e cgroups: implemen... |
57 |
|
4b1c7840b device_cgroup: ad... |
58 |
lockdep_assert_held(&devcgroup_mutex); |
db9aeca97 device_cgroup: re... |
59 60 |
list_for_each_entry(ex, orig, list) { new = kmemdup(ex, sizeof(*ex), GFP_KERNEL); |
08ce5f16e cgroups: implemen... |
61 62 |
if (!new) goto free_and_exit; |
08ce5f16e cgroups: implemen... |
63 64 65 66 67 68 |
list_add_tail(&new->list, dest); } return 0; free_and_exit: |
db9aeca97 device_cgroup: re... |
69 70 71 |
list_for_each_entry_safe(ex, tmp, dest, list) { list_del(&ex->list); kfree(ex); |
08ce5f16e cgroups: implemen... |
72 73 74 |
} return -ENOMEM; } |
08ce5f16e cgroups: implemen... |
75 |
/* |
b4046f00e devcgroup: avoid ... |
76 |
* called under devcgroup_mutex |
08ce5f16e cgroups: implemen... |
77 |
*/ |
db9aeca97 device_cgroup: re... |
78 79 |
static int dev_exception_add(struct dev_cgroup *dev_cgroup, struct dev_exception_item *ex) |
08ce5f16e cgroups: implemen... |
80 |
{ |
db9aeca97 device_cgroup: re... |
81 |
struct dev_exception_item *excopy, *walk; |
08ce5f16e cgroups: implemen... |
82 |
|
4b1c7840b device_cgroup: ad... |
83 |
lockdep_assert_held(&devcgroup_mutex); |
db9aeca97 device_cgroup: re... |
84 85 |
excopy = kmemdup(ex, sizeof(*ex), GFP_KERNEL); if (!excopy) |
08ce5f16e cgroups: implemen... |
86 |
return -ENOMEM; |
db9aeca97 device_cgroup: re... |
87 88 |
list_for_each_entry(walk, &dev_cgroup->exceptions, list) { if (walk->type != ex->type) |
d1ee2971f devscgroup: make ... |
89 |
continue; |
db9aeca97 device_cgroup: re... |
90 |
if (walk->major != ex->major) |
d1ee2971f devscgroup: make ... |
91 |
continue; |
db9aeca97 device_cgroup: re... |
92 |
if (walk->minor != ex->minor) |
d1ee2971f devscgroup: make ... |
93 |
continue; |
db9aeca97 device_cgroup: re... |
94 95 96 |
walk->access |= ex->access; kfree(excopy); excopy = NULL; |
d1ee2971f devscgroup: make ... |
97 |
} |
db9aeca97 device_cgroup: re... |
98 99 |
if (excopy != NULL) list_add_tail_rcu(&excopy->list, &dev_cgroup->exceptions); |
08ce5f16e cgroups: implemen... |
100 101 102 103 |
return 0; } /* |
b4046f00e devcgroup: avoid ... |
104 |
* called under devcgroup_mutex |
08ce5f16e cgroups: implemen... |
105 |
*/ |
db9aeca97 device_cgroup: re... |
106 107 |
static void dev_exception_rm(struct dev_cgroup *dev_cgroup, struct dev_exception_item *ex) |
08ce5f16e cgroups: implemen... |
108 |
{ |
db9aeca97 device_cgroup: re... |
109 |
struct dev_exception_item *walk, *tmp; |
08ce5f16e cgroups: implemen... |
110 |
|
4b1c7840b device_cgroup: ad... |
111 |
lockdep_assert_held(&devcgroup_mutex); |
db9aeca97 device_cgroup: re... |
112 113 |
list_for_each_entry_safe(walk, tmp, &dev_cgroup->exceptions, list) { if (walk->type != ex->type) |
08ce5f16e cgroups: implemen... |
114 |
continue; |
db9aeca97 device_cgroup: re... |
115 |
if (walk->major != ex->major) |
08ce5f16e cgroups: implemen... |
116 |
continue; |
db9aeca97 device_cgroup: re... |
117 |
if (walk->minor != ex->minor) |
08ce5f16e cgroups: implemen... |
118 |
continue; |
db9aeca97 device_cgroup: re... |
119 |
walk->access &= ~ex->access; |
08ce5f16e cgroups: implemen... |
120 |
if (!walk->access) { |
4efd1a1b2 devcgroup: relax ... |
121 |
list_del_rcu(&walk->list); |
6034f7e60 security,rcu: Con... |
122 |
kfree_rcu(walk, rcu); |
08ce5f16e cgroups: implemen... |
123 124 |
} } |
08ce5f16e cgroups: implemen... |
125 |
} |
53eb8c82d device_cgroup: do... |
126 127 128 129 130 131 132 133 134 |
static void __dev_exception_clean(struct dev_cgroup *dev_cgroup) { struct dev_exception_item *ex, *tmp; list_for_each_entry_safe(ex, tmp, &dev_cgroup->exceptions, list) { list_del_rcu(&ex->list); kfree_rcu(ex, rcu); } } |
868539a3b device_cgroup: in... |
135 |
/** |
db9aeca97 device_cgroup: re... |
136 137 |
* dev_exception_clean - frees all entries of the exception list * @dev_cgroup: dev_cgroup with the exception list to be cleaned |
868539a3b device_cgroup: in... |
138 139 140 |
* * called under devcgroup_mutex */ |
db9aeca97 device_cgroup: re... |
141 |
static void dev_exception_clean(struct dev_cgroup *dev_cgroup) |
868539a3b device_cgroup: in... |
142 |
{ |
4b1c7840b device_cgroup: ad... |
143 |
lockdep_assert_held(&devcgroup_mutex); |
53eb8c82d device_cgroup: do... |
144 |
__dev_exception_clean(dev_cgroup); |
868539a3b device_cgroup: in... |
145 |
} |
bd2953ebb devcg: propagate ... |
146 147 148 149 |
static inline bool is_devcg_online(const struct dev_cgroup *devcg) { return (devcg->behavior != DEVCG_DEFAULT_NONE); } |
1909554c9 devcg: use css_on... |
150 151 152 |
/** * devcgroup_online - initializes devcgroup's behavior and exceptions based on * parent's |
eb95419b0 cgroup: pass arou... |
153 |
* @css: css getting online |
1909554c9 devcg: use css_on... |
154 155 |
* returns 0 in case of success, error code otherwise */ |
eb95419b0 cgroup: pass arou... |
156 |
static int devcgroup_online(struct cgroup_subsys_state *css) |
1909554c9 devcg: use css_on... |
157 |
{ |
eb95419b0 cgroup: pass arou... |
158 |
struct dev_cgroup *dev_cgroup = css_to_devcgroup(css); |
5c9d535b8 cgroup: remove cs... |
159 |
struct dev_cgroup *parent_dev_cgroup = css_to_devcgroup(css->parent); |
1909554c9 devcg: use css_on... |
160 161 162 |
int ret = 0; mutex_lock(&devcgroup_mutex); |
1909554c9 devcg: use css_on... |
163 164 165 166 167 168 169 170 171 172 173 174 175 |
if (parent_dev_cgroup == NULL) dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW; else { ret = dev_exceptions_copy(&dev_cgroup->exceptions, &parent_dev_cgroup->exceptions); if (!ret) dev_cgroup->behavior = parent_dev_cgroup->behavior; } mutex_unlock(&devcgroup_mutex); return ret; } |
eb95419b0 cgroup: pass arou... |
176 |
static void devcgroup_offline(struct cgroup_subsys_state *css) |
1909554c9 devcg: use css_on... |
177 |
{ |
eb95419b0 cgroup: pass arou... |
178 |
struct dev_cgroup *dev_cgroup = css_to_devcgroup(css); |
1909554c9 devcg: use css_on... |
179 180 181 182 183 |
mutex_lock(&devcgroup_mutex); dev_cgroup->behavior = DEVCG_DEFAULT_NONE; mutex_unlock(&devcgroup_mutex); } |
08ce5f16e cgroups: implemen... |
184 185 186 |
/* * called from kernel/cgroup.c with cgroup_lock() held. */ |
eb95419b0 cgroup: pass arou... |
187 188 |
static struct cgroup_subsys_state * devcgroup_css_alloc(struct cgroup_subsys_state *parent_css) |
08ce5f16e cgroups: implemen... |
189 |
{ |
1909554c9 devcg: use css_on... |
190 |
struct dev_cgroup *dev_cgroup; |
08ce5f16e cgroups: implemen... |
191 192 193 194 |
dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL); if (!dev_cgroup) return ERR_PTR(-ENOMEM); |
db9aeca97 device_cgroup: re... |
195 |
INIT_LIST_HEAD(&dev_cgroup->exceptions); |
1909554c9 devcg: use css_on... |
196 |
dev_cgroup->behavior = DEVCG_DEFAULT_NONE; |
08ce5f16e cgroups: implemen... |
197 |
|
08ce5f16e cgroups: implemen... |
198 199 |
return &dev_cgroup->css; } |
eb95419b0 cgroup: pass arou... |
200 |
static void devcgroup_css_free(struct cgroup_subsys_state *css) |
08ce5f16e cgroups: implemen... |
201 |
{ |
eb95419b0 cgroup: pass arou... |
202 |
struct dev_cgroup *dev_cgroup = css_to_devcgroup(css); |
08ce5f16e cgroups: implemen... |
203 |
|
53eb8c82d device_cgroup: do... |
204 |
__dev_exception_clean(dev_cgroup); |
08ce5f16e cgroups: implemen... |
205 206 207 208 209 |
kfree(dev_cgroup); } #define DEVCG_ALLOW 1 #define DEVCG_DENY 2 |
29486df32 cgroups: introduc... |
210 |
#define DEVCG_LIST 3 |
17d213f80 devcgroup: always... |
211 |
#define MAJMINLEN 13 |
29486df32 cgroups: introduc... |
212 |
#define ACCLEN 4 |
08ce5f16e cgroups: implemen... |
213 214 215 216 |
static void set_access(char *acc, short access) { int idx = 0; |
29486df32 cgroups: introduc... |
217 |
memset(acc, 0, ACCLEN); |
67e306fdb device_cgroup: ad... |
218 |
if (access & DEVCG_ACC_READ) |
08ce5f16e cgroups: implemen... |
219 |
acc[idx++] = 'r'; |
67e306fdb device_cgroup: ad... |
220 |
if (access & DEVCG_ACC_WRITE) |
08ce5f16e cgroups: implemen... |
221 |
acc[idx++] = 'w'; |
67e306fdb device_cgroup: ad... |
222 |
if (access & DEVCG_ACC_MKNOD) |
08ce5f16e cgroups: implemen... |
223 224 225 226 227 |
acc[idx++] = 'm'; } static char type_to_char(short type) { |
67e306fdb device_cgroup: ad... |
228 |
if (type == DEVCG_DEV_ALL) |
08ce5f16e cgroups: implemen... |
229 |
return 'a'; |
67e306fdb device_cgroup: ad... |
230 |
if (type == DEVCG_DEV_CHAR) |
08ce5f16e cgroups: implemen... |
231 |
return 'c'; |
67e306fdb device_cgroup: ad... |
232 |
if (type == DEVCG_DEV_BLOCK) |
08ce5f16e cgroups: implemen... |
233 234 235 |
return 'b'; return 'X'; } |
29486df32 cgroups: introduc... |
236 |
static void set_majmin(char *str, unsigned m) |
08ce5f16e cgroups: implemen... |
237 |
{ |
08ce5f16e cgroups: implemen... |
238 |
if (m == ~0) |
7759fc9d1 devcgroup: code c... |
239 |
strcpy(str, "*"); |
08ce5f16e cgroups: implemen... |
240 |
else |
7759fc9d1 devcgroup: code c... |
241 |
sprintf(str, "%u", m); |
08ce5f16e cgroups: implemen... |
242 |
} |
2da8ca822 cgroup: replace c... |
243 |
static int devcgroup_seq_show(struct seq_file *m, void *v) |
08ce5f16e cgroups: implemen... |
244 |
{ |
2da8ca822 cgroup: replace c... |
245 |
struct dev_cgroup *devcgroup = css_to_devcgroup(seq_css(m)); |
db9aeca97 device_cgroup: re... |
246 |
struct dev_exception_item *ex; |
29486df32 cgroups: introduc... |
247 |
char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN]; |
08ce5f16e cgroups: implemen... |
248 |
|
4efd1a1b2 devcgroup: relax ... |
249 |
rcu_read_lock(); |
ad676077a device_cgroup: co... |
250 251 252 253 254 255 |
/* * To preserve the compatibility: * - Only show the "all devices" when the default policy is to allow * - List the exceptions in case the default policy is to deny * This way, the file remains as a "whitelist of devices" */ |
5b7aa7d5b device_cgroup: re... |
256 |
if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { |
67e306fdb device_cgroup: ad... |
257 |
set_access(acc, DEVCG_ACC_MASK); |
ad676077a device_cgroup: co... |
258 259 |
set_majmin(maj, ~0); set_majmin(min, ~0); |
67e306fdb device_cgroup: ad... |
260 261 |
seq_printf(m, "%c %s:%s %s ", type_to_char(DEVCG_DEV_ALL), |
29486df32 cgroups: introduc... |
262 |
maj, min, acc); |
ad676077a device_cgroup: co... |
263 |
} else { |
db9aeca97 device_cgroup: re... |
264 265 266 267 268 269 |
list_for_each_entry_rcu(ex, &devcgroup->exceptions, list) { set_access(acc, ex->access); set_majmin(maj, ex->major); set_majmin(min, ex->minor); seq_printf(m, "%c %s:%s %s ", type_to_char(ex->type), |
ad676077a device_cgroup: co... |
270 271 |
maj, min, acc); } |
08ce5f16e cgroups: implemen... |
272 |
} |
4efd1a1b2 devcgroup: relax ... |
273 |
rcu_read_unlock(); |
08ce5f16e cgroups: implemen... |
274 |
|
29486df32 cgroups: introduc... |
275 |
return 0; |
08ce5f16e cgroups: implemen... |
276 |
} |
ad676077a device_cgroup: co... |
277 |
/** |
f5f3cf6f7 device_cgroup: fi... |
278 |
* match_exception - iterates the exception list trying to find a complete match |
79d719749 device_cgroup: re... |
279 |
* @exceptions: list of exceptions |
67e306fdb device_cgroup: ad... |
280 |
* @type: device type (DEVCG_DEV_BLOCK or DEVCG_DEV_CHAR) |
79d719749 device_cgroup: re... |
281 282 |
* @major: device file major number, ~0 to match all * @minor: device file minor number, ~0 to match all |
67e306fdb device_cgroup: ad... |
283 |
* @access: permission mask (DEVCG_ACC_READ, DEVCG_ACC_WRITE, DEVCG_ACC_MKNOD) |
79d719749 device_cgroup: re... |
284 |
* |
f5f3cf6f7 device_cgroup: fi... |
285 286 287 288 |
* It is considered a complete match if an exception is found that will * contain the entire range of provided parameters. * * Return: true in case it matches an exception completely |
08ce5f16e cgroups: implemen... |
289 |
*/ |
79d719749 device_cgroup: re... |
290 291 |
static bool match_exception(struct list_head *exceptions, short type, u32 major, u32 minor, short access) |
08ce5f16e cgroups: implemen... |
292 |
{ |
db9aeca97 device_cgroup: re... |
293 |
struct dev_exception_item *ex; |
08ce5f16e cgroups: implemen... |
294 |
|
79d719749 device_cgroup: re... |
295 |
list_for_each_entry_rcu(ex, exceptions, list) { |
67e306fdb device_cgroup: ad... |
296 |
if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK)) |
79d719749 device_cgroup: re... |
297 |
continue; |
67e306fdb device_cgroup: ad... |
298 |
if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR)) |
79d719749 device_cgroup: re... |
299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
continue; if (ex->major != ~0 && ex->major != major) continue; if (ex->minor != ~0 && ex->minor != minor) continue; /* provided access cannot have more than the exception rule */ if (access & (~ex->access)) continue; return true; } return false; } /** |
f5f3cf6f7 device_cgroup: fi... |
313 |
* match_exception_partial - iterates the exception list trying to find a partial match |
79d719749 device_cgroup: re... |
314 |
* @exceptions: list of exceptions |
67e306fdb device_cgroup: ad... |
315 |
* @type: device type (DEVCG_DEV_BLOCK or DEVCG_DEV_CHAR) |
79d719749 device_cgroup: re... |
316 317 |
* @major: device file major number, ~0 to match all * @minor: device file minor number, ~0 to match all |
67e306fdb device_cgroup: ad... |
318 |
* @access: permission mask (DEVCG_ACC_READ, DEVCG_ACC_WRITE, DEVCG_ACC_MKNOD) |
79d719749 device_cgroup: re... |
319 |
* |
f5f3cf6f7 device_cgroup: fi... |
320 321 322 323 324 325 |
* It is considered a partial match if an exception's range is found to * contain *any* of the devices specified by provided parameters. This is * used to make sure no extra access is being granted that is forbidden by * any of the exception list. * * Return: true in case the provided range mat matches an exception completely |
79d719749 device_cgroup: re... |
326 327 328 329 330 |
*/ static bool match_exception_partial(struct list_head *exceptions, short type, u32 major, u32 minor, short access) { struct dev_exception_item *ex; |
4b1c7840b device_cgroup: ad... |
331 |
|
bc62d68e2 device_cgroup: Fi... |
332 333 |
list_for_each_entry_rcu(ex, exceptions, list, lockdep_is_held(&devcgroup_mutex)) { |
67e306fdb device_cgroup: ad... |
334 |
if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK)) |
08ce5f16e cgroups: implemen... |
335 |
continue; |
67e306fdb device_cgroup: ad... |
336 |
if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR)) |
08ce5f16e cgroups: implemen... |
337 |
continue; |
79d719749 device_cgroup: re... |
338 339 340 341 342 |
/* * We must be sure that both the exception and the provided * range aren't masking all devices */ if (ex->major != ~0 && major != ~0 && ex->major != major) |
08ce5f16e cgroups: implemen... |
343 |
continue; |
79d719749 device_cgroup: re... |
344 |
if (ex->minor != ~0 && minor != ~0 && ex->minor != minor) |
08ce5f16e cgroups: implemen... |
345 |
continue; |
79d719749 device_cgroup: re... |
346 347 348 349 350 351 |
/* * In order to make sure the provided range isn't matching * an exception, all its access bits shouldn't match the * exception's access bits */ if (!(access & ex->access)) |
08ce5f16e cgroups: implemen... |
352 |
continue; |
79d719749 device_cgroup: re... |
353 |
return true; |
08ce5f16e cgroups: implemen... |
354 |
} |
79d719749 device_cgroup: re... |
355 356 357 358 |
return false; } /** |
f5f3cf6f7 device_cgroup: fi... |
359 |
* verify_new_ex - verifies if a new exception is allowed by parent cgroup's permissions |
79d719749 device_cgroup: re... |
360 361 362 |
* @dev_cgroup: dev cgroup to be tested against * @refex: new exception * @behavior: behavior of the exception's dev_cgroup |
f5f3cf6f7 device_cgroup: fi... |
363 364 365 |
* * This is used to make sure a child cgroup won't have more privileges * than its parent |
79d719749 device_cgroup: re... |
366 367 368 369 370 371 |
*/ static bool verify_new_ex(struct dev_cgroup *dev_cgroup, struct dev_exception_item *refex, enum devcg_behavior behavior) { bool match = false; |
f78f5b90c rcu: Rename rcu_l... |
372 |
RCU_LOCKDEP_WARN(!rcu_read_lock_held() && |
dc3a04d55 security/device_c... |
373 |
!lockdep_is_held(&devcgroup_mutex), |
f78f5b90c rcu: Rename rcu_l... |
374 |
"device_cgroup:verify_new_ex called without proper synchronization"); |
ad676077a device_cgroup: co... |
375 |
|
c39a2a301 devcg: prepare ma... |
376 377 |
if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) { if (behavior == DEVCG_DEFAULT_ALLOW) { |
79d719749 device_cgroup: re... |
378 379 380 381 |
/* * new exception in the child doesn't matter, only * adding extra restrictions */ |
c39a2a301 devcg: prepare ma... |
382 383 |
return true; } else { |
79d719749 device_cgroup: re... |
384 385 386 387 388 389 390 391 392 393 |
/* * new exception in the child will add more devices * that can be acessed, so it can't match any of * parent's exceptions, even slightly */ match = match_exception_partial(&dev_cgroup->exceptions, refex->type, refex->major, refex->minor, refex->access); |
c39a2a301 devcg: prepare ma... |
394 |
if (match) |
c39a2a301 devcg: prepare ma... |
395 |
return false; |
26898fdff devcg: expand may... |
396 |
return true; |
c39a2a301 devcg: prepare ma... |
397 |
} |
26898fdff devcg: expand may... |
398 |
} else { |
79d719749 device_cgroup: re... |
399 400 401 402 403 404 405 406 407 |
/* * Only behavior == DEVCG_DEFAULT_DENY allowed here, therefore * the new exception will add access to more devices and must * be contained completely in an parent's exception to be * allowed */ match = match_exception(&dev_cgroup->exceptions, refex->type, refex->major, refex->minor, refex->access); |
c39a2a301 devcg: prepare ma... |
408 409 |
if (match) /* parent has an exception that matches the proposed */ |
26898fdff devcg: expand may... |
410 |
return true; |
c39a2a301 devcg: prepare ma... |
411 412 |
else return false; |
26898fdff devcg: expand may... |
413 414 |
} return false; |
08ce5f16e cgroups: implemen... |
415 416 417 418 |
} /* * parent_has_perm: |
db9aeca97 device_cgroup: re... |
419 |
* when adding a new allow rule to a device exception list, the rule |
08ce5f16e cgroups: implemen... |
420 421 |
* must be allowed in the parent device */ |
f92523e3a cgroup files: con... |
422 |
static int parent_has_perm(struct dev_cgroup *childcg, |
db9aeca97 device_cgroup: re... |
423 |
struct dev_exception_item *ex) |
08ce5f16e cgroups: implemen... |
424 |
{ |
5c9d535b8 cgroup: remove cs... |
425 |
struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent); |
08ce5f16e cgroups: implemen... |
426 |
|
638769869 cgroup: add css_p... |
427 |
if (!parent) |
08ce5f16e cgroups: implemen... |
428 |
return 1; |
79d719749 device_cgroup: re... |
429 |
return verify_new_ex(parent, ex, childcg->behavior); |
08ce5f16e cgroups: implemen... |
430 |
} |
4cef7299b device_cgroup: ad... |
431 |
/** |
d2c2b11cf device_cgroup: ch... |
432 433 434 435 436 437 438 439 440 441 442 443 444 |
* parent_allows_removal - verify if it's ok to remove an exception * @childcg: child cgroup from where the exception will be removed * @ex: exception being removed * * When removing an exception in cgroups with default ALLOW policy, it must * be checked if removing it will give the child cgroup more access than the * parent. * * Return: true if it's ok to remove exception, false otherwise */ static bool parent_allows_removal(struct dev_cgroup *childcg, struct dev_exception_item *ex) { |
5c9d535b8 cgroup: remove cs... |
445 |
struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent); |
d2c2b11cf device_cgroup: ch... |
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 |
if (!parent) return true; /* It's always allowed to remove access to devices */ if (childcg->behavior == DEVCG_DEFAULT_DENY) return true; /* * Make sure you're not removing part or a whole exception existing in * the parent cgroup */ return !match_exception_partial(&parent->exceptions, ex->type, ex->major, ex->minor, ex->access); } /** |
4cef7299b device_cgroup: ad... |
463 464 465 466 467 468 469 |
* may_allow_all - checks if it's possible to change the behavior to * allow based on parent's rules. * @parent: device cgroup's parent * returns: != 0 in case it's allowed, 0 otherwise */ static inline int may_allow_all(struct dev_cgroup *parent) { |
64e104771 device_cgroup: fi... |
470 471 |
if (!parent) return 1; |
4cef7299b device_cgroup: ad... |
472 473 |
return parent->behavior == DEVCG_DEFAULT_ALLOW; } |
bd2953ebb devcg: propagate ... |
474 475 476 477 478 479 480 481 482 483 484 |
/** * revalidate_active_exceptions - walks through the active exception list and * revalidates the exceptions based on parent's * behavior and exceptions. The exceptions that * are no longer valid will be removed. * Called with devcgroup_mutex held. * @devcg: cgroup which exceptions will be checked * * This is one of the three key functions for hierarchy implementation. * This function is responsible for re-evaluating all the cgroup's active * exceptions due to a parent's exception change. |
da82c92f1 docs: cgroup-v1: ... |
485 |
* Refer to Documentation/admin-guide/cgroup-v1/devices.rst for more details. |
bd2953ebb devcg: propagate ... |
486 487 488 489 490 491 492 493 494 495 496 497 498 499 |
*/ static void revalidate_active_exceptions(struct dev_cgroup *devcg) { struct dev_exception_item *ex; struct list_head *this, *tmp; list_for_each_safe(this, tmp, &devcg->exceptions) { ex = container_of(this, struct dev_exception_item, list); if (!parent_has_perm(devcg, ex)) dev_exception_rm(devcg, ex); } } /** |
bd2953ebb devcg: propagate ... |
500 501 502 503 504 505 506 507 508 |
* propagate_exception - propagates a new exception to the children * @devcg_root: device cgroup that added a new exception * @ex: new exception to be propagated * * returns: 0 in case of success, != 0 in case of error */ static int propagate_exception(struct dev_cgroup *devcg_root, struct dev_exception_item *ex) { |
492eb21b9 cgroup: make hier... |
509 |
struct cgroup_subsys_state *pos; |
bd2953ebb devcg: propagate ... |
510 |
int rc = 0; |
bd2953ebb devcg: propagate ... |
511 |
|
d591fb566 device_cgroup: si... |
512 |
rcu_read_lock(); |
bd2953ebb devcg: propagate ... |
513 |
|
492eb21b9 cgroup: make hier... |
514 515 |
css_for_each_descendant_pre(pos, &devcg_root->css) { struct dev_cgroup *devcg = css_to_devcgroup(pos); |
d591fb566 device_cgroup: si... |
516 517 518 519 520 521 522 |
/* * Because devcgroup_mutex is held, no devcg will become * online or offline during the tree walk (see on/offline * methods), and online ones are safe to access outside RCU * read lock without bumping refcnt. */ |
bd8815a6d cgroup: make css_... |
523 |
if (pos == &devcg_root->css || !is_devcg_online(devcg)) |
d591fb566 device_cgroup: si... |
524 525 526 |
continue; rcu_read_unlock(); |
bd2953ebb devcg: propagate ... |
527 528 529 530 531 532 533 534 535 |
/* * in case both root's behavior and devcg is allow, a new * restriction means adding to the exception list */ if (devcg_root->behavior == DEVCG_DEFAULT_ALLOW && devcg->behavior == DEVCG_DEFAULT_ALLOW) { rc = dev_exception_add(devcg, ex); if (rc) |
0fcc4c8c0 device_cgroup: fi... |
536 |
return rc; |
bd2953ebb devcg: propagate ... |
537 538 539 540 541 542 543 544 545 546 |
} else { /* * in the other possible cases: * root's behavior: allow, devcg's: deny * root's behavior: deny, devcg's: deny * the exception will be removed */ dev_exception_rm(devcg, ex); } revalidate_active_exceptions(devcg); |
d591fb566 device_cgroup: si... |
547 |
rcu_read_lock(); |
bd2953ebb devcg: propagate ... |
548 |
} |
d591fb566 device_cgroup: si... |
549 550 |
rcu_read_unlock(); |
bd2953ebb devcg: propagate ... |
551 552 |
return rc; } |
08ce5f16e cgroups: implemen... |
553 |
/* |
db9aeca97 device_cgroup: re... |
554 |
* Modify the exception list using allow/deny rules. |
08ce5f16e cgroups: implemen... |
555 556 |
* CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD * so we can give a container CAP_MKNOD to let it create devices but not |
db9aeca97 device_cgroup: re... |
557 |
* modify the exception list. |
08ce5f16e cgroups: implemen... |
558 559 |
* It seems likely we'll want to add a CAP_CONTAINER capability to allow * us to also grant CAP_SYS_ADMIN to containers without giving away the |
db9aeca97 device_cgroup: re... |
560 |
* device exception list controls, but for now we'll stick with CAP_SYS_ADMIN |
08ce5f16e cgroups: implemen... |
561 562 563 564 565 |
* * Taking rules away is always allowed (given CAP_SYS_ADMIN). Granting * new access is only allowed if you're in the top-level cgroup, or your * parent cgroup has the access you're asking for. */ |
f92523e3a cgroup files: con... |
566 |
static int devcgroup_update_access(struct dev_cgroup *devcgroup, |
4d3bb511b cgroup: drop cons... |
567 |
int filetype, char *buffer) |
08ce5f16e cgroups: implemen... |
568 |
{ |
f92523e3a cgroup files: con... |
569 |
const char *b; |
26fd8405d device_cgroup: st... |
570 |
char temp[12]; /* 11 + 1 characters needed for a u32 */ |
c39a2a301 devcg: prepare ma... |
571 |
int count, rc = 0; |
db9aeca97 device_cgroup: re... |
572 |
struct dev_exception_item ex; |
5c9d535b8 cgroup: remove cs... |
573 |
struct dev_cgroup *parent = css_to_devcgroup(devcgroup->css.parent); |
08ce5f16e cgroups: implemen... |
574 575 576 |
if (!capable(CAP_SYS_ADMIN)) return -EPERM; |
db9aeca97 device_cgroup: re... |
577 |
memset(&ex, 0, sizeof(ex)); |
08ce5f16e cgroups: implemen... |
578 579 580 581 |
b = buffer; switch (*b) { case 'a': |
ad676077a device_cgroup: co... |
582 583 |
switch (filetype) { case DEVCG_ALLOW: |
7a3bb24f7 device_cgroup: us... |
584 |
if (css_has_online_children(&devcgroup->css)) |
bd2953ebb devcg: propagate ... |
585 |
return -EINVAL; |
4cef7299b device_cgroup: ad... |
586 |
if (!may_allow_all(parent)) |
ad676077a device_cgroup: co... |
587 |
return -EPERM; |
db9aeca97 device_cgroup: re... |
588 |
dev_exception_clean(devcgroup); |
64e104771 device_cgroup: fi... |
589 590 591 |
devcgroup->behavior = DEVCG_DEFAULT_ALLOW; if (!parent) break; |
4cef7299b device_cgroup: ad... |
592 593 594 595 |
rc = dev_exceptions_copy(&devcgroup->exceptions, &parent->exceptions); if (rc) return rc; |
ad676077a device_cgroup: co... |
596 597 |
break; case DEVCG_DENY: |
7a3bb24f7 device_cgroup: us... |
598 |
if (css_has_online_children(&devcgroup->css)) |
bd2953ebb devcg: propagate ... |
599 |
return -EINVAL; |
db9aeca97 device_cgroup: re... |
600 |
dev_exception_clean(devcgroup); |
5b7aa7d5b device_cgroup: re... |
601 |
devcgroup->behavior = DEVCG_DEFAULT_DENY; |
ad676077a device_cgroup: co... |
602 603 604 605 606 |
break; default: return -EINVAL; } return 0; |
08ce5f16e cgroups: implemen... |
607 |
case 'b': |
67e306fdb device_cgroup: ad... |
608 |
ex.type = DEVCG_DEV_BLOCK; |
08ce5f16e cgroups: implemen... |
609 610 |
break; case 'c': |
67e306fdb device_cgroup: ad... |
611 |
ex.type = DEVCG_DEV_CHAR; |
08ce5f16e cgroups: implemen... |
612 613 |
break; default: |
f92523e3a cgroup files: con... |
614 |
return -EINVAL; |
08ce5f16e cgroups: implemen... |
615 616 |
} b++; |
f92523e3a cgroup files: con... |
617 618 |
if (!isspace(*b)) return -EINVAL; |
08ce5f16e cgroups: implemen... |
619 620 |
b++; if (*b == '*') { |
db9aeca97 device_cgroup: re... |
621 |
ex.major = ~0; |
08ce5f16e cgroups: implemen... |
622 623 |
b++; } else if (isdigit(*b)) { |
26fd8405d device_cgroup: st... |
624 625 626 627 628 629 630 631 632 633 |
memset(temp, 0, sizeof(temp)); for (count = 0; count < sizeof(temp) - 1; count++) { temp[count] = *b; b++; if (!isdigit(*b)) break; } rc = kstrtou32(temp, 10, &ex.major); if (rc) return -EINVAL; |
08ce5f16e cgroups: implemen... |
634 |
} else { |
f92523e3a cgroup files: con... |
635 |
return -EINVAL; |
08ce5f16e cgroups: implemen... |
636 |
} |
f92523e3a cgroup files: con... |
637 638 |
if (*b != ':') return -EINVAL; |
08ce5f16e cgroups: implemen... |
639 640 641 642 |
b++; /* read minor */ if (*b == '*') { |
db9aeca97 device_cgroup: re... |
643 |
ex.minor = ~0; |
08ce5f16e cgroups: implemen... |
644 645 |
b++; } else if (isdigit(*b)) { |
26fd8405d device_cgroup: st... |
646 647 648 649 650 651 652 653 654 655 |
memset(temp, 0, sizeof(temp)); for (count = 0; count < sizeof(temp) - 1; count++) { temp[count] = *b; b++; if (!isdigit(*b)) break; } rc = kstrtou32(temp, 10, &ex.minor); if (rc) return -EINVAL; |
08ce5f16e cgroups: implemen... |
656 |
} else { |
f92523e3a cgroup files: con... |
657 |
return -EINVAL; |
08ce5f16e cgroups: implemen... |
658 |
} |
f92523e3a cgroup files: con... |
659 660 |
if (!isspace(*b)) return -EINVAL; |
08ce5f16e cgroups: implemen... |
661 662 663 |
for (b++, count = 0; count < 3; count++, b++) { switch (*b) { case 'r': |
67e306fdb device_cgroup: ad... |
664 |
ex.access |= DEVCG_ACC_READ; |
08ce5f16e cgroups: implemen... |
665 666 |
break; case 'w': |
67e306fdb device_cgroup: ad... |
667 |
ex.access |= DEVCG_ACC_WRITE; |
08ce5f16e cgroups: implemen... |
668 669 |
break; case 'm': |
67e306fdb device_cgroup: ad... |
670 |
ex.access |= DEVCG_ACC_MKNOD; |
08ce5f16e cgroups: implemen... |
671 672 673 674 675 676 677 |
break; case ' ': case '\0': count = 3; break; default: |
f92523e3a cgroup files: con... |
678 |
return -EINVAL; |
08ce5f16e cgroups: implemen... |
679 680 |
} } |
08ce5f16e cgroups: implemen... |
681 682 |
switch (filetype) { case DEVCG_ALLOW: |
ad676077a device_cgroup: co... |
683 684 685 686 687 |
/* * If the default policy is to allow by default, try to remove * an matching exception instead. And be silent about it: we * don't want to break compatibility */ |
5b7aa7d5b device_cgroup: re... |
688 |
if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { |
d2c2b11cf device_cgroup: ch... |
689 690 691 |
/* Check if the parent allows removing it first */ if (!parent_allows_removal(devcgroup, &ex)) return -EPERM; |
db9aeca97 device_cgroup: re... |
692 |
dev_exception_rm(devcgroup, &ex); |
d2c2b11cf device_cgroup: ch... |
693 |
break; |
ad676077a device_cgroup: co... |
694 |
} |
d2c2b11cf device_cgroup: ch... |
695 696 697 |
if (!parent_has_perm(devcgroup, &ex)) return -EPERM; |
bd2953ebb devcg: propagate ... |
698 699 |
rc = dev_exception_add(devcgroup, &ex); break; |
08ce5f16e cgroups: implemen... |
700 |
case DEVCG_DENY: |
ad676077a device_cgroup: co... |
701 702 703 704 705 |
/* * If the default policy is to deny by default, try to remove * an matching exception instead. And be silent about it: we * don't want to break compatibility */ |
bd2953ebb devcg: propagate ... |
706 |
if (devcgroup->behavior == DEVCG_DEFAULT_DENY) |
db9aeca97 device_cgroup: re... |
707 |
dev_exception_rm(devcgroup, &ex); |
bd2953ebb devcg: propagate ... |
708 709 710 711 712 713 714 715 |
else rc = dev_exception_add(devcgroup, &ex); if (rc) break; /* we only propagate new restrictions */ rc = propagate_exception(devcgroup, &ex); break; |
08ce5f16e cgroups: implemen... |
716 |
default: |
bd2953ebb devcg: propagate ... |
717 |
rc = -EINVAL; |
08ce5f16e cgroups: implemen... |
718 |
} |
bd2953ebb devcg: propagate ... |
719 |
return rc; |
f92523e3a cgroup files: con... |
720 |
} |
08ce5f16e cgroups: implemen... |
721 |
|
451af504d cgroup: replace c... |
722 723 |
static ssize_t devcgroup_access_write(struct kernfs_open_file *of, char *buf, size_t nbytes, loff_t off) |
f92523e3a cgroup files: con... |
724 725 |
{ int retval; |
b4046f00e devcgroup: avoid ... |
726 727 |
mutex_lock(&devcgroup_mutex); |
451af504d cgroup: replace c... |
728 729 |
retval = devcgroup_update_access(css_to_devcgroup(of_css(of)), of_cft(of)->private, strstrip(buf)); |
b4046f00e devcgroup: avoid ... |
730 |
mutex_unlock(&devcgroup_mutex); |
451af504d cgroup: replace c... |
731 |
return retval ?: nbytes; |
08ce5f16e cgroups: implemen... |
732 733 734 735 736 |
} static struct cftype dev_cgroup_files[] = { { .name = "allow", |
451af504d cgroup: replace c... |
737 |
.write = devcgroup_access_write, |
08ce5f16e cgroups: implemen... |
738 739 740 741 |
.private = DEVCG_ALLOW, }, { .name = "deny", |
451af504d cgroup: replace c... |
742 |
.write = devcgroup_access_write, |
08ce5f16e cgroups: implemen... |
743 744 |
.private = DEVCG_DENY, }, |
29486df32 cgroups: introduc... |
745 746 |
{ .name = "list", |
2da8ca822 cgroup: replace c... |
747 |
.seq_show = devcgroup_seq_show, |
29486df32 cgroups: introduc... |
748 749 |
.private = DEVCG_LIST, }, |
4baf6e332 cgroup: convert a... |
750 |
{ } /* terminate */ |
08ce5f16e cgroups: implemen... |
751 |
}; |
073219e99 cgroup: clean up ... |
752 |
struct cgroup_subsys devices_cgrp_subsys = { |
92fb97487 cgroup: rename ->... |
753 754 |
.css_alloc = devcgroup_css_alloc, .css_free = devcgroup_css_free, |
1909554c9 devcg: use css_on... |
755 756 |
.css_online = devcgroup_online, .css_offline = devcgroup_offline, |
5577964e6 cgroup: rename cg... |
757 |
.legacy_cftypes = dev_cgroup_files, |
08ce5f16e cgroups: implemen... |
758 |
}; |
ad676077a device_cgroup: co... |
759 |
/** |
eec8fd027 device_cgroup: Cl... |
760 |
* devcgroup_legacy_check_permission - checks if an inode operation is permitted |
ad676077a device_cgroup: co... |
761 762 763 764 |
* @dev_cgroup: the dev cgroup to be tested against * @type: device type * @major: device major number * @minor: device minor number |
67e306fdb device_cgroup: ad... |
765 |
* @access: combination of DEVCG_ACC_WRITE, DEVCG_ACC_READ and DEVCG_ACC_MKNOD |
ad676077a device_cgroup: co... |
766 767 768 |
* * returns 0 on success, -EPERM case the operation is not permitted */ |
eec8fd027 device_cgroup: Cl... |
769 |
static int devcgroup_legacy_check_permission(short type, u32 major, u32 minor, |
4b7d4d453 device_cgroup: Ex... |
770 |
short access) |
08ce5f16e cgroups: implemen... |
771 |
{ |
8c9506d16 cgroup: fix inval... |
772 |
struct dev_cgroup *dev_cgroup; |
79d719749 device_cgroup: re... |
773 |
bool rc; |
36fd71d29 devcgroup: fix ra... |
774 |
|
ad676077a device_cgroup: co... |
775 |
rcu_read_lock(); |
8c9506d16 cgroup: fix inval... |
776 |
dev_cgroup = task_devcgroup(current); |
79d719749 device_cgroup: re... |
777 778 779 780 781 782 783 784 |
if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) /* Can't match any of the exceptions, even partially */ rc = !match_exception_partial(&dev_cgroup->exceptions, type, major, minor, access); else /* Need to match completely one exception to be allowed */ rc = match_exception(&dev_cgroup->exceptions, type, major, minor, access); |
ad676077a device_cgroup: co... |
785 |
rcu_read_unlock(); |
cd5008196 devcgroup: skip s... |
786 |
|
ad676077a device_cgroup: co... |
787 788 |
if (!rc) return -EPERM; |
36fd71d29 devcgroup: fix ra... |
789 |
|
ad676077a device_cgroup: co... |
790 791 |
return 0; } |
4b7d4d453 device_cgroup: Ex... |
792 |
|
eec8fd027 device_cgroup: Cl... |
793 794 795 |
#endif /* CONFIG_CGROUP_DEVICE */ #if defined(CONFIG_CGROUP_DEVICE) || defined(CONFIG_CGROUP_BPF) |
4b7d4d453 device_cgroup: Ex... |
796 797 798 799 800 801 |
int devcgroup_check_permission(short type, u32 major, u32 minor, short access) { int rc = BPF_CGROUP_RUN_PROG_DEVICE_CGROUP(type, major, minor, access); if (rc) return -EPERM; |
eec8fd027 device_cgroup: Cl... |
802 803 804 805 806 807 808 |
#ifdef CONFIG_CGROUP_DEVICE return devcgroup_legacy_check_permission(type, major, minor, access); #else /* CONFIG_CGROUP_DEVICE */ return 0; #endif /* CONFIG_CGROUP_DEVICE */ |
4b7d4d453 device_cgroup: Ex... |
809 810 |
} EXPORT_SYMBOL(devcgroup_check_permission); |
eec8fd027 device_cgroup: Cl... |
811 |
#endif /* defined(CONFIG_CGROUP_DEVICE) || defined(CONFIG_CGROUP_BPF) */ |