Blame view
security/device_cgroup.c
21.1 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 377 378 379 380 |
*/ static bool verify_new_ex(struct dev_cgroup *dev_cgroup, struct dev_exception_item *refex, enum devcg_behavior behavior) { bool match = false; rcu_lockdep_assert(rcu_read_lock_held() || lockdep_is_held(&devcgroup_mutex), "device_cgroup:verify_new_ex called without proper synchronization"); |
ad676077a device_cgroup: co... |
381 |
|
c39a2a301 devcg: prepare ma... |
382 383 |
if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) { if (behavior == DEVCG_DEFAULT_ALLOW) { |
79d719749 device_cgroup: re... |
384 385 386 387 |
/* * new exception in the child doesn't matter, only * adding extra restrictions */ |
c39a2a301 devcg: prepare ma... |
388 389 |
return true; } else { |
79d719749 device_cgroup: re... |
390 391 392 393 394 395 396 397 398 399 |
/* * 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... |
400 |
if (match) |
c39a2a301 devcg: prepare ma... |
401 |
return false; |
26898fdff devcg: expand may... |
402 |
return true; |
c39a2a301 devcg: prepare ma... |
403 |
} |
26898fdff devcg: expand may... |
404 |
} else { |
79d719749 device_cgroup: re... |
405 406 407 408 409 410 411 412 413 |
/* * 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... |
414 415 |
if (match) /* parent has an exception that matches the proposed */ |
26898fdff devcg: expand may... |
416 |
return true; |
c39a2a301 devcg: prepare ma... |
417 418 |
else return false; |
26898fdff devcg: expand may... |
419 420 |
} return false; |
08ce5f16e cgroups: implemen... |
421 422 423 424 |
} /* * parent_has_perm: |
db9aeca97 device_cgroup: re... |
425 |
* when adding a new allow rule to a device exception list, the rule |
08ce5f16e cgroups: implemen... |
426 427 |
* must be allowed in the parent device */ |
f92523e3a cgroup files: con... |
428 |
static int parent_has_perm(struct dev_cgroup *childcg, |
db9aeca97 device_cgroup: re... |
429 |
struct dev_exception_item *ex) |
08ce5f16e cgroups: implemen... |
430 |
{ |
5c9d535b8 cgroup: remove cs... |
431 |
struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent); |
08ce5f16e cgroups: implemen... |
432 |
|
638769869 cgroup: add css_p... |
433 |
if (!parent) |
08ce5f16e cgroups: implemen... |
434 |
return 1; |
79d719749 device_cgroup: re... |
435 |
return verify_new_ex(parent, ex, childcg->behavior); |
08ce5f16e cgroups: implemen... |
436 |
} |
4cef7299b device_cgroup: ad... |
437 |
/** |
d2c2b11cf device_cgroup: ch... |
438 439 440 441 442 443 444 445 446 447 448 449 450 |
* 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... |
451 |
struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent); |
d2c2b11cf device_cgroup: ch... |
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 |
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... |
469 470 471 472 473 474 475 |
* 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... |
476 477 |
if (!parent) return 1; |
4cef7299b device_cgroup: ad... |
478 479 |
return parent->behavior == DEVCG_DEFAULT_ALLOW; } |
bd2953ebb devcg: propagate ... |
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 505 |
/** * 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 ... |
506 507 508 509 510 511 512 513 514 |
* 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... |
515 |
struct cgroup_subsys_state *pos; |
bd2953ebb devcg: propagate ... |
516 |
int rc = 0; |
bd2953ebb devcg: propagate ... |
517 |
|
d591fb566 device_cgroup: si... |
518 |
rcu_read_lock(); |
bd2953ebb devcg: propagate ... |
519 |
|
492eb21b9 cgroup: make hier... |
520 521 |
css_for_each_descendant_pre(pos, &devcg_root->css) { struct dev_cgroup *devcg = css_to_devcgroup(pos); |
d591fb566 device_cgroup: si... |
522 523 524 525 526 527 528 |
/* * 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_... |
529 |
if (pos == &devcg_root->css || !is_devcg_online(devcg)) |
d591fb566 device_cgroup: si... |
530 531 532 |
continue; rcu_read_unlock(); |
bd2953ebb devcg: propagate ... |
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 |
/* * 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... |
553 |
rcu_read_lock(); |
bd2953ebb devcg: propagate ... |
554 |
} |
d591fb566 device_cgroup: si... |
555 556 |
rcu_read_unlock(); |
bd2953ebb devcg: propagate ... |
557 558 |
return rc; } |
08ce5f16e cgroups: implemen... |
559 |
/* |
db9aeca97 device_cgroup: re... |
560 |
* Modify the exception list using allow/deny rules. |
08ce5f16e cgroups: implemen... |
561 562 |
* 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... |
563 |
* modify the exception list. |
08ce5f16e cgroups: implemen... |
564 565 |
* 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... |
566 |
* device exception list controls, but for now we'll stick with CAP_SYS_ADMIN |
08ce5f16e cgroups: implemen... |
567 568 569 570 571 |
* * 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... |
572 |
static int devcgroup_update_access(struct dev_cgroup *devcgroup, |
4d3bb511b cgroup: drop cons... |
573 |
int filetype, char *buffer) |
08ce5f16e cgroups: implemen... |
574 |
{ |
f92523e3a cgroup files: con... |
575 |
const char *b; |
26fd8405d device_cgroup: st... |
576 |
char temp[12]; /* 11 + 1 characters needed for a u32 */ |
c39a2a301 devcg: prepare ma... |
577 |
int count, rc = 0; |
db9aeca97 device_cgroup: re... |
578 |
struct dev_exception_item ex; |
5c9d535b8 cgroup: remove cs... |
579 |
struct dev_cgroup *parent = css_to_devcgroup(devcgroup->css.parent); |
08ce5f16e cgroups: implemen... |
580 581 582 |
if (!capable(CAP_SYS_ADMIN)) return -EPERM; |
db9aeca97 device_cgroup: re... |
583 |
memset(&ex, 0, sizeof(ex)); |
08ce5f16e cgroups: implemen... |
584 585 586 587 |
b = buffer; switch (*b) { case 'a': |
ad676077a device_cgroup: co... |
588 589 |
switch (filetype) { case DEVCG_ALLOW: |
7a3bb24f7 device_cgroup: us... |
590 |
if (css_has_online_children(&devcgroup->css)) |
bd2953ebb devcg: propagate ... |
591 |
return -EINVAL; |
4cef7299b device_cgroup: ad... |
592 |
if (!may_allow_all(parent)) |
ad676077a device_cgroup: co... |
593 |
return -EPERM; |
db9aeca97 device_cgroup: re... |
594 |
dev_exception_clean(devcgroup); |
64e104771 device_cgroup: fi... |
595 596 597 |
devcgroup->behavior = DEVCG_DEFAULT_ALLOW; if (!parent) break; |
4cef7299b device_cgroup: ad... |
598 599 600 601 |
rc = dev_exceptions_copy(&devcgroup->exceptions, &parent->exceptions); if (rc) return rc; |
ad676077a device_cgroup: co... |
602 603 |
break; case DEVCG_DENY: |
7a3bb24f7 device_cgroup: us... |
604 |
if (css_has_online_children(&devcgroup->css)) |
bd2953ebb devcg: propagate ... |
605 |
return -EINVAL; |
db9aeca97 device_cgroup: re... |
606 |
dev_exception_clean(devcgroup); |
5b7aa7d5b device_cgroup: re... |
607 |
devcgroup->behavior = DEVCG_DEFAULT_DENY; |
ad676077a device_cgroup: co... |
608 609 610 611 612 |
break; default: return -EINVAL; } return 0; |
08ce5f16e cgroups: implemen... |
613 |
case 'b': |
db9aeca97 device_cgroup: re... |
614 |
ex.type = DEV_BLOCK; |
08ce5f16e cgroups: implemen... |
615 616 |
break; case 'c': |
db9aeca97 device_cgroup: re... |
617 |
ex.type = DEV_CHAR; |
08ce5f16e cgroups: implemen... |
618 619 |
break; default: |
f92523e3a cgroup files: con... |
620 |
return -EINVAL; |
08ce5f16e cgroups: implemen... |
621 622 |
} b++; |
f92523e3a cgroup files: con... |
623 624 |
if (!isspace(*b)) return -EINVAL; |
08ce5f16e cgroups: implemen... |
625 626 |
b++; if (*b == '*') { |
db9aeca97 device_cgroup: re... |
627 |
ex.major = ~0; |
08ce5f16e cgroups: implemen... |
628 629 |
b++; } else if (isdigit(*b)) { |
26fd8405d device_cgroup: st... |
630 631 632 633 634 635 636 637 638 639 |
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... |
640 |
} else { |
f92523e3a cgroup files: con... |
641 |
return -EINVAL; |
08ce5f16e cgroups: implemen... |
642 |
} |
f92523e3a cgroup files: con... |
643 644 |
if (*b != ':') return -EINVAL; |
08ce5f16e cgroups: implemen... |
645 646 647 648 |
b++; /* read minor */ if (*b == '*') { |
db9aeca97 device_cgroup: re... |
649 |
ex.minor = ~0; |
08ce5f16e cgroups: implemen... |
650 651 |
b++; } else if (isdigit(*b)) { |
26fd8405d device_cgroup: st... |
652 653 654 655 656 657 658 659 660 661 |
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... |
662 |
} else { |
f92523e3a cgroup files: con... |
663 |
return -EINVAL; |
08ce5f16e cgroups: implemen... |
664 |
} |
f92523e3a cgroup files: con... |
665 666 |
if (!isspace(*b)) return -EINVAL; |
08ce5f16e cgroups: implemen... |
667 668 669 |
for (b++, count = 0; count < 3; count++, b++) { switch (*b) { case 'r': |
db9aeca97 device_cgroup: re... |
670 |
ex.access |= ACC_READ; |
08ce5f16e cgroups: implemen... |
671 672 |
break; case 'w': |
db9aeca97 device_cgroup: re... |
673 |
ex.access |= ACC_WRITE; |
08ce5f16e cgroups: implemen... |
674 675 |
break; case 'm': |
db9aeca97 device_cgroup: re... |
676 |
ex.access |= ACC_MKNOD; |
08ce5f16e cgroups: implemen... |
677 678 679 680 681 682 683 |
break; case ' ': case '\0': count = 3; break; default: |
f92523e3a cgroup files: con... |
684 |
return -EINVAL; |
08ce5f16e cgroups: implemen... |
685 686 |
} } |
08ce5f16e cgroups: implemen... |
687 688 |
switch (filetype) { case DEVCG_ALLOW: |
ad676077a device_cgroup: co... |
689 690 691 692 693 |
/* * 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... |
694 |
if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { |
d2c2b11cf device_cgroup: ch... |
695 696 697 |
/* Check if the parent allows removing it first */ if (!parent_allows_removal(devcgroup, &ex)) return -EPERM; |
db9aeca97 device_cgroup: re... |
698 |
dev_exception_rm(devcgroup, &ex); |
d2c2b11cf device_cgroup: ch... |
699 |
break; |
ad676077a device_cgroup: co... |
700 |
} |
d2c2b11cf device_cgroup: ch... |
701 702 703 |
if (!parent_has_perm(devcgroup, &ex)) return -EPERM; |
bd2953ebb devcg: propagate ... |
704 705 |
rc = dev_exception_add(devcgroup, &ex); break; |
08ce5f16e cgroups: implemen... |
706 |
case DEVCG_DENY: |
ad676077a device_cgroup: co... |
707 708 709 710 711 |
/* * 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 ... |
712 |
if (devcgroup->behavior == DEVCG_DEFAULT_DENY) |
db9aeca97 device_cgroup: re... |
713 |
dev_exception_rm(devcgroup, &ex); |
bd2953ebb devcg: propagate ... |
714 715 716 717 718 719 720 721 |
else rc = dev_exception_add(devcgroup, &ex); if (rc) break; /* we only propagate new restrictions */ rc = propagate_exception(devcgroup, &ex); break; |
08ce5f16e cgroups: implemen... |
722 |
default: |
bd2953ebb devcg: propagate ... |
723 |
rc = -EINVAL; |
08ce5f16e cgroups: implemen... |
724 |
} |
bd2953ebb devcg: propagate ... |
725 |
return rc; |
f92523e3a cgroup files: con... |
726 |
} |
08ce5f16e cgroups: implemen... |
727 |
|
451af504d cgroup: replace c... |
728 729 |
static ssize_t devcgroup_access_write(struct kernfs_open_file *of, char *buf, size_t nbytes, loff_t off) |
f92523e3a cgroup files: con... |
730 731 |
{ int retval; |
b4046f00e devcgroup: avoid ... |
732 733 |
mutex_lock(&devcgroup_mutex); |
451af504d cgroup: replace c... |
734 735 |
retval = devcgroup_update_access(css_to_devcgroup(of_css(of)), of_cft(of)->private, strstrip(buf)); |
b4046f00e devcgroup: avoid ... |
736 |
mutex_unlock(&devcgroup_mutex); |
451af504d cgroup: replace c... |
737 |
return retval ?: nbytes; |
08ce5f16e cgroups: implemen... |
738 739 740 741 742 |
} static struct cftype dev_cgroup_files[] = { { .name = "allow", |
451af504d cgroup: replace c... |
743 |
.write = devcgroup_access_write, |
08ce5f16e cgroups: implemen... |
744 745 746 747 |
.private = DEVCG_ALLOW, }, { .name = "deny", |
451af504d cgroup: replace c... |
748 |
.write = devcgroup_access_write, |
08ce5f16e cgroups: implemen... |
749 750 |
.private = DEVCG_DENY, }, |
29486df32 cgroups: introduc... |
751 752 |
{ .name = "list", |
2da8ca822 cgroup: replace c... |
753 |
.seq_show = devcgroup_seq_show, |
29486df32 cgroups: introduc... |
754 755 |
.private = DEVCG_LIST, }, |
4baf6e332 cgroup: convert a... |
756 |
{ } /* terminate */ |
08ce5f16e cgroups: implemen... |
757 |
}; |
073219e99 cgroup: clean up ... |
758 |
struct cgroup_subsys devices_cgrp_subsys = { |
92fb97487 cgroup: rename ->... |
759 760 |
.css_alloc = devcgroup_css_alloc, .css_free = devcgroup_css_free, |
1909554c9 devcg: use css_on... |
761 762 |
.css_online = devcgroup_online, .css_offline = devcgroup_offline, |
5577964e6 cgroup: rename cg... |
763 |
.legacy_cftypes = dev_cgroup_files, |
08ce5f16e cgroups: implemen... |
764 |
}; |
ad676077a device_cgroup: co... |
765 766 767 768 769 770 771 772 773 774 |
/** * __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... |
775 |
static int __devcgroup_check_permission(short type, u32 major, u32 minor, |
ad676077a device_cgroup: co... |
776 |
short access) |
08ce5f16e cgroups: implemen... |
777 |
{ |
8c9506d16 cgroup: fix inval... |
778 |
struct dev_cgroup *dev_cgroup; |
79d719749 device_cgroup: re... |
779 |
bool rc; |
36fd71d29 devcgroup: fix ra... |
780 |
|
ad676077a device_cgroup: co... |
781 |
rcu_read_lock(); |
8c9506d16 cgroup: fix inval... |
782 |
dev_cgroup = task_devcgroup(current); |
79d719749 device_cgroup: re... |
783 784 785 786 787 788 789 790 |
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... |
791 |
rcu_read_unlock(); |
cd5008196 devcgroup: skip s... |
792 |
|
ad676077a device_cgroup: co... |
793 794 |
if (!rc) return -EPERM; |
36fd71d29 devcgroup: fix ra... |
795 |
|
ad676077a device_cgroup: co... |
796 797 |
return 0; } |
08ce5f16e cgroups: implemen... |
798 |
|
ad676077a device_cgroup: co... |
799 800 |
int __devcgroup_inode_permission(struct inode *inode, int mask) { |
ad676077a device_cgroup: co... |
801 802 803 804 805 806 807 808 809 810 |
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... |
811 812 |
return __devcgroup_check_permission(type, imajor(inode), iminor(inode), access); |
08ce5f16e cgroups: implemen... |
813 814 815 816 |
} int devcgroup_inode_mknod(int mode, dev_t dev) { |
ad676077a device_cgroup: co... |
817 |
short type; |
08ce5f16e cgroups: implemen... |
818 |
|
0b82ac37b devices cgroup: a... |
819 820 |
if (!S_ISBLK(mode) && !S_ISCHR(mode)) return 0; |
ad676077a device_cgroup: co... |
821 822 823 824 |
if (S_ISBLK(mode)) type = DEV_BLOCK; else type = DEV_CHAR; |
36fd71d29 devcgroup: fix ra... |
825 |
|
8c9506d16 cgroup: fix inval... |
826 827 |
return __devcgroup_check_permission(type, MAJOR(dev), MINOR(dev), ACC_MKNOD); |
36fd71d29 devcgroup: fix ra... |
828 |
|
08ce5f16e cgroups: implemen... |
829 |
} |