diff options
Diffstat (limited to 'security/device_cgroup.c')
-rw-r--r-- | security/device_cgroup.c | 162 |
1 files changed, 122 insertions, 40 deletions
diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 8365909f5f8c..b9048dc46b1a 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c | |||
@@ -306,57 +306,139 @@ static int devcgroup_seq_show(struct seq_file *m, void *v) | |||
306 | } | 306 | } |
307 | 307 | ||
308 | /** | 308 | /** |
309 | * may_access - verifies if a new exception is part of what is allowed | 309 | * match_exception - iterates the exception list trying to match a rule |
310 | * by a dev cgroup based on the default policy + | 310 | * based on type, major, minor and access type. It is |
311 | * exceptions. This is used to make sure a child cgroup | 311 | * considered a match if an exception is found that |
312 | * won't have more privileges than its parent or to | 312 | * will contain the entire range of provided parameters. |
313 | * verify if a certain access is allowed. | 313 | * @exceptions: list of exceptions |
314 | * @dev_cgroup: dev cgroup to be tested against | 314 | * @type: device type (DEV_BLOCK or DEV_CHAR) |
315 | * @refex: new exception | 315 | * @major: device file major number, ~0 to match all |
316 | * @behavior: behavior of the exception | 316 | * @minor: device file minor number, ~0 to match all |
317 | * @access: permission mask (ACC_READ, ACC_WRITE, ACC_MKNOD) | ||
318 | * | ||
319 | * returns: true in case it matches an exception completely | ||
317 | */ | 320 | */ |
318 | static bool may_access(struct dev_cgroup *dev_cgroup, | 321 | static bool match_exception(struct list_head *exceptions, short type, |
319 | struct dev_exception_item *refex, | 322 | u32 major, u32 minor, short access) |
320 | enum devcg_behavior behavior) | ||
321 | { | 323 | { |
322 | struct dev_exception_item *ex; | 324 | struct dev_exception_item *ex; |
323 | bool match = false; | ||
324 | 325 | ||
325 | rcu_lockdep_assert(rcu_read_lock_held() || | 326 | list_for_each_entry_rcu(ex, exceptions, list) { |
326 | lockdep_is_held(&devcgroup_mutex), | 327 | if ((type & DEV_BLOCK) && !(ex->type & DEV_BLOCK)) |
327 | "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 | } | ||
342 | |||
343 | /** | ||
344 | * match_exception_partial - iterates the exception list trying to match a rule | ||
345 | * based on type, major, minor and access type. It is | ||
346 | * considered a match if an exception's range is | ||
347 | * found to contain *any* of the devices specified by | ||
348 | * provided parameters. This is used to make sure no | ||
349 | * extra access is being granted that is forbidden by | ||
350 | * any of the exception list. | ||
351 | * @exceptions: list of exceptions | ||
352 | * @type: device type (DEV_BLOCK or DEV_CHAR) | ||
353 | * @major: device file major number, ~0 to match all | ||
354 | * @minor: device file minor number, ~0 to match all | ||
355 | * @access: permission mask (ACC_READ, ACC_WRITE, ACC_MKNOD) | ||
356 | * | ||
357 | * returns: true in case the provided range mat matches an exception completely | ||
358 | */ | ||
359 | static bool match_exception_partial(struct list_head *exceptions, short type, | ||
360 | u32 major, u32 minor, short access) | ||
361 | { | ||
362 | struct dev_exception_item *ex; | ||
328 | 363 | ||
329 | list_for_each_entry_rcu(ex, &dev_cgroup->exceptions, list) { | 364 | list_for_each_entry_rcu(ex, exceptions, list) { |
330 | if ((refex->type & DEV_BLOCK) && !(ex->type & DEV_BLOCK)) | 365 | if ((type & DEV_BLOCK) && !(ex->type & DEV_BLOCK)) |
331 | continue; | 366 | continue; |
332 | if ((refex->type & DEV_CHAR) && !(ex->type & DEV_CHAR)) | 367 | if ((type & DEV_CHAR) && !(ex->type & DEV_CHAR)) |
333 | continue; | 368 | continue; |
334 | if (ex->major != ~0 && ex->major != refex->major) | 369 | /* |
370 | * We must be sure that both the exception and the provided | ||
371 | * range aren't masking all devices | ||
372 | */ | ||
373 | if (ex->major != ~0 && major != ~0 && ex->major != major) | ||
335 | continue; | 374 | continue; |
336 | if (ex->minor != ~0 && ex->minor != refex->minor) | 375 | if (ex->minor != ~0 && minor != ~0 && ex->minor != minor) |
337 | continue; | 376 | continue; |
338 | if (refex->access & (~ex->access)) | 377 | /* |
378 | * In order to make sure the provided range isn't matching | ||
379 | * an exception, all its access bits shouldn't match the | ||
380 | * exception's access bits | ||
381 | */ | ||
382 | if (!(access & ex->access)) | ||
339 | continue; | 383 | continue; |
340 | match = true; | 384 | return true; |
341 | break; | ||
342 | } | 385 | } |
386 | return false; | ||
387 | } | ||
388 | |||
389 | /** | ||
390 | * verify_new_ex - verifies if a new exception is part of what is allowed | ||
391 | * by a dev cgroup based on the default policy + | ||
392 | * exceptions. This is used to make sure a child cgroup | ||
393 | * won't have more privileges than its parent | ||
394 | * @dev_cgroup: dev cgroup to be tested against | ||
395 | * @refex: new exception | ||
396 | * @behavior: behavior of the exception's dev_cgroup | ||
397 | */ | ||
398 | static bool verify_new_ex(struct dev_cgroup *dev_cgroup, | ||
399 | struct dev_exception_item *refex, | ||
400 | enum devcg_behavior behavior) | ||
401 | { | ||
402 | bool match = false; | ||
403 | |||
404 | rcu_lockdep_assert(rcu_read_lock_held() || | ||
405 | lockdep_is_held(&devcgroup_mutex), | ||
406 | "device_cgroup:verify_new_ex called without proper synchronization"); | ||
343 | 407 | ||
344 | if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) { | 408 | if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) { |
345 | if (behavior == DEVCG_DEFAULT_ALLOW) { | 409 | if (behavior == DEVCG_DEFAULT_ALLOW) { |
346 | /* the exception will deny access to certain devices */ | 410 | /* |
411 | * new exception in the child doesn't matter, only | ||
412 | * adding extra restrictions | ||
413 | */ | ||
347 | return true; | 414 | return true; |
348 | } else { | 415 | } else { |
349 | /* the exception will allow access to certain devices */ | 416 | /* |
417 | * new exception in the child will add more devices | ||
418 | * that can be acessed, so it can't match any of | ||
419 | * parent's exceptions, even slightly | ||
420 | */ | ||
421 | match = match_exception_partial(&dev_cgroup->exceptions, | ||
422 | refex->type, | ||
423 | refex->major, | ||
424 | refex->minor, | ||
425 | refex->access); | ||
426 | |||
350 | if (match) | 427 | if (match) |
351 | /* | ||
352 | * a new exception allowing access shouldn't | ||
353 | * match an parent's exception | ||
354 | */ | ||
355 | return false; | 428 | return false; |
356 | return true; | 429 | return true; |
357 | } | 430 | } |
358 | } else { | 431 | } else { |
359 | /* only behavior == DEVCG_DEFAULT_DENY allowed here */ | 432 | /* |
433 | * Only behavior == DEVCG_DEFAULT_DENY allowed here, therefore | ||
434 | * the new exception will add access to more devices and must | ||
435 | * be contained completely in an parent's exception to be | ||
436 | * allowed | ||
437 | */ | ||
438 | match = match_exception(&dev_cgroup->exceptions, refex->type, | ||
439 | refex->major, refex->minor, | ||
440 | refex->access); | ||
441 | |||
360 | if (match) | 442 | if (match) |
361 | /* parent has an exception that matches the proposed */ | 443 | /* parent has an exception that matches the proposed */ |
362 | return true; | 444 | return true; |
@@ -378,7 +460,7 @@ static int parent_has_perm(struct dev_cgroup *childcg, | |||
378 | 460 | ||
379 | if (!parent) | 461 | if (!parent) |
380 | return 1; | 462 | return 1; |
381 | return may_access(parent, ex, childcg->behavior); | 463 | return verify_new_ex(parent, ex, childcg->behavior); |
382 | } | 464 | } |
383 | 465 | ||
384 | /** | 466 | /** |
@@ -704,18 +786,18 @@ static int __devcgroup_check_permission(short type, u32 major, u32 minor, | |||
704 | short access) | 786 | short access) |
705 | { | 787 | { |
706 | struct dev_cgroup *dev_cgroup; | 788 | struct dev_cgroup *dev_cgroup; |
707 | struct dev_exception_item ex; | 789 | bool rc; |
708 | int rc; | ||
709 | |||
710 | memset(&ex, 0, sizeof(ex)); | ||
711 | ex.type = type; | ||
712 | ex.major = major; | ||
713 | ex.minor = minor; | ||
714 | ex.access = access; | ||
715 | 790 | ||
716 | rcu_read_lock(); | 791 | rcu_read_lock(); |
717 | dev_cgroup = task_devcgroup(current); | 792 | dev_cgroup = task_devcgroup(current); |
718 | rc = may_access(dev_cgroup, &ex, dev_cgroup->behavior); | 793 | if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) |
794 | /* Can't match any of the exceptions, even partially */ | ||
795 | rc = !match_exception_partial(&dev_cgroup->exceptions, | ||
796 | type, major, minor, access); | ||
797 | else | ||
798 | /* Need to match completely one exception to be allowed */ | ||
799 | rc = match_exception(&dev_cgroup->exceptions, type, major, | ||
800 | minor, access); | ||
719 | rcu_read_unlock(); | 801 | rcu_read_unlock(); |
720 | 802 | ||
721 | if (!rc) | 803 | if (!rc) |