diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /security/device_cgroup.c | |
download | linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip |
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski:
"Core:
- Add dedicated kmem_cache for typical/small skb->head, avoid having
to access struct page at kfree time, and improve memory use.
- Introduce sysctl to set default RPS configuration for new netdevs.
- Define Netlink protocol specification format which can be used to
describe messages used by each family and auto-generate parsers.
Add tools for generating kernel data structures and uAPI headers.
- Expose all net/core sysctls inside netns.
- Remove 4s sleep in netpoll if carrier is instantly detected on
boot.
- Add configurable limit of MDB entries per port, and port-vlan.
- Continue populating drop reasons throughout the stack.
- Retire a handful of legacy Qdiscs and classifiers.
Protocols:
- Support IPv4 big TCP (TSO frames larger than 64kB).
- Add IP_LOCAL_PORT_RANGE socket option, to control local port range
on socket by socket basis.
- Track and report in procfs number of MPTCP sockets used.
- Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path
manager.
- IPv6: don't check net.ipv6.route.max_size and rely on garbage
collection to free memory (similarly to IPv4).
- Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986).
- ICMP: add per-rate limit counters.
- Add support for user scanning requests in ieee802154.
- Remove static WEP support.
- Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate
reporting.
- WiFi 7 EHT channel puncturing support (client & AP).
BPF:
- Add a rbtree data structure following the "next-gen data structure"
precedent set by recently added linked list, that is, by using
kfunc + kptr instead of adding a new BPF map type.
- Expose XDP hints via kfuncs with initial support for RX hash and
timestamp metadata.
- Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to
better support decap on GRE tunnel devices not operating in collect
metadata.
- Improve x86 JIT's codegen for PROBE_MEM runtime error checks.
- Remove the need for trace_printk_lock for bpf_trace_printk and
bpf_trace_vprintk helpers.
- Extend libbpf's bpf_tracing.h support for tracing arguments of
kprobes/uprobes and syscall as a special case.
- Significantly reduce the search time for module symbols by
livepatch and BPF.
- Enable cpumasks to be used as kptrs, which is useful for tracing
programs tracking which tasks end up running on which CPUs in
different time intervals.
- Add support for BPF trampoline on s390x and riscv64.
- Add capability to export the XDP features supported by the NIC.
- Add __bpf_kfunc tag for marking kernel functions as kfuncs.
- Add cgroup.memory=nobpf kernel parameter option to disable BPF
memory accounting for container environments.
Netfilter:
- Remove the CLUSTERIP target. It has been marked as obsolete for
years, and we still have WARN splats wrt races of the out-of-band
/proc interface installed by this target.
- Add 'destroy' commands to nf_tables. They are identical to the
existing 'delete' commands, but do not return an error if the
referenced object (set, chain, rule...) did not exist.
Driver API:
- Improve cpumask_local_spread() locality to help NICs set the right
IRQ affinity on AMD platforms.
- Separate C22 and C45 MDIO bus transactions more clearly.
- Introduce new DCB table to control DSCP rewrite on egress.
- Support configuration of Physical Layer Collision Avoidance (PLCA)
Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of
shared medium Ethernet.
- Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing
preemption of low priority frames by high priority frames.
- Add support for controlling MACSec offload using netlink SET.
- Rework devlink instance refcounts to allow registration and
de-registration under the instance lock. Split the code into
multiple files, drop some of the unnecessarily granular locks and
factor out common parts of netlink operation handling.
- Add TX frame aggregation parameters (for USB drivers).
- Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning
messages with notifications for debug.
- Allow offloading of UDP NEW connections via act_ct.
- Add support for per action HW stats in TC.
- Support hardware miss to TC action (continue processing in SW from
a specific point in the action chain).
- Warn if old Wireless Extension user space interface is used with
modern cfg80211/mac80211 drivers. Do not support Wireless
Extensions for Wi-Fi 7 devices at all. Everyone should switch to
using nl80211 interface instead.
- Improve the CAN bit timing configuration. Use extack to return
error messages directly to user space, update the SJW handling,
including the definition of a new default value that will benefit
CAN-FD controllers, by increasing their oscillator tolerance.
New hardware / drivers:
- Ethernet:
- nVidia BlueField-3 support (control traffic driver)
- Ethernet support for imx93 SoCs
- Motorcomm yt8531 gigabit Ethernet PHY
- onsemi NCN26000 10BASE-T1S PHY (with support for PLCA)
- Microchip LAN8841 PHY (incl. cable diagnostics and PTP)
- Amlogic gxl MDIO mux
- WiFi:
- RealTek RTL8188EU (rtl8xxxu)
- Qualcomm Wi-Fi 7 devices (ath12k)
- CAN:
- Renesas R-Car V4H
Drivers:
- Bluetooth:
- Set Per Platform Antenna Gain (PPAG) for Intel controllers.
- Ethernet NICs:
- Intel (1G, igc):
- support TSN / Qbv / packet scheduling features of i226 model
- Intel (100G, ice):
- use GNSS subsystem instead of TTY
- multi-buffer XDP support
- extend support for GPIO pins to E823 devices
- nVidia/Mellanox:
- update the shared buffer configuration on PFC commands
- implement PTP adjphase function for HW offset control
- TC support for Geneve and GRE with VF tunnel offload
- more efficient crypto key management method
- multi-port eswitch support
- Netronome/Corigine:
- add DCB IEEE support
- support IPsec offloading for NFP3800
- Freescale/NXP (enetc):
- support XDP_REDIRECT for XDP non-linear buffers
- improve reconfig, avoid link flap and waiting for idle
- support MAC Merge layer
- Other NICs:
- sfc/ef100: add basic devlink support for ef100
- ionic: rx_push mode operation (writing descriptors via MMIO)
- bnxt: use the auxiliary bus abstraction for RDMA
- r8169: disable ASPM and reset bus in case of tx timeout
- cpsw: support QSGMII mode for J721e CPSW9G
- cpts: support pulse-per-second output
- ngbe: add an mdio bus driver
- usbnet: optimize usbnet_bh() by avoiding unnecessary queuing
- r8152: handle devices with FW with NCM support
- amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation
- virtio-net: support multi buffer XDP
- virtio/vsock: replace virtio_vsock_pkt with sk_buff
- tsnep: XDP support
- Ethernet high-speed switches:
- nVidia/Mellanox (mlxsw):
- add support for latency TLV (in FW control messages)
- Microchip (sparx5):
- separate explicit and implicit traffic forwarding rules, make
the implicit rules always active
- add support for egress DSCP rewrite
- IS0 VCAP support (Ingress Classification)
- IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS
etc.)
- ES2 VCAP support (Egress Access Control)
- support for Per-Stream Filtering and Policing (802.1Q,
8.6.5.1)
- Ethernet embedded switches:
- Marvell (mv88e6xxx):
- add MAB (port auth) offload support
- enable PTP receive for mv88e6390
- NXP (ocelot):
- support MAC Merge layer
- support for the the vsc7512 internal copper phys
- Microchip:
- lan9303: convert to PHYLINK
- lan966x: support TC flower filter statistics
- lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x
- lan937x: support Credit Based Shaper configuration
- ksz9477: support Energy Efficient Ethernet
- other:
- qca8k: convert to regmap read/write API, use bulk operations
- rswitch: Improve TX timestamp accuracy
- Intel WiFi (iwlwifi):
- EHT (Wi-Fi 7) rate reporting
- STEP equalizer support: transfer some STEP (connection to radio
on platforms with integrated wifi) related parameters from the
BIOS to the firmware.
- Qualcomm 802.11ax WiFi (ath11k):
- IPQ5018 support
- Fine Timing Measurement (FTM) responder role support
- channel 177 support
- MediaTek WiFi (mt76):
- per-PHY LED support
- mt7996: EHT (Wi-Fi 7) support
- Wireless Ethernet Dispatch (WED) reset support
- switch to using page pool allocator
- RealTek WiFi (rtw89):
- support new version of Bluetooth co-existance
- Mobile:
- rmnet: support TX aggregation"
* tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits)
page_pool: add a comment explaining the fragment counter usage
net: ethtool: fix __ethtool_dev_mm_supported() implementation
ethtool: pse-pd: Fix double word in comments
xsk: add linux/vmalloc.h to xsk.c
sefltests: netdevsim: wait for devlink instance after netns removal
selftest: fib_tests: Always cleanup before exit
net/mlx5e: Align IPsec ASO result memory to be as required by hardware
net/mlx5e: TC, Set CT miss to the specific ct action instance
net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG
net/mlx5: Refactor tc miss handling to a single function
net/mlx5: Kconfig: Make tc offload depend on tc skb extension
net/sched: flower: Support hardware miss to tc action
net/sched: flower: Move filter handle initialization earlier
net/sched: cls_api: Support hardware miss to tc action
net/sched: Rename user cookie and act cookie
sfc: fix builds without CONFIG_RTC_LIB
sfc: clean up some inconsistent indentings
net/mlx4_en: Introduce flexible array to silence overflow warning
net: lan966x: Fix possible deadlock inside PTP
net/ulp: Remove redundant ->clone() test in inet_clone_ulp().
...
Diffstat (limited to 'security/device_cgroup.c')
-rw-r--r-- | security/device_cgroup.c | 877 |
1 files changed, 877 insertions, 0 deletions
diff --git a/security/device_cgroup.c b/security/device_cgroup.c new file mode 100644 index 000000000..bef2b9285 --- /dev/null +++ b/security/device_cgroup.c @@ -0,0 +1,877 @@ +// 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> +#include <linux/list.h> +#include <linux/uaccess.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/rcupdate.h> +#include <linux/mutex.h> + +#ifdef CONFIG_CGROUP_DEVICE + +static DEFINE_MUTEX(devcgroup_mutex); + +enum devcg_behavior { + DEVCG_DEFAULT_NONE, + DEVCG_DEFAULT_ALLOW, + DEVCG_DEFAULT_DENY, +}; + +/* + * exception list locking rules: + * hold devcgroup_mutex for update/read. + * hold rcu_read_lock() for read. + */ + +struct dev_exception_item { + u32 major, minor; + short type; + short access; + struct list_head list; + struct rcu_head rcu; +}; + +struct dev_cgroup { + struct cgroup_subsys_state css; + struct list_head exceptions; + enum devcg_behavior behavior; +}; + +static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s) +{ + return s ? container_of(s, struct dev_cgroup, css) : NULL; +} + +static inline struct dev_cgroup *task_devcgroup(struct task_struct *task) +{ + return css_to_devcgroup(task_css(task, devices_cgrp_id)); +} + +/* + * called under devcgroup_mutex + */ +static int dev_exceptions_copy(struct list_head *dest, struct list_head *orig) +{ + struct dev_exception_item *ex, *tmp, *new; + + lockdep_assert_held(&devcgroup_mutex); + + list_for_each_entry(ex, orig, list) { + new = kmemdup(ex, sizeof(*ex), GFP_KERNEL); + if (!new) + goto free_and_exit; + list_add_tail(&new->list, dest); + } + + return 0; + +free_and_exit: + list_for_each_entry_safe(ex, tmp, dest, list) { + list_del(&ex->list); + kfree(ex); + } + 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 + */ +static int dev_exception_add(struct dev_cgroup *dev_cgroup, + struct dev_exception_item *ex) +{ + struct dev_exception_item *excopy, *walk; + + lockdep_assert_held(&devcgroup_mutex); + + excopy = kmemdup(ex, sizeof(*ex), GFP_KERNEL); + if (!excopy) + return -ENOMEM; + + list_for_each_entry(walk, &dev_cgroup->exceptions, list) { + if (walk->type != ex->type) + continue; + if (walk->major != ex->major) + continue; + if (walk->minor != ex->minor) + continue; + + walk->access |= ex->access; + kfree(excopy); + excopy = NULL; + } + + if (excopy != NULL) + list_add_tail_rcu(&excopy->list, &dev_cgroup->exceptions); + return 0; +} + +/* + * called under devcgroup_mutex + */ +static void dev_exception_rm(struct dev_cgroup *dev_cgroup, + struct dev_exception_item *ex) +{ + struct dev_exception_item *walk, *tmp; + + lockdep_assert_held(&devcgroup_mutex); + + list_for_each_entry_safe(walk, tmp, &dev_cgroup->exceptions, list) { + if (walk->type != ex->type) + continue; + if (walk->major != ex->major) + continue; + if (walk->minor != ex->minor) + continue; + + walk->access &= ~ex->access; + if (!walk->access) { + list_del_rcu(&walk->list); + kfree_rcu(walk, rcu); + } + } +} + +static void __dev_exception_clean(struct dev_cgroup *dev_cgroup) +{ + struct dev_exception_item *ex, *tmp; + + list_for_each_entry_safe(ex, tmp, &dev_cgroup->exceptions, list) { + list_del_rcu(&ex->list); + kfree_rcu(ex, rcu); + } +} + +/** + * dev_exception_clean - frees all entries of the exception list + * @dev_cgroup: dev_cgroup with the exception list to be cleaned + * + * called under devcgroup_mutex + */ +static void dev_exception_clean(struct dev_cgroup *dev_cgroup) +{ + lockdep_assert_held(&devcgroup_mutex); + + __dev_exception_clean(dev_cgroup); +} + +static inline bool is_devcg_online(const struct dev_cgroup *devcg) +{ + return (devcg->behavior != DEVCG_DEFAULT_NONE); +} + +/** + * devcgroup_online - initializes devcgroup's behavior and exceptions based on + * parent's + * @css: css getting online + * returns 0 in case of success, error code otherwise + */ +static int devcgroup_online(struct cgroup_subsys_state *css) +{ + struct dev_cgroup *dev_cgroup = css_to_devcgroup(css); + struct dev_cgroup *parent_dev_cgroup = css_to_devcgroup(css->parent); + int ret = 0; + + mutex_lock(&devcgroup_mutex); + + if (parent_dev_cgroup == NULL) + dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW; + else { + ret = dev_exceptions_copy(&dev_cgroup->exceptions, + &parent_dev_cgroup->exceptions); + if (!ret) + dev_cgroup->behavior = parent_dev_cgroup->behavior; + } + mutex_unlock(&devcgroup_mutex); + + return ret; +} + +static void devcgroup_offline(struct cgroup_subsys_state *css) +{ + struct dev_cgroup *dev_cgroup = css_to_devcgroup(css); + + mutex_lock(&devcgroup_mutex); + dev_cgroup->behavior = DEVCG_DEFAULT_NONE; + mutex_unlock(&devcgroup_mutex); +} + +/* + * called from kernel/cgroup.c with cgroup_lock() held. + */ +static struct cgroup_subsys_state * +devcgroup_css_alloc(struct cgroup_subsys_state *parent_css) +{ + struct dev_cgroup *dev_cgroup; + + dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL); + if (!dev_cgroup) + return ERR_PTR(-ENOMEM); + INIT_LIST_HEAD(&dev_cgroup->exceptions); + dev_cgroup->behavior = DEVCG_DEFAULT_NONE; + + return &dev_cgroup->css; +} + +static void devcgroup_css_free(struct cgroup_subsys_state *css) +{ + struct dev_cgroup *dev_cgroup = css_to_devcgroup(css); + + __dev_exception_clean(dev_cgroup); + kfree(dev_cgroup); +} + +#define DEVCG_ALLOW 1 +#define DEVCG_DENY 2 +#define DEVCG_LIST 3 + +#define MAJMINLEN 13 +#define ACCLEN 4 + +static void set_access(char *acc, short access) +{ + int idx = 0; + memset(acc, 0, ACCLEN); + if (access & DEVCG_ACC_READ) + acc[idx++] = 'r'; + if (access & DEVCG_ACC_WRITE) + acc[idx++] = 'w'; + if (access & DEVCG_ACC_MKNOD) + acc[idx++] = 'm'; +} + +static char type_to_char(short type) +{ + if (type == DEVCG_DEV_ALL) + return 'a'; + if (type == DEVCG_DEV_CHAR) + return 'c'; + if (type == DEVCG_DEV_BLOCK) + return 'b'; + return 'X'; +} + +static void set_majmin(char *str, unsigned m) +{ + if (m == ~0) + strcpy(str, "*"); + else + sprintf(str, "%u", m); +} + +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(); + /* + * To preserve the compatibility: + * - Only show the "all devices" when the default policy is to allow + * - List the exceptions in case the default policy is to deny + * This way, the file remains as a "whitelist of devices" + */ + if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { + set_access(acc, DEVCG_ACC_MASK); + set_majmin(maj, ~0); + set_majmin(min, ~0); + seq_printf(m, "%c %s:%s %s\n", type_to_char(DEVCG_DEV_ALL), + maj, min, acc); + } 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); + } + } + rcu_read_unlock(); + + return 0; +} + +/** + * match_exception - iterates the exception list trying to find a complete match + * @exceptions: list of exceptions + * @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 (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. + * + * Return: true in case it matches an exception completely + */ +static bool match_exception(struct list_head *exceptions, short type, + u32 major, u32 minor, short access) +{ + struct dev_exception_item *ex; + + list_for_each_entry_rcu(ex, exceptions, list) { + if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK)) + continue; + if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR)) + continue; + if (ex->major != ~0 && ex->major != major) + continue; + if (ex->minor != ~0 && ex->minor != minor) + continue; + /* provided access cannot have more than the exception rule */ + if (access & (~ex->access)) + continue; + return true; + } + return false; +} + +/** + * match_exception_partial - iterates the exception list trying to find a partial match + * @exceptions: list of exceptions + * @type: device type (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 (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 + * used to make sure no extra access is being granted that is forbidden by + * any of the exception list. + * + * Return: true in case the provided range mat matches an exception completely + */ +static bool match_exception_partial(struct list_head *exceptions, short type, + u32 major, u32 minor, short access) +{ + struct dev_exception_item *ex; + + list_for_each_entry_rcu(ex, exceptions, list, + lockdep_is_held(&devcgroup_mutex)) { + if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK)) + continue; + if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR)) + continue; + /* + * We must be sure that both the exception and the provided + * range aren't masking all devices + */ + if (ex->major != ~0 && major != ~0 && ex->major != major) + continue; + if (ex->minor != ~0 && minor != ~0 && ex->minor != minor) + continue; + /* + * In order to make sure the provided range isn't matching + * an exception, all its access bits shouldn't match the + * exception's access bits + */ + if (!(access & ex->access)) + continue; + return true; + } + return false; +} + +/** + * verify_new_ex - verifies if a new exception is allowed by parent cgroup's permissions + * @dev_cgroup: dev cgroup to be tested against + * @refex: new exception + * @behavior: behavior of the exception's dev_cgroup + * + * This is used to make sure a child cgroup won't have more privileges + * than its parent + */ +static bool verify_new_ex(struct dev_cgroup *dev_cgroup, + struct dev_exception_item *refex, + enum devcg_behavior behavior) +{ + bool match = false; + + RCU_LOCKDEP_WARN(!rcu_read_lock_held() && + !lockdep_is_held(&devcgroup_mutex), + "device_cgroup:verify_new_ex called without proper synchronization"); + + if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) { + if (behavior == DEVCG_DEFAULT_ALLOW) { + /* + * new exception in the child doesn't matter, only + * adding extra restrictions + */ + return true; + } else { + /* + * new exception in the child will add more devices + * that can be acessed, so it can't match any of + * parent's exceptions, even slightly + */ + match = match_exception_partial(&dev_cgroup->exceptions, + refex->type, + refex->major, + refex->minor, + refex->access); + + if (match) + return false; + return true; + } + } else { + /* + * Only behavior == DEVCG_DEFAULT_DENY allowed here, therefore + * the new exception will add access to more devices and must + * be contained completely in an parent's exception to be + * allowed + */ + match = match_exception(&dev_cgroup->exceptions, refex->type, + refex->major, refex->minor, + refex->access); + + if (match) + /* parent has an exception that matches the proposed */ + return true; + else + return false; + } + return false; +} + +/* + * parent_has_perm: + * when adding a new allow rule to a device exception list, the rule + * must be allowed in the parent device + */ +static int parent_has_perm(struct dev_cgroup *childcg, + struct dev_exception_item *ex) +{ + struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent); + + if (!parent) + return 1; + return verify_new_ex(parent, ex, childcg->behavior); +} + +/** + * parent_allows_removal - verify if it's ok to remove an exception + * @childcg: child cgroup from where the exception will be removed + * @ex: exception being removed + * + * When removing an exception in cgroups with default ALLOW policy, it must + * be checked if removing it will give the child cgroup more access than the + * parent. + * + * Return: true if it's ok to remove exception, false otherwise + */ +static bool parent_allows_removal(struct dev_cgroup *childcg, + struct dev_exception_item *ex) +{ + struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent); + + if (!parent) + return true; + + /* It's always allowed to remove access to devices */ + if (childcg->behavior == DEVCG_DEFAULT_DENY) + return true; + + /* + * Make sure you're not removing part or a whole exception existing in + * the parent cgroup + */ + return !match_exception_partial(&parent->exceptions, ex->type, + ex->major, ex->minor, ex->access); +} + +/** + * may_allow_all - checks if it's possible to change the behavior to + * allow based on parent's rules. + * @parent: device cgroup's parent + * returns: != 0 in case it's allowed, 0 otherwise + */ +static inline int may_allow_all(struct dev_cgroup *parent) +{ + if (!parent) + return 1; + return parent->behavior == DEVCG_DEFAULT_ALLOW; +} + +/** + * revalidate_active_exceptions - walks through the active exception list and + * revalidates the exceptions based on parent's + * behavior and exceptions. The exceptions that + * are no longer valid will be removed. + * Called with devcgroup_mutex held. + * @devcg: cgroup which exceptions will be checked + * + * 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/admin-guide/cgroup-v1/devices.rst for more details. + */ +static void revalidate_active_exceptions(struct dev_cgroup *devcg) +{ + struct dev_exception_item *ex; + struct list_head *this, *tmp; + + list_for_each_safe(this, tmp, &devcg->exceptions) { + ex = container_of(this, struct dev_exception_item, list); + if (!parent_has_perm(devcg, ex)) + dev_exception_rm(devcg, ex); + } +} + +/** + * propagate_exception - propagates a new exception to the children + * @devcg_root: device cgroup that added a new exception + * @ex: new exception to be propagated + * + * returns: 0 in case of success, != 0 in case of error + */ +static int propagate_exception(struct dev_cgroup *devcg_root, + struct dev_exception_item *ex) +{ + struct cgroup_subsys_state *pos; + int rc = 0; + + rcu_read_lock(); + + css_for_each_descendant_pre(pos, &devcg_root->css) { + struct dev_cgroup *devcg = css_to_devcgroup(pos); + + /* + * Because devcgroup_mutex is held, no devcg will become + * online or offline during the tree walk (see on/offline + * methods), and online ones are safe to access outside RCU + * read lock without bumping refcnt. + */ + if (pos == &devcg_root->css || !is_devcg_online(devcg)) + continue; + + rcu_read_unlock(); + + /* + * in case both root's behavior and devcg is allow, a new + * restriction means adding to the exception list + */ + if (devcg_root->behavior == DEVCG_DEFAULT_ALLOW && + devcg->behavior == DEVCG_DEFAULT_ALLOW) { + rc = dev_exception_add(devcg, ex); + if (rc) + return rc; + } else { + /* + * in the other possible cases: + * root's behavior: allow, devcg's: deny + * root's behavior: deny, devcg's: deny + * the exception will be removed + */ + dev_exception_rm(devcg, ex); + } + revalidate_active_exceptions(devcg); + + rcu_read_lock(); + } + + rcu_read_unlock(); + return rc; +} + +/* + * Modify the exception list using allow/deny rules. + * CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD + * so we can give a container CAP_MKNOD to let it create devices but not + * modify the exception list. + * It seems likely we'll want to add a CAP_CONTAINER capability to allow + * us to also grant CAP_SYS_ADMIN to containers without giving away the + * device exception list controls, but for now we'll stick with CAP_SYS_ADMIN + * + * Taking rules away is always allowed (given CAP_SYS_ADMIN). Granting + * new access is only allowed if you're in the top-level cgroup, or your + * parent cgroup has the access you're asking for. + */ +static int devcgroup_update_access(struct dev_cgroup *devcgroup, + int filetype, char *buffer) +{ + const char *b; + char temp[12]; /* 11 + 1 characters needed for a u32 */ + int count, rc = 0; + struct dev_exception_item ex; + struct dev_cgroup *parent = css_to_devcgroup(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) { + case 'a': + switch (filetype) { + case DEVCG_ALLOW: + if (css_has_online_children(&devcgroup->css)) + return -EINVAL; + + if (!may_allow_all(parent)) + return -EPERM; + 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) { + 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)) + return -EINVAL; + + dev_exception_clean(devcgroup); + devcgroup->behavior = DEVCG_DEFAULT_DENY; + break; + default: + return -EINVAL; + } + return 0; + case 'b': + ex.type = DEVCG_DEV_BLOCK; + break; + case 'c': + ex.type = DEVCG_DEV_CHAR; + break; + default: + return -EINVAL; + } + b++; + if (!isspace(*b)) + return -EINVAL; + b++; + if (*b == '*') { + ex.major = ~0; + b++; + } else if (isdigit(*b)) { + memset(temp, 0, sizeof(temp)); + for (count = 0; count < sizeof(temp) - 1; count++) { + temp[count] = *b; + b++; + if (!isdigit(*b)) + break; + } + rc = kstrtou32(temp, 10, &ex.major); + if (rc) + return -EINVAL; + } else { + return -EINVAL; + } + if (*b != ':') + return -EINVAL; + b++; + + /* read minor */ + if (*b == '*') { + ex.minor = ~0; + b++; + } else if (isdigit(*b)) { + memset(temp, 0, sizeof(temp)); + for (count = 0; count < sizeof(temp) - 1; count++) { + temp[count] = *b; + b++; + if (!isdigit(*b)) + break; + } + rc = kstrtou32(temp, 10, &ex.minor); + if (rc) + return -EINVAL; + } else { + return -EINVAL; + } + if (!isspace(*b)) + return -EINVAL; + for (b++, count = 0; count < 3; count++, b++) { + switch (*b) { + case 'r': + ex.access |= DEVCG_ACC_READ; + break; + case 'w': + ex.access |= DEVCG_ACC_WRITE; + break; + case 'm': + ex.access |= DEVCG_ACC_MKNOD; + break; + case '\n': + case '\0': + count = 3; + break; + default: + return -EINVAL; + } + } + + switch (filetype) { + case DEVCG_ALLOW: + /* + * If the default policy is to allow by default, try to remove + * an matching exception instead. And be silent about it: we + * don't want to break compatibility + */ + if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { + /* Check if the parent allows removing it first */ + if (!parent_allows_removal(devcgroup, &ex)) + return -EPERM; + dev_exception_rm(devcgroup, &ex); + break; + } + + if (!parent_has_perm(devcgroup, &ex)) + return -EPERM; + rc = dev_exception_add(devcgroup, &ex); + break; + case DEVCG_DENY: + /* + * If the default policy is to deny by default, try to remove + * an matching exception instead. And be silent about it: we + * don't want to break compatibility + */ + if (devcgroup->behavior == DEVCG_DEFAULT_DENY) + dev_exception_rm(devcgroup, &ex); + else + rc = dev_exception_add(devcgroup, &ex); + + if (rc) + break; + /* we only propagate new restrictions */ + rc = propagate_exception(devcgroup, &ex); + break; + default: + rc = -EINVAL; + } + return rc; +} + +static ssize_t devcgroup_access_write(struct kernfs_open_file *of, + char *buf, size_t nbytes, loff_t off) +{ + int retval; + + mutex_lock(&devcgroup_mutex); + retval = devcgroup_update_access(css_to_devcgroup(of_css(of)), + of_cft(of)->private, strstrip(buf)); + mutex_unlock(&devcgroup_mutex); + return retval ?: nbytes; +} + +static struct cftype dev_cgroup_files[] = { + { + .name = "allow", + .write = devcgroup_access_write, + .private = DEVCG_ALLOW, + }, + { + .name = "deny", + .write = devcgroup_access_write, + .private = DEVCG_DENY, + }, + { + .name = "list", + .seq_show = devcgroup_seq_show, + .private = DEVCG_LIST, + }, + { } /* terminate */ +}; + +struct cgroup_subsys devices_cgrp_subsys = { + .css_alloc = devcgroup_css_alloc, + .css_free = devcgroup_css_free, + .css_online = devcgroup_online, + .css_offline = devcgroup_offline, + .legacy_cftypes = dev_cgroup_files, +}; + +/** + * devcgroup_legacy_check_permission - checks if an inode operation is permitted + * @dev_cgroup: the dev cgroup to be tested against + * @type: device type + * @major: device major number + * @minor: device minor number + * @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_legacy_check_permission(short type, u32 major, u32 minor, + short access) +{ + struct dev_cgroup *dev_cgroup; + bool rc; + + rcu_read_lock(); + dev_cgroup = task_devcgroup(current); + if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) + /* Can't match any of the exceptions, even partially */ + rc = !match_exception_partial(&dev_cgroup->exceptions, + type, major, minor, access); + else + /* Need to match completely one exception to be allowed */ + rc = match_exception(&dev_cgroup->exceptions, type, major, + minor, access); + rcu_read_unlock(); + + if (!rc) + return -EPERM; + + return 0; +} + +#endif /* CONFIG_CGROUP_DEVICE */ + +#if defined(CONFIG_CGROUP_DEVICE) || defined(CONFIG_CGROUP_BPF) + +int devcgroup_check_permission(short type, u32 major, u32 minor, short access) +{ + int rc = BPF_CGROUP_RUN_PROG_DEVICE_CGROUP(type, major, minor, access); + + if (rc) + return rc; + + #ifdef CONFIG_CGROUP_DEVICE + return devcgroup_legacy_check_permission(type, major, minor, access); + + #else /* CONFIG_CGROUP_DEVICE */ + return 0; + + #endif /* CONFIG_CGROUP_DEVICE */ +} +EXPORT_SYMBOL(devcgroup_check_permission); +#endif /* defined(CONFIG_CGROUP_DEVICE) || defined(CONFIG_CGROUP_BPF) */ |