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