diff options
Diffstat (limited to 'security')
-rw-r--r-- | security/device_cgroup.c | 267 |
1 files changed, 211 insertions, 56 deletions
diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 1c69e38e3a2c..dd0dc574d78d 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c | |||
@@ -25,6 +25,12 @@ | |||
25 | 25 | ||
26 | static DEFINE_MUTEX(devcgroup_mutex); | 26 | static DEFINE_MUTEX(devcgroup_mutex); |
27 | 27 | ||
28 | enum devcg_behavior { | ||
29 | DEVCG_DEFAULT_NONE, | ||
30 | DEVCG_DEFAULT_ALLOW, | ||
31 | DEVCG_DEFAULT_DENY, | ||
32 | }; | ||
33 | |||
28 | /* | 34 | /* |
29 | * exception list locking rules: | 35 | * exception list locking rules: |
30 | * hold devcgroup_mutex for update/read. | 36 | * hold devcgroup_mutex for update/read. |
@@ -42,10 +48,9 @@ struct dev_exception_item { | |||
42 | struct dev_cgroup { | 48 | struct dev_cgroup { |
43 | struct cgroup_subsys_state css; | 49 | struct cgroup_subsys_state css; |
44 | struct list_head exceptions; | 50 | struct list_head exceptions; |
45 | enum { | 51 | enum devcg_behavior behavior; |
46 | DEVCG_DEFAULT_ALLOW, | 52 | /* temporary list for pending propagation operations */ |
47 | DEVCG_DEFAULT_DENY, | 53 | struct list_head propagate_pending; |
48 | } behavior; | ||
49 | }; | 54 | }; |
50 | 55 | ||
51 | 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) |
@@ -182,35 +187,62 @@ static void dev_exception_clean(struct dev_cgroup *dev_cgroup) | |||
182 | __dev_exception_clean(dev_cgroup); | 187 | __dev_exception_clean(dev_cgroup); |
183 | } | 188 | } |
184 | 189 | ||
190 | static inline bool is_devcg_online(const struct dev_cgroup *devcg) | ||
191 | { | ||
192 | return (devcg->behavior != DEVCG_DEFAULT_NONE); | ||
193 | } | ||
194 | |||
195 | /** | ||
196 | * devcgroup_online - initializes devcgroup's behavior and exceptions based on | ||
197 | * parent's | ||
198 | * @cgroup: cgroup getting online | ||
199 | * returns 0 in case of success, error code otherwise | ||
200 | */ | ||
201 | static int devcgroup_online(struct cgroup *cgroup) | ||
202 | { | ||
203 | struct dev_cgroup *dev_cgroup, *parent_dev_cgroup = NULL; | ||
204 | int ret = 0; | ||
205 | |||
206 | mutex_lock(&devcgroup_mutex); | ||
207 | dev_cgroup = cgroup_to_devcgroup(cgroup); | ||
208 | if (cgroup->parent) | ||
209 | parent_dev_cgroup = cgroup_to_devcgroup(cgroup->parent); | ||
210 | |||
211 | if (parent_dev_cgroup == NULL) | ||
212 | dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW; | ||
213 | else { | ||
214 | ret = dev_exceptions_copy(&dev_cgroup->exceptions, | ||
215 | &parent_dev_cgroup->exceptions); | ||
216 | if (!ret) | ||
217 | dev_cgroup->behavior = parent_dev_cgroup->behavior; | ||
218 | } | ||
219 | mutex_unlock(&devcgroup_mutex); | ||
220 | |||
221 | return ret; | ||
222 | } | ||
223 | |||
224 | static void devcgroup_offline(struct cgroup *cgroup) | ||
225 | { | ||
226 | struct dev_cgroup *dev_cgroup = cgroup_to_devcgroup(cgroup); | ||
227 | |||
228 | mutex_lock(&devcgroup_mutex); | ||
229 | dev_cgroup->behavior = DEVCG_DEFAULT_NONE; | ||
230 | mutex_unlock(&devcgroup_mutex); | ||
231 | } | ||
232 | |||
185 | /* | 233 | /* |
186 | * called from kernel/cgroup.c with cgroup_lock() held. | 234 | * called from kernel/cgroup.c with cgroup_lock() held. |
187 | */ | 235 | */ |
188 | static struct cgroup_subsys_state *devcgroup_css_alloc(struct cgroup *cgroup) | 236 | static struct cgroup_subsys_state *devcgroup_css_alloc(struct cgroup *cgroup) |
189 | { | 237 | { |
190 | struct dev_cgroup *dev_cgroup, *parent_dev_cgroup; | 238 | struct dev_cgroup *dev_cgroup; |
191 | struct cgroup *parent_cgroup; | ||
192 | int ret; | ||
193 | 239 | ||
194 | dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL); | 240 | dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL); |
195 | if (!dev_cgroup) | 241 | if (!dev_cgroup) |
196 | return ERR_PTR(-ENOMEM); | 242 | return ERR_PTR(-ENOMEM); |
197 | INIT_LIST_HEAD(&dev_cgroup->exceptions); | 243 | INIT_LIST_HEAD(&dev_cgroup->exceptions); |
198 | parent_cgroup = cgroup->parent; | 244 | INIT_LIST_HEAD(&dev_cgroup->propagate_pending); |
199 | 245 | dev_cgroup->behavior = DEVCG_DEFAULT_NONE; | |
200 | if (parent_cgroup == NULL) | ||
201 | dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW; | ||
202 | else { | ||
203 | parent_dev_cgroup = cgroup_to_devcgroup(parent_cgroup); | ||
204 | mutex_lock(&devcgroup_mutex); | ||
205 | ret = dev_exceptions_copy(&dev_cgroup->exceptions, | ||
206 | &parent_dev_cgroup->exceptions); | ||
207 | dev_cgroup->behavior = parent_dev_cgroup->behavior; | ||
208 | mutex_unlock(&devcgroup_mutex); | ||
209 | if (ret) { | ||
210 | kfree(dev_cgroup); | ||
211 | return ERR_PTR(ret); | ||
212 | } | ||
213 | } | ||
214 | 246 | ||
215 | return &dev_cgroup->css; | 247 | return &dev_cgroup->css; |
216 | } | 248 | } |
@@ -304,9 +336,11 @@ static int devcgroup_seq_read(struct cgroup *cgroup, struct cftype *cft, | |||
304 | * verify if a certain access is allowed. | 336 | * verify if a certain access is allowed. |
305 | * @dev_cgroup: dev cgroup to be tested against | 337 | * @dev_cgroup: dev cgroup to be tested against |
306 | * @refex: new exception | 338 | * @refex: new exception |
339 | * @behavior: behavior of the exception | ||
307 | */ | 340 | */ |
308 | static int may_access(struct dev_cgroup *dev_cgroup, | 341 | static bool may_access(struct dev_cgroup *dev_cgroup, |
309 | struct dev_exception_item *refex) | 342 | struct dev_exception_item *refex, |
343 | enum devcg_behavior behavior) | ||
310 | { | 344 | { |
311 | struct dev_exception_item *ex; | 345 | struct dev_exception_item *ex; |
312 | bool match = false; | 346 | bool match = false; |
@@ -330,18 +364,29 @@ static int may_access(struct dev_cgroup *dev_cgroup, | |||
330 | break; | 364 | break; |
331 | } | 365 | } |
332 | 366 | ||
333 | /* | 367 | if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) { |
334 | * In two cases we'll consider this new exception valid: | 368 | if (behavior == DEVCG_DEFAULT_ALLOW) { |
335 | * - the dev cgroup has its default policy to allow + exception list: | 369 | /* the exception will deny access to certain devices */ |
336 | * the new exception should *not* match any of the exceptions | 370 | return true; |
337 | * (behavior == DEVCG_DEFAULT_ALLOW, !match) | 371 | } else { |
338 | * - the dev cgroup has its default policy to deny + exception list: | 372 | /* the exception will allow access to certain devices */ |
339 | * the new exception *should* match the exceptions | 373 | if (match) |
340 | * (behavior == DEVCG_DEFAULT_DENY, match) | 374 | /* |
341 | */ | 375 | * a new exception allowing access shouldn't |
342 | if ((dev_cgroup->behavior == DEVCG_DEFAULT_DENY) == match) | 376 | * match an parent's exception |
343 | return 1; | 377 | */ |
344 | return 0; | 378 | return false; |
379 | return true; | ||
380 | } | ||
381 | } else { | ||
382 | /* only behavior == DEVCG_DEFAULT_DENY allowed here */ | ||
383 | if (match) | ||
384 | /* parent has an exception that matches the proposed */ | ||
385 | return true; | ||
386 | else | ||
387 | return false; | ||
388 | } | ||
389 | return false; | ||
345 | } | 390 | } |
346 | 391 | ||
347 | /* | 392 | /* |
@@ -358,7 +403,7 @@ static int parent_has_perm(struct dev_cgroup *childcg, | |||
358 | if (!pcg) | 403 | if (!pcg) |
359 | return 1; | 404 | return 1; |
360 | parent = cgroup_to_devcgroup(pcg); | 405 | parent = cgroup_to_devcgroup(pcg); |
361 | return may_access(parent, ex); | 406 | return may_access(parent, ex, childcg->behavior); |
362 | } | 407 | } |
363 | 408 | ||
364 | /** | 409 | /** |
@@ -374,6 +419,111 @@ static inline int may_allow_all(struct dev_cgroup *parent) | |||
374 | return parent->behavior == DEVCG_DEFAULT_ALLOW; | 419 | return parent->behavior == DEVCG_DEFAULT_ALLOW; |
375 | } | 420 | } |
376 | 421 | ||
422 | /** | ||
423 | * revalidate_active_exceptions - walks through the active exception list and | ||
424 | * revalidates the exceptions based on parent's | ||
425 | * behavior and exceptions. The exceptions that | ||
426 | * are no longer valid will be removed. | ||
427 | * Called with devcgroup_mutex held. | ||
428 | * @devcg: cgroup which exceptions will be checked | ||
429 | * | ||
430 | * This is one of the three key functions for hierarchy implementation. | ||
431 | * This function is responsible for re-evaluating all the cgroup's active | ||
432 | * exceptions due to a parent's exception change. | ||
433 | * Refer to Documentation/cgroups/devices.txt for more details. | ||
434 | */ | ||
435 | static void revalidate_active_exceptions(struct dev_cgroup *devcg) | ||
436 | { | ||
437 | struct dev_exception_item *ex; | ||
438 | struct list_head *this, *tmp; | ||
439 | |||
440 | list_for_each_safe(this, tmp, &devcg->exceptions) { | ||
441 | ex = container_of(this, struct dev_exception_item, list); | ||
442 | if (!parent_has_perm(devcg, ex)) | ||
443 | dev_exception_rm(devcg, ex); | ||
444 | } | ||
445 | } | ||
446 | |||
447 | /** | ||
448 | * get_online_devcg - walks the cgroup tree and fills a list with the online | ||
449 | * groups | ||
450 | * @root: cgroup used as starting point | ||
451 | * @online: list that will be filled with online groups | ||
452 | * | ||
453 | * Must be called with devcgroup_mutex held. Grabs RCU lock. | ||
454 | * Because devcgroup_mutex is held, no devcg will become online or offline | ||
455 | * during the tree walk (see devcgroup_online, devcgroup_offline) | ||
456 | * A separated list is needed because propagate_behavior() and | ||
457 | * propagate_exception() need to allocate memory and can block. | ||
458 | */ | ||
459 | static void get_online_devcg(struct cgroup *root, struct list_head *online) | ||
460 | { | ||
461 | struct cgroup *pos; | ||
462 | struct dev_cgroup *devcg; | ||
463 | |||
464 | lockdep_assert_held(&devcgroup_mutex); | ||
465 | |||
466 | rcu_read_lock(); | ||
467 | cgroup_for_each_descendant_pre(pos, root) { | ||
468 | devcg = cgroup_to_devcgroup(pos); | ||
469 | if (is_devcg_online(devcg)) | ||
470 | list_add_tail(&devcg->propagate_pending, online); | ||
471 | } | ||
472 | rcu_read_unlock(); | ||
473 | } | ||
474 | |||
475 | /** | ||
476 | * propagate_exception - propagates a new exception to the children | ||
477 | * @devcg_root: device cgroup that added a new exception | ||
478 | * @ex: new exception to be propagated | ||
479 | * | ||
480 | * returns: 0 in case of success, != 0 in case of error | ||
481 | */ | ||
482 | static int propagate_exception(struct dev_cgroup *devcg_root, | ||
483 | struct dev_exception_item *ex) | ||
484 | { | ||
485 | struct cgroup *root = devcg_root->css.cgroup; | ||
486 | struct dev_cgroup *devcg, *parent, *tmp; | ||
487 | int rc = 0; | ||
488 | LIST_HEAD(pending); | ||
489 | |||
490 | get_online_devcg(root, &pending); | ||
491 | |||
492 | list_for_each_entry_safe(devcg, tmp, &pending, propagate_pending) { | ||
493 | parent = cgroup_to_devcgroup(devcg->css.cgroup->parent); | ||
494 | |||
495 | /* | ||
496 | * in case both root's behavior and devcg is allow, a new | ||
497 | * restriction means adding to the exception list | ||
498 | */ | ||
499 | if (devcg_root->behavior == DEVCG_DEFAULT_ALLOW && | ||
500 | devcg->behavior == DEVCG_DEFAULT_ALLOW) { | ||
501 | rc = dev_exception_add(devcg, ex); | ||
502 | if (rc) | ||
503 | break; | ||
504 | } else { | ||
505 | /* | ||
506 | * in the other possible cases: | ||
507 | * root's behavior: allow, devcg's: deny | ||
508 | * root's behavior: deny, devcg's: deny | ||
509 | * the exception will be removed | ||
510 | */ | ||
511 | dev_exception_rm(devcg, ex); | ||
512 | } | ||
513 | revalidate_active_exceptions(devcg); | ||
514 | |||
515 | list_del_init(&devcg->propagate_pending); | ||
516 | } | ||
517 | return rc; | ||
518 | } | ||
519 | |||
520 | static inline bool has_children(struct dev_cgroup *devcgroup) | ||
521 | { | ||
522 | struct cgroup *cgrp = devcgroup->css.cgroup; | ||
523 | |||
524 | return !list_empty(&cgrp->children); | ||
525 | } | ||
526 | |||
377 | /* | 527 | /* |
378 | * Modify the exception list using allow/deny rules. | 528 | * Modify the exception list using allow/deny rules. |
379 | * CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD | 529 | * CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD |
@@ -392,7 +542,7 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, | |||
392 | { | 542 | { |
393 | const char *b; | 543 | const char *b; |
394 | char temp[12]; /* 11 + 1 characters needed for a u32 */ | 544 | char temp[12]; /* 11 + 1 characters needed for a u32 */ |
395 | int count, rc; | 545 | int count, rc = 0; |
396 | struct dev_exception_item ex; | 546 | struct dev_exception_item ex; |
397 | struct cgroup *p = devcgroup->css.cgroup; | 547 | struct cgroup *p = devcgroup->css.cgroup; |
398 | struct dev_cgroup *parent = NULL; | 548 | struct dev_cgroup *parent = NULL; |
@@ -410,6 +560,9 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, | |||
410 | case 'a': | 560 | case 'a': |
411 | switch (filetype) { | 561 | switch (filetype) { |
412 | case DEVCG_ALLOW: | 562 | case DEVCG_ALLOW: |
563 | if (has_children(devcgroup)) | ||
564 | return -EINVAL; | ||
565 | |||
413 | if (!may_allow_all(parent)) | 566 | if (!may_allow_all(parent)) |
414 | return -EPERM; | 567 | return -EPERM; |
415 | dev_exception_clean(devcgroup); | 568 | dev_exception_clean(devcgroup); |
@@ -423,6 +576,9 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, | |||
423 | return rc; | 576 | return rc; |
424 | break; | 577 | break; |
425 | case DEVCG_DENY: | 578 | case DEVCG_DENY: |
579 | if (has_children(devcgroup)) | ||
580 | return -EINVAL; | ||
581 | |||
426 | dev_exception_clean(devcgroup); | 582 | dev_exception_clean(devcgroup); |
427 | devcgroup->behavior = DEVCG_DEFAULT_DENY; | 583 | devcgroup->behavior = DEVCG_DEFAULT_DENY; |
428 | break; | 584 | break; |
@@ -517,22 +673,28 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, | |||
517 | dev_exception_rm(devcgroup, &ex); | 673 | dev_exception_rm(devcgroup, &ex); |
518 | return 0; | 674 | return 0; |
519 | } | 675 | } |
520 | return dev_exception_add(devcgroup, &ex); | 676 | rc = dev_exception_add(devcgroup, &ex); |
677 | break; | ||
521 | case DEVCG_DENY: | 678 | case DEVCG_DENY: |
522 | /* | 679 | /* |
523 | * If the default policy is to deny by default, try to remove | 680 | * If the default policy is to deny by default, try to remove |
524 | * an matching exception instead. And be silent about it: we | 681 | * an matching exception instead. And be silent about it: we |
525 | * don't want to break compatibility | 682 | * don't want to break compatibility |
526 | */ | 683 | */ |
527 | if (devcgroup->behavior == DEVCG_DEFAULT_DENY) { | 684 | if (devcgroup->behavior == DEVCG_DEFAULT_DENY) |
528 | dev_exception_rm(devcgroup, &ex); | 685 | dev_exception_rm(devcgroup, &ex); |
529 | return 0; | 686 | else |
530 | } | 687 | rc = dev_exception_add(devcgroup, &ex); |
531 | return dev_exception_add(devcgroup, &ex); | 688 | |
689 | if (rc) | ||
690 | break; | ||
691 | /* we only propagate new restrictions */ | ||
692 | rc = propagate_exception(devcgroup, &ex); | ||
693 | break; | ||
532 | default: | 694 | default: |
533 | return -EINVAL; | 695 | rc = -EINVAL; |
534 | } | 696 | } |
535 | return 0; | 697 | return rc; |
536 | } | 698 | } |
537 | 699 | ||
538 | static int devcgroup_access_write(struct cgroup *cgrp, struct cftype *cft, | 700 | static int devcgroup_access_write(struct cgroup *cgrp, struct cftype *cft, |
@@ -571,17 +733,10 @@ struct cgroup_subsys devices_subsys = { | |||
571 | .can_attach = devcgroup_can_attach, | 733 | .can_attach = devcgroup_can_attach, |
572 | .css_alloc = devcgroup_css_alloc, | 734 | .css_alloc = devcgroup_css_alloc, |
573 | .css_free = devcgroup_css_free, | 735 | .css_free = devcgroup_css_free, |
736 | .css_online = devcgroup_online, | ||
737 | .css_offline = devcgroup_offline, | ||
574 | .subsys_id = devices_subsys_id, | 738 | .subsys_id = devices_subsys_id, |
575 | .base_cftypes = dev_cgroup_files, | 739 | .base_cftypes = dev_cgroup_files, |
576 | |||
577 | /* | ||
578 | * While devices cgroup has the rudimentary hierarchy support which | ||
579 | * checks the parent's restriction, it doesn't properly propagates | ||
580 | * config changes in ancestors to their descendents. A child | ||
581 | * should only be allowed to add more restrictions to the parent's | ||
582 | * configuration. Fix it and remove the following. | ||
583 | */ | ||
584 | .broken_hierarchy = true, | ||
585 | }; | 740 | }; |
586 | 741 | ||
587 | /** | 742 | /** |
@@ -609,7 +764,7 @@ static int __devcgroup_check_permission(short type, u32 major, u32 minor, | |||
609 | 764 | ||
610 | rcu_read_lock(); | 765 | rcu_read_lock(); |
611 | dev_cgroup = task_devcgroup(current); | 766 | dev_cgroup = task_devcgroup(current); |
612 | rc = may_access(dev_cgroup, &ex); | 767 | rc = may_access(dev_cgroup, &ex, dev_cgroup->behavior); |
613 | rcu_read_unlock(); | 768 | rcu_read_unlock(); |
614 | 769 | ||
615 | if (!rc) | 770 | if (!rc) |