aboutsummaryrefslogtreecommitdiffstats
path: root/security/device_cgroup.c
diff options
context:
space:
mode:
authorAristeu Rozanski <aris@redhat.com>2014-04-21 12:13:03 -0400
committerTejun Heo <tj@kernel.org>2014-04-21 18:21:42 -0400
commit79d719749d23234e9b725098aa49133f3ef7299d (patch)
tree454b591af0e26506982cf92a9712b67bcf77c96c /security/device_cgroup.c
parente37a06f10994c2ba86f54d8f96734f2483a869b8 (diff)
device_cgroup: rework device access check and exception checking
Whenever a device file is opened and checked against current device cgroup rules, it uses the same function (may_access()) as when a new exception rule is added by writing devices.{allow,deny}. And in both cases, the algorithm is the same, doesn't matter the behavior. First problem is having device access to be considered the same as rule checking. Consider the following structure: A (default behavior: allow, exceptions disallow access) \ B (default behavior: allow, exceptions disallow access) A new exception is added to B by writing devices.deny: c 12:34 rw When checking if that exception is allowed in may_access(): if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) { if (behavior == DEVCG_DEFAULT_ALLOW) { /* the exception will deny access to certain devices */ return true; Which is ok, since B is not getting more privileges than A, it doesn't matter and the rule is accepted Now, consider it's a device file open check and the process belongs to cgroup B. The access will be generated as: behavior: allow exception: c 12:34 rw The very same chunk of code will allow it, even if there's an explicit exception telling to do otherwise. A simple test case: # mkdir new_group # cd new_group # echo $$ >tasks # echo "c 1:3 w" >devices.deny # echo >/dev/null # echo $? 0 This is a serious bug and was introduced on c39a2a3018f8 devcg: prepare may_access() for hierarchy support To solve this problem, the device file open function was split from the new exception check. Second problem is how exceptions are processed by may_access(). The first part of the said function tries to match fully with an existing exception: list_for_each_entry_rcu(ex, &dev_cgroup->exceptions, list) { if ((refex->type & DEV_BLOCK) && !(ex->type & DEV_BLOCK)) continue; if ((refex->type & DEV_CHAR) && !(ex->type & DEV_CHAR)) continue; if (ex->major != ~0 && ex->major != refex->major) continue; if (ex->minor != ~0 && ex->minor != refex->minor) continue; if (refex->access & (~ex->access)) continue; match = true; break; } That means the new exception should be contained into an existing one to be considered a match: New exception Existing match? notes b 12:34 rwm b 12:34 rwm yes b 12:34 r b *:34 rw yes b 12:34 rw b 12:34 w no extra "r" b *:34 rw b 12:34 rw no too broad "*" b *:34 rw b *:34 rwm yes Which is fine in some cases. Consider: A (default behavior: deny, exceptions allow access) \ B (default behavior: deny, exceptions allow access) In this case the full match makes sense, the new exception cannot add more access than the parent allows But this doesn't always work, consider: A (default behavior: allow, exceptions disallow access) \ B (default behavior: deny, exceptions allow access) In this case, a new exception in B shouldn't match any of the exceptions in A, after all you can't allow something that was forbidden by A. But consider this scenario: New exception Existing in A match? outcome b 12:34 rw b 12:34 r no exception is accepted Because the new exception has "w" as extra, it doesn't match, so it'll be added to B's exception list. The same problem can happen during a file access check. Consider a cgroup with allow as default behavior: Access Exception match? b 12:34 rw b 12:34 r no In this case, the access didn't match any of the exceptions in the cgroup, which is required since exceptions will disallow access. To solve this problem, two new functions were created to match an exception either fully or partially. In the example above, a partial check will be performed and it'll produce a match since at least "b 12:34 r" from "b 12:34 rw" access matches. Cc: cgroups@vger.kernel.org Cc: Tejun Heo <tj@kernel.org> Cc: Serge Hallyn <serge.hallyn@canonical.com> Cc: Li Zefan <lizefan@huawei.com> Cc: stable@vger.kernel.org Signed-off-by: Aristeu Rozanski <arozansk@redhat.com> Signed-off-by: Tejun Heo <tj@kernel.org>
Diffstat (limited to 'security/device_cgroup.c')
-rw-r--r--security/device_cgroup.c162
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 */
318static bool may_access(struct dev_cgroup *dev_cgroup, 321static 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 */
359static 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 */
398static 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)