From 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 21 Feb 2023 18:24:12 -0800 Subject: Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next 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(). ... --- security/device_cgroup.c | 877 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 877 insertions(+) create mode 100644 security/device_cgroup.c (limited to 'security/device_cgroup.c') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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) */ -- cgit v1.2.3