Blame view
security/device_cgroup.c
17.5 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 165 |
struct dev_cgroup *dev_cgroup = css_to_devcgroup(css); struct dev_cgroup *parent_dev_cgroup = css_to_devcgroup(css_parent(css)); |
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 |
/** |
db9aeca97 device_cgroup: re... |
284 285 286 287 288 |
* may_access - verifies if a new exception is part of what is allowed * by a dev cgroup based on the default policy + * exceptions. This is used to make sure a child cgroup * won't have more privileges than its parent or to * verify if a certain access is allowed. |
ad676077a device_cgroup: co... |
289 |
* @dev_cgroup: dev cgroup to be tested against |
db9aeca97 device_cgroup: re... |
290 |
* @refex: new exception |
c39a2a301 devcg: prepare ma... |
291 |
* @behavior: behavior of the exception |
08ce5f16e cgroups: implemen... |
292 |
*/ |
26898fdff devcg: expand may... |
293 |
static bool may_access(struct dev_cgroup *dev_cgroup, |
c39a2a301 devcg: prepare ma... |
294 295 |
struct dev_exception_item *refex, enum devcg_behavior behavior) |
08ce5f16e cgroups: implemen... |
296 |
{ |
db9aeca97 device_cgroup: re... |
297 |
struct dev_exception_item *ex; |
ad676077a device_cgroup: co... |
298 |
bool match = false; |
08ce5f16e cgroups: implemen... |
299 |
|
4b1c7840b device_cgroup: ad... |
300 301 302 |
rcu_lockdep_assert(rcu_read_lock_held() || lockdep_is_held(&devcgroup_mutex), "device_cgroup::may_access() called without proper synchronization"); |
201e72acb device_cgroup: fi... |
303 |
list_for_each_entry_rcu(ex, &dev_cgroup->exceptions, list) { |
db9aeca97 device_cgroup: re... |
304 |
if ((refex->type & DEV_BLOCK) && !(ex->type & DEV_BLOCK)) |
08ce5f16e cgroups: implemen... |
305 |
continue; |
db9aeca97 device_cgroup: re... |
306 |
if ((refex->type & DEV_CHAR) && !(ex->type & DEV_CHAR)) |
08ce5f16e cgroups: implemen... |
307 |
continue; |
db9aeca97 device_cgroup: re... |
308 |
if (ex->major != ~0 && ex->major != refex->major) |
08ce5f16e cgroups: implemen... |
309 |
continue; |
db9aeca97 device_cgroup: re... |
310 |
if (ex->minor != ~0 && ex->minor != refex->minor) |
08ce5f16e cgroups: implemen... |
311 |
continue; |
db9aeca97 device_cgroup: re... |
312 |
if (refex->access & (~ex->access)) |
08ce5f16e cgroups: implemen... |
313 |
continue; |
ad676077a device_cgroup: co... |
314 315 |
match = true; break; |
08ce5f16e cgroups: implemen... |
316 |
} |
ad676077a device_cgroup: co... |
317 |
|
c39a2a301 devcg: prepare ma... |
318 319 320 321 322 323 324 325 326 327 328 329 |
if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) { if (behavior == DEVCG_DEFAULT_ALLOW) { /* the exception will deny access to certain devices */ return true; } else { /* the exception will allow access to certain devices */ if (match) /* * a new exception allowing access shouldn't * match an parent's exception */ return false; |
26898fdff devcg: expand may... |
330 |
return true; |
c39a2a301 devcg: prepare ma... |
331 |
} |
26898fdff devcg: expand may... |
332 |
} else { |
c39a2a301 devcg: prepare ma... |
333 334 335 |
/* only behavior == DEVCG_DEFAULT_DENY allowed here */ if (match) /* parent has an exception that matches the proposed */ |
26898fdff devcg: expand may... |
336 |
return true; |
c39a2a301 devcg: prepare ma... |
337 338 |
else return false; |
26898fdff devcg: expand may... |
339 340 |
} return false; |
08ce5f16e cgroups: implemen... |
341 342 343 344 |
} /* * parent_has_perm: |
db9aeca97 device_cgroup: re... |
345 |
* when adding a new allow rule to a device exception list, the rule |
08ce5f16e cgroups: implemen... |
346 347 |
* must be allowed in the parent device */ |
f92523e3a cgroup files: con... |
348 |
static int parent_has_perm(struct dev_cgroup *childcg, |
db9aeca97 device_cgroup: re... |
349 |
struct dev_exception_item *ex) |
08ce5f16e cgroups: implemen... |
350 |
{ |
638769869 cgroup: add css_p... |
351 |
struct dev_cgroup *parent = css_to_devcgroup(css_parent(&childcg->css)); |
08ce5f16e cgroups: implemen... |
352 |
|
638769869 cgroup: add css_p... |
353 |
if (!parent) |
08ce5f16e cgroups: implemen... |
354 |
return 1; |
c39a2a301 devcg: prepare ma... |
355 |
return may_access(parent, ex, childcg->behavior); |
08ce5f16e cgroups: implemen... |
356 |
} |
4cef7299b device_cgroup: ad... |
357 358 359 360 361 362 363 364 |
/** * 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... |
365 366 |
if (!parent) return 1; |
4cef7299b device_cgroup: ad... |
367 368 |
return parent->behavior == DEVCG_DEFAULT_ALLOW; } |
bd2953ebb devcg: propagate ... |
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 |
/** * 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 ... |
395 396 397 398 399 400 401 402 403 |
* 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... |
404 |
struct cgroup_subsys_state *pos; |
bd2953ebb devcg: propagate ... |
405 |
int rc = 0; |
bd2953ebb devcg: propagate ... |
406 |
|
d591fb566 device_cgroup: si... |
407 |
rcu_read_lock(); |
bd2953ebb devcg: propagate ... |
408 |
|
492eb21b9 cgroup: make hier... |
409 410 |
css_for_each_descendant_pre(pos, &devcg_root->css) { struct dev_cgroup *devcg = css_to_devcgroup(pos); |
d591fb566 device_cgroup: si... |
411 412 413 414 415 416 417 |
/* * 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_... |
418 |
if (pos == &devcg_root->css || !is_devcg_online(devcg)) |
d591fb566 device_cgroup: si... |
419 420 421 |
continue; rcu_read_unlock(); |
bd2953ebb devcg: propagate ... |
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 |
/* * 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... |
442 |
rcu_read_lock(); |
bd2953ebb devcg: propagate ... |
443 |
} |
d591fb566 device_cgroup: si... |
444 445 |
rcu_read_unlock(); |
bd2953ebb devcg: propagate ... |
446 447 448 449 450 451 452 453 454 |
return rc; } static inline bool has_children(struct dev_cgroup *devcgroup) { struct cgroup *cgrp = devcgroup->css.cgroup; return !list_empty(&cgrp->children); } |
08ce5f16e cgroups: implemen... |
455 |
/* |
db9aeca97 device_cgroup: re... |
456 |
* Modify the exception list using allow/deny rules. |
08ce5f16e cgroups: implemen... |
457 458 |
* 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... |
459 |
* modify the exception list. |
08ce5f16e cgroups: implemen... |
460 461 |
* 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... |
462 |
* device exception list controls, but for now we'll stick with CAP_SYS_ADMIN |
08ce5f16e cgroups: implemen... |
463 464 465 466 467 |
* * 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... |
468 |
static int devcgroup_update_access(struct dev_cgroup *devcgroup, |
4d3bb511b cgroup: drop cons... |
469 |
int filetype, char *buffer) |
08ce5f16e cgroups: implemen... |
470 |
{ |
f92523e3a cgroup files: con... |
471 |
const char *b; |
26fd8405d device_cgroup: st... |
472 |
char temp[12]; /* 11 + 1 characters needed for a u32 */ |
c39a2a301 devcg: prepare ma... |
473 |
int count, rc = 0; |
db9aeca97 device_cgroup: re... |
474 |
struct dev_exception_item ex; |
638769869 cgroup: add css_p... |
475 |
struct dev_cgroup *parent = css_to_devcgroup(css_parent(&devcgroup->css)); |
08ce5f16e cgroups: implemen... |
476 477 478 |
if (!capable(CAP_SYS_ADMIN)) return -EPERM; |
db9aeca97 device_cgroup: re... |
479 |
memset(&ex, 0, sizeof(ex)); |
08ce5f16e cgroups: implemen... |
480 481 482 483 |
b = buffer; switch (*b) { case 'a': |
ad676077a device_cgroup: co... |
484 485 |
switch (filetype) { case DEVCG_ALLOW: |
bd2953ebb devcg: propagate ... |
486 487 |
if (has_children(devcgroup)) return -EINVAL; |
4cef7299b device_cgroup: ad... |
488 |
if (!may_allow_all(parent)) |
ad676077a device_cgroup: co... |
489 |
return -EPERM; |
db9aeca97 device_cgroup: re... |
490 |
dev_exception_clean(devcgroup); |
64e104771 device_cgroup: fi... |
491 492 493 |
devcgroup->behavior = DEVCG_DEFAULT_ALLOW; if (!parent) break; |
4cef7299b device_cgroup: ad... |
494 495 496 497 |
rc = dev_exceptions_copy(&devcgroup->exceptions, &parent->exceptions); if (rc) return rc; |
ad676077a device_cgroup: co... |
498 499 |
break; case DEVCG_DENY: |
bd2953ebb devcg: propagate ... |
500 501 |
if (has_children(devcgroup)) return -EINVAL; |
db9aeca97 device_cgroup: re... |
502 |
dev_exception_clean(devcgroup); |
5b7aa7d5b device_cgroup: re... |
503 |
devcgroup->behavior = DEVCG_DEFAULT_DENY; |
ad676077a device_cgroup: co... |
504 505 506 507 508 |
break; default: return -EINVAL; } return 0; |
08ce5f16e cgroups: implemen... |
509 |
case 'b': |
db9aeca97 device_cgroup: re... |
510 |
ex.type = DEV_BLOCK; |
08ce5f16e cgroups: implemen... |
511 512 |
break; case 'c': |
db9aeca97 device_cgroup: re... |
513 |
ex.type = DEV_CHAR; |
08ce5f16e cgroups: implemen... |
514 515 |
break; default: |
f92523e3a cgroup files: con... |
516 |
return -EINVAL; |
08ce5f16e cgroups: implemen... |
517 518 |
} b++; |
f92523e3a cgroup files: con... |
519 520 |
if (!isspace(*b)) return -EINVAL; |
08ce5f16e cgroups: implemen... |
521 522 |
b++; if (*b == '*') { |
db9aeca97 device_cgroup: re... |
523 |
ex.major = ~0; |
08ce5f16e cgroups: implemen... |
524 525 |
b++; } else if (isdigit(*b)) { |
26fd8405d device_cgroup: st... |
526 527 528 529 530 531 532 533 534 535 |
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... |
536 |
} else { |
f92523e3a cgroup files: con... |
537 |
return -EINVAL; |
08ce5f16e cgroups: implemen... |
538 |
} |
f92523e3a cgroup files: con... |
539 540 |
if (*b != ':') return -EINVAL; |
08ce5f16e cgroups: implemen... |
541 542 543 544 |
b++; /* read minor */ if (*b == '*') { |
db9aeca97 device_cgroup: re... |
545 |
ex.minor = ~0; |
08ce5f16e cgroups: implemen... |
546 547 |
b++; } else if (isdigit(*b)) { |
26fd8405d device_cgroup: st... |
548 549 550 551 552 553 554 555 556 557 |
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... |
558 |
} else { |
f92523e3a cgroup files: con... |
559 |
return -EINVAL; |
08ce5f16e cgroups: implemen... |
560 |
} |
f92523e3a cgroup files: con... |
561 562 |
if (!isspace(*b)) return -EINVAL; |
08ce5f16e cgroups: implemen... |
563 564 565 |
for (b++, count = 0; count < 3; count++, b++) { switch (*b) { case 'r': |
db9aeca97 device_cgroup: re... |
566 |
ex.access |= ACC_READ; |
08ce5f16e cgroups: implemen... |
567 568 |
break; case 'w': |
db9aeca97 device_cgroup: re... |
569 |
ex.access |= ACC_WRITE; |
08ce5f16e cgroups: implemen... |
570 571 |
break; case 'm': |
db9aeca97 device_cgroup: re... |
572 |
ex.access |= ACC_MKNOD; |
08ce5f16e cgroups: implemen... |
573 574 575 576 577 578 579 |
break; case ' ': case '\0': count = 3; break; default: |
f92523e3a cgroup files: con... |
580 |
return -EINVAL; |
08ce5f16e cgroups: implemen... |
581 582 |
} } |
08ce5f16e cgroups: implemen... |
583 584 |
switch (filetype) { case DEVCG_ALLOW: |
db9aeca97 device_cgroup: re... |
585 |
if (!parent_has_perm(devcgroup, &ex)) |
f92523e3a cgroup files: con... |
586 |
return -EPERM; |
ad676077a device_cgroup: co... |
587 588 589 590 591 |
/* * 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... |
592 |
if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { |
db9aeca97 device_cgroup: re... |
593 |
dev_exception_rm(devcgroup, &ex); |
ad676077a device_cgroup: co... |
594 595 |
return 0; } |
bd2953ebb devcg: propagate ... |
596 597 |
rc = dev_exception_add(devcgroup, &ex); break; |
08ce5f16e cgroups: implemen... |
598 |
case DEVCG_DENY: |
ad676077a device_cgroup: co... |
599 600 601 602 603 |
/* * 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 ... |
604 |
if (devcgroup->behavior == DEVCG_DEFAULT_DENY) |
db9aeca97 device_cgroup: re... |
605 |
dev_exception_rm(devcgroup, &ex); |
bd2953ebb devcg: propagate ... |
606 607 608 609 610 611 612 613 |
else rc = dev_exception_add(devcgroup, &ex); if (rc) break; /* we only propagate new restrictions */ rc = propagate_exception(devcgroup, &ex); break; |
08ce5f16e cgroups: implemen... |
614 |
default: |
bd2953ebb devcg: propagate ... |
615 |
rc = -EINVAL; |
08ce5f16e cgroups: implemen... |
616 |
} |
bd2953ebb devcg: propagate ... |
617 |
return rc; |
f92523e3a cgroup files: con... |
618 |
} |
08ce5f16e cgroups: implemen... |
619 |
|
182446d08 cgroup: pass arou... |
620 |
static int devcgroup_access_write(struct cgroup_subsys_state *css, |
4d3bb511b cgroup: drop cons... |
621 |
struct cftype *cft, char *buffer) |
f92523e3a cgroup files: con... |
622 623 |
{ int retval; |
b4046f00e devcgroup: avoid ... |
624 625 |
mutex_lock(&devcgroup_mutex); |
182446d08 cgroup: pass arou... |
626 |
retval = devcgroup_update_access(css_to_devcgroup(css), |
f92523e3a cgroup files: con... |
627 |
cft->private, buffer); |
b4046f00e devcgroup: avoid ... |
628 |
mutex_unlock(&devcgroup_mutex); |
08ce5f16e cgroups: implemen... |
629 630 631 632 633 634 |
return retval; } static struct cftype dev_cgroup_files[] = { { .name = "allow", |
f92523e3a cgroup files: con... |
635 |
.write_string = devcgroup_access_write, |
08ce5f16e cgroups: implemen... |
636 637 638 639 |
.private = DEVCG_ALLOW, }, { .name = "deny", |
f92523e3a cgroup files: con... |
640 |
.write_string = devcgroup_access_write, |
08ce5f16e cgroups: implemen... |
641 642 |
.private = DEVCG_DENY, }, |
29486df32 cgroups: introduc... |
643 644 |
{ .name = "list", |
2da8ca822 cgroup: replace c... |
645 |
.seq_show = devcgroup_seq_show, |
29486df32 cgroups: introduc... |
646 647 |
.private = DEVCG_LIST, }, |
4baf6e332 cgroup: convert a... |
648 |
{ } /* terminate */ |
08ce5f16e cgroups: implemen... |
649 |
}; |
073219e99 cgroup: clean up ... |
650 |
struct cgroup_subsys devices_cgrp_subsys = { |
92fb97487 cgroup: rename ->... |
651 652 |
.css_alloc = devcgroup_css_alloc, .css_free = devcgroup_css_free, |
1909554c9 devcg: use css_on... |
653 654 |
.css_online = devcgroup_online, .css_offline = devcgroup_offline, |
4baf6e332 cgroup: convert a... |
655 |
.base_cftypes = dev_cgroup_files, |
08ce5f16e cgroups: implemen... |
656 |
}; |
ad676077a device_cgroup: co... |
657 658 659 660 661 662 663 664 665 666 |
/** * __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... |
667 |
static int __devcgroup_check_permission(short type, u32 major, u32 minor, |
ad676077a device_cgroup: co... |
668 |
short access) |
08ce5f16e cgroups: implemen... |
669 |
{ |
8c9506d16 cgroup: fix inval... |
670 |
struct dev_cgroup *dev_cgroup; |
db9aeca97 device_cgroup: re... |
671 |
struct dev_exception_item ex; |
ad676077a device_cgroup: co... |
672 |
int rc; |
36fd71d29 devcgroup: fix ra... |
673 |
|
db9aeca97 device_cgroup: re... |
674 675 676 677 678 |
memset(&ex, 0, sizeof(ex)); ex.type = type; ex.major = major; ex.minor = minor; ex.access = access; |
36fd71d29 devcgroup: fix ra... |
679 |
|
ad676077a device_cgroup: co... |
680 |
rcu_read_lock(); |
8c9506d16 cgroup: fix inval... |
681 |
dev_cgroup = task_devcgroup(current); |
c39a2a301 devcg: prepare ma... |
682 |
rc = may_access(dev_cgroup, &ex, dev_cgroup->behavior); |
ad676077a device_cgroup: co... |
683 |
rcu_read_unlock(); |
cd5008196 devcgroup: skip s... |
684 |
|
ad676077a device_cgroup: co... |
685 686 |
if (!rc) return -EPERM; |
36fd71d29 devcgroup: fix ra... |
687 |
|
ad676077a device_cgroup: co... |
688 689 |
return 0; } |
08ce5f16e cgroups: implemen... |
690 |
|
ad676077a device_cgroup: co... |
691 692 |
int __devcgroup_inode_permission(struct inode *inode, int mask) { |
ad676077a device_cgroup: co... |
693 694 695 696 697 698 699 700 701 702 |
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... |
703 704 |
return __devcgroup_check_permission(type, imajor(inode), iminor(inode), access); |
08ce5f16e cgroups: implemen... |
705 706 707 708 |
} int devcgroup_inode_mknod(int mode, dev_t dev) { |
ad676077a device_cgroup: co... |
709 |
short type; |
08ce5f16e cgroups: implemen... |
710 |
|
0b82ac37b devices cgroup: a... |
711 712 |
if (!S_ISBLK(mode) && !S_ISCHR(mode)) return 0; |
ad676077a device_cgroup: co... |
713 714 715 716 |
if (S_ISBLK(mode)) type = DEV_BLOCK; else type = DEV_CHAR; |
36fd71d29 devcgroup: fix ra... |
717 |
|
8c9506d16 cgroup: fix inval... |
718 719 |
return __devcgroup_check_permission(type, MAJOR(dev), MINOR(dev), ACC_MKNOD); |
36fd71d29 devcgroup: fix ra... |
720 |
|
08ce5f16e cgroups: implemen... |
721 |
} |