aboutsummaryrefslogtreecommitdiffstats
path: root/security
diff options
context:
space:
mode:
authorAristeu Rozanski <aris@redhat.com>2013-02-15 11:55:47 -0500
committerTejun Heo <tj@kernel.org>2013-03-20 10:50:21 -0400
commitbd2953ebbb533aeda9b86c82a53d5197a9a38f1b (patch)
treeb59a35d956a8223d7f68b8d7190a3d14ebf29580 /security
parent1909554c9715e4d032497993bb56f2726bfa89ae (diff)
devcg: propagate local changes down the hierarchy
This patch makes exception changes to propagate down in hierarchy respecting when possible local exceptions. New exceptions allowing additional access to devices won't be propagated, but it'll be possible to add an exception to access all of part of the newly allowed device(s). New exceptions disallowing access to devices will be propagated down and the local group's exceptions will be revalidated for the new situation. Example: A / \ B group behavior exceptions A allow "b 8:* rwm", "c 116:1 rw" B deny "c 1:3 rwm", "c 116:2 rwm", "b 3:* rwm" If a new exception is added to group A: # echo "c 116:* r" > A/devices.deny it'll propagate down and after revalidating B's local exceptions, the exception "c 116:2 rwm" will be removed. In case parent's exceptions change and local exceptions are not allowed anymore, they'll be deleted. v7: - do not allow behavior change when the cgroup has children - update documentation v6: fixed issues pointed by Serge Hallyn - only copy parent's exceptions while propagating behavior if the local behavior is different - while propagating exceptions, do not clear and copy parent's: it'd be against the premise we don't propagate access to more devices v5: fixed issues pointed by Serge Hallyn - updated documentation - not propagating when an exception is written to devices.allow - when propagating a new behavior, clean the local exceptions list if they're for a different behavior v4: fixed issues pointed by Tejun Heo - separated function to walk the tree and collect valid propagation targets v3: fixed issues pointed by Tejun Heo - update documentation - move css_online/css_offline changes to a new patch - use cgroup_for_each_descendant_pre() instead of own descendant walk - move exception_copy rework to a separared patch - move exception_clean rework to a separated patch v2: fixed issues pointed by Tejun Heo - instead of keeping the local settings that won't apply anymore, remove them Cc: Tejun Heo <tj@kernel.org> Cc: Serge Hallyn <serge.hallyn@canonical.com> Signed-off-by: Aristeu Rozanski <aris@redhat.com> Signed-off-by: Tejun Heo <tj@kernel.org>
Diffstat (limited to 'security')
-rw-r--r--security/device_cgroup.c139
1 files changed, 132 insertions, 7 deletions
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
54static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s) 56static 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
190static 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 */
437static 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 */
461static 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 */
484static 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
522static 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
577static int devcgroup_access_write(struct cgroup *cgrp, struct cftype *cft, 702static int devcgroup_access_write(struct cgroup *cgrp, struct cftype *cft,