// Copyright (c) 2018 Levente Kurusa // // SPDX-License-Identifier: Apache-2.0 or MIT // //! This module contains the implementation of the `devices` cgroup subsystem. //! //! See the Kernel's documentation for more information about this subsystem, found at: //! [Documentation/cgroup-v1/devices.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/devices.txt) use std::io::{Read, Write}; use std::path::PathBuf; use log::*; use crate::error::ErrorKind::*; use crate::error::*; use crate::{ ControllIdentifier, ControllerInternal, Controllers, DeviceResource, DeviceResources, Resources, Subsystem, }; /// A controller that allows controlling the `devices` subsystem of a Cgroup. /// /// In essence, using the devices controller, it is possible to allow or disallow sets of devices to /// be used by the control group's tasks. #[derive(Debug, Clone)] pub struct DevicesController { base: PathBuf, path: PathBuf, } /// An enum holding the different types of devices that can be manipulated using this controller. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum DeviceType { /// The rule applies to all devices. All, /// The rule only applies to character devices. Char, /// The rule only applies to block devices. Block, } impl Default for DeviceType { fn default() -> Self { DeviceType::All } } impl DeviceType { /// Convert a DeviceType into the character that the kernel recognizes. #[allow(clippy::should_implement_trait, clippy::wrong_self_convention)] pub fn to_char(&self) -> char { match self { DeviceType::All => 'a', DeviceType::Char => 'c', DeviceType::Block => 'b', } } /// Convert the kenrel's representation into the DeviceType type. pub fn from_char(c: Option) -> Option { match c { Some('a') => Some(DeviceType::All), Some('c') => Some(DeviceType::Char), Some('b') => Some(DeviceType::Block), _ => None, } } } /// An enum with the permissions that can be allowed/denied to the control group. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum DevicePermissions { /// Permission to read from the device. Read, /// Permission to write to the device. Write, /// Permission to execute the `mknod(2)` system call with the device's major and minor numbers. /// That is, the permission to create a special file that refers to the device node. MkNod, } impl DevicePermissions { /// Convert a DevicePermissions into the character that the kernel recognizes. #[allow(clippy::should_implement_trait, clippy::wrong_self_convention)] pub fn to_char(&self) -> char { match self { DevicePermissions::Read => 'r', DevicePermissions::Write => 'w', DevicePermissions::MkNod => 'm', } } /// Convert a char to a DevicePermission if there is such a mapping. pub fn from_char(c: char) -> Option { match c { 'r' => Some(DevicePermissions::Read), 'w' => Some(DevicePermissions::Write), 'm' => Some(DevicePermissions::MkNod), _ => None, } } /// Checks whether the string is a valid descriptor of DevicePermissions. pub fn is_valid(s: &str) -> bool { if s.is_empty() { return false; } for i in s.chars() { if i != 'r' && i != 'w' && i != 'm' { return false; } } true } /// Returns a Vec will all the permissions that a device can have. pub fn all() -> Vec { vec![ DevicePermissions::Read, DevicePermissions::Write, DevicePermissions::MkNod, ] } /// Convert a string into DevicePermissions. #[allow(clippy::should_implement_trait)] pub fn from_str(s: &str) -> Result> { let mut v = Vec::new(); if s.is_empty() { return Ok(v); } for e in s.chars() { let perm = DevicePermissions::from_char(e).ok_or_else(|| Error::new(ParseError))?; v.push(perm); } Ok(v) } } impl ControllerInternal for DevicesController { fn control_type(&self) -> Controllers { Controllers::Devices } fn get_path(&self) -> &PathBuf { &self.path } fn get_path_mut(&mut self) -> &mut PathBuf { &mut self.path } fn get_base(&self) -> &PathBuf { &self.base } fn apply(&self, res: &Resources) -> Result<()> { // get the resources that apply to this controller let res: &DeviceResources = &res.devices; for i in &res.devices { if i.allow { let _ = self.allow_device(i.devtype, i.major, i.minor, &i.access); } else { let _ = self.deny_device(i.devtype, i.major, i.minor, &i.access); } } Ok(()) } } impl ControllIdentifier for DevicesController { fn controller_type() -> Controllers { Controllers::Devices } } impl<'a> From<&'a Subsystem> for &'a DevicesController { fn from(sub: &'a Subsystem) -> &'a DevicesController { unsafe { match sub { Subsystem::Devices(c) => c, _ => { assert_eq!(1, 0); let v = std::mem::MaybeUninit::uninit(); v.assume_init() } } } } } impl DevicesController { /// Constructs a new `DevicesController` with `root` serving as the root of the control group. pub fn new(root: PathBuf) -> Self { Self { base: root.clone(), path: root, } } /// Allow a (possibly, set of) device(s) to be used by the tasks in the control group. /// /// When `-1` is passed as `major` or `minor`, the kernel interprets that value as "any", /// meaning that it will match any device. pub fn allow_device( &self, devtype: DeviceType, major: i64, minor: i64, perm: &[DevicePermissions], ) -> Result<()> { let perms = perm .iter() .map(DevicePermissions::to_char) .collect::(); let minor = if minor == -1 { "*".to_string() } else { format!("{}", minor) }; let major = if major == -1 { "*".to_string() } else { format!("{}", major) }; let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); self.open_path("devices.allow", true).and_then(|mut file| { file.write_all(final_str.as_ref()) .map_err(|e| Error::with_cause(WriteFailed, e)) }) } /// Deny the control group's tasks access to the devices covered by `dev`. /// /// When `-1` is passed as `major` or `minor`, the kernel interprets that value as "any", /// meaning that it will match any device. pub fn deny_device( &self, devtype: DeviceType, major: i64, minor: i64, perm: &[DevicePermissions], ) -> Result<()> { let perms = perm .iter() .map(DevicePermissions::to_char) .collect::(); let minor = if minor == -1 { "*".to_string() } else { format!("{}", minor) }; let major = if major == -1 { "*".to_string() } else { format!("{}", major) }; let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); self.open_path("devices.deny", true).and_then(|mut file| { file.write_all(final_str.as_ref()) .map_err(|e| Error::with_cause(WriteFailed, e)) }) } /// Get the current list of allowed devices. pub fn allowed_devices(&self) -> Result> { self.open_path("devices.list", false).and_then(|mut file| { let mut s = String::new(); let res = file.read_to_string(&mut s); match res { Ok(_) => { s.lines().fold(Ok(Vec::new()), |acc, line| { let ls = line.to_string().split(|c| c == ' ' || c == ':').map(|x| x.to_string()).collect::>(); if acc.is_err() || ls.len() != 4 { error!("allowed_devices: acc: {:?}, ls: {:?}", acc, ls); Err(Error::new(ParseError)) } else { let devtype = DeviceType::from_char(ls[0].chars().next()); let mut major = ls[1].parse::(); let mut minor = ls[2].parse::(); if major.is_err() && ls[1] == "*" { major = Ok(-1); } if minor.is_err() && ls[2] == "*" { minor = Ok(-1); } if devtype.is_none() || major.is_err() || minor.is_err() || !DevicePermissions::is_valid(&ls[3]) { error!("allowed_devices: acc: {:?}, ls: {:?}, devtype: {:?}, major {:?} minor {:?} ls3 {:?}", acc, ls, devtype, major, minor, &ls[3]); Err(Error::new(ParseError)) } else { let access = DevicePermissions::from_str(&ls[3])?; let mut acc = acc.unwrap(); acc.push(DeviceResource { allow: true, devtype: devtype.unwrap(), major: major.unwrap(), minor: minor.unwrap(), access, }); Ok(acc) } } }) }, Err(e) => Err(Error::with_cause(ReadFailed, e)), } }) } }