device_cgroup.c 11.6 KB
Newer Older
1
/*
L
Lai Jiangshan 已提交
2
 * device_cgroup.c - device cgroup subsystem
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>
12
#include <linux/seq_file.h>
L
Lai Jiangshan 已提交
13
#include <linux/rcupdate.h>
14 15 16 17 18 19 20 21 22 23 24 25

#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 */

/*
 * whitelist locking rules:
L
Lai Jiangshan 已提交
26 27
 * hold cgroup_lock() for update/read.
 * hold rcu_read_lock() for read.
28 29 30 31 32 33 34
 */

struct dev_whitelist_item {
	u32 major, minor;
	short type;
	short access;
	struct list_head list;
35
	struct rcu_head rcu;
36 37 38 39 40 41 42
};

struct dev_cgroup {
	struct cgroup_subsys_state css;
	struct list_head whitelist;
};

43 44 45 46 47
static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s)
{
	return container_of(s, struct dev_cgroup, css);
}

48 49
static inline struct dev_cgroup *cgroup_to_devcgroup(struct cgroup *cgroup)
{
50
	return css_to_devcgroup(cgroup_subsys_state(cgroup, devices_subsys_id));
51 52
}

53 54 55 56 57
static inline struct dev_cgroup *task_devcgroup(struct task_struct *task)
{
	return css_to_devcgroup(task_subsys_state(task, devices_subsys_id));
}

58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
struct cgroup_subsys devices_subsys;

static int devcgroup_can_attach(struct cgroup_subsys *ss,
		struct cgroup *new_cgroup, struct task_struct *task)
{
	if (current != task && !capable(CAP_SYS_ADMIN))
			return -EPERM;

	return 0;
}

/*
 * called under cgroup_lock()
 */
static int dev_whitelist_copy(struct list_head *dest, struct list_head *orig)
{
	struct dev_whitelist_item *wh, *tmp, *new;

	list_for_each_entry(wh, orig, list) {
L
Li Zefan 已提交
77
		new = kmemdup(wh, sizeof(*wh), GFP_KERNEL);
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
		if (!new)
			goto free_and_exit;
		list_add_tail(&new->list, dest);
	}

	return 0;

free_and_exit:
	list_for_each_entry_safe(wh, tmp, dest, list) {
		list_del(&wh->list);
		kfree(wh);
	}
	return -ENOMEM;
}

/* Stupid prototype - don't bother combining existing entries */
/*
 * called under cgroup_lock()
 */
static int dev_whitelist_add(struct dev_cgroup *dev_cgroup,
			struct dev_whitelist_item *wh)
{
100
	struct dev_whitelist_item *whcopy, *walk;
101

L
Li Zefan 已提交
102
	whcopy = kmemdup(wh, sizeof(*wh), GFP_KERNEL);
103 104 105
	if (!whcopy)
		return -ENOMEM;

106 107 108 109 110 111 112 113 114 115 116 117 118 119
	list_for_each_entry(walk, &dev_cgroup->whitelist, list) {
		if (walk->type != wh->type)
			continue;
		if (walk->major != wh->major)
			continue;
		if (walk->minor != wh->minor)
			continue;

		walk->access |= wh->access;
		kfree(whcopy);
		whcopy = NULL;
	}

	if (whcopy != NULL)
120
		list_add_tail_rcu(&whcopy->list, &dev_cgroup->whitelist);
121 122 123
	return 0;
}

124 125 126 127 128 129 130 131
static void whitelist_item_free(struct rcu_head *rcu)
{
	struct dev_whitelist_item *item;

	item = container_of(rcu, struct dev_whitelist_item, rcu);
	kfree(item);
}

132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
/*
 * called under cgroup_lock()
 */
static void dev_whitelist_rm(struct dev_cgroup *dev_cgroup,
			struct dev_whitelist_item *wh)
{
	struct dev_whitelist_item *walk, *tmp;

