851 lines · 21.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * device_cgroup.c - device cgroup subsystem * * 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>#include <linux/seq_file.h>#include <linux/slab.h>#include <linux/rcupdate.h>#include <linux/mutex.h> #ifdef CONFIG_CGROUP_DEVICE static DEFINE_MUTEX(devcgroup_mutex); enum devcg_behavior {	DEVCG_DEFAULT_NONE,	DEVCG_DEFAULT_ALLOW,	DEVCG_DEFAULT_DENY,}; /* * exception list locking rules: * hold devcgroup_mutex for update/read. * hold rcu_read_lock() for read. */ struct dev_exception_item {	u32 major, minor;	short type;	short access;	struct list_head list;	struct rcu_head rcu;}; struct dev_cgroup {	struct cgroup_subsys_state css;	struct list_head exceptions;	enum devcg_behavior behavior;}; static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s){	return s ? container_of(s, struct dev_cgroup, css) : NULL;} static inline struct dev_cgroup *task_devcgroup(struct task_struct *task){	return css_to_devcgroup(task_css(task, devices_cgrp_id));} /* * called under devcgroup_mutex */static int dev_exceptions_copy(struct list_head *dest, struct list_head *orig){	struct dev_exception_item *ex, *tmp, *new; 	lockdep_assert_held(&devcgroup_mutex); 	list_for_each_entry(ex, orig, list) {		new = kmemdup(ex, sizeof(*ex), GFP_KERNEL);		if (!new)			goto free_and_exit;		list_add_tail(&new->list, dest);	} 	return 0; free_and_exit:	list_for_each_entry_safe(ex, tmp, dest, list) {		list_del(&ex->list);		kfree(ex);	}	return -ENOMEM;} /* * called under devcgroup_mutex */static int dev_exception_add(struct dev_cgroup *dev_cgroup,			     struct dev_exception_item *ex){	struct dev_exception_item *excopy, *walk; 	lockdep_assert_held(&devcgroup_mutex); 	excopy = kmemdup(ex, sizeof(*ex), GFP_KERNEL);	if (!excopy)		return -ENOMEM; 	list_for_each_entry(walk, &dev_cgroup->exceptions, list) {		if (walk->type != ex->type)			continue;		if (walk->major != ex->major)			continue;		if (walk->minor != ex->minor)			continue; 		walk->access |= ex->access;		kfree(excopy);		excopy = NULL;	} 	if (excopy != NULL)		list_add_tail_rcu(&excopy->list, &dev_cgroup->exceptions);	return 0;} /* * called under devcgroup_mutex */static void dev_exception_rm(struct dev_cgroup *dev_cgroup,			     struct dev_exception_item *ex){	struct dev_exception_item *walk, *tmp; 	lockdep_assert_held(&devcgroup_mutex); 	list_for_each_entry_safe(walk, tmp, &dev_cgroup->exceptions, list) {		if (walk->type != ex->type)			continue;		if (walk->major != ex->major)			continue;		if (walk->minor != ex->minor)			continue; 		walk->access &= ~ex->access;		if (!walk->access) {			list_del_rcu(&walk->list);			kfree_rcu(walk, rcu);		}	}} static void __dev_exception_clean(struct dev_cgroup *dev_cgroup){	struct dev_exception_item *ex, *tmp; 	list_for_each_entry_safe(ex, tmp, &dev_cgroup->exceptions, list) {		list_del_rcu(&ex->list);		kfree_rcu(ex, rcu);	}} /** * dev_exception_clean - frees all entries of the exception list * @dev_cgroup: dev_cgroup with the exception list to be cleaned * * called under devcgroup_mutex */static void dev_exception_clean(struct dev_cgroup *dev_cgroup){	lockdep_assert_held(&devcgroup_mutex); 	__dev_exception_clean(dev_cgroup);} static inline bool is_devcg_online(const struct dev_cgroup *devcg){	return (devcg->behavior != DEVCG_DEFAULT_NONE);} /** * devcgroup_online - initializes devcgroup's behavior and exceptions based on * 		      parent's * @css: css getting online * returns 0 in case of success, error code otherwise */static int devcgroup_online(struct cgroup_subsys_state *css){	struct dev_cgroup *dev_cgroup = css_to_devcgroup(css);	struct dev_cgroup *parent_dev_cgroup = css_to_devcgroup(css->parent);	int ret = 0; 	mutex_lock(&devcgroup_mutex); 	if (parent_dev_cgroup == NULL)		dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW;	else {		ret = dev_exceptions_copy(&dev_cgroup->exceptions,					  &parent_dev_cgroup->exceptions);		if (!ret)			dev_cgroup->behavior = parent_dev_cgroup->behavior;	}	mutex_unlock(&devcgroup_mutex); 	return ret;} static void devcgroup_offline(struct cgroup_subsys_state *css){	struct dev_cgroup *dev_cgroup = css_to_devcgroup(css); 	mutex_lock(&devcgroup_mutex);	dev_cgroup->behavior = DEVCG_DEFAULT_NONE;	mutex_unlock(&devcgroup_mutex);} /* * called from kernel/cgroup.c with cgroup_lock() held. */static struct cgroup_subsys_state *devcgroup_css_alloc(struct cgroup_subsys_state *parent_css){	struct dev_cgroup *dev_cgroup; 	dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL);	if (!dev_cgroup)		return ERR_PTR(-ENOMEM);	INIT_LIST_HEAD(&dev_cgroup->exceptions);	dev_cgroup->behavior = DEVCG_DEFAULT_NONE; 	return &dev_cgroup->css;} static void devcgroup_css_free(struct cgroup_subsys_state *css){	struct dev_cgroup *dev_cgroup = css_to_devcgroup(css); 	__dev_exception_clean(dev_cgroup);	kfree(dev_cgroup);} #define DEVCG_ALLOW 1#define DEVCG_DENY 2#define DEVCG_LIST 3 #define MAJMINLEN 13#define ACCLEN 4 static void set_access(char *acc, short access){	int idx = 0;	memset(acc, 0, ACCLEN);	if (access & DEVCG_ACC_READ)		acc[idx++] = 'r';	if (access & DEVCG_ACC_WRITE)		acc[idx++] = 'w';	if (access & DEVCG_ACC_MKNOD)		acc[idx++] = 'm';} static char type_to_char(short type){	if (type == DEVCG_DEV_ALL)		return 'a';	if (type == DEVCG_DEV_CHAR)		return 'c';	if (type == DEVCG_DEV_BLOCK)		return 'b';	return 'X';} static void set_majmin(char *str, unsigned m){	if (m == ~0)		strcpy(str, "*");	else		sprintf(str, "%u", m);} static int devcgroup_seq_show(struct seq_file *m, void *v){	struct dev_cgroup *devcgroup = css_to_devcgroup(seq_css(m));	struct dev_exception_item *ex;	char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN]; 	rcu_read_lock();	/*	 * To preserve the compatibility:	 * - Only show the "all devices" when the default policy is to allow	 * - List the exceptions in case the default policy is to deny	 * This way, the file remains as a "whitelist of devices"	 */	if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) {		set_access(acc, DEVCG_ACC_MASK);		set_majmin(maj, ~0);		set_majmin(min, ~0);		seq_printf(m, "%c %s:%s %s\n", type_to_char(DEVCG_DEV_ALL),			   maj, min, acc);	} else {		list_for_each_entry_rcu(ex, &devcgroup->exceptions, list) {			set_access(acc, ex->access);			set_majmin(maj, ex->major);			set_majmin(min, ex->minor);			seq_printf(m, "%c %s:%s %s\n", type_to_char(ex->type),				   maj, min, acc);		}	}	rcu_read_unlock(); 	return 0;} /** * match_exception	- iterates the exception list trying to find a complete match * @exceptions: list of exceptions * @type: device type (DEVCG_DEV_BLOCK or DEVCG_DEV_CHAR) * @major: device file major number, ~0 to match all * @minor: device file minor number, ~0 to match all * @access: permission mask (DEVCG_ACC_READ, DEVCG_ACC_WRITE, DEVCG_ACC_MKNOD) * * It is considered a complete match if an exception is found that will * contain the entire range of provided parameters. * * Return: true in case it matches an exception completely */static bool match_exception(struct list_head *exceptions, short type,			    u32 major, u32 minor, short access){	struct dev_exception_item *ex; 	list_for_each_entry_rcu(ex, exceptions, list) {		if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK))			continue;		if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR))			continue;		if (ex->major != ~0 && ex->major != major)			continue;		if (ex->minor != ~0 && ex->minor != minor)			continue;		/* provided access cannot have more than the exception rule */		if (access & (~ex->access))			continue;		return true;	}	return false;} /** * match_exception_partial - iterates the exception list trying to find a partial match * @exceptions: list of exceptions * @type: device type (DEVCG_DEV_BLOCK or DEVCG_DEV_CHAR) * @major: device file major number, ~0 to match all * @minor: device file minor number, ~0 to match all * @access: permission mask (DEVCG_ACC_READ, DEVCG_ACC_WRITE, DEVCG_ACC_MKNOD) * * It is considered a partial match if an exception's range is found to * contain *any* of the devices specified by provided parameters. This is * used to make sure no extra access is being granted that is forbidden by * any of the exception list. * * Return: true in case the provided range mat matches an exception completely */static bool match_exception_partial(struct list_head *exceptions, short type,				    u32 major, u32 minor, short access){	struct dev_exception_item *ex; 	list_for_each_entry_rcu(ex, exceptions, list,				lockdep_is_held(&devcgroup_mutex)) {		if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK))			continue;		if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR))			continue;		/*		 * We must be sure that both the exception and the provided		 * range aren't masking all devices		 */		if (ex->major != ~0 && major != ~0 && ex->major != major)			continue;		if (ex->minor != ~0 && minor != ~0 && ex->minor != minor)			continue;		/*		 * In order to make sure the provided range isn't matching		 * an exception, all its access bits shouldn't match the		 * exception's access bits		 */		if (!(access & ex->access))			continue;		return true;	}	return false;} /** * verify_new_ex - verifies if a new exception is allowed by parent cgroup's permissions * @dev_cgroup: dev cgroup to be tested against * @refex: new exception * @behavior: behavior of the exception's dev_cgroup * * This is used to make sure a child cgroup won't have more privileges * than its parent */static bool verify_new_ex(struct dev_cgroup *dev_cgroup,		          struct dev_exception_item *refex,		          enum devcg_behavior behavior){	bool match = false; 	RCU_LOCKDEP_WARN(!rcu_read_lock_held() &&			 !lockdep_is_held(&devcgroup_mutex),			 "device_cgroup:verify_new_ex called without proper synchronization"); 	if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) {		if (behavior == DEVCG_DEFAULT_ALLOW) {			/*			 * new exception in the child doesn't matter, only			 * adding extra restrictions			 */ 			return true;		} else {			/*			 * new exception in the child will add more devices			 * that can be acessed, so it can't match any of			 * parent's exceptions, even slightly			 */ 			match = match_exception_partial(&dev_cgroup->exceptions,							refex->type,							refex->major,							refex->minor,							refex->access); 			if (match)				return false;			return true;		}	} else {		/*		 * Only behavior == DEVCG_DEFAULT_DENY allowed here, therefore		 * the new exception will add access to more devices and must		 * be contained completely in an parent's exception to be		 * allowed		 */		match = match_exception(&dev_cgroup->exceptions, refex->type,					refex->major, refex->minor,					refex->access); 		if (match)			/* parent has an exception that matches the proposed */			return true;		else			return false;	}	return false;} /* * parent_has_perm: * when adding a new allow rule to a device exception list, the rule * must be allowed in the parent device */static int parent_has_perm(struct dev_cgroup *childcg,				  struct dev_exception_item *ex){	struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent); 	if (!parent)		return 1;	return verify_new_ex(parent, ex, childcg->behavior);} /** * parent_allows_removal - verify if it's ok to remove an exception * @childcg: child cgroup from where the exception will be removed * @ex: exception being removed * * When removing an exception in cgroups with default ALLOW policy, it must * be checked if removing it will give the child cgroup more access than the * parent. * * Return: true if it's ok to remove exception, false otherwise */static bool parent_allows_removal(struct dev_cgroup *childcg,				  struct dev_exception_item *ex){	struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent); 	if (!parent)		return true; 	/* It's always allowed to remove access to devices */	if (childcg->behavior == DEVCG_DEFAULT_DENY)		return true; 	/*	 * Make sure you're not removing part or a whole exception existing in	 * the parent cgroup	 */	return !match_exception_partial(&parent->exceptions, ex->type,					ex->major, ex->minor, ex->access);} /** * may_allow_all - checks if it's possible to change the behavior to *		   allow based on parent's rules. * @parent: device cgroup's parent * returns: != 0 in case it's allowed, 0 otherwise */static inline int may_allow_all(struct dev_cgroup *parent){	if (!parent)		return 1;

Lines 1–500 of 851.