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