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