162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * device_cgroup.c - device cgroup subsystem 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2007 IBM Corp 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/bpf-cgroup.h> 962306a36Sopenharmony_ci#include <linux/device_cgroup.h> 1062306a36Sopenharmony_ci#include <linux/cgroup.h> 1162306a36Sopenharmony_ci#include <linux/ctype.h> 1262306a36Sopenharmony_ci#include <linux/list.h> 1362306a36Sopenharmony_ci#include <linux/uaccess.h> 1462306a36Sopenharmony_ci#include <linux/seq_file.h> 1562306a36Sopenharmony_ci#include <linux/slab.h> 1662306a36Sopenharmony_ci#include <linux/rcupdate.h> 1762306a36Sopenharmony_ci#include <linux/mutex.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#ifdef CONFIG_CGROUP_DEVICE 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic DEFINE_MUTEX(devcgroup_mutex); 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cienum devcg_behavior { 2462306a36Sopenharmony_ci DEVCG_DEFAULT_NONE, 2562306a36Sopenharmony_ci DEVCG_DEFAULT_ALLOW, 2662306a36Sopenharmony_ci DEVCG_DEFAULT_DENY, 2762306a36Sopenharmony_ci}; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* 3062306a36Sopenharmony_ci * exception list locking rules: 3162306a36Sopenharmony_ci * hold devcgroup_mutex for update/read. 3262306a36Sopenharmony_ci * hold rcu_read_lock() for read. 3362306a36Sopenharmony_ci */ 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistruct dev_exception_item { 3662306a36Sopenharmony_ci u32 major, minor; 3762306a36Sopenharmony_ci short type; 3862306a36Sopenharmony_ci short access; 3962306a36Sopenharmony_ci struct list_head list; 4062306a36Sopenharmony_ci struct rcu_head rcu; 4162306a36Sopenharmony_ci}; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistruct dev_cgroup { 4462306a36Sopenharmony_ci struct cgroup_subsys_state css; 4562306a36Sopenharmony_ci struct list_head exceptions; 4662306a36Sopenharmony_ci enum devcg_behavior behavior; 4762306a36Sopenharmony_ci}; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci return s ? container_of(s, struct dev_cgroup, css) : NULL; 5262306a36Sopenharmony_ci} 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic inline struct dev_cgroup *task_devcgroup(struct task_struct *task) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci return css_to_devcgroup(task_css(task, devices_cgrp_id)); 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/* 6062306a36Sopenharmony_ci * called under devcgroup_mutex 6162306a36Sopenharmony_ci */ 6262306a36Sopenharmony_cistatic int dev_exceptions_copy(struct list_head *dest, struct list_head *orig) 6362306a36Sopenharmony_ci{ 6462306a36Sopenharmony_ci struct dev_exception_item *ex, *tmp, *new; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci lockdep_assert_held(&devcgroup_mutex); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci list_for_each_entry(ex, orig, list) { 6962306a36Sopenharmony_ci new = kmemdup(ex, sizeof(*ex), GFP_KERNEL); 7062306a36Sopenharmony_ci if (!new) 7162306a36Sopenharmony_ci goto free_and_exit; 7262306a36Sopenharmony_ci list_add_tail(&new->list, dest); 7362306a36Sopenharmony_ci } 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci return 0; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cifree_and_exit: 7862306a36Sopenharmony_ci list_for_each_entry_safe(ex, tmp, dest, list) { 7962306a36Sopenharmony_ci list_del(&ex->list); 8062306a36Sopenharmony_ci kfree(ex); 8162306a36Sopenharmony_ci } 8262306a36Sopenharmony_ci return -ENOMEM; 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic void dev_exceptions_move(struct list_head *dest, struct list_head *orig) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci struct dev_exception_item *ex, *tmp; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci lockdep_assert_held(&devcgroup_mutex); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci list_for_each_entry_safe(ex, tmp, orig, list) { 9262306a36Sopenharmony_ci list_move_tail(&ex->list, dest); 9362306a36Sopenharmony_ci } 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci/* 9762306a36Sopenharmony_ci * called under devcgroup_mutex 9862306a36Sopenharmony_ci */ 9962306a36Sopenharmony_cistatic int dev_exception_add(struct dev_cgroup *dev_cgroup, 10062306a36Sopenharmony_ci struct dev_exception_item *ex) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci struct dev_exception_item *excopy, *walk; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci lockdep_assert_held(&devcgroup_mutex); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci excopy = kmemdup(ex, sizeof(*ex), GFP_KERNEL); 10762306a36Sopenharmony_ci if (!excopy) 10862306a36Sopenharmony_ci return -ENOMEM; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci list_for_each_entry(walk, &dev_cgroup->exceptions, list) { 11162306a36Sopenharmony_ci if (walk->type != ex->type) 11262306a36Sopenharmony_ci continue; 11362306a36Sopenharmony_ci if (walk->major != ex->major) 11462306a36Sopenharmony_ci continue; 11562306a36Sopenharmony_ci if (walk->minor != ex->minor) 11662306a36Sopenharmony_ci continue; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci walk->access |= ex->access; 11962306a36Sopenharmony_ci kfree(excopy); 12062306a36Sopenharmony_ci excopy = NULL; 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (excopy != NULL) 12462306a36Sopenharmony_ci list_add_tail_rcu(&excopy->list, &dev_cgroup->exceptions); 12562306a36Sopenharmony_ci return 0; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci/* 12962306a36Sopenharmony_ci * called under devcgroup_mutex 13062306a36Sopenharmony_ci */ 13162306a36Sopenharmony_cistatic void dev_exception_rm(struct dev_cgroup *dev_cgroup, 13262306a36Sopenharmony_ci struct dev_exception_item *ex) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci struct dev_exception_item *walk, *tmp; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci lockdep_assert_held(&devcgroup_mutex); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci list_for_each_entry_safe(walk, tmp, &dev_cgroup->exceptions, list) { 13962306a36Sopenharmony_ci if (walk->type != ex->type) 14062306a36Sopenharmony_ci continue; 14162306a36Sopenharmony_ci if (walk->major != ex->major) 14262306a36Sopenharmony_ci continue; 14362306a36Sopenharmony_ci if (walk->minor != ex->minor) 14462306a36Sopenharmony_ci continue; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci walk->access &= ~ex->access; 14762306a36Sopenharmony_ci if (!walk->access) { 14862306a36Sopenharmony_ci list_del_rcu(&walk->list); 14962306a36Sopenharmony_ci kfree_rcu(walk, rcu); 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci } 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic void __dev_exception_clean(struct dev_cgroup *dev_cgroup) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci struct dev_exception_item *ex, *tmp; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci list_for_each_entry_safe(ex, tmp, &dev_cgroup->exceptions, list) { 15962306a36Sopenharmony_ci list_del_rcu(&ex->list); 16062306a36Sopenharmony_ci kfree_rcu(ex, rcu); 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci/** 16562306a36Sopenharmony_ci * dev_exception_clean - frees all entries of the exception list 16662306a36Sopenharmony_ci * @dev_cgroup: dev_cgroup with the exception list to be cleaned 16762306a36Sopenharmony_ci * 16862306a36Sopenharmony_ci * called under devcgroup_mutex 16962306a36Sopenharmony_ci */ 17062306a36Sopenharmony_cistatic void dev_exception_clean(struct dev_cgroup *dev_cgroup) 17162306a36Sopenharmony_ci{ 17262306a36Sopenharmony_ci lockdep_assert_held(&devcgroup_mutex); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci __dev_exception_clean(dev_cgroup); 17562306a36Sopenharmony_ci} 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_cistatic inline bool is_devcg_online(const struct dev_cgroup *devcg) 17862306a36Sopenharmony_ci{ 17962306a36Sopenharmony_ci return (devcg->behavior != DEVCG_DEFAULT_NONE); 18062306a36Sopenharmony_ci} 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci/** 18362306a36Sopenharmony_ci * devcgroup_online - initializes devcgroup's behavior and exceptions based on 18462306a36Sopenharmony_ci * parent's 18562306a36Sopenharmony_ci * @css: css getting online 18662306a36Sopenharmony_ci * returns 0 in case of success, error code otherwise 18762306a36Sopenharmony_ci */ 18862306a36Sopenharmony_cistatic int devcgroup_online(struct cgroup_subsys_state *css) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci struct dev_cgroup *dev_cgroup = css_to_devcgroup(css); 19162306a36Sopenharmony_ci struct dev_cgroup *parent_dev_cgroup = css_to_devcgroup(css->parent); 19262306a36Sopenharmony_ci int ret = 0; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci mutex_lock(&devcgroup_mutex); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci if (parent_dev_cgroup == NULL) 19762306a36Sopenharmony_ci dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW; 19862306a36Sopenharmony_ci else { 19962306a36Sopenharmony_ci ret = dev_exceptions_copy(&dev_cgroup->exceptions, 20062306a36Sopenharmony_ci &parent_dev_cgroup->exceptions); 20162306a36Sopenharmony_ci if (!ret) 20262306a36Sopenharmony_ci dev_cgroup->behavior = parent_dev_cgroup->behavior; 20362306a36Sopenharmony_ci } 20462306a36Sopenharmony_ci mutex_unlock(&devcgroup_mutex); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci return ret; 20762306a36Sopenharmony_ci} 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_cistatic void devcgroup_offline(struct cgroup_subsys_state *css) 21062306a36Sopenharmony_ci{ 21162306a36Sopenharmony_ci struct dev_cgroup *dev_cgroup = css_to_devcgroup(css); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci mutex_lock(&devcgroup_mutex); 21462306a36Sopenharmony_ci dev_cgroup->behavior = DEVCG_DEFAULT_NONE; 21562306a36Sopenharmony_ci mutex_unlock(&devcgroup_mutex); 21662306a36Sopenharmony_ci} 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci/* 21962306a36Sopenharmony_ci * called from kernel/cgroup/cgroup.c with cgroup_lock() held. 22062306a36Sopenharmony_ci */ 22162306a36Sopenharmony_cistatic struct cgroup_subsys_state * 22262306a36Sopenharmony_cidevcgroup_css_alloc(struct cgroup_subsys_state *parent_css) 22362306a36Sopenharmony_ci{ 22462306a36Sopenharmony_ci struct dev_cgroup *dev_cgroup; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL); 22762306a36Sopenharmony_ci if (!dev_cgroup) 22862306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 22962306a36Sopenharmony_ci INIT_LIST_HEAD(&dev_cgroup->exceptions); 23062306a36Sopenharmony_ci dev_cgroup->behavior = DEVCG_DEFAULT_NONE; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci return &dev_cgroup->css; 23362306a36Sopenharmony_ci} 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_cistatic void devcgroup_css_free(struct cgroup_subsys_state *css) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci struct dev_cgroup *dev_cgroup = css_to_devcgroup(css); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci __dev_exception_clean(dev_cgroup); 24062306a36Sopenharmony_ci kfree(dev_cgroup); 24162306a36Sopenharmony_ci} 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci#define DEVCG_ALLOW 1 24462306a36Sopenharmony_ci#define DEVCG_DENY 2 24562306a36Sopenharmony_ci#define DEVCG_LIST 3 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci#define MAJMINLEN 13 24862306a36Sopenharmony_ci#define ACCLEN 4 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic void set_access(char *acc, short access) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci int idx = 0; 25362306a36Sopenharmony_ci memset(acc, 0, ACCLEN); 25462306a36Sopenharmony_ci if (access & DEVCG_ACC_READ) 25562306a36Sopenharmony_ci acc[idx++] = 'r'; 25662306a36Sopenharmony_ci if (access & DEVCG_ACC_WRITE) 25762306a36Sopenharmony_ci acc[idx++] = 'w'; 25862306a36Sopenharmony_ci if (access & DEVCG_ACC_MKNOD) 25962306a36Sopenharmony_ci acc[idx++] = 'm'; 26062306a36Sopenharmony_ci} 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_cistatic char type_to_char(short type) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci if (type == DEVCG_DEV_ALL) 26562306a36Sopenharmony_ci return 'a'; 26662306a36Sopenharmony_ci if (type == DEVCG_DEV_CHAR) 26762306a36Sopenharmony_ci return 'c'; 26862306a36Sopenharmony_ci if (type == DEVCG_DEV_BLOCK) 26962306a36Sopenharmony_ci return 'b'; 27062306a36Sopenharmony_ci return 'X'; 27162306a36Sopenharmony_ci} 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_cistatic void set_majmin(char *str, unsigned m) 27462306a36Sopenharmony_ci{ 27562306a36Sopenharmony_ci if (m == ~0) 27662306a36Sopenharmony_ci strcpy(str, "*"); 27762306a36Sopenharmony_ci else 27862306a36Sopenharmony_ci sprintf(str, "%u", m); 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cistatic int devcgroup_seq_show(struct seq_file *m, void *v) 28262306a36Sopenharmony_ci{ 28362306a36Sopenharmony_ci struct dev_cgroup *devcgroup = css_to_devcgroup(seq_css(m)); 28462306a36Sopenharmony_ci struct dev_exception_item *ex; 28562306a36Sopenharmony_ci char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN]; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci rcu_read_lock(); 28862306a36Sopenharmony_ci /* 28962306a36Sopenharmony_ci * To preserve the compatibility: 29062306a36Sopenharmony_ci * - Only show the "all devices" when the default policy is to allow 29162306a36Sopenharmony_ci * - List the exceptions in case the default policy is to deny 29262306a36Sopenharmony_ci * This way, the file remains as a "whitelist of devices" 29362306a36Sopenharmony_ci */ 29462306a36Sopenharmony_ci if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { 29562306a36Sopenharmony_ci set_access(acc, DEVCG_ACC_MASK); 29662306a36Sopenharmony_ci set_majmin(maj, ~0); 29762306a36Sopenharmony_ci set_majmin(min, ~0); 29862306a36Sopenharmony_ci seq_printf(m, "%c %s:%s %s\n", type_to_char(DEVCG_DEV_ALL), 29962306a36Sopenharmony_ci maj, min, acc); 30062306a36Sopenharmony_ci } else { 30162306a36Sopenharmony_ci list_for_each_entry_rcu(ex, &devcgroup->exceptions, list) { 30262306a36Sopenharmony_ci set_access(acc, ex->access); 30362306a36Sopenharmony_ci set_majmin(maj, ex->major); 30462306a36Sopenharmony_ci set_majmin(min, ex->minor); 30562306a36Sopenharmony_ci seq_printf(m, "%c %s:%s %s\n", type_to_char(ex->type), 30662306a36Sopenharmony_ci maj, min, acc); 30762306a36Sopenharmony_ci } 30862306a36Sopenharmony_ci } 30962306a36Sopenharmony_ci rcu_read_unlock(); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci return 0; 31262306a36Sopenharmony_ci} 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci/** 31562306a36Sopenharmony_ci * match_exception - iterates the exception list trying to find a complete match 31662306a36Sopenharmony_ci * @exceptions: list of exceptions 31762306a36Sopenharmony_ci * @type: device type (DEVCG_DEV_BLOCK or DEVCG_DEV_CHAR) 31862306a36Sopenharmony_ci * @major: device file major number, ~0 to match all 31962306a36Sopenharmony_ci * @minor: device file minor number, ~0 to match all 32062306a36Sopenharmony_ci * @access: permission mask (DEVCG_ACC_READ, DEVCG_ACC_WRITE, DEVCG_ACC_MKNOD) 32162306a36Sopenharmony_ci * 32262306a36Sopenharmony_ci * It is considered a complete match if an exception is found that will 32362306a36Sopenharmony_ci * contain the entire range of provided parameters. 32462306a36Sopenharmony_ci * 32562306a36Sopenharmony_ci * Return: true in case it matches an exception completely 32662306a36Sopenharmony_ci */ 32762306a36Sopenharmony_cistatic bool match_exception(struct list_head *exceptions, short type, 32862306a36Sopenharmony_ci u32 major, u32 minor, short access) 32962306a36Sopenharmony_ci{ 33062306a36Sopenharmony_ci struct dev_exception_item *ex; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci list_for_each_entry_rcu(ex, exceptions, list) { 33362306a36Sopenharmony_ci if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK)) 33462306a36Sopenharmony_ci continue; 33562306a36Sopenharmony_ci if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR)) 33662306a36Sopenharmony_ci continue; 33762306a36Sopenharmony_ci if (ex->major != ~0 && ex->major != major) 33862306a36Sopenharmony_ci continue; 33962306a36Sopenharmony_ci if (ex->minor != ~0 && ex->minor != minor) 34062306a36Sopenharmony_ci continue; 34162306a36Sopenharmony_ci /* provided access cannot have more than the exception rule */ 34262306a36Sopenharmony_ci if (access & (~ex->access)) 34362306a36Sopenharmony_ci continue; 34462306a36Sopenharmony_ci return true; 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci return false; 34762306a36Sopenharmony_ci} 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci/** 35062306a36Sopenharmony_ci * match_exception_partial - iterates the exception list trying to find a partial match 35162306a36Sopenharmony_ci * @exceptions: list of exceptions 35262306a36Sopenharmony_ci * @type: device type (DEVCG_DEV_BLOCK or DEVCG_DEV_CHAR) 35362306a36Sopenharmony_ci * @major: device file major number, ~0 to match all 35462306a36Sopenharmony_ci * @minor: device file minor number, ~0 to match all 35562306a36Sopenharmony_ci * @access: permission mask (DEVCG_ACC_READ, DEVCG_ACC_WRITE, DEVCG_ACC_MKNOD) 35662306a36Sopenharmony_ci * 35762306a36Sopenharmony_ci * It is considered a partial match if an exception's range is found to 35862306a36Sopenharmony_ci * contain *any* of the devices specified by provided parameters. This is 35962306a36Sopenharmony_ci * used to make sure no extra access is being granted that is forbidden by 36062306a36Sopenharmony_ci * any of the exception list. 36162306a36Sopenharmony_ci * 36262306a36Sopenharmony_ci * Return: true in case the provided range mat matches an exception completely 36362306a36Sopenharmony_ci */ 36462306a36Sopenharmony_cistatic bool match_exception_partial(struct list_head *exceptions, short type, 36562306a36Sopenharmony_ci u32 major, u32 minor, short access) 36662306a36Sopenharmony_ci{ 36762306a36Sopenharmony_ci struct dev_exception_item *ex; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci list_for_each_entry_rcu(ex, exceptions, list, 37062306a36Sopenharmony_ci lockdep_is_held(&devcgroup_mutex)) { 37162306a36Sopenharmony_ci if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK)) 37262306a36Sopenharmony_ci continue; 37362306a36Sopenharmony_ci if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR)) 37462306a36Sopenharmony_ci continue; 37562306a36Sopenharmony_ci /* 37662306a36Sopenharmony_ci * We must be sure that both the exception and the provided 37762306a36Sopenharmony_ci * range aren't masking all devices 37862306a36Sopenharmony_ci */ 37962306a36Sopenharmony_ci if (ex->major != ~0 && major != ~0 && ex->major != major) 38062306a36Sopenharmony_ci continue; 38162306a36Sopenharmony_ci if (ex->minor != ~0 && minor != ~0 && ex->minor != minor) 38262306a36Sopenharmony_ci continue; 38362306a36Sopenharmony_ci /* 38462306a36Sopenharmony_ci * In order to make sure the provided range isn't matching 38562306a36Sopenharmony_ci * an exception, all its access bits shouldn't match the 38662306a36Sopenharmony_ci * exception's access bits 38762306a36Sopenharmony_ci */ 38862306a36Sopenharmony_ci if (!(access & ex->access)) 38962306a36Sopenharmony_ci continue; 39062306a36Sopenharmony_ci return true; 39162306a36Sopenharmony_ci } 39262306a36Sopenharmony_ci return false; 39362306a36Sopenharmony_ci} 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci/** 39662306a36Sopenharmony_ci * verify_new_ex - verifies if a new exception is allowed by parent cgroup's permissions 39762306a36Sopenharmony_ci * @dev_cgroup: dev cgroup to be tested against 39862306a36Sopenharmony_ci * @refex: new exception 39962306a36Sopenharmony_ci * @behavior: behavior of the exception's dev_cgroup 40062306a36Sopenharmony_ci * 40162306a36Sopenharmony_ci * This is used to make sure a child cgroup won't have more privileges 40262306a36Sopenharmony_ci * than its parent 40362306a36Sopenharmony_ci */ 40462306a36Sopenharmony_cistatic bool verify_new_ex(struct dev_cgroup *dev_cgroup, 40562306a36Sopenharmony_ci struct dev_exception_item *refex, 40662306a36Sopenharmony_ci enum devcg_behavior behavior) 40762306a36Sopenharmony_ci{ 40862306a36Sopenharmony_ci bool match = false; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci RCU_LOCKDEP_WARN(!rcu_read_lock_held() && 41162306a36Sopenharmony_ci !lockdep_is_held(&devcgroup_mutex), 41262306a36Sopenharmony_ci "device_cgroup:verify_new_ex called without proper synchronization"); 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) { 41562306a36Sopenharmony_ci if (behavior == DEVCG_DEFAULT_ALLOW) { 41662306a36Sopenharmony_ci /* 41762306a36Sopenharmony_ci * new exception in the child doesn't matter, only 41862306a36Sopenharmony_ci * adding extra restrictions 41962306a36Sopenharmony_ci */ 42062306a36Sopenharmony_ci return true; 42162306a36Sopenharmony_ci } else { 42262306a36Sopenharmony_ci /* 42362306a36Sopenharmony_ci * new exception in the child will add more devices 42462306a36Sopenharmony_ci * that can be accessed, so it can't match any of 42562306a36Sopenharmony_ci * parent's exceptions, even slightly 42662306a36Sopenharmony_ci */ 42762306a36Sopenharmony_ci match = match_exception_partial(&dev_cgroup->exceptions, 42862306a36Sopenharmony_ci refex->type, 42962306a36Sopenharmony_ci refex->major, 43062306a36Sopenharmony_ci refex->minor, 43162306a36Sopenharmony_ci refex->access); 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci if (match) 43462306a36Sopenharmony_ci return false; 43562306a36Sopenharmony_ci return true; 43662306a36Sopenharmony_ci } 43762306a36Sopenharmony_ci } else { 43862306a36Sopenharmony_ci /* 43962306a36Sopenharmony_ci * Only behavior == DEVCG_DEFAULT_DENY allowed here, therefore 44062306a36Sopenharmony_ci * the new exception will add access to more devices and must 44162306a36Sopenharmony_ci * be contained completely in an parent's exception to be 44262306a36Sopenharmony_ci * allowed 44362306a36Sopenharmony_ci */ 44462306a36Sopenharmony_ci match = match_exception(&dev_cgroup->exceptions, refex->type, 44562306a36Sopenharmony_ci refex->major, refex->minor, 44662306a36Sopenharmony_ci refex->access); 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci if (match) 44962306a36Sopenharmony_ci /* parent has an exception that matches the proposed */ 45062306a36Sopenharmony_ci return true; 45162306a36Sopenharmony_ci else 45262306a36Sopenharmony_ci return false; 45362306a36Sopenharmony_ci } 45462306a36Sopenharmony_ci return false; 45562306a36Sopenharmony_ci} 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci/* 45862306a36Sopenharmony_ci * parent_has_perm: 45962306a36Sopenharmony_ci * when adding a new allow rule to a device exception list, the rule 46062306a36Sopenharmony_ci * must be allowed in the parent device 46162306a36Sopenharmony_ci */ 46262306a36Sopenharmony_cistatic int parent_has_perm(struct dev_cgroup *childcg, 46362306a36Sopenharmony_ci struct dev_exception_item *ex) 46462306a36Sopenharmony_ci{ 46562306a36Sopenharmony_ci struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent); 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci if (!parent) 46862306a36Sopenharmony_ci return 1; 46962306a36Sopenharmony_ci return verify_new_ex(parent, ex, childcg->behavior); 47062306a36Sopenharmony_ci} 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci/** 47362306a36Sopenharmony_ci * parent_allows_removal - verify if it's ok to remove an exception 47462306a36Sopenharmony_ci * @childcg: child cgroup from where the exception will be removed 47562306a36Sopenharmony_ci * @ex: exception being removed 47662306a36Sopenharmony_ci * 47762306a36Sopenharmony_ci * When removing an exception in cgroups with default ALLOW policy, it must 47862306a36Sopenharmony_ci * be checked if removing it will give the child cgroup more access than the 47962306a36Sopenharmony_ci * parent. 48062306a36Sopenharmony_ci * 48162306a36Sopenharmony_ci * Return: true if it's ok to remove exception, false otherwise 48262306a36Sopenharmony_ci */ 48362306a36Sopenharmony_cistatic bool parent_allows_removal(struct dev_cgroup *childcg, 48462306a36Sopenharmony_ci struct dev_exception_item *ex) 48562306a36Sopenharmony_ci{ 48662306a36Sopenharmony_ci struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent); 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci if (!parent) 48962306a36Sopenharmony_ci return true; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci /* It's always allowed to remove access to devices */ 49262306a36Sopenharmony_ci if (childcg->behavior == DEVCG_DEFAULT_DENY) 49362306a36Sopenharmony_ci return true; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci /* 49662306a36Sopenharmony_ci * Make sure you're not removing part or a whole exception existing in 49762306a36Sopenharmony_ci * the parent cgroup 49862306a36Sopenharmony_ci */ 49962306a36Sopenharmony_ci return !match_exception_partial(&parent->exceptions, ex->type, 50062306a36Sopenharmony_ci ex->major, ex->minor, ex->access); 50162306a36Sopenharmony_ci} 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci/** 50462306a36Sopenharmony_ci * may_allow_all - checks if it's possible to change the behavior to 50562306a36Sopenharmony_ci * allow based on parent's rules. 50662306a36Sopenharmony_ci * @parent: device cgroup's parent 50762306a36Sopenharmony_ci * returns: != 0 in case it's allowed, 0 otherwise 50862306a36Sopenharmony_ci */ 50962306a36Sopenharmony_cistatic inline int may_allow_all(struct dev_cgroup *parent) 51062306a36Sopenharmony_ci{ 51162306a36Sopenharmony_ci if (!parent) 51262306a36Sopenharmony_ci return 1; 51362306a36Sopenharmony_ci return parent->behavior == DEVCG_DEFAULT_ALLOW; 51462306a36Sopenharmony_ci} 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci/** 51762306a36Sopenharmony_ci * revalidate_active_exceptions - walks through the active exception list and 51862306a36Sopenharmony_ci * revalidates the exceptions based on parent's 51962306a36Sopenharmony_ci * behavior and exceptions. The exceptions that 52062306a36Sopenharmony_ci * are no longer valid will be removed. 52162306a36Sopenharmony_ci * Called with devcgroup_mutex held. 52262306a36Sopenharmony_ci * @devcg: cgroup which exceptions will be checked 52362306a36Sopenharmony_ci * 52462306a36Sopenharmony_ci * This is one of the three key functions for hierarchy implementation. 52562306a36Sopenharmony_ci * This function is responsible for re-evaluating all the cgroup's active 52662306a36Sopenharmony_ci * exceptions due to a parent's exception change. 52762306a36Sopenharmony_ci * Refer to Documentation/admin-guide/cgroup-v1/devices.rst for more details. 52862306a36Sopenharmony_ci */ 52962306a36Sopenharmony_cistatic void revalidate_active_exceptions(struct dev_cgroup *devcg) 53062306a36Sopenharmony_ci{ 53162306a36Sopenharmony_ci struct dev_exception_item *ex; 53262306a36Sopenharmony_ci struct list_head *this, *tmp; 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci list_for_each_safe(this, tmp, &devcg->exceptions) { 53562306a36Sopenharmony_ci ex = container_of(this, struct dev_exception_item, list); 53662306a36Sopenharmony_ci if (!parent_has_perm(devcg, ex)) 53762306a36Sopenharmony_ci dev_exception_rm(devcg, ex); 53862306a36Sopenharmony_ci } 53962306a36Sopenharmony_ci} 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci/** 54262306a36Sopenharmony_ci * propagate_exception - propagates a new exception to the children 54362306a36Sopenharmony_ci * @devcg_root: device cgroup that added a new exception 54462306a36Sopenharmony_ci * @ex: new exception to be propagated 54562306a36Sopenharmony_ci * 54662306a36Sopenharmony_ci * returns: 0 in case of success, != 0 in case of error 54762306a36Sopenharmony_ci */ 54862306a36Sopenharmony_cistatic int propagate_exception(struct dev_cgroup *devcg_root, 54962306a36Sopenharmony_ci struct dev_exception_item *ex) 55062306a36Sopenharmony_ci{ 55162306a36Sopenharmony_ci struct cgroup_subsys_state *pos; 55262306a36Sopenharmony_ci int rc = 0; 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci rcu_read_lock(); 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ci css_for_each_descendant_pre(pos, &devcg_root->css) { 55762306a36Sopenharmony_ci struct dev_cgroup *devcg = css_to_devcgroup(pos); 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci /* 56062306a36Sopenharmony_ci * Because devcgroup_mutex is held, no devcg will become 56162306a36Sopenharmony_ci * online or offline during the tree walk (see on/offline 56262306a36Sopenharmony_ci * methods), and online ones are safe to access outside RCU 56362306a36Sopenharmony_ci * read lock without bumping refcnt. 56462306a36Sopenharmony_ci */ 56562306a36Sopenharmony_ci if (pos == &devcg_root->css || !is_devcg_online(devcg)) 56662306a36Sopenharmony_ci continue; 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci rcu_read_unlock(); 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci /* 57162306a36Sopenharmony_ci * in case both root's behavior and devcg is allow, a new 57262306a36Sopenharmony_ci * restriction means adding to the exception list 57362306a36Sopenharmony_ci */ 57462306a36Sopenharmony_ci if (devcg_root->behavior == DEVCG_DEFAULT_ALLOW && 57562306a36Sopenharmony_ci devcg->behavior == DEVCG_DEFAULT_ALLOW) { 57662306a36Sopenharmony_ci rc = dev_exception_add(devcg, ex); 57762306a36Sopenharmony_ci if (rc) 57862306a36Sopenharmony_ci return rc; 57962306a36Sopenharmony_ci } else { 58062306a36Sopenharmony_ci /* 58162306a36Sopenharmony_ci * in the other possible cases: 58262306a36Sopenharmony_ci * root's behavior: allow, devcg's: deny 58362306a36Sopenharmony_ci * root's behavior: deny, devcg's: deny 58462306a36Sopenharmony_ci * the exception will be removed 58562306a36Sopenharmony_ci */ 58662306a36Sopenharmony_ci dev_exception_rm(devcg, ex); 58762306a36Sopenharmony_ci } 58862306a36Sopenharmony_ci revalidate_active_exceptions(devcg); 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci rcu_read_lock(); 59162306a36Sopenharmony_ci } 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci rcu_read_unlock(); 59462306a36Sopenharmony_ci return rc; 59562306a36Sopenharmony_ci} 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci/* 59862306a36Sopenharmony_ci * Modify the exception list using allow/deny rules. 59962306a36Sopenharmony_ci * CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD 60062306a36Sopenharmony_ci * so we can give a container CAP_MKNOD to let it create devices but not 60162306a36Sopenharmony_ci * modify the exception list. 60262306a36Sopenharmony_ci * It seems likely we'll want to add a CAP_CONTAINER capability to allow 60362306a36Sopenharmony_ci * us to also grant CAP_SYS_ADMIN to containers without giving away the 60462306a36Sopenharmony_ci * device exception list controls, but for now we'll stick with CAP_SYS_ADMIN 60562306a36Sopenharmony_ci * 60662306a36Sopenharmony_ci * Taking rules away is always allowed (given CAP_SYS_ADMIN). Granting 60762306a36Sopenharmony_ci * new access is only allowed if you're in the top-level cgroup, or your 60862306a36Sopenharmony_ci * parent cgroup has the access you're asking for. 60962306a36Sopenharmony_ci */ 61062306a36Sopenharmony_cistatic int devcgroup_update_access(struct dev_cgroup *devcgroup, 61162306a36Sopenharmony_ci int filetype, char *buffer) 61262306a36Sopenharmony_ci{ 61362306a36Sopenharmony_ci const char *b; 61462306a36Sopenharmony_ci char temp[12]; /* 11 + 1 characters needed for a u32 */ 61562306a36Sopenharmony_ci int count, rc = 0; 61662306a36Sopenharmony_ci struct dev_exception_item ex; 61762306a36Sopenharmony_ci struct dev_cgroup *parent = css_to_devcgroup(devcgroup->css.parent); 61862306a36Sopenharmony_ci struct dev_cgroup tmp_devcgrp; 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ci if (!capable(CAP_SYS_ADMIN)) 62162306a36Sopenharmony_ci return -EPERM; 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci memset(&ex, 0, sizeof(ex)); 62462306a36Sopenharmony_ci memset(&tmp_devcgrp, 0, sizeof(tmp_devcgrp)); 62562306a36Sopenharmony_ci b = buffer; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci switch (*b) { 62862306a36Sopenharmony_ci case 'a': 62962306a36Sopenharmony_ci switch (filetype) { 63062306a36Sopenharmony_ci case DEVCG_ALLOW: 63162306a36Sopenharmony_ci if (css_has_online_children(&devcgroup->css)) 63262306a36Sopenharmony_ci return -EINVAL; 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_ci if (!may_allow_all(parent)) 63562306a36Sopenharmony_ci return -EPERM; 63662306a36Sopenharmony_ci if (!parent) { 63762306a36Sopenharmony_ci devcgroup->behavior = DEVCG_DEFAULT_ALLOW; 63862306a36Sopenharmony_ci dev_exception_clean(devcgroup); 63962306a36Sopenharmony_ci break; 64062306a36Sopenharmony_ci } 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci INIT_LIST_HEAD(&tmp_devcgrp.exceptions); 64362306a36Sopenharmony_ci rc = dev_exceptions_copy(&tmp_devcgrp.exceptions, 64462306a36Sopenharmony_ci &devcgroup->exceptions); 64562306a36Sopenharmony_ci if (rc) 64662306a36Sopenharmony_ci return rc; 64762306a36Sopenharmony_ci dev_exception_clean(devcgroup); 64862306a36Sopenharmony_ci rc = dev_exceptions_copy(&devcgroup->exceptions, 64962306a36Sopenharmony_ci &parent->exceptions); 65062306a36Sopenharmony_ci if (rc) { 65162306a36Sopenharmony_ci dev_exceptions_move(&devcgroup->exceptions, 65262306a36Sopenharmony_ci &tmp_devcgrp.exceptions); 65362306a36Sopenharmony_ci return rc; 65462306a36Sopenharmony_ci } 65562306a36Sopenharmony_ci devcgroup->behavior = DEVCG_DEFAULT_ALLOW; 65662306a36Sopenharmony_ci dev_exception_clean(&tmp_devcgrp); 65762306a36Sopenharmony_ci break; 65862306a36Sopenharmony_ci case DEVCG_DENY: 65962306a36Sopenharmony_ci if (css_has_online_children(&devcgroup->css)) 66062306a36Sopenharmony_ci return -EINVAL; 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci dev_exception_clean(devcgroup); 66362306a36Sopenharmony_ci devcgroup->behavior = DEVCG_DEFAULT_DENY; 66462306a36Sopenharmony_ci break; 66562306a36Sopenharmony_ci default: 66662306a36Sopenharmony_ci return -EINVAL; 66762306a36Sopenharmony_ci } 66862306a36Sopenharmony_ci return 0; 66962306a36Sopenharmony_ci case 'b': 67062306a36Sopenharmony_ci ex.type = DEVCG_DEV_BLOCK; 67162306a36Sopenharmony_ci break; 67262306a36Sopenharmony_ci case 'c': 67362306a36Sopenharmony_ci ex.type = DEVCG_DEV_CHAR; 67462306a36Sopenharmony_ci break; 67562306a36Sopenharmony_ci default: 67662306a36Sopenharmony_ci return -EINVAL; 67762306a36Sopenharmony_ci } 67862306a36Sopenharmony_ci b++; 67962306a36Sopenharmony_ci if (!isspace(*b)) 68062306a36Sopenharmony_ci return -EINVAL; 68162306a36Sopenharmony_ci b++; 68262306a36Sopenharmony_ci if (*b == '*') { 68362306a36Sopenharmony_ci ex.major = ~0; 68462306a36Sopenharmony_ci b++; 68562306a36Sopenharmony_ci } else if (isdigit(*b)) { 68662306a36Sopenharmony_ci memset(temp, 0, sizeof(temp)); 68762306a36Sopenharmony_ci for (count = 0; count < sizeof(temp) - 1; count++) { 68862306a36Sopenharmony_ci temp[count] = *b; 68962306a36Sopenharmony_ci b++; 69062306a36Sopenharmony_ci if (!isdigit(*b)) 69162306a36Sopenharmony_ci break; 69262306a36Sopenharmony_ci } 69362306a36Sopenharmony_ci rc = kstrtou32(temp, 10, &ex.major); 69462306a36Sopenharmony_ci if (rc) 69562306a36Sopenharmony_ci return -EINVAL; 69662306a36Sopenharmony_ci } else { 69762306a36Sopenharmony_ci return -EINVAL; 69862306a36Sopenharmony_ci } 69962306a36Sopenharmony_ci if (*b != ':') 70062306a36Sopenharmony_ci return -EINVAL; 70162306a36Sopenharmony_ci b++; 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci /* read minor */ 70462306a36Sopenharmony_ci if (*b == '*') { 70562306a36Sopenharmony_ci ex.minor = ~0; 70662306a36Sopenharmony_ci b++; 70762306a36Sopenharmony_ci } else if (isdigit(*b)) { 70862306a36Sopenharmony_ci memset(temp, 0, sizeof(temp)); 70962306a36Sopenharmony_ci for (count = 0; count < sizeof(temp) - 1; count++) { 71062306a36Sopenharmony_ci temp[count] = *b; 71162306a36Sopenharmony_ci b++; 71262306a36Sopenharmony_ci if (!isdigit(*b)) 71362306a36Sopenharmony_ci break; 71462306a36Sopenharmony_ci } 71562306a36Sopenharmony_ci rc = kstrtou32(temp, 10, &ex.minor); 71662306a36Sopenharmony_ci if (rc) 71762306a36Sopenharmony_ci return -EINVAL; 71862306a36Sopenharmony_ci } else { 71962306a36Sopenharmony_ci return -EINVAL; 72062306a36Sopenharmony_ci } 72162306a36Sopenharmony_ci if (!isspace(*b)) 72262306a36Sopenharmony_ci return -EINVAL; 72362306a36Sopenharmony_ci for (b++, count = 0; count < 3; count++, b++) { 72462306a36Sopenharmony_ci switch (*b) { 72562306a36Sopenharmony_ci case 'r': 72662306a36Sopenharmony_ci ex.access |= DEVCG_ACC_READ; 72762306a36Sopenharmony_ci break; 72862306a36Sopenharmony_ci case 'w': 72962306a36Sopenharmony_ci ex.access |= DEVCG_ACC_WRITE; 73062306a36Sopenharmony_ci break; 73162306a36Sopenharmony_ci case 'm': 73262306a36Sopenharmony_ci ex.access |= DEVCG_ACC_MKNOD; 73362306a36Sopenharmony_ci break; 73462306a36Sopenharmony_ci case '\n': 73562306a36Sopenharmony_ci case '\0': 73662306a36Sopenharmony_ci count = 3; 73762306a36Sopenharmony_ci break; 73862306a36Sopenharmony_ci default: 73962306a36Sopenharmony_ci return -EINVAL; 74062306a36Sopenharmony_ci } 74162306a36Sopenharmony_ci } 74262306a36Sopenharmony_ci 74362306a36Sopenharmony_ci switch (filetype) { 74462306a36Sopenharmony_ci case DEVCG_ALLOW: 74562306a36Sopenharmony_ci /* 74662306a36Sopenharmony_ci * If the default policy is to allow by default, try to remove 74762306a36Sopenharmony_ci * an matching exception instead. And be silent about it: we 74862306a36Sopenharmony_ci * don't want to break compatibility 74962306a36Sopenharmony_ci */ 75062306a36Sopenharmony_ci if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { 75162306a36Sopenharmony_ci /* Check if the parent allows removing it first */ 75262306a36Sopenharmony_ci if (!parent_allows_removal(devcgroup, &ex)) 75362306a36Sopenharmony_ci return -EPERM; 75462306a36Sopenharmony_ci dev_exception_rm(devcgroup, &ex); 75562306a36Sopenharmony_ci break; 75662306a36Sopenharmony_ci } 75762306a36Sopenharmony_ci 75862306a36Sopenharmony_ci if (!parent_has_perm(devcgroup, &ex)) 75962306a36Sopenharmony_ci return -EPERM; 76062306a36Sopenharmony_ci rc = dev_exception_add(devcgroup, &ex); 76162306a36Sopenharmony_ci break; 76262306a36Sopenharmony_ci case DEVCG_DENY: 76362306a36Sopenharmony_ci /* 76462306a36Sopenharmony_ci * If the default policy is to deny by default, try to remove 76562306a36Sopenharmony_ci * an matching exception instead. And be silent about it: we 76662306a36Sopenharmony_ci * don't want to break compatibility 76762306a36Sopenharmony_ci */ 76862306a36Sopenharmony_ci if (devcgroup->behavior == DEVCG_DEFAULT_DENY) 76962306a36Sopenharmony_ci dev_exception_rm(devcgroup, &ex); 77062306a36Sopenharmony_ci else 77162306a36Sopenharmony_ci rc = dev_exception_add(devcgroup, &ex); 77262306a36Sopenharmony_ci 77362306a36Sopenharmony_ci if (rc) 77462306a36Sopenharmony_ci break; 77562306a36Sopenharmony_ci /* we only propagate new restrictions */ 77662306a36Sopenharmony_ci rc = propagate_exception(devcgroup, &ex); 77762306a36Sopenharmony_ci break; 77862306a36Sopenharmony_ci default: 77962306a36Sopenharmony_ci rc = -EINVAL; 78062306a36Sopenharmony_ci } 78162306a36Sopenharmony_ci return rc; 78262306a36Sopenharmony_ci} 78362306a36Sopenharmony_ci 78462306a36Sopenharmony_cistatic ssize_t devcgroup_access_write(struct kernfs_open_file *of, 78562306a36Sopenharmony_ci char *buf, size_t nbytes, loff_t off) 78662306a36Sopenharmony_ci{ 78762306a36Sopenharmony_ci int retval; 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_ci mutex_lock(&devcgroup_mutex); 79062306a36Sopenharmony_ci retval = devcgroup_update_access(css_to_devcgroup(of_css(of)), 79162306a36Sopenharmony_ci of_cft(of)->private, strstrip(buf)); 79262306a36Sopenharmony_ci mutex_unlock(&devcgroup_mutex); 79362306a36Sopenharmony_ci return retval ?: nbytes; 79462306a36Sopenharmony_ci} 79562306a36Sopenharmony_ci 79662306a36Sopenharmony_cistatic struct cftype dev_cgroup_files[] = { 79762306a36Sopenharmony_ci { 79862306a36Sopenharmony_ci .name = "allow", 79962306a36Sopenharmony_ci .write = devcgroup_access_write, 80062306a36Sopenharmony_ci .private = DEVCG_ALLOW, 80162306a36Sopenharmony_ci }, 80262306a36Sopenharmony_ci { 80362306a36Sopenharmony_ci .name = "deny", 80462306a36Sopenharmony_ci .write = devcgroup_access_write, 80562306a36Sopenharmony_ci .private = DEVCG_DENY, 80662306a36Sopenharmony_ci }, 80762306a36Sopenharmony_ci { 80862306a36Sopenharmony_ci .name = "list", 80962306a36Sopenharmony_ci .seq_show = devcgroup_seq_show, 81062306a36Sopenharmony_ci .private = DEVCG_LIST, 81162306a36Sopenharmony_ci }, 81262306a36Sopenharmony_ci { } /* terminate */ 81362306a36Sopenharmony_ci}; 81462306a36Sopenharmony_ci 81562306a36Sopenharmony_cistruct cgroup_subsys devices_cgrp_subsys = { 81662306a36Sopenharmony_ci .css_alloc = devcgroup_css_alloc, 81762306a36Sopenharmony_ci .css_free = devcgroup_css_free, 81862306a36Sopenharmony_ci .css_online = devcgroup_online, 81962306a36Sopenharmony_ci .css_offline = devcgroup_offline, 82062306a36Sopenharmony_ci .legacy_cftypes = dev_cgroup_files, 82162306a36Sopenharmony_ci}; 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci/** 82462306a36Sopenharmony_ci * devcgroup_legacy_check_permission - checks if an inode operation is permitted 82562306a36Sopenharmony_ci * @type: device type 82662306a36Sopenharmony_ci * @major: device major number 82762306a36Sopenharmony_ci * @minor: device minor number 82862306a36Sopenharmony_ci * @access: combination of DEVCG_ACC_WRITE, DEVCG_ACC_READ and DEVCG_ACC_MKNOD 82962306a36Sopenharmony_ci * 83062306a36Sopenharmony_ci * returns 0 on success, -EPERM case the operation is not permitted 83162306a36Sopenharmony_ci */ 83262306a36Sopenharmony_cistatic int devcgroup_legacy_check_permission(short type, u32 major, u32 minor, 83362306a36Sopenharmony_ci short access) 83462306a36Sopenharmony_ci{ 83562306a36Sopenharmony_ci struct dev_cgroup *dev_cgroup; 83662306a36Sopenharmony_ci bool rc; 83762306a36Sopenharmony_ci 83862306a36Sopenharmony_ci rcu_read_lock(); 83962306a36Sopenharmony_ci dev_cgroup = task_devcgroup(current); 84062306a36Sopenharmony_ci if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) 84162306a36Sopenharmony_ci /* Can't match any of the exceptions, even partially */ 84262306a36Sopenharmony_ci rc = !match_exception_partial(&dev_cgroup->exceptions, 84362306a36Sopenharmony_ci type, major, minor, access); 84462306a36Sopenharmony_ci else 84562306a36Sopenharmony_ci /* Need to match completely one exception to be allowed */ 84662306a36Sopenharmony_ci rc = match_exception(&dev_cgroup->exceptions, type, major, 84762306a36Sopenharmony_ci minor, access); 84862306a36Sopenharmony_ci rcu_read_unlock(); 84962306a36Sopenharmony_ci 85062306a36Sopenharmony_ci if (!rc) 85162306a36Sopenharmony_ci return -EPERM; 85262306a36Sopenharmony_ci 85362306a36Sopenharmony_ci return 0; 85462306a36Sopenharmony_ci} 85562306a36Sopenharmony_ci 85662306a36Sopenharmony_ci#endif /* CONFIG_CGROUP_DEVICE */ 85762306a36Sopenharmony_ci 85862306a36Sopenharmony_ci#if defined(CONFIG_CGROUP_DEVICE) || defined(CONFIG_CGROUP_BPF) 85962306a36Sopenharmony_ci 86062306a36Sopenharmony_ciint devcgroup_check_permission(short type, u32 major, u32 minor, short access) 86162306a36Sopenharmony_ci{ 86262306a36Sopenharmony_ci int rc = BPF_CGROUP_RUN_PROG_DEVICE_CGROUP(type, major, minor, access); 86362306a36Sopenharmony_ci 86462306a36Sopenharmony_ci if (rc) 86562306a36Sopenharmony_ci return rc; 86662306a36Sopenharmony_ci 86762306a36Sopenharmony_ci #ifdef CONFIG_CGROUP_DEVICE 86862306a36Sopenharmony_ci return devcgroup_legacy_check_permission(type, major, minor, access); 86962306a36Sopenharmony_ci 87062306a36Sopenharmony_ci #else /* CONFIG_CGROUP_DEVICE */ 87162306a36Sopenharmony_ci return 0; 87262306a36Sopenharmony_ci 87362306a36Sopenharmony_ci #endif /* CONFIG_CGROUP_DEVICE */ 87462306a36Sopenharmony_ci} 87562306a36Sopenharmony_ciEXPORT_SYMBOL(devcgroup_check_permission); 87662306a36Sopenharmony_ci#endif /* defined(CONFIG_CGROUP_DEVICE) || defined(CONFIG_CGROUP_BPF) */ 877