From ede7201b7303c512771bbe149ea0f2582fb90cb4 Mon Sep 17 00:00:00 2001 From: Levente Kurusa Date: Tue, 4 Sep 2018 10:02:54 +0200 Subject: [PATCH] devices: parse files in the devices subsystem Also, a test! Signed-off-by: Levente Kurusa --- src/devices.rs | 174 +++++++++++++++++++++++++++++++++++++++++------ src/lib.rs | 8 +-- tests/devices.rs | 45 ++++++++++++ 3 files changed, 202 insertions(+), 25 deletions(-) create mode 100644 tests/devices.rs diff --git a/src/devices.rs b/src/devices.rs index 4ea50a3..5aa48d8 100644 --- a/src/devices.rs +++ b/src/devices.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use std::io::{Read, Write}; -use {CgroupError, DeviceResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem}; +use {DeviceResource, CgroupError, DeviceResources, Controllers, Controller, Resources, ControllIdentifier, Subsystem}; /// A controller that allows controlling the `devices` subsystem of a Cgroup. /// @@ -17,6 +17,108 @@ pub struct DevicesController{ 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 { + All, + Char, + Block, +} + +impl Default for DeviceType { + fn default() -> Self { DeviceType::All } +} + +impl DeviceType { + /// Convert a DeviceType into the character that the kernel recognizes. + pub fn to_char(self: &Self) -> char { + match self { + DeviceType::All => 'a', + DeviceType::Char => 'c', + DeviceType::Block => 'b', + } + } + + 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. + pub fn to_char(self: &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: &String) -> bool { + if s == "" { + return false; + } + for i in s.chars() { + if i != 'r' && i != 'w' && i != 'm' { + return false; + } + } + return 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. + /// + /// NOTE: This function makes no effort in verifying the String. + pub fn from_string(s: &String) -> Vec { + let mut v = Vec::new(); + if s == "" { + return v; + } + for e in s.chars() { + v.push(DevicePermissions::from_char(e).unwrap()); + } + + v + } +} + impl Controller for DevicesController { fn control_type(self: &Self) -> Controllers { Controllers::Devices } fn get_path<'a>(self: &'a Self) -> &'a PathBuf { &self.path } @@ -29,12 +131,10 @@ impl Controller for DevicesController { if res.update_values { for i in &res.devices { - let wstr = format!("{} {}:{} {}", - i.devtype, i.major, i.minor, i.access); if i.allow { - let _ = self.allow_device(&wstr); + let _ = self.allow_device(i.devtype, i.major, i.minor, &i.access); } else { - let _ = self.deny_device(&wstr); + let _ = self.deny_device(i.devtype, i.major, i.minor, &i.access); } } } @@ -74,41 +174,73 @@ impl DevicesController { /// Allow a (possibly, set of) device(s) to be used by the tasks in the control group. /// - /// The format of `dev` is rather simple: - /// `$type $major:$minor $rwm` - /// where `$rwm` is a combination of the characters `r`, `w`, `m`, each standing for read, - /// write, mknod permissions. - /// /// Note that `dev` can be "regex"-like: both `$major` and `$minor` can be `*` which implies /// that their value does not matter. - pub fn allow_device(self: &Self, dev: &String) -> Result<(), CgroupError> { + pub fn allow_device(self: &Self, devtype: DeviceType, major: i64, minor: i64, perm: &Vec) -> Result<(), CgroupError> { + 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(dev.as_ref()).map_err(CgroupError::WriteError) + file.write_all(final_str.as_ref()).map_err(CgroupError::WriteError) }) } /// Deny the control group's tasks access to the devices covered by `dev`. /// - /// The format of `dev` is rather simple: - /// `$type $major:$minor $rwm` - /// where `$rwm` is a combination of the characters `r`, `w`, `m`, each standing for read, - /// write, mknod permissions. - /// /// Note that `dev` can be "regex"-like: both `$major` and `$minor` can be `*` which implies /// that their value does not matter. - pub fn deny_device(self: &Self, dev: &String) -> Result<(), CgroupError> { + pub fn deny_device(self: &Self, devtype: DeviceType, major: i64, minor: i64, perm: &Vec) -> Result<(), CgroupError> { + 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(dev.as_ref()).map_err(CgroupError::WriteError) + file.write_all(final_str.as_ref()).map_err(CgroupError::WriteError) }) } /// Get the current list of allowed devices. - pub fn allowed_devices(self: &Self) -> Result { + pub fn allowed_devices(self: &Self) -> Result, CgroupError> { 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(_) => Ok(s), + 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 { + println!("line 204: acc: {:?}, ls: {:?}", acc, ls); + Err(CgroupError::ParseError) + } else { + let devtype = DeviceType::from_char(ls[0].chars().nth(0)); + let mut major = ls[1].parse::(); + let mut minor = ls[2].parse::(); + if major.is_err() && ls[1] == "*".to_string() { + major = Ok(-1); + } + if minor.is_err() && ls[2] == "*".to_string() { + minor = Ok(-1); + } + if devtype.is_none() || major.is_err() || minor.is_err() || !DevicePermissions::is_valid(&ls[3]) { + println!("line 211: acc: {:?}, ls: {:?}, devtype: {:?}, major {:?} minor {:?} ls3 {:?}", + acc, ls, devtype, major, minor, &ls[3]); + Err(CgroupError::ParseError) + } else { + let access = DevicePermissions::from_string(&ls[3]); + let mut acc = acc.unwrap(); + acc.push(DeviceResource { + allow: true, + devtype: devtype.unwrap(), + major: major.unwrap(), + minor: minor.unwrap(), + access: access, + }); + Ok(acc) + } + } + }) + }, Err(e) => Err(CgroupError::ReadError(e)), } }) diff --git a/src/lib.rs b/src/lib.rs index e0600b4..ff18bbc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -309,13 +309,13 @@ pub struct DeviceResource { /// If true, access to the device is allowed, otherwise it's denied. pub allow: bool, /// `'c'` for character device, `'b'` for block device; or `'a'` for all devices. - pub devtype: String, + pub devtype: ::devices::DeviceType, /// The major number of the device. - pub major: u64, + pub major: i64, /// The minor number of the device. - pub minor: u64, + pub minor: i64, /// Sequence of `'r'`, `'w'` or `'m'`, each denoting read, write or mknod permissions. - pub access: String, + pub access: Vec<::devices::DevicePermissions>, } /// Limit the usage of devices for the control group's tasks. diff --git a/tests/devices.rs b/tests/devices.rs new file mode 100644 index 0000000..b34ea6a --- /dev/null +++ b/tests/devices.rs @@ -0,0 +1,45 @@ +//! Integration tests about the devices subsystem + +extern crate cgroups; +use cgroups::{Cgroup, DeviceResource}; +use cgroups::devices::{DevicesController, DevicePermissions, DeviceType}; + +#[test] +fn test_devices_parsing() { + let hier = cgroups::hierarchies::V1::new(); + let cg = Cgroup::new(&hier, String::from("test_devices_parsing")); + { + let devices: &DevicesController = cg.controller_of().unwrap(); + + // Deny access to all devices first + devices.deny_device(DeviceType::All, -1, -1, &vec![DevicePermissions::Read, DevicePermissions::Write, DevicePermissions::MkNod]); + // Acquire the list of allowed devices after we denied all + let allowed_devices = devices.allowed_devices(); + // Verify that there are no devices that we can access. + assert!(allowed_devices.is_ok()); + assert_eq!(allowed_devices.unwrap(), Vec::new()); + + // Now add mknod access to /dev/null device + devices.allow_device(DeviceType::Char, 1, 3, &vec![DevicePermissions::MkNod]); + let allowed_devices = devices.allowed_devices(); + assert!(allowed_devices.is_ok()); + let allowed_devices = allowed_devices.unwrap(); + assert_eq!(allowed_devices.len(), 1); + assert_eq!(allowed_devices[0], DeviceResource { + allow: true, + devtype: DeviceType::Char, + major: 1, + minor: 3, + access: vec![DevicePermissions::MkNod], + }); + + // Now deny, this device explicitly. + devices.deny_device(DeviceType::Char, 1, 3, &DevicePermissions::all()); + // Finally, check that. + let allowed_devices = devices.allowed_devices(); + // Verify that there are no devices that we can access. + assert!(allowed_devices.is_ok()); + assert_eq!(allowed_devices.unwrap(), Vec::new()); + } + cg.delete(); +}