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