	list_for_each_entry_safe(walk, tmp, &dev_cgroup->whitelist, list) {
		if (walk->type == DEV_ALL)
			goto remove;
		if (walk->type != wh->type)
			continue;
		if (walk->major != ~0 && walk->major != wh->major)
			continue;
		if (walk->minor != ~0 && walk->minor != wh->minor)
			continue;

remove:
		walk->access &= ~wh->access;
		if (!walk->access) {
153 154
			list_del_rcu(&walk->list);
			call_rcu(&walk->rcu, whitelist_item_free);
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
		}
	}
}

/*
 * called from kernel/cgroup.c with cgroup_lock() held.
 */
static struct cgroup_subsys_state *devcgroup_create(struct cgroup_subsys *ss,
						struct cgroup *cgroup)
{
	struct dev_cgroup *dev_cgroup, *parent_dev_cgroup;
	struct cgroup *parent_cgroup;
	int ret;

	dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL);
	if (!dev_cgroup)
		return ERR_PTR(-ENOMEM);
	INIT_LIST_HEAD(&dev_cgroup->whitelist);
	parent_cgroup = cgroup->parent;

	if (parent_cgroup == NULL) {
		struct dev_whitelist_item *wh;
		wh = kmalloc(sizeof(*wh), GFP_KERNEL);
		if (!wh) {
			kfree(dev_cgroup);
			return ERR_PTR(-ENOMEM);
		}
		wh->minor = wh->major = ~0;
		wh->type = DEV_ALL;
L
Li Zefan 已提交
184
		wh->access = ACC_MASK;
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
		list_add(&wh->list, &dev_cgroup->whitelist);
	} else {
		parent_dev_cgroup = cgroup_to_devcgroup(parent_cgroup);
		ret = dev_whitelist_copy(&dev_cgroup->whitelist,
				&parent_dev_cgroup->whitelist);
		if (ret) {
			kfree(dev_cgroup);
			return ERR_PTR(ret);
		}
	}

	return &dev_cgroup->css;
}

static void devcgroup_destroy(struct cgroup_subsys *ss,
			struct cgroup *cgroup)
{
	struct dev_cgroup *dev_cgroup;
	struct dev_whitelist_item *wh, *tmp;

	dev_cgroup = cgroup_to_devcgroup(cgroup);
	list_for_each_entry_safe(wh, tmp, &dev_cgroup->whitelist, list) {
		list_del(&wh->list);
		kfree(wh);
	}
	kfree(dev_cgroup);
}

#define DEVCG_ALLOW 1
#define DEVCG_DENY 2
215 216
#define DEVCG_LIST 3

217
#define MAJMINLEN 13
218
#define ACCLEN 4
219 220 221 222

static void set_access(char *acc, short access)
{
	int idx = 0;
223
	memset(acc, 0, ACCLEN);
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
	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';
}

243
static void set_majmin(char *str, unsigned m)
244 245
{
	if (m == ~0)
L
Li Zefan 已提交
246
		strcpy(str, "*");
247
	else
L
Li Zefan 已提交
248
		sprintf(str, "%u", m);
249 250
}

251 252
static int devcgroup_seq_read(struct cgroup *cgroup, struct cftype *cft,
				struct seq_file *m)
253
{
254
	struct dev_cgroup *devcgroup = cgroup_to_devcgroup(cgroup);
255
	struct dev_whitelist_item *wh;
256
	char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN];
257

258 259
	rcu_read_lock();
	list_for_each_entry_rcu(wh, &devcgroup->whitelist, list) {
260
		set_access(acc, wh->access);
261 262 263 264
		set_majmin(maj, wh->major);
		set_majmin(min, wh->minor);
		seq_printf(m, "%c %s:%s %s\n", type_to_char(wh->type),
			   maj, min, acc);
265
	}
266
	rcu_read_unlock();
267

268
	return 0;
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
}

/*
 * may_access_whitelist:
 * does the access granted to dev_cgroup c contain the access
 * requested in whitelist item refwh.
 * return 1 if yes, 0 if no.
 * call with c->lock held
 */
static int may_access_whitelist(struct dev_cgroup *c,
				       struct dev_whitelist_item *refwh)
{
	struct dev_whitelist_item *whitem;

