diff options
Diffstat (limited to 'security/device_cgroup.c')
| -rw-r--r-- | security/device_cgroup.c | 261 | 
1 files changed, 177 insertions, 84 deletions
diff --git a/security/device_cgroup.c b/security/device_cgroup.c index c123628d3f8..d9d69e6930e 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -58,19 +58,7 @@ static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s)  static inline struct dev_cgroup *task_devcgroup(struct task_struct *task)  { -	return css_to_devcgroup(task_css(task, devices_subsys_id)); -} - -struct cgroup_subsys devices_subsys; - -static int devcgroup_can_attach(struct cgroup_subsys_state *new_css, -				struct cgroup_taskset *set) -{ -	struct task_struct *task = cgroup_taskset_first(set); - -	if (current != task && !capable(CAP_SYS_ADMIN)) -		return -EPERM; -	return 0; +	return css_to_devcgroup(task_css(task, devices_cgrp_id));  }  /* @@ -194,7 +182,7 @@ static inline bool is_devcg_online(const struct dev_cgroup *devcg)  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(css)); +	struct dev_cgroup *parent_dev_cgroup = css_to_devcgroup(css->parent);  	int ret = 0;  	mutex_lock(&devcgroup_mutex); @@ -284,10 +272,9 @@ static void set_majmin(char *str, unsigned m)  		sprintf(str, "%u", m);  } -static int devcgroup_seq_read(struct cgroup_subsys_state *css, -			      struct cftype *cft, struct seq_file *m) +static int devcgroup_seq_show(struct seq_file *m, void *v)  { -	struct dev_cgroup *devcgroup = css_to_devcgroup(css); +	struct dev_cgroup *devcgroup = css_to_devcgroup(seq_css(m));  	struct dev_exception_item *ex;  	char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN]; @@ -319,57 +306,138 @@ static int devcgroup_seq_read(struct cgroup_subsys_state *css,  }  /** - * may_access - verifies if a new exception is part of what is allowed - *		by a dev cgroup based on the default policy + - *		exceptions. This is used to make sure a child cgroup - *		won't have more privileges than its parent or to - *		verify if a certain access is allowed. - * @dev_cgroup: dev cgroup to be tested against - * @refex: new exception - * @behavior: behavior of the exception + * match_exception	- iterates the exception list trying to find a complete match + * @exceptions: list of exceptions + * @type: device type (DEV_BLOCK or DEV_CHAR) + * @major: device file major number, ~0 to match all + * @minor: device file minor number, ~0 to match all + * @access: permission mask (ACC_READ, ACC_WRITE, 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 may_access(struct dev_cgroup *dev_cgroup, -		       struct dev_exception_item *refex, -		       enum devcg_behavior behavior) +static bool match_exception(struct list_head *exceptions, short type, +			    u32 major, u32 minor, short access)  {  	struct dev_exception_item *ex; -	bool match = false; -	rcu_lockdep_assert(rcu_read_lock_held() || -			   lockdep_is_held(&devcgroup_mutex), -			   "device_cgroup::may_access() called without proper synchronization"); +	list_for_each_entry_rcu(ex, exceptions, list) { +		if ((type & DEV_BLOCK) && !(ex->type & DEV_BLOCK)) +			continue; +		if ((type & DEV_CHAR) && !(ex->type & 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 (DEV_BLOCK or DEV_CHAR) + * @major: device file major number, ~0 to match all + * @minor: device file minor number, ~0 to match all + * @access: permission mask (ACC_READ, ACC_WRITE, 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, &dev_cgroup->exceptions, list) { -		if ((refex->type & DEV_BLOCK) && !(ex->type & DEV_BLOCK)) +	list_for_each_entry_rcu(ex, exceptions, list) { +		if ((type & DEV_BLOCK) && !(ex->type & DEV_BLOCK))  			continue; -		if ((refex->type & DEV_CHAR) && !(ex->type & DEV_CHAR)) +		if ((type & DEV_CHAR) && !(ex->type & DEV_CHAR))  			continue; -		if (ex->major != ~0 && ex->major != refex->major) +		/* +		 * 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 && ex->minor != refex->minor) +		if (ex->minor != ~0 && minor != ~0 && ex->minor != minor)  			continue; -		if (refex->access & (~ex->access)) +		/* +		 * 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; -		match = true; -		break; +		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_assert(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) { -			/* the exception will deny access to certain devices */ +			/* +			 * new exception in the child doesn't matter, only +			 * adding extra restrictions +			 */   			return true;  		} else { -			/* the exception will allow access to certain devices */ +			/* +			 * 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) -				/* -				 * a new exception allowing access shouldn't -				 * match an parent's exception -				 */  				return false;  			return true;  		}  	} else { -		/* only behavior == DEVCG_DEFAULT_DENY allowed here */ +		/* +		 * 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; @@ -387,11 +455,42 @@ static bool may_access(struct dev_cgroup *dev_cgroup,  static int parent_has_perm(struct dev_cgroup *childcg,  				  struct dev_exception_item *ex)  { -	struct dev_cgroup *parent = css_to_devcgroup(css_parent(&childcg->css)); +	struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent);  	if (!parent)  		return 1; -	return may_access(parent, ex, childcg->behavior); +	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);  }  /** @@ -488,13 +587,6 @@ static int propagate_exception(struct dev_cgroup *devcg_root,  	return rc;  } -static inline bool has_children(struct dev_cgroup *devcgroup) -{ -	struct cgroup *cgrp = devcgroup->css.cgroup; - -	return !list_empty(&cgrp->children); -} -  /*   * Modify the exception list using allow/deny rules.   * CAP_SYS_ADMIN is needed for this.  It's at least separate from CAP_MKNOD @@ -509,13 +601,13 @@ static inline bool has_children(struct dev_cgroup *devcgroup)   * parent cgroup has the access you're asking for.   */  static int devcgroup_update_access(struct dev_cgroup *devcgroup, -				   int filetype, const char *buffer) +				   int filetype, char *buffer)  {  	const char *b;  	char temp[12];		/* 11 + 1 characters needed for a u32 */  	int count, rc = 0;  	struct dev_exception_item ex; -	struct dev_cgroup *parent = css_to_devcgroup(css_parent(&devcgroup->css)); +	struct dev_cgroup *parent = css_to_devcgroup(devcgroup->css.parent);  	if (!capable(CAP_SYS_ADMIN))  		return -EPERM; @@ -527,7 +619,7 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup,  	case 'a':  		switch (filetype) {  		case DEVCG_ALLOW: -			if (has_children(devcgroup)) +			if (css_has_online_children(&devcgroup->css))  				return -EINVAL;  			if (!may_allow_all(parent)) @@ -543,7 +635,7 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup,  				return rc;  			break;  		case DEVCG_DENY: -			if (has_children(devcgroup)) +			if (css_has_online_children(&devcgroup->css))  				return -EINVAL;  			dev_exception_clean(devcgroup); @@ -629,17 +721,21 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup,  	switch (filetype) {  	case DEVCG_ALLOW: -		if (!parent_has_perm(devcgroup, &ex)) -			return -EPERM;  		/*  		 * If the default policy is to allow by default, try to remove  		 * an matching exception instead. And be silent about it: we  		 * don't want to break compatibility  		 */  		if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { +			/* Check if the parent allows removing it first */ +			if (!parent_allows_removal(devcgroup, &ex)) +				return -EPERM;  			dev_exception_rm(devcgroup, &ex); -			return 0; +			break;  		} + +		if (!parent_has_perm(devcgroup, &ex)) +			return -EPERM;  		rc = dev_exception_add(devcgroup, &ex);  		break;  	case DEVCG_DENY: @@ -664,45 +760,42 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup,  	return rc;  } -static int devcgroup_access_write(struct cgroup_subsys_state *css, -				  struct cftype *cft, const char *buffer) +static ssize_t devcgroup_access_write(struct kernfs_open_file *of, +				      char *buf, size_t nbytes, loff_t off)  {  	int retval;  	mutex_lock(&devcgroup_mutex); -	retval = devcgroup_update_access(css_to_devcgroup(css), -					 cft->private, buffer); +	retval = devcgroup_update_access(css_to_devcgroup(of_css(of)), +					 of_cft(of)->private, strstrip(buf));  	mutex_unlock(&devcgroup_mutex); -	return retval; +	return retval ?: nbytes;  }  static struct cftype dev_cgroup_files[] = {  	{  		.name = "allow", -		.write_string  = devcgroup_access_write, +		.write = devcgroup_access_write,  		.private = DEVCG_ALLOW,  	},  	{  		.name = "deny", -		.write_string = devcgroup_access_write, +		.write = devcgroup_access_write,  		.private = DEVCG_DENY,  	},  	{  		.name = "list", -		.read_seq_string = devcgroup_seq_read, +		.seq_show = devcgroup_seq_show,  		.private = DEVCG_LIST,  	},  	{ }	/* terminate */  }; -struct cgroup_subsys devices_subsys = { -	.name = "devices", -	.can_attach = devcgroup_can_attach, +struct cgroup_subsys devices_cgrp_subsys = {  	.css_alloc = devcgroup_css_alloc,  	.css_free = devcgroup_css_free,  	.css_online = devcgroup_online,  	.css_offline = devcgroup_offline, -	.subsys_id = devices_subsys_id,  	.base_cftypes = dev_cgroup_files,  }; @@ -720,18 +813,18 @@ static int __devcgroup_check_permission(short type, u32 major, u32 minor,  				        short access)  {  	struct dev_cgroup *dev_cgroup; -	struct dev_exception_item ex; -	int rc; - -	memset(&ex, 0, sizeof(ex)); -	ex.type = type; -	ex.major = major; -	ex.minor = minor; -	ex.access = access; +	bool rc;  	rcu_read_lock();  	dev_cgroup = task_devcgroup(current); -	rc = may_access(dev_cgroup, &ex, dev_cgroup->behavior); +	if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) +		/* Can't match any of the exceptions, even partially */ +		rc = !match_exception_partial(&dev_cgroup->exceptions, +					      type, major, minor, access); +	else +		/* Need to match completely one exception to be allowed */ +		rc = match_exception(&dev_cgroup->exceptions, type, major, +				     minor, access);  	rcu_read_unlock();  	if (!rc)  | 
