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) |
