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 /*
219*f89f8e16SKamalesh 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
42479d71974SAristeu Rozanski * 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
828ad676077SAristeu Rozanski * @access: combination of DEVCG_ACC_WRITE, DEVCG_ACC_READ and DEVCG_ACC_MKNOD
82967e306fdSRoman Gushchin *
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)832ad676077SAristeu Rozanski static int devcgroup_legacy_check_permission(short type, u32 major, u32 minor,
833eec8fd02SOdin Ugedal short access)
834ad676077SAristeu Rozanski {
83508ce5f16SSerge E. Hallyn struct dev_cgroup *dev_cgroup;
8368c9506d1SJiri Slaby bool rc;
83779d71974SAristeu Rozanski
83808ce5f16SSerge E. Hallyn rcu_read_lock();
8394efd1a1bSPavel Emelyanov dev_cgroup = task_devcgroup(current);
8408c9506d1SJiri Slaby 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);
84879d71974SAristeu Rozanski rcu_read_unlock();
8494efd1a1bSPavel Emelyanov
850ad676077SAristeu Rozanski if (!rc)
851ad676077SAristeu Rozanski return -EPERM;
852ad676077SAristeu Rozanski
853ad676077SAristeu Rozanski return 0;
85408ce5f16SSerge E. Hallyn }
85508ce5f16SSerge E. Hallyn
8564b7d4d45SHarish Kasiviswanathan #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)860eec8fd02SOdin Ugedal 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)
8654b7d4d45SHarish Kasiviswanathan return rc;
866f10d0596SYiFei Zhu
8674b7d4d45SHarish Kasiviswanathan #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 */
874eec8fd02SOdin Ugedal }
8754b7d4d45SHarish Kasiviswanathan EXPORT_SYMBOL(devcgroup_check_permission);
8764b7d4d45SHarish Kasiviswanathan #endif /* defined(CONFIG_CGROUP_DEVICE) || defined(CONFIG_CGROUP_BPF) */
877eec8fd02SOdin Ugedal