	list_for_each_entry(whitem, &c->whitelist, list) {
		if (whitem->type & DEV_ALL)
			return 1;
		if ((refwh->type & DEV_BLOCK) && !(whitem->type & DEV_BLOCK))
			continue;
		if ((refwh->type & DEV_CHAR) && !(whitem->type & DEV_CHAR))
			continue;
		if (whitem->major != ~0 && whitem->major != refwh->major)
			continue;
		if (whitem->minor != ~0 && whitem->minor != refwh->minor)
			continue;
294
		if (refwh->access & (~whitem->access))
295 296 297 298 299 300 301 302 303 304 305
			continue;
		return 1;
	}
	return 0;
}

/*
 * parent_has_perm:
 * when adding a new allow rule to a device whitelist, the rule
 * must be allowed in the parent device
 */
306
static int parent_has_perm(struct dev_cgroup *childcg,
307 308
				  struct dev_whitelist_item *wh)
{
309
	struct cgroup *pcg = childcg->css.cgroup->parent;
310 311 312 313 314
	struct dev_cgroup *parent;

	if (!pcg)
		return 1;
	parent = cgroup_to_devcgroup(pcg);
L
Lai Jiangshan 已提交
315
	return may_access_whitelist(parent, wh);
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
}

/*
 * Modify the whitelist using allow/deny rules.
 * 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
 * modify the whitelist.
 * 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
 * device whitelist controls, but for now we'll stick with CAP_SYS_ADMIN
 *
 * 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.
 */
331 332
static int devcgroup_update_access(struct dev_cgroup *devcgroup,
				   int filetype, const char *buffer)
333
{
334
	const char *b;
L
Li Zefan 已提交
335
	char *endp;
L
Li Zefan 已提交
336
	int count;
337 338 339 340 341 342 343 344 345 346 347 348
	struct dev_whitelist_item wh;

	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	memset(&wh, 0, sizeof(wh));
	b = buffer;

	switch (*b) {
	case 'a':
		wh.type = DEV_ALL;
		wh.access = ACC_MASK;
349 350
		wh.major = ~0;
		wh.minor = ~0;
351 352 353 354 355 356 357 358
		goto handle;
	case 'b':
		wh.type = DEV_BLOCK;
		break;
	case 'c':
		wh.type = DEV_CHAR;
		break;
	default:
359
		return -EINVAL;
360 361
	}
	b++;
362 363
	if (!isspace(*b))
		return -EINVAL;
364 365 366 367 368
	b++;
	if (*b == '*') {
		wh.major = ~0;
		b++;
	} else if (isdigit(*b)) {
L
Li Zefan 已提交
369 370
		wh.major = simple_strtoul(b, &endp, 10);
		b = endp;
371
	} else {
372
		return -EINVAL;
373
	}
374 375
	if (*b != ':')
		return -EINVAL;
376 377 378 379 380 381 382
	b++;

	/* read minor */
	if (*b == '*') {
		wh.minor = ~0;
		b++;
	} else if (isdigit(*b)) {
L
Li Zefan 已提交
383 384
		wh.minor = simple_strtoul(b, &endp, 10);
		b = endp;
385
	} else {
386
		return -EINVAL;
387
	}
388 389
	if (!isspace(*b))
		return -EINVAL;
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
	for (b++, count = 0; count < 3; count++, b++) {
		switch (*b) {
		case 'r':
			wh.access |= ACC_READ;
			break;
		case 'w':
			wh.access |= ACC_WRITE;
			break;
		case 'm':
			wh.access |= ACC_MKNOD;
			break;
		case '\n':
		case '\0':
			count = 3;
			break;
		default:
406
			return -EINVAL;
407 408 409 410 411 412
		}
	}

handle:
	switch (filetype) {
	case DEVCG_ALLOW:
413 414 415
		if (!parent_has_perm(devcgroup, &wh))
			return -EPERM;
		return dev_whitelist_add(devcgroup, &wh);
416 417 418 419
	case DEVCG_DENY:
		dev_whitelist_rm(devcgroup, &wh);
		break;
	default:
420
		return -EINVAL;
421
	}
422 423
	return 0;
}
424

