diff options
Diffstat (limited to 'security/device_cgroup.c')
-rw-r--r-- | security/device_cgroup.c | 158 |
1 files changed, 68 insertions, 90 deletions
diff --git a/security/device_cgroup.c b/security/device_cgroup.c index ddd92cec78ed..7bd296cca041 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c | |||
@@ -41,6 +41,7 @@ struct dev_whitelist_item { | |||
41 | short type; | 41 | short type; |
42 | short access; | 42 | short access; |
43 | struct list_head list; | 43 | struct list_head list; |
44 | struct rcu_head rcu; | ||
44 | }; | 45 | }; |
45 | 46 | ||
46 | struct dev_cgroup { | 47 | struct dev_cgroup { |
@@ -59,6 +60,11 @@ static inline struct dev_cgroup *cgroup_to_devcgroup(struct cgroup *cgroup) | |||
59 | return css_to_devcgroup(cgroup_subsys_state(cgroup, devices_subsys_id)); | 60 | return css_to_devcgroup(cgroup_subsys_state(cgroup, devices_subsys_id)); |
60 | } | 61 | } |
61 | 62 | ||
63 | static inline struct dev_cgroup *task_devcgroup(struct task_struct *task) | ||
64 | { | ||
65 | return css_to_devcgroup(task_subsys_state(task, devices_subsys_id)); | ||
66 | } | ||
67 | |||
62 | struct cgroup_subsys devices_subsys; | 68 | struct cgroup_subsys devices_subsys; |
63 | 69 | ||
64 | static int devcgroup_can_attach(struct cgroup_subsys *ss, | 70 | static int devcgroup_can_attach(struct cgroup_subsys *ss, |
@@ -128,11 +134,19 @@ static int dev_whitelist_add(struct dev_cgroup *dev_cgroup, | |||
128 | } | 134 | } |
129 | 135 | ||
130 | if (whcopy != NULL) | 136 | if (whcopy != NULL) |
131 | list_add_tail(&whcopy->list, &dev_cgroup->whitelist); | 137 | list_add_tail_rcu(&whcopy->list, &dev_cgroup->whitelist); |
132 | spin_unlock(&dev_cgroup->lock); | 138 | spin_unlock(&dev_cgroup->lock); |
133 | return 0; | 139 | return 0; |
134 | } | 140 | } |
135 | 141 | ||
142 | static void whitelist_item_free(struct rcu_head *rcu) | ||
143 | { | ||
144 | struct dev_whitelist_item *item; | ||
145 | |||
146 | item = container_of(rcu, struct dev_whitelist_item, rcu); | ||
147 | kfree(item); | ||
148 | } | ||
149 | |||
136 | /* | 150 | /* |
137 | * called under cgroup_lock() | 151 | * called under cgroup_lock() |
138 | * since the list is visible to other tasks, we need the spinlock also | 152 | * since the list is visible to other tasks, we need the spinlock also |
@@ -156,8 +170,8 @@ static void dev_whitelist_rm(struct dev_cgroup *dev_cgroup, | |||
156 | remove: | 170 | remove: |
157 | walk->access &= ~wh->access; | 171 | walk->access &= ~wh->access; |
158 | if (!walk->access) { | 172 | if (!walk->access) { |
159 | list_del(&walk->list); | 173 | list_del_rcu(&walk->list); |
160 | kfree(walk); | 174 | call_rcu(&walk->rcu, whitelist_item_free); |
161 | } | 175 | } |
162 | } | 176 | } |
163 | spin_unlock(&dev_cgroup->lock); | 177 | spin_unlock(&dev_cgroup->lock); |
@@ -188,7 +202,7 @@ static struct cgroup_subsys_state *devcgroup_create(struct cgroup_subsys *ss, | |||
188 | } | 202 | } |
189 | wh->minor = wh->major = ~0; | 203 | wh->minor = wh->major = ~0; |
190 | wh->type = DEV_ALL; | 204 | wh->type = DEV_ALL; |
191 | wh->access = ACC_MKNOD | ACC_READ | ACC_WRITE; | 205 | wh->access = ACC_MASK; |
192 | list_add(&wh->list, &dev_cgroup->whitelist); | 206 | list_add(&wh->list, &dev_cgroup->whitelist); |
193 | } else { | 207 | } else { |
194 | parent_dev_cgroup = cgroup_to_devcgroup(parent_cgroup); | 208 | parent_dev_cgroup = cgroup_to_devcgroup(parent_cgroup); |
@@ -250,11 +264,10 @@ static char type_to_char(short type) | |||
250 | 264 | ||
251 | static void set_majmin(char *str, unsigned m) | 265 | static void set_majmin(char *str, unsigned m) |
252 | { | 266 | { |
253 | memset(str, 0, MAJMINLEN); | ||
254 | if (m == ~0) | 267 | if (m == ~0) |
255 | sprintf(str, "*"); | 268 | strcpy(str, "*"); |
256 | else | 269 | else |
257 | snprintf(str, MAJMINLEN, "%u", m); | 270 | sprintf(str, "%u", m); |
258 | } | 271 | } |
259 | 272 | ||
260 | static int devcgroup_seq_read(struct cgroup *cgroup, struct cftype *cft, | 273 | static int devcgroup_seq_read(struct cgroup *cgroup, struct cftype *cft, |
@@ -264,15 +277,15 @@ static int devcgroup_seq_read(struct cgroup *cgroup, struct cftype *cft, | |||
264 | struct dev_whitelist_item *wh; | 277 | struct dev_whitelist_item *wh; |
265 | char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN]; | 278 | char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN]; |
266 | 279 | ||
267 | spin_lock(&devcgroup->lock); | 280 | rcu_read_lock(); |
268 | list_for_each_entry(wh, &devcgroup->whitelist, list) { | 281 | list_for_each_entry_rcu(wh, &devcgroup->whitelist, list) { |
269 | set_access(acc, wh->access); | 282 | set_access(acc, wh->access); |
270 | set_majmin(maj, wh->major); | 283 | set_majmin(maj, wh->major); |
271 | set_majmin(min, wh->minor); | 284 | set_majmin(min, wh->minor); |
272 | seq_printf(m, "%c %s:%s %s\n", type_to_char(wh->type), | 285 | seq_printf(m, "%c %s:%s %s\n", type_to_char(wh->type), |
273 | maj, min, acc); | 286 | maj, min, acc); |
274 | } | 287 | } |
275 | spin_unlock(&devcgroup->lock); | 288 | rcu_read_unlock(); |
276 | 289 | ||
277 | return 0; | 290 | return 0; |
278 | } | 291 | } |
@@ -312,10 +325,10 @@ static int may_access_whitelist(struct dev_cgroup *c, | |||
312 | * when adding a new allow rule to a device whitelist, the rule | 325 | * when adding a new allow rule to a device whitelist, the rule |
313 | * must be allowed in the parent device | 326 | * must be allowed in the parent device |
314 | */ | 327 | */ |
315 | static int parent_has_perm(struct cgroup *childcg, | 328 | static int parent_has_perm(struct dev_cgroup *childcg, |
316 | struct dev_whitelist_item *wh) | 329 | struct dev_whitelist_item *wh) |
317 | { | 330 | { |
318 | struct cgroup *pcg = childcg->parent; | 331 | struct cgroup *pcg = childcg->css.cgroup->parent; |
319 | struct dev_cgroup *parent; | 332 | struct dev_cgroup *parent; |
320 | int ret; | 333 | int ret; |
321 | 334 | ||
@@ -341,39 +354,19 @@ static int parent_has_perm(struct cgroup *childcg, | |||
341 | * new access is only allowed if you're in the top-level cgroup, or your | 354 | * new access is only allowed if you're in the top-level cgroup, or your |
342 | * parent cgroup has the access you're asking for. | 355 | * parent cgroup has the access you're asking for. |
343 | */ | 356 | */ |
344 | static ssize_t devcgroup_access_write(struct cgroup *cgroup, struct cftype *cft, | 357 | static int devcgroup_update_access(struct dev_cgroup *devcgroup, |
345 | struct file *file, const char __user *userbuf, | 358 | int filetype, const char *buffer) |
346 | size_t nbytes, loff_t *ppos) | ||
347 | { | 359 | { |
348 | struct cgroup *cur_cgroup; | 360 | struct dev_cgroup *cur_devcgroup; |
349 | struct dev_cgroup *devcgroup, *cur_devcgroup; | 361 | const char *b; |
350 | int filetype = cft->private; | 362 | char *endp; |
351 | char *buffer, *b; | ||
352 | int retval = 0, count; | 363 | int retval = 0, count; |
353 | struct dev_whitelist_item wh; | 364 | struct dev_whitelist_item wh; |
354 | 365 | ||
355 | if (!capable(CAP_SYS_ADMIN)) | 366 | if (!capable(CAP_SYS_ADMIN)) |
356 | return -EPERM; | 367 | return -EPERM; |
357 | 368 | ||
358 | devcgroup = cgroup_to_devcgroup(cgroup); | 369 | cur_devcgroup = task_devcgroup(current); |
359 | cur_cgroup = task_cgroup(current, devices_subsys.subsys_id); | ||
360 | cur_devcgroup = cgroup_to_devcgroup(cur_cgroup); | ||
361 | |||
362 | buffer = kmalloc(nbytes+1, GFP_KERNEL); | ||
363 | if (!buffer) | ||
364 | return -ENOMEM; | ||
365 | |||
366 | if (copy_from_user(buffer, userbuf, nbytes)) { | ||
367 | retval = -EFAULT; | ||
368 | goto out1; | ||
369 | } | ||
370 | buffer[nbytes] = 0; /* nul-terminate */ | ||
371 | |||
372 | cgroup_lock(); | ||
373 | if (cgroup_is_removed(cgroup)) { | ||
374 | retval = -ENODEV; | ||
375 | goto out2; | ||
376 | } | ||
377 | 370 | ||
378 | memset(&wh, 0, sizeof(wh)); | 371 | memset(&wh, 0, sizeof(wh)); |
379 | b = buffer; | 372 | b = buffer; |
@@ -392,32 +385,23 @@ static ssize_t devcgroup_access_write(struct cgroup *cgroup, struct cftype *cft, | |||
392 | wh.type = DEV_CHAR; | 385 | wh.type = DEV_CHAR; |
393 | break; | 386 | break; |
394 | default: | 387 | default: |
395 | retval = -EINVAL; | 388 | return -EINVAL; |
396 | goto out2; | ||
397 | } | 389 | } |
398 | b++; | 390 | b++; |
399 | if (!isspace(*b)) { | 391 | if (!isspace(*b)) |
400 | retval = -EINVAL; | 392 | return -EINVAL; |
401 | goto out2; | ||
402 | } | ||
403 | b++; | 393 | b++; |
404 | if (*b == '*') { | 394 | if (*b == '*') { |
405 | wh.major = ~0; | 395 | wh.major = ~0; |
406 | b++; | 396 | b++; |
407 | } else if (isdigit(*b)) { | 397 | } else if (isdigit(*b)) { |
408 | wh.major = 0; | 398 | wh.major = simple_strtoul(b, &endp, 10); |
409 | while (isdigit(*b)) { | 399 | b = endp; |
410 | wh.major = wh.major*10+(*b-'0'); | ||
411 | b++; | ||
412 | } | ||
413 | } else { | 400 | } else { |
414 | retval = -EINVAL; | 401 | return -EINVAL; |
415 | goto out2; | ||
416 | } | ||
417 | if (*b != ':') { | ||
418 | retval = -EINVAL; | ||
419 | goto out2; | ||
420 | } | 402 | } |
403 | if (*b != ':') | ||
404 | return -EINVAL; | ||
421 | b++; | 405 | b++; |
422 | 406 | ||
423 | /* read minor */ | 407 | /* read minor */ |
@@ -425,19 +409,13 @@ static ssize_t devcgroup_access_write(struct cgroup *cgroup, struct cftype *cft, | |||
425 | wh.minor = ~0; | 409 | wh.minor = ~0; |
426 | b++; | 410 | b++; |
427 | } else if (isdigit(*b)) { | 411 | } else if (isdigit(*b)) { |
428 | wh.minor = 0; | 412 | wh.minor = simple_strtoul(b, &endp, 10); |
429 | while (isdigit(*b)) { | 413 | b = endp; |
430 | wh.minor = wh.minor*10+(*b-'0'); | ||
431 | b++; | ||
432 | } | ||
433 | } else { | 414 | } else { |
434 | retval = -EINVAL; | 415 | return -EINVAL; |
435 | goto out2; | ||
436 | } | ||
437 | if (!isspace(*b)) { | ||
438 | retval = -EINVAL; | ||
439 | goto out2; | ||
440 | } | 416 | } |
417 | if (!isspace(*b)) | ||
418 | return -EINVAL; | ||
441 | for (b++, count = 0; count < 3; count++, b++) { | 419 | for (b++, count = 0; count < 3; count++, b++) { |
442 | switch (*b) { | 420 | switch (*b) { |
443 | case 'r': | 421 | case 'r': |
@@ -454,8 +432,7 @@ static ssize_t devcgroup_access_write(struct cgroup *cgroup, struct cftype *cft, | |||
454 | count = 3; | 432 | count = 3; |
455 | break; | 433 | break; |
456 | default: | 434 | default: |
457 | retval = -EINVAL; | 435 | return -EINVAL; |
458 | goto out2; | ||
459 | } | 436 | } |
460 | } | 437 | } |
461 | 438 | ||
@@ -463,38 +440,39 @@ handle: | |||
463 | retval = 0; | 440 | retval = 0; |
464 | switch (filetype) { | 441 | switch (filetype) { |
465 | case DEVCG_ALLOW: | 442 | case DEVCG_ALLOW: |
466 | if (!parent_has_perm(cgroup, &wh)) | 443 | if (!parent_has_perm(devcgroup, &wh)) |
467 | retval = -EPERM; | 444 | return -EPERM; |
468 | else | 445 | return dev_whitelist_add(devcgroup, &wh); |
469 | retval = dev_whitelist_add(devcgroup, &wh); | ||
470 | break; | ||
471 | case DEVCG_DENY: | 446 | case DEVCG_DENY: |
472 | dev_whitelist_rm(devcgroup, &wh); | 447 | dev_whitelist_rm(devcgroup, &wh); |
473 | break; | 448 | break; |
474 | default: | 449 | default: |
475 | retval = -EINVAL; | 450 | return -EINVAL; |
476 | goto out2; | ||
477 | } | 451 | } |
452 | return 0; | ||
453 | } | ||
478 | 454 | ||
479 | if (retval == 0) | 455 | static int devcgroup_access_write(struct cgroup *cgrp, struct cftype *cft, |
480 | retval = nbytes; | 456 | const char *buffer) |
481 | 457 | { | |
482 | out2: | 458 | int retval; |
459 | if (!cgroup_lock_live_group(cgrp)) | ||
460 | return -ENODEV; | ||
461 | retval = devcgroup_update_access(cgroup_to_devcgroup(cgrp), | ||
462 | cft->private, buffer); | ||
483 | cgroup_unlock(); | 463 | cgroup_unlock(); |
484 | out1: | ||
485 | kfree(buffer); | ||
486 | return retval; | 464 | return retval; |
487 | } | 465 | } |
488 | 466 | ||
489 | static struct cftype dev_cgroup_files[] = { | 467 | static struct cftype dev_cgroup_files[] = { |
490 | { | 468 | { |
491 | .name = "allow", | 469 | .name = "allow", |
492 | .write = devcgroup_access_write, | 470 | .write_string = devcgroup_access_write, |
493 | .private = DEVCG_ALLOW, | 471 | .private = DEVCG_ALLOW, |
494 | }, | 472 | }, |
495 | { | 473 | { |
496 | .name = "deny", | 474 | .name = "deny", |
497 | .write = devcgroup_access_write, | 475 | .write_string = devcgroup_access_write, |
498 | .private = DEVCG_DENY, | 476 | .private = DEVCG_DENY, |
499 | }, | 477 | }, |
500 | { | 478 | { |
@@ -535,8 +513,8 @@ int devcgroup_inode_permission(struct inode *inode, int mask) | |||
535 | if (!dev_cgroup) | 513 | if (!dev_cgroup) |
536 | return 0; | 514 | return 0; |
537 | 515 | ||
538 | spin_lock(&dev_cgroup->lock); | 516 | rcu_read_lock(); |
539 | list_for_each_entry(wh, &dev_cgroup->whitelist, list) { | 517 | list_for_each_entry_rcu(wh, &dev_cgroup->whitelist, list) { |
540 | if (wh->type & DEV_ALL) | 518 | if (wh->type & DEV_ALL) |
541 | goto acc_check; | 519 | goto acc_check; |
542 | if ((wh->type & DEV_BLOCK) && !S_ISBLK(inode->i_mode)) | 520 | if ((wh->type & DEV_BLOCK) && !S_ISBLK(inode->i_mode)) |
@@ -552,10 +530,10 @@ acc_check: | |||
552 | continue; | 530 | continue; |
553 | if ((mask & MAY_READ) && !(wh->access & ACC_READ)) | 531 | if ((mask & MAY_READ) && !(wh->access & ACC_READ)) |
554 | continue; | 532 | continue; |
555 | spin_unlock(&dev_cgroup->lock); | 533 | rcu_read_unlock(); |
556 | return 0; | 534 | return 0; |
557 | } | 535 | } |
558 | spin_unlock(&dev_cgroup->lock); | 536 | rcu_read_unlock(); |
559 | 537 | ||
560 | return -EPERM; | 538 | return -EPERM; |
561 | } | 539 | } |
@@ -570,7 +548,7 @@ int devcgroup_inode_mknod(int mode, dev_t dev) | |||
570 | if (!dev_cgroup) | 548 | if (!dev_cgroup) |
571 | return 0; | 549 | return 0; |
572 | 550 | ||
573 | spin_lock(&dev_cgroup->lock); | 551 | rcu_read_lock(); |
574 | list_for_each_entry(wh, &dev_cgroup->whitelist, list) { | 552 | list_for_each_entry(wh, &dev_cgroup->whitelist, list) { |
575 | if (wh->type & DEV_ALL) | 553 | if (wh->type & DEV_ALL) |
576 | goto acc_check; | 554 | goto acc_check; |
@@ -585,9 +563,9 @@ int devcgroup_inode_mknod(int mode, dev_t dev) | |||
585 | acc_check: | 563 | acc_check: |
586 | if (!(wh->access & ACC_MKNOD)) | 564 | if (!(wh->access & ACC_MKNOD)) |
587 | continue; | 565 | continue; |
588 | spin_unlock(&dev_cgroup->lock); | 566 | rcu_read_unlock(); |
589 | return 0; | 567 | return 0; |
590 | } | 568 | } |
591 | spin_unlock(&dev_cgroup->lock); | 569 | rcu_read_unlock(); |
592 | return -EPERM; | 570 | return -EPERM; |
593 | } | 571 | } |