diff options
author | Paul Moore <pmoore@redhat.com> | 2014-06-17 17:30:23 -0400 |
---|---|---|
committer | Paul Moore <pmoore@redhat.com> | 2014-06-17 17:30:23 -0400 |
commit | 170b5910d9fbea79de1bb40df22eda5f98250c0c (patch) | |
tree | ca9560e878d2842d45c6f99077d0d8b8f8b0f9ba /security/device_cgroup.c | |
parent | 47dd0b76ace953bd2c0479076db0d3e3b9594003 (diff) | |
parent | 1860e379875dfe7271c649058aeddffe5afd9d0d (diff) |
Merge tag 'v3.15' into next
Linux 3.15
Diffstat (limited to 'security/device_cgroup.c')
-rw-r--r-- | security/device_cgroup.c | 214 |
1 files changed, 163 insertions, 51 deletions
diff --git a/security/device_cgroup.c b/security/device_cgroup.c index d3b6d2cd3a06..9134dbf70d3e 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c | |||
@@ -58,11 +58,9 @@ static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s) | |||
58 | 58 | ||
59 | static inline struct dev_cgroup *task_devcgroup(struct task_struct *task) | 59 | static inline struct dev_cgroup *task_devcgroup(struct task_struct *task) |
60 | { | 60 | { |
61 | return css_to_devcgroup(task_css(task, devices_subsys_id)); | 61 | return css_to_devcgroup(task_css(task, devices_cgrp_id)); |
62 | } | 62 | } |
63 | 63 | ||
64 | struct cgroup_subsys devices_subsys; | ||
65 | |||
66 | /* | 64 | /* |
67 | * called under devcgroup_mutex | 65 | * called under devcgroup_mutex |
68 | */ | 66 | */ |
@@ -308,57 +306,138 @@ static int devcgroup_seq_show(struct seq_file *m, void *v) | |||
308 | } | 306 | } |
309 | 307 | ||
310 | /** | 308 | /** |
311 | * may_access - verifies if a new exception is part of what is allowed | 309 | * match_exception - iterates the exception list trying to find a complete match |
312 | * by a dev cgroup based on the default policy + | 310 | * @exceptions: list of exceptions |
313 | * exceptions. This is used to make sure a child cgroup | 311 | * @type: device type (DEV_BLOCK or DEV_CHAR) |
314 | * won't have more privileges than its parent or to | 312 | * @major: device file major number, ~0 to match all |
315 | * verify if a certain access is allowed. | 313 | * @minor: device file minor number, ~0 to match all |
316 | * @dev_cgroup: dev cgroup to be tested against | 314 | * @access: permission mask (ACC_READ, ACC_WRITE, ACC_MKNOD) |
317 | * @refex: new exception | 315 | * |
318 | * @behavior: behavior of the exception | 316 | * It is considered a complete match if an exception is found that will |
317 | * contain the entire range of provided parameters. | ||
318 | * | ||
319 | * Return: true in case it matches an exception completely | ||
319 | */ | 320 | */ |
320 | static bool may_access(struct dev_cgroup *dev_cgroup, | 321 | static bool match_exception(struct list_head *exceptions, short type, |
321 | struct dev_exception_item *refex, | 322 | u32 major, u32 minor, short access) |
322 | enum devcg_behavior behavior) | ||
323 | { | 323 | { |
324 | struct dev_exception_item *ex; | 324 | struct dev_exception_item *ex; |
325 | bool match = false; | ||
326 | 325 | ||
327 | rcu_lockdep_assert(rcu_read_lock_held() || | 326 | list_for_each_entry_rcu(ex, exceptions, list) { |
328 | lockdep_is_held(&devcgroup_mutex), | 327 | if ((type & DEV_BLOCK) && !(ex->type & DEV_BLOCK)) |
329 | "device_cgroup::may_access() called without proper synchronization"); | 328 | continue; |
329 | if ((type & DEV_CHAR) && !(ex->type & DEV_CHAR)) | ||
330 | continue; | ||
331 | if (ex->major != ~0 && ex->major != major) | ||
332 | continue; | ||
333 | if (ex->minor != ~0 && ex->minor != minor) | ||
334 | continue; | ||
335 | /* provided access cannot have more than the exception rule */ | ||
336 | if (access & (~ex->access)) | ||
337 | continue; | ||
338 | return true; | ||
339 | } | ||
340 | return false; | ||
341 | } | ||
330 | 342 | ||
331 | list_for_each_entry_rcu(ex, &dev_cgroup->exceptions, list) { | 343 | /** |
332 | if ((refex->type & DEV_BLOCK) && !(ex->type & DEV_BLOCK)) | 344 | * match_exception_partial - iterates the exception list trying to find a partial match |
345 | * @exceptions: list of exceptions | ||
346 | * @type: device type (DEV_BLOCK or DEV_CHAR) | ||
347 | * @major: device file major number, ~0 to match all | ||
348 | * @minor: device file minor number, ~0 to match all | ||
349 | * @access: permission mask (ACC_READ, ACC_WRITE, ACC_MKNOD) | ||
350 | * | ||
351 | * It is considered a partial match if an exception's range is found to | ||
352 | * contain *any* of the devices specified by provided parameters. This is | ||
353 | * used to make sure no extra access is being granted that is forbidden by | ||
354 | * any of the exception list. | ||
355 | * | ||
356 | * Return: true in case the provided range mat matches an exception completely | ||
357 | */ | ||
358 | static bool match_exception_partial(struct list_head *exceptions, short type, | ||
359 | u32 major, u32 minor, short access) | ||
360 | { | ||
361 | struct dev_exception_item *ex; | ||
362 | |||
363 | list_for_each_entry_rcu(ex, exceptions, list) { | ||
364 | if ((type & DEV_BLOCK) && !(ex->type & DEV_BLOCK)) | ||
333 | continue; | 365 | continue; |
334 | if ((refex->type & DEV_CHAR) && !(ex->type & DEV_CHAR)) | 366 | if ((type & DEV_CHAR) && !(ex->type & DEV_CHAR)) |
335 | continue; | 367 | continue; |
336 | if (ex->major != ~0 && ex->major != refex->major) | 368 | /* |
369 | * We must be sure that both the exception and the provided | ||
370 | * range aren't masking all devices | ||
371 | */ | ||
372 | if (ex->major != ~0 && major != ~0 && ex->major != major) | ||
337 | continue; | 373 | continue; |
338 | if (ex->minor != ~0 && ex->minor != refex->minor) | 374 | if (ex->minor != ~0 && minor != ~0 && ex->minor != minor) |
339 | continue; | 375 | continue; |
340 | if (refex->access & (~ex->access)) | 376 | /* |
377 | * In order to make sure the provided range isn't matching | ||
378 | * an exception, all its access bits shouldn't match the | ||
379 | * exception's access bits | ||
380 | */ | ||
381 | if (!(access & ex->access)) | ||
341 | continue; | 382 | continue; |
342 | match = true; | 383 | return true; |
343 | break; | ||
344 | } | 384 | } |
385 | return false; | ||
386 | } | ||
387 | |||
388 | /** | ||
389 | * verify_new_ex - verifies if a new exception is allowed by parent cgroup's permissions | ||
390 | * @dev_cgroup: dev cgroup to be tested against | ||
391 | * @refex: new exception | ||
392 | * @behavior: behavior of the exception's dev_cgroup | ||
393 | * | ||
394 | * This is used to make sure a child cgroup won't have more privileges | ||
395 | * than its parent | ||
396 | */ | ||
397 | static bool verify_new_ex(struct dev_cgroup *dev_cgroup, | ||
398 | struct dev_exception_item *refex, | ||
399 | enum devcg_behavior behavior) | ||
400 | { | ||
401 | bool match = false; | ||
402 | |||
403 | rcu_lockdep_assert(rcu_read_lock_held() || | ||
404 | lockdep_is_held(&devcgroup_mutex), | ||
405 | "device_cgroup:verify_new_ex called without proper synchronization"); | ||
345 | 406 | ||
346 | if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) { | 407 | if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) { |
347 | if (behavior == DEVCG_DEFAULT_ALLOW) { | 408 | if (behavior == DEVCG_DEFAULT_ALLOW) { |
348 | /* the exception will deny access to certain devices */ | 409 | /* |
410 | * new exception in the child doesn't matter, only | ||
411 | * adding extra restrictions | ||
412 | */ | ||
349 | return true; | 413 | return true; |
350 | } else { | 414 | } else { |
351 | /* the exception will allow access to certain devices */ | 415 | /* |
416 | * new exception in the child will add more devices | ||
417 | * that can be acessed, so it can't match any of | ||
418 | * parent's exceptions, even slightly | ||
419 | */ | ||
420 | match = match_exception_partial(&dev_cgroup->exceptions, | ||
421 | refex->type, | ||
422 | refex->major, | ||
423 | refex->minor, | ||
424 | refex->access); | ||
425 | |||
352 | if (match) | 426 | if (match) |
353 | /* | ||
354 | * a new exception allowing access shouldn't | ||
355 | * match an parent's exception | ||
356 | */ | ||
357 | return false; | 427 | return false; |
358 | return true; | 428 | return true; |
359 | } | 429 | } |
360 | } else { | 430 | } else { |
361 | /* only behavior == DEVCG_DEFAULT_DENY allowed here */ | 431 | /* |
432 | * Only behavior == DEVCG_DEFAULT_DENY allowed here, therefore | ||
433 | * the new exception will add access to more devices and must | ||
434 | * be contained completely in an parent's exception to be | ||
435 | * allowed | ||
436 | */ | ||
437 | match = match_exception(&dev_cgroup->exceptions, refex->type, | ||
438 | refex->major, refex->minor, | ||
439 | refex->access); | ||
440 | |||
362 | if (match) | 441 | if (match) |
363 | /* parent has an exception that matches the proposed */ | 442 | /* parent has an exception that matches the proposed */ |
364 | return true; | 443 | return true; |
@@ -380,7 +459,38 @@ static int parent_has_perm(struct dev_cgroup *childcg, | |||
380 | 459 | ||
381 | if (!parent) | 460 | if (!parent) |
382 | return 1; | 461 | return 1; |
383 | return may_access(parent, ex, childcg->behavior); | 462 | return verify_new_ex(parent, ex, childcg->behavior); |
463 | } | ||
464 | |||
465 | /** | ||
466 | * parent_allows_removal - verify if it's ok to remove an exception | ||
467 | * @childcg: child cgroup from where the exception will be removed | ||
468 | * @ex: exception being removed | ||
469 | * | ||
470 | * When removing an exception in cgroups with default ALLOW policy, it must | ||
471 | * be checked if removing it will give the child cgroup more access than the | ||
472 | * parent. | ||
473 | * | ||
474 | * Return: true if it's ok to remove exception, false otherwise | ||
475 | */ | ||
476 | static bool parent_allows_removal(struct dev_cgroup *childcg, | ||
477 | struct dev_exception_item *ex) | ||
478 | { | ||
479 | struct dev_cgroup *parent = css_to_devcgroup(css_parent(&childcg->css)); | ||
480 | |||
481 | if (!parent) | ||
482 | return true; | ||
483 | |||
484 | /* It's always allowed to remove access to devices */ | ||
485 | if (childcg->behavior == DEVCG_DEFAULT_DENY) | ||
486 | return true; | ||
487 | |||
488 | /* | ||
489 | * Make sure you're not removing part or a whole exception existing in | ||
490 | * the parent cgroup | ||
491 | */ | ||
492 | return !match_exception_partial(&parent->exceptions, ex->type, | ||
493 | ex->major, ex->minor, ex->access); | ||
384 | } | 494 | } |
385 | 495 | ||
386 | /** | 496 | /** |
@@ -498,7 +608,7 @@ static inline bool has_children(struct dev_cgroup *devcgroup) | |||
498 | * parent cgroup has the access you're asking for. | 608 | * parent cgroup has the access you're asking for. |
499 | */ | 609 | */ |
500 | static int devcgroup_update_access(struct dev_cgroup *devcgroup, | 610 | static int devcgroup_update_access(struct dev_cgroup *devcgroup, |
501 | int filetype, const char *buffer) | 611 | int filetype, char *buffer) |
502 | { | 612 | { |
503 | const char *b; | 613 | const char *b; |
504 | char temp[12]; /* 11 + 1 characters needed for a u32 */ | 614 | char temp[12]; /* 11 + 1 characters needed for a u32 */ |
@@ -618,17 +728,21 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, | |||
618 | 728 | ||
619 | switch (filetype) { | 729 | switch (filetype) { |
620 | case DEVCG_ALLOW: | 730 | case DEVCG_ALLOW: |
621 | if (!parent_has_perm(devcgroup, &ex)) | ||
622 | return -EPERM; | ||
623 | /* | 731 | /* |
624 | * If the default policy is to allow by default, try to remove | 732 | * If the default policy is to allow by default, try to remove |
625 | * an matching exception instead. And be silent about it: we | 733 | * an matching exception instead. And be silent about it: we |
626 | * don't want to break compatibility | 734 | * don't want to break compatibility |
627 | */ | 735 | */ |
628 | if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { | 736 | if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { |
737 | /* Check if the parent allows removing it first */ | ||
738 | if (!parent_allows_removal(devcgroup, &ex)) | ||
739 | return -EPERM; | ||
629 | dev_exception_rm(devcgroup, &ex); | 740 | dev_exception_rm(devcgroup, &ex); |
630 | return 0; | 741 | break; |
631 | } | 742 | } |
743 | |||
744 | if (!parent_has_perm(devcgroup, &ex)) | ||
745 | return -EPERM; | ||
632 | rc = dev_exception_add(devcgroup, &ex); | 746 | rc = dev_exception_add(devcgroup, &ex); |
633 | break; | 747 | break; |
634 | case DEVCG_DENY: | 748 | case DEVCG_DENY: |
@@ -654,7 +768,7 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, | |||
654 | } | 768 | } |
655 | 769 | ||
656 | static int devcgroup_access_write(struct cgroup_subsys_state *css, | 770 | static int devcgroup_access_write(struct cgroup_subsys_state *css, |
657 | struct cftype *cft, const char *buffer) | 771 | struct cftype *cft, char *buffer) |
658 | { | 772 | { |
659 | int retval; | 773 | int retval; |
660 | 774 | ||
@@ -684,13 +798,11 @@ static struct cftype dev_cgroup_files[] = { | |||
684 | { } /* terminate */ | 798 | { } /* terminate */ |
685 | }; | 799 | }; |
686 | 800 | ||
687 | struct cgroup_subsys devices_subsys = { | 801 | struct cgroup_subsys devices_cgrp_subsys = { |
688 | .name = "devices", | ||
689 | .css_alloc = devcgroup_css_alloc, | 802 | .css_alloc = devcgroup_css_alloc, |
690 | .css_free = devcgroup_css_free, | 803 | .css_free = devcgroup_css_free, |
691 | .css_online = devcgroup_online, | 804 | .css_online = devcgroup_online, |
692 | .css_offline = devcgroup_offline, | 805 | .css_offline = devcgroup_offline, |
693 | .subsys_id = devices_subsys_id, | ||
694 | .base_cftypes = dev_cgroup_files, | 806 | .base_cftypes = dev_cgroup_files, |
695 | }; | 807 | }; |
696 | 808 | ||
@@ -708,18 +820,18 @@ static int __devcgroup_check_permission(short type, u32 major, u32 minor, | |||
708 | short access) | 820 | short access) |
709 | { | 821 | { |
710 | struct dev_cgroup *dev_cgroup; | 822 | struct dev_cgroup *dev_cgroup; |
711 | struct dev_exception_item ex; | 823 | bool rc; |
712 | int rc; | ||
713 | |||
714 | memset(&ex, 0, sizeof(ex)); | ||
715 | ex.type = type; | ||
716 | ex.major = major; | ||
717 | ex.minor = minor; | ||
718 | ex.access = access; | ||
719 | 824 | ||
720 | rcu_read_lock(); | 825 | rcu_read_lock(); |
721 | dev_cgroup = task_devcgroup(current); | 826 | dev_cgroup = task_devcgroup(current); |
722 | rc = may_access(dev_cgroup, &ex, dev_cgroup->behavior); | 827 | if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) |
828 | /* Can't match any of the exceptions, even partially */ | ||
829 | rc = !match_exception_partial(&dev_cgroup->exceptions, | ||
830 | type, major, minor, access); | ||
831 | else | ||
832 | /* Need to match completely one exception to be allowed */ | ||
833 | rc = match_exception(&dev_cgroup->exceptions, type, major, | ||
834 | minor, access); | ||
723 | rcu_read_unlock(); | 835 | rcu_read_unlock(); |
724 | 836 | ||
725 | if (!rc) | 837 | if (!rc) |