diff options
-rw-r--r-- | Documentation/cgroups/devices.txt | 70 | ||||
-rw-r--r-- | security/device_cgroup.c | 139 |
2 files changed, 199 insertions, 10 deletions
diff --git a/Documentation/cgroups/devices.txt b/Documentation/cgroups/devices.txt index 16624a7f8222..3c1095ca02ea 100644 --- a/Documentation/cgroups/devices.txt +++ b/Documentation/cgroups/devices.txt | |||
@@ -13,9 +13,7 @@ either an integer or * for all. Access is a composition of r | |||
13 | The root device cgroup starts with rwm to 'all'. A child device | 13 | The root device cgroup starts with rwm to 'all'. A child device |
14 | cgroup gets a copy of the parent. Administrators can then remove | 14 | cgroup gets a copy of the parent. Administrators can then remove |
15 | devices from the whitelist or add new entries. A child cgroup can | 15 | devices from the whitelist or add new entries. A child cgroup can |
16 | never receive a device access which is denied by its parent. However | 16 | never receive a device access which is denied by its parent. |
17 | when a device access is removed from a parent it will not also be | ||
18 | removed from the child(ren). | ||
19 | 17 | ||
20 | 2. User Interface | 18 | 2. User Interface |
21 | 19 | ||
@@ -50,3 +48,69 @@ task to a new cgroup. (Again we'll probably want to change that). | |||
50 | 48 | ||
51 | A cgroup may not be granted more permissions than the cgroup's | 49 | A cgroup may not be granted more permissions than the cgroup's |
52 | parent has. | 50 | parent has. |
51 | |||
52 | 4. Hierarchy | ||
53 | |||
54 | device cgroups maintain hierarchy by making sure a cgroup never has more | ||
55 | access permissions than its parent. Every time an entry is written to | ||
56 | a cgroup's devices.deny file, all its children will have that entry removed | ||
57 | from their whitelist and all the locally set whitelist entries will be | ||
58 | re-evaluated. In case one of the locally set whitelist entries would provide | ||
59 | more access than the cgroup's parent, it'll be removed from the whitelist. | ||
60 | |||
61 | Example: | ||
62 | A | ||
63 | / \ | ||
64 | B | ||
65 | |||
66 | group behavior exceptions | ||
67 | A allow "b 8:* rwm", "c 116:1 rw" | ||
68 | B deny "c 1:3 rwm", "c 116:2 rwm", "b 3:* rwm" | ||
69 | |||
70 | If a device is denied in group A: | ||
71 | # echo "c 116:* r" > A/devices.deny | ||
72 | it'll propagate down and after revalidating B's entries, the whitelist entry | ||
73 | "c 116:2 rwm" will be removed: | ||
74 | |||
75 | group whitelist entries denied devices | ||
76 | A all "b 8:* rwm", "c 116:* rw" | ||
77 | B "c 1:3 rwm", "b 3:* rwm" all the rest | ||
78 | |||
79 | In case parent's exceptions change and local exceptions are not allowed | ||
80 | anymore, they'll be deleted. | ||
81 | |||
82 | Notice that new whitelist entries will not be propagated: | ||
83 | A | ||
84 | / \ | ||
85 | B | ||
86 | |||
87 | group whitelist entries denied devices | ||
88 | A "c 1:3 rwm", "c 1:5 r" all the rest | ||
89 | B "c 1:3 rwm", "c 1:5 r" all the rest | ||
90 | |||
91 | when adding "c *:3 rwm": | ||
92 | # echo "c *:3 rwm" >A/devices.allow | ||
93 | |||
94 | the result: | ||
95 | group whitelist entries denied devices | ||
96 | A "c *:3 rwm", "c 1:5 r" all the rest | ||
97 | B "c 1:3 rwm", "c 1:5 r" all the rest | ||
98 | |||
99 | but now it'll be possible to add new entries to B: | ||
100 | # echo "c 2:3 rwm" >B/devices.allow | ||
101 | # echo "c 50:3 r" >B/devices.allow | ||
102 | or even | ||
103 | # echo "c *:3 rwm" >B/devices.allow | ||
104 | |||
105 | Allowing or denying all by writing 'a' to devices.allow or devices.deny will | ||
106 | not be possible once the device cgroups has children. | ||
107 | |||
108 | 4.1 Hierarchy (internal implementation) | ||
109 | |||
110 | device cgroups is implemented internally using a behavior (ALLOW, DENY) and a | ||
111 | list of exceptions. The internal state is controlled using the same user | ||
112 | interface to preserve compatibility with the previous whitelist-only | ||
113 | implementation. Removal or addition of exceptions that will reduce the access | ||
114 | to devices will be propagated down the hierarchy. | ||
115 | For every propagated exception, the effective rules will be re-evaluated based | ||
116 | on current parent's access rules. | ||
diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 16c9e1069be6..221967d4690c 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c | |||
@@ -49,6 +49,8 @@ struct dev_cgroup { | |||
49 | struct cgroup_subsys_state css; | 49 | struct cgroup_subsys_state css; |
50 | struct list_head exceptions; | 50 | struct list_head exceptions; |
51 | enum devcg_behavior behavior; | 51 | enum devcg_behavior behavior; |
52 | /* temporary list for pending propagation operations */ | ||
53 | struct list_head propagate_pending; | ||
52 | }; | 54 | }; |
53 | 55 | ||
54 | static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s) | 56 | static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s) |
@@ -185,6 +187,11 @@ static void dev_exception_clean(struct dev_cgroup *dev_cgroup) | |||
185 | __dev_exception_clean(dev_cgroup); | 187 | __dev_exception_clean(dev_cgroup); |
186 | } | 188 | } |
187 | 189 | ||
190 | static inline bool is_devcg_online(const struct dev_cgroup *devcg) | ||
191 | { | ||
192 | return (devcg->behavior != DEVCG_DEFAULT_NONE); | ||
193 | } | ||
194 | |||
188 | /** | 195 | /** |
189 | * devcgroup_online - initializes devcgroup's behavior and exceptions based on | 196 | * devcgroup_online - initializes devcgroup's behavior and exceptions based on |
190 | * parent's | 197 | * parent's |
@@ -235,6 +242,7 @@ static struct cgroup_subsys_state *devcgroup_css_alloc(struct cgroup *cgroup) | |||
235 | if (!dev_cgroup) | 242 | if (!dev_cgroup) |
236 | return ERR_PTR(-ENOMEM); | 243 | return ERR_PTR(-ENOMEM); |
237 | INIT_LIST_HEAD(&dev_cgroup->exceptions); | 244 | INIT_LIST_HEAD(&dev_cgroup->exceptions); |
245 | INIT_LIST_HEAD(&dev_cgroup->propagate_pending); | ||
238 | dev_cgroup->behavior = DEVCG_DEFAULT_NONE; | 246 | dev_cgroup->behavior = DEVCG_DEFAULT_NONE; |
239 | parent_cgroup = cgroup->parent; | 247 | parent_cgroup = cgroup->parent; |
240 | 248 | ||
@@ -413,6 +421,111 @@ static inline int may_allow_all(struct dev_cgroup *parent) | |||
413 | return parent->behavior == DEVCG_DEFAULT_ALLOW; | 421 | return parent->behavior == DEVCG_DEFAULT_ALLOW; |
414 | } | 422 | } |
415 | 423 | ||
424 | /** | ||
425 | * revalidate_active_exceptions - walks through the active exception list and | ||
426 | * revalidates the exceptions based on parent's | ||
427 | * behavior and exceptions. The exceptions that | ||
428 | * are no longer valid will be removed. | ||
429 | * Called with devcgroup_mutex held. | ||
430 | * @devcg: cgroup which exceptions will be checked | ||
431 | * | ||
432 | * This is one of the three key functions for hierarchy implementation. | ||
433 | * This function is responsible for re-evaluating all the cgroup's active | ||
434 | * exceptions due to a parent's exception change. | ||
435 | * Refer to Documentation/cgroups/devices.txt for more details. | ||
436 | */ | ||
437 | static void revalidate_active_exceptions(struct dev_cgroup *devcg) | ||
438 | { | ||
439 | struct dev_exception_item *ex; | ||
440 | struct list_head *this, *tmp; | ||
441 | |||
442 | list_for_each_safe(this, tmp, &devcg->exceptions) { | ||
443 | ex = container_of(this, struct dev_exception_item, list); | ||
444 | if (!parent_has_perm(devcg, ex)) | ||
445 | dev_exception_rm(devcg, ex); | ||
446 | } | ||
447 | } | ||
448 | |||
449 | /** | ||
450 | * get_online_devcg - walks the cgroup tree and fills a list with the online | ||
451 | * groups | ||
452 | * @root: cgroup used as starting point | ||
453 | * @online: list that will be filled with online groups | ||
454 | * | ||
455 | * Must be called with devcgroup_mutex held. Grabs RCU lock. | ||
456 | * Because devcgroup_mutex is held, no devcg will become online or offline | ||
457 | * during the tree walk (see devcgroup_online, devcgroup_offline) | ||
458 | * A separated list is needed because propagate_behavior() and | ||
459 | * propagate_exception() need to allocate memory and can block. | ||
460 | */ | ||
461 | static void get_online_devcg(struct cgroup *root, struct list_head *online) | ||
462 | { | ||
463 | struct cgroup *pos; | ||
464 | struct dev_cgroup *devcg; | ||
465 | |||
466 | lockdep_assert_held(&devcgroup_mutex); | ||
467 | |||
468 | rcu_read_lock(); | ||
469 | cgroup_for_each_descendant_pre(pos, root) { | ||
470 | devcg = cgroup_to_devcgroup(pos); | ||
471 | if (is_devcg_online(devcg)) | ||
472 | list_add_tail(&devcg->propagate_pending, online); | ||
473 | } | ||
474 | rcu_read_unlock(); | ||
475 | } | ||
476 | |||
477 | /** | ||
478 | * propagate_exception - propagates a new exception to the children | ||
479 | * @devcg_root: device cgroup that added a new exception | ||
480 | * @ex: new exception to be propagated | ||
481 | * | ||
482 | * returns: 0 in case of success, != 0 in case of error | ||
483 | */ | ||
484 | static int propagate_exception(struct dev_cgroup *devcg_root, | ||
485 | struct dev_exception_item *ex) | ||
486 | { | ||
487 | struct cgroup *root = devcg_root->css.cgroup; | ||
488 | struct dev_cgroup *devcg, *parent, *tmp; | ||
489 | int rc = 0; | ||
490 | LIST_HEAD(pending); | ||
491 | |||
492 | get_online_devcg(root, &pending); | ||
493 | |||
494 | list_for_each_entry_safe(devcg, tmp, &pending, propagate_pending) { | ||
495 | parent = cgroup_to_devcgroup(devcg->css.cgroup->parent); | ||
496 | |||
497 | /* | ||
498 | * in case both root's behavior and devcg is allow, a new | ||
499 | * restriction means adding to the exception list | ||
500 | */ | ||
501 | if (devcg_root->behavior == DEVCG_DEFAULT_ALLOW && | ||
502 | devcg->behavior == DEVCG_DEFAULT_ALLOW) { | ||
503 | rc = dev_exception_add(devcg, ex); | ||
504 | if (rc) | ||
505 | break; | ||
506 | } else { | ||
507 | /* | ||
508 | * in the other possible cases: | ||
509 | * root's behavior: allow, devcg's: deny | ||
510 | * root's behavior: deny, devcg's: deny | ||
511 | * the exception will be removed | ||
512 | */ | ||
513 | dev_exception_rm(devcg, ex); | ||
514 | } | ||
515 | revalidate_active_exceptions(devcg); | ||
516 | |||
517 | list_del_init(&devcg->propagate_pending); | ||
518 | } | ||
519 | return rc; | ||
520 | } | ||
521 | |||
522 | static inline bool has_children(struct dev_cgroup *devcgroup) | ||
523 | { | ||
524 | struct cgroup *cgrp = devcgroup->css.cgroup; | ||
525 | |||
526 | return !list_empty(&cgrp->children); | ||
527 | } | ||
528 | |||
416 | /* | 529 | /* |
417 | * Modify the exception list using allow/deny rules. | 530 | * Modify the exception list using allow/deny rules. |
418 | * CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD | 531 | * CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD |
@@ -449,6 +562,9 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, | |||
449 | case 'a': | 562 | case 'a': |
450 | switch (filetype) { | 563 | switch (filetype) { |
451 | case DEVCG_ALLOW: | 564 | case DEVCG_ALLOW: |
565 | if (has_children(devcgroup)) | ||
566 | return -EINVAL; | ||
567 | |||
452 | if (!may_allow_all(parent)) | 568 | if (!may_allow_all(parent)) |
453 | return -EPERM; | 569 | return -EPERM; |
454 | dev_exception_clean(devcgroup); | 570 | dev_exception_clean(devcgroup); |
@@ -462,6 +578,9 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, | |||
462 | return rc; | 578 | return rc; |
463 | break; | 579 | break; |
464 | case DEVCG_DENY: | 580 | case DEVCG_DENY: |
581 | if (has_children(devcgroup)) | ||
582 | return -EINVAL; | ||
583 | |||
465 | dev_exception_clean(devcgroup); | 584 | dev_exception_clean(devcgroup); |
466 | devcgroup->behavior = DEVCG_DEFAULT_DENY; | 585 | devcgroup->behavior = DEVCG_DEFAULT_DENY; |
467 | break; | 586 | break; |
@@ -556,22 +675,28 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, | |||
556 | dev_exception_rm(devcgroup, &ex); | 675 | dev_exception_rm(devcgroup, &ex); |
557 | return 0; | 676 | return 0; |
558 | } | 677 | } |
559 | return dev_exception_add(devcgroup, &ex); | 678 | rc = dev_exception_add(devcgroup, &ex); |
679 | break; | ||
560 | case DEVCG_DENY: | 680 | case DEVCG_DENY: |
561 | /* | 681 | /* |
562 | * If the default policy is to deny by default, try to remove | 682 | * If the default policy is to deny by default, try to remove |
563 | * an matching exception instead. And be silent about it: we | 683 | * an matching exception instead. And be silent about it: we |
564 | * don't want to break compatibility | 684 | * don't want to break compatibility |
565 | */ | 685 | */ |
566 | if (devcgroup->behavior == DEVCG_DEFAULT_DENY) { | 686 | if (devcgroup->behavior == DEVCG_DEFAULT_DENY) |
567 | dev_exception_rm(devcgroup, &ex); | 687 | dev_exception_rm(devcgroup, &ex); |
568 | return 0; | 688 | else |
569 | } | 689 | rc = dev_exception_add(devcgroup, &ex); |
570 | return dev_exception_add(devcgroup, &ex); | 690 | |
691 | if (rc) | ||
692 | break; | ||
693 | /* we only propagate new restrictions */ | ||
694 | rc = propagate_exception(devcgroup, &ex); | ||
695 | break; | ||
571 | default: | 696 | default: |
572 | return -EINVAL; | 697 | rc = -EINVAL; |
573 | } | 698 | } |
574 | return 0; | 699 | return rc; |
575 | } | 700 | } |
576 | 701 | ||
577 | static int devcgroup_access_write(struct cgroup *cgrp, struct cftype *cft, | 702 | static int devcgroup_access_write(struct cgroup *cgrp, struct cftype *cft, |