425 426 427 428 429 430 431 432
static int devcgroup_access_write(struct cgroup *cgrp, struct cftype *cft,
				  const char *buffer)
{
	int retval;
	if (!cgroup_lock_live_group(cgrp))
		return -ENODEV;
	retval = devcgroup_update_access(cgroup_to_devcgroup(cgrp),
					 cft->private, buffer);
433 434 435 436 437 438 439
	cgroup_unlock();
	return retval;
}

static struct cftype dev_cgroup_files[] = {
	{
		.name = "allow",
440
		.write_string  = devcgroup_access_write,
441 442 443 444
		.private = DEVCG_ALLOW,
	},
	{
		.name = "deny",
445
		.write_string = devcgroup_access_write,
446 447
		.private = DEVCG_DENY,
	},
448 449 450 451 452
	{
		.name = "list",
		.read_seq_string = devcgroup_seq_read,
		.private = DEVCG_LIST,
	},
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
};

static int devcgroup_populate(struct cgroup_subsys *ss,
				struct cgroup *cgroup)
{
	return cgroup_add_files(cgroup, ss, dev_cgroup_files,
					ARRAY_SIZE(dev_cgroup_files));
}

struct cgroup_subsys devices_subsys = {
	.name = "devices",
	.can_attach = devcgroup_can_attach,
	.create = devcgroup_create,
	.destroy  = devcgroup_destroy,
	.populate = devcgroup_populate,
	.subsys_id = devices_subsys_id,
};

int devcgroup_inode_permission(struct inode *inode, int mask)
{
	struct dev_cgroup *dev_cgroup;
	struct dev_whitelist_item *wh;

	dev_t device = inode->i_rdev;
	if (!device)
		return 0;
	if (!S_ISBLK(inode->i_mode) && !S_ISCHR(inode->i_mode))
		return 0;

482
	rcu_read_lock();
L
Li Zefan 已提交
483 484 485

	dev_cgroup = task_devcgroup(current);

486
	list_for_each_entry_rcu(wh, &dev_cgroup->whitelist, list) {
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
		if (wh->type & DEV_ALL)
			goto acc_check;
		if ((wh->type & DEV_BLOCK) && !S_ISBLK(inode->i_mode))
			continue;
		if ((wh->type & DEV_CHAR) && !S_ISCHR(inode->i_mode))
			continue;
		if (wh->major != ~0 && wh->major != imajor(inode))
			continue;
		if (wh->minor != ~0 && wh->minor != iminor(inode))
			continue;
acc_check:
		if ((mask & MAY_WRITE) && !(wh->access & ACC_WRITE))
			continue;
		if ((mask & MAY_READ) && !(wh->access & ACC_READ))
			continue;
502
		rcu_read_unlock();
503 504
		return 0;
	}
L
Li Zefan 已提交
505

506
	rcu_read_unlock();
507 508 509 510 511 512 513 514 515

	return -EPERM;
}

int devcgroup_inode_mknod(int mode, dev_t dev)
{
	struct dev_cgroup *dev_cgroup;
	struct dev_whitelist_item *wh;

516
	rcu_read_lock();
L
Li Zefan 已提交
517 518 519

	dev_cgroup = task_devcgroup(current);

520
	list_for_each_entry_rcu(wh, &dev_cgroup->whitelist, list) {
521 522 523 524 525 526 527 528 529 530 531 532 533
		if (wh->type & DEV_ALL)
			goto acc_check;
		if ((wh->type & DEV_BLOCK) && !S_ISBLK(mode))
			continue;
		if ((wh->type & DEV_CHAR) && !S_ISCHR(mode))
			continue;
		if (wh->major != ~0 && wh->major != MAJOR(dev))
			continue;
		if (wh->minor != ~0 && wh->minor != MINOR(dev))
			continue;
acc_check:
		if (!(wh->access & ACC_MKNOD))
			continue;
534
		rcu_read_unlock();
535 536
		return 0;
	}
L
Li Zefan 已提交
537

538
	rcu_read_unlock();
L
Li Zefan 已提交
539

540 541
	return -EPERM;
}