cgroups_rs/fs/
devices.rs

1// Copyright (c) 2018 Levente Kurusa
2//
3// SPDX-License-Identifier: Apache-2.0 or MIT
4//
5
6//! This module contains the implementation of the `devices` cgroup subsystem.
7//!
8//! See the Kernel's documentation for more information about this subsystem, found at:
9//!  [Documentation/cgroup-v1/devices.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/devices.txt)
10use std::io::{Read, Write};
11use std::path::PathBuf;
12
13use log::*;
14
15use crate::fs::error::ErrorKind::*;
16use crate::fs::error::*;
17
18use crate::fs::{
19    ControllIdentifier, ControllerInternal, Controllers, DeviceResource, DeviceResources,
20    Resources, Subsystem,
21};
22
23/// A controller that allows controlling the `devices` subsystem of a Cgroup.
24///
25/// In essence, using the devices controller, it is possible to allow or disallow sets of devices to
26/// be used by the control group's tasks.
27#[derive(Debug, Clone)]
28pub struct DevicesController {
29    base: PathBuf,
30    path: PathBuf,
31}
32
33/// An enum holding the different types of devices that can be manipulated using this controller.
34#[derive(Debug, Copy, Clone, PartialEq, Eq)]
35#[cfg_attr(
36    feature = "serde",
37    derive(serde::Serialize, serde::Deserialize),
38    serde(rename_all = "snake_case")
39)]
40pub enum DeviceType {
41    /// The rule applies to all devices.
42    All,
43    /// The rule only applies to character devices.
44    Char,
45    /// The rule only applies to block devices.
46    Block,
47}
48
49#[allow(clippy::derivable_impls)]
50impl Default for DeviceType {
51    fn default() -> Self {
52        DeviceType::All
53    }
54}
55
56impl DeviceType {
57    /// Convert a DeviceType into the character that the kernel recognizes.
58    #[allow(clippy::should_implement_trait, clippy::wrong_self_convention)]
59    pub fn to_char(&self) -> char {
60        match self {
61            DeviceType::All => 'a',
62            DeviceType::Char => 'c',
63            DeviceType::Block => 'b',
64        }
65    }
66
67    /// Convert the kenrel's representation into the DeviceType type.
68    pub fn from_char(c: Option<char>) -> Option<DeviceType> {
69        match c {
70            Some('a') => Some(DeviceType::All),
71            Some('c') => Some(DeviceType::Char),
72            Some('b') => Some(DeviceType::Block),
73            _ => None,
74        }
75    }
76}
77
78/// An enum with the permissions that can be allowed/denied to the control group.
79#[derive(Debug, Copy, Clone, PartialEq, Eq)]
80#[cfg_attr(
81    feature = "serde",
82    derive(serde::Serialize, serde::Deserialize),
83    serde(rename_all = "snake_case")
84)]
85pub enum DevicePermissions {
86    /// Permission to read from the device.
87    Read,
88    /// Permission to write to the device.
89    Write,
90    /// Permission to execute the `mknod(2)` system call with the device's major and minor numbers.
91    /// That is, the permission to create a special file that refers to the device node.
92    MkNod,
93}
94
95impl DevicePermissions {
96    /// Convert a DevicePermissions into the character that the kernel recognizes.
97    #[allow(clippy::should_implement_trait, clippy::wrong_self_convention)]
98    pub fn to_char(&self) -> char {
99        match self {
100            DevicePermissions::Read => 'r',
101            DevicePermissions::Write => 'w',
102            DevicePermissions::MkNod => 'm',
103        }
104    }
105
106    /// Convert a char to a DevicePermission if there is such a mapping.
107    pub fn from_char(c: char) -> Option<DevicePermissions> {
108        match c {
109            'r' => Some(DevicePermissions::Read),
110            'w' => Some(DevicePermissions::Write),
111            'm' => Some(DevicePermissions::MkNod),
112            _ => None,
113        }
114    }
115
116    /// Checks whether the string is a valid descriptor of DevicePermissions.
117    pub fn is_valid(s: &str) -> bool {
118        if s.is_empty() {
119            return false;
120        }
121        for i in s.chars() {
122            if i != 'r' && i != 'w' && i != 'm' {
123                return false;
124            }
125        }
126        true
127    }
128
129    /// Returns a Vec will all the permissions that a device can have.
130    pub fn all() -> Vec<DevicePermissions> {
131        vec![
132            DevicePermissions::Read,
133            DevicePermissions::Write,
134            DevicePermissions::MkNod,
135        ]
136    }
137
138    /// Convert a string into DevicePermissions.
139    #[allow(clippy::should_implement_trait)]
140    pub fn from_str(s: &str) -> Result<Vec<DevicePermissions>> {
141        let mut v = Vec::new();
142        if s.is_empty() {
143            return Ok(v);
144        }
145        for e in s.chars() {
146            let perm = DevicePermissions::from_char(e).ok_or_else(|| Error::new(ParseError))?;
147            v.push(perm);
148        }
149
150        Ok(v)
151    }
152}
153
154impl ControllerInternal for DevicesController {
155    fn control_type(&self) -> Controllers {
156        Controllers::Devices
157    }
158    fn get_path(&self) -> &PathBuf {
159        &self.path
160    }
161    fn get_path_mut(&mut self) -> &mut PathBuf {
162        &mut self.path
163    }
164    fn get_base(&self) -> &PathBuf {
165        &self.base
166    }
167
168    fn apply(&self, res: &Resources) -> Result<()> {
169        // get the resources that apply to this controller
170        let res: &DeviceResources = &res.devices;
171
172        for i in &res.devices {
173            if i.allow {
174                self.allow_device(i.devtype, i.major, i.minor, &i.access)?;
175            } else {
176                self.deny_device(i.devtype, i.major, i.minor, &i.access)?;
177            }
178        }
179
180        Ok(())
181    }
182}
183
184impl ControllIdentifier for DevicesController {
185    fn controller_type() -> Controllers {
186        Controllers::Devices
187    }
188}
189
190impl<'a> From<&'a Subsystem> for &'a DevicesController {
191    fn from(sub: &'a Subsystem) -> &'a DevicesController {
192        unsafe {
193            match sub {
194                Subsystem::Devices(c) => c,
195                _ => {
196                    assert_eq!(1, 0);
197                    let v = std::mem::MaybeUninit::uninit();
198                    v.assume_init()
199                }
200            }
201        }
202    }
203}
204
205impl DevicesController {
206    /// Constructs a new `DevicesController` with `root` serving as the root of the control group.
207    pub fn new(point: PathBuf, root: PathBuf) -> Self {
208        Self {
209            base: root,
210            path: point,
211        }
212    }
213
214    /// Allow a (possibly, set of) device(s) to be used by the tasks in the control group.
215    ///
216    /// When `-1` is passed as `major` or `minor`, the kernel interprets that value as "any",
217    /// meaning that it will match any device.
218    pub fn allow_device(
219        &self,
220        devtype: DeviceType,
221        major: i64,
222        minor: i64,
223        perm: &[DevicePermissions],
224    ) -> Result<()> {
225        let perms = perm
226            .iter()
227            .map(DevicePermissions::to_char)
228            .collect::<String>();
229        let minor = if minor == -1 {
230            "*".to_string()
231        } else {
232            format!("{}", minor)
233        };
234        let major = if major == -1 {
235            "*".to_string()
236        } else {
237            format!("{}", major)
238        };
239        let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
240        self.open_path("devices.allow", true).and_then(|mut file| {
241            file.write_all(final_str.as_ref()).map_err(|e| {
242                Error::with_cause(
243                    WriteFailed(
244                        self.get_path().join("devices.allow").display().to_string(),
245                        final_str,
246                    ),
247                    e,
248                )
249            })
250        })
251    }
252
253    /// Deny the control group's tasks access to the devices covered by `dev`.
254    ///
255    /// When `-1` is passed as `major` or `minor`, the kernel interprets that value as "any",
256    /// meaning that it will match any device.
257    pub fn deny_device(
258        &self,
259        devtype: DeviceType,
260        major: i64,
261        minor: i64,
262        perm: &[DevicePermissions],
263    ) -> Result<()> {
264        let perms = perm
265            .iter()
266            .map(DevicePermissions::to_char)
267            .collect::<String>();
268        let minor = if minor == -1 {
269            "*".to_string()
270        } else {
271            format!("{}", minor)
272        };
273        let major = if major == -1 {
274            "*".to_string()
275        } else {
276            format!("{}", major)
277        };
278        let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms);
279        self.open_path("devices.deny", true).and_then(|mut file| {
280            file.write_all(final_str.as_ref()).map_err(|e| {
281                Error::with_cause(
282                    WriteFailed(
283                        self.get_path().join("devices.deny").display().to_string(),
284                        final_str,
285                    ),
286                    e,
287                )
288            })
289        })
290    }
291
292    /// Get the current list of allowed devices.
293    pub fn allowed_devices(&self) -> Result<Vec<DeviceResource>> {
294        self.open_path("devices.list", false).and_then(|mut file| {
295            let mut s = String::new();
296            let res = file.read_to_string(&mut s);
297            match res {
298                Ok(_) => s
299                    .lines()
300                    .map(|line| parse_device_line(line, true))
301                    .collect(),
302                Err(e) => Err(Error::with_cause(ReadFailed("devices.list".to_string()), e)),
303            }
304        })
305    }
306}
307
308fn parse_device_number(s: &str) -> Result<i64> {
309    if s == "*" {
310        Ok(-1)
311    } else {
312        s.parse::<i64>().map_err(|_| Error::new(ParseError))
313    }
314}
315
316fn parse_device_line(line: &str, allow: bool) -> Result<DeviceResource> {
317    let parts: Vec<&str> = line.split([' ', ':']).collect();
318    if parts.len() != 4 {
319        error!("allowed_devices: invalid line format: {:?}", line);
320        return Err(Error::new(ParseError));
321    }
322
323    let devtype = DeviceType::from_char(parts[0].chars().next()).ok_or_else(|| {
324        error!("allowed_devices: invalid device type: {:?}", parts[0]);
325        Error::new(ParseError)
326    })?;
327    let major = parse_device_number(parts[1]).inspect_err(|_| {
328        error!("allowed_devices: invalid major number: {:?}", parts[1]);
329    })?;
330    let minor = parse_device_number(parts[2]).inspect_err(|_| {
331        error!("allowed_devices: invalid minor number: {:?}", parts[2]);
332    })?;
333    let access = DevicePermissions::from_str(parts[3])?;
334
335    Ok(DeviceResource {
336        allow,
337        devtype,
338        major,
339        minor,
340        access,
341    })
342}