xref: /openbmc/linux/security/device_cgroup.c (revision 4be22f16)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
208ce5f16SSerge E. Hallyn /*
347c59803SLai Jiangshan  * device_cgroup.c - device cgroup subsystem
408ce5f16SSerge E. Hallyn  *
508ce5f16SSerge E. Hallyn  * Copyright 2007 IBM Corp
608ce5f16SSerge E. Hallyn  */
708ce5f16SSerge E. Hallyn 
8aef2fedaSJakub Kicinski #include <linux/bpf-cgroup.h>
908ce5f16SSerge E. Hallyn #include <linux/device_cgroup.h>
1008ce5f16SSerge E. Hallyn #include <linux/cgroup.h>
1108ce5f16SSerge E. Hallyn #include <linux/ctype.h>
1208ce5f16SSerge E. Hallyn #include <linux/list.h>
1308ce5f16SSerge E. Hallyn #include <linux/uaccess.h>
1429486df3SSerge E. Hallyn #include <linux/seq_file.h>
155a0e3ad6STejun Heo #include <linux/slab.h>
1647c59803SLai Jiangshan #include <linux/rcupdate.h>
17b4046f00SLi Zefan #include <linux/mutex.h>
1808ce5f16SSerge E. Hallyn 
19eec8fd02SOdin Ugedal #ifdef CONFIG_CGROUP_DEVICE
20eec8fd02SOdin Ugedal 
21b4046f00SLi Zefan static DEFINE_MUTEX(devcgroup_mutex);
22b4046f00SLi Zefan 
23c39a2a30SAristeu Rozanski enum devcg_behavior {
24c39a2a30SAristeu Rozanski 	DEVCG_DEFAULT_NONE,
25c39a2a30SAristeu Rozanski 	DEVCG_DEFAULT_ALLOW,
26c39a2a30SAristeu Rozanski 	DEVCG_DEFAULT_DENY,
27c39a2a30SAristeu Rozanski };
28c39a2a30SAristeu Rozanski 
2908ce5f16SSerge E. Hallyn /*
30db9aeca9SAristeu Rozanski  * exception list locking rules:
31b4046f00SLi Zefan  * hold devcgroup_mutex for update/read.
3247c59803SLai Jiangshan  * hold rcu_read_lock() for read.
3308ce5f16SSerge E. Hallyn  */
3408ce5f16SSerge E. Hallyn 
35db9aeca9SAristeu Rozanski struct dev_exception_item {
3608ce5f16SSerge E. Hallyn 	u32 major, minor;
3708ce5f16SSerge E. Hallyn 	short type;
3808ce5f16SSerge E. Hallyn 	short access;
3908ce5f16SSerge E. Hallyn 	struct list_head list;
404efd1a1bSPavel Emelyanov 	struct rcu_head rcu;
4108ce5f16SSerge E. Hallyn };
4208ce5f16SSerge E. Hallyn 
4308ce5f16SSerge E. Hallyn struct dev_cgroup {
4408ce5f16SSerge E. Hallyn 	struct cgroup_subsys_state css;
45db9aeca9SAristeu Rozanski 	struct list_head exceptions;
46c39a2a30SAristeu Rozanski 	enum devcg_behavior behavior;
4708ce5f16SSerge E. Hallyn };
4808ce5f16SSerge E. Hallyn 
css_to_devcgroup(struct cgroup_subsys_state * s)49b66862f7SPavel Emelyanov static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s)
50b66862f7SPavel Emelyanov {
51a7c6d554STejun Heo 	return s ? container_of(s, struct dev_cgroup, css) : NULL;
52b66862f7SPavel Emelyanov }
53b66862f7SPavel Emelyanov 
task_devcgroup(struct task_struct * task)54f92523e3SPaul Menage static inline struct dev_cgroup *task_devcgroup(struct task_struct *task)
55f92523e3SPaul Menage {
56073219e9STejun Heo 	return css_to_devcgroup(task_css(task, devices_cgrp_id));
57f92523e3SPaul Menage }
58f92523e3SPaul Menage 
5908ce5f16SSerge E. Hallyn /*
60b4046f00SLi Zefan  * called under devcgroup_mutex
6108ce5f16SSerge E. Hallyn  */
dev_exceptions_copy(struct list_head * dest,struct list_head * orig)62db9aeca9SAristeu Rozanski static int dev_exceptions_copy(struct list_head *dest, struct list_head *orig)
6308ce5f16SSerge E. Hallyn {
64db9aeca9SAristeu Rozanski 	struct dev_exception_item *ex, *tmp, *new;
6508ce5f16SSerge E. Hallyn 
664b1c7840STejun Heo 	lockdep_assert_held(&devcgroup_mutex);
674b1c7840STejun Heo 
68db9aeca9SAristeu Rozanski 	list_for_each_entry(ex, orig, list) {
69db9aeca9SAristeu Rozanski 		new = kmemdup(ex, sizeof(*ex), GFP_KERNEL);
7008ce5f16SSerge E. Hallyn 		if (!new)
7108ce5f16SSerge E. Hallyn 			goto free_and_exit;
7208ce5f16SSerge E. Hallyn 		list_add_tail(&new->list, dest);
7308ce5f16SSerge E. Hallyn 	}
7408ce5f16SSerge E. Hallyn 
7508ce5f16SSerge E. Hallyn 	return 0;
7608ce5f16SSerge E. Hallyn 
7708ce5f16SSerge E. Hallyn free_and_exit:
78db9aeca9SAristeu Rozanski 	list_for_each_entry_safe(ex, tmp, dest, list) {
79db9aeca9SAristeu Rozanski 		list_del(&ex->list);
80db9aeca9SAristeu Rozanski 		kfree(ex);
8108ce5f16SSerge E. Hallyn 	}
8208ce5f16SSerge E. Hallyn 	return -ENOMEM;
8308ce5f16SSerge E. Hallyn }
8408ce5f16SSerge E. Hallyn 
dev_exceptions_move(struct list_head * dest,struct list_head * orig)85e68bfbd3SWang Weiyang static void dev_exceptions_move(struct list_head *dest, struct list_head *orig)
86e68bfbd3SWang Weiyang {
87e68bfbd3SWang Weiyang 	struct dev_exception_item *ex, *tmp;
88e68bfbd3SWang Weiyang 
89e68bfbd3SWang Weiyang 	lockdep_assert_held(&devcgroup_mutex);
90e68bfbd3SWang Weiyang 
91e68bfbd3SWang Weiyang 	list_for_each_entry_safe(ex, tmp, orig, list) {
92e68bfbd3SWang Weiyang 		list_move_tail(&ex->list, dest);
93e68bfbd3SWang Weiyang 	}
94e68bfbd3SWang Weiyang }
95e68bfbd3SWang Weiyang 
9608ce5f16SSerge E. Hallyn /*
97b4046f00SLi Zefan  * called under devcgroup_mutex
9808ce5f16SSerge E. Hallyn  */
dev_exception_add(struct dev_cgroup * dev_cgroup,struct dev_exception_item * ex)99db9aeca9SAristeu Rozanski static int dev_exception_add(struct dev_cgroup *dev_cgroup,
100db9aeca9SAristeu Rozanski 			     struct dev_exception_item *ex)
10108ce5f16SSerge E. Hallyn {
102db9aeca9SAristeu Rozanski 	struct dev_exception_item *excopy, *walk;
10308ce5f16SSerge E. Hallyn 
1044b1c7840STejun Heo 	lockdep_assert_held(&devcgroup_mutex);
1054b1c7840STejun Heo 
106db9aeca9SAristeu Rozanski 	excopy = kmemdup(ex, sizeof(*ex), GFP_KERNEL);
107db9aeca9SAristeu Rozanski 	if (!excopy)
10808ce5f16SSerge E. Hallyn 		return -ENOMEM;
10908ce5f16SSerge E. Hallyn 
110db9aeca9SAristeu Rozanski 	list_for_each_entry(walk, &dev_cgroup->exceptions, list) {
111db9aeca9SAristeu Rozanski 		if (walk->type != ex->type)
112d1ee2971SPavel Emelyanov 			continue;
113db9aeca9SAristeu Rozanski 		if (walk->major != ex->major)
114d1ee2971SPavel Emelyanov 			continue;
115db9aeca9SAristeu Rozanski 		if (walk->minor != ex->minor)
116d1ee2971SPavel Emelyanov 			continue;
117d1ee2971SPavel Emelyanov 
118db9aeca9SAristeu Rozanski 		walk->access |= ex->access;
119db9aeca9SAristeu Rozanski 		kfree(excopy);
120db9aeca9SAristeu Rozanski 		excopy = NULL;
121d1ee2971SPavel Emelyanov 	}
122d1ee2971SPavel Emelyanov 
123db9aeca9SAristeu Rozanski 	if (excopy != NULL)
124db9aeca9SAristeu Rozanski 		list_add_tail_rcu(&excopy->list, &dev_cgroup->exceptions);
12508ce5f16SSerge E. Hallyn 	return 0;
12608ce5f16SSerge E. Hallyn }
12708ce5f16SSerge E. Hallyn 
12808ce5f16SSerge E. Hallyn /*
129b4046f00SLi Zefan  * called under devcgroup_mutex
13008ce5f16SSerge E. Hallyn  */
dev_exception_rm(struct dev_cgroup * dev_cgroup,struct dev_exception_item * ex)131db9aeca9SAristeu Rozanski static void dev_exception_rm(struct dev_cgroup *dev_cgroup,
132db9aeca9SAristeu Rozanski 			     struct dev_exception_item *ex)
13308ce5f16SSerge E. Hallyn {
134db9aeca9SAristeu Rozanski 	struct dev_exception_item *walk, *tmp;
13508ce5f16SSerge E. Hallyn 
1364b1c7840STejun Heo 	lockdep_assert_held(&devcgroup_mutex);
1374b1c7840STejun Heo 
138db9aeca9SAristeu Rozanski 	list_for_each_entry_safe(walk, tmp, &dev_cgroup->exceptions, list) {
139db9aeca9SAristeu Rozanski 		if (walk->type != ex->type)
14008ce5f16SSerge E. Hallyn 			continue;
141db9aeca9SAristeu Rozanski 		if (walk->major != ex->major)
14208ce5f16SSerge E. Hallyn 			continue;
143db9aeca9SAristeu Rozanski 		if (walk->minor != ex->minor)
14408ce5f16SSerge E. Hallyn 			continue;
14508ce5f16SSerge E. Hallyn 
146db9aeca9SAristeu Rozanski 		walk->access &= ~ex->access;
14708ce5f16SSerge E. Hallyn 		if (!walk->access) {
1484efd1a1bSPavel Emelyanov 			list_del_rcu(&walk->list);
1496034f7e6SLai Jiangshan 			kfree_rcu(walk, rcu);
15008ce5f16SSerge E. Hallyn 		}
15108ce5f16SSerge E. Hallyn 	}
15208ce5f16SSerge E. Hallyn }
15308ce5f16SSerge E. Hallyn 
__dev_exception_clean(struct dev_cgroup * dev_cgroup)15453eb8c82SJerry Snitselaar static void __dev_exception_clean(struct dev_cgroup *dev_cgroup)
15553eb8c82SJerry Snitselaar {
15653eb8c82SJerry Snitselaar 	struct dev_exception_item *ex, *tmp;
15753eb8c82SJerry Snitselaar 
15853eb8c82SJerry Snitselaar 	list_for_each_entry_safe(ex, tmp, &dev_cgroup->exceptions, list) {
15953eb8c82SJerry Snitselaar 		list_del_rcu(&ex->list);
16053eb8c82SJerry Snitselaar 		kfree_rcu(ex, rcu);
16153eb8c82SJerry Snitselaar 	}
16253eb8c82SJerry Snitselaar }
16353eb8c82SJerry Snitselaar 
164868539a3SAristeu Rozanski /**
165db9aeca9SAristeu Rozanski  * dev_exception_clean - frees all entries of the exception list
166db9aeca9SAristeu Rozanski  * @dev_cgroup: dev_cgroup with the exception list to be cleaned
167868539a3SAristeu Rozanski  *
168868539a3SAristeu Rozanski  * called under devcgroup_mutex
169868539a3SAristeu Rozanski  */
dev_exception_clean(struct dev_cgroup * dev_cgroup)170db9aeca9SAristeu Rozanski static void dev_exception_clean(struct dev_cgroup *dev_cgroup)
171868539a3SAristeu Rozanski {
1724b1c7840STejun Heo 	lockdep_assert_held(&devcgroup_mutex);
1734b1c7840STejun Heo 
17453eb8c82SJerry Snitselaar 	__dev_exception_clean(dev_cgroup);
175868539a3SAristeu Rozanski }
176868539a3SAristeu Rozanski 
is_devcg_online(const struct dev_cgroup * devcg)177bd2953ebSAristeu Rozanski static inline bool is_devcg_online(const struct dev_cgroup *devcg)
178bd2953ebSAristeu Rozanski {
179bd2953ebSAristeu Rozanski 	return (devcg->behavior != DEVCG_DEFAULT_NONE);
180bd2953ebSAristeu Rozanski }
181bd2953ebSAristeu Rozanski 
1821909554cSAristeu Rozanski /**
1831909554cSAristeu Rozanski  * devcgroup_online - initializes devcgroup's behavior and exceptions based on
1841909554cSAristeu Rozanski  * 		      parent's
185eb95419bSTejun Heo  * @css: css getting online
1861909554cSAristeu Rozanski  * returns 0 in case of success, error code otherwise
1871909554cSAristeu Rozanski  */
devcgroup_online(struct cgroup_subsys_state * css)188eb95419bSTejun Heo static int devcgroup_online(struct cgroup_subsys_state *css)
1891909554cSAristeu Rozanski {
190eb95419bSTejun Heo 	struct dev_cgroup *dev_cgroup = css_to_devcgroup(css);
1915c9d535bSTejun Heo 	struct dev_cgroup *parent_dev_cgroup = css_to_devcgroup(css->parent);
1921909554cSAristeu Rozanski 	int ret = 0;
1931909554cSAristeu Rozanski 
1941909554cSAristeu Rozanski 	mutex_lock(&devcgroup_mutex);
1951909554cSAristeu Rozanski 
1961909554cSAristeu Rozanski 	if (parent_dev_cgroup == NULL)
1971909554cSAristeu Rozanski 		dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW;
1981909554cSAristeu Rozanski 	else {
1991909554cSAristeu Rozanski 		ret = dev_exceptions_copy(&dev_cgroup->exceptions,
2001909554cSAristeu Rozanski 					  &parent_dev_cgroup->exceptions);
2011909554cSAristeu Rozanski 		if (!ret)
2021909554cSAristeu Rozanski 			dev_cgroup->behavior = parent_dev_cgroup->behavior;
2031909554cSAristeu Rozanski 	}
2041909554cSAristeu Rozanski 	mutex_unlock(&devcgroup_mutex);
2051909554cSAristeu Rozanski 
2061909554cSAristeu Rozanski 	return ret;
2071909554cSAristeu Rozanski }
2081909554cSAristeu Rozanski 
devcgroup_offline(struct cgroup_subsys_state * css)209eb95419bSTejun Heo static void devcgroup_offline(struct cgroup_subsys_state *css)
2101909554cSAristeu Rozanski {
211eb95419bSTejun Heo 	struct dev_cgroup *dev_cgroup = css_to_devcgroup(css);
2121909554cSAristeu Rozanski 
2131909554cSAristeu Rozanski 	mutex_lock(&devcgroup_mutex);
2141909554cSAristeu Rozanski 	dev_cgroup->behavior = DEVCG_DEFAULT_NONE;
2151909554cSAristeu Rozanski 	mutex_unlock(&devcgroup_mutex);
2161909554cSAristeu Rozanski }
2171909554cSAristeu Rozanski 
21808ce5f16SSerge E. Hallyn /*
219f89f8e16SKamalesh Babulal  * called from kernel/cgroup/cgroup.c with cgroup_lock() held.
22008ce5f16SSerge E. Hallyn  */
221eb95419bSTejun Heo static struct cgroup_subsys_state *
devcgroup_css_alloc(struct cgroup_subsys_state * parent_css)222eb95419bSTejun Heo devcgroup_css_alloc(struct cgroup_subsys_state *parent_css)
22308ce5f16SSerge E. Hallyn {
2241909554cSAristeu Rozanski 	struct dev_cgroup *dev_cgroup;
22508ce5f16SSerge E. Hallyn 
22608ce5f16SSerge E. Hallyn 	dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL);
22708ce5f16SSerge E. Hallyn 	if (!dev_cgroup)
22808ce5f16SSerge E. Hallyn 		return ERR_PTR(-ENOMEM);
229db9aeca9SAristeu Rozanski 	INIT_LIST_HEAD(&dev_cgroup->exceptions);
2301909554cSAristeu Rozanski 	dev_cgroup->behavior = DEVCG_DEFAULT_NONE;
23108ce5f16SSerge E. Hallyn 
23208ce5f16SSerge E. Hallyn 	return &dev_cgroup->css;
23308ce5f16SSerge E. Hallyn }
23408ce5f16SSerge E. Hallyn 
devcgroup_css_free(struct cgroup_subsys_state * css)235eb95419bSTejun Heo static void devcgroup_css_free(struct cgroup_subsys_state *css)
23608ce5f16SSerge E. Hallyn {
237eb95419bSTejun Heo 	struct dev_cgroup *dev_cgroup = css_to_devcgroup(css);
23808ce5f16SSerge E. Hallyn 
23953eb8c82SJerry Snitselaar 	__dev_exception_clean(dev_cgroup);
24008ce5f16SSerge E. Hallyn 	kfree(dev_cgroup);
24108ce5f16SSerge E. Hallyn }
24208ce5f16SSerge E. Hallyn 
24308ce5f16SSerge E. Hallyn #define DEVCG_ALLOW 1
24408ce5f16SSerge E. Hallyn #define DEVCG_DENY 2
24529486df3SSerge E. Hallyn #define DEVCG_LIST 3
24629486df3SSerge E. Hallyn 
24717d213f8SLi Zefan #define MAJMINLEN 13
24829486df3SSerge E. Hallyn #define ACCLEN 4
24908ce5f16SSerge E. Hallyn 
set_access(char * acc,short access)25008ce5f16SSerge E. Hallyn static void set_access(char *acc, short access)
25108ce5f16SSerge E. Hallyn {
25208ce5f16SSerge E. Hallyn 	int idx = 0;
25329486df3SSerge E. Hallyn 	memset(acc, 0, ACCLEN);
25467e306fdSRoman Gushchin 	if (access & DEVCG_ACC_READ)
25508ce5f16SSerge E. Hallyn 		acc[idx++] = 'r';
25667e306fdSRoman Gushchin 	if (access & DEVCG_ACC_WRITE)
25708ce5f16SSerge E. Hallyn 		acc[idx++] = 'w';
25867e306fdSRoman Gushchin 	if (access & DEVCG_ACC_MKNOD)
25908ce5f16SSerge E. Hallyn 		acc[idx++] = 'm';
26008ce5f16SSerge E. Hallyn }
26108ce5f16SSerge E. Hallyn 
type_to_char(short type)26208ce5f16SSerge E. Hallyn static char type_to_char(short type)
26308ce5f16SSerge E. Hallyn {
26467e306fdSRoman Gushchin 	if (type == DEVCG_DEV_ALL)
26508ce5f16SSerge E. Hallyn 		return 'a';
26667e306fdSRoman Gushchin 	if (type == DEVCG_DEV_CHAR)
26708ce5f16SSerge E. Hallyn 		return 'c';
26867e306fdSRoman Gushchin 	if (type == DEVCG_DEV_BLOCK)
26908ce5f16SSerge E. Hallyn 		return 'b';
27008ce5f16SSerge E. Hallyn 	return 'X';
27108ce5f16SSerge E. Hallyn }
27208ce5f16SSerge E. Hallyn 
set_majmin(char * str,unsigned m)27329486df3SSerge E. Hallyn static void set_majmin(char *str, unsigned m)
27408ce5f16SSerge E. Hallyn {
27508ce5f16SSerge E. Hallyn 	if (m == ~0)
2767759fc9dSLi Zefan 		strcpy(str, "*");
27708ce5f16SSerge E. Hallyn 	else
2787759fc9dSLi Zefan 		sprintf(str, "%u", m);
27908ce5f16SSerge E. Hallyn }
28008ce5f16SSerge E. Hallyn 
devcgroup_seq_show(struct seq_file * m,void * v)2812da8ca82STejun Heo static int devcgroup_seq_show(struct seq_file *m, void *v)
28208ce5f16SSerge E. Hallyn {
2832da8ca82STejun Heo 	struct dev_cgroup *devcgroup = css_to_devcgroup(seq_css(m));
284db9aeca9SAristeu Rozanski 	struct dev_exception_item *ex;
28529486df3SSerge E. Hallyn 	char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN];
28608ce5f16SSerge E. Hallyn 
2874efd1a1bSPavel Emelyanov 	rcu_read_lock();
288ad676077SAristeu Rozanski 	/*
289ad676077SAristeu Rozanski 	 * To preserve the compatibility:
290ad676077SAristeu Rozanski 	 * - Only show the "all devices" when the default policy is to allow
291ad676077SAristeu Rozanski 	 * - List the exceptions in case the default policy is to deny
292ad676077SAristeu Rozanski 	 * This way, the file remains as a "whitelist of devices"
293ad676077SAristeu Rozanski 	 */
2945b7aa7d5SAristeu Rozanski 	if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) {
29567e306fdSRoman Gushchin 		set_access(acc, DEVCG_ACC_MASK);
296ad676077SAristeu Rozanski 		set_majmin(maj, ~0);
297ad676077SAristeu Rozanski 		set_majmin(min, ~0);
29867e306fdSRoman Gushchin 		seq_printf(m, "%c %s:%s %s\n", type_to_char(DEVCG_DEV_ALL),
299ad676077SAristeu Rozanski 			   maj, min, acc);
300ad676077SAristeu Rozanski 	} else {
301db9aeca9SAristeu Rozanski 		list_for_each_entry_rcu(ex, &devcgroup->exceptions, list) {
302db9aeca9SAristeu Rozanski 			set_access(acc, ex->access);
303db9aeca9SAristeu Rozanski 			set_majmin(maj, ex->major);
304db9aeca9SAristeu Rozanski 			set_majmin(min, ex->minor);
305db9aeca9SAristeu Rozanski 			seq_printf(m, "%c %s:%s %s\n", type_to_char(ex->type),
30629486df3SSerge E. Hallyn 				   maj, min, acc);
30708ce5f16SSerge E. Hallyn 		}
308ad676077SAristeu Rozanski 	}
3094efd1a1bSPavel Emelyanov 	rcu_read_unlock();
31008ce5f16SSerge E. Hallyn 
31129486df3SSerge E. Hallyn 	return 0;
31208ce5f16SSerge E. Hallyn }
31308ce5f16SSerge E. Hallyn 
314ad676077SAristeu Rozanski /**
315f5f3cf6fSAristeu Rozanski  * match_exception	- iterates the exception list trying to find a complete match
31679d71974SAristeu Rozanski  * @exceptions: list of exceptions
31767e306fdSRoman Gushchin  * @type: device type (DEVCG_DEV_BLOCK or DEVCG_DEV_CHAR)
31879d71974SAristeu Rozanski  * @major: device file major number, ~0 to match all
31979d71974SAristeu Rozanski  * @minor: device file minor number, ~0 to match all
32067e306fdSRoman Gushchin  * @access: permission mask (DEVCG_ACC_READ, DEVCG_ACC_WRITE, DEVCG_ACC_MKNOD)
32179d71974SAristeu Rozanski  *
322f5f3cf6fSAristeu Rozanski  * It is considered a complete match if an exception is found that will
323f5f3cf6fSAristeu Rozanski  * contain the entire range of provided parameters.
324f5f3cf6fSAristeu Rozanski  *
325f5f3cf6fSAristeu Rozanski  * Return: true in case it matches an exception completely
32679d71974SAristeu Rozanski  */
match_exception(struct list_head * exceptions,short type,u32 major,u32 minor,short access)32779d71974SAristeu Rozanski static bool match_exception(struct list_head *exceptions, short type,
32879d71974SAristeu Rozanski 			    u32 major, u32 minor, short access)
32979d71974SAristeu Rozanski {
33079d71974SAristeu Rozanski 	struct dev_exception_item *ex;
33179d71974SAristeu Rozanski 
33279d71974SAristeu Rozanski 	list_for_each_entry_rcu(ex, exceptions, list) {
33367e306fdSRoman Gushchin 		if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK))
33479d71974SAristeu Rozanski 			continue;
33567e306fdSRoman Gushchin 		if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR))
33679d71974SAristeu Rozanski 			continue;
33779d71974SAristeu Rozanski 		if (ex->major != ~0 && ex->major != major)
33879d71974SAristeu Rozanski 			continue;
33979d71974SAristeu Rozanski 		if (ex->minor != ~0 && ex->minor != minor)
34079d71974SAristeu Rozanski 			continue;
34179d71974SAristeu Rozanski 		/* provided access cannot have more than the exception rule */
34279d71974SAristeu Rozanski 		if (access & (~ex->access))
34379d71974SAristeu Rozanski 			continue;
34479d71974SAristeu Rozanski 		return true;
34579d71974SAristeu Rozanski 	}
34679d71974SAristeu Rozanski 	return false;
34779d71974SAristeu Rozanski }
34879d71974SAristeu Rozanski 
34979d71974SAristeu Rozanski /**
350f5f3cf6fSAristeu Rozanski  * match_exception_partial - iterates the exception list trying to find a partial match
35179d71974SAristeu Rozanski  * @exceptions: list of exceptions
35267e306fdSRoman Gushchin  * @type: device type (DEVCG_DEV_BLOCK or DEVCG_DEV_CHAR)
35379d71974SAristeu Rozanski  * @major: device file major number, ~0 to match all
35479d71974SAristeu Rozanski  * @minor: device file minor number, ~0 to match all
35567e306fdSRoman Gushchin  * @access: permission mask (DEVCG_ACC_READ, DEVCG_ACC_WRITE, DEVCG_ACC_MKNOD)
35679d71974SAristeu Rozanski  *
357f5f3cf6fSAristeu Rozanski  * It is considered a partial match if an exception's range is found to
358f5f3cf6fSAristeu Rozanski  * contain *any* of the devices specified by provided parameters. This is
359f5f3cf6fSAristeu Rozanski  * used to make sure no extra access is being granted that is forbidden by
360f5f3cf6fSAristeu Rozanski  * any of the exception list.
361f5f3cf6fSAristeu Rozanski  *
362f5f3cf6fSAristeu Rozanski  * Return: true in case the provided range mat matches an exception completely
36379d71974SAristeu Rozanski  */
match_exception_partial(struct list_head * exceptions,short type,u32 major,u32 minor,short access)36479d71974SAristeu Rozanski static bool match_exception_partial(struct list_head *exceptions, short type,
36579d71974SAristeu Rozanski 				    u32 major, u32 minor, short access)
36679d71974SAristeu Rozanski {
36779d71974SAristeu Rozanski 	struct dev_exception_item *ex;
36879d71974SAristeu Rozanski 
369bc62d68eSAmol Grover 	list_for_each_entry_rcu(ex, exceptions, list,
370bc62d68eSAmol Grover 				lockdep_is_held(&devcgroup_mutex)) {
37167e306fdSRoman Gushchin 		if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK))
37279d71974SAristeu Rozanski 			continue;
37367e306fdSRoman Gushchin 		if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR))
37479d71974SAristeu Rozanski 			continue;
37579d71974SAristeu Rozanski 		/*
37679d71974SAristeu Rozanski 		 * We must be sure that both the exception and the provided
37779d71974SAristeu Rozanski 		 * range aren't masking all devices
37879d71974SAristeu Rozanski 		 */
37979d71974SAristeu Rozanski 		if (ex->major != ~0 && major != ~0 && ex->major != major)
38079d71974SAristeu Rozanski 			continue;
38179d71974SAristeu Rozanski 		if (ex->minor != ~0 && minor != ~0 && ex->minor != minor)
38279d71974SAristeu Rozanski 			continue;
38379d71974SAristeu Rozanski 		/*
38479d71974SAristeu Rozanski 		 * In order to make sure the provided range isn't matching
38579d71974SAristeu Rozanski 		 * an exception, all its access bits shouldn't match the
38679d71974SAristeu Rozanski 		 * exception's access bits
38779d71974SAristeu Rozanski 		 */
38879d71974SAristeu Rozanski 		if (!(access & ex->access))
38979d71974SAristeu Rozanski 			continue;
39079d71974SAristeu Rozanski 		return true;
39179d71974SAristeu Rozanski 	}
39279d71974SAristeu Rozanski 	return false;
39379d71974SAristeu Rozanski }
39479d71974SAristeu Rozanski 
39579d71974SAristeu Rozanski /**
396f5f3cf6fSAristeu Rozanski  * verify_new_ex - verifies if a new exception is allowed by parent cgroup's permissions
397ad676077SAristeu Rozanski  * @dev_cgroup: dev cgroup to be tested against
398db9aeca9SAristeu Rozanski  * @refex: new exception
39979d71974SAristeu Rozanski  * @behavior: behavior of the exception's dev_cgroup
400f5f3cf6fSAristeu Rozanski  *
401f5f3cf6fSAristeu Rozanski  * This is used to make sure a child cgroup won't have more privileges
402f5f3cf6fSAristeu Rozanski  * than its parent
40308ce5f16SSerge E. Hallyn  */
verify_new_ex(struct dev_cgroup * dev_cgroup,struct dev_exception_item * refex,enum devcg_behavior behavior)40479d71974SAristeu Rozanski static bool verify_new_ex(struct dev_cgroup *dev_cgroup,
405c39a2a30SAristeu Rozanski 		          struct dev_exception_item *refex,
406c39a2a30SAristeu Rozanski 		          enum devcg_behavior behavior)
40708ce5f16SSerge E. Hallyn {
408ad676077SAristeu Rozanski 	bool match = false;
40908ce5f16SSerge E. Hallyn 
410f78f5b90SPaul E. McKenney 	RCU_LOCKDEP_WARN(!rcu_read_lock_held() &&
411dc3a04d5SPaul E. McKenney 			 !lockdep_is_held(&devcgroup_mutex),
41279d71974SAristeu Rozanski 			 "device_cgroup:verify_new_ex called without proper synchronization");
413ad676077SAristeu Rozanski 
414c39a2a30SAristeu Rozanski 	if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) {
415c39a2a30SAristeu Rozanski 		if (behavior == DEVCG_DEFAULT_ALLOW) {
41679d71974SAristeu Rozanski 			/*
41779d71974SAristeu Rozanski 			 * new exception in the child doesn't matter, only
41879d71974SAristeu Rozanski 			 * adding extra restrictions
41979d71974SAristeu Rozanski 			 */
42026898fdfSAristeu Rozanski 			return true;
42126898fdfSAristeu Rozanski 		} else {
422c39a2a30SAristeu Rozanski 			/*
42379d71974SAristeu Rozanski 			 * new exception in the child will add more devices
424*4432b507SPaul Moore 			 * that can be accessed, so it can't match any of
42579d71974SAristeu Rozanski 			 * parent's exceptions, even slightly
426c39a2a30SAristeu Rozanski 			 */
42779d71974SAristeu Rozanski 			match = match_exception_partial(&dev_cgroup->exceptions,
42879d71974SAristeu Rozanski 							refex->type,
42979d71974SAristeu Rozanski 							refex->major,
43079d71974SAristeu Rozanski 							refex->minor,
43179d71974SAristeu Rozanski 							refex->access);
43279d71974SAristeu Rozanski 
43379d71974SAristeu Rozanski 			if (match)
434c39a2a30SAristeu Rozanski 				return false;
43526898fdfSAristeu Rozanski 			return true;
43626898fdfSAristeu Rozanski 		}
437c39a2a30SAristeu Rozanski 	} else {
43879d71974SAristeu Rozanski 		/*
43979d71974SAristeu Rozanski 		 * Only behavior == DEVCG_DEFAULT_DENY allowed here, therefore
44079d71974SAristeu Rozanski 		 * the new exception will add access to more devices and must
44179d71974SAristeu Rozanski 		 * be contained completely in an parent's exception to be
44279d71974SAristeu Rozanski 		 * allowed
44379d71974SAristeu Rozanski 		 */
44479d71974SAristeu Rozanski 		match = match_exception(&dev_cgroup->exceptions, refex->type,
44579d71974SAristeu Rozanski 					refex->major, refex->minor,
44679d71974SAristeu Rozanski 					refex->access);
44779d71974SAristeu Rozanski 
448c39a2a30SAristeu Rozanski 		if (match)
449c39a2a30SAristeu Rozanski 			/* parent has an exception that matches the proposed */
450c39a2a30SAristeu Rozanski 			return true;
451c39a2a30SAristeu Rozanski 		else
452c39a2a30SAristeu Rozanski 			return false;
453c39a2a30SAristeu Rozanski 	}
45426898fdfSAristeu Rozanski 	return false;
45508ce5f16SSerge E. Hallyn }
45608ce5f16SSerge E. Hallyn 
45708ce5f16SSerge E. Hallyn /*
45808ce5f16SSerge E. Hallyn  * parent_has_perm:
459db9aeca9SAristeu Rozanski  * when adding a new allow rule to a device exception list, the rule
46008ce5f16SSerge E. Hallyn  * must be allowed in the parent device
46108ce5f16SSerge E. Hallyn  */
parent_has_perm(struct dev_cgroup * childcg,struct dev_exception_item * ex)462f92523e3SPaul Menage static int parent_has_perm(struct dev_cgroup *childcg,
463db9aeca9SAristeu Rozanski 				  struct dev_exception_item *ex)
46408ce5f16SSerge E. Hallyn {
4655c9d535bSTejun Heo 	struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent);
46608ce5f16SSerge E. Hallyn 
46763876986STejun Heo 	if (!parent)
46808ce5f16SSerge E. Hallyn 		return 1;
46979d71974SAristeu Rozanski 	return verify_new_ex(parent, ex, childcg->behavior);
47008ce5f16SSerge E. Hallyn }
47108ce5f16SSerge E. Hallyn 
4724cef7299SAristeu Rozanski /**
473d2c2b11cSAristeu Rozanski  * parent_allows_removal - verify if it's ok to remove an exception
474d2c2b11cSAristeu Rozanski  * @childcg: child cgroup from where the exception will be removed
475d2c2b11cSAristeu Rozanski  * @ex: exception being removed
476d2c2b11cSAristeu Rozanski  *
477d2c2b11cSAristeu Rozanski  * When removing an exception in cgroups with default ALLOW policy, it must
478d2c2b11cSAristeu Rozanski  * be checked if removing it will give the child cgroup more access than the
479d2c2b11cSAristeu Rozanski  * parent.
480d2c2b11cSAristeu Rozanski  *
481d2c2b11cSAristeu Rozanski  * Return: true if it's ok to remove exception, false otherwise
482d2c2b11cSAristeu Rozanski  */
parent_allows_removal(struct dev_cgroup * childcg,struct dev_exception_item * ex)483d2c2b11cSAristeu Rozanski static bool parent_allows_removal(struct dev_cgroup *childcg,
484d2c2b11cSAristeu Rozanski 				  struct dev_exception_item *ex)
485d2c2b11cSAristeu Rozanski {
4865c9d535bSTejun Heo 	struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent);
487d2c2b11cSAristeu Rozanski 
488d2c2b11cSAristeu Rozanski 	if (!parent)
489d2c2b11cSAristeu Rozanski 		return true;
490d2c2b11cSAristeu Rozanski 
491d2c2b11cSAristeu Rozanski 	/* It's always allowed to remove access to devices */
492d2c2b11cSAristeu Rozanski 	if (childcg->behavior == DEVCG_DEFAULT_DENY)
493d2c2b11cSAristeu Rozanski 		return true;
494d2c2b11cSAristeu Rozanski 
495d2c2b11cSAristeu Rozanski 	/*
496d2c2b11cSAristeu Rozanski 	 * Make sure you're not removing part or a whole exception existing in
497d2c2b11cSAristeu Rozanski 	 * the parent cgroup
498d2c2b11cSAristeu Rozanski 	 */
499d2c2b11cSAristeu Rozanski 	return !match_exception_partial(&parent->exceptions, ex->type,
500d2c2b11cSAristeu Rozanski 					ex->major, ex->minor, ex->access);
501d2c2b11cSAristeu Rozanski }
502d2c2b11cSAristeu Rozanski 
503d2c2b11cSAristeu Rozanski /**
5044cef7299SAristeu Rozanski  * may_allow_all - checks if it's possible to change the behavior to
5054cef7299SAristeu Rozanski  *		   allow based on parent's rules.
5064cef7299SAristeu Rozanski  * @parent: device cgroup's parent
5074cef7299SAristeu Rozanski  * returns: != 0 in case it's allowed, 0 otherwise
5084cef7299SAristeu Rozanski  */
may_allow_all(struct dev_cgroup * parent)5094cef7299SAristeu Rozanski static inline int may_allow_all(struct dev_cgroup *parent)
5104cef7299SAristeu Rozanski {
51164e10477SAristeu Rozanski 	if (!parent)
51264e10477SAristeu Rozanski 		return 1;
5134cef7299SAristeu Rozanski 	return parent->behavior == DEVCG_DEFAULT_ALLOW;
5144cef7299SAristeu Rozanski }
5154cef7299SAristeu Rozanski 
516bd2953ebSAristeu Rozanski /**
517bd2953ebSAristeu Rozanski  * revalidate_active_exceptions - walks through the active exception list and
518bd2953ebSAristeu Rozanski  * 				  revalidates the exceptions based on parent's
519bd2953ebSAristeu Rozanski  * 				  behavior and exceptions. The exceptions that
520bd2953ebSAristeu Rozanski  * 				  are no longer valid will be removed.
521bd2953ebSAristeu Rozanski  * 				  Called with devcgroup_mutex held.
522bd2953ebSAristeu Rozanski  * @devcg: cgroup which exceptions will be checked
523bd2953ebSAristeu Rozanski  *
524bd2953ebSAristeu Rozanski  * This is one of the three key functions for hierarchy implementation.
525bd2953ebSAristeu Rozanski  * This function is responsible for re-evaluating all the cgroup's active
526bd2953ebSAristeu Rozanski  * exceptions due to a parent's exception change.
527da82c92fSMauro Carvalho Chehab  * Refer to Documentation/admin-guide/cgroup-v1/devices.rst for more details.
528bd2953ebSAristeu Rozanski  */
revalidate_active_exceptions(struct dev_cgroup * devcg)529bd2953ebSAristeu Rozanski static void revalidate_active_exceptions(struct dev_cgroup *devcg)
530bd2953ebSAristeu Rozanski {
531bd2953ebSAristeu Rozanski 	struct dev_exception_item *ex;
532bd2953ebSAristeu Rozanski 	struct list_head *this, *tmp;
533bd2953ebSAristeu Rozanski 
534bd2953ebSAristeu Rozanski 	list_for_each_safe(this, tmp, &devcg->exceptions) {
535bd2953ebSAristeu Rozanski 		ex = container_of(this, struct dev_exception_item, list);
536bd2953ebSAristeu Rozanski 		if (!parent_has_perm(devcg, ex))
537bd2953ebSAristeu Rozanski 			dev_exception_rm(devcg, ex);
538bd2953ebSAristeu Rozanski 	}
539bd2953ebSAristeu Rozanski }
540bd2953ebSAristeu Rozanski 
541bd2953ebSAristeu Rozanski /**
542bd2953ebSAristeu Rozanski  * propagate_exception - propagates a new exception to the children
543bd2953ebSAristeu Rozanski  * @devcg_root: device cgroup that added a new exception
544bd2953ebSAristeu Rozanski  * @ex: new exception to be propagated
545bd2953ebSAristeu Rozanski  *
546bd2953ebSAristeu Rozanski  * returns: 0 in case of success, != 0 in case of error
547bd2953ebSAristeu Rozanski  */
propagate_exception(struct dev_cgroup * devcg_root,struct dev_exception_item * ex)548bd2953ebSAristeu Rozanski static int propagate_exception(struct dev_cgroup *devcg_root,
549bd2953ebSAristeu Rozanski 			       struct dev_exception_item *ex)
550bd2953ebSAristeu Rozanski {
551492eb21bSTejun Heo 	struct cgroup_subsys_state *pos;
552bd2953ebSAristeu Rozanski 	int rc = 0;
553bd2953ebSAristeu Rozanski 
554d591fb56STejun Heo 	rcu_read_lock();
555bd2953ebSAristeu Rozanski 
556492eb21bSTejun Heo 	css_for_each_descendant_pre(pos, &devcg_root->css) {
557492eb21bSTejun Heo 		struct dev_cgroup *devcg = css_to_devcgroup(pos);
558d591fb56STejun Heo 
559d591fb56STejun Heo 		/*
560d591fb56STejun Heo 		 * Because devcgroup_mutex is held, no devcg will become
561d591fb56STejun Heo 		 * online or offline during the tree walk (see on/offline
562d591fb56STejun Heo 		 * methods), and online ones are safe to access outside RCU
563d591fb56STejun Heo 		 * read lock without bumping refcnt.
564d591fb56STejun Heo 		 */
565bd8815a6STejun Heo 		if (pos == &devcg_root->css || !is_devcg_online(devcg))
566d591fb56STejun Heo 			continue;
567d591fb56STejun Heo 
568d591fb56STejun Heo 		rcu_read_unlock();
569bd2953ebSAristeu Rozanski 
570bd2953ebSAristeu Rozanski 		/*
571bd2953ebSAristeu Rozanski 		 * in case both root's behavior and devcg is allow, a new
572bd2953ebSAristeu Rozanski 		 * restriction means adding to the exception list
573bd2953ebSAristeu Rozanski 		 */
574bd2953ebSAristeu Rozanski 		if (devcg_root->behavior == DEVCG_DEFAULT_ALLOW &&
575bd2953ebSAristeu Rozanski 		    devcg->behavior == DEVCG_DEFAULT_ALLOW) {
576bd2953ebSAristeu Rozanski 			rc = dev_exception_add(devcg, ex);
577bd2953ebSAristeu Rozanski 			if (rc)
5780fcc4c8cSJann Horn 				return rc;
579bd2953ebSAristeu Rozanski 		} else {
580bd2953ebSAristeu Rozanski 			/*
581bd2953ebSAristeu Rozanski 			 * in the other possible cases:
582bd2953ebSAristeu Rozanski 			 * root's behavior: allow, devcg's: deny
583bd2953ebSAristeu Rozanski 			 * root's behavior: deny, devcg's: deny
584bd2953ebSAristeu Rozanski 			 * the exception will be removed
585bd2953ebSAristeu Rozanski 			 */
586bd2953ebSAristeu Rozanski 			dev_exception_rm(devcg, ex);
587bd2953ebSAristeu Rozanski 		}
588bd2953ebSAristeu Rozanski 		revalidate_active_exceptions(devcg);
589bd2953ebSAristeu Rozanski 
590d591fb56STejun Heo 		rcu_read_lock();
591bd2953ebSAristeu Rozanski 	}
592d591fb56STejun Heo 
593d591fb56STejun Heo 	rcu_read_unlock();
594bd2953ebSAristeu Rozanski 	return rc;
595bd2953ebSAristeu Rozanski }
596bd2953ebSAristeu Rozanski 
59708ce5f16SSerge E. Hallyn /*
598db9aeca9SAristeu Rozanski  * Modify the exception list using allow/deny rules.
59908ce5f16SSerge E. Hallyn  * CAP_SYS_ADMIN is needed for this.  It's at least separate from CAP_MKNOD
60008ce5f16SSerge E. Hallyn  * so we can give a container CAP_MKNOD to let it create devices but not
601db9aeca9SAristeu Rozanski  * modify the exception list.
60208ce5f16SSerge E. Hallyn  * It seems likely we'll want to add a CAP_CONTAINER capability to allow
60308ce5f16SSerge E. Hallyn  * us to also grant CAP_SYS_ADMIN to containers without giving away the
604db9aeca9SAristeu Rozanski  * device exception list controls, but for now we'll stick with CAP_SYS_ADMIN
60508ce5f16SSerge E. Hallyn  *
60608ce5f16SSerge E. Hallyn  * Taking rules away is always allowed (given CAP_SYS_ADMIN).  Granting
60708ce5f16SSerge E. Hallyn  * new access is only allowed if you're in the top-level cgroup, or your
60808ce5f16SSerge E. Hallyn  * parent cgroup has the access you're asking for.
60908ce5f16SSerge E. Hallyn  */
devcgroup_update_access(struct dev_cgroup * devcgroup,int filetype,char * buffer)610f92523e3SPaul Menage static int devcgroup_update_access(struct dev_cgroup *devcgroup,
6114d3bb511STejun Heo 				   int filetype, char *buffer)
61208ce5f16SSerge E. Hallyn {
613f92523e3SPaul Menage 	const char *b;
61426fd8405SAristeu Rozanski 	char temp[12];		/* 11 + 1 characters needed for a u32 */
615c39a2a30SAristeu Rozanski 	int count, rc = 0;
616db9aeca9SAristeu Rozanski 	struct dev_exception_item ex;
6175c9d535bSTejun Heo 	struct dev_cgroup *parent = css_to_devcgroup(devcgroup->css.parent);
618e68bfbd3SWang Weiyang 	struct dev_cgroup tmp_devcgrp;
61908ce5f16SSerge E. Hallyn 
62008ce5f16SSerge E. Hallyn 	if (!capable(CAP_SYS_ADMIN))
62108ce5f16SSerge E. Hallyn 		return -EPERM;
62208ce5f16SSerge E. Hallyn 
623db9aeca9SAristeu Rozanski 	memset(&ex, 0, sizeof(ex));
624e68bfbd3SWang Weiyang 	memset(&tmp_devcgrp, 0, sizeof(tmp_devcgrp));
62508ce5f16SSerge E. Hallyn 	b = buffer;
62608ce5f16SSerge E. Hallyn 
62708ce5f16SSerge E. Hallyn 	switch (*b) {
62808ce5f16SSerge E. Hallyn 	case 'a':
629ad676077SAristeu Rozanski 		switch (filetype) {
630ad676077SAristeu Rozanski 		case DEVCG_ALLOW:
6317a3bb24fSTejun Heo 			if (css_has_online_children(&devcgroup->css))
632bd2953ebSAristeu Rozanski 				return -EINVAL;
633bd2953ebSAristeu Rozanski 
6344cef7299SAristeu Rozanski 			if (!may_allow_all(parent))
635ad676077SAristeu Rozanski 				return -EPERM;
636e68bfbd3SWang Weiyang 			if (!parent) {
63764e10477SAristeu Rozanski 				devcgroup->behavior = DEVCG_DEFAULT_ALLOW;
638e68bfbd3SWang Weiyang 				dev_exception_clean(devcgroup);
63964e10477SAristeu Rozanski 				break;
640e68bfbd3SWang Weiyang 			}
64164e10477SAristeu Rozanski 
642e68bfbd3SWang Weiyang 			INIT_LIST_HEAD(&tmp_devcgrp.exceptions);
643e68bfbd3SWang Weiyang 			rc = dev_exceptions_copy(&tmp_devcgrp.exceptions,
644e68bfbd3SWang Weiyang 						 &devcgroup->exceptions);
6454cef7299SAristeu Rozanski 			if (rc)
6464cef7299SAristeu Rozanski 				return rc;
647e68bfbd3SWang Weiyang 			dev_exception_clean(devcgroup);
648e68bfbd3SWang Weiyang 			rc = dev_exceptions_copy(&devcgroup->exceptions,
649e68bfbd3SWang Weiyang 						 &parent->exceptions);
650e68bfbd3SWang Weiyang 			if (rc) {
651e68bfbd3SWang Weiyang 				dev_exceptions_move(&devcgroup->exceptions,
652e68bfbd3SWang Weiyang 						    &tmp_devcgrp.exceptions);
653e68bfbd3SWang Weiyang 				return rc;
654e68bfbd3SWang Weiyang 			}
655e68bfbd3SWang Weiyang 			devcgroup->behavior = DEVCG_DEFAULT_ALLOW;
656e68bfbd3SWang Weiyang 			dev_exception_clean(&tmp_devcgrp);
657ad676077SAristeu Rozanski 			break;
658ad676077SAristeu Rozanski 		case DEVCG_DENY:
6597a3bb24fSTejun Heo 			if (css_has_online_children(&devcgroup->css))
660bd2953ebSAristeu Rozanski 				return -EINVAL;
661bd2953ebSAristeu Rozanski 
662db9aeca9SAristeu Rozanski 			dev_exception_clean(devcgroup);
6635b7aa7d5SAristeu Rozanski 			devcgroup->behavior = DEVCG_DEFAULT_DENY;
664ad676077SAristeu Rozanski 			break;
665ad676077SAristeu Rozanski 		default:
666ad676077SAristeu Rozanski 			return -EINVAL;
667ad676077SAristeu Rozanski 		}
668ad676077SAristeu Rozanski 		return 0;
66908ce5f16SSerge E. Hallyn 	case 'b':
67067e306fdSRoman Gushchin 		ex.type = DEVCG_DEV_BLOCK;
67108ce5f16SSerge E. Hallyn 		break;
67208ce5f16SSerge E. Hallyn 	case 'c':
67367e306fdSRoman Gushchin 		ex.type = DEVCG_DEV_CHAR;
67408ce5f16SSerge E. Hallyn 		break;
67508ce5f16SSerge E. Hallyn 	default:
676f92523e3SPaul Menage 		return -EINVAL;
67708ce5f16SSerge E. Hallyn 	}
67808ce5f16SSerge E. Hallyn 	b++;
679f92523e3SPaul Menage 	if (!isspace(*b))
680f92523e3SPaul Menage 		return -EINVAL;
68108ce5f16SSerge E. Hallyn 	b++;
68208ce5f16SSerge E. Hallyn 	if (*b == '*') {
683db9aeca9SAristeu Rozanski 		ex.major = ~0;
68408ce5f16SSerge E. Hallyn 		b++;
68508ce5f16SSerge E. Hallyn 	} else if (isdigit(*b)) {
68626fd8405SAristeu Rozanski 		memset(temp, 0, sizeof(temp));
68726fd8405SAristeu Rozanski 		for (count = 0; count < sizeof(temp) - 1; count++) {
68826fd8405SAristeu Rozanski 			temp[count] = *b;
68926fd8405SAristeu Rozanski 			b++;
69026fd8405SAristeu Rozanski 			if (!isdigit(*b))
69126fd8405SAristeu Rozanski 				break;
69226fd8405SAristeu Rozanski 		}
69326fd8405SAristeu Rozanski 		rc = kstrtou32(temp, 10, &ex.major);
69426fd8405SAristeu Rozanski 		if (rc)
69526fd8405SAristeu Rozanski 			return -EINVAL;
69608ce5f16SSerge E. Hallyn 	} else {
697f92523e3SPaul Menage 		return -EINVAL;
69808ce5f16SSerge E. Hallyn 	}
699f92523e3SPaul Menage 	if (*b != ':')
700f92523e3SPaul Menage 		return -EINVAL;
70108ce5f16SSerge E. Hallyn 	b++;
70208ce5f16SSerge E. Hallyn 
70308ce5f16SSerge E. Hallyn 	/* read minor */
70408ce5f16SSerge E. Hallyn 	if (*b == '*') {
705db9aeca9SAristeu Rozanski 		ex.minor = ~0;
70608ce5f16SSerge E. Hallyn 		b++;
70708ce5f16SSerge E. Hallyn 	} else if (isdigit(*b)) {
70826fd8405SAristeu Rozanski 		memset(temp, 0, sizeof(temp));
70926fd8405SAristeu Rozanski 		for (count = 0; count < sizeof(temp) - 1; count++) {
71026fd8405SAristeu Rozanski 			temp[count] = *b;
71126fd8405SAristeu Rozanski 			b++;
71226fd8405SAristeu Rozanski 			if (!isdigit(*b))
71326fd8405SAristeu Rozanski 				break;
71426fd8405SAristeu Rozanski 		}
71526fd8405SAristeu Rozanski 		rc = kstrtou32(temp, 10, &ex.minor);
71626fd8405SAristeu Rozanski 		if (rc)
71726fd8405SAristeu Rozanski 			return -EINVAL;
71808ce5f16SSerge E. Hallyn 	} else {
719f92523e3SPaul Menage 		return -EINVAL;
72008ce5f16SSerge E. Hallyn 	}
721f92523e3SPaul Menage 	if (!isspace(*b))
722f92523e3SPaul Menage 		return -EINVAL;
72308ce5f16SSerge E. Hallyn 	for (b++, count = 0; count < 3; count++, b++) {
72408ce5f16SSerge E. Hallyn 		switch (*b) {
72508ce5f16SSerge E. Hallyn 		case 'r':
72667e306fdSRoman Gushchin 			ex.access |= DEVCG_ACC_READ;
72708ce5f16SSerge E. Hallyn 			break;
72808ce5f16SSerge E. Hallyn 		case 'w':
72967e306fdSRoman Gushchin 			ex.access |= DEVCG_ACC_WRITE;
73008ce5f16SSerge E. Hallyn 			break;
73108ce5f16SSerge E. Hallyn 		case 'm':
73267e306fdSRoman Gushchin 			ex.access |= DEVCG_ACC_MKNOD;
73308ce5f16SSerge E. Hallyn 			break;
73408ce5f16SSerge E. Hallyn 		case '\n':
73508ce5f16SSerge E. Hallyn 		case '\0':
73608ce5f16SSerge E. Hallyn 			count = 3;
73708ce5f16SSerge E. Hallyn 			break;
73808ce5f16SSerge E. Hallyn 		default:
739f92523e3SPaul Menage 			return -EINVAL;
74008ce5f16SSerge E. Hallyn 		}
74108ce5f16SSerge E. Hallyn 	}
74208ce5f16SSerge E. Hallyn 
74308ce5f16SSerge E. Hallyn 	switch (filetype) {
74408ce5f16SSerge E. Hallyn 	case DEVCG_ALLOW:
745ad676077SAristeu Rozanski 		/*
746ad676077SAristeu Rozanski 		 * If the default policy is to allow by default, try to remove
747ad676077SAristeu Rozanski 		 * an matching exception instead. And be silent about it: we
748ad676077SAristeu Rozanski 		 * don't want to break compatibility
749ad676077SAristeu Rozanski 		 */
7505b7aa7d5SAristeu Rozanski 		if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) {
751d2c2b11cSAristeu Rozanski 			/* Check if the parent allows removing it first */
752d2c2b11cSAristeu Rozanski 			if (!parent_allows_removal(devcgroup, &ex))
753d2c2b11cSAristeu Rozanski 				return -EPERM;
754db9aeca9SAristeu Rozanski 			dev_exception_rm(devcgroup, &ex);
755d2c2b11cSAristeu Rozanski 			break;
756ad676077SAristeu Rozanski 		}
757d2c2b11cSAristeu Rozanski 
758d2c2b11cSAristeu Rozanski 		if (!parent_has_perm(devcgroup, &ex))
759d2c2b11cSAristeu Rozanski 			return -EPERM;
760bd2953ebSAristeu Rozanski 		rc = dev_exception_add(devcgroup, &ex);
761bd2953ebSAristeu Rozanski 		break;
76208ce5f16SSerge E. Hallyn 	case DEVCG_DENY:
763ad676077SAristeu Rozanski 		/*
764ad676077SAristeu Rozanski 		 * If the default policy is to deny by default, try to remove
765ad676077SAristeu Rozanski 		 * an matching exception instead. And be silent about it: we
766ad676077SAristeu Rozanski 		 * don't want to break compatibility
767ad676077SAristeu Rozanski 		 */
768bd2953ebSAristeu Rozanski 		if (devcgroup->behavior == DEVCG_DEFAULT_DENY)
769db9aeca9SAristeu Rozanski 			dev_exception_rm(devcgroup, &ex);
770bd2953ebSAristeu Rozanski 		else
771bd2953ebSAristeu Rozanski 			rc = dev_exception_add(devcgroup, &ex);
772bd2953ebSAristeu Rozanski 
773bd2953ebSAristeu Rozanski 		if (rc)
774bd2953ebSAristeu Rozanski 			break;
775bd2953ebSAristeu Rozanski 		/* we only propagate new restrictions */
776bd2953ebSAristeu Rozanski 		rc = propagate_exception(devcgroup, &ex);
777bd2953ebSAristeu Rozanski 		break;
77808ce5f16SSerge E. Hallyn 	default:
779bd2953ebSAristeu Rozanski 		rc = -EINVAL;
780f92523e3SPaul Menage 	}
781bd2953ebSAristeu Rozanski 	return rc;
78208ce5f16SSerge E. Hallyn }
78308ce5f16SSerge E. Hallyn 
devcgroup_access_write(struct kernfs_open_file * of,char * buf,size_t nbytes,loff_t off)784451af504STejun Heo static ssize_t devcgroup_access_write(struct kernfs_open_file *of,
785451af504STejun Heo 				      char *buf, size_t nbytes, loff_t off)
786f92523e3SPaul Menage {
787f92523e3SPaul Menage 	int retval;
788b4046f00SLi Zefan 
789b4046f00SLi Zefan 	mutex_lock(&devcgroup_mutex);
790451af504STejun Heo 	retval = devcgroup_update_access(css_to_devcgroup(of_css(of)),
791451af504STejun Heo 					 of_cft(of)->private, strstrip(buf));
792b4046f00SLi Zefan 	mutex_unlock(&devcgroup_mutex);
793451af504STejun Heo 	return retval ?: nbytes;
79408ce5f16SSerge E. Hallyn }
79508ce5f16SSerge E. Hallyn 
79608ce5f16SSerge E. Hallyn static struct cftype dev_cgroup_files[] = {
79708ce5f16SSerge E. Hallyn 	{
79808ce5f16SSerge E. Hallyn 		.name = "allow",
799451af504STejun Heo 		.write = devcgroup_access_write,
80008ce5f16SSerge E. Hallyn 		.private = DEVCG_ALLOW,
80108ce5f16SSerge E. Hallyn 	},
80208ce5f16SSerge E. Hallyn 	{
80308ce5f16SSerge E. Hallyn 		.name = "deny",
804451af504STejun Heo 		.write = devcgroup_access_write,
80508ce5f16SSerge E. Hallyn 		.private = DEVCG_DENY,
80608ce5f16SSerge E. Hallyn 	},
80729486df3SSerge E. Hallyn 	{
80829486df3SSerge E. Hallyn 		.name = "list",
8092da8ca82STejun Heo 		.seq_show = devcgroup_seq_show,
81029486df3SSerge E. Hallyn 		.private = DEVCG_LIST,
81129486df3SSerge E. Hallyn 	},
8124baf6e33STejun Heo 	{ }	/* terminate */
81308ce5f16SSerge E. Hallyn };
81408ce5f16SSerge E. Hallyn 
815073219e9STejun Heo struct cgroup_subsys devices_cgrp_subsys = {
81692fb9748STejun Heo 	.css_alloc = devcgroup_css_alloc,
81792fb9748STejun Heo 	.css_free = devcgroup_css_free,
8181909554cSAristeu Rozanski 	.css_online = devcgroup_online,
8191909554cSAristeu Rozanski 	.css_offline = devcgroup_offline,
8205577964eSTejun Heo 	.legacy_cftypes = dev_cgroup_files,
82108ce5f16SSerge E. Hallyn };
82208ce5f16SSerge E. Hallyn 
823ad676077SAristeu Rozanski /**
824eec8fd02SOdin Ugedal  * devcgroup_legacy_check_permission - checks if an inode operation is permitted
825ad676077SAristeu Rozanski  * @type: device type
826ad676077SAristeu Rozanski  * @major: device major number
827ad676077SAristeu Rozanski  * @minor: device minor number
82867e306fdSRoman Gushchin  * @access: combination of DEVCG_ACC_WRITE, DEVCG_ACC_READ and DEVCG_ACC_MKNOD
829ad676077SAristeu Rozanski  *
830ad676077SAristeu Rozanski  * returns 0 on success, -EPERM case the operation is not permitted
831ad676077SAristeu Rozanski  */
devcgroup_legacy_check_permission(short type,u32 major,u32 minor,short access)832eec8fd02SOdin Ugedal static int devcgroup_legacy_check_permission(short type, u32 major, u32 minor,
833ad676077SAristeu Rozanski 					short access)
83408ce5f16SSerge E. Hallyn {
8358c9506d1SJiri Slaby 	struct dev_cgroup *dev_cgroup;
83679d71974SAristeu Rozanski 	bool rc;
83708ce5f16SSerge E. Hallyn 
8384efd1a1bSPavel Emelyanov 	rcu_read_lock();
8398c9506d1SJiri Slaby 	dev_cgroup = task_devcgroup(current);
84079d71974SAristeu Rozanski 	if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW)
84179d71974SAristeu Rozanski 		/* Can't match any of the exceptions, even partially */
84279d71974SAristeu Rozanski 		rc = !match_exception_partial(&dev_cgroup->exceptions,
84379d71974SAristeu Rozanski 					      type, major, minor, access);
84479d71974SAristeu Rozanski 	else
84579d71974SAristeu Rozanski 		/* Need to match completely one exception to be allowed */
84679d71974SAristeu Rozanski 		rc = match_exception(&dev_cgroup->exceptions, type, major,
84779d71974SAristeu Rozanski 				     minor, access);
8484efd1a1bSPavel Emelyanov 	rcu_read_unlock();
849ad676077SAristeu Rozanski 
850ad676077SAristeu Rozanski 	if (!rc)
851ad676077SAristeu Rozanski 		return -EPERM;
852ad676077SAristeu Rozanski 
85308ce5f16SSerge E. Hallyn 	return 0;
85408ce5f16SSerge E. Hallyn }
8554b7d4d45SHarish Kasiviswanathan 
856eec8fd02SOdin Ugedal #endif /* CONFIG_CGROUP_DEVICE */
857eec8fd02SOdin Ugedal 
858eec8fd02SOdin Ugedal #if defined(CONFIG_CGROUP_DEVICE) || defined(CONFIG_CGROUP_BPF)
859eec8fd02SOdin Ugedal 
devcgroup_check_permission(short type,u32 major,u32 minor,short access)8604b7d4d45SHarish Kasiviswanathan int devcgroup_check_permission(short type, u32 major, u32 minor, short access)
8614b7d4d45SHarish Kasiviswanathan {
8624b7d4d45SHarish Kasiviswanathan 	int rc = BPF_CGROUP_RUN_PROG_DEVICE_CGROUP(type, major, minor, access);
8634b7d4d45SHarish Kasiviswanathan 
8644b7d4d45SHarish Kasiviswanathan 	if (rc)
865f10d0596SYiFei Zhu 		return rc;
8664b7d4d45SHarish Kasiviswanathan 
867eec8fd02SOdin Ugedal 	#ifdef CONFIG_CGROUP_DEVICE
868eec8fd02SOdin Ugedal 	return devcgroup_legacy_check_permission(type, major, minor, access);
869eec8fd02SOdin Ugedal 
870eec8fd02SOdin Ugedal 	#else /* CONFIG_CGROUP_DEVICE */
871eec8fd02SOdin Ugedal 	return 0;
872eec8fd02SOdin Ugedal 
873eec8fd02SOdin Ugedal 	#endif /* CONFIG_CGROUP_DEVICE */
8744b7d4d45SHarish Kasiviswanathan }
8754b7d4d45SHarish Kasiviswanathan EXPORT_SYMBOL(devcgroup_check_permission);
876eec8fd02SOdin Ugedal #endif /* defined(CONFIG_CGROUP_DEVICE) || defined(CONFIG_CGROUP_BPF) */
877