1
2
3
4
5
6
7#include <linux/device_cgroup.h>
8#include <linux/cgroup.h>
9#include <linux/ctype.h>
10#include <linux/list.h>
11#include <linux/uaccess.h>
12#include <linux/seq_file.h>
13#include <linux/slab.h>
14#include <linux/rcupdate.h>
15#include <linux/mutex.h>
16
17#define ACC_MKNOD 1
18#define ACC_READ 2
19#define ACC_WRITE 4
20#define ACC_MASK (ACC_MKNOD | ACC_READ | ACC_WRITE)
21
22#define DEV_BLOCK 1
23#define DEV_CHAR 2
24#define DEV_ALL 4
25
26static DEFINE_MUTEX(devcgroup_mutex);
27
28enum devcg_behavior {
29 DEVCG_DEFAULT_NONE,
30 DEVCG_DEFAULT_ALLOW,
31 DEVCG_DEFAULT_DENY,
32};
33
34
35
36
37
38
39
40struct dev_exception_item {
41 u32 major, minor;
42 short type;
43 short access;
44 struct list_head list;
45 struct rcu_head rcu;
46};
47
48struct dev_cgroup {
49 struct cgroup_subsys_state css;
50 struct list_head exceptions;
51 enum devcg_behavior behavior;
52};
53
54static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s)
55{
56 return s ? container_of(s, struct dev_cgroup, css) : NULL;
57}
58
59static inline struct dev_cgroup *task_devcgroup(struct task_struct *task)
60{
61 return css_to_devcgroup(task_css(task, devices_cgrp_id));
62}
63
64
65
66
67static int dev_exceptions_copy(struct list_head *dest, struct list_head *orig)
68{
69 struct dev_exception_item *ex, *tmp, *new;
70
71 lockdep_assert_held(&devcgroup_mutex);
72
73 list_for_each_entry(ex, orig, list) {
74 new = kmemdup(ex, sizeof(*ex), GFP_KERNEL);
75 if (!new)
76 goto free_and_exit;
77 list_add_tail(&new->list, dest);
78 }
79
80 return 0;
81
82free_and_exit:
83 list_for_each_entry_safe(ex, tmp, dest, list) {
84 list_del(&ex->list);
85 kfree(ex);
86 }
87 return -ENOMEM;
88}
89
90
91
92
93static int dev_exception_add(struct dev_cgroup *dev_cgroup,
94 struct dev_exception_item *ex)
95{
96 struct dev_exception_item *excopy, *walk;
97
98 lockdep_assert_held(&devcgroup_mutex);
99
100 excopy = kmemdup(ex, sizeof(*ex), GFP_KERNEL);
101 if (!excopy)
102 return -ENOMEM;
103
104 list_for_each_entry(walk, &dev_cgroup->exceptions, list) {
105 if (walk->type != ex->type)
106 continue;
107 if (walk->major != ex->major)
108 continue;
109 if (walk->minor != ex->minor)
110 continue;
111
112 walk->access |= ex->access;
113 kfree(excopy);
114 excopy = NULL;
115 }
116
117 if (excopy != NULL)
118 list_add_tail_rcu(&excopy->list, &dev_cgroup->exceptions);
119 return 0;
120}
121
122
123
124
125static void dev_exception_rm(struct dev_cgroup *dev_cgroup,
126 struct dev_exception_item *ex)
127{
128 struct dev_exception_item *walk, *tmp;
129
130 lockdep_assert_held(&devcgroup_mutex);
131
132 list_for_each_entry_safe(walk, tmp, &dev_cgroup->exceptions, list) {
133 if (walk->type != ex->type)
134 continue;
135 if (walk->major != ex->major)
136 continue;
137 if (walk->minor != ex->minor)
138 continue;
139
140 walk->access &= ~ex->access;
141 if (!walk->access) {
142 list_del_rcu(&walk->list);
143 kfree_rcu(walk, rcu);
144 }
145 }
146}
147
148static void __dev_exception_clean(struct dev_cgroup *dev_cgroup)
149{
150 struct dev_exception_item *ex, *tmp;
151
152 list_for_each_entry_safe(ex, tmp, &dev_cgroup->exceptions, list) {
153 list_del_rcu(&ex->list);
154 kfree_rcu(ex, rcu);
155 }
156}
157
158
159
160
161
162
163
164static void dev_exception_clean(struct dev_cgroup *dev_cgroup)
165{
166 lockdep_assert_held(&devcgroup_mutex);
167
168 __dev_exception_clean(dev_cgroup);
169}
170
171static inline bool is_devcg_online(const struct dev_cgroup *devcg)
172{
173 return (devcg->behavior != DEVCG_DEFAULT_NONE);
174}
175
176
177
178
179
180
181
182static int devcgroup_online(struct cgroup_subsys_state *css)
183{
184 struct dev_cgroup *dev_cgroup = css_to_devcgroup(css);
185 struct dev_cgroup *parent_dev_cgroup = css_to_devcgroup(css->parent);
186 int ret = 0;
187
188 mutex_lock(&devcgroup_mutex);
189
190 if (parent_dev_cgroup == NULL)
191 dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW;
192 else {
193 ret = dev_exceptions_copy(&dev_cgroup->exceptions,
194 &parent_dev_cgroup->exceptions);
195 if (!ret)
196 dev_cgroup->behavior = parent_dev_cgroup->behavior;
197 }
198 mutex_unlock(&devcgroup_mutex);
199
200 return ret;
201}
202
203static void devcgroup_offline(struct cgroup_subsys_state *css)
204{
205 struct dev_cgroup *dev_cgroup = css_to_devcgroup(css);
206
207 mutex_lock(&devcgroup_mutex);
208 dev_cgroup->behavior = DEVCG_DEFAULT_NONE;
209 mutex_unlock(&devcgroup_mutex);
210}
211
212
213
214
215static struct cgroup_subsys_state *
216devcgroup_css_alloc(struct cgroup_subsys_state *parent_css)
217{
218 struct dev_cgroup *dev_cgroup;
219
220 dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL);
221 if (!dev_cgroup)
222 return ERR_PTR(-ENOMEM);
223 INIT_LIST_HEAD(&dev_cgroup->exceptions);
224 dev_cgroup->behavior = DEVCG_DEFAULT_NONE;
225
226 return &dev_cgroup->css;
227}
228
229static void devcgroup_css_free(struct cgroup_subsys_state *css)
230{
231 struct dev_cgroup *dev_cgroup = css_to_devcgroup(css);
232
233 __dev_exception_clean(dev_cgroup);
234 kfree(dev_cgroup);
235}
236
237#define DEVCG_ALLOW 1
238#define DEVCG_DENY 2
239#define DEVCG_LIST 3
240
241#define MAJMINLEN 13
242#define ACCLEN 4
243
244static void set_access(char *acc, short access)
245{
246 int idx = 0;
247 memset(acc, 0, ACCLEN);
248 if (access & ACC_READ)
249 acc[idx++] = 'r';
250 if (access & ACC_WRITE)
251 acc[idx++] = 'w';
252 if (access & ACC_MKNOD)
253 acc[idx++] = 'm';
254}
255
256static char type_to_char(short type)
257{
258 if (type == DEV_ALL)
259 return 'a';
260 if (type == DEV_CHAR)
261 return 'c';
262 if (type == DEV_BLOCK)
263 return 'b';
264 return 'X';
265}
266
267static void set_majmin(char *str, unsigned m)
268{
269 if (m == ~0)
270 strcpy(str, "*");
271 else
272 sprintf(str, "%u", m);
273}
274
275static int devcgroup_seq_show(struct seq_file *m, void *v)
276{
277 struct dev_cgroup *devcgroup = css_to_devcgroup(seq_css(m));
278 struct dev_exception_item *ex;
279 char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN];
280
281 rcu_read_lock();
282
283
284
285
286
287
288 if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) {
289 set_access(acc, ACC_MASK);
290 set_majmin(maj, ~0);
291 set_majmin(min, ~0);
292 seq_printf(m, "%c %s:%s %s\n", type_to_char(DEV_ALL),
293 maj, min, acc);
294 } else {
295 list_for_each_entry_rcu(ex, &devcgroup->exceptions, list) {
296 set_access(acc, ex->access);
297 set_majmin(maj, ex->major);
298 set_majmin(min, ex->minor);
299 seq_printf(m, "%c %s:%s %s\n", type_to_char(ex->type),
300 maj, min, acc);
301 }
302 }
303 rcu_read_unlock();
304
305 return 0;
306}
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321static bool match_exception(struct list_head *exceptions, short type,
322 u32 major, u32 minor, short access)
323{
324 struct dev_exception_item *ex;
325
326 list_for_each_entry_rcu(ex, exceptions, list) {
327 if ((type & DEV_BLOCK) && !(ex->type & DEV_BLOCK))
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
336 if (access & (~ex->access))
337 continue;
338 return true;
339 }
340 return false;
341}
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358static 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))
365 continue;
366 if ((type & DEV_CHAR) && !(ex->type & DEV_CHAR))
367 continue;
368
369
370
371
372 if (ex->major != ~0 && major != ~0 && ex->major != major)
373 continue;
374 if (ex->minor != ~0 && minor != ~0 && ex->minor != minor)
375 continue;
376
377
378
379
380
381 if (!(access & ex->access))
382 continue;
383 return true;
384 }
385 return false;
386}
387
388
389
390
391
392
393
394
395
396
397static 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_WARN(!rcu_read_lock_held() &&
404 !lockdep_is_held(&devcgroup_mutex),
405 "device_cgroup:verify_new_ex called without proper synchronization");
406
407 if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) {
408 if (behavior == DEVCG_DEFAULT_ALLOW) {
409
410
411
412
413 return true;
414 } else {
415
416
417
418
419
420 match = match_exception_partial(&dev_cgroup->exceptions,
421 refex->type,
422 refex->major,
423 refex->minor,
424 refex->access);
425
426 if (match)
427 return false;
428 return true;
429 }
430 } else {
431
432
433
434
435
436
437 match = match_exception(&dev_cgroup->exceptions, refex->type,
438 refex->major, refex->minor,
439 refex->access);
440
441 if (match)
442
443 return true;
444 else
445 return false;
446 }
447 return false;
448}
449
450
451
452
453
454
455static int parent_has_perm(struct dev_cgroup *childcg,
456 struct dev_exception_item *ex)
457{
458 struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent);
459
460 if (!parent)
461 return 1;
462 return verify_new_ex(parent, ex, childcg->behavior);
463}
464
465
466
467
468
469
470
471
472
473
474
475
476static bool parent_allows_removal(struct dev_cgroup *childcg,
477 struct dev_exception_item *ex)
478{
479 struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent);
480
481 if (!parent)
482 return true;
483
484
485 if (childcg->behavior == DEVCG_DEFAULT_DENY)
486 return true;
487
488
489
490
491
492 return !match_exception_partial(&parent->exceptions, ex->type,
493 ex->major, ex->minor, ex->access);
494}
495
496
497
498
499
500
501
502static inline int may_allow_all(struct dev_cgroup *parent)
503{
504 if (!parent)
505 return 1;
506 return parent->behavior == DEVCG_DEFAULT_ALLOW;
507}
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522static void revalidate_active_exceptions(struct dev_cgroup *devcg)
523{
524 struct dev_exception_item *ex;
525 struct list_head *this, *tmp;
526
527 list_for_each_safe(this, tmp, &devcg->exceptions) {
528 ex = container_of(this, struct dev_exception_item, list);
529 if (!parent_has_perm(devcg, ex))
530 dev_exception_rm(devcg, ex);
531 }
532}
533
534
535
536
537
538
539
540
541static int propagate_exception(struct dev_cgroup *devcg_root,
542 struct dev_exception_item *ex)
543{
544 struct cgroup_subsys_state *pos;
545 int rc = 0;
546
547 rcu_read_lock();
548
549 css_for_each_descendant_pre(pos, &devcg_root->css) {
550 struct dev_cgroup *devcg = css_to_devcgroup(pos);
551
552
553
554
555
556
557
558 if (pos == &devcg_root->css || !is_devcg_online(devcg))
559 continue;
560
561 rcu_read_unlock();
562
563
564
565
566
567 if (devcg_root->behavior == DEVCG_DEFAULT_ALLOW &&
568 devcg->behavior == DEVCG_DEFAULT_ALLOW) {
569 rc = dev_exception_add(devcg, ex);
570 if (rc)
571 break;
572 } else {
573
574
575
576
577
578
579 dev_exception_rm(devcg, ex);
580 }
581 revalidate_active_exceptions(devcg);
582
583 rcu_read_lock();
584 }
585
586 rcu_read_unlock();
587 return rc;
588}
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603static int devcgroup_update_access(struct dev_cgroup *devcgroup,
604 int filetype, char *buffer)
605{
606 const char *b;
607 char temp[12];
608 int count, rc = 0;
609 struct dev_exception_item ex;
610 struct dev_cgroup *parent = css_to_devcgroup(devcgroup->css.parent);
611
612 if (!capable(CAP_SYS_ADMIN))
613 return -EPERM;
614
615 memset(&ex, 0, sizeof(ex));
616 b = buffer;
617
618 switch (*b) {
619 case 'a':
620 switch (filetype) {
621 case DEVCG_ALLOW:
622 if (css_has_online_children(&devcgroup->css))
623 return -EINVAL;
624
625 if (!may_allow_all(parent))
626 return -EPERM;
627 dev_exception_clean(devcgroup);
628 devcgroup->behavior = DEVCG_DEFAULT_ALLOW;
629 if (!parent)
630 break;
631
632 rc = dev_exceptions_copy(&devcgroup->exceptions,
633 &parent->exceptions);
634 if (rc)
635 return rc;
636 break;
637 case DEVCG_DENY:
638 if (css_has_online_children(&devcgroup->css))
639 return -EINVAL;
640
641 dev_exception_clean(devcgroup);
642 devcgroup->behavior = DEVCG_DEFAULT_DENY;
643 break;
644 default:
645 return -EINVAL;
646 }
647 return 0;
648 case 'b':
649 ex.type = DEV_BLOCK;
650 break;
651 case 'c':
652 ex.type = DEV_CHAR;
653 break;
654 default:
655 return -EINVAL;
656 }
657 b++;
658 if (!isspace(*b))
659 return -EINVAL;
660 b++;
661 if (*b == '*') {
662 ex.major = ~0;
663 b++;
664 } else if (isdigit(*b)) {
665 memset(temp, 0, sizeof(temp));
666 for (count = 0; count < sizeof(temp) - 1; count++) {
667 temp[count] = *b;
668 b++;
669 if (!isdigit(*b))
670 break;
671 }
672 rc = kstrtou32(temp, 10, &ex.major);
673 if (rc)
674 return -EINVAL;
675 } else {
676 return -EINVAL;
677 }
678 if (*b != ':')
679 return -EINVAL;
680 b++;
681
682
683 if (*b == '*') {
684 ex.minor = ~0;
685 b++;
686 } else if (isdigit(*b)) {
687 memset(temp, 0, sizeof(temp));
688 for (count = 0; count < sizeof(temp) - 1; count++) {
689 temp[count] = *b;
690 b++;
691 if (!isdigit(*b))
692 break;
693 }
694 rc = kstrtou32(temp, 10, &ex.minor);
695 if (rc)
696 return -EINVAL;
697 } else {
698 return -EINVAL;
699 }
700 if (!isspace(*b))
701 return -EINVAL;
702 for (b++, count = 0; count < 3; count++, b++) {
703 switch (*b) {
704 case 'r':
705 ex.access |= ACC_READ;
706 break;
707 case 'w':
708 ex.access |= ACC_WRITE;
709 break;
710 case 'm':
711 ex.access |= ACC_MKNOD;
712 break;
713 case '\n':
714 case '\0':
715 count = 3;
716 break;
717 default:
718 return -EINVAL;
719 }
720 }
721
722 switch (filetype) {
723 case DEVCG_ALLOW:
724
725
726
727
728
729 if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) {
730
731 if (!parent_allows_removal(devcgroup, &ex))
732 return -EPERM;
733 dev_exception_rm(devcgroup, &ex);
734 break;
735 }
736
737 if (!parent_has_perm(devcgroup, &ex))
738 return -EPERM;
739 rc = dev_exception_add(devcgroup, &ex);
740 break;
741 case DEVCG_DENY:
742
743
744
745
746
747 if (devcgroup->behavior == DEVCG_DEFAULT_DENY)
748 dev_exception_rm(devcgroup, &ex);
749 else
750 rc = dev_exception_add(devcgroup, &ex);
751
752 if (rc)
753 break;
754
755 rc = propagate_exception(devcgroup, &ex);
756 break;
757 default:
758 rc = -EINVAL;
759 }
760 return rc;
761}
762
763static ssize_t devcgroup_access_write(struct kernfs_open_file *of,
764 char *buf, size_t nbytes, loff_t off)
765{
766 int retval;
767
768 mutex_lock(&devcgroup_mutex);
769 retval = devcgroup_update_access(css_to_devcgroup(of_css(of)),
770 of_cft(of)->private, strstrip(buf));
771 mutex_unlock(&devcgroup_mutex);
772 return retval ?: nbytes;
773}
774
775static struct cftype dev_cgroup_files[] = {
776 {
777 .name = "allow",
778 .write = devcgroup_access_write,
779 .private = DEVCG_ALLOW,
780 },
781 {
782 .name = "deny",
783 .write = devcgroup_access_write,
784 .private = DEVCG_DENY,
785 },
786 {
787 .name = "list",
788 .seq_show = devcgroup_seq_show,
789 .private = DEVCG_LIST,
790 },
791 { }
792};
793
794struct cgroup_subsys devices_cgrp_subsys = {
795 .css_alloc = devcgroup_css_alloc,
796 .css_free = devcgroup_css_free,
797 .css_online = devcgroup_online,
798 .css_offline = devcgroup_offline,
799 .legacy_cftypes = dev_cgroup_files,
800};
801
802
803
804
805
806
807
808
809
810
811
812static int __devcgroup_check_permission(short type, u32 major, u32 minor,
813 short access)
814{
815 struct dev_cgroup *dev_cgroup;
816 bool rc;
817
818 rcu_read_lock();
819 dev_cgroup = task_devcgroup(current);
820 if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW)
821
822 rc = !match_exception_partial(&dev_cgroup->exceptions,
823 type, major, minor, access);
824 else
825
826 rc = match_exception(&dev_cgroup->exceptions, type, major,
827 minor, access);
828 rcu_read_unlock();
829
830 if (!rc)
831 return -EPERM;
832
833 return 0;
834}
835
836int __devcgroup_inode_permission(struct inode *inode, int mask)
837{
838 short type, access = 0;
839
840 if (S_ISBLK(inode->i_mode))
841 type = DEV_BLOCK;
842 if (S_ISCHR(inode->i_mode))
843 type = DEV_CHAR;
844 if (mask & MAY_WRITE)
845 access |= ACC_WRITE;
846 if (mask & MAY_READ)
847 access |= ACC_READ;
848
849 return __devcgroup_check_permission(type, imajor(inode), iminor(inode),
850 access);
851}
852
853int devcgroup_inode_mknod(int mode, dev_t dev)
854{
855 short type;
856
857 if (!S_ISBLK(mode) && !S_ISCHR(mode))
858 return 0;
859
860 if (S_ISBLK(mode))
861 type = DEV_BLOCK;
862 else
863 type = DEV_CHAR;
864
865 return __devcgroup_check_permission(type, MAJOR(dev), MINOR(dev),
866 ACC_MKNOD);
867
868}
869