aboutsummaryrefslogtreecommitdiffstats
path: root/security/device_cgroup.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/device_cgroup.c')
-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,