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