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