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