1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * device_cgroup.c - device cgroup subsystem |
4 | * |
5 | * Copyright 2007 IBM Corp |
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 | |
21 | static DEFINE_MUTEX(devcgroup_mutex); |
22 | |
23 | enum devcg_behavior { |
24 | DEVCG_DEFAULT_NONE, |
25 | DEVCG_DEFAULT_ALLOW, |
26 | DEVCG_DEFAULT_DENY, |
27 | }; |
28 | |
29 | /* |
30 | * exception list locking rules: |
31 | * hold devcgroup_mutex for update/read. |
32 | * hold rcu_read_lock() for read. |
33 | */ |
34 | |
35 | struct 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 | |
43 | struct dev_cgroup { |
44 | struct cgroup_subsys_state css; |
45 | struct list_head exceptions; |
46 | enum devcg_behavior behavior; |
47 | }; |
48 | |
49 | static 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 | |
54 | static inline struct dev_cgroup *task_devcgroup(struct task_struct *task) |
55 | { |
56 | return css_to_devcgroup(s: task_css(task, subsys_id: devices_cgrp_id)); |
57 | } |
58 | |
59 | /* |
60 | * called under devcgroup_mutex |
61 | */ |
62 | static 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(p: ex, size: sizeof(*ex), GFP_KERNEL); |
70 | if (!new) |
71 | goto free_and_exit; |
72 | list_add_tail(new: &new->list, head: dest); |
73 | } |
74 | |
75 | return 0; |
76 | |
77 | free_and_exit: |
78 | list_for_each_entry_safe(ex, tmp, dest, list) { |
79 | list_del(entry: &ex->list); |
80 | kfree(objp: ex); |
81 | } |
82 | return -ENOMEM; |
83 | } |
84 | |
85 | static void dev_exceptions_move(struct list_head *dest, struct list_head *orig) |
86 | { |
87 | struct dev_exception_item *ex, *tmp; |
88 | |
89 | lockdep_assert_held(&devcgroup_mutex); |
90 | |
91 | list_for_each_entry_safe(ex, tmp, orig, list) { |
92 | list_move_tail(list: &ex->list, head: dest); |
93 | } |
94 | } |
95 | |
96 | /* |
97 | * called under devcgroup_mutex |
98 | */ |
99 | static int dev_exception_add(struct dev_cgroup *dev_cgroup, |
100 | struct dev_exception_item *ex) |
101 | { |
102 | struct dev_exception_item *excopy, *walk; |
103 | |
104 | lockdep_assert_held(&devcgroup_mutex); |
105 | |
106 | excopy = kmemdup(p: ex, size: sizeof(*ex), GFP_KERNEL); |
107 | if (!excopy) |
108 | return -ENOMEM; |
109 | |
110 | list_for_each_entry(walk, &dev_cgroup->exceptions, list) { |
111 | if (walk->type != ex->type) |
112 | continue; |
113 | if (walk->major != ex->major) |
114 | continue; |
115 | if (walk->minor != ex->minor) |
116 | continue; |
117 | |
118 | walk->access |= ex->access; |
119 | kfree(objp: excopy); |
120 | excopy = NULL; |
121 | } |
122 | |
123 | if (excopy != NULL) |
124 | list_add_tail_rcu(new: &excopy->list, head: &dev_cgroup->exceptions); |
125 | return 0; |
126 | } |
127 | |
128 | /* |
129 | * called under devcgroup_mutex |
130 | */ |
131 | static void dev_exception_rm(struct dev_cgroup *dev_cgroup, |
132 | struct dev_exception_item *ex) |
133 | { |
134 | struct dev_exception_item *walk, *tmp; |
135 | |
136 | lockdep_assert_held(&devcgroup_mutex); |
137 | |
138 | list_for_each_entry_safe(walk, tmp, &dev_cgroup->exceptions, list) { |
139 | if (walk->type != ex->type) |
140 | continue; |
141 | if (walk->major != ex->major) |
142 | continue; |
143 | if (walk->minor != ex->minor) |
144 | continue; |
145 | |
146 | walk->access &= ~ex->access; |
147 | if (!walk->access) { |
148 | list_del_rcu(entry: &walk->list); |
149 | kfree_rcu(walk, rcu); |
150 | } |
151 | } |
152 | } |
153 | |
154 | static void __dev_exception_clean(struct dev_cgroup *dev_cgroup) |
155 | { |
156 | struct dev_exception_item *ex, *tmp; |
157 | |
158 | list_for_each_entry_safe(ex, tmp, &dev_cgroup->exceptions, list) { |
159 | list_del_rcu(entry: &ex->list); |
160 | kfree_rcu(ex, rcu); |
161 | } |
162 | } |
163 | |
164 | /** |
165 | * dev_exception_clean - frees all entries of the exception list |
166 | * @dev_cgroup: dev_cgroup with the exception list to be cleaned |
167 | * |
168 | * called under devcgroup_mutex |
169 | */ |
170 | static void dev_exception_clean(struct dev_cgroup *dev_cgroup) |
171 | { |
172 | lockdep_assert_held(&devcgroup_mutex); |
173 | |
174 | __dev_exception_clean(dev_cgroup); |
175 | } |
176 | |
177 | static inline bool is_devcg_online(const struct dev_cgroup *devcg) |
178 | { |
179 | return (devcg->behavior != DEVCG_DEFAULT_NONE); |
180 | } |
181 | |
182 | /** |
183 | * devcgroup_online - initializes devcgroup's behavior and exceptions based on |
184 | * parent's |
185 | * @css: css getting online |
186 | * returns 0 in case of success, error code otherwise |
187 | */ |
188 | static int devcgroup_online(struct cgroup_subsys_state *css) |
189 | { |
190 | struct dev_cgroup *dev_cgroup = css_to_devcgroup(s: css); |
191 | struct dev_cgroup *parent_dev_cgroup = css_to_devcgroup(s: css->parent); |
192 | int ret = 0; |
193 | |
194 | mutex_lock(&devcgroup_mutex); |
195 | |
196 | if (parent_dev_cgroup == NULL) |
197 | dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW; |
198 | else { |
199 | ret = dev_exceptions_copy(dest: &dev_cgroup->exceptions, |
200 | orig: &parent_dev_cgroup->exceptions); |
201 | if (!ret) |
202 | dev_cgroup->behavior = parent_dev_cgroup->behavior; |
203 | } |
204 | mutex_unlock(lock: &devcgroup_mutex); |
205 | |
206 | return ret; |
207 | } |
208 | |
209 | static void devcgroup_offline(struct cgroup_subsys_state *css) |
210 | { |
211 | struct dev_cgroup *dev_cgroup = css_to_devcgroup(s: css); |
212 | |
213 | mutex_lock(&devcgroup_mutex); |
214 | dev_cgroup->behavior = DEVCG_DEFAULT_NONE; |
215 | mutex_unlock(lock: &devcgroup_mutex); |
216 | } |
217 | |
218 | /* |
219 | * called from kernel/cgroup/cgroup.c with cgroup_lock() held. |
220 | */ |
221 | static struct cgroup_subsys_state * |
222 | devcgroup_css_alloc(struct cgroup_subsys_state *parent_css) |
223 | { |
224 | struct dev_cgroup *dev_cgroup; |
225 | |
226 | dev_cgroup = kzalloc(size: sizeof(*dev_cgroup), GFP_KERNEL); |
227 | if (!dev_cgroup) |
228 | return ERR_PTR(error: -ENOMEM); |
229 | INIT_LIST_HEAD(list: &dev_cgroup->exceptions); |
230 | dev_cgroup->behavior = DEVCG_DEFAULT_NONE; |
231 | |
232 | return &dev_cgroup->css; |
233 | } |
234 | |
235 | static void devcgroup_css_free(struct cgroup_subsys_state *css) |
236 | { |
237 | struct dev_cgroup *dev_cgroup = css_to_devcgroup(s: css); |
238 | |
239 | __dev_exception_clean(dev_cgroup); |
240 | kfree(objp: dev_cgroup); |
241 | } |
242 | |
243 | #define DEVCG_ALLOW 1 |
244 | #define DEVCG_DENY 2 |
245 | #define DEVCG_LIST 3 |
246 | |
247 | #define MAJMINLEN 13 |
248 | #define ACCLEN 4 |
249 | |
250 | static void set_access(char *acc, short access) |
251 | { |
252 | int idx = 0; |
253 | memset(acc, 0, ACCLEN); |
254 | if (access & DEVCG_ACC_READ) |
255 | acc[idx++] = 'r'; |
256 | if (access & DEVCG_ACC_WRITE) |
257 | acc[idx++] = 'w'; |
258 | if (access & DEVCG_ACC_MKNOD) |
259 | acc[idx++] = 'm'; |
260 | } |
261 | |
262 | static char type_to_char(short type) |
263 | { |
264 | if (type == DEVCG_DEV_ALL) |
265 | return 'a'; |
266 | if (type == DEVCG_DEV_CHAR) |
267 | return 'c'; |
268 | if (type == DEVCG_DEV_BLOCK) |
269 | return 'b'; |
270 | return 'X'; |
271 | } |
272 | |
273 | static void set_majmin(char *str, unsigned m) |
274 | { |
275 | if (m == ~0) |
276 | strcpy(p: str, q: "*" ); |
277 | else |
278 | sprintf(buf: str, fmt: "%u" , m); |
279 | } |
280 | |
281 | static int devcgroup_seq_show(struct seq_file *m, void *v) |
282 | { |
283 | struct dev_cgroup *devcgroup = css_to_devcgroup(s: seq_css(seq: m)); |
284 | struct dev_exception_item *ex; |
285 | char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN]; |
286 | |
287 | rcu_read_lock(); |
288 | /* |
289 | * To preserve the compatibility: |
290 | * - Only show the "all devices" when the default policy is to allow |
291 | * - List the exceptions in case the default policy is to deny |
292 | * This way, the file remains as a "whitelist of devices" |
293 | */ |
294 | if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { |
295 | set_access(acc, DEVCG_ACC_MASK); |
296 | set_majmin(str: maj, m: ~0); |
297 | set_majmin(str: min, m: ~0); |
298 | seq_printf(m, fmt: "%c %s:%s %s\n" , type_to_char(DEVCG_DEV_ALL), |
299 | maj, min, acc); |
300 | } else { |
301 | list_for_each_entry_rcu(ex, &devcgroup->exceptions, list) { |
302 | set_access(acc, access: ex->access); |
303 | set_majmin(str: maj, m: ex->major); |
304 | set_majmin(str: min, m: ex->minor); |
305 | seq_printf(m, fmt: "%c %s:%s %s\n" , type_to_char(type: ex->type), |
306 | maj, min, acc); |
307 | } |
308 | } |
309 | rcu_read_unlock(); |
310 | |
311 | return 0; |
312 | } |
313 | |
314 | /** |
315 | * match_exception - iterates the exception list trying to find a complete match |
316 | * @exceptions: list of exceptions |
317 | * @type: device type (DEVCG_DEV_BLOCK or DEVCG_DEV_CHAR) |
318 | * @major: device file major number, ~0 to match all |
319 | * @minor: device file minor number, ~0 to match all |
320 | * @access: permission mask (DEVCG_ACC_READ, DEVCG_ACC_WRITE, DEVCG_ACC_MKNOD) |
321 | * |
322 | * It is considered a complete match if an exception is found that will |
323 | * contain the entire range of provided parameters. |
324 | * |
325 | * Return: true in case it matches an exception completely |
326 | */ |
327 | static bool match_exception(struct list_head *exceptions, short type, |
328 | u32 major, u32 minor, short access) |
329 | { |
330 | struct dev_exception_item *ex; |
331 | |
332 | list_for_each_entry_rcu(ex, exceptions, list) { |
333 | if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK)) |
334 | continue; |
335 | if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR)) |
336 | continue; |
337 | if (ex->major != ~0 && ex->major != major) |
338 | continue; |
339 | if (ex->minor != ~0 && ex->minor != minor) |
340 | continue; |
341 | /* provided access cannot have more than the exception rule */ |
342 | if (access & (~ex->access)) |
343 | continue; |
344 | return true; |
345 | } |
346 | return false; |
347 | } |
348 | |
349 | /** |
350 | * match_exception_partial - iterates the exception list trying to find a partial match |
351 | * @exceptions: list of exceptions |
352 | * @type: device type (DEVCG_DEV_BLOCK or DEVCG_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 (DEVCG_ACC_READ, DEVCG_ACC_WRITE, DEVCG_ACC_MKNOD) |
356 | * |
357 | * It is considered a partial match if an exception's range is found to |
358 | * contain *any* of the devices specified by provided parameters. This is |
359 | * used to make sure no extra access is being granted that is forbidden by |
360 | * any of the exception list. |
361 | * |
362 | * Return: true in case the provided range mat matches an exception completely |
363 | */ |
364 | static bool match_exception_partial(struct list_head *exceptions, short type, |
365 | u32 major, u32 minor, short access) |
366 | { |
367 | struct dev_exception_item *ex; |
368 | |
369 | list_for_each_entry_rcu(ex, exceptions, list, |
370 | lockdep_is_held(&devcgroup_mutex)) { |
371 | if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK)) |
372 | continue; |
373 | if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR)) |
374 | continue; |
375 | /* |
376 | * We must be sure that both the exception and the provided |
377 | * range aren't masking all devices |
378 | */ |
379 | if (ex->major != ~0 && major != ~0 && ex->major != major) |
380 | continue; |
381 | if (ex->minor != ~0 && minor != ~0 && ex->minor != minor) |
382 | continue; |
383 | /* |
384 | * In order to make sure the provided range isn't matching |
385 | * an exception, all its access bits shouldn't match the |
386 | * exception's access bits |
387 | */ |
388 | if (!(access & ex->access)) |
389 | continue; |
390 | return true; |
391 | } |
392 | return false; |
393 | } |
394 | |
395 | /** |
396 | * verify_new_ex - verifies if a new exception is allowed by parent cgroup's permissions |
397 | * @dev_cgroup: dev cgroup to be tested against |
398 | * @refex: new exception |
399 | * @behavior: behavior of the exception's dev_cgroup |
400 | * |
401 | * This is used to make sure a child cgroup won't have more privileges |
402 | * than its parent |
403 | */ |
404 | static bool verify_new_ex(struct dev_cgroup *dev_cgroup, |
405 | struct dev_exception_item *refex, |
406 | enum devcg_behavior behavior) |
407 | { |
408 | bool match = false; |
409 | |
410 | RCU_LOCKDEP_WARN(!rcu_read_lock_held() && |
411 | !lockdep_is_held(&devcgroup_mutex), |
412 | "device_cgroup:verify_new_ex called without proper synchronization" ); |
413 | |
414 | if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) { |
415 | if (behavior == DEVCG_DEFAULT_ALLOW) { |
416 | /* |
417 | * new exception in the child doesn't matter, only |
418 | * adding extra restrictions |
419 | */ |
420 | return true; |
421 | } else { |
422 | /* |
423 | * new exception in the child will add more devices |
424 | * that can be accessed, so it can't match any of |
425 | * parent's exceptions, even slightly |
426 | */ |
427 | match = match_exception_partial(exceptions: &dev_cgroup->exceptions, |
428 | type: refex->type, |
429 | major: refex->major, |
430 | minor: refex->minor, |
431 | access: refex->access); |
432 | |
433 | if (match) |
434 | return false; |
435 | return true; |
436 | } |
437 | } else { |
438 | /* |
439 | * Only behavior == DEVCG_DEFAULT_DENY allowed here, therefore |
440 | * the new exception will add access to more devices and must |
441 | * be contained completely in an parent's exception to be |
442 | * allowed |
443 | */ |
444 | match = match_exception(exceptions: &dev_cgroup->exceptions, type: refex->type, |
445 | major: refex->major, minor: refex->minor, |
446 | access: refex->access); |
447 | |
448 | if (match) |
449 | /* parent has an exception that matches the proposed */ |
450 | return true; |
451 | else |
452 | return false; |
453 | } |
454 | return false; |
455 | } |
456 | |
457 | /* |
458 | * parent_has_perm: |
459 | * when adding a new allow rule to a device exception list, the rule |
460 | * must be allowed in the parent device |
461 | */ |
462 | static int parent_has_perm(struct dev_cgroup *childcg, |
463 | struct dev_exception_item *ex) |
464 | { |
465 | struct dev_cgroup *parent = css_to_devcgroup(s: childcg->css.parent); |
466 | |
467 | if (!parent) |
468 | return 1; |
469 | return verify_new_ex(dev_cgroup: parent, refex: ex, behavior: childcg->behavior); |
470 | } |
471 | |
472 | /** |
473 | * parent_allows_removal - verify if it's ok to remove an exception |
474 | * @childcg: child cgroup from where the exception will be removed |
475 | * @ex: exception being removed |
476 | * |
477 | * When removing an exception in cgroups with default ALLOW policy, it must |
478 | * be checked if removing it will give the child cgroup more access than the |
479 | * parent. |
480 | * |
481 | * Return: true if it's ok to remove exception, false otherwise |
482 | */ |
483 | static bool parent_allows_removal(struct dev_cgroup *childcg, |
484 | struct dev_exception_item *ex) |
485 | { |
486 | struct dev_cgroup *parent = css_to_devcgroup(s: childcg->css.parent); |
487 | |
488 | if (!parent) |
489 | return true; |
490 | |
491 | /* It's always allowed to remove access to devices */ |
492 | if (childcg->behavior == DEVCG_DEFAULT_DENY) |
493 | return true; |
494 | |
495 | /* |
496 | * Make sure you're not removing part or a whole exception existing in |
497 | * the parent cgroup |
498 | */ |
499 | return !match_exception_partial(exceptions: &parent->exceptions, type: ex->type, |
500 | major: ex->major, minor: ex->minor, access: ex->access); |
501 | } |
502 | |
503 | /** |
504 | * may_allow_all - checks if it's possible to change the behavior to |
505 | * allow based on parent's rules. |
506 | * @parent: device cgroup's parent |
507 | * returns: != 0 in case it's allowed, 0 otherwise |
508 | */ |
509 | static inline int may_allow_all(struct dev_cgroup *parent) |
510 | { |
511 | if (!parent) |
512 | return 1; |
513 | return parent->behavior == DEVCG_DEFAULT_ALLOW; |
514 | } |
515 | |
516 | /** |
517 | * revalidate_active_exceptions - walks through the active exception list and |
518 | * revalidates the exceptions based on parent's |
519 | * behavior and exceptions. The exceptions that |
520 | * are no longer valid will be removed. |
521 | * Called with devcgroup_mutex held. |
522 | * @devcg: cgroup which exceptions will be checked |
523 | * |
524 | * This is one of the three key functions for hierarchy implementation. |
525 | * This function is responsible for re-evaluating all the cgroup's active |
526 | * exceptions due to a parent's exception change. |
527 | * Refer to Documentation/admin-guide/cgroup-v1/devices.rst for more details. |
528 | */ |
529 | static void revalidate_active_exceptions(struct dev_cgroup *devcg) |
530 | { |
531 | struct dev_exception_item *ex; |
532 | struct list_head *this, *tmp; |
533 | |
534 | list_for_each_safe(this, tmp, &devcg->exceptions) { |
535 | ex = container_of(this, struct dev_exception_item, list); |
536 | if (!parent_has_perm(childcg: devcg, ex)) |
537 | dev_exception_rm(dev_cgroup: devcg, ex); |
538 | } |
539 | } |
540 | |
541 | /** |
542 | * propagate_exception - propagates a new exception to the children |
543 | * @devcg_root: device cgroup that added a new exception |
544 | * @ex: new exception to be propagated |
545 | * |
546 | * returns: 0 in case of success, != 0 in case of error |
547 | */ |
548 | static int propagate_exception(struct dev_cgroup *devcg_root, |
549 | struct dev_exception_item *ex) |
550 | { |
551 | struct cgroup_subsys_state *pos; |
552 | int rc = 0; |
553 | |
554 | rcu_read_lock(); |
555 | |
556 | css_for_each_descendant_pre(pos, &devcg_root->css) { |
557 | struct dev_cgroup *devcg = css_to_devcgroup(s: pos); |
558 | |
559 | /* |
560 | * Because devcgroup_mutex is held, no devcg will become |
561 | * online or offline during the tree walk (see on/offline |
562 | * methods), and online ones are safe to access outside RCU |
563 | * read lock without bumping refcnt. |
564 | */ |
565 | if (pos == &devcg_root->css || !is_devcg_online(devcg)) |
566 | continue; |
567 | |
568 | rcu_read_unlock(); |
569 | |
570 | /* |
571 | * in case both root's behavior and devcg is allow, a new |
572 | * restriction means adding to the exception list |
573 | */ |
574 | if (devcg_root->behavior == DEVCG_DEFAULT_ALLOW && |
575 | devcg->behavior == DEVCG_DEFAULT_ALLOW) { |
576 | rc = dev_exception_add(dev_cgroup: devcg, ex); |
577 | if (rc) |
578 | return rc; |
579 | } else { |
580 | /* |
581 | * in the other possible cases: |
582 | * root's behavior: allow, devcg's: deny |
583 | * root's behavior: deny, devcg's: deny |
584 | * the exception will be removed |
585 | */ |
586 | dev_exception_rm(dev_cgroup: devcg, ex); |
587 | } |
588 | revalidate_active_exceptions(devcg); |
589 | |
590 | rcu_read_lock(); |
591 | } |
592 | |
593 | rcu_read_unlock(); |
594 | return rc; |
595 | } |
596 | |
597 | /* |
598 | * Modify the exception list using allow/deny rules. |
599 | * CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD |
600 | * so we can give a container CAP_MKNOD to let it create devices but not |
601 | * modify the exception list. |
602 | * It seems likely we'll want to add a CAP_CONTAINER capability to allow |
603 | * us to also grant CAP_SYS_ADMIN to containers without giving away the |
604 | * device exception list controls, but for now we'll stick with CAP_SYS_ADMIN |
605 | * |
606 | * Taking rules away is always allowed (given CAP_SYS_ADMIN). Granting |
607 | * new access is only allowed if you're in the top-level cgroup, or your |
608 | * parent cgroup has the access you're asking for. |
609 | */ |
610 | static int devcgroup_update_access(struct dev_cgroup *devcgroup, |
611 | int filetype, char *buffer) |
612 | { |
613 | const char *b; |
614 | char temp[12]; /* 11 + 1 characters needed for a u32 */ |
615 | int count, rc = 0; |
616 | struct dev_exception_item ex; |
617 | struct dev_cgroup *parent = css_to_devcgroup(s: devcgroup->css.parent); |
618 | struct dev_cgroup tmp_devcgrp; |
619 | |
620 | if (!capable(CAP_SYS_ADMIN)) |
621 | return -EPERM; |
622 | |
623 | memset(&ex, 0, sizeof(ex)); |
624 | memset(&tmp_devcgrp, 0, sizeof(tmp_devcgrp)); |
625 | b = buffer; |
626 | |
627 | switch (*b) { |
628 | case 'a': |
629 | switch (filetype) { |
630 | case DEVCG_ALLOW: |
631 | if (css_has_online_children(css: &devcgroup->css)) |
632 | return -EINVAL; |
633 | |
634 | if (!may_allow_all(parent)) |
635 | return -EPERM; |
636 | if (!parent) { |
637 | devcgroup->behavior = DEVCG_DEFAULT_ALLOW; |
638 | dev_exception_clean(dev_cgroup: devcgroup); |
639 | break; |
640 | } |
641 | |
642 | INIT_LIST_HEAD(list: &tmp_devcgrp.exceptions); |
643 | rc = dev_exceptions_copy(dest: &tmp_devcgrp.exceptions, |
644 | orig: &devcgroup->exceptions); |
645 | if (rc) |
646 | return rc; |
647 | dev_exception_clean(dev_cgroup: devcgroup); |
648 | rc = dev_exceptions_copy(dest: &devcgroup->exceptions, |
649 | orig: &parent->exceptions); |
650 | if (rc) { |
651 | dev_exceptions_move(dest: &devcgroup->exceptions, |
652 | orig: &tmp_devcgrp.exceptions); |
653 | return rc; |
654 | } |
655 | devcgroup->behavior = DEVCG_DEFAULT_ALLOW; |
656 | dev_exception_clean(dev_cgroup: &tmp_devcgrp); |
657 | break; |
658 | case DEVCG_DENY: |
659 | if (css_has_online_children(css: &devcgroup->css)) |
660 | return -EINVAL; |
661 | |
662 | dev_exception_clean(dev_cgroup: devcgroup); |
663 | devcgroup->behavior = DEVCG_DEFAULT_DENY; |
664 | break; |
665 | default: |
666 | return -EINVAL; |
667 | } |
668 | return 0; |
669 | case 'b': |
670 | ex.type = DEVCG_DEV_BLOCK; |
671 | break; |
672 | case 'c': |
673 | ex.type = DEVCG_DEV_CHAR; |
674 | break; |
675 | default: |
676 | return -EINVAL; |
677 | } |
678 | b++; |
679 | if (!isspace(*b)) |
680 | return -EINVAL; |
681 | b++; |
682 | if (*b == '*') { |
683 | ex.major = ~0; |
684 | b++; |
685 | } else if (isdigit(c: *b)) { |
686 | memset(temp, 0, sizeof(temp)); |
687 | for (count = 0; count < sizeof(temp) - 1; count++) { |
688 | temp[count] = *b; |
689 | b++; |
690 | if (!isdigit(c: *b)) |
691 | break; |
692 | } |
693 | rc = kstrtou32(s: temp, base: 10, res: &ex.major); |
694 | if (rc) |
695 | return -EINVAL; |
696 | } else { |
697 | return -EINVAL; |
698 | } |
699 | if (*b != ':') |
700 | return -EINVAL; |
701 | b++; |
702 | |
703 | /* read minor */ |
704 | if (*b == '*') { |
705 | ex.minor = ~0; |
706 | b++; |
707 | } else if (isdigit(c: *b)) { |
708 | memset(temp, 0, sizeof(temp)); |
709 | for (count = 0; count < sizeof(temp) - 1; count++) { |
710 | temp[count] = *b; |
711 | b++; |
712 | if (!isdigit(c: *b)) |
713 | break; |
714 | } |
715 | rc = kstrtou32(s: temp, base: 10, res: &ex.minor); |
716 | if (rc) |
717 | return -EINVAL; |
718 | } else { |
719 | return -EINVAL; |
720 | } |
721 | if (!isspace(*b)) |
722 | return -EINVAL; |
723 | for (b++, count = 0; count < 3; count++, b++) { |
724 | switch (*b) { |
725 | case 'r': |
726 | ex.access |= DEVCG_ACC_READ; |
727 | break; |
728 | case 'w': |
729 | ex.access |= DEVCG_ACC_WRITE; |
730 | break; |
731 | case 'm': |
732 | ex.access |= DEVCG_ACC_MKNOD; |
733 | break; |
734 | case '\n': |
735 | case '\0': |
736 | count = 3; |
737 | break; |
738 | default: |
739 | return -EINVAL; |
740 | } |
741 | } |
742 | |
743 | switch (filetype) { |
744 | case DEVCG_ALLOW: |
745 | /* |
746 | * If the default policy is to allow by default, try to remove |
747 | * an matching exception instead. And be silent about it: we |
748 | * don't want to break compatibility |
749 | */ |
750 | if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { |
751 | /* Check if the parent allows removing it first */ |
752 | if (!parent_allows_removal(childcg: devcgroup, ex: &ex)) |
753 | return -EPERM; |
754 | dev_exception_rm(dev_cgroup: devcgroup, ex: &ex); |
755 | break; |
756 | } |
757 | |
758 | if (!parent_has_perm(childcg: devcgroup, ex: &ex)) |
759 | return -EPERM; |
760 | rc = dev_exception_add(dev_cgroup: devcgroup, ex: &ex); |
761 | break; |
762 | case DEVCG_DENY: |
763 | /* |
764 | * If the default policy is to deny by default, try to remove |
765 | * an matching exception instead. And be silent about it: we |
766 | * don't want to break compatibility |
767 | */ |
768 | if (devcgroup->behavior == DEVCG_DEFAULT_DENY) |
769 | dev_exception_rm(dev_cgroup: devcgroup, ex: &ex); |
770 | else |
771 | rc = dev_exception_add(dev_cgroup: devcgroup, ex: &ex); |
772 | |
773 | if (rc) |
774 | break; |
775 | /* we only propagate new restrictions */ |
776 | rc = propagate_exception(devcg_root: devcgroup, ex: &ex); |
777 | break; |
778 | default: |
779 | rc = -EINVAL; |
780 | } |
781 | return rc; |
782 | } |
783 | |
784 | static ssize_t devcgroup_access_write(struct kernfs_open_file *of, |
785 | char *buf, size_t nbytes, loff_t off) |
786 | { |
787 | int retval; |
788 | |
789 | mutex_lock(&devcgroup_mutex); |
790 | retval = devcgroup_update_access(devcgroup: css_to_devcgroup(s: of_css(of)), |
791 | filetype: of_cft(of)->private, buffer: strstrip(str: buf)); |
792 | mutex_unlock(lock: &devcgroup_mutex); |
793 | return retval ?: nbytes; |
794 | } |
795 | |
796 | static struct cftype dev_cgroup_files[] = { |
797 | { |
798 | .name = "allow" , |
799 | .write = devcgroup_access_write, |
800 | .private = DEVCG_ALLOW, |
801 | }, |
802 | { |
803 | .name = "deny" , |
804 | .write = devcgroup_access_write, |
805 | .private = DEVCG_DENY, |
806 | }, |
807 | { |
808 | .name = "list" , |
809 | .seq_show = devcgroup_seq_show, |
810 | .private = DEVCG_LIST, |
811 | }, |
812 | { } /* terminate */ |
813 | }; |
814 | |
815 | struct cgroup_subsys devices_cgrp_subsys = { |
816 | .css_alloc = devcgroup_css_alloc, |
817 | .css_free = devcgroup_css_free, |
818 | .css_online = devcgroup_online, |
819 | .css_offline = devcgroup_offline, |
820 | .legacy_cftypes = dev_cgroup_files, |
821 | }; |
822 | |
823 | /** |
824 | * devcgroup_legacy_check_permission - checks if an inode operation is permitted |
825 | * @type: device type |
826 | * @major: device major number |
827 | * @minor: device minor number |
828 | * @access: combination of DEVCG_ACC_WRITE, DEVCG_ACC_READ and DEVCG_ACC_MKNOD |
829 | * |
830 | * returns 0 on success, -EPERM case the operation is not permitted |
831 | */ |
832 | static int devcgroup_legacy_check_permission(short type, u32 major, u32 minor, |
833 | short access) |
834 | { |
835 | struct dev_cgroup *dev_cgroup; |
836 | bool rc; |
837 | |
838 | rcu_read_lock(); |
839 | dev_cgroup = task_devcgroup(current); |
840 | if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) |
841 | /* Can't match any of the exceptions, even partially */ |
842 | rc = !match_exception_partial(exceptions: &dev_cgroup->exceptions, |
843 | type, major, minor, access); |
844 | else |
845 | /* Need to match completely one exception to be allowed */ |
846 | rc = match_exception(exceptions: &dev_cgroup->exceptions, type, major, |
847 | minor, access); |
848 | rcu_read_unlock(); |
849 | |
850 | if (!rc) |
851 | return -EPERM; |
852 | |
853 | return 0; |
854 | } |
855 | |
856 | #endif /* CONFIG_CGROUP_DEVICE */ |
857 | |
858 | #if defined(CONFIG_CGROUP_DEVICE) || defined(CONFIG_CGROUP_BPF) |
859 | |
860 | int devcgroup_check_permission(short type, u32 major, u32 minor, short access) |
861 | { |
862 | int rc = BPF_CGROUP_RUN_PROG_DEVICE_CGROUP(type, major, minor, access); |
863 | |
864 | if (rc) |
865 | return rc; |
866 | |
867 | #ifdef CONFIG_CGROUP_DEVICE |
868 | return devcgroup_legacy_check_permission(type, major, minor, access); |
869 | |
870 | #else /* CONFIG_CGROUP_DEVICE */ |
871 | return 0; |
872 | |
873 | #endif /* CONFIG_CGROUP_DEVICE */ |
874 | } |
875 | EXPORT_SYMBOL(devcgroup_check_permission); |
876 | #endif /* defined(CONFIG_CGROUP_DEVICE) || defined(CONFIG_CGROUP_BPF) */ |
877 | |