diff options
Diffstat (limited to 'security/device_cgroup.c')
| -rw-r--r-- | security/device_cgroup.c | 194 |
1 files changed, 98 insertions, 96 deletions
diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 03c1652c9a1f..7fec575d32d6 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -1,9 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 /* * device_cgroup.c - device cgroup subsystem * * Copyright 2007 IBM Corp */ +#include <linux/bpf-cgroup.h> #include <linux/device_cgroup.h> #include <linux/cgroup.h> #include <linux/ctype.h> @@ -14,14 +16,7 @@ #include <linux/rcupdate.h> #include <linux/mutex.h> -#define ACC_MKNOD 1 -#define ACC_READ 2 -#define ACC_WRITE 4 -#define ACC_MASK (ACC_MKNOD | ACC_READ | ACC_WRITE) - -#define DEV_BLOCK 1 -#define DEV_CHAR 2 -#define DEV_ALL 4 /* this represents all devices */ +#ifdef CONFIG_CGROUP_DEVICE static DEFINE_MUTEX(devcgroup_mutex); @@ -87,6 +82,17 @@ free_and_exit: return -ENOMEM; } +static void dev_exceptions_move(struct list_head *dest, struct list_head *orig) +{ + struct dev_exception_item *ex, *tmp; + + lockdep_assert_held(&devcgroup_mutex); + + list_for_each_entry_safe(ex, tmp, orig, list) { + list_move_tail(&ex->list, dest); + } +} + /* * called under devcgroup_mutex */ @@ -210,7 +216,7 @@ static void devcgroup_offline(struct cgroup_subsys_state *css) } /* - * called from kernel/cgroup.c with cgroup_lock() held. + * called from kernel/cgroup/cgroup.c with cgroup_lock() held. */ static struct cgroup_subsys_state * devcgroup_css_alloc(struct cgroup_subsys_state *parent_css) @@ -238,45 +244,40 @@ static void devcgroup_css_free(struct cgroup_subsys_state *css) #define DEVCG_DENY 2 #define DEVCG_LIST 3 -#define MAJMINLEN 13 -#define ACCLEN 4 - -static void set_access(char *acc, short access) +static void seq_putaccess(struct seq_file *m, short access) { - int idx = 0; - memset(acc, 0, ACCLEN); - if (access & ACC_READ) - acc[idx++] = 'r'; - if (access & ACC_WRITE) - acc[idx++] = 'w'; - if (access & ACC_MKNOD) - acc[idx++] = 'm'; + if (access & DEVCG_ACC_READ) + seq_putc(m, 'r'); + if (access & DEVCG_ACC_WRITE) + seq_putc(m, 'w'); + if (access & DEVCG_ACC_MKNOD) + seq_putc(m, 'm'); } -static char type_to_char(short type) +static void seq_puttype(struct seq_file *m, short type) { - if (type == DEV_ALL) - return 'a'; - if (type == DEV_CHAR) - return 'c'; - if (type == DEV_BLOCK) - return 'b'; - return 'X'; + if (type == DEVCG_DEV_ALL) + seq_putc(m, 'a'); + else if (type == DEVCG_DEV_CHAR) + seq_putc(m, 'c'); + else if (type == DEVCG_DEV_BLOCK) + seq_putc(m, 'b'); + else + seq_putc(m, 'X'); } -static void set_majmin(char *str, unsigned m) +static void seq_putversion(struct seq_file *m, unsigned int version) { - if (m == ~0) - strcpy(str, "*"); + if (version == ~0) + seq_putc(m, '*'); else - sprintf(str, "%u", m); + seq_printf(m, "%u", version); } static int devcgroup_seq_show(struct seq_file *m, void *v) { struct dev_cgroup *devcgroup = css_to_devcgroup(seq_css(m)); struct dev_exception_item *ex; - char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN]; rcu_read_lock(); /* @@ -286,18 +287,17 @@ static int devcgroup_seq_show(struct seq_file *m, void *v) * This way, the file remains as a "whitelist of devices" */ if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { - set_access(acc, ACC_MASK); - set_majmin(maj, ~0); - set_majmin(min, ~0); - seq_printf(m, "%c %s:%s %s\n", type_to_char(DEV_ALL), - maj, min, acc); + seq_puts(m, "a *:* rwm\n"); } else { list_for_each_entry_rcu(ex, &devcgroup->exceptions, list) { - set_access(acc, ex->access); - set_majmin(maj, ex->major); - set_majmin(min, ex->minor); - seq_printf(m, "%c %s:%s %s\n", type_to_char(ex->type), - maj, min, acc); + seq_puttype(m, ex->type); + seq_putc(m, ' '); + seq_putversion(m, ex->major); + seq_putc(m, ':'); + seq_putversion(m, ex->minor); + seq_putc(m, ' '); + seq_putaccess(m, ex->access); + seq_putc(m, '\n'); } } rcu_read_unlock(); @@ -308,10 +308,10 @@ static int devcgroup_seq_show(struct seq_file *m, void *v) /** * match_exception - iterates the exception list trying to find a complete match * @exceptions: list of exceptions - * @type: device type (DEV_BLOCK or DEV_CHAR) + * @type: device type (DEVCG_DEV_BLOCK or DEVCG_DEV_CHAR) * @major: device file major number, ~0 to match all * @minor: device file minor number, ~0 to match all - * @access: permission mask (ACC_READ, ACC_WRITE, ACC_MKNOD) + * @access: permission mask (DEVCG_ACC_READ, DEVCG_ACC_WRITE, DEVCG_ACC_MKNOD) * * It is considered a complete match if an exception is found that will * contain the entire range of provided parameters. @@ -324,9 +324,9 @@ static bool match_exception(struct list_head *exceptions, short type, struct dev_exception_item *ex; list_for_each_entry_rcu(ex, exceptions, list) { - if ((type & DEV_BLOCK) && !(ex->type & DEV_BLOCK)) + if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK)) continue; - if ((type & DEV_CHAR) && !(ex->type & DEV_CHAR)) + if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR)) continue; if (ex->major != ~0 && ex->major != major) continue; @@ -343,10 +343,10 @@ static bool match_exception(struct list_head *exceptions, short type, /** * match_exception_partial - iterates the exception list trying to find a partial match * @exceptions: list of exceptions - * @type: device type (DEV_BLOCK or DEV_CHAR) + * @type: device type (DEVCG_DEV_BLOCK or DEVCG_DEV_CHAR) * @major: device file major number, ~0 to match all * @minor: device file minor number, ~0 to match all - * @access: permission mask (ACC_READ, ACC_WRITE, ACC_MKNOD) + * @access: permission mask (DEVCG_ACC_READ, DEVCG_ACC_WRITE, DEVCG_ACC_MKNOD) * * It is considered a partial match if an exception's range is found to * contain *any* of the devices specified by provided parameters. This is @@ -360,10 +360,11 @@ static bool match_exception_partial(struct list_head *exceptions, short type, { struct dev_exception_item *ex; - list_for_each_entry_rcu(ex, exceptions, list) { - if ((type & DEV_BLOCK) && !(ex->type & DEV_BLOCK)) + list_for_each_entry_rcu(ex, exceptions, list, + lockdep_is_held(&devcgroup_mutex)) { + if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK)) continue; - if ((type & DEV_CHAR) && !(ex->type & DEV_CHAR)) + if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR)) continue; /* * We must be sure that both the exception and the provided @@ -414,7 +415,7 @@ static bool verify_new_ex(struct dev_cgroup *dev_cgroup, } else { /* * new exception in the child will add more devices - * that can be acessed, so it can't match any of + * that can be accessed, so it can't match any of * parent's exceptions, even slightly */ match = match_exception_partial(&dev_cgroup->exceptions, @@ -517,7 +518,7 @@ static inline int may_allow_all(struct dev_cgroup *parent) * This is one of the three key functions for hierarchy implementation. * This function is responsible for re-evaluating all the cgroup's active * exceptions due to a parent's exception change. - * Refer to Documentation/cgroups/devices.txt for more details. + * Refer to Documentation/admin-guide/cgroup-v1/devices.rst for more details. */ static void revalidate_active_exceptions(struct dev_cgroup *devcg) { @@ -568,7 +569,7 @@ static int propagate_exception(struct dev_cgroup *devcg_root, devcg->behavior == DEVCG_DEFAULT_ALLOW) { rc = dev_exception_add(devcg, ex); if (rc) - break; + return rc; } else { /* * in the other possible cases: @@ -608,11 +609,13 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, int count, rc = 0; struct dev_exception_item ex; struct dev_cgroup *parent = css_to_devcgroup(devcgroup->css.parent); + struct dev_cgroup tmp_devcgrp; if (!capable(CAP_SYS_ADMIN)) return -EPERM; memset(&ex, 0, sizeof(ex)); + memset(&tmp_devcgrp, 0, sizeof(tmp_devcgrp)); b = buffer; switch (*b) { @@ -624,15 +627,27 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, if (!may_allow_all(parent)) return -EPERM; - dev_exception_clean(devcgroup); - devcgroup->behavior = DEVCG_DEFAULT_ALLOW; - if (!parent) + if (!parent) { + devcgroup->behavior = DEVCG_DEFAULT_ALLOW; + dev_exception_clean(devcgroup); break; + } + INIT_LIST_HEAD(&tmp_devcgrp.exceptions); + rc = dev_exceptions_copy(&tmp_devcgrp.exceptions, + &devcgroup->exceptions); + if (rc) + return rc; + dev_exception_clean(devcgroup); rc = dev_exceptions_copy(&devcgroup->exceptions, &parent->exceptions); - if (rc) + if (rc) { + dev_exceptions_move(&devcgroup->exceptions, + &tmp_devcgrp.exceptions); return rc; + } + devcgroup->behavior = DEVCG_DEFAULT_ALLOW; + dev_exception_clean(&tmp_devcgrp); break; case DEVCG_DENY: if (css_has_online_children(&devcgroup->css)) @@ -646,10 +661,10 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, } return 0; case 'b': - ex.type = DEV_BLOCK; + ex.type = DEVCG_DEV_BLOCK; break; case 'c': - ex.type = DEV_CHAR; + ex.type = DEVCG_DEV_CHAR; break; default: return -EINVAL; @@ -702,13 +717,13 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, for (b++, count = 0; count < 3; count++, b++) { switch (*b) { case 'r': - ex.access |= ACC_READ; + ex.access |= DEVCG_ACC_READ; break; case 'w': - ex.access |= ACC_WRITE; + ex.access |= DEVCG_ACC_WRITE; break; case 'm': - ex.access |= ACC_MKNOD; + ex.access |= DEVCG_ACC_MKNOD; break; case '\n': case '\0': @@ -800,17 +815,16 @@ struct cgroup_subsys devices_cgrp_subsys = { }; /** - * __devcgroup_check_permission - checks if an inode operation is permitted - * @dev_cgroup: the dev cgroup to be tested against + * devcgroup_legacy_check_permission - checks if an inode operation is permitted * @type: device type * @major: device major number * @minor: device minor number - * @access: combination of ACC_WRITE, ACC_READ and ACC_MKNOD + * @access: combination of DEVCG_ACC_WRITE, DEVCG_ACC_READ and DEVCG_ACC_MKNOD * * returns 0 on success, -EPERM case the operation is not permitted */ -static int __devcgroup_check_permission(short type, u32 major, u32 minor, - short access) +static int devcgroup_legacy_check_permission(short type, u32 major, u32 minor, + short access) { struct dev_cgroup *dev_cgroup; bool rc; @@ -833,36 +847,24 @@ static int __devcgroup_check_permission(short type, u32 major, u32 minor, return 0; } -int __devcgroup_inode_permission(struct inode *inode, int mask) -{ - short type, access = 0; - - if (S_ISBLK(inode->i_mode)) - type = DEV_BLOCK; - if (S_ISCHR(inode->i_mode)) - type = DEV_CHAR; - if (mask & MAY_WRITE) - access |= ACC_WRITE; - if (mask & MAY_READ) - access |= ACC_READ; - - return __devcgroup_check_permission(type, imajor(inode), iminor(inode), - access); -} +#endif /* CONFIG_CGROUP_DEVICE */ -int devcgroup_inode_mknod(int mode, dev_t dev) +#if defined(CONFIG_CGROUP_DEVICE) || defined(CONFIG_CGROUP_BPF) + +int devcgroup_check_permission(short type, u32 major, u32 minor, short access) { - short type; + int rc = BPF_CGROUP_RUN_PROG_DEVICE_CGROUP(type, major, minor, access); - if (!S_ISBLK(mode) && !S_ISCHR(mode)) - return 0; + if (rc) + return rc; - if (S_ISBLK(mode)) - type = DEV_BLOCK; - else - type = DEV_CHAR; + #ifdef CONFIG_CGROUP_DEVICE + return devcgroup_legacy_check_permission(type, major, minor, access); - return __devcgroup_check_permission(type, MAJOR(dev), MINOR(dev), - ACC_MKNOD); + #else /* CONFIG_CGROUP_DEVICE */ + return 0; + #endif /* CONFIG_CGROUP_DEVICE */ } +EXPORT_SYMBOL(devcgroup_check_permission); +#endif /* defined(CONFIG_CGROUP_DEVICE) || defined(CONFIG_CGROUP_BPF) */